svelte-multiselect 3.1.0 → 3.1.1
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.
- package/MultiSelect.svelte +16 -9
- package/MultiSelect.svelte.d.ts +2 -2
- package/Wiggle.svelte +24 -0
- package/Wiggle.svelte.d.ts +25 -0
- package/actions.js +0 -23
- package/package.json +1 -1
- package/readme.md +61 -36
package/MultiSelect.svelte
CHANGED
|
@@ -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 = [];
|
|
@@ -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
|
|
@@ -209,7 +213,7 @@ display above those of another following shortly after it -->
|
|
|
209
213
|
<ul class="selected {ulSelectedClass}">
|
|
210
214
|
{#each selected as option, idx}
|
|
211
215
|
<li class={liSelectedClass}>
|
|
212
|
-
<slot name="
|
|
216
|
+
<slot name="renderSelected" {option} {idx}>
|
|
213
217
|
{option.label}
|
|
214
218
|
</slot>
|
|
215
219
|
{#if !readonly}
|
|
@@ -239,7 +243,9 @@ display above those of another following shortly after it -->
|
|
|
239
243
|
<ReadOnlyIcon height="14pt" />
|
|
240
244
|
{:else if selected.length > 0}
|
|
241
245
|
{#if maxSelect !== null && maxSelect > 1}
|
|
242
|
-
<
|
|
246
|
+
<Wiggle bind:wiggle angle={20}>
|
|
247
|
+
<span style="padding: 0 3pt;">{maxSelectMsg(selected.length, maxSelect)}</span>
|
|
248
|
+
</Wiggle>
|
|
243
249
|
{/if}
|
|
244
250
|
<button
|
|
245
251
|
type="button"
|
|
@@ -273,7 +279,7 @@ display above those of another following shortly after it -->
|
|
|
273
279
|
title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
|
|
274
280
|
class={liOptionClass}
|
|
275
281
|
>
|
|
276
|
-
<slot name="
|
|
282
|
+
<slot name="renderOptions" {option} {idx}>
|
|
277
283
|
{option.label}
|
|
278
284
|
</slot>
|
|
279
285
|
</li>
|
|
@@ -285,21 +291,23 @@ display above those of another following shortly after it -->
|
|
|
285
291
|
</div>
|
|
286
292
|
|
|
287
293
|
<style>
|
|
288
|
-
:where(.multiselect) {
|
|
294
|
+
:where(div.multiselect) {
|
|
289
295
|
position: relative;
|
|
290
296
|
margin: 1em 0;
|
|
291
297
|
border: var(--sms-border, 1pt solid lightgray);
|
|
292
298
|
border-radius: var(--sms-border-radius, 5pt);
|
|
299
|
+
background: var(--sms-input-bg);
|
|
300
|
+
height: var(--sms-input-height, 2em);
|
|
293
301
|
align-items: center;
|
|
294
302
|
min-height: 18pt;
|
|
295
303
|
display: flex;
|
|
296
304
|
cursor: text;
|
|
297
305
|
padding: 0 3pt;
|
|
298
306
|
}
|
|
299
|
-
:where(.multiselect:focus-within) {
|
|
307
|
+
:where(div.multiselect:focus-within) {
|
|
300
308
|
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
301
309
|
}
|
|
302
|
-
:where(.multiselect.readonly) {
|
|
310
|
+
:where(div.multiselect.readonly) {
|
|
303
311
|
background: var(--sms-readonly-bg, lightgray);
|
|
304
312
|
}
|
|
305
313
|
|
|
@@ -329,15 +337,14 @@ display above those of another following shortly after it -->
|
|
|
329
337
|
outline: none;
|
|
330
338
|
padding: 0 2pt;
|
|
331
339
|
}
|
|
332
|
-
:where(ul.selected > li button:hover, button.remove-all:hover) {
|
|
340
|
+
:where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
|
|
333
341
|
color: var(--sms-remove-x-hover-focus-color, lightskyblue);
|
|
334
342
|
}
|
|
335
343
|
:where(button:focus) {
|
|
336
|
-
color: var(--sms-remove-x-hover-focus-color, lightskyblue);
|
|
337
344
|
transform: scale(1.04);
|
|
338
345
|
}
|
|
339
346
|
|
|
340
|
-
:where(.multiselect input) {
|
|
347
|
+
:where(div.multiselect input) {
|
|
341
348
|
border: none;
|
|
342
349
|
outline: none;
|
|
343
350
|
background: none;
|
package/MultiSelect.svelte.d.ts
CHANGED
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,7 +5,7 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "3.1.
|
|
8
|
+
"version": "3.1.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
package/readme.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
- **Single / multiple select:** pass `maxSelect={1}` prop to only allow one selection
|
|
27
27
|
- **Dropdowns:** scrollable lists for large numbers of options
|
|
28
28
|
- **Searchable:** start typing to filter options
|
|
29
|
-
- **Tagging:** selected options are recorded as tags
|
|
29
|
+
- **Tagging:** selected options are recorded as tags in the input
|
|
30
30
|
- **Server-side rendering:** no reliance on browser objects like `window` or `document`
|
|
31
31
|
- **Configurable:** see [props](#props)
|
|
32
32
|
- **No dependencies:** needs only Svelte as dev dependency
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
## Recent breaking changes
|
|
36
36
|
|
|
37
|
-
- v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects
|
|
37
|
+
- 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`).
|
|
38
38
|
- v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
|
|
39
39
|
|
|
40
40
|
```js
|
|
@@ -108,19 +108,22 @@ Full list of props/bindable variables for this component:
|
|
|
108
108
|
|
|
109
109
|
`MultiSelect.svelte` accepts two named slots
|
|
110
110
|
|
|
111
|
-
- `slot="
|
|
112
|
-
- `slot="
|
|
111
|
+
- `slot="renderOptions"`
|
|
112
|
+
- `slot="renderSelected"`
|
|
113
113
|
|
|
114
114
|
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:
|
|
115
115
|
|
|
116
116
|
```svelte
|
|
117
|
-
<MultiSelect options={[`Banana`, `
|
|
118
|
-
<span let:idx let:option slot="
|
|
119
|
-
{idx + 1}. {option.label}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
>
|
|
117
|
+
<MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
|
|
118
|
+
<span let:idx let:option slot="renderOptions">
|
|
119
|
+
{idx + 1}. {option.label}
|
|
120
|
+
{option.label === `Mango` ? `🎉` : ``}
|
|
121
|
+
</span>
|
|
122
|
+
|
|
123
|
+
<span let:idx let:option slot="renderSelected">
|
|
124
|
+
#{idx + 1}
|
|
125
|
+
{option.label}
|
|
126
|
+
</span>
|
|
124
127
|
</MultiSelect>
|
|
125
128
|
```
|
|
126
129
|
|
|
@@ -152,7 +155,7 @@ to customize rendering individual options in the dropdown and the list of select
|
|
|
152
155
|
|
|
153
156
|
## TypeScript
|
|
154
157
|
|
|
155
|
-
TypeScript users can import the types used for internal type safety
|
|
158
|
+
TypeScript users can import the types used for internal type safety:
|
|
156
159
|
|
|
157
160
|
```svelte
|
|
158
161
|
<script lang="ts">
|
|
@@ -171,26 +174,48 @@ TypeScript users can import the types used for internal type safety for external
|
|
|
171
174
|
|
|
172
175
|
## Styling
|
|
173
176
|
|
|
174
|
-
There are 3 ways to style this component.
|
|
177
|
+
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:
|
|
178
|
+
|
|
179
|
+
```svelte
|
|
180
|
+
<div class="multiselect">
|
|
181
|
+
<ul class="selected">
|
|
182
|
+
<li>Selected 1</li>
|
|
183
|
+
<li>Selected 2</li>
|
|
184
|
+
</ul>
|
|
185
|
+
<ul class="options">
|
|
186
|
+
<li>Option 1</li>
|
|
187
|
+
<li>Option 2</li>
|
|
188
|
+
</ul>
|
|
189
|
+
</div>
|
|
190
|
+
```
|
|
175
191
|
|
|
176
192
|
### With CSS variables
|
|
177
193
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- `
|
|
181
|
-
- `border
|
|
182
|
-
- `
|
|
183
|
-
- `
|
|
184
|
-
- `
|
|
185
|
-
- `
|
|
186
|
-
- `
|
|
187
|
-
- `
|
|
188
|
-
- `
|
|
189
|
-
- `
|
|
190
|
-
- `
|
|
191
|
-
- `
|
|
192
|
-
- `
|
|
193
|
-
- `
|
|
194
|
+
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.
|
|
195
|
+
|
|
196
|
+
- `div.multiselect`:
|
|
197
|
+
- `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.
|
|
198
|
+
- `border-radius: var(--sms-border-radius, 5pt)`: Input border radius.
|
|
199
|
+
- `background: var(--sms-input-bg)`: Input background.
|
|
200
|
+
- `height: var(--sms-input-height, 2em)`: Input height.
|
|
201
|
+
- `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`.
|
|
202
|
+
- `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state.
|
|
203
|
+
- `div.multiselect input`
|
|
204
|
+
- `color: var(--sms-text-color, inherit)`: Input text color.
|
|
205
|
+
- `ul.selected > li`:
|
|
206
|
+
- `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
|
|
207
|
+
- `ul.selected > li button:hover, button.remove-all:hover`
|
|
208
|
+
- `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.
|
|
209
|
+
- `ul.options`
|
|
210
|
+
- `background: var(--sms-options-bg, white)`: Background of options list.
|
|
211
|
+
- `ul.options > li.selected`
|
|
212
|
+
- `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
|
|
213
|
+
- `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
|
|
214
|
+
- `ul.options > li.active`
|
|
215
|
+
- `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item.
|
|
216
|
+
- `ul.options > li.disabled`
|
|
217
|
+
- `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
|
|
218
|
+
- `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
|
|
194
219
|
|
|
195
220
|
For example, to change the background color of the options dropdown:
|
|
196
221
|
|
|
@@ -211,14 +236,14 @@ The second method allows you to pass in custom classes to the important DOM elem
|
|
|
211
236
|
This simplified version of the DOM structure of this component shows where these classes are inserted:
|
|
212
237
|
|
|
213
238
|
```svelte
|
|
214
|
-
<div class={outerDivClass}>
|
|
215
|
-
<ul class={ulSelectedClass}>
|
|
216
|
-
<li class={liSelectedClass}>
|
|
217
|
-
<li class={liSelectedClass}>
|
|
239
|
+
<div class="multiselect {outerDivClass}">
|
|
240
|
+
<ul class="selected {ulSelectedClass}">
|
|
241
|
+
<li class={liSelectedClass}>Selected 1</li>
|
|
242
|
+
<li class={liSelectedClass}>Selected 2</li>
|
|
218
243
|
</ul>
|
|
219
|
-
<ul class={ulOptionsClass}>
|
|
220
|
-
<li class={liOptionClass}>
|
|
221
|
-
<li class={liOptionClass}>
|
|
244
|
+
<ul class="options {ulOptionsClass}">
|
|
245
|
+
<li class={liOptionClass}>Option 1</li>
|
|
246
|
+
<li class={liOptionClass}>Option 2</li>
|
|
222
247
|
</ul>
|
|
223
248
|
</div>
|
|
224
249
|
```
|