svelte-multiselect 4.0.0 → 4.0.3

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.
@@ -1,8 +1,7 @@
1
1
  <script >import { createEventDispatcher, onMount, tick } from 'svelte';
2
2
  import { fly } from 'svelte/transition';
3
- import { onClickOutside } from './actions';
4
3
  import CircleSpinner from './CircleSpinner.svelte';
5
- import { CrossIcon, ExpandIcon, ReadOnlyIcon } from './icons';
4
+ import { CrossIcon, ExpandIcon, DisabledIcon } from './icons';
6
5
  import Wiggle from './Wiggle.svelte';
7
6
  export let selected = [];
8
7
  export let selectedLabels = [];
@@ -11,9 +10,11 @@ export let searchText = ``;
11
10
  export let showOptions = false;
12
11
  export let maxSelect = null; // null means any number of options are selectable
13
12
  export let maxSelectMsg = null;
14
- export let readonly = false;
13
+ export let disabled = false;
14
+ export let disabledTitle = `This field is disabled`;
15
15
  export let options;
16
16
  export let input = null;
17
+ export let outerDiv = null;
17
18
  export let placeholder = undefined;
18
19
  export let id = undefined;
19
20
  export let name = id;
@@ -30,14 +31,16 @@ export let liSelectedClass = ``;
30
31
  export let ulOptionsClass = ``;
31
32
  export let liOptionClass = ``;
32
33
  export let liActiveOptionClass = ``;
34
+ export let inputClass = ``;
33
35
  export let removeBtnTitle = `Remove`;
34
36
  export let removeAllTitle = `Remove all`;
35
- // https://github.com/sveltejs/svelte/issues/6964
36
37
  export let defaultDisabledTitle = `This option is disabled`;
37
38
  export let allowUserOptions = false;
38
39
  export let autoScroll = true;
39
40
  export let loading = false;
40
41
  export let required = false;
42
+ export let autocomplete = `off`;
43
+ export let invalid = false;
41
44
  if (maxSelect !== null && maxSelect < 0) {
42
45
  console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
43
46
  }
@@ -46,12 +49,8 @@ if (!(options?.length > 0))
46
49
  if (!Array.isArray(selected))
47
50
  console.error(`selected prop must be an array`);
48
51
  onMount(() => {
49
- selected = _options.filter((op) => op?.preselected);
52
+ selected = _options.filter((op) => op?.preselected) ?? [];
50
53
  });
51
- let wiggle = false;
52
- // formValue binds to input.form-control to prevent form submission if required
53
- // prop is true and no options are selected
54
- $: formValue = selectedValues.join(`,`);
55
54
  const dispatch = createEventDispatcher();
