chore(multi-select): keep checkboxes rendered in DOM

Display ListBox via CSS
This commit is contained in:
Enrico Sacchetti 2023-10-26 16:53:21 -04:00
commit 43ac7a811f
5 changed files with 44 additions and 133 deletions

View file

@ -2355,46 +2355,43 @@ export interface MultiSelectItem {
### Props
| Prop name | Required | Kind | Reactive | Type | Default value | Description |
| :------------------------------ | :------------------------ | :---------------------------------------------------------------------------------------------------------------------------- | :------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| highlightedId | No | <code>let</code> | Yes | <code>null &#124; MultiSelectItemId</code> | <code>null</code> | Id of the highlighted ListBoxMenuItem |
| selectionRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the selection element |
| fieldRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the field box element |
| multiSelectRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the outer div element |
| inputRef | No | <code>let</code> | Yes | <code>null &#124; HTMLInputElement</code> | <code>null</code> | Obtain a reference to the input HTML element |
| open | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the dropdown |
| value | No | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the multiselect value |
| selectedIds | No | <code>let</code> | Yes | <code>ReadonlyArray<MultiSelectItemId></code> | <code>[]</code> | Set the selected ids |
| items | No | <code>let</code> | Yes | <code>ReadonlyArray<MultiSelectItem></code> | <code>[]</code> | Set the multiselect items |
| itemToString | No | <code>let</code> | No | <code>(item: MultiSelectItem) => any</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a multiselect item |
| itemToInput | No | <code>let</code> | No | <code>(item: MultiSelectItem) => { name?: string; labelText?: any; title?: |
| string; value?: string }</code> | <code>(item) => {}</code> | Override the item name, title, labelText, or value passed to the user-selectable checkbox input as well as the hidden inputs. |
| selectedOnly | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to only render selected options as hidden inputs for form submission. |
| combineValues | No | <code>let</code> | No | <code>false &#124; true &#124; string</code> | <code>false</code> | Combine selected items as comma-separated values when submitted in a form.<br />If set to `true`, the default separator is a comma `,`.<br />Pass in a string to override the separator. |
| size | No | <code>let</code> | No | <code>"sm" &#124; "lg" &#124; "xl"</code> | <code>undefined</code> | Set the size of the combobox |
| type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of multiselect |
| direction | No | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the multiselect dropdown menu |
| selectionFeedback | No | <code>let</code> | No | <code>"top" &#124; "fixed" &#124; "top-after-reopen"</code> | <code>"top-after-reopen"</code> | Specify the selection feedback after selecting items |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown |
| filterable | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to filter items |
| filterItem | No | <code>let</code> | No | <code>(item: MultiSelectItem, value: string) => string</code> | <code>(item, value) => item.text.toLowerCase().includes(value.trim().toLowerCase())</code> | Override the filtering logic<br />The default filtering is an exact string comparison |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| locale | No | <code>let</code> | No | <code>string</code> | <code>"en"</code> | Specify the locale |
| placeholder | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text |
| sortItem | No | <code>let</code> | No | <code>((a: MultiSelectItem, b: MultiSelectItem) => MultiSelectItem) &#124; (() => void)</code> | <code>(a, b) => a.text.localeCompare(b.text, locale, { numeric: true })</code> | Override the sorting logic<br />The default sorting compare the item text value |
| translateWithId | No | <code>let</code> | No | <code>(id: import("../ListBox/ListBoxMenuIcon.svelte").ListBoxMenuIconTranslationId) => string</code> | <code>undefined</code> | Override the chevron icon label based on the open state.<br />Defaults to "Open menu" when closed and "Close menu" when open |
| translateWithIdSelection | No | <code>let</code> | No | <code>(id: import("../ListBox/ListBoxSelection.svelte").ListBoxSelectionTranslationId) => string</code> | <code>undefined</code> | Override the label of the clear button when the input has a selection.<br />Defaults to "Clear selected item" and "Clear all items" if more than one item is selected |
| titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text |
| useTitleInItem | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to pass the item to `itemToString` in the checkbox |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| label | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the list box label |
| hideLabel | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to visually hide the label text |
| id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component |
| name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the select |
| Prop name | Required | Kind | Reactive | Type | Default value | Description |
| :----------------------- | :------- | :--------------- | :------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| highlightedId | No | <code>let</code> | Yes | <code>null &#124; MultiSelectItemId</code> | <code>null</code> | Id of the highlighted ListBoxMenuItem |
| selectionRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the selection element |
| fieldRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the field box element |
| multiSelectRef | No | <code>let</code> | Yes | <code>null &#124; HTMLDivElement</code> | <code>null</code> | Obtain a reference to the outer div element |
| inputRef | No | <code>let</code> | Yes | <code>null &#124; HTMLInputElement</code> | <code>null</code> | Obtain a reference to the input HTML element |
| open | No | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the dropdown |
| value | No | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the multiselect value |
| selectedIds | No | <code>let</code> | Yes | <code>ReadonlyArray<MultiSelectItemId></code> | <code>[]</code> | Set the selected ids |
| items | No | <code>let</code> | Yes | <code>ReadonlyArray<MultiSelectItem></code> | <code>[]</code> | Set the multiselect items |
| itemToString | No | <code>let</code> | No | <code>(item: MultiSelectItem) => any</code> | <code>(item) => item.text &#124;&#124; item.id</code> | Override the display of a multiselect item |
| itemToInput | No | <code>let</code> | No | <code>(item: MultiSelectItem) => { name?: string; labelText?: any; title?: string; value?: string }</code> | <code>(item) => {}</code> | Override the item name, title, labelText, or value passed to the user-selectable checkbox input as well as the hidden inputs. |
| size | No | <code>let</code> | No | <code>"sm" &#124; "lg" &#124; "xl"</code> | <code>undefined</code> | Set the size of the combobox |
| type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of multiselect |
| direction | No | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the multiselect dropdown menu |
| selectionFeedback | No | <code>let</code> | No | <code>"top" &#124; "fixed" &#124; "top-after-reopen"</code> | <code>"top-after-reopen"</code> | Specify the selection feedback after selecting items |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown |
| filterable | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to filter items |
| filterItem | No | <code>let</code> | No | <code>(item: MultiSelectItem, value: string) => string</code> | <code>(item, value) => item.text.toLowerCase().includes(value.trim().toLowerCase())</code> | Override the filtering logic<br />The default filtering is an exact string comparison |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| locale | No | <code>let</code> | No | <code>string</code> | <code>"en"</code> | Specify the locale |
| placeholder | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text |
| sortItem | No | <code>let</code> | No | <code>((a: MultiSelectItem, b: MultiSelectItem) => MultiSelectItem) &#124; (() => void)</code> | <code>(a, b) => a.text.localeCompare(b.text, locale, { numeric: true })</code> | Override the sorting logic<br />The default sorting compare the item text value |
| translateWithId | No | <code>let</code> | No | <code>(id: import("../ListBox/ListBoxMenuIcon.svelte").ListBoxMenuIconTranslationId) => string</code> | <code>undefined</code> | Override the chevron icon label based on the open state.<br />Defaults to "Open menu" when closed and "Close menu" when open |
| translateWithIdSelection | No | <code>let</code> | No | <code>(id: import("../ListBox/ListBoxSelection.svelte").ListBoxSelectionTranslationId) => string</code> | <code>undefined</code> | Override the label of the clear button when the input has a selection.<br />Defaults to "Clear selected item" and "Clear all items" if more than one item is selected |
| titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text |
| useTitleInItem | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to pass the item to `itemToString` in the checkbox |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| label | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the list box label |
| hideLabel | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to visually hide the label text |
| id | No | <code>let</code> | No | <code>string</code> | <code>"ccs-" + Math.random().toString(36)</code> | Set an id for the list box component |
| name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the select |
### Slots

View file

@ -7007,7 +7007,7 @@
"name": "itemToInput",
"kind": "let",
"description": "Override the item name, title, labelText, or value passed to the user-selectable checkbox input as well as the hidden inputs.",
"type": "(item: MultiSelectItem) => { name?: string; labelText?: any; title?:\nstring; value?: string }",
"type": "(item: MultiSelectItem) => { name?: string; labelText?: any; title?: string; value?: string }",
"value": "(item) => {}",
"isFunction": true,
"isFunctionDeclaration": false,
@ -7015,30 +7015,6 @@
"constant": false,
"reactive": false
},
{
"name": "selectedOnly",
"kind": "let",
"description": "Set to `true` to only render selected options as hidden inputs for form submission.",
"type": "boolean",
"value": "false",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "combineValues",
"kind": "let",
"description": "Combine selected items as comma-separated values when submitted in a form.\nIf set to `true`, the default separator is a comma `,`.\nPass in a string to override the separator.",
"type": "false | true | string",
"value": "false",
"isFunction": false,
"isFunctionDeclaration": false,
"isRequired": false,
"constant": false,
"reactive": false
},
{
"name": "selectedIds",
"kind": "let",

View file

@ -14,10 +14,9 @@
By default, items will be ordered alphabetically based on the `item.text` value.
To prevent this, see [#no-alphabetical-ordering](#no-alphabetical-ordering).
Hidden inputs will be rendered based on user selection, such as `<input type="hidden"
name="0" value="true" />` to mirror checkbox values and to allow MultiSelect to
be submittable within forms. These hidden inputs can be customized with
the `combineValues` or `itemToInput` props.
MultiSelect provides interactivity for a list of checkbox inputs. Those
checkboxes will remain rendered in the DOM and are submittable within forms.
Checkbox attributes can be adjusted via the `itemToInput` prop.
<MultiSelect titleText="Contact" label="Select contact methods..."
items="{[{id: "0", text: "Slack"},
@ -96,30 +95,8 @@ The above function sets the `name` attribute to
`Contact_0` (respective to each item's `id`) for every hidden input that
renders, along with each respective item's `id` set to the `value` attribute.
When using with the `selectedOnly` prop, you can override every hidden input to
use the same `name` attribute. This may be useful if you
wish to use `formData.getAll('contact')` in your server-side form handler.
<MultiSelect
itemToInput={(item) => ({name: 'contact', value: item.id})}
selectedOnly
titleText="Contact"
label="Select contact methods..."
items="{[
{id: "0", text: "Slack"},
{id: "1", text: "Email"},
{id: "2", text: "Fax"}
]}"
/>
## Combine values into a single input
With the `combineValues` prop, all selected items' values will render as a
comma-separated string within a single hidden input. A custom delimiter can
alternatively be passed in.
<MultiSelect
combineValues
titleText="Contact"
label="Select contact methods..."
items="{[

View file

@ -23,25 +23,10 @@
/**
* Override the item name, title, labelText, or value passed to the user-selectable checkbox input as well as the hidden inputs.
* @type {(item: MultiSelectItem) => { name?: string; labelText?: any; title?:
* string; value?: string }}
* @type {(item: MultiSelectItem) => { name?: string; labelText?: any; title?: string; value?: string }}
*/
export let itemToInput = (item) => {};
/**
* Set to `true` to only render selected options as hidden inputs for form submission.
* @type {boolean}
*/
export let selectedOnly = false
/**
* Combine selected items as comma-separated values when submitted in a form.
* If set to `true`, the default separator is a comma `,`.
* Pass in a string to override the separator.
* @type {false | true | string}
*/
export let combineValues = false
/**
* Set the selected ids
* @type {ReadonlyArray<MultiSelectItemId>}
@ -498,7 +483,7 @@
<ListBoxMenuIcon open="{open}" translateWithId="{translateWithId}" />
{/if}
</ListBoxField>
{#if open}
<div style:display="{open ? "block" : "none"}">
<ListBoxMenu
aria-label="{ariaLabel}"
id="{id}"
@ -548,17 +533,7 @@
</ListBoxMenuItem>
{/each}
</ListBoxMenu>
{:else}
{#if combineValues}
{@const items = checked.map(el => el.id).join(typeof combineValues ===
'string' ? combineValues : ',')}
<input type="hidden" name={titleText} value="{items}">
{:else}
{#each selectedOnly ? checked : sortedItems as item (item.id)}
<input type="hidden" name="{item.id}" value={item.checked} {...itemToInput(item)} />
{/each}
{/if}
{/if}
</div>
</ListBox>
{#if !inline && !invalid && !warn && helperText}
<div

View file

@ -37,20 +37,6 @@ export interface MultiSelectProps extends RestProps {
value?: string;
};
/**
* Set to `true` to only render selected options as hidden inputs for form submission.
* @default false
*/
selectedOnly?: boolean;
/**
* Combine selected items as comma-separated values when submitted in a form.
* If set to `true`, the default separator is a comma `,`.
* Pass in a string to override the separator.
* @default false
*/
combineValues?: false | true | string;
/**
* Set the selected ids
* @default []