svelte-multiselect 3.2.3 → 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;
@@ -26,6 +28,7 @@ export let ulSelectedClass = ``;
26
28
  export let liSelectedClass = ``;
27
29
  export let ulOptionsClass = ``;
28
30
  export let liOptionClass = ``;
31
+ export let liActiveOptionClass = ``;
29
32
  export let removeBtnTitle = `Remove`;
30
33
  export let removeAllTitle = `Remove all`;
31
34
  // https://github.com/sveltejs/svelte/issues/6964
@@ -41,6 +44,7 @@ onMount(() => {
41
44
  selected = _options.filter((op) => op?.preselected);
42
45
  });
43
46
  let wiggle = false;
47
+ const dispatch = createEventDispatcher();
44
48
  function isObject(item) {
45
49
  return typeof item === `object` && !Array.isArray(item) && item !== null;
46
50
  }
@@ -67,9 +71,6 @@ $: if (new Set(labels).size !== options.length) {
67
71
  }
68
72
  $: selectedLabels = selected.map((op) => op.label);
69
73
  $: selectedValues = selected.map((op) => op.value);
70
- const dispatch = createEventDispatcher();
71
- let searchText = ``;
72
- let showOptions = false;
73
74
  // options matching the current search text
74
75
  $: matchingOptions = _options.filter((op) => filterFunc(op, searchText));
75
76
  $: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
@@ -87,7 +88,7 @@ function add(label) {
87
88
  if (!readonly &&
88
89
  !selectedLabels.includes(label) &&
89
90
  // for maxselect = 1 we always replace current option with new selection
90
- (maxSelect == null || maxSelect == 1 || selected.length < maxSelect)) {
91
+ (maxSelect === null || maxSelect === 1 || selected.length < maxSelect)) {
91
92
  searchText = ``; // reset search string on selection
92
93
  const option = _options.find((op) => op.label === label);
93
94
  if (!option) {
@@ -198,10 +199,10 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
198
199
  <!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
199
200
  display above those of another following shortly after it -->
200
201
  <div
201
- class="multiselect {outerDivClass}"
202
202
  class:readonly
203
- class:single={maxSelect == 1}
203
+ class:single={maxSelect === 1}
204
204
  class:open={showOptions}
205
+ class="multiselect {outerDivClass}"
205
206
  on:mouseup|stopPropagation={() => setOptionsVisible(true)}
206
207
  use:onClickOutside={() => setOptionsVisible(false)}
207
208
  use:onClickOutside={() => dispatch(`blur`)}
@@ -242,42 +243,48 @@ display above those of another following shortly after it -->
242
243
  {#if readonly}
243
244
  <ReadOnlyIcon height="14pt" />
244
245
  {:else if selected.length > 0}
245
- {#if maxSelect !== null && maxSelectMsg !== null}
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}
@@ -371,12 +378,14 @@ display above those of another following shortly after it -->
371
378
  max-height: 50vh;
372
379
  padding: 0;
373
380
  top: 100%;
381
+ left: 0;
374
382
  width: 100%;
375
383
  position: absolute;
376
384
  border-radius: 1ex;
377
385
  overflow: auto;
378
386
  background: var(--sms-options-bg, white);
379
387
  overscroll-behavior: var(--sms-options-overscroll, none);
388
+ box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
380
389
  }
381
390
  :where(div.multiselect > ul.options.hidden) {
382
391
  visibility: hidden;
@@ -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;
@@ -21,6 +23,7 @@ declare const __propDef: {
21
23
  liSelectedClass?: string | undefined;
22
24
  ulOptionsClass?: string | undefined;
23
25
  liOptionClass?: string | undefined;
26
+ liActiveOptionClass?: string | undefined;
24
27
  removeBtnTitle?: string | undefined;
25
28
  removeAllTitle?: string | undefined;
26
29
  defaultDisabledTitle?: string | undefined;
package/package.json CHANGED
@@ -5,13 +5,13 @@
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.3",
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
13
  "@sveltejs/adapter-static": "^1.0.0-next.28",
14
- "@sveltejs/kit": "^1.0.0-next.269",
14
+ "@sveltejs/kit": "^1.0.0-next.278",
15
15
  "@typescript-eslint/eslint-plugin": "^5.12.0",
16
16
  "@typescript-eslint/parser": "^5.12.0",
17
17
  "eslint": "^8.9.0",
@@ -23,14 +23,14 @@
23
23
  "rehype-autolink-headings": "^6.1.1",
24
24
  "rehype-slug": "^5.0.1",
25
25
  "svelte": "^3.46.4",
26
- "svelte-check": "^2.4.3",
26
+ "svelte-check": "^2.4.5",
27
27
  "svelte-github-corner": "^0.1.0",
28
28
  "svelte-preprocess": "^4.10.3",
29
- "svelte-toc": "^0.2.5",
30
- "svelte2tsx": "^0.5.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.8.2"
33
+ "vite": "^2.8.4"
34
34
  },
35
35
  "keywords": [
36
36
  "svelte",
package/readme.md CHANGED
@@ -88,19 +88,22 @@ 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
- | `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
97
- | `selectedLabels` | `[]` | Labels of currently selected options. |
98
- | `selectedValues` | `[]` | Values of currently selected options. |
99
- | `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
100
- | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
101
- | `input` | `undefined` | Handle to the `<input>` DOM node. |
102
- | `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. |
103
- | `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>`. |
104
107
 
105
108
  </div>
106
109
 
@@ -115,7 +118,7 @@ Full list of props/bindable variables for this component:
115
118
  }
116
119
  ```
117
120
 
118
- 2. `maxSelectMsg = (current: number, max: number) => string`: Inform the user how many of the maximum allowed options they have currently selected. Return empty string to disable, i.e. `() => ''`. Is automatically disabled when `maxSelect === null`. Defaults to:
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:
119
122
 
120
123
  ```ts
121
124
  maxSelectMsg = (current: number, max: number) => `${current}/${max}`
@@ -231,6 +234,7 @@ If you only want to make small adjustments, you can pass the following CSS varia
231
234
  - `div.multiselect > ul.options`
232
235
  - `background: var(--sms-options-bg, white)`: Background of dropdown list.
233
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.
234
238
  - `div.multiselect > ul.options > li.selected`
235
239
  - `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))`
236
240
  - `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
@@ -258,6 +262,7 @@ The second method allows you to pass in custom classes to the important DOM elem
258
262
  - `liSelectedClass`
259
263
  - `ulOptionsClass`
260
264
  - `liOptionClass`
265
+ - `liActiveOptionClass`
261
266
 
262
267
  This simplified version of the DOM structure of this component shows where these classes are inserted:
263
268
 
@@ -269,7 +274,9 @@ This simplified version of the DOM structure of this component shows where these
269
274
  </ul>
270
275
  <ul class="options {ulOptionsClass}">
271
276
  <li class={liOptionClass}>Option 1</li>
272
- <li class={liOptionClass}>Option 2</li>
277
+ <li class="{liOptionClass} {liActiveOptionClass}">
278
+ Option 2 (currently active)
279
+ </li>
273
280
  </ul>
274
281
  </div>
275
282
  ```