56
55
  function isObject(item) {
57
56
  return typeof item === `object` && !Array.isArray(item) && item !== null;
@@ -77,16 +76,21 @@ $: labels = _options.map((op) => op.label);
77
76
  $: if (new Set(labels).size !== options.length) {
78
77
  console.error(`Option labels must be unique. Duplicates found: ${labels.filter((label, idx) => labels.indexOf(label) !== idx)}`);
79
78
  }
79
+ let wiggle = false;
80
80
  $: selectedLabels = selected.map((op) => op.label);
81
81
  $: selectedValues = selected.map((op) => op.value);
82
+ // formValue binds to input.form-control to prevent form submission if required
83
+ // prop is true and no options are selected
84
+ $: formValue = selectedValues.join(`,`);
85
+ $: if (formValue)
86
+ invalid = false; // reset error status whenever component state changes
82
87
  // options matching the current search text
83
88
  $: matchingOptions = _options.filter((op) => filterFunc(op, searchText) && !selectedLabels.includes(op.label));
84
89
  $: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
85
90
  function add(label) {
86
91
  if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
87
92
  wiggle = true;
88
- if (!readonly &&
89
- !selectedLabels.includes(label) &&
93
+ if (!selectedLabels.includes(label) &&
90
94
  // for maxselect = 1 we always replace current option with new selection
91
95
  (maxSelect === null || maxSelect === 1 || selected.length < maxSelect)) {
92
96
  searchText = ``; // reset search string on selection
@@ -108,7 +112,7 @@ function add(label) {
108
112
  }
109
113
  }
110
114
  function remove(label) {
111
- if (selected.length === 0 || readonly)
115
+ if (selected.length === 0)
112
116
  return;
113
117
  const option = _options.find((option) => option.label === label);
114
118
  if (!option) {
@@ -119,18 +123,23 @@ function remove(label) {
119
123
  dispatch(`change`, { option, type: `remove` });
120
124
  }
121
125
  function setOptionsVisible(show) {
126
+ if (disabled)
127
+ return;
122
128
  showOptions = show;
123
- if (show)
129
+ if (show) {
124
130
  input?.focus();
131
+ dispatch(`focus`);
132
+ }
125
133
  else {
126
134
  input?.blur();
127
135
  activeOption = null;
136
+ dispatch(`blur`);
128
137
  }
129
138
  }
130
139
  // handle all keyboard events this component receives
131
140
  async function handleKeydown(event) {
132
- // on escape: dismiss options dropdown and reset search text
133
- if (event.key === `Escape`) {
141
+ // on escape or tab out of input: dismiss options dropdown and reset search text
142
+ if (event.key === `Escape` || event.key === `Tab`) {
134
143
  setOptionsVisible(false);
135
144
  searchText = ``;
136
145
  }
@@ -201,49 +210,71 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
201
210
  };
202
211
  </script>
203
212
 
213
+ <svelte:window
214
+ on:click={(event) => {
215
+ if (outerDiv && !outerDiv.contains(event.target)) {
216
+ setOptionsVisible(false)
217
+ }
218
+ }}
219
+ />
220
+
204
221
  <!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
205
222
  display above those of another following shortly after it -->
206
223
  <div
207
- class:readonly
224
+ bind:this={outerDiv}
225
+ class:disabled
208
226
  class:single={maxSelect === 1}
209
227
  class:open={showOptions}
228
+ aria-expanded={showOptions}
229
+ aria-multiselectable={maxSelect === null || maxSelect > 1}
230
+ class:invalid
210
231
  class="multiselect {outerDivClass}"
211
232
  on:mouseup|stopPropagation={() => setOptionsVisible(true)}
212
- use:onClickOutside={() => setOptionsVisible(false)}
213
- use:onClickOutside={() => dispatch(`blur`)}
233
+ title={disabled ? disabledTitle : null}
234
+ aria-disabled={disabled ? `true` : null}
214
235
  >
215
- <!-- invisible input, used only to prevent form submission if required=true and no options selected -->
216
- <input {required} bind:value={formValue} tabindex="-1" class="form-control" />
217
- <ExpandIcon style="min-width: 1em; padding: 0 1pt;" />
236
+ <input
237
+ {required}
238
+ bind:value={formValue}
239
+ tabindex="-1"
240
+ aria-hidden="true"
241
+ aria-label="ignore this, used only to prevent form submission if select is required but empty"
242
+ class="form-control"
243
+ on:invalid={() => (invalid = true)}
244
+ />
245
+ <ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt;" />
218
246
  <ul class="selected {ulSelectedClass}">
219
247
  {#each selected as option, idx}
220
- <li class={liSelectedClass}>
248
+ <li class={liSelectedClass} aria-selected="true">
221
249
  <slot name="selected" {option} {idx}>
222
250
  {option.label}
223
251
  </slot>
224
- {#if !readonly}
252
+ {#if !disabled}
225
253
  <button
226
254
  on:mouseup|stopPropagation={() => remove(option.label)}
227
255
  on:keydown={handleEnterAndSpaceKeys(() => remove(option.label))}
228
256
  type="button"
229
257
  title="{removeBtnTitle} {option.label}"
230
258
  >
231
- <CrossIcon height="12pt" />
259
+ <CrossIcon width="15px" />
232
260
  </button>
233
261
  {/if}
234
262
  </li>
235
263
  {/each}
236
264
  <li style="display: contents;">
237
265
  <input
266
+ class={inputClass}
238
267
  bind:this={input}
239
- autocomplete="off"
268
+ {autocomplete}
240
269
  bind:value={searchText}
241
270
  on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
242
271
  on:keydown={handleKeydown}
243
272
  on:focus={() => setOptionsVisible(true)}
244
273
  {id}
245
274
  {name}
275
+ {disabled}
246
276
  placeholder={selectedLabels.length ? `` : placeholder}
277
+ aria-invalid={invalid ? `true` : null}
247
278
  />
248
279
  </li>
249
280
  </ul>
@@ -252,8 +283,10 @@ display above those of another following shortly after it -->
252
283
  <CircleSpinner />
253
284
  </slot>
254
285
  {/if}
255
- {#if readonly}
256
- <ReadOnlyIcon height="14pt" />
286
+ {#if disabled}
287
+ <slot name="disabled-icon">
288
+ <DisabledIcon width="15px" />
289
+ </slot>
257
290
  {:else if selected.length > 0}
258
291
  {#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
259
292
  <Wiggle bind:wiggle angle={20}>
@@ -263,7 +296,7 @@ display above those of another following shortly after it -->
263
296
  </span>
264
297
  </Wiggle>
265
298
  {/if}
266
- {#if maxSelect !== 1}
299
+ {#if maxSelect !== 1 && selected.length > 1}
267
300
  <button
268
301
  type="button"
269
302
  class="remove-all"
@@ -271,7 +304,7 @@ display above those of another following shortly after it -->
271
304
  on:mouseup|stopPropagation={removeAll}
272
305
  on:keydown={handleEnterAndSpaceKeys(removeAll)}
273
306
  >
274
- <CrossIcon height="14pt" />
307
+ <CrossIcon width="15px" />
275
308
  </button>
276
309
  {/if}
277
310
  {/if}
@@ -297,6 +330,17 @@ display above those of another following shortly after it -->
297
330
  class:active
298
331
  class:disabled
299
332
  class="{liOptionClass} {active ? liActiveOptionClass : ``}"
333
+ on:mouseover={() => {
334
+ if (disabled) return
335
+ activeOption = option
336
+ }}
337
+ on:focus={() => {
338
+ if (disabled) return
339
+ activeOption = option
340
+ }}
341
+ on:mouseout={() => (activeOption = null)}
342
+ on:blur={() => (activeOption = null)}
343
+ aria-selected="false"
300
344
  >
301
345
  <slot name="option" {option} {idx}>
302
346
  {option.label}
@@ -316,11 +360,14 @@ display above those of another following shortly after it -->
316
360
  align-items: center;
317
361
  display: flex;
318
362
  cursor: text;
319
- padding: 0 3pt;
320
363
  border: var(--sms-border, 1pt solid lightgray);
321
- border-radius: var(--sms-border-radius, 5pt);
322
- background: var(--sms-input-bg);
323
- min-height: var(--sms-input-min-height, 22pt);
364
+ border-radius: var(--sms-border-radius, 3pt);
365
+ background: var(--sms-bg);
366
+ max-width: var(--sms-max-width);
367
+ padding: var(--sms-padding, 0 3pt);
368
+ color: var(--sms-text-color);
369
+ font-size: var(--sms-font-size, inherit);
370
+ min-height: var(--sms-min-height, 19pt);
324
371
  }
325
372
  :where(div.multiselect.open) {
326
373
  z-index: var(--sms-open-z-index, 4);
@@ -328,8 +375,9 @@ display above those of another following shortly after it -->
328
375
  :where(div.multiselect:focus-within) {
329
376
  border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
330
377
  }
331
- :where(div.multiselect.readonly) {
332
- background: var(--sms-readonly-bg, lightgray);
378
+ :where(div.multiselect.disabled) {
379
+ background: var(--sms-disabled-bg, lightgray);
380
+ cursor: not-allowed;
333
381
  }
334
382
 
335
383
  :where(div.multiselect > ul.selected) {
@@ -341,48 +389,51 @@ display above those of another following shortly after it -->
341
389
  }
342
390
  :where(div.multiselect > ul.selected > li) {
343
391
  align-items: center;
344
- border-radius: 4pt;
392
+ border-radius: 3pt;
345
393
  display: flex;
346
394
  margin: 2pt;
347
395
  line-height: normal;
348
- padding: 1pt 2pt 1pt 5pt;
349
396
  transition: 0.3s;
350
397
  white-space: nowrap;
351
- background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue));
352
- height: var(--sms-selected-li-height);
398
+ background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
399
+ padding: var(--sms-selected-li-padding, 1pt 5pt);
400
+ color: var(--sms-selected-text-color, var(--sms-text-color));
353
401
  }
354
- :where(div.multiselect > ul.selected > li button, button.remove-all) {
355
- align-items: center;
402
+ :where(div.multiselect button) {
356
403
  border-radius: 50%;
357
404
  display: flex;
358
- cursor: pointer;
359
405
  transition: 0.2s;
360
- }
361
- :where(div.multiselect button) {
362
406
  color: inherit;
363
407
  background: transparent;
364
408
  border: none;
365
409
  cursor: pointer;
366
410
  outline: none;
367
- padding: 0 2pt;
411
+ padding: 0;
412
+ margin: 0 0 0 3pt; /* CSS reset */
368
413
  }
369
- :where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
370
- color: var(--sms-remove-x-hover-focus-color, lightskyblue);
414
+ :where(div.multiselect button.remove-all) {
415
+ margin: 0 3pt;
371
416
  }
372
- :where(div.multiselect > button:focus) {
373
- transform: scale(1.04);
417
+ :where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
418
+ color: var(--sms-button-hover-color, lightskyblue);
374
419
  }
375
420
 
421
+ :where(div.multiselect input) {
422
+ margin: auto 0; /* CSS reset */
423
+ padding: 0; /* CSS reset */
424
+ }
376
425
  :where(div.multiselect > ul.selected > li > input) {
377
426
  border: none;
378
427
  outline: none;
379
428
  background: none;
380
429
  flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
381
430
  min-width: 2em;
382
- /* minimum font-size > 16px ensures iOS doesn't zoom in when focusing input */
383
- /* https://stackoverflow.com/a/6394497 */
384
- font-size: calc(16px + 0.1vw);
385
- color: var(--sms-text-color, inherit);
431
+ color: inherit;
432
+ font-size: inherit;
433
+ cursor: inherit; /* needed for disabled state */
434
+ }
435
+ :where(div.multiselect > ul.selected > li > input)::placeholder {
436
+ color: var(--sms-placeholder-color);
386
437
  }
387
438
  :where(div.multiselect > input.form-control) {
388
439
  width: 2em;
@@ -392,6 +443,7 @@ display above those of another following shortly after it -->
392
443
  outline: none;
393
444
  z-index: -1;
394
445
  opacity: 0;
446
+ pointer-events: none;
395
447
  }
396
448
 
397
449
  :where(div.multiselect > ul.options) {
@@ -421,28 +473,15 @@ display above those of another following shortly after it -->
421
473
  padding: 3pt 2ex;
422
474
  }
423
475
  :where(div.multiselect > ul.options > li.selected) {
424
- border-left: var(
425
- --sms-li-selected-border-left,
426
- 3pt solid var(--sms-selected-color, green)
427
- );
428
- background: var(--sms-li-selected-bg, inherit);
429
- color: var(--sms-li-selected-color, inherit);
430
- }
431
- :where(div.multiselect > ul.options > li:not(.selected):hover) {
432
- border-left: var(
433
- --sms-li-not-selected-hover-border-left,
434
- 3pt solid var(--sms-active-color, cornflowerblue)
435
- );
476
+ background: var(--sms-li-selected-bg);
477
+ color: var(--sms-li-selected-color);
436
478
  }
437
479
  :where(div.multiselect > ul.options > li.active) {
438
- background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue));
480
+ background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
439
481
  }
440
482
  :where(div.multiselect > ul.options > li.disabled) {
441
483
  cursor: not-allowed;
442
484
  background: var(--sms-li-disabled-bg, #f5f5f6);
443
485
  color: var(--sms-li-disabled-text, #b8b8b8);
444
486
  }
445
- :where(div.multiselect > ul.options > li.disabled:hover) {
446
- border-left: unset;
447
- }
448
487
  </style>
@@ -9,9 +9,11 @@ declare const __propDef: {
9
9
  showOptions?: boolean | undefined;
10
10
  maxSelect?: number | null | undefined;
11
11
  maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
12
- readonly?: boolean | undefined;
12
+ disabled?: boolean | undefined;
13
+ disabledTitle?: string | undefined;
13
14
  options: ProtoOption[];
14
15
  input?: HTMLInputElement | null | undefined;
16
+ outerDiv?: HTMLDivElement | null | undefined;
15
17
  placeholder?: string | undefined;
16
18
  id?: string | undefined;
17
19
  name?: string | undefined;
@@ -24,6 +26,7 @@ declare const __propDef: {
24
26
  ulOptionsClass?: string | undefined;
25
27
  liOptionClass?: string | undefined;
26
28
  liActiveOptionClass?: string | undefined;
29
+ inputClass?: string | undefined;
27
30
  removeBtnTitle?: string | undefined;
28
31
  removeAllTitle?: string | undefined;
29
32
  defaultDisabledTitle?: string | undefined;
@@ -31,6 +34,8 @@ declare const __propDef: {
31
34
  autoScroll?: boolean | undefined;
32
35
  loading?: boolean | undefined;
33
36
  required?: boolean | undefined;
37
+ autocomplete?: string | undefined;
38
+ invalid?: boolean | undefined;
34
39
  };
35
40
  events: {
36
41
  mouseup: MouseEvent;
@@ -43,6 +48,7 @@ declare const __propDef: {
43
48
  idx: any;
44
49
  };
45
50
  spinner: {};
51
+ 'disabled-icon': {};
46
52
  option: {
47
53
  option: Option;
48
54
  idx: any;
@@ -1,9 +1,4 @@
1
- <script >export let width = `1em`;
2
- export let height = width;
3
- export let style = ``;
4
- </script>
5
-
6
- <svg {width} {height} {style} fill="currentColor" viewBox="0 0 16 16">
1
+ <svg {...$$props} fill="currentColor" viewBox="0 0 16 16">
7
2
  <path
8
3
  d="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708z"
9
4
  />
@@ -1,18 +1,23 @@
1
+ /** @typedef {typeof __propDef.props} ChevronExpandProps */
2
+ /** @typedef {typeof __propDef.events} ChevronExpandEvents */
3
+ /** @typedef {typeof __propDef.slots} ChevronExpandSlots */
4
+ export default class ChevronExpand extends SvelteComponentTyped<{
5
+ [x: string]: any;
6
+ }, {
7
+ [evt: string]: CustomEvent<any>;
8
+ }, {}> {
9
+ }
10
+ export type ChevronExpandProps = typeof __propDef.props;
11
+ export type ChevronExpandEvents = typeof __propDef.events;
12
+ export type ChevronExpandSlots = typeof __propDef.slots;
1
13
  import { SvelteComponentTyped } from "svelte";
2
14
  declare const __propDef: {
3
15
  props: {
4
- width?: string | number | undefined;
5
- height?: string | number | undefined;
6
- style?: string | undefined;
16
+ [x: string]: any;
7
17
  };
8
18
  events: {
9
19
  [evt: string]: CustomEvent<any>;
10
20
  };
11
21
  slots: {};
12
22
  };
13
- export declare type ChevronExpandProps = typeof __propDef.props;
14
- export declare type ChevronExpandEvents = typeof __propDef.events;
15
- export declare type ChevronExpandSlots = typeof __propDef.slots;
16
- export default class ChevronExpand extends SvelteComponentTyped<ChevronExpandProps, ChevronExpandEvents, ChevronExpandSlots> {
17
- }
18
23
  export {};
@@ -1,9 +1,4 @@
1
- <script >export let width = `1em`;
2
- export let height = width;
3
- export let style = ``;
4
- </script>
5
-
6
- <svg {width} {height} {style} viewBox="0 0 20 20" fill="currentColor">
1
+ <svg {...$$props} viewBox="0 0 20 20" fill="currentColor">
7
2
  <path
8
3
  d="M10 1.6a8.4 8.4 0 100 16.8 8.4 8.4 0 000-16.8zm4.789 11.461L13.06 14.79 10 11.729l-3.061 3.06L5.21 13.06 8.272 10 5.211 6.939 6.94 5.211 10 8.271l3.061-3.061 1.729 1.729L11.728 10l3.061 3.061z"
9
4
  />
@@ -1,18 +1,23 @@
1
+ /** @typedef {typeof __propDef.props} CrossProps */
2
+ /** @typedef {typeof __propDef.events} CrossEvents */
3
+ /** @typedef {typeof __propDef.slots} CrossSlots */
4
+ export default class Cross extends SvelteComponentTyped<{
5
+ [x: string]: any;
6
+ }, {
7
+ [evt: string]: CustomEvent<any>;
8
+ }, {}> {
9
+ }
10
+ export type CrossProps = typeof __propDef.props;
11
+ export type CrossEvents = typeof __propDef.events;
12
+ export type CrossSlots = typeof __propDef.slots;
1
13
  import { SvelteComponentTyped } from "svelte";
2
14
  declare const __propDef: {
3
15
  props: {
4
- width?: string | number | undefined;
5
- height?: string | number | undefined;
6
- style?: string | undefined;
16
+ [x: string]: any;
7
17
  };
8
18
  events: {
9
19
  [evt: string]: CustomEvent<any>;
10
20
  };
11
21
  slots: {};
12
22
  };
13
- export declare type CrossProps = typeof __propDef.props;
14
- export declare type CrossEvents = typeof __propDef.events;
15
- export declare type CrossSlots = typeof __propDef.slots;
16
- export default class Cross extends SvelteComponentTyped<CrossProps, CrossEvents, CrossSlots> {
17
- }
18
23
  export {};
@@ -1,9 +1,4 @@
1
- <script >export let width = `1em`;
2
- export let height = width;
3
- export let style = ``;
4
- </script>
5
-
6
- <svg {width} {height} {style} viewBox="0 0 24 24" fill="currentColor">
1
+ <svg {...$$props} viewBox="0 0 24 24" fill="currentColor">
7
2
  <path fill="none" d="M0 0h24v24H0V0z" />
8
3
  <path
9
4
  d="M14.48 11.95c.17.02.34.05.52.05 2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4c0 .18.03.35.05.52l3.43 3.43zm2.21 2.21L22.53 20H23v-2c0-2.14-3.56-3.5-6.31-3.84zM0 3.12l4 4V10H1v2h3v3h2v-3h2.88l2.51 2.51C9.19 15.11 7 16.3 7 18v2h9.88l4 4 1.41-1.41L1.41 1.71 0 3.12zM6.88 10H6v-.88l.88.88z"
@@ -0,0 +1,23 @@
1
+ /** @typedef {typeof __propDef.props} DisabledProps */
2
+ /** @typedef {typeof __propDef.events} DisabledEvents */
3
+ /** @typedef {typeof __propDef.slots} DisabledSlots */
4
+ export default class Disabled extends SvelteComponentTyped<{
5
+ [x: string]: any;
6
+ }, {
7
+ [evt: string]: CustomEvent<any>;
8
+ }, {}> {
9
+ }
10
+ export type DisabledProps = typeof __propDef.props;
11
+ export type DisabledEvents = typeof __propDef.events;
12
+ export type DisabledSlots = typeof __propDef.slots;
13
+ import { SvelteComponentTyped } from "svelte";
14
+ declare const __propDef: {
15
+ props: {
16
+ [x: string]: any;
17
+ };
18
+ events: {
19
+ [evt: string]: CustomEvent<any>;
20
+ };
21
+ slots: {};
22
+ };
23
+ export {};
package/icons/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { default as CrossIcon } from './Cross.svelte';
2
1
  export { default as ExpandIcon } from './ChevronExpand.svelte';
3
- export { default as ReadOnlyIcon } from './ReadOnly.svelte';
2
+ export { default as CrossIcon } from './Cross.svelte';
3
+ export { default as DisabledIcon } from './Disabled.svelte';
package/icons/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { default as CrossIcon } from './Cross.svelte';
2
1
  export { default as ExpandIcon } from './ChevronExpand.svelte';
3
- export { default as ReadOnlyIcon } from './ReadOnly.svelte';
2
+ export { default as CrossIcon } from './Cross.svelte';
3
+ export { default as DisabledIcon } from './Disabled.svelte';
package/index.d.ts CHANGED
@@ -28,5 +28,6 @@ export declare type DispatchEvents = {
28
28
  options?: Option[];
29
29
  type: 'add' | 'remove' | 'removeAll';
30
30
  };
31
+ focus: undefined;
31
32
  blur: undefined;
32
33
  };
package/package.json CHANGED
@@ -5,32 +5,37 @@
5
5
  "homepage": "https://svelte-multiselect.netlify.app",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "4.0.0",
8
+ "version": "4.0.3",
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.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
- "eslint-plugin-svelte3": "^3.4.0",
13
+ "@sveltejs/adapter-static": "^1.0.0-next.29",
14
+ "@sveltejs/kit": "^1.0.0-next.302",
15
+ "@sveltejs/vite-plugin-svelte": "^1.0.0-next.40",
16
+ "@typescript-eslint/eslint-plugin": "^5.16.0",
17
+ "@typescript-eslint/parser": "^5.16.0",
18
+ "@vitest/ui": "^0.7.9",
19
+ "eslint": "^8.11.0",
20
+ "eslint-plugin-svelte3": "^3.4.1",
19
21
  "hastscript": "^7.0.2",
22
+ "jsdom": "^19.0.0",
20
23
  "mdsvex": "^0.10.5",
21
- "prettier": "^2.5.1",
24
+ "playwright": "^1.20.0",
25
+ "prettier": "^2.6.0",
22
26
  "prettier-plugin-svelte": "^2.6.0",
23
27
  "rehype-autolink-headings": "^6.1.1",
24
28
  "rehype-slug": "^5.0.1",
25
29
  "svelte": "^3.46.4",
26
- "svelte-check": "^2.4.5",
30
+ "svelte-check": "^2.4.6",
27
31
  "svelte-github-corner": "^0.1.0",
28
- "svelte-preprocess": "^4.10.3",
29
- "svelte-toc": "^0.2.6",
30
- "svelte2tsx": "^0.5.5",
32
+ "svelte-preprocess": "^4.10.4",
33
+ "svelte-toc": "^0.2.8",
34
+ "svelte2tsx": "^0.5.6",
31
35
  "tslib": "^2.3.1",
32
- "typescript": "^4.5.5",
33
- "vite": "^2.8.4"
36
+ "typescript": "^4.6.2",
37
+ "vite": "^2.8.6",
38
+ "vitest": "^0.7.9"
34
39
  },
35
40
  "keywords": [
36
41
  "svelte",
package/readme.md CHANGED
@@ -5,22 +5,23 @@
5
5
 
6
6
  <h4 align="center">
7
7
 
8
+ [![Tests](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml/badge.svg)](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
8
9
  [![Netlify Status](https://api.netlify.com/api/v1/badges/a45b62c3-ea45-4cfd-9912-77ec4fc8d7e8/deploy-status)](https://app.netlify.com/sites/svelte-multiselect/deploys)
9
10
  [![NPM version](https://img.shields.io/npm/v/svelte-multiselect?color=blue&logo=NPM)](https://npmjs.com/package/svelte-multiselect)
10
11
  [![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)
12
+ [![Needs Svelte version](https://img.shields.io/npm/dependency-version/svelte-multiselect/dev/svelte)](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
12
13
 
13
14
  </h4>
14
15
 
15
- <div class="hide-in-docs">
16
-
17
- **[Live demo](https://svelte-multiselect.netlify.app)**.
18
-
19
- </div>
20
-
21
16
  **Keyboard-friendly, zero-dependency multi-select Svelte component.**
17
+ <strong class="hide-in-docs">
18
+ <a href="https://svelte-multiselect.netlify.app">Docs</a> &bull;
19
+ </strong>
20
+ <strong>
21
+ <a href="https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05">REPL</a>
22
+ </strong>
22
23
 
23
- <slot />
24
+ <slot name="examples" />
24
25
 
25
26
  ## Key features
26
27
 
@@ -33,21 +34,28 @@
33
34
  - **No dependencies:** needs only Svelte as dev dependency
34
35
  - **Keyboard friendly** for mouse-less form completion
35
36
 
37
+ <slot name="nav" />
38
+
36
39
  ## Recent breaking changes
37
40
 
38
41
  - v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
39
42
 
40
43
  ```js
41
- on:add={(e) => console.log(e.detail.token.label)} // v2.0.0
42
- on:add={(e) => console.log(e.detail.option.label)} // v3.0.0
44
+ on:add={(e) => console.log(e.detail.token.label)} // v2
45
+ on:add={(e) => console.log(e.detail.option.label)} // v3
43
46
  ```
44
47
 
45
48
  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`.
46
49
 
47
50
  - v4.0.0 renamed the slots for customizing how selected options and dropdown list items are rendered:
51
+
48
52
  - old: `<slot name="renderOptions" />`, new: `<slot name="option" />`
49
53
  - old: `<slot name="renderSelected" />`, new: `<slot name="selected" />`
50
54
 
55
+ - v4.0.1 renamed the `readonly` prop to `disabled` which now prevents all form or user interaction with this component including opening the dropdown list which was still possible before. See [#45](https://github.com/janosh/svelte-multiselect/issues/45) for details. The associated CSS class applied to the outer `div` was likewise renamed to `div.multiselect.{readonly=>disabled}`.
56
+
57
+ - v4.0.3 CSS variables starting with `--sms-input-<attr>` were renamed to just `--sms-<attr>`. E.g. `--sms-input-min-height` is now `--sms-min-height`.
58
+
51
59
  ## Installation
52
60
 
53
61
  ```sh
@@ -60,28 +68,16 @@ yarn add -D svelte-multiselect
60
68
  <script>
61
69
  import MultiSelect from 'svelte-multiselect'
62
70
 
63
- const webFrameworks = [
64
- `Svelte`,
65
- `React`,
66
- `Vue`,
67
- `Angular`,
68
- `Polymer`,
69
- `Ruby on Rails`,
70
- `ASP.net`,
71
- `Laravel`,
72
- `Django`,
73
- `Express`,
74
- `Spring`,
75
- ]
71
+ const ui_libs = [`Svelte`, `React`, `Vue`, `Angular`, `...`]
76
72
 
77
- let selected
73
+ let selected = []
78
74
  </script>
79
75
 
80
- Favorite Web Frameworks?
76
+ Favorite Frontend Frameworks?
81
77
 
82
78
  <code>selected = {JSON.stringify(selected)}</code>
83
79
 
84
- <MultiSelect bind:selected options={webFrameworks} />
80
+ <MultiSelect bind:selected options={ui_libs} />
85
81
  ```
86
82
 
87
83
  ## Props
@@ -91,26 +87,33 @@ Full list of props/bindable variables for this component:
91
87
  <div class="table">
92
88
 
93
89
  <!-- prettier-ignore -->
94
- | name | default | description |
95
- | :----------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
96
- | `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. |
97
- | `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
98
- | `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. |
99
- | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
100
- | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
101
- | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
102
- | `selectedLabels` | `[]` | Labels of currently selected options. |
103
- | `selectedValues` | `[]` | Values of currently selected options. |
104
- | `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
105
- | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
106
- | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
107
- | `input` | `undefined` | Handle to the `<input>` DOM node. |
108
- | `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. |
109
- | `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>`. |
110
- | `required` | `false` | Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected. |
111
- | `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
112
- | `allowUserOptions` | `false` | Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected. |
113
- | `loading` | `false` | Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner. |
90
+ | name | default | description |
91
+ | :--------------------- | :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
92
+ | `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. |
93
+ | `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
94
+ | `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. |
95
+ | `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
96
+ | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
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
+ | `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
101
+ | `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
102
+ | `disabledTitle` | `This field is disabled` | Tooltip text to display on hover when the component is in `disabled` state. |
103
+ | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
104
+ | `input` | `null` | Handle to the `<input>` DOM node. Only available after component mounts (`null` before then). |
105
+ | `outerDiv` | `null` | Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then). |
106
+ | `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. |
107
+ | `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>`. |
108
+ | `required` | `false` | Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected. |
109
+ | `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
110
+ | `allowUserOptions` | `false` | Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected. |
111
+ | `loading` | `false` | Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner. |
112
+ | `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (cross icon) to remove selected option. |
113
+ | `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
114
+ | `defaultDisabledTitle` | `'This option is disabled'` | Title text to display when user hovers over a disabled option. Each option can override this through its `disabledTitle` attribute. |
115
+ | `autocomplete` | `'off'` | Applied to the `<input>`. Specifies if browser is permitted to auto-fill this form field. See [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for other admissible values. |
116
+ | `invalid` | `false` | If `required=true` and user tries to submit but `selected = []` is empty, `invalid` is automatically set to `true` and CSS class `invalid` applied to the top-level `div.multiselect`. `invalid` class is removed again as soon as the user selects an option. `invalid` can also be controlled externally by binding to it `<MultiSelect bind:invalid />` and setting it to `true` based on outside events or custom validation. |
114
117
 
115
118
  </div>
116
119
 
@@ -138,19 +141,22 @@ Full list of props/bindable variables for this component:
138
141
  - `slot="option"`: Customize rendering of dropdown options. Receives as props the `option` object and the zero-indexed position (`idx`) it has in the dropdown.
139
142
  - `slot="selected"`: Customize rendering selected tags. Receives as props the `option` object and the zero-indexed position (`idx`) it has in the list of selected items.
140
143
  - `slot="spinner"`: Custom spinner component to display when in `loading` state. Receives no props.
144
+ - `slot="disabled-icon"`: Custom icon to display inside the input when in `disabled` state. Receives no props. Use an empty `<span slot="disabled-icon" />` or `div` to remove the default disabled icon.
141
145
 
142
146
  Example:
143
147
 
144
148
  ```svelte
145
- <MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
149
+ <MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]}>
146
150
  <span let:idx let:option slot="option">
147
- {idx + 1}. {option.label}
148
- {option.label === `Mango` ? `🎉` : ``}
151
+ {idx + 1}
152
+ {option.label}
153
+ <span style:background={option.label} style=" width: 1em; height: 1em;" />
149
154
  </span>
150
155
 
151
156
  <span let:idx let:option slot="selected">
152
- #{idx + 1}
157
+ {idx + 1}
153
158
  {option.label}
159
+ <span style:background={option.label} style=" width: 1em; height: 1em;" />
154
160
  </span>
155
161
 
156
162
  <CustomSpinner slot="spinner">
@@ -161,13 +167,13 @@ Example:
161
167
 
162
168
  `MultiSelect.svelte` dispatches the following events:
163
169
 
164
- | name | detail | description |
165
- | ----------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
166
- | `add` | `{ option: Option }` | Triggers when a new option is selected. |
167
- | `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
168
- | `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
169
- | `change` | `{ option?: Option, options?: Option[] }`, `type: 'add' \| 'remove' \| 'removeAll'` | Triggers when a option is either added or removed, or all options are removed at once. |
170
- | `blur` | none | Triggers when the input field looses focus. |
170
+ | name | detail | description |
171
+ | ----------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
172
+ | `add` | `{ option: Option }` | Triggers when a new option is selected. |
173
+ | `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
174
+ | `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
175
+ | `change` | `type: 'add' \| 'remove' \| 'removeAll'` | Triggers when a option is either added or removed, or all options are removed at once. Payload will be a single or an aarray of `Option` objects, respectively. |
176
+ | `blur` | none | Triggers when the input field looses focus. |
171
177
 
172
178
  ### Examples
173
179
 
@@ -225,22 +231,26 @@ If you only want to make small adjustments, you can pass the following CSS varia
225
231
 
226
232
  - `div.multiselect`
227
233
  - `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.
228
- - `border-radius: var(--sms-border-radius, 5pt)`
229
- - `background: var(--sms-input-bg)`
230
- - `height: var(--sms-input-height, 2em)`
234
+ - `border-radius: var(--sms-border-radius, 3pt)`
235
+ - `padding: var(--sms-padding, 0 3pt)`
236
+ - `background: var(--sms-bg)`
237
+ - `color: var(--sms-text-color)`
238
+ - `min-height: var(--sms-min-height)`
239
+ - `max-width: var(--sms-max-width)`
231
240
  - `div.multiselect.open`
232
241
  - `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
233
242
  - `div.multiselect:focus-within`
234
243
  - `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`.
235
- - `div.multiselect.readonly`
236
- - `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state.
237
- - `div.multiselect > ul.selected > li > input`
238
- - `color: var(--sms-text-color, inherit)`: Input text color.
244
+ - `div.multiselect.disabled`
245
+ - `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
246
+ - `div.multiselect input::placeholder`
247
+ - `color: var(--sms-placeholder-color)`
239
248
  - `div.multiselect > ul.selected > li`
240
- - `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
241
- - `height: var(--sms-selected-li-height)`: Height of selected options.
249
+ - `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
250
+ - `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
251
+ - `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
242
252
  - `ul.selected > li button:hover, button.remove-all:hover, button:focus`
243
- - `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.
253
+ - `color: var(--sms-button-hover-color, lightskyblue)`: Color of the cross-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
244
254
  - `div.multiselect > ul.options`
245
255
  - `background: var(--sms-options-bg, white)`: Background of dropdown list.
246
256
  - `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
@@ -249,13 +259,10 @@ If you only want to make small adjustments, you can pass the following CSS varia
249
259
  - `div.multiselect > ul.options > li`
250
260
  - `scroll-margin: var(--sms-options-scroll-margin, 100px)`: Top/bottom margin to keep between dropdown list items and top/bottom screen edge when auto-scrolling list to keep items in view.
251
261
  - `div.multiselect > ul.options > li.selected`
252
- - `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))`
253
- - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
254
- - `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
255
- - `div.multiselect > ul.options > li:not(.selected):hover`
256
- - `border-left: var(--sms-li-not-selected-hover-border-left, 3pt solid var(--sms-active-color, cornflowerblue))`
262
+ - `background: var(--sms-li-selected-bg)`: Background of selected list items in options pane.
263
+ - `color: var(--sms-li-selected-color)`: Text color of selected list items in options pane.
257
264
  - `div.multiselect > ul.options > li.active`
258
- - `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item.
265
+ - `background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)))`: Background of active dropdown item. Items become active either by mouseover or by navigating to them with arrow keys.
259
266
  - `div.multiselect > ul.options > li.disabled`
260
267
  - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
261
268
  - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
@@ -281,6 +288,7 @@ This simplified version of the DOM structure of this component shows where these
281
288
 
282
289
  ```svelte
283
290
  <div class="multiselect {outerDivClass}">
291
+ <input class={inputClass} />
284
292
  <ul class="selected {ulSelectedClass}">
285
293
  <li class={liSelectedClass}>Selected 1</li>
286
294
  <li class={liSelectedClass}>Selected 2</li>
@@ -305,8 +313,8 @@ You can alternatively style every part of this component with more fine-grained
305
313
  :global(div.multiselect.open) {
306
314
  /* top-level wrapper div when dropdown open */
307
315
  }
308
- :global(div.multiselect.readonly) {
309
- /* top-level wrapper div when in readonly state */
316
+ :global(div.multiselect.disabled) {
317
+ /* top-level wrapper div when in disabled state */
310
318
  }
311
319
  :global(div.multiselect > ul.selected) {
312
320
  /* selected list */
@@ -344,6 +352,40 @@ You can alternatively style every part of this component with more fine-grained
344
352
  }
345
353
  ```
346
354
 
355
+ ## Downstream testing
356
+
357
+ To test a Svelte component which imports `svelte-multiselect`, you need to configure your test runner to avoid [transpiling issues](https://github.com/janosh/svelte-multiselect/issues/48).
358
+
359
+ For Jest, exclude `svelte-multiselect` from `transformIgnorePatterns` in your `jest.config.json`:
360
+
361
+ ```json
362
+ {
363
+ "transformIgnorePatterns": ["node_modules/?!(svelte-multiselect)"],
364
+ "transform": {
365
+ "^.+\\.[t|j]s?$": "esbuild-jest",
366
+ "^.+\\.svelte$": ["svelte-jester", { "preprocess": true }]
367
+ }
368
+ }
369
+ ```
370
+
371
+ For Vitest, include `svelte-multiselect` in `deps.inline`:
372
+
373
+ ```ts
374
+ // vite.config.ts
375
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
376
+
377
+ export default {
378
+ plugins: [svelte({ hot: !process.env.VITEST })],
379
+ test: {
380
+ deps: {
381
+ inline: [/svelte-multiselect/],
382
+ },
383
+ },
384
+ }
385
+ ```
386
+
387
+ Here's a [Stackblitz example](https://stackblitz.com/fork/github/davipon/test-svelte-multiselect?initialPath=__vitest__) that also uses [`'vitest-svelte-kit'`](https://github.com/nickbreaton/vitest-svelte-kit).
388
+
347
389
  ## Want to contribute?
348
390
 
349
391
  To submit a PR, clone the repo, install dependencies and start the dev server to try out your changes.
package/actions.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export declare function onClickOutside(node: HTMLElement, cb?: () => void): {
2
- destroy(): void;
3
- };
package/actions.js DELETED
@@ -1,16 +0,0 @@
1
- export function onClickOutside(node, cb) {
2
- const dispatchOnClickOutside = (event) => {
3
- const clickWasOutside = node && !node.contains(event.target);
4
- if (clickWasOutside && !event.defaultPrevented) {
5
- node.dispatchEvent(new CustomEvent(`clickOutside`));
6
- if (cb)
7
- cb();
8
- }
9
- };
10
- document.addEventListener(`click`, dispatchOnClickOutside);
11
- return {
12
- destroy() {
13
- document.removeEventListener(`click`, dispatchOnClickOutside);
14
- },
15
- };
16
- }
@@ -1,18 +0,0 @@
1
- import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: {
4
- width?: string | number | undefined;
5
- height?: string | number | undefined;
6
- style?: string | undefined;
7
- };
8
- events: {
9
- [evt: string]: CustomEvent<any>;
10
- };
11
- slots: {};
12
- };
13
- export declare type ReadOnlyProps = typeof __propDef.props;
14
- export declare type ReadOnlyEvents = typeof __propDef.events;
15
- export declare type ReadOnlySlots = typeof __propDef.slots;
16
- export default class ReadOnly extends SvelteComponentTyped<ReadOnlyProps, ReadOnlyEvents, ReadOnlySlots> {
17
- }
18
- export {};