svelte-multiselect 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,8 +6,10 @@ import Wiggle from './Wiggle.svelte';
6
6
  export let selected = [];
7
7
  export let selectedLabels = [];
8
8
  export let selectedValues = [];
9
+ export let searchText = ``;
10
+ export let showOptions = false;
9
11
  export let maxSelect = null; // null means any number of options are selectable
10
- export let maxSelectMsg = (current, max) => `${current}/${max}`;
12
+ export let maxSelectMsg = null;
11
13
  export let readonly = false;
12
14
  export let options;
13
15
  export let input = null;
@@ -16,11 +18,17 @@ export let id = undefined;
16
18
  export let name = id;
17
19
  export let noOptionsMsg = `No matching options`;
18
20
  export let activeOption = null;
21
+ export let filterFunc = (op, searchText) => {
22
+ if (!searchText)
23
+ return true;
24
+ return `${op.label}`.toLowerCase().includes(searchText.toLowerCase());
25
+ };
19
26
  export let outerDivClass = ``;
20
27
  export let ulSelectedClass = ``;
21
28
  export let liSelectedClass = ``;
22
29
  export let ulOptionsClass = ``;
23
30
  export let liOptionClass = ``;
31
+ export let liActiveOptionClass = ``;
24
32
  export let removeBtnTitle = `Remove`;
25
33
  export let removeAllTitle = `Remove all`;
26
34
  // https://github.com/sveltejs/svelte/issues/6964
@@ -36,6 +44,7 @@ onMount(() => {
36
44
  selected = _options.filter((op) => op?.preselected);
37
45
  });
38
46
  let wiggle = false;
47
+ const dispatch = createEventDispatcher();
39
48
  function isObject(item) {
40
49
  return typeof item === `object` && !Array.isArray(item) && item !== null;
41
50
  }
@@ -62,15 +71,8 @@ $: if (new Set(labels).size !== options.length) {
62
71
  }
63
72
  $: selectedLabels = selected.map((op) => op.label);
64
73
  $: selectedValues = selected.map((op) => op.value);
65
- const dispatch = createEventDispatcher();
66
- let searchText = ``;
67
- let showOptions = false;
68
74
  // options matching the current search text
69
- $: matchingOptions = _options.filter((op) => {
70
- if (!searchText)
71
- return true;
72
- return `${op.label}`.toLowerCase().includes(searchText.toLowerCase());
73
- });
75
+ $: matchingOptions = _options.filter((op) => filterFunc(op, searchText));
74
76
  $: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
