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 ### Props
| Prop name | Required | Kind | Reactive | Type | Default value | Description | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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?: | | 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. |
| 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 |
| 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. | | type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of multiselect |
| 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. | | direction | No | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the multiselect dropdown menu |
| size | No | <code>let</code> | No | <code>"sm" &#124; "lg" &#124; "xl"</code> | <code>undefined</code> | Set the size of the combobox | | 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 |
| type | No | <code>let</code> | No | <code>"default" &#124; "inline"</code> | <code>"default"</code> | Specify the type of multiselect | | disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown |
| direction | No | <code>let</code> | No | <code>"bottom" &#124; "top"</code> | <code>"bottom"</code> | Specify the direction of the multiselect dropdown menu | | filterable | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to filter items |
| 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 | | 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 |
| disabled | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to disable the dropdown | | light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant |
| filterable | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to filter items | | locale | No | <code>let</code> | No | <code>string</code> | <code>"en"</code> | Specify the locale |
| 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 | | placeholder | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text |
| light | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to enable the light variant | | 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 |
| locale | No | <code>let</code> | No | <code>string</code> | <code>"en"</code> | Specify the locale | | 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 |
| placeholder | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the placeholder text | | 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 |
| 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 | | titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text |
| 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 | | useTitleInItem | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to pass the item to `itemToString` in the checkbox |
| 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 | | invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state |
| titleText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the title text | | invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state 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 | | warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state |
| invalid | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an invalid state | | warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text |
| invalidText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the invalid state text | | helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper text |
| warn | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to indicate an warning state | | label | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the list box label |
| warnText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the warning state text | | hideLabel | No | <code>let</code> | No | <code>boolean</code> | <code>false</code> | Set to `true` to visually hide the label text |
| helperText | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the helper 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 |
| label | No | <code>let</code> | No | <code>string</code> | <code>""</code> | Specify the list box label | | name | No | <code>let</code> | No | <code>string</code> | <code>undefined</code> | Specify a name attribute for the select |
| 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 ### Slots

View file

@ -7007,7 +7007,7 @@
"name": "itemToInput", "name": "itemToInput",
"kind": "let", "kind": "let",
"description": "Override the item name, title, labelText, or value passed to the user-selectable checkbox input as well as the hidden inputs.", "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) => {}", "value": "(item) => {}",
"isFunction": true, "isFunction": true,
"isFunctionDeclaration": false, "isFunctionDeclaration": false,
@ -7015,30 +7015,6 @@
"constant": false, "constant": false,
"reactive": 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", "name": "selectedIds",
"kind": "let", "kind": "let",

View file

@ -14,10 +14,9 @@
By default, items will be ordered alphabetically based on the `item.text` value. By default, items will be ordered alphabetically based on the `item.text` value.
To prevent this, see [#no-alphabetical-ordering](#no-alphabetical-ordering). To prevent this, see [#no-alphabetical-ordering](#no-alphabetical-ordering).
Hidden inputs will be rendered based on user selection, such as `<input type="hidden" MultiSelect provides interactivity for a list of checkbox inputs. Those
name="0" value="true" />` to mirror checkbox values and to allow MultiSelect to checkboxes will remain rendered in the DOM and are submittable within forms.
be submittable within forms. These hidden inputs can be customized with Checkbox attributes can be adjusted via the `itemToInput` prop.
the `combineValues` or `itemToInput` props.
<MultiSelect titleText="Contact" label="Select contact methods..." <MultiSelect titleText="Contact" label="Select contact methods..."
items="{[{id: "0", text: "Slack"}, 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 `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. 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 <MultiSelect
itemToInput={(item) => ({name: 'contact', value: item.id})} 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" titleText="Contact"
label="Select contact methods..." label="Select contact methods..."
items="{[ 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. * 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?: * @type {(item: MultiSelectItem) => { name?: string; labelText?: any; title?: string; value?: string }}
* string; value?: string }}
*/ */
export let itemToInput = (item) => {}; 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 * Set the selected ids
* @type {ReadonlyArray<MultiSelectItemId>} * @type {ReadonlyArray<MultiSelectItemId>}
@ -498,7 +483,7 @@
<ListBoxMenuIcon open="{open}" translateWithId="{translateWithId}" /> <ListBoxMenuIcon open="{open}" translateWithId="{translateWithId}" />
{/if} {/if}
</ListBoxField> </ListBoxField>
{#if open} <div style:display="{open ? "block" : "none"}">
<ListBoxMenu <ListBoxMenu
aria-label="{ariaLabel}" aria-label="{ariaLabel}"
id="{id}" id="{id}"
@ -548,17 +533,7 @@
</ListBoxMenuItem> </ListBoxMenuItem>
{/each} {/each}
</ListBoxMenu> </ListBoxMenu>
{:else} </div>
{#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}
</ListBox> </ListBox>
{#if !inline && !invalid && !warn && helperText} {#if !inline && !invalid && !warn && helperText}
<div <div

View file

@ -37,20 +37,6 @@ export interface MultiSelectProps extends RestProps {
value?: string; 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 * Set the selected ids
* @default [] * @default []