svelte-multiselect
Advanced tools
+29
-29
@@ -19,2 +19,7 @@ <script >import { createEventDispatcher, onMount } from 'svelte'; | ||
| export let activeOption = null; | ||
| export let filterFunc = (op, searchText) => { | ||
| if (!searchText) | ||
| return true; | ||
| return `${op.label}`.toLowerCase().includes(searchText.toLowerCase()); | ||
| }; | ||
| export let outerDivClass = ``; | ||
@@ -70,7 +75,3 @@ export let ulSelectedClass = ``; | ||
| // options matching the current search text | ||
| $: matchingOptions = _options.filter((op) => { | ||
| if (!searchText) | ||
| return true; | ||
| return `${op.label}`.toLowerCase().includes(searchText.toLowerCase()); | ||
| }); | ||
| $: matchingOptions = _options.filter((op) => filterFunc(op, searchText)); | ||
| $: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled); | ||
@@ -299,6 +300,2 @@ $: if ( | ||
| margin: 1em 0; | ||
| border: var(--sms-border, 1pt solid lightgray); | ||
| border-radius: var(--sms-border-radius, 5pt); | ||
| background: var(--sms-input-bg); | ||
| height: var(--sms-input-height, 2em); | ||
| align-items: center; | ||
@@ -309,2 +306,6 @@ min-height: 18pt; | ||
| padding: 0 3pt; | ||
| border: var(--sms-border, 1pt solid lightgray); | ||
| border-radius: var(--sms-border-radius, 5pt); | ||
| background: var(--sms-input-bg); | ||
| height: var(--sms-input-height, 2em); | ||
| } | ||
@@ -321,3 +322,3 @@ :where(div.multiselect.open) { | ||
| :where(ul.selected) { | ||
| :where(div.multiselect > ul.selected) { | ||
| display: flex; | ||
@@ -328,4 +329,3 @@ padding: 0; | ||
| } | ||
| :where(ul.selected > li) { | ||
| background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue)); | ||
| :where(div.multiselect > ul.selected > li) { | ||
| align-items: center; | ||
@@ -335,8 +335,9 @@ border-radius: 4pt; | ||
| margin: 2pt; | ||
| padding: 0 0 0 1ex; | ||
| padding: 0 0 0 5pt; | ||
| transition: 0.3s; | ||
| white-space: nowrap; | ||
| height: 16pt; | ||
| background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue)); | ||
| height: var(--sms-selected-li-height); | ||
| } | ||
| :where(ul.selected > li button, button.remove-all) { | ||
| :where(div.multiselect > ul.selected > li button, button.remove-all) { | ||
| align-items: center; | ||
@@ -348,3 +349,3 @@ border-radius: 50%; | ||
| } | ||
| :where(button) { | ||
| :where(div.multiselect button) { | ||
| color: inherit; | ||
@@ -360,3 +361,3 @@ background: transparent; | ||
| } | ||
| :where(button:focus) { | ||
| :where(div.multiselect > button:focus) { | ||
| transform: scale(1.04); | ||
@@ -369,3 +370,2 @@ } | ||
| background: none; | ||
| color: var(--sms-text-color, inherit); | ||
| flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */ | ||
@@ -376,5 +376,6 @@ min-width: 2em; | ||
| font-size: calc(16px + 0.1vw); | ||
| color: var(--sms-text-color, inherit); | ||
| } | ||
| :where(ul.options) { | ||
| :where(div.multiselect > ul.options) { | ||
| list-style: none; | ||
@@ -391,6 +392,6 @@ max-height: 50vh; | ||
| } | ||
| :where(ul.options.hidden) { | ||
| :where(div.multiselect > ul.options.hidden) { | ||
| visibility: hidden; | ||
| } | ||
| :where(ul.options li) { | ||
| :where(div.multiselect > ul.options > li) { | ||
| padding: 3pt 2ex; | ||
@@ -400,6 +401,6 @@ cursor: pointer; | ||
| /* for noOptionsMsg */ | ||
| :where(ul.options span) { | ||
| :where(div.multiselect > ul.options span) { | ||
| padding: 3pt 2ex; | ||
| } | ||
| :where(ul.options li.selected) { | ||
| :where(div.multiselect > ul.options > li.selected) { | ||
| border-left: var( | ||
@@ -412,3 +413,3 @@ --sms-li-selected-border-left, | ||
| } | ||
| :where(ul.options li:not(.selected):hover) { | ||
| :where(div.multiselect > ul.options > li:not(.selected):hover) { | ||
| border-left: var( | ||
@@ -418,15 +419,14 @@ --sms-li-not-selected-hover-border-left, | ||
| ); | ||
| border-left: 3pt solid var(--blue); | ||
| } | ||
| :where(ul.options li.active) { | ||
| :where(div.multiselect > ul.options > li.active) { | ||
| background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue)); | ||
| } | ||
| :where(ul.options li.disabled) { | ||
| :where(div.multiselect > ul.options > li.disabled) { | ||
| cursor: not-allowed; | ||
| background: var(--sms-li-disabled-bg, #f5f5f6); | ||
| color: var(--sms-li-disabled-text, #b8b8b8); | ||
| cursor: not-allowed; | ||
| } | ||
| :where(ul.options li.disabled:hover) { | ||
| :where(div.multiselect > ul.options > li.disabled:hover) { | ||
| border-left: unset; | ||
| } | ||
| </style> |
@@ -18,2 +18,3 @@ import { SvelteComponentTyped } from "svelte"; | ||
| activeOption?: Option | null | undefined; | ||
| filterFunc?: ((op: Option, searchText: string) => boolean) | undefined; | ||
| outerDivClass?: string | undefined; | ||
@@ -20,0 +21,0 @@ ulSelectedClass?: string | undefined; |
+12
-12
@@ -8,3 +8,3 @@ { | ||
| "license": "MIT", | ||
| "version": "3.2.1", | ||
| "version": "3.2.2", | ||
| "type": "module", | ||
@@ -14,7 +14,7 @@ "svelte": "index.js", | ||
| "devDependencies": { | ||
| "@sveltejs/adapter-static": "^1.0.0-next.26", | ||
| "@sveltejs/kit": "^1.0.0-next.259", | ||
| "@typescript-eslint/eslint-plugin": "^5.10.2", | ||
| "@typescript-eslint/parser": "^5.10.2", | ||
| "eslint": "^8.8.0", | ||
| "@sveltejs/adapter-static": "^1.0.0-next.28", | ||
| "@sveltejs/kit": "^1.0.0-next.269", | ||
| "@typescript-eslint/eslint-plugin": "^5.12.0", | ||
| "@typescript-eslint/parser": "^5.12.0", | ||
| "eslint": "^8.9.0", | ||
| "eslint-plugin-svelte3": "^3.4.0", | ||
@@ -27,11 +27,11 @@ "hastscript": "^7.0.2", | ||
| "rehype-slug": "^5.0.1", | ||
| "svelte": "^3.46.3", | ||
| "svelte-check": "^2.4.2", | ||
| "svelte": "^3.46.4", | ||
| "svelte-check": "^2.4.3", | ||
| "svelte-github-corner": "^0.1.0", | ||
| "svelte-preprocess": "^4.10.2", | ||
| "svelte-toc": "^0.2.3", | ||
| "svelte2tsx": "^0.5.2", | ||
| "svelte-preprocess": "^4.10.3", | ||
| "svelte-toc": "^0.2.5", | ||
| "svelte2tsx": "^0.5.3", | ||
| "tslib": "^2.3.1", | ||
| "typescript": "^4.5.5", | ||
| "vite": "^2.7.13" | ||
| "vite": "^2.8.2" | ||
| }, | ||
@@ -38,0 +38,0 @@ "keywords": [ |
+78
-49
@@ -25,3 +25,3 @@ <h1 align="center"> | ||
| ## Key Features | ||
| ## Key features | ||
@@ -92,19 +92,35 @@ - **Single / multiple select:** pass `maxSelect={1}` prop to only allow one selection | ||
| <!-- prettier-ignore --> | ||
| | name | default | description | | ||
| | :--------------- | :--------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. | | ||
| | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. | | ||
| | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. | | ||
| | `maxSelectMsg` | ``(current: number, max: number) => `${current}/${max}` `` | Function that returns a string informing the user how many of the maximum allowed options they have currently selected. Return empty string to disable, i.e. `() => ''`. | | ||
| | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. | | ||
| | `selectedLabels` | `[]` | Labels of currently selected options. | | ||
| | `selectedValues` | `[]` | Values of currently selected options. | | ||
| | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. | | ||
| | `placeholder` | `undefined` | String shown in the text input when no option is selected. | | ||
| | `input` | `undefined` | Handle to the `<input>` DOM node. | | ||
| | `id` | `undefined` | Applied to the `<input>` element for associating HTML form `<label>`s with this component for accessibility. Also, clicking a `<label>` with same `for` attribute as `id` will focus this component. | | ||
| | `name` | `id` | Applied to the `<input>` element. If not provided, will be set to the value of `id`. Sets the key of this field in a submitted form data object. Not useful at the moment since the value is stored in Svelte state, not on the `<input>`. | | ||
| | name | default | description | | ||
| | :--------------- | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. | | ||
| | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. | | ||
| | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. | | ||
| | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. | | ||
| | `selectedLabels` | `[]` | Labels of currently selected options. | | ||
| | `selectedValues` | `[]` | Values of currently selected options. | | ||
| | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. | | ||
| | `placeholder` | `undefined` | String shown in the text input when no option is selected. | | ||
| | `input` | `undefined` | Handle to the `<input>` DOM node. | | ||
| | `id` | `undefined` | Applied to the `<input>` element for associating HTML form `<label>`s with this component for accessibility. Also, clicking a `<label>` with same `for` attribute as `id` will focus this component. | | ||
| | `name` | `id` | Applied to the `<input>` element. If not provided, will be set to the value of `id`. Sets the key of this field in a submitted form data object. Not useful at the moment since the value is stored in Svelte state, not on the `<input>`. | | ||
| </div> | ||
| ## Exposed methods | ||
| 1. `filterFunc = (op: Option, searchText: string) => boolean`: Determine what options are shown when user enters search string to filter dropdown list. Defaults to: | ||
| ```ts | ||
| filterFunc = (op: Option, searchText: string) => { | ||
| if (!searchText) return true | ||
| return `${op.label}`.toLowerCase().includes(searchText.toLowerCase()) | ||
| } | ||
| ``` | ||
| 2. `maxSelectMsg = (current: number, max: number) => string`: Inform the user how many of the maximum allowed options they have currently selected. Return empty string to disable, i.e. `() => ''`. Is automatically disabled when `maxSelect === null`. Defaults to: | ||
| ```ts | ||
| maxSelectMsg = (current: number, max: number) => `${current}/${max}` | ||
| ``` | ||
| ## Slots | ||
@@ -199,26 +215,32 @@ | ||
| - `div.multiselect`: | ||
| - `div.multiselect` | ||
| - `border: var(--sms-border, 1pt solid lightgray)`: Change this to e.g. to `1px solid red` to indicate this form field is in an invalid state. | ||
| - `border-radius: var(--sms-border-radius, 5pt)`: Input border radius. | ||
| - `background: var(--sms-input-bg)`: Input background. | ||
| - `height: var(--sms-input-height, 2em)`: Input height. | ||
| - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when focused. Falls back to `--sms-active-color` if not set which in turn falls back on `cornflowerblue`. | ||
| - `border-radius: var(--sms-border-radius, 5pt)` | ||
| - `background: var(--sms-input-bg)` | ||
| - `height: var(--sms-input-height, 2em)` | ||
| - `div.multiselect.open` | ||
| - `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements. | ||
| - `div.multiselect:focus-within` | ||
| - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` if not set which defaults to `cornflowerblue`. | ||
| - `div.multiselect.readonly` | ||
| - `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state. | ||
| - `div.multiselect.open`: | ||
| - `z-index: var(--sms-open-z-index, 4)`: Useful to ensure the dropdown list of options is displayed on top of other page elements of increased `z-index`. | ||
| - `div.multiselect > input` | ||
| - `color: var(--sms-text-color, inherit)`: Input text color. | ||
| - `ul.selected > li`: | ||
| - `div.multiselect > ul.selected > li` | ||
| - `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options. | ||
| - `ul.selected > li button:hover, button.remove-all:hover` | ||
| - `height: var(--sms-selected-li-height)`: Height of selected options. | ||
| - `ul.selected > li button:hover, button.remove-all:hover, button:focus` | ||
| - `color: var(--sms-remove-x-hover-focus-color, lightskyblue)`: Color of the cross-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state. | ||
| - `ul.options` | ||
| - `background: var(--sms-options-bg, white)`: Background of options list. | ||
| - `background: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior). | ||
| - `ul.options > li.selected` | ||
| - `div.multiselect > ul.options` | ||
| - `background: var(--sms-options-bg, white)`: Background of dropdown list. | ||
| - `overscroll-behavior: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior). | ||
| - `div.multiselect > ul.options > li.selected` | ||
| - `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))` | ||
| - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane. | ||
| - `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane. | ||
| - `ul.options > li.active` | ||
| - `div.multiselect > ul.options > li:not(.selected):hover` | ||
| - `border-left: var(--sms-li-not-selected-hover-border-left, 3pt solid var(--sms-active-color, cornflowerblue))` | ||
| - `div.multiselect > ul.options > li.active` | ||
| - `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item. | ||
| - `ul.options > li.disabled` | ||
| - `div.multiselect > ul.options > li.disabled` | ||
| - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list. | ||
@@ -263,36 +285,43 @@ - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list. | ||
| ```css | ||
| :global(.multiselect) { | ||
| :global(div.multiselect) { | ||
| /* top-level wrapper div */ | ||
| } | ||
| :global(.multiselect ul.selected > li) { | ||
| /* selected options */ | ||
| :global(div.multiselect.open) { | ||
| /* top-level wrapper div when dropdown open */ | ||
| } | ||
| :global(.multiselect ul.selected > li button), | ||
| :global(.multiselect button.remove-all) { | ||
| :global(div.multiselect.readonly) { | ||
| /* top-level wrapper div when in readonly state */ | ||
| } | ||
| :global(div.multiselect > ul.selected) { | ||
| /* selected list */ | ||
| } | ||
| :global(div.multiselect > ul.selected > li) { | ||
| /* selected list items */ | ||
| } | ||
| :global(div.multiselect button) { | ||
| /* target all buttons in this component */ | ||
| } | ||
| :global(div.multiselect > ul.selected > li button, button.remove-all) { | ||
| /* buttons to remove a single or all selected options at once */ | ||
| } | ||
| :global(.multiselect ul.options) { | ||
| :global(div.multiselect > input) { | ||
| /* input inside the top-level wrapper div */ | ||
| } | ||
| :global(div.multiselect > ul.options) { | ||
| /* dropdown options */ | ||
| } | ||
| :global(.multiselect ul.options li) { | ||
| /* dropdown list of available options */ | ||
| :global(div.multiselect > ul.options > li) { | ||
| /* dropdown list items */ | ||
| } | ||
| :global(.multiselect ul.options li.selected) { | ||
| :global(div.multiselect > ul.options > li.selected) { | ||
| /* selected options in the dropdown list */ | ||
| } | ||
| :global(.multiselect ul.options li:not(.selected):hover) { | ||
| :global(div.multiselect > ul.options > li:not(.selected):hover) { | ||
| /* unselected but hovered options in the dropdown list */ | ||
| } | ||
| :global(.multiselect ul.options li.selected:hover) { | ||
| /* selected and hovered options in the dropdown list */ | ||
| /* probably not necessary to style this state in most cases */ | ||
| } | ||
| :global(.multiselect ul.options li.active) { | ||
| :global(div.multiselect > ul.options > li.active) { | ||
| /* active means item was navigated to with up/down arrow keys */ | ||
| /* ready to be selected by pressing enter */ | ||
| } | ||
| :global(.multiselect ul.options li.selected.active) { | ||
| /* both active and already selected, pressing enter now will deselect the item */ | ||
| } | ||
| :global(.multiselect ul.options li.disabled) { | ||
| :global(div.multiselect > ul.options > li.disabled) { | ||
| /* options with disabled key set to true (see props above) */ | ||
@@ -299,0 +328,0 @@ } |
41322
1.89%169
0.6%337
9.42%