svelte-multiselect 3.0.0 → 3.2.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.
@@ -2,6 +2,7 @@
2
2
  import { fly } from 'svelte/transition';
3
3
  import { onClickOutside } from './actions';
4
4
  import { CrossIcon, ExpandIcon, ReadOnlyIcon } from './icons';
5
+ import Wiggle from './Wiggle.svelte';
5
6
  export let selected = [];
6
7
  export let selectedLabels = [];
7
8
  export let selectedValues = [];
@@ -11,8 +12,8 @@ export let readonly = false;
11
12
  export let options;
12
13
  export let input = null;
13
14
  export let placeholder = undefined;
14
- export let name = undefined;
15
15
  export let id = undefined;
16
+ export let name = id;
16
17
  export let noOptionsMsg = `No matching options`;
17
18
  export let activeOption = null;
18
19
  export let outerDivClass = ``;
@@ -34,6 +35,7 @@ if (!Array.isArray(selected))
34
35
  onMount(() => {
35
36
  selected = _options.filter((op) => op?.preselected);
36
37
  });
38
+ let wiggle = false;
37
39
  function isObject(item) {
38
40
  return typeof item === `object` && !Array.isArray(item) && item !== null;
39
41
  }
@@ -79,6 +81,8 @@ $: if (
79
81
  // make the first filtered option active
80
82
  activeOption = matchingEnabledOptions[0];
81
83
  function add(label) {
84
+ if (selected.length - (maxSelect ?? 0) < 1)
85
+ wiggle = true;
82
86
  if (!readonly &&
83
87
  !selectedLabels.includes(label) &&
84
88
  // for maxselect = 1 we always replace current option with new selection
@@ -196,41 +200,33 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
196
200
  <!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
197
201
  display above those of another following shortly after it -->
198
202
  <div
199
- {id}
200
203
  class="multiselect {outerDivClass}"
201
204
  class:readonly
202
205
  class:single={maxSelect == 1}
203
- style={showOptions ? `z-index: 2;` : undefined}
206
+ class:open={showOptions}
204
207
  on:mouseup|stopPropagation={() => setOptionsVisible(true)}
205
208
  use:onClickOutside={() => setOptionsVisible(false)}
206
209
  use:onClickOutside={() => dispatch(`blur`)}
207
210
  >
208
211
  <ExpandIcon height="14pt" style="padding: 0 3pt 0 1pt;" />
209
212
  <ul class="selected {ulSelectedClass}">
210
- {#if maxSelect == 1 && selected[0]?.label}
211
- <span on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}>
212
- {selected[0].label}
213
- </span>
214
- {:else}
215
- {#each selected as { label }}
216
- <li
217
- class={liSelectedClass}
218
- on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
219
- >
220
- {label}
221
- {#if !readonly}
222
- <button
223
- on:mouseup|stopPropagation={() => remove(label)}
224
- on:keydown={handleEnterAndSpaceKeys(() => remove(label))}
225
- type="button"
226
- title="{removeBtnTitle} {label}"
227
- >
228
- <CrossIcon height="12pt" />
229
- </button>
230
- {/if}
231
- </li>
232
- {/each}
233
- {/if}
213
+ {#each selected as option, idx}
214
+ <li class={liSelectedClass}>
215
+ <slot name="renderSelected" {option} {idx}>
216
+ {option.label}
217
+ </slot>
218
+ {#if !readonly}
219
+ <button
220
+ on:mouseup|stopPropagation={() => remove(option.label)}
221
+ on:keydown={handleEnterAndSpaceKeys(() => remove(option.label))}
222
+ type="button"
223
+ title="{removeBtnTitle} {option.label}"
224
+ >
225
+ <CrossIcon height="12pt" />
226
+ </button>
227
+ {/if}
228
+ </li>
229
+ {/each}
234
230
  <input
235
231
  bind:this={input}
236
232
  autocomplete="off"
@@ -238,6 +234,7 @@ display above those of another following shortly after it -->
238
234
  on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
239
235
  on:keydown={handleKeydown}
240
236
  on:focus={() => setOptionsVisible(true)}
237
+ {id}
241
238
  {name}
242
239
  placeholder={selectedLabels.length ? `` : placeholder}
243
240
  />
@@ -246,7 +243,9 @@ display above those of another following shortly after it -->
246
243
  <ReadOnlyIcon height="14pt" />
247
244
  {:else if selected.length > 0}
248
245
  {#if maxSelect !== null && maxSelect > 1}
249
- <span style="padding: 0 3pt;">{maxSelectMsg(selected.length, maxSelect)}</span>
246
+ <Wiggle bind:wiggle angle={20}>
247
+ <span style="padding: 0 3pt;">{maxSelectMsg(selected.length, maxSelect)}</span>
248
+ </Wiggle>
250
249
  {/if}
251
250
  <button
252
251
  type="button"
@@ -265,7 +264,9 @@ display above those of another following shortly after it -->
265
264
  class:hidden={!showOptions}
266
265
  transition:fly|local={{ duration: 300, y: 40 }}
267
266
  >
268
- {#each matchingOptions as { label, disabled, title = null, selectedTitle, disabledTitle = defaultDisabledTitle }}
267
+ {#each matchingOptions as option, idx}
268
+ {@const { label, disabled, title = null, selectedTitle } = option}
269
+ {@const { disabledTitle = defaultDisabledTitle } = option}
269
270
  <li
270
271
  on:mouseup|preventDefault|stopPropagation
271
272
  on:mousedown|preventDefault|stopPropagation={() => {
@@ -278,31 +279,38 @@ display above those of another following shortly after it -->
278
279
  title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
279
280
  class={liOptionClass}
280
281
  >
281
- {label}
282
+ <slot name="renderOptions" {option} {idx}>
283
+ {option.label}
284
+ </slot>
282
285
  </li>
283
286
  {:else}
284
- {noOptionsMsg}
287
+ <span>{noOptionsMsg}</span>
285
288
  {/each}
286
289
  </ul>
287
290
  {/key}
288
291
  </div>
289
292
 
290
293
  <style>
291
- :where(.multiselect) {
294
+ :where(div.multiselect) {
292
295
  position: relative;
293
296
  margin: 1em 0;
294
297
  border: var(--sms-border, 1pt solid lightgray);
295
298
  border-radius: var(--sms-border-radius, 5pt);
299
+ background: var(--sms-input-bg);
300
+ height: var(--sms-input-height, 2em);
296
301
  align-items: center;
297
302
  min-height: 18pt;
298
303
  display: flex;
299
304
  cursor: text;
300
305
  padding: 0 3pt;
301
306
  }
302
- :where(.multiselect:focus-within) {
307
+ :where(div.multiselect.open) {
308
+ z-index: var(--sms-open-z-index, 4);
309
+ }
310
+ :where(div.multiselect:focus-within) {
303
311
  border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
304
312
  }
305
- :where(.multiselect.readonly) {
313
+ :where(div.multiselect.readonly) {
306
314
  background: var(--sms-readonly-bg, lightgray);
307
315
  }
308
316
 
@@ -332,21 +340,23 @@ display above those of another following shortly after it -->
332
340
  outline: none;
333
341
  padding: 0 2pt;
334
342
  }
335
- :where(ul.selected > li button:hover, button.remove-all:hover) {
343
+ :where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
336
344
  color: var(--sms-remove-x-hover-focus-color, lightskyblue);
337
345
  }
338
346
  :where(button:focus) {
339
- color: var(--sms-remove-x-hover-focus-color, lightskyblue);
340
347
  transform: scale(1.04);
341
348
  }
342
349
 
343
- :where(.multiselect input) {
350
+ :where(div.multiselect input) {
344
351
  border: none;
345
352
  outline: none;
346
353
  background: none;
347
354
  color: var(--sms-text-color, inherit);
348
355
  flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
349
356
  min-width: 2em;
357
+ /* minimum font-size > 16px ensures iOS doesn't zoom in when focusing input */
358
+ /* https://stackoverflow.com/a/6394497 */
359
+ font-size: calc(16px + 0.1vw);
350
360
  }
351
361
 
352
362
  :where(ul.selected) {
@@ -376,6 +386,10 @@ display above those of another following shortly after it -->
376
386
  padding: 3pt 2ex;
377
387
  cursor: pointer;
378
388
  }
389
+ /* for noOptionsMsg */
390
+ :where(ul.options span) {
391
+ padding: 3pt 2ex;
392
+ }
379
393
  :where(ul.options li.selected) {
380
394
  border-left: var(
381
395
  --sms-li-selected-border-left,
@@ -11,8 +11,8 @@ declare const __propDef: {
11
11
  options: ProtoOption[];
12
12
  input?: HTMLInputElement | null | undefined;
13
13
  placeholder?: string | undefined;
14
- name?: string | undefined;
15
14
  id?: string | undefined;
15
+ name?: string | undefined;
16
16
  noOptionsMsg?: string | undefined;
17
17
  activeOption?: Option | null | undefined;
18
18
  outerDivClass?: string | undefined;
@@ -29,7 +29,16 @@ declare const __propDef: {
29
29
  } & {
30
30
  [evt: string]: CustomEvent<any>;
31
31
  };
32
- slots: {};
32
+ slots: {
33
+ renderSelected: {
34
+ option: Option;
35
+ idx: any;
36
+ };
37
+ renderOptions: {
38
+ option: Option;
39
+ idx: any;
40
+ };
41
+ };
33
42
  };
34
43
  export declare type MultiSelectProps = typeof __propDef.props;
35
44
  export declare type MultiSelectEvents = typeof __propDef.events;
package/Wiggle.svelte ADDED
@@ -0,0 +1,24 @@
1
+ <script >import { spring } from 'svelte/motion';
2
+ // bind to this state and set it to true from parent
3
+ export let wiggle = false;
4
+ // intended use case: set max value during wiggle for one of angle, scale, dx, dy through props
5
+ export let angle = 0; // try 20
6
+ export let scale = 1; // try 1.2
7
+ export let dx = 0; // try 10
8
+ export let dy = 0; // try 10
9
+ export let duration = 200;
10
+ export let stiffness = 0.05;
11
+ export let damping = 0.1;
12
+ let restState = { angle: 0, scale: 1, dx: 0, dy: 0 };
13
+ let store = spring(restState, { stiffness, damping });
14
+ $: store.set(wiggle ? { scale, angle, dx, dy } : restState);
15
+ $: if (wiggle)
16
+ setTimeout(() => (wiggle = false), duration);
17
+ </script>
18
+
19
+ <span
20
+ style:transform="rotate({$store.angle}deg) scale({$store.scale}) translate({$store.dx}px,
21
+ {$store.dy}px)"
22
+ >
23
+ <slot />
24
+ </span>
@@ -0,0 +1,25 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ wiggle?: boolean | undefined;
5
+ angle?: number | undefined;
6
+ scale?: number | undefined;
7
+ dx?: number | undefined;
8
+ dy?: number | undefined;
9
+ duration?: number | undefined;
10
+ stiffness?: number | undefined;
11
+ damping?: number | undefined;
12
+ };
13
+ events: {
14
+ [evt: string]: CustomEvent<any>;
15
+ };
16
+ slots: {
17
+ default: {};
18
+ };
19
+ };
20
+ export declare type WiggleProps = typeof __propDef.props;
21
+ export declare type WiggleEvents = typeof __propDef.events;
22
+ export declare type WiggleSlots = typeof __propDef.slots;
23
+ export default class Wiggle extends SvelteComponentTyped<WiggleProps, WiggleEvents, WiggleSlots> {
24
+ }
25
+ export {};
package/actions.js CHANGED
@@ -14,26 +14,3 @@ export function onClickOutside(node, cb) {
14
14
  },
15
15
  };
16
16
  }
17
- // import { spring } from 'svelte/motion'
18
- // export default function boop(node: HTMLElement, params = {}) {
19
- // const { setter } = params
20
- // const springyRotation = spring(
21
- // { x: 0, y: 0, rotation: 0, scale: 1 },
22
- // { stiffness: 0.1, damping: 0.15 }
23
- // )
24
- // node.style.display = `inline-block`
25
- // const unsubscribe = springyRotation.subscribe(({ x, y, rotation, scale }) => {
26
- // node.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${scale})`
27
- // })
28
- // return {
29
- // update({ isBooped: x = 0, y = 0, rotation = 0, scale = 1, timing }) {
30
- // springyRotation.set(
31
- // isBooped
32
- // ? { x, y, rotation, scale }
33
- // : { x: 0, y: 0, rotation: 0, scale: 1 }
34
- // )
35
- // if (isBooped) window.setTimeout(() => setter(false), timing)
36
- // },
37
- // destroy: unsubscribe,
38
- // }
39
- // }
package/package.json CHANGED
@@ -5,35 +5,32 @@
5
5
  "homepage": "https://svelte-multiselect.netlify.app",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "3.0.0",
8
+ "version": "3.2.0",
9
9
  "type": "module",
10
- "svelte": "MultiSelect.svelte",
11
- "bugs": {
12
- "url": "https://github.com/janosh/svelte-multiselect/issues"
13
- },
10
+ "svelte": "index.js",
11
+ "bugs": "https://github.com/janosh/svelte-multiselect/issues",
14
12
  "devDependencies": {
15
- "@sveltejs/adapter-static": "^1.0.0-next.22",
16
- "@sveltejs/kit": "^1.0.0-next.202",
17
- "@testing-library/svelte": "^3.0.3",
18
- "@typescript-eslint/eslint-plugin": "^5.7.0",
19
- "@typescript-eslint/parser": "^5.7.0",
20
- "ava": "^3.15.0",
21
- "eslint": "^8.5.0",
22
- "eslint-plugin-svelte3": "^3.2.1",
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",
18
+ "eslint-plugin-svelte3": "^3.4.0",
23
19
  "hastscript": "^7.0.2",
24
- "mdsvex": "^0.9.8",
20
+ "mdsvex": "^0.10.5",
25
21
  "prettier": "^2.5.1",
26
- "prettier-plugin-svelte": "^2.5.1",
27
- "rehype-autolink-headings": "^6.1.0",
28
- "rehype-slug": "^5.0.0",
29
- "svelte": "^3.44.3",
30
- "svelte-check": "^2.2.11",
31
- "svelte-preprocess": "^4.10.1",
32
- "svelte-toc": "^0.1.10",
33
- "svelte2tsx": "^0.4.12",
22
+ "prettier-plugin-svelte": "^2.6.0",
23
+ "rehype-autolink-headings": "^6.1.1",
24
+ "rehype-slug": "^5.0.1",
25
+ "svelte": "^3.46.3",
26
+ "svelte-check": "^2.4.2",
27
+ "svelte-github-corner": "^0.1.0",
28
+ "svelte-preprocess": "^4.10.2",
29
+ "svelte-toc": "^0.2.3",
30
+ "svelte2tsx": "^0.5.2",
34
31
  "tslib": "^2.3.1",
35
- "typescript": "^4.5.4",
36
- "vite": "^2.7.3"
32
+ "typescript": "^4.5.5",
33
+ "vite": "^2.7.13"
37
34
  },
38
35
  "keywords": [
39
36
  "svelte",
@@ -48,7 +45,6 @@
48
45
  "exports": {
49
46
  "./package.json": "./package.json",
50
47
  "./MultiSelect.svelte": "./MultiSelect.svelte",
51
- "./actions": "./actions.js",
52
48
  ".": "./index.js"
53
49
  }
54
50
  }
package/readme.md CHANGED
@@ -1,49 +1,49 @@
1
- <div class="maybe-hide">
2
-
3
- <p align="center">
4
- <img src="static/favicon.svg" alt="Svelte MultiSelect" height=80>
5
- </p>
6
-
7
- <h1 align="center">Svelte MultiSelect</h1>
1
+ <h1 align="center">
2
+ <img src="https://raw.githubusercontent.com/janosh/svelte-toc/main/static/favicon.svg" alt="Svelte MultiSelect" height=60>
3
+ <br>&ensp;Svelte MultiSelect
4
+ </h1>
8
5
 
9
6
  <h4 align="center">
10
7
 
11
8
  [![Netlify Status](https://api.netlify.com/api/v1/badges/a45b62c3-ea45-4cfd-9912-77ec4fc8d7e8/deploy-status)](https://app.netlify.com/sites/svelte-multiselect/deploys)
12
9
  [![NPM version](https://img.shields.io/npm/v/svelte-multiselect?color=blue&logo=NPM)](https://npmjs.com/package/svelte-multiselect)
13
10
  [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/janosh/svelte-multiselect/main.svg)](https://results.pre-commit.ci/latest/github/janosh/svelte-multiselect/main)
11
+ ![Needs Svelte version](https://img.shields.io/npm/dependency-version/svelte-multiselect/dev/svelte)
14
12
 
15
13
  </h4>
16
14
 
15
+ <div class="hide-in-docs">
16
+
17
17
  **[Live demo](https://svelte-multiselect.netlify.app)**.
18
18
 
19
19
  </div>
20
20
 
21
- <!-- remove above in docs -->
22
-
23
21
  **Keyboard-friendly, zero-dependency multi-select Svelte component.**
24
22
 
23
+ <slot />
24
+
25
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
29
29
  - **Searchable:** start typing to filter options
30
- - **Tagging:** selected options are recorded as tags within the text input
30
+ - **Tagging:** selected options are recorded as tags in the input
31
31
  - **Server-side rendering:** no reliance on browser objects like `window` or `document`
32
32
  - **Configurable:** see [props](#props)
33
33
  - **No dependencies:** needs only Svelte as dev dependency
34
34
  - **Keyboard friendly** for mouse-less form completion
35
35
 
36
- > ## Recent breaking changes
37
- >
38
- > - v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects as well, even if you still pass in `options` as strings.
39
- > - v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
40
- >
41
- > ```js
42
- > on:add={(e) => console.log(e.detail.token.label)} // v2.0.0
43
- > on:add={(e) => console.log(e.detail.option.label)} // v3.0.0
44
- > ```
45
- >
46
- > It also added a separate event type `removeAll` for when the user removes all currently selected options at once which previously fired a normal `remove`. The props `ulTokensClass` and `liTokenClass` were renamed to `ulSelectedClass` and `liSelectedClass`. Similarly, the CSS variable `--sms-token-bg` changed to `--sms-selected-bg`.
36
+ ## Recent breaking changes
37
+
38
+ - v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects, even if you still pass in `options` as strings. To get the same stuff you would have gotten from `bind:selected` before, there's now `bind:selectedLabels` (and `bind:selectedValues`).
39
+ - v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
40
+
41
+ ```js
42
+ on:add={(e) => console.log(e.detail.token.label)} // v2.0.0
43
+ on:add={(e) => console.log(e.detail.option.label)} // v3.0.0
44
+ ```
45
+
46
+ It also added a separate event type `removeAll` for when the user removes all currently selected options at once which previously fired a normal `remove`. The props `ulTokensClass` and `liTokenClass` were renamed to `ulSelectedClass` and `liSelectedClass`. Similarly, the CSS variable `--sms-token-bg` changed to `--sms-selected-bg`.
47
47
 
48
48
  ## Installation
49
49
 
@@ -88,23 +88,46 @@ 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
- | `name` | `undefined` | Passed to the `<input>` for associating HTML form `<label>`s with this component. E.g. clicking a `<label>` with same name will focus this component. |
104
- | `id` | `undefined` | Applied to the top-level `<div>` e.g. for `document.getElementById()`. |
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>`. |
105
105
 
106
106
  </div>
107
107
 
108
+ ## Slots
109
+
110
+ `MultiSelect.svelte` accepts two named slots
111
+
112
+ - `slot="renderOptions"`
113
+ - `slot="renderSelected"`
114
+
115
+ to customize rendering individual options in the dropdown and the list of selected tags, respectively. Each renderer receives the full `option` object along with the zero-indexed position (`idx`) in its list, both available via the `let:` directive:
116
+
117
+ ```svelte
118
+ <MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
119
+ <span let:idx let:option slot="renderOptions">
120
+ {idx + 1}. {option.label}
121
+ {option.label === `Mango` ? `🎉` : ``}
122
+ </span>
123
+
124
+ <span let:idx let:option slot="renderSelected">
125
+ #{idx + 1}
126
+ {option.label}
127
+ </span>
128
+ </MultiSelect>
129
+ ```
130
+
108
131
  ## Events
109
132
 
110
133
  `MultiSelect.svelte` dispatches the following events:
@@ -131,28 +154,71 @@ Full list of props/bindable variables for this component:
131
154
  />
132
155
  ```
133
156
 
157
+ ## TypeScript
158
+
159
+ TypeScript users can import the types used for internal type safety:
160
+
161
+ ```svelte
162
+ <script lang="ts">
163
+ import MultiSelect, {
164
+ Option,
165
+ Primitive,
166
+ ProtoOption,
167
+ } from 'svelte-multiselect'
168
+
169
+ const myOptions: Option[] = [
170
+ { label: 'foo', value: 42 },
171
+ { label: 'bar', value: 69 },
172
+ ]
173
+ </script>
174
+ ```
175
+
134
176
  ## Styling
135
177
 
136
- There are 3 ways to style this component.
178
+ There are 3 ways to style this component. To understand which options do what, it helps to keep in mind this simplified DOM structure of the component:
179
+
180
+ ```svelte
181
+ <div class="multiselect">
182
+ <ul class="selected">
183
+ <li>Selected 1</li>
184
+ <li>Selected 2</li>
185
+ </ul>
186
+ <ul class="options">
187
+ <li>Option 1</li>
188
+ <li>Option 2</li>
189
+ </ul>
190
+ </div>
191
+ ```
137
192
 
138
193
  ### With CSS variables
139
194
 
140
- The first, if you only want to make small adjustments, allows you to pass the following CSS variables directly to the component as props.
141
-
142
- - `border: var(--sms-border, 1pt solid lightgray)`: Border around top-level `div.multiselect`. Change this to e.g. to `1px solid red` to indicate this form field is in an invalid state.
143
- - `border-radius: var(--sms-border-radius, 5pt)`: `div.multiselect` border radius.
144
- - `color: var(--sms-text-color, inherit)`: Input text color.
145
- - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: `div.multiselect` border when focused. Falls back to `--sms-active-color` if not set which in turn falls back on `cornflowerblue`.
146
- - `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state.
147
- - `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
148
- - `color: var(--sms-remove-x-hover+focus-color, lightgray)`: Hover color of cross icon to remove selected options.
149
- - `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.
150
- - `background: var(--sms-options-bg, white)`: Background of options list.
151
- - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
152
- - `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
153
- - `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item.
154
- - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
155
- - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
195
+ 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
+
197
+ - `div.multiselect`:
198
+ - `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`.
203
+ - `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`
207
+ - `color: var(--sms-text-color, inherit)`: Input text color.
208
+ - `ul.selected > li`:
209
+ - `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
210
+ - `ul.selected > li button:hover, button.remove-all:hover`
211
+ - `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`
215
+ - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
216
+ - `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
217
+ - `ul.options > li.active`
218
+ - `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`
220
+ - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
221
+ - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
156
222
 
157
223
  For example, to change the background color of the options dropdown:
158
224
 
@@ -173,14 +239,14 @@ The second method allows you to pass in custom classes to the important DOM elem
173
239
  This simplified version of the DOM structure of this component shows where these classes are inserted:
174
240
 
175
241
  ```svelte
176
- <div class={outerDivClass}>
177
- <ul class={ulSelectedClass}>
178
- <li class={liSelectedClass}>First selected tag</li>
179
- <li class={liSelectedClass}>Second selected tag</li>
242
+ <div class="multiselect {outerDivClass}">
243
+ <ul class="selected {ulSelectedClass}">
244
+ <li class={liSelectedClass}>Selected 1</li>
245
+ <li class={liSelectedClass}>Selected 2</li>
180
246
  </ul>
181
- <ul class={ulOptionsClass}>
182
- <li class={liOptionClass}>First available option</li>
183
- <li class={liOptionClass}>Second available option</li>
247
+ <ul class="options {ulOptionsClass}">
248
+ <li class={liOptionClass}>Option 1</li>
249
+ <li class={liOptionClass}>Option 2</li>
184
250
  </ul>
185
251
  </div>
186
252
  ```