svelte-multiselect 4.0.2 → 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.
@@ -14,6 +14,7 @@ export let disabled = false;
14
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,6 +31,7 @@ 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
37
  export let defaultDisabledTitle = `This option is disabled`;
@@ -136,8 +138,8 @@ function setOptionsVisible(show) {
136
138
  }
137
139
  // handle all keyboard events this component receives
138
140
  async function handleKeydown(event) {
139
- // on escape: dismiss options dropdown and reset search text
140
- 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`) {
141
143
  setOptionsVisible(false);
142
144
  searchText = ``;
143
145
  }
@@ -208,9 +210,18 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
208
210
  };
209
211
  </script>
210
212
 
213
+ <svelte:window
214
+ on:click={(event) => {
215
+ if (outerDiv && !outerDiv.contains(event.target)) {
216
+ setOptionsVisible(false)
217
+ }
218
+ }}
219
+ />
220
+
211
221
  <!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
212
222
  display above those of another following shortly after it -->
213
223
  <div
224
+ bind:this={outerDiv}
214
225
  class:disabled
215
226
  class:single={maxSelect === 1}
216
227
  class:open={showOptions}
@@ -219,16 +230,15 @@ display above those of another following shortly after it -->
219
230
  class:invalid
220
231
  class="multiselect {outerDivClass}"
221
232
  on:mouseup|stopPropagation={() => setOptionsVisible(true)}
222
- on:focusout={() => setOptionsVisible(false)}
223
233
  title={disabled ? disabledTitle : null}
224
234
  aria-disabled={disabled ? `true` : null}
225
235
  >
226
- <!-- invisible input, used only to prevent form submission if required=true and no options selected -->
227
236
  <input
228
237
  {required}
229
238
  bind:value={formValue}
230
239
  tabindex="-1"
231
240
  aria-hidden="true"
241
+ aria-label="ignore this, used only to prevent form submission if select is required but empty"
232
242
  class="form-control"
233
243
  on:invalid={() => (invalid = true)}
234
244
  />
@@ -253,13 +263,13 @@ display above those of another following shortly after it -->
253
263
  {/each}
254
264
  <li style="display: contents;">
255
265
  <input
266
+ class={inputClass}
256
267
  bind:this={input}
257
268
  {autocomplete}
258
269
  bind:value={searchText}
259
270
  on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
260
271
  on:keydown={handleKeydown}
261
272
  on:focus={() => setOptionsVisible(true)}
262
- on:blur={() => setOptionsVisible(false)}
263
273
  {id}
264
274
  {name}
265
275
  {disabled}
@@ -350,13 +360,14 @@ display above those of another following shortly after it -->
350
360
  align-items: center;
351
361
  display: flex;
352
362
  cursor: text;
353
- padding: 0 3pt;
354
363
  border: var(--sms-border, 1pt solid lightgray);
355
364
  border-radius: var(--sms-border-radius, 3pt);
356
- background: var(--sms-input-bg);
357
- min-height: var(--sms-input-min-height, 22pt);
365
+ background: var(--sms-bg);
366
+ max-width: var(--sms-max-width);
367
+ padding: var(--sms-padding, 0 3pt);
358
368
  color: var(--sms-text-color);
359
369
  font-size: var(--sms-font-size, inherit);
370
+ min-height: var(--sms-min-height, 19pt);
360
371
  }
361
372
  :where(div.multiselect.open) {
362
373
  z-index: var(--sms-open-z-index, 4);
@@ -378,15 +389,14 @@ display above those of another following shortly after it -->
378
389
  }
379
390
  :where(div.multiselect > ul.selected > li) {
380
391
  align-items: center;
381
- border-radius: 4pt;
392
+ border-radius: 3pt;
382
393
  display: flex;
383
394
  margin: 2pt;
384
395
  line-height: normal;
385
- padding: 1pt 5pt;
386
396
  transition: 0.3s;
387
397
  white-space: nowrap;
388
398
  background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
389
- height: var(--sms-selected-li-height);
399
+ padding: var(--sms-selected-li-padding, 1pt 5pt);
390
400
  color: var(--sms-selected-text-color, var(--sms-text-color));
391
401
  }
392
402
  :where(div.multiselect button) {
@@ -13,6 +13,7 @@ declare const __propDef: {
13
13
  disabledTitle?: string | undefined;
14
14
  options: ProtoOption[];
15
15
  input?: HTMLInputElement | null | undefined;
16
+ outerDiv?: HTMLDivElement | null | undefined;
16
17
  placeholder?: string | undefined;
17
18
  id?: string | undefined;
18
19
  name?: string | undefined;
@@ -25,6 +26,7 @@ declare const __propDef: {
25
26
  ulOptionsClass?: string | undefined;
26
27
  liOptionClass?: string | undefined;
27
28
  liActiveOptionClass?: string | undefined;
29
+ inputClass?: string | undefined;
28
30
  removeBtnTitle?: string | undefined;
29
31
  removeAllTitle?: string | undefined;
30
32
  defaultDisabledTitle?: string | undefined;
package/package.json CHANGED
@@ -5,37 +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.2",
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
13
  "@sveltejs/adapter-static": "^1.0.0-next.29",
14
- "@sveltejs/kit": "^1.0.0-next.295",
15
- "@sveltejs/vite-plugin-svelte": "^1.0.0-next.39",
16
- "@typescript-eslint/eslint-plugin": "^5.14.0",
17
- "@typescript-eslint/parser": "^5.14.0",
18
- "@vitest/ui": "^0.6.0",
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
19
  "eslint": "^8.11.0",
20
20
  "eslint-plugin-svelte3": "^3.4.1",
21
21
  "hastscript": "^7.0.2",
22
22
  "jsdom": "^19.0.0",
23
23
  "mdsvex": "^0.10.5",
24
- "playwright": "^1.19.2",
25
- "prettier": "^2.5.1",
24
+ "playwright": "^1.20.0",
25
+ "prettier": "^2.6.0",
26
26
  "prettier-plugin-svelte": "^2.6.0",
27
27
  "rehype-autolink-headings": "^6.1.1",
28
28
  "rehype-slug": "^5.0.1",
29
29
  "svelte": "^3.46.4",
30
- "svelte-check": "^2.4.5",
30
+ "svelte-check": "^2.4.6",
31
31
  "svelte-github-corner": "^0.1.0",
32
32
  "svelte-preprocess": "^4.10.4",
33
- "svelte-toc": "^0.2.7",
34
- "svelte2tsx": "^0.5.5",
33
+ "svelte-toc": "^0.2.8",
34
+ "svelte2tsx": "^0.5.6",
35
35
  "tslib": "^2.3.1",
36
36
  "typescript": "^4.6.2",
37
37
  "vite": "^2.8.6",
38
- "vitest": "^0.6.0"
38
+ "vitest": "^0.7.9"
39
39
  },
40
40
  "keywords": [
41
41
  "svelte",
package/readme.md CHANGED
@@ -13,7 +13,13 @@
13
13
 
14
14
  </h4>
15
15
 
16
- **Keyboard-friendly, zero-dependency multi-select Svelte component.** <strong class="hide-in-docs"><a href="https://svelte-multiselect.netlify.app">Live demo</a></strong>
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>
17
23
 
18
24
  <slot name="examples" />
19
25
 
@@ -48,6 +54,8 @@
48
54
 
49
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}`.
50
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
@@ -93,7 +101,8 @@ Full list of props/bindable variables for this component:
93
101
  | `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
94
102
  | `disabledTitle` | `This field is disabled` | Tooltip text to display on hover when the component is in `disabled` state. |
95
103
  | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
96
- | `input` | `undefined` | Handle to the `<input>` DOM node. |
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). |
97
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. |
98
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>`. |
99
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. |
@@ -137,15 +146,17 @@ Full list of props/bindable variables for this component:
137
146
  Example:
138
147
 
139
148
  ```svelte
140
- <MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
149
+ <MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]}>
141
150
  <span let:idx let:option slot="option">
142
- {idx + 1}. {option.label}
143
- {option.label === `Mango` ? `🎉` : ``}
151
+ {idx + 1}
152
+ {option.label}
153
+ <span style:background={option.label} style=" width: 1em; height: 1em;" />
144
154
  </span>
145
155
 
146
156
  <span let:idx let:option slot="selected">
147
- #{idx + 1}
157
+ {idx + 1}
148
158
  {option.label}
159
+ <span style:background={option.label} style=" width: 1em; height: 1em;" />
149
160
  </span>
150
161
 
151
162
  <CustomSpinner slot="spinner">
@@ -156,13 +167,13 @@ Example:
156
167
 
157
168
  `MultiSelect.svelte` dispatches the following events:
158
169
 
159
- | name | detail | description |
160
- | ----------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
161
- | `add` | `{ option: Option }` | Triggers when a new option is selected. |
162
- | `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
163
- | `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
164
- | `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. |
165
- | `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. |
166
177
 
167
178
  ### Examples
168
179
 
@@ -221,19 +232,22 @@ If you only want to make small adjustments, you can pass the following CSS varia
221
232
  - `div.multiselect`
222
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.
223
234
  - `border-radius: var(--sms-border-radius, 3pt)`
224
- - `background: var(--sms-input-bg)`
225
- - `height: var(--sms-input-height, 2em)`
235
+ - `padding: var(--sms-padding, 0 3pt)`
236
+ - `background: var(--sms-bg)`
226
237
  - `color: var(--sms-text-color)`
227
- - `color: var(--sms-placeholder-color)`
238
+ - `min-height: var(--sms-min-height)`
239
+ - `max-width: var(--sms-max-width)`
228
240
  - `div.multiselect.open`
229
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.
230
242
  - `div.multiselect:focus-within`
231
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`.
232
244
  - `div.multiselect.disabled`
233
245
  - `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
246
+ - `div.multiselect input::placeholder`
247
+ - `color: var(--sms-placeholder-color)`
234
248
  - `div.multiselect > ul.selected > li`
235
249
  - `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
236
- - `height: var(--sms-selected-li-height)`: Height of selected options.
250
+ - `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
237
251
  - `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
238
252
  - `ul.selected > li button:hover, button.remove-all:hover, button:focus`
239
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.
@@ -274,6 +288,7 @@ This simplified version of the DOM structure of this component shows where these
274
288
 
275
289
  ```svelte
276
290
  <div class="multiselect {outerDivClass}">
291
+ <input class={inputClass} />
277
292
  <ul class="selected {ulSelectedClass}">
278
293
  <li class={liSelectedClass}>Selected 1</li>
279
294
  <li class={liSelectedClass}>Selected 2</li>