75
77
  $: if (
76
78
  // if there was an active option but it's not in the filtered list of options
@@ -81,12 +83,12 @@ $: if (
81
83
  // make the first filtered option active
82
84
  activeOption = matchingEnabledOptions[0];
83
85
  function add(label) {
84
- if (selected.length - (maxSelect ?? 0) < 1)
86
+ if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
85
87
  wiggle = true;
86
88
  if (!readonly &&
87
89
  !selectedLabels.includes(label) &&
88
90
  // for maxselect = 1 we always replace current option with new selection
89
- (maxSelect == null || maxSelect == 1 || selected.length < maxSelect)) {
91
+ (maxSelect === null || maxSelect === 1 || selected.length < maxSelect)) {
90
92
  searchText = ``; // reset search string on selection
91
93
  const option = _options.find((op) => op.label === label);
92
94
  if (!option) {
@@ -114,9 +116,6 @@ function remove(label) {
114
116
  dispatch(`change`, { option, type: `remove` });
115
117
  }
116
118
  function setOptionsVisible(show) {
117
- // nothing to do if visibility is already as intended
118
- if (readonly || show === showOptions)
119
- return;
120
119
  showOptions = show;
121
120
  if (show)
122
121
  input?.focus();
@@ -200,15 +199,15 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
200
199
  <!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
201
200
  display above those of another following shortly after it -->
202
201
  <div
203
- class="multiselect {outerDivClass}"
204
202
  class:readonly
205
- class:single={maxSelect == 1}
203
+ class:single={maxSelect === 1}
206
204
  class:open={showOptions}
205
+ class="multiselect {outerDivClass}"
207
206
  on:mouseup|stopPropagation={() => setOptionsVisible(true)}
208
207
  use:onClickOutside={() => setOptionsVisible(false)}
209
208
  use:onClickOutside={() => dispatch(`blur`)}
210
209
  >
211
- <ExpandIcon height="14pt" style="padding: 0 3pt 0 1pt;" />
210
+ <ExpandIcon style="min-width: 1em; padding: 0 1pt;" />
212
211
  <ul class="selected {ulSelectedClass}">
213
212
  {#each selected as option, idx}
214
213
  <li class={liSelectedClass}>
@@ -227,57 +226,65 @@ display above those of another following shortly after it -->
227
226
  {/if}
228
227
  </li>
229
228
  {/each}
230
- <input
231
- bind:this={input}
232
- autocomplete="off"
233
- bind:value={searchText}
234
- on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
235
- on:keydown={handleKeydown}
236
- on:focus={() => setOptionsVisible(true)}
237
- {id}
238
- {name}
239
- placeholder={selectedLabels.length ? `` : placeholder}
240
- />
229
+ <li style="display: contents;">
230
+ <input
231
+ bind:this={input}
232
+ autocomplete="off"
233
+ bind:value={searchText}
234
+ on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
235
+ on:keydown={handleKeydown}
236
+ on:focus={() => setOptionsVisible(true)}
237
+ {id}
238
+ {name}
239
+ placeholder={selectedLabels.length ? `` : placeholder}
240
+ />
241
+ </li>
241
242
  </ul>
242
243
  {#if readonly}
243
244
  <ReadOnlyIcon height="14pt" />
244
245
  {:else if selected.length > 0}
245
- {#if maxSelect !== null && maxSelect > 1}
246
+ {#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
246
247
  <Wiggle bind:wiggle angle={20}>
247
- <span style="padding: 0 3pt;">{maxSelectMsg(selected.length, maxSelect)}</span>
248
+ <span style="padding: 0 3pt;">
249
+ {maxSelectMsg?.(selected.length, maxSelect) ??
250
+ (maxSelect > 1 ? `${selected.length}/${maxSelect}` : ``)}
251
+ </span>
248
252
  </Wiggle>
249
253
  {/if}
250
- <button
251
- type="button"
252
- class="remove-all"
253
- title={removeAllTitle}
254
- on:mouseup|stopPropagation={removeAll}
255
- on:keydown={handleEnterAndSpaceKeys(removeAll)}
256
- >
257
- <CrossIcon height="14pt" />
258
- </button>
254
+ {#if maxSelect !== 1}
255
+ <button
256
+ type="button"
257
+ class="remove-all"
258
+ title={removeAllTitle}
259
+ on:mouseup|stopPropagation={removeAll}
260
+ on:keydown={handleEnterAndSpaceKeys(removeAll)}
261
+ >
262
+ <CrossIcon height="14pt" />
263
+ </button>
264
+ {/if}
259
265
  {/if}
260
266
 
261
267
  {#key showOptions}
262
268
  <ul
263
- class="options {ulOptionsClass}"
264
269
  class:hidden={!showOptions}
270
+ class="options {ulOptionsClass}"
265
271
  transition:fly|local={{ duration: 300, y: 40 }}
266
272
  >
267
273
  {#each matchingOptions as option, idx}
268
274
  {@const { label, disabled, title = null, selectedTitle } = option}
269
275
  {@const { disabledTitle = defaultDisabledTitle } = option}
276
+ {@const active = activeOption?.label === label}
270
277
  <li
271
278
  on:mouseup|preventDefault|stopPropagation
272
279
  on:mousedown|preventDefault|stopPropagation={() => {
273
280
  if (disabled) return
274
281
  isSelected(label) ? remove(label) : add(label)
275
282
  }}
283
+ title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
276
284
  class:selected={isSelected(label)}
277
- class:active={activeOption?.label === label}
285
+ class:active
278
286
  class:disabled
279
- title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
280
- class={liOptionClass}
287
+ class="{liOptionClass} {active ? liActiveOptionClass : ``}"
281
288
  >
282
289
  <slot name="renderOptions" {option} {idx}>
283
290
  {option.label}
@@ -294,15 +301,14 @@ display above those of another following shortly after it -->
294
301
  :where(div.multiselect) {
295
302
  position: relative;
296
303
  margin: 1em 0;
297
- border: var(--sms-border, 1pt solid lightgray);
298
- border-radius: var(--sms-border-radius, 5pt);
299
- background: var(--sms-input-bg);
300
- height: var(--sms-input-height, 2em);
301
304
  align-items: center;
302
- min-height: 18pt;
303
305
  display: flex;
304
306
  cursor: text;
305
307
  padding: 0 3pt;
308
+ border: var(--sms-border, 1pt solid lightgray);
309
+ border-radius: var(--sms-border-radius, 5pt);
310
+ background: var(--sms-input-bg);
311
+ min-height: var(--sms-input-min-height, 22pt);
306
312
  }
307
313
  :where(div.multiselect.open) {
308
314
  z-index: var(--sms-open-z-index, 4);
@@ -314,25 +320,33 @@ display above those of another following shortly after it -->
314
320
  background: var(--sms-readonly-bg, lightgray);
315
321
  }
316
322
 
317
- :where(ul.selected > li) {
318
- background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue));
323
+ :where(div.multiselect > ul.selected) {
324
+ display: flex;
325
+ flex: 1;
326
+ padding: 0;
327
+ margin: 0;
328
+ flex-wrap: wrap;
329
+ }
330
+ :where(div.multiselect > ul.selected > li) {
319
331
  align-items: center;
320
332
  border-radius: 4pt;
321
333
  display: flex;
322
334
  margin: 2pt;
323
- padding: 0 0 0 1ex;
335
+ line-height: normal;
336
+ padding: 1pt 2pt 1pt 5pt;
324
337
  transition: 0.3s;
325
338
  white-space: nowrap;
326
- height: 16pt;
339
+ background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue));
340
+ height: var(--sms-selected-li-height);
327
341
  }
328
- :where(ul.selected > li button, button.remove-all) {
342
+ :where(div.multiselect > ul.selected > li button, button.remove-all) {
329
343
  align-items: center;
330
344
  border-radius: 50%;
331
345
  display: flex;
332
346
  cursor: pointer;
333
347
  transition: 0.2s;
334
348
  }
335
- :where(button) {
349
+ :where(div.multiselect button) {
336
350
  color: inherit;
337
351
  background: transparent;
338
352
  border: none;
@@ -343,54 +357,48 @@ display above those of another following shortly after it -->
343
357
  :where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
344
358
  color: var(--sms-remove-x-hover-focus-color, lightskyblue);
345
359
  }
346
- :where(button:focus) {
360
+ :where(div.multiselect > button:focus) {
347
361
  transform: scale(1.04);
348
362
  }
349
363
 
350
- :where(div.multiselect input) {
364
+ :where(div.multiselect > ul.selected > li > input) {
351
365
  border: none;
352
366
  outline: none;
353
367
  background: none;
354
- color: var(--sms-text-color, inherit);
355
368
  flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
356
369
  min-width: 2em;
357
370
  /* minimum font-size > 16px ensures iOS doesn't zoom in when focusing input */
358
371
  /* https://stackoverflow.com/a/6394497 */
359
372
  font-size: calc(16px + 0.1vw);
373
+ color: var(--sms-text-color, inherit);
360
374
  }
361
375
 
362
- :where(ul.selected) {
363
- display: flex;
364
- padding: 0;
365
- margin: 0;
366
- flex-wrap: wrap;
367
- flex: 1;
368
- overscroll-behavior: none;
369
- }
370
-
371
- :where(ul.options) {
376
+ :where(div.multiselect > ul.options) {
372
377
  list-style: none;
373
378
  max-height: 50vh;
374
379
  padding: 0;
375
380
  top: 100%;
381
+ left: 0;
376
382
  width: 100%;
377
383
  position: absolute;
378
384
  border-radius: 1ex;
379
385
  overflow: auto;
380
386
  background: var(--sms-options-bg, white);
387
+ overscroll-behavior: var(--sms-options-overscroll, none);
388
+ box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
381
389
  }
382
- :where(ul.options.hidden) {
390
+ :where(div.multiselect > ul.options.hidden) {
383
391
  visibility: hidden;
384
392
  }
385
- :where(ul.options li) {
393
+ :where(div.multiselect > ul.options > li) {
386
394
  padding: 3pt 2ex;
387
395
  cursor: pointer;
388
396
  }
389
397
  /* for noOptionsMsg */
390
- :where(ul.options span) {
398
+ :where(div.multiselect > ul.options span) {
391
399
  padding: 3pt 2ex;
392
400
  }
393
- :where(ul.options li.selected) {
401
+ :where(div.multiselect > ul.options > li.selected) {
394
402
  border-left: var(
395
403
  --sms-li-selected-border-left,
396
404
  3pt solid var(--sms-selected-color, green)
@@ -398,22 +406,21 @@ display above those of another following shortly after it -->
398
406
  background: var(--sms-li-selected-bg, inherit);
399
407
  color: var(--sms-li-selected-color, inherit);
400
408
  }
401
- :where(ul.options li:not(.selected):hover) {
409
+ :where(div.multiselect > ul.options > li:not(.selected):hover) {
402
410
  border-left: var(
403
411
  --sms-li-not-selected-hover-border-left,
404
412
  3pt solid var(--sms-active-color, cornflowerblue)
405
413
  );
406
- border-left: 3pt solid var(--blue);
407
414
  }
408
- :where(ul.options li.active) {
415
+ :where(div.multiselect > ul.options > li.active) {
409
416
  background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue));
410
417
  }
411
- :where(ul.options li.disabled) {
418
+ :where(div.multiselect > ul.options > li.disabled) {
419
+ cursor: not-allowed;
412
420
  background: var(--sms-li-disabled-bg, #f5f5f6);
413
421
  color: var(--sms-li-disabled-text, #b8b8b8);
414
- cursor: not-allowed;
415
422
  }
416
- :where(ul.options li.disabled:hover) {
423
+ :where(div.multiselect > ul.options > li.disabled:hover) {
417
424
  border-left: unset;
418
425
  }
419
426
  </style>
@@ -5,8 +5,10 @@ declare const __propDef: {
5
5
  selected?: Option[] | undefined;
6
6
  selectedLabels?: Primitive[] | undefined;
7
7
  selectedValues?: Primitive[] | undefined;
8
+ searchText?: string | undefined;
9
+ showOptions?: boolean | undefined;
8
10
  maxSelect?: number | null | undefined;
9
- maxSelectMsg?: ((current: number, max: number) => string) | undefined;
11
+ maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
10
12
  readonly?: boolean | undefined;
11
13
  options: ProtoOption[];
12
14
  input?: HTMLInputElement | null | undefined;
@@ -15,11 +17,13 @@ declare const __propDef: {
15
17
  name?: string | undefined;
16
18
  noOptionsMsg?: string | undefined;
17
19
  activeOption?: Option | null | undefined;
20
+ filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
18
21
  outerDivClass?: string | undefined;
19
22
  ulSelectedClass?: string | undefined;
20
23
  liSelectedClass?: string | undefined;
21
24
  ulOptionsClass?: string | undefined;
22
25
  liOptionClass?: string | undefined;
26
+ liActiveOptionClass?: string | undefined;
23
27
  removeBtnTitle?: string | undefined;
24
28
  removeAllTitle?: string | undefined;
25
29
  defaultDisabledTitle?: string | undefined;
package/package.json CHANGED
@@ -5,16 +5,16 @@
5
5
  "homepage": "https://svelte-multiselect.netlify.app",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "3.2.0",
8
+ "version": "3.3.0",
9
9
  "type": "module",
10
10
  "svelte": "index.js",
11
11
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
12
12
  "devDependencies": {
13
- "@sveltejs/adapter-static": "^1.0.0-next.26",
14
- "@sveltejs/kit": "^1.0.0-next.259",
15
- "@typescript-eslint/eslint-plugin": "^5.10.2",
16
- "@typescript-eslint/parser": "^5.10.2",
17
- "eslint": "^8.8.0",
13
+ "@sveltejs/adapter-static": "^1.0.0-next.28",
14
+ "@sveltejs/kit": "^1.0.0-next.278",
15
+ "@typescript-eslint/eslint-plugin": "^5.12.0",
16
+ "@typescript-eslint/parser": "^5.12.0",
17
+ "eslint": "^8.9.0",
18
18
  "eslint-plugin-svelte3": "^3.4.0",
19
19
  "hastscript": "^7.0.2",
20
20
  "mdsvex": "^0.10.5",
@@ -22,15 +22,15 @@
22
22
  "prettier-plugin-svelte": "^2.6.0",
23
23
  "rehype-autolink-headings": "^6.1.1",
24
24
  "rehype-slug": "^5.0.1",
25
- "svelte": "^3.46.3",
26
- "svelte-check": "^2.4.2",
25
+ "svelte": "^3.46.4",
26
+ "svelte-check": "^2.4.5",
27
27
  "svelte-github-corner": "^0.1.0",
28
- "svelte-preprocess": "^4.10.2",
29
- "svelte-toc": "^0.2.3",
30
- "svelte2tsx": "^0.5.2",
28
+ "svelte-preprocess": "^4.10.3",
29
+ "svelte-toc": "^0.2.6",
30
+ "svelte2tsx": "^0.5.5",
31
31
  "tslib": "^2.3.1",
32
32
  "typescript": "^4.5.5",
33
- "vite": "^2.7.13"
33
+ "vite": "^2.8.4"
34
34
  },
35
35
  "keywords": [
36
36
  "svelte",
package/readme.md CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  <slot />
24
24
 
25
- ## Key Features
25
+ ## Key features
26
26
 
27
27
  - **Single / multiple select:** pass `maxSelect={1}` prop to only allow one selection
28
28
  - **Dropdowns:** scrollable lists for large numbers of options
@@ -88,23 +88,42 @@ Full list of props/bindable variables for this component:
88
88
  <div class="table">
89
89
 
90
90
  <!-- prettier-ignore -->
91
- | name | default | description |
92
- | :--------------- | :--------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
- | `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. |
94
- | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
95
- | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
96
- | `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. `() => ''`. |
97
- | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
98
- | `selectedLabels` | `[]` | Labels of currently selected options. |
99
- | `selectedValues` | `[]` | Values of currently selected options. |
100
- | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
101
- | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
102
- | `input` | `undefined` | Handle to the `<input>` DOM node. |
103
- | `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. |
104
- | `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>`. |
91
+ | name | default | description |
92
+ | :--------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `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. |
94
+ | `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
95
+ | `searchText` | `` | Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text. |
96
+ | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
97
+ | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
98
+ | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
99
+ | `selectedLabels` | `[]` | Labels of currently selected options. |
100
+ | `selectedValues` | `[]` | Values of currently selected options. |
101
+ | `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
102
+ | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
103
+ | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
104
+ | `input` | `undefined` | Handle to the `<input>` DOM node. |
105
+ | `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. |
106
+ | `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>`. |
105
107
 
106
108
  </div>
107
109
 
110
+ ## Exposed methods
111
+
112
+ 1. `filterFunc = (op: Option, searchText: string) => boolean`: Determine what options are shown when user enters search string to filter dropdown list. Defaults to:
113
+
114
+ ```ts
115
+ filterFunc = (op: Option, searchText: string) => {
116
+ if (!searchText) return true
117
+ return `${op.label}`.toLowerCase().includes(searchText.toLowerCase())
118
+ }
119
+ ```
120
+
121
+ 2. `maxSelectMsg = (current: number, max: number) => string`: Inform users how many of the maximum allowed options they have already selected. Set `maxSelectMsg={null}` to not show a message. Defaults to `null` when `maxSelect={1}` or `maxSelect={null}`. Else if `maxSelect > 1`, defaults to:
122
+
123
+ ```ts
124
+ maxSelectMsg = (current: number, max: number) => `${current}/${max}`
125
+ ```
126
+
108
127
  ## Slots
109
128
 
110
129
  `MultiSelect.svelte` accepts two named slots
@@ -194,29 +213,37 @@ There are 3 ways to style this component. To understand which options do what, i
194
213
 
195
214
  If you only want to make small adjustments, you can pass the following CSS variables directly to the component as props or define them in a `:global()` CSS context.
196
215
 
197
- - `div.multiselect`:
216
+ - `div.multiselect`
198
217
  - `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.
199
- - `border-radius: var(--sms-border-radius, 5pt)`: Input border radius.
200
- - `background: var(--sms-input-bg)`: Input background.
201
- - `height: var(--sms-input-height, 2em)`: Input height.
202
- - `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`.
218
+ - `border-radius: var(--sms-border-radius, 5pt)`
219
+ - `background: var(--sms-input-bg)`
220
+ - `height: var(--sms-input-height, 2em)`
221
+ - `div.multiselect.open`
222
+ - `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
223
+ - `div.multiselect:focus-within`
224
+ - `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`.
225
+ - `div.multiselect.readonly`
203
226
  - `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state.
204
- - `div.multiselect.open`:
205
- - `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`.
206
- - `div.multiselect input`
227
+ - `div.multiselect > ul.selected > li > input`
207
228
  - `color: var(--sms-text-color, inherit)`: Input text color.
208
- - `ul.selected > li`:
229
+ - `div.multiselect > ul.selected > li`
209
230
  - `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
210
- - `ul.selected > li button:hover, button.remove-all:hover`
231
+ - `height: var(--sms-selected-li-height)`: Height of selected options.
232
+ - `ul.selected > li button:hover, button.remove-all:hover, button:focus`
211
233
  - `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.
212
- - `ul.options`
213
- - `background: var(--sms-options-bg, white)`: Background of options list.
214
- - `ul.options > li.selected`
234
+ - `div.multiselect > ul.options`
235
+ - `background: var(--sms-options-bg, white)`: Background of dropdown list.
236
+ - `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).
237
+ - `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);`: Box shadow of dropdown list.
238
+ - `div.multiselect > ul.options > li.selected`
239
+ - `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))`
215
240
  - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
216
241
  - `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
217
- - `ul.options > li.active`
242
+ - `div.multiselect > ul.options > li:not(.selected):hover`
243
+ - `border-left: var(--sms-li-not-selected-hover-border-left, 3pt solid var(--sms-active-color, cornflowerblue))`
244
+ - `div.multiselect > ul.options > li.active`
218
245
  - `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item.
219
- - `ul.options > li.disabled`
246
+ - `div.multiselect > ul.options > li.disabled`
220
247
  - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
221
248
  - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
222
249
 
@@ -235,6 +262,7 @@ The second method allows you to pass in custom classes to the important DOM elem
235
262
  - `liSelectedClass`
236
263
  - `ulOptionsClass`
237
264
  - `liOptionClass`
265
+ - `liActiveOptionClass`
238
266
 
239
267
  This simplified version of the DOM structure of this component shows where these classes are inserted:
240
268
 
@@ -246,7 +274,9 @@ This simplified version of the DOM structure of this component shows where these
246
274
  </ul>
247
275
  <ul class="options {ulOptionsClass}">
248
276
  <li class={liOptionClass}>Option 1</li>
249
- <li class={liOptionClass}>Option 2</li>
277
+ <li class="{liOptionClass} {liActiveOptionClass}">
278
+ Option 2 (currently active)
279
+ </li>
250
280
  </ul>
251
281
  </div>
252
282
  ```
@@ -256,40 +286,47 @@ This simplified version of the DOM structure of this component shows where these
256
286
  You can alternatively style every part of this component with more fine-grained control by using the following `:global()` CSS selectors. `ul.selected` is the list of currently selected options rendered inside the component's input whereas `ul.options` is the list of available options that slides out when the component has focus.
257
287
 
258
288
  ```css
259
- :global(.multiselect) {
289
+ :global(div.multiselect) {
260
290
  /* top-level wrapper div */
261
291
  }
262
- :global(.multiselect ul.selected > li) {
263
- /* selected options */
292
+ :global(div.multiselect.open) {
293
+ /* top-level wrapper div when dropdown open */
294
+ }
295
+ :global(div.multiselect.readonly) {
296
+ /* top-level wrapper div when in readonly state */
264
297
  }
265
- :global(.multiselect ul.selected > li button),
266
- :global(.multiselect button.remove-all) {
298
+ :global(div.multiselect > ul.selected) {
299
+ /* selected list */
300
+ }
301
+ :global(div.multiselect > ul.selected > li) {
302
+ /* selected list items */
303
+ }
304
+ :global(div.multiselect button) {
305
+ /* target all buttons in this component */
306
+ }
307
+ :global(div.multiselect > ul.selected > li button, button.remove-all) {
267
308
  /* buttons to remove a single or all selected options at once */
268
309
  }
269
- :global(.multiselect ul.options) {
310
+ :global(div.multiselect > ul.selected > li > input) {
311
+ /* input inside the top-level wrapper div */
312
+ }
313
+ :global(div.multiselect > ul.options) {
270
314
  /* dropdown options */
271
315
  }
272
- :global(.multiselect ul.options li) {
273
- /* dropdown list of available options */
316
+ :global(div.multiselect > ul.options > li) {
317
+ /* dropdown list items */
274
318
  }
275
- :global(.multiselect ul.options li.selected) {
319
+ :global(div.multiselect > ul.options > li.selected) {
276
320
  /* selected options in the dropdown list */
277
321
  }
278
- :global(.multiselect ul.options li:not(.selected):hover) {
322
+ :global(div.multiselect > ul.options > li:not(.selected):hover) {
279
323
  /* unselected but hovered options in the dropdown list */
280
324
  }
281
- :global(.multiselect ul.options li.selected:hover) {
282
- /* selected and hovered options in the dropdown list */
283
- /* probably not necessary to style this state in most cases */
284
- }
285
- :global(.multiselect ul.options li.active) {
325
+ :global(div.multiselect > ul.options > li.active) {
286
326
  /* active means item was navigated to with up/down arrow keys */
287
327
  /* ready to be selected by pressing enter */
288
328
  }
289
- :global(.multiselect ul.options li.selected.active) {
290
- /* both active and already selected, pressing enter now will deselect the item */
291
- }
292
- :global(.multiselect ul.options li.disabled) {
329
+ :global(div.multiselect > ul.options > li.disabled) {
293
330
  /* options with disabled key set to true (see props above) */
294
331
  }
295
332
  ```