svelte-5-select 1.0.0 → 2.0.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.
- package/README.md +239 -109
- package/dist/ChevronIcon.svelte +8 -13
- package/dist/ClearIcon.svelte +3 -11
- package/dist/LoadingIcon.svelte +9 -2
- package/dist/Select.svelte +955 -437
- package/dist/Select.svelte.d.ts +37 -5
- package/dist/aria-handlers.svelte.d.ts +4 -1
- package/dist/aria-handlers.svelte.js +19 -8
- package/dist/filter.d.ts +10 -2
- package/dist/filter.js +14 -7
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -2
- package/dist/keyboard-navigation.svelte.d.ts +7 -2
- package/dist/keyboard-navigation.svelte.js +285 -47
- package/dist/no-styles/ChevronIcon.svelte +11 -0
- package/dist/no-styles/ChevronIcon.svelte.d.ts +26 -0
- package/dist/no-styles/ClearIcon.svelte +8 -0
- package/dist/no-styles/ClearIcon.svelte.d.ts +26 -0
- package/dist/no-styles/LoadingIcon.svelte +13 -0
- package/dist/no-styles/LoadingIcon.svelte.d.ts +26 -0
- package/dist/no-styles/Select.svelte +1383 -0
- package/dist/no-styles/Select.svelte.d.ts +38 -0
- package/dist/select-state.svelte.d.ts +15 -0
- package/dist/select-state.svelte.js +161 -0
- package/dist/styles/default.css +112 -71
- package/dist/tailwind.css +120 -17
- package/dist/types.d.ts +459 -129
- package/dist/use-hover.svelte.d.ts +3 -7
- package/dist/use-hover.svelte.js +91 -36
- package/dist/use-load-options.svelte.d.ts +18 -3
- package/dist/use-load-options.svelte.js +333 -42
- package/dist/use-value.svelte.d.ts +2 -11
- package/dist/use-value.svelte.js +322 -111
- package/dist/utils.d.ts +19 -8
- package/dist/utils.js +52 -8
- package/package.json +117 -95
package/dist/types.d.ts
CHANGED
|
@@ -1,205 +1,535 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
3
|
import type { ComputePositionConfig } from 'svelte-floating-ui/dom';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The bound for user item types. Deliberately `Record<string, any>` rather than
|
|
6
|
+
* {@link SelectItem}: interface-declared item types have no implicit index
|
|
7
|
+
* signature, so they satisfy this bound but not `SelectItem` itself.
|
|
8
|
+
*/
|
|
9
|
+
export type ItemLike = Record<string, any>;
|
|
10
|
+
/**
|
|
11
|
+
* The payload shape of `onValueChange`/`onSelectionChange`: an item array in
|
|
12
|
+
* multiple mode, a single item or null otherwise. `Multiple` defaults to
|
|
13
|
+
* `boolean`, which distributes to the loose `Item[] | Item | null` union.
|
|
14
|
+
*/
|
|
15
|
+
export type SelectValue<Item extends ItemLike = SelectItem, Multiple extends boolean = boolean> = Multiple extends true ? Item[] : Item | null;
|
|
16
|
+
/**
|
|
17
|
+
* The bindable `value` prop shape: raw string ids are also accepted and are
|
|
18
|
+
* normalized into items against `items`.
|
|
19
|
+
*
|
|
20
|
+
* The component always *writes* `undefined` for an empty selection — in both single
|
|
21
|
+
* and multiple mode, and on every clear path (the clear button, removing the last
|
|
22
|
+
* tag, a `loadOptionsDeps` reload invalidating the value, a multiple→single switch).
|
|
23
|
+
* `null` is accepted on the way in, so an existing `bind:value` initialized to `null`
|
|
24
|
+
* keeps working; read an emptied value as falsy rather than testing `=== null`.
|
|
25
|
+
* (The `onValueChange`/`onSelectionChange` payload is a separate contract — see
|
|
26
|
+
* {@link SelectValue} — and reports `null` in single mode and `[]` in multiple mode.)
|
|
27
|
+
*/
|
|
28
|
+
export type SelectValueProp<Item extends ItemLike = SelectItem, Multiple extends boolean = boolean> = Multiple extends true ? Item[] | string[] | null | undefined : Item | string | null | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The payload of `onclear`. Clearing the whole selection passes the full removed
|
|
31
|
+
* value (an `Item[]` in multiple mode); removing a single tag in multiple mode
|
|
32
|
+
* passes just that removed entry. Discriminated by `Multiple` so single mode never
|
|
33
|
+
* has to account for an array, and one-tag removal is only in the multiple branch.
|
|
34
|
+
* Raw string ids are included because `value` accepts them.
|
|
35
|
+
*/
|
|
36
|
+
export type SelectClearValue<Item extends ItemLike = SelectItem, Multiple extends boolean = boolean> = Multiple extends true ? Item[] | string[] | Item | string | null : Item | string | null;
|
|
37
|
+
/** Payload of the `onerror` callback. `type` discriminates the failure source; `loadOptions` (a rejected loader promise) is currently the only emitter, with the rejection reason in `details`. */
|
|
38
|
+
export interface SelectErrorEvent {
|
|
39
|
+
type: 'loadOptions';
|
|
6
40
|
details: unknown;
|
|
7
41
|
}
|
|
8
|
-
|
|
9
|
-
|
|
42
|
+
/** The configuration object a custom `filter` prop receives — a snapshot of everything the built-in pipeline uses. See the exported `filter` for the default implementation to delegate to or replace. */
|
|
43
|
+
export interface FilterConfig<Item extends ItemLike = SelectItem> {
|
|
44
|
+
/** The `loadOptions` prop, if set. When present, results are already filtered remotely — the default pipeline skips `itemFilter` entirely. */
|
|
45
|
+
loadOptions?: (filterText: string) => Promise<Item[] | string[]>;
|
|
46
|
+
/** The current filter text. */
|
|
10
47
|
filterText: string;
|
|
11
|
-
items
|
|
48
|
+
/** The `items` prop as supplied — entries may still be raw strings; pass them through `convertStringItemsToObjects` before reading item fields. */
|
|
49
|
+
items: Item[] | string[] | null;
|
|
50
|
+
/** The `multiple` prop. */
|
|
12
51
|
multiple: boolean;
|
|
13
|
-
|
|
52
|
+
/** The current (normalized) selection; raw string entries have already been synthesized into `SelectItem`s. Used with `filterSelectedItems` to hide picked options. */
|
|
53
|
+
value: Item | SelectItem | (Item | SelectItem)[] | null | undefined;
|
|
54
|
+
/** The `itemId` prop — the field name selections are compared by. */
|
|
14
55
|
itemId: string;
|
|
15
|
-
|
|
56
|
+
/** The `groupBy` prop, if set. */
|
|
57
|
+
groupBy?: (item: Item) => string;
|
|
58
|
+
/** The `label` prop — the field name option text is read from. */
|
|
16
59
|
label: string;
|
|
60
|
+
/** The `filterSelectedItems` prop: hide already-selected options in multiple mode. */
|
|
17
61
|
filterSelectedItems: boolean;
|
|
18
|
-
|
|
62
|
+
/** The per-item match predicate (the `itemFilter` prop). The default pipeline skips it when `loadOptions` is set. */
|
|
63
|
+
itemFilter: (label: string, filterText: string, option: Item) => boolean;
|
|
64
|
+
/** Synthesizes `{ value, label, index }` items from raw string entries. */
|
|
19
65
|
convertStringItemsToObjects: (items: string[]) => SelectItem[];
|
|
20
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Transforms the flat filtered list into a grouped one (inserting headers and
|
|
68
|
+
* applying the `groupBy`/`groupFilter` props). Distinct from the `groupFilter`
|
|
69
|
+
* prop, which only reorders/filters the group keys this transform consumes.
|
|
70
|
+
*/
|
|
71
|
+
applyGrouping: (items: SelectItem[]) => SelectItem[];
|
|
21
72
|
}
|
|
73
|
+
/** The `floatingConfig` prop shape: floating-ui's `computePosition` options plus `autoUpdate` to reposition on scroll/resize. */
|
|
22
74
|
export interface FloatingConfig extends Partial<ComputePositionConfig> {
|
|
23
75
|
autoUpdate?: boolean;
|
|
24
76
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The `justValue` shape: bare `itemId` values instead of whole items. Includes
|
|
79
|
+
* `undefined` because that is what the component writes for an emptied selection —
|
|
80
|
+
* on every clear path, mirroring `value` (see {@link SelectValueProp}). `null` and
|
|
81
|
+
* `[]` are accepted on the way in but never written back.
|
|
82
|
+
*/
|
|
83
|
+
export type JustValue = string | number | string[] | number[] | null | undefined;
|
|
84
|
+
/** The default item shape (and the shape synthesized for raw string items). Your own item type does not need to extend it — any {@link ItemLike} works; these are just the fields the component reads when present. */
|
|
85
|
+
export interface SelectItem {
|
|
86
|
+
value?: unknown;
|
|
87
|
+
label?: string;
|
|
88
|
+
index?: number;
|
|
89
|
+
group?: string;
|
|
90
|
+
groupHeader?: boolean;
|
|
91
|
+
groupItem?: boolean;
|
|
92
|
+
selectable?: boolean;
|
|
93
|
+
id?: string | number;
|
|
94
|
+
[key: string]: unknown;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A group header row synthesized by `groupBy` and injected into the rendered list.
|
|
98
|
+
* It is not one of your items — it carries the group's own value and label plus the
|
|
99
|
+
* marker fields, and nothing else — so it cannot be read as an `Item`.
|
|
100
|
+
*/
|
|
101
|
+
export interface SelectGroupHeader extends SelectItem {
|
|
102
|
+
groupHeader: true;
|
|
103
|
+
selectable: boolean;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A row of the rendered list. With `groupBy` set the list interleaves your items with
|
|
107
|
+
* synthesized {@link SelectGroupHeader} rows, so everything that reads the rendered
|
|
108
|
+
* list — `getFilteredItems()`, `onfilter`, `listSnippet`, `itemSnippet` — sees this
|
|
109
|
+
* union rather than a bare `Item`. Narrow it with {@link isGroupHeader}:
|
|
110
|
+
*
|
|
111
|
+
* ```svelte
|
|
112
|
+
* {#snippet itemSnippet(row)}
|
|
113
|
+
* {#if isGroupHeader(row)}
|
|
114
|
+
* <strong>{row.label}</strong>
|
|
115
|
+
* {:else}
|
|
116
|
+
* {row.name} <!-- row is your Item here -->
|
|
117
|
+
* {/if}
|
|
118
|
+
* {/snippet}
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* Without `groupBy` no headers are ever produced, but the type cannot know that
|
|
122
|
+
* statically — the guard is a one-line narrow in that case.
|
|
123
|
+
*/
|
|
124
|
+
export type SelectRow<Item extends ItemLike = SelectItem> = Item | SelectGroupHeader;
|
|
125
|
+
/**
|
|
126
|
+
* The single reactive state object shared by Select.svelte and its composables.
|
|
127
|
+
* Prop-backed fields are live accessors over the component's `$props()` bindings;
|
|
128
|
+
* the rest is internal shared state owned by `createSelectState`. Reading a field
|
|
129
|
+
* tracks only that field's signal.
|
|
130
|
+
*/
|
|
131
|
+
export interface SelectState<Item extends ItemLike = SelectItem> {
|
|
132
|
+
value: Item | Item[] | string | string[] | null | undefined;
|
|
133
|
+
items: Item[] | string[] | null;
|
|
134
|
+
filterText: string;
|
|
135
|
+
justValue: JustValue;
|
|
136
|
+
listOpen: boolean;
|
|
137
|
+
loading: boolean;
|
|
138
|
+
focused: boolean;
|
|
139
|
+
hoverItemIndex: number;
|
|
140
|
+
readonly multiple: boolean;
|
|
141
|
+
readonly itemId: string;
|
|
142
|
+
readonly label: string;
|
|
143
|
+
readonly searchable: boolean;
|
|
144
|
+
readonly disabled: boolean;
|
|
145
|
+
readonly useJustValue: boolean;
|
|
146
|
+
readonly closeListOnChange: boolean;
|
|
147
|
+
readonly debounceWait: number;
|
|
148
|
+
readonly groupBy: ((item: Item) => string) | undefined;
|
|
149
|
+
readonly loadOptions: ((filterText: string) => Promise<Item[] | string[]>) | undefined;
|
|
150
|
+
readonly loadOptionsDeps: unknown[];
|
|
151
|
+
readonly filteredItems: SelectItem[];
|
|
152
|
+
readonly normalizedValue: SelectItem | SelectItem[] | null;
|
|
153
|
+
readonly hasValue: boolean;
|
|
154
|
+
activeValue: number | undefined;
|
|
155
|
+
isScrolling: boolean;
|
|
156
|
+
clearState: boolean;
|
|
157
|
+
prevValue: Item | Item[] | string | string[] | null | undefined;
|
|
158
|
+
prevFilterText: string | undefined;
|
|
159
|
+
prevMultiple: boolean | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* One-shot handshake between type-ahead and the hover effects: set when a
|
|
162
|
+
* type-ahead keypress opens the list *and* parks hover on its match, so the
|
|
163
|
+
* open-with-value effect must not snap hover back to the selected value.
|
|
164
|
+
* Consumed (cleared) on the next run of that effect. Deliberately
|
|
165
|
+
* non-reactive scratch, like the prev* fields.
|
|
166
|
+
*/
|
|
167
|
+
suppressValueHoverSnap: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Whether the user has expressed commit-intent since the list last opened:
|
|
170
|
+
* moved the option cursor (arrow/Home/End/Page keys, type-ahead), typed in
|
|
171
|
+
* the input, or moved the pointer over the open list (mousemove, not
|
|
172
|
+
* mouseover — browsers synthesize mouseover for content appearing under a
|
|
173
|
+
* stationary cursor). Tab only commits the hovered option when this is set —
|
|
174
|
+
* the cursor auto-parks on an option the moment the list opens, and a bare
|
|
175
|
+
* open-then-Tab must close without selecting. Reset whenever the list
|
|
176
|
+
* closes. Deliberately non-reactive scratch, like the prev* fields.
|
|
177
|
+
*/
|
|
178
|
+
userNavigatedSinceOpen: boolean;
|
|
179
|
+
}
|
|
180
|
+
/** The subset of {@link SelectState} that keyboard navigation needs; any object with these fields works. */
|
|
181
|
+
export interface KeyboardNavigationState<Item extends ItemLike = SelectItem> {
|
|
182
|
+
listOpen: boolean;
|
|
183
|
+
readonly filteredItems: SelectItem[];
|
|
184
|
+
hoverItemIndex: number;
|
|
185
|
+
readonly multiple: boolean;
|
|
186
|
+
readonly value: Item | Item[] | string | string[] | null | undefined;
|
|
187
|
+
readonly filterText: string;
|
|
188
|
+
activeValue: number | undefined;
|
|
189
|
+
readonly itemId: string;
|
|
190
|
+
readonly label: string;
|
|
191
|
+
readonly searchable: boolean;
|
|
192
|
+
readonly focused: boolean;
|
|
193
|
+
readonly disabled: boolean;
|
|
194
|
+
/** See {@link SelectState.suppressValueHoverSnap} — written by type-ahead when it opens the list. */
|
|
195
|
+
suppressValueHoverSnap: boolean;
|
|
196
|
+
/** See {@link SelectState.userNavigatedSinceOpen} — gates Tab's commit-on-close. */
|
|
197
|
+
userNavigatedSinceOpen: boolean;
|
|
198
|
+
}
|
|
199
|
+
export interface KeyboardNavigationActions {
|
|
40
200
|
closeList: () => void;
|
|
41
201
|
setHoverIndex: (increment: number) => void;
|
|
42
202
|
handleSelect: (item: SelectItem) => void;
|
|
43
|
-
handleMultiItemClear: (index: number) => Promise<void>;
|
|
203
|
+
handleMultiItemClear: (index: number) => void | Promise<void>;
|
|
44
204
|
}
|
|
45
|
-
export interface
|
|
46
|
-
getState: () => {
|
|
47
|
-
listOpen: boolean;
|
|
48
|
-
filteredItems: SelectItem[];
|
|
49
|
-
hoverItemIndex: number;
|
|
50
|
-
multiple: boolean;
|
|
51
|
-
value: SelectProps['value'];
|
|
52
|
-
isScrolling: boolean;
|
|
53
|
-
groupBy: ((item: SelectItem) => string) | undefined;
|
|
54
|
-
itemId: string;
|
|
55
|
-
};
|
|
56
|
-
setHoverItemIndex: (value: number) => void;
|
|
57
|
-
setIsScrolling: (value: boolean) => void;
|
|
58
|
-
onhoveritem: (index: number) => void;
|
|
59
|
-
}
|
|
60
|
-
export type JustValue = string | number | string[] | number[] | null;
|
|
61
|
-
export interface ValueContext {
|
|
62
|
-
getState: () => {
|
|
63
|
-
value: SelectProps['value'];
|
|
64
|
-
prevValue: SelectProps['value'];
|
|
65
|
-
items: SelectItem[] | string[] | null;
|
|
66
|
-
multiple: boolean;
|
|
67
|
-
itemId: string;
|
|
68
|
-
label: string;
|
|
69
|
-
hasValue: boolean;
|
|
70
|
-
normalizedValue: SelectItem | SelectItem[] | null;
|
|
71
|
-
useJustValue: boolean;
|
|
72
|
-
justValue: JustValue | undefined;
|
|
73
|
-
clearState: boolean;
|
|
74
|
-
closeListOnChange: boolean;
|
|
75
|
-
};
|
|
76
|
-
setValue: (value: SelectProps['value']) => void;
|
|
77
|
-
setJustValue: (value: JustValue) => void;
|
|
78
|
-
setPrevValue: (value: SelectProps['value']) => void;
|
|
79
|
-
setClearState: (value: boolean) => void;
|
|
80
|
-
setActiveValue: (value: number | undefined) => void;
|
|
81
|
-
setFilterText: (value: string) => void;
|
|
205
|
+
export interface ValueActions {
|
|
82
206
|
closeList: () => void;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Retire a pending dependency-reload's validation verdict when the selection
|
|
209
|
+
* being committed was picked from fresher results (see useLoadOptions).
|
|
210
|
+
* Optional: standalone useValue has no async loading to retire.
|
|
211
|
+
*/
|
|
212
|
+
retireStaleValidation?: () => void;
|
|
213
|
+
onValueChange: (value: SelectItem | string | (SelectItem | string)[] | null | undefined) => void;
|
|
214
|
+
onSelectionChange: (value: SelectItem | string | (SelectItem | string)[] | null | undefined) => void;
|
|
215
|
+
onclear: (value: SelectItem | string | (SelectItem | string)[] | null | undefined) => void;
|
|
86
216
|
onselect: (selection: SelectItem) => void;
|
|
87
217
|
}
|
|
88
|
-
export interface
|
|
89
|
-
getState: () => {
|
|
90
|
-
filterText: string;
|
|
91
|
-
prevFilterText: string | undefined;
|
|
92
|
-
loadOptionsDeps: any[];
|
|
93
|
-
loadOptions: ((filterText: string) => Promise<SelectItem[] | string[]>) | undefined;
|
|
94
|
-
disabled: boolean;
|
|
95
|
-
multiple: boolean;
|
|
96
|
-
value: SelectProps['value'];
|
|
97
|
-
items: SelectItem[] | string[] | null;
|
|
98
|
-
itemId: string;
|
|
99
|
-
useJustValue: boolean;
|
|
100
|
-
justValue: JustValue | undefined;
|
|
101
|
-
listOpen: boolean;
|
|
102
|
-
debounceWait: number;
|
|
103
|
-
};
|
|
104
|
-
setItems: (items: SelectItem[] | string[] | null) => void;
|
|
105
|
-
setValue: (value: SelectProps['value']) => void;
|
|
106
|
-
setJustValue: (value: JustValue) => void;
|
|
107
|
-
setLoading: (value: boolean) => void;
|
|
108
|
-
setListOpen: (value: boolean) => void;
|
|
218
|
+
export interface LoadOptionsActions {
|
|
109
219
|
debounce: (fn: () => void, wait: number) => void;
|
|
110
|
-
convertStringItemsToObjects: (items: string[]) => SelectItem[];
|
|
111
220
|
onloaded: (options: SelectItem[]) => void;
|
|
112
|
-
onerror: (error:
|
|
113
|
-
}
|
|
114
|
-
export interface ScrollActionParams {
|
|
115
|
-
scroll: boolean;
|
|
116
|
-
}
|
|
117
|
-
export interface SelectItem {
|
|
118
|
-
value?: any;
|
|
119
|
-
label?: string;
|
|
120
|
-
index?: number;
|
|
121
|
-
group?: string;
|
|
122
|
-
groupHeader?: boolean;
|
|
123
|
-
groupItem?: boolean;
|
|
124
|
-
selectable?: boolean;
|
|
125
|
-
id?: string | number;
|
|
126
|
-
[key: string]: any;
|
|
221
|
+
onerror: (error: SelectErrorEvent) => void;
|
|
127
222
|
}
|
|
128
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Props of the `<Select>` component.
|
|
225
|
+
*
|
|
226
|
+
* The surface is deliberately closed: unknown attributes are not spread onto the
|
|
227
|
+
* container element. Style via `class`/`containerStyles`, reach the input element
|
|
228
|
+
* via `inputAttributes`/`id`, and wrap the component when the container itself
|
|
229
|
+
* needs extra attributes (e.g. `data-testid`). Keeping it closed means a later
|
|
230
|
+
* release can add passthrough without a breaking change — the reverse is not true.
|
|
231
|
+
*/
|
|
232
|
+
export interface SelectProps<Item extends ItemLike = SelectItem, Multiple extends boolean = false> {
|
|
233
|
+
/**
|
|
234
|
+
* Bindable. The text typed into the input; drives filtering and `loadOptions`.
|
|
235
|
+
* An initial value is kept on mount and applied passively: it filters (and
|
|
236
|
+
* fetches, with `loadOptions`) but does not open the list or move focus.
|
|
237
|
+
* Later programmatic writes behave like typing and open the list.
|
|
238
|
+
*
|
|
239
|
+
* The DOM input's value is only ever this filter text — a selection is
|
|
240
|
+
* rendered alongside the input (and announced via the live regions), never
|
|
241
|
+
* written into the textbox. Read the selection from `value`/`justValue` or
|
|
242
|
+
* the form field (`name`), not from the DOM input.
|
|
243
|
+
*/
|
|
129
244
|
filterText?: string;
|
|
245
|
+
/** Which field uniquely identifies an item. Values are compared by this field, not by reference. @default 'value' */
|
|
130
246
|
itemId?: string;
|
|
131
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Bindable. The options to choose from. A `string[]` is converted to
|
|
249
|
+
* `{ value, label, index }` items for you. Leave unset when using `loadOptions`,
|
|
250
|
+
* which populates this.
|
|
251
|
+
*/
|
|
252
|
+
items?: Item[] | string[] | null;
|
|
253
|
+
/**
|
|
254
|
+
* Bindable, and only meaningful with `useJustValue`: the selection reduced to bare
|
|
255
|
+
* `itemId` values (`'a'` / `['a', 'b']`) instead of whole items. Writing it while
|
|
256
|
+
* no selection exists hydrates `value` — the matching items are resolved out of
|
|
257
|
+
* `items`, retrying when async items arrive. While a selection exists, `justValue`
|
|
258
|
+
* is derived from it and a conflicting write is corrected back: write (or clear)
|
|
259
|
+
* `value` to change the selection.
|
|
260
|
+
*
|
|
261
|
+
* Empty is always `undefined`: every clear path writes `undefined`, never `''`,
|
|
262
|
+
* `null`, or `[]` — read an emptied `justValue` as falsy rather than `=== null`.
|
|
263
|
+
*/
|
|
132
264
|
justValue?: JustValue;
|
|
265
|
+
/** Which field holds an item's display text. @default 'label' */
|
|
133
266
|
label?: string;
|
|
134
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Bindable. The current selection: an item (or `Item[]` with `multiple`), or a raw
|
|
269
|
+
* string id that is normalized against `items`. Empty is always `undefined` —
|
|
270
|
+
* see {@link SelectValueProp}.
|
|
271
|
+
*/
|
|
272
|
+
value?: SelectValueProp<Item, Multiple>;
|
|
273
|
+
/**
|
|
274
|
+
* Blocks interaction: the list closes, focus is released, and typing and
|
|
275
|
+
* keyboard navigation are ignored. The selection is kept — except with
|
|
276
|
+
* `loadOptions`, where disabling clears `value` and `items` (loaded options
|
|
277
|
+
* may be stale by re-enable). Rendered as `aria-disabled` + `readonly`
|
|
278
|
+
* rather than the native `disabled` attribute, so the current value stays
|
|
279
|
+
* in the accessibility tree.
|
|
280
|
+
*/
|
|
135
281
|
disabled?: boolean;
|
|
282
|
+
/**
|
|
283
|
+
* Bindable. Whether the input has focus. Writable: setting `true` moves DOM
|
|
284
|
+
* focus to the input, `false` blurs it and closes the list.
|
|
285
|
+
*/
|
|
136
286
|
focused?: boolean;
|
|
287
|
+
/** Applies the error styling and sets `aria-invalid`. Pair with `ariaErrorMessage`. */
|
|
137
288
|
hasError?: boolean;
|
|
289
|
+
/** Renders nothing instead of the "No options" row when the list is empty. Also suppresses a supplied `emptySnippet`. */
|
|
138
290
|
hideEmptyState?: boolean;
|
|
291
|
+
/** The input's `id`, for an external `<label for>`. Also seeds every internal id; one is generated if unset. */
|
|
139
292
|
id?: string | null;
|
|
293
|
+
/** Bindable. Whether the option list is open. */
|
|
140
294
|
listOpen?: boolean;
|
|
295
|
+
/** Bindable. Shows the spinner and sets `aria-busy`. Managed for you while `loadOptions` is in flight. */
|
|
141
296
|
loading?: boolean;
|
|
297
|
+
/** Name of the hidden input, for native form submission. */
|
|
142
298
|
name?: string | null;
|
|
299
|
+
/** Prompt shown in the empty input; also the input's last-resort accessible name (see `ariaLabel`). @default 'Please select' */
|
|
143
300
|
placeholder?: string;
|
|
301
|
+
/** Keep the placeholder visible even when an item is selected (multiple mode). */
|
|
144
302
|
placeholderAlwaysShow?: boolean;
|
|
303
|
+
/** Show the chevron indicator on the right of the control. @default false */
|
|
145
304
|
showChevron?: boolean;
|
|
305
|
+
/** Show the clear-all button. @default true */
|
|
146
306
|
clearable?: boolean;
|
|
307
|
+
/** Reset `filterText` when the input loses focus. @default true */
|
|
147
308
|
clearFilterTextOnBlur?: boolean;
|
|
309
|
+
/** Close the list after a selection. Set `false` to keep picking in multiple mode. @default true */
|
|
148
310
|
closeListOnChange?: boolean;
|
|
311
|
+
/** Hide already-selected items from the list. @default true */
|
|
149
312
|
filterSelectedItems?: boolean;
|
|
313
|
+
/**
|
|
314
|
+
* Let group headers be selected like options; otherwise they are presentational.
|
|
315
|
+
*
|
|
316
|
+
* A selected header is the synthesized {@link SelectGroupHeader} row, not one of
|
|
317
|
+
* your items — but `value` and the `onselect`/`onSelectionChange`/`onValueChange`
|
|
318
|
+
* payloads deliberately keep their ergonomic `Item` typing rather than forcing
|
|
319
|
+
* every consumer through a union. If you enable this, narrow those payloads with
|
|
320
|
+
* `isGroupHeader` before reading item fields off them.
|
|
321
|
+
*/
|
|
150
322
|
groupHeaderSelectable?: boolean;
|
|
323
|
+
/** Clicking anywhere on a tag removes it, and the whole tag becomes a keyboard tab stop. */
|
|
151
324
|
multiFullItemClearable?: boolean;
|
|
152
|
-
|
|
325
|
+
/**
|
|
326
|
+
* Allow several selections, rendered as tags. Drives typing: with `multiple`,
|
|
327
|
+
* `value` and the `onValueChange`/`onSelectionChange` payloads narrow to `Item[]`.
|
|
328
|
+
*/
|
|
329
|
+
multiple?: Multiple;
|
|
330
|
+
/** Block native form submission while nothing is selected, and set `aria-required`. */
|
|
153
331
|
required?: boolean;
|
|
332
|
+
/** Allow typing to filter. When `false` the input is read-only and printable keys jump to matching options. @default true */
|
|
154
333
|
searchable?: boolean;
|
|
334
|
+
/** Mirror the selection into `justValue` as bare ids, and hydrate `value` from it on mount. */
|
|
155
335
|
useJustValue?: boolean;
|
|
336
|
+
/** Extra class(es) on the container, alongside `svelte-select`. */
|
|
156
337
|
class?: string;
|
|
338
|
+
/** Inline styles on the container. */
|
|
157
339
|
containerStyles?: string;
|
|
340
|
+
/** Inline styles on the text input. */
|
|
158
341
|
inputStyles?: string;
|
|
159
|
-
|
|
342
|
+
/** Inline styles on the option list. */
|
|
343
|
+
listStyles?: string;
|
|
344
|
+
/** How long typing settles before `loadOptions` fires, in ms. Only typing is debounced. @default 300 */
|
|
160
345
|
debounceWait?: number;
|
|
346
|
+
/** Passed to floating-ui to position the list (placement, middleware, autoUpdate). */
|
|
161
347
|
floatingConfig?: FloatingConfig;
|
|
348
|
+
/** Bindable. Index of the option under the keyboard cursor. */
|
|
162
349
|
hoverItemIndex?: number;
|
|
163
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Extra attributes for the text input. Merged over the component's own ARIA
|
|
352
|
+
* wiring, so it can override it. Event handlers are the exception: an
|
|
353
|
+
* `oninput`/`onblur`/`onfocus`/`onkeydown` here composes with (runs after)
|
|
354
|
+
* the component's own handler instead of replacing it, so filtering, focus
|
|
355
|
+
* handling, and keyboard navigation keep working alongside yours.
|
|
356
|
+
*/
|
|
357
|
+
inputAttributes?: HTMLInputAttributes;
|
|
358
|
+
/** Size the list to the container's width. @default true */
|
|
164
359
|
listAutoWidth?: boolean;
|
|
360
|
+
/** Gap between the container and the list, in px. @default 5 */
|
|
165
361
|
listOffset?: number;
|
|
166
|
-
|
|
167
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Values that `loadOptions` depends on (e.g. a parent select's value). When any of
|
|
364
|
+
* them changes, options are re-fetched immediately (no debounce) and the current
|
|
365
|
+
* selection is validated against the result: entries a non-empty result no longer
|
|
366
|
+
* offers are dropped (the whole value clears to `undefined` only when nothing
|
|
367
|
+
* survives). An empty result is treated as no evidence and never clears — a search
|
|
368
|
+
* endpoint returns nothing for the retained (usually empty) filter text regardless
|
|
369
|
+
* of the selection. Typing-driven loads never invalidate the selection this way.
|
|
370
|
+
*
|
|
371
|
+
* Elements are compared by identity (`===`): pass primitives (`[country.id]`) or
|
|
372
|
+
* stable references, not inline literals (`[{ country }]` recreates the object per
|
|
373
|
+
* parent render and re-fires the reload every time — a dev-only warning surfaces
|
|
374
|
+
* this).
|
|
375
|
+
*/
|
|
376
|
+
loadOptionsDeps?: unknown[];
|
|
377
|
+
/** Builds the header row for a group; receives the group's value and its first item. */
|
|
378
|
+
createGroupHeaderItem?: (groupValue: string, item: NoInfer<Item>) => SelectItem;
|
|
379
|
+
/** Replace the debounce strategy used for typing-driven `loadOptions` calls. */
|
|
168
380
|
debounce?: (fn: () => void, wait: number) => void;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Replace the whole filtering pipeline. Most cases only need `itemFilter`.
|
|
383
|
+
* May return your own `Item`s directly, rows from `config.applyGrouping` /
|
|
384
|
+
* `config.convertStringItemsToObjects`, or a mix.
|
|
385
|
+
*/
|
|
386
|
+
filter?: (config: FilterConfig<NoInfer<Item>>) => (Item | SelectItem)[];
|
|
387
|
+
/**
|
|
388
|
+
* Returns the group an item belongs to. Setting this makes the list interleave
|
|
389
|
+
* synthesized header rows with your items — see {@link SelectRow}.
|
|
390
|
+
*/
|
|
391
|
+
groupBy?: ((item: NoInfer<Item>) => string) | undefined;
|
|
392
|
+
/** Reorders or filters the group keys produced by `groupBy`. */
|
|
172
393
|
groupFilter?: (groups: string[]) => string[];
|
|
173
|
-
|
|
174
|
-
|
|
394
|
+
/** Decides whether one option survives the current `filterText`. Not applied to `loadOptions` results. */
|
|
395
|
+
itemFilter?: (label: string, filterText: string, option: NoInfer<Item>) => boolean;
|
|
396
|
+
/**
|
|
397
|
+
* Fetches options asynchronously. Runs on mount, on typing (debounced by
|
|
398
|
+
* `debounceWait`), and whenever `loadOptionsDeps` or `disabled` changes.
|
|
399
|
+
* Opening or closing the list never fetches, with one exception: reopening
|
|
400
|
+
* with retained filter text whose load was cancelled on close (e.g. with
|
|
401
|
+
* `clearFilterTextOnBlur={false}`) refreshes the stale results immediately.
|
|
402
|
+
* Whatever it returns is what the list shows: results are not re-filtered by
|
|
403
|
+
* `filterText`. Set `items` or this, not both.
|
|
404
|
+
*/
|
|
405
|
+
loadOptions?: (filterText: string) => Promise<Item[] | string[]>;
|
|
406
|
+
/** Announced when the selection is cleared. */
|
|
407
|
+
ariaCleared?: () => string;
|
|
408
|
+
/** `aria-label` for the clear-all button. */
|
|
409
|
+
ariaClearSelectLabel?: string;
|
|
410
|
+
/** Announced when an open list has no results. Also rendered as the list's visible empty-state copy (unless `emptySnippet` replaces it), so overriding it localizes both. */
|
|
411
|
+
ariaEmpty?: () => string;
|
|
412
|
+
/** id of an external element describing the error; wired to `aria-errormessage` only while `hasError` is true. */
|
|
413
|
+
ariaErrorMessage?: string;
|
|
414
|
+
/** Announced when the input is focused with the list closed. The default omits its "type to refine list" phrase when `searchable` is `false`. */
|
|
175
415
|
ariaFocused?: () => string;
|
|
416
|
+
/**
|
|
417
|
+
* Accessible name for the input. Prefer an external `<label for={id}>`; this
|
|
418
|
+
* overrides it. With neither, the placeholder is a last-resort fallback that some
|
|
419
|
+
* screen readers ignore, and a dev-only warning fires.
|
|
420
|
+
*/
|
|
176
421
|
ariaLabel?: string;
|
|
422
|
+
/**
|
|
423
|
+
* Announced when the list opens and when filtering changes the result count — not
|
|
424
|
+
* on every arrow key, since `aria-activedescendant` already announces each option
|
|
425
|
+
* as the cursor reaches it. Receives the focused option's label and the count.
|
|
426
|
+
*/
|
|
177
427
|
ariaListOpen?: (label: string, count: number) => string;
|
|
428
|
+
/** Announced while an open list is still fetching options. Also rendered as the list's visible loading copy (unless `emptySnippet` replaces it), so overriding it localizes both. */
|
|
429
|
+
ariaLoading?: () => string;
|
|
430
|
+
/** `aria-label` for each multi-select tag's remove button; receives the item's label. */
|
|
431
|
+
ariaRemoveItemLabel?: (label: string) => string;
|
|
432
|
+
/** Announced when the selection changes; receives the selected labels, comma-joined. */
|
|
178
433
|
ariaValues?: (values: string) => string;
|
|
179
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Replaces the clear button's behavior entirely — the default clear (emptying
|
|
436
|
+
* `value`, firing `onclear`, closing the list, refocusing the input) does not run,
|
|
437
|
+
* so an override owns all of it. To merely observe a clear, use `onclear`.
|
|
438
|
+
*/
|
|
439
|
+
handleClear?: (e?: MouseEvent) => void;
|
|
440
|
+
/** Literal DOM `blur` passthrough from the text input. Fires after the component's own blur handling (list close, filter-text reset). */
|
|
180
441
|
onblur?: (e: FocusEvent) => void;
|
|
181
|
-
|
|
182
|
-
onclear?: (value:
|
|
183
|
-
|
|
184
|
-
|
|
442
|
+
/** Clear-all receives the full removed value; removing one tag receives that single entry. */
|
|
443
|
+
onclear?: (value: SelectClearValue<Item, Multiple>) => void;
|
|
444
|
+
/** Fires when a `loadOptions` promise rejects. */
|
|
445
|
+
onerror?: (error: SelectErrorEvent) => void;
|
|
446
|
+
/**
|
|
447
|
+
* Fires with the rendered list whenever it changes while open. Rows include any
|
|
448
|
+
* synthesized group headers — narrow with `isGroupHeader`.
|
|
449
|
+
*/
|
|
450
|
+
onfilter?: (items: SelectRow<Item>[]) => void;
|
|
451
|
+
/** Literal DOM `focus` passthrough from the text input. Fires after the component's own focus handling. */
|
|
185
452
|
onfocus?: (e: FocusEvent) => void;
|
|
453
|
+
/** Fires with the index of the option under the keyboard/mouse cursor. */
|
|
186
454
|
onhoveritem?: (index: number) => void;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
455
|
+
/**
|
|
456
|
+
* Fires with the options a `loadOptions` call resolved. When the loader
|
|
457
|
+
* resolves raw strings, the delivered options are the synthesized
|
|
458
|
+
* `{ value, label, index }` items built from them — `SelectItem`s, not your
|
|
459
|
+
* item type — hence the widened element type. An item-resolving loader only
|
|
460
|
+
* ever delivers `Item`s.
|
|
461
|
+
*/
|
|
462
|
+
onloaded?: (options: (Item | SelectItem)[]) => void;
|
|
463
|
+
/**
|
|
464
|
+
* Fires alongside {@link SelectProps.onSelectionChange} when the user picks an
|
|
465
|
+
* option, but receives just the item selected rather than the whole value.
|
|
466
|
+
*/
|
|
467
|
+
onselect?: (selection: Item) => void;
|
|
468
|
+
/**
|
|
469
|
+
* Fires only when the user picks an option from the list — never on a clear, a
|
|
470
|
+
* programmatic `bind:value` write, or a `loadOptionsDeps` invalidation. Use
|
|
471
|
+
* {@link SelectProps.onValueChange} to observe every value change instead.
|
|
472
|
+
* (Renamed from `onchange` in v2: it is a component state-change callback, not
|
|
473
|
+
* the DOM `change` event.)
|
|
474
|
+
*/
|
|
475
|
+
onSelectionChange?: (value: SelectValue<Item, Multiple>) => void;
|
|
476
|
+
/**
|
|
477
|
+
* Fires on *every* value change — a user selection, a clear, a programmatic
|
|
478
|
+
* `bind:value` write, or a `loadOptionsDeps` invalidation. Contrast
|
|
479
|
+
* {@link SelectProps.onSelectionChange}, which only fires for a user selection.
|
|
480
|
+
* An emptied value arrives here as `null` (single) or `[]` (multiple), which is
|
|
481
|
+
* the one place those shapes appear — `bind:value` itself always empties to
|
|
482
|
+
* `undefined`. (Renamed from `oninput` in v2: typing is `bind:filterText`, and
|
|
483
|
+
* an `oninput` that never fired per keystroke read as the DOM event it wasn't.)
|
|
484
|
+
*/
|
|
485
|
+
onValueChange?: (value: SelectValue<Item, Multiple>) => void;
|
|
486
|
+
/** Replaces the chevron icon; the boolean is `listOpen`, so the icon can flip while open. Rendered only with `showChevron`. */
|
|
190
487
|
chevronIconSnippet?: Snippet<[boolean]>;
|
|
488
|
+
/** Replaces the icon inside the clear-all button (the button itself, its `ariaClearSelectLabel`, and its behavior stay). */
|
|
191
489
|
clearIconSnippet?: Snippet;
|
|
490
|
+
/** Replaces the "No options" row of an open, empty list. Suppressed entirely by `hideEmptyState`. */
|
|
192
491
|
emptySnippet?: Snippet;
|
|
193
|
-
|
|
194
|
-
|
|
492
|
+
/**
|
|
493
|
+
* Replaces the hidden input(s) used for native form submission; receives the
|
|
494
|
+
* current value. Overriding takes charge of serialization — render your own
|
|
495
|
+
* `<input type="hidden" name={...}>` (the default submits JSON-stringified
|
|
496
|
+
* items, or bare ids with `useJustValue`).
|
|
497
|
+
*/
|
|
498
|
+
inputHiddenSnippet?: Snippet<[SelectValueProp<Item, Multiple> | undefined]>;
|
|
499
|
+
/**
|
|
500
|
+
* Renders one row of the list. Also renders synthesized group headers when
|
|
501
|
+
* `groupBy` is set — narrow with `isGroupHeader`.
|
|
502
|
+
*/
|
|
503
|
+
itemSnippet?: Snippet<[SelectRow<Item>, number]>;
|
|
504
|
+
/** Rendered inside the list after the options — e.g. a "load more" row or footer. */
|
|
195
505
|
listAppendSnippet?: Snippet;
|
|
506
|
+
/** Rendered inside the list before the options — e.g. a sticky header. */
|
|
196
507
|
listPrependSnippet?: Snippet;
|
|
197
|
-
|
|
508
|
+
/**
|
|
509
|
+
* Replaces the entire list body. Rows include any synthesized group headers —
|
|
510
|
+
* narrow with `isGroupHeader`.
|
|
511
|
+
*
|
|
512
|
+
* Note: taking over rendering means `aria-activedescendant` can no longer resolve
|
|
513
|
+
* to an option, so give each rendered option `id="listbox-{id}-item-{index}"` and
|
|
514
|
+
* `role="option"` to keep the combobox navigable by screen readers.
|
|
515
|
+
*/
|
|
516
|
+
listSnippet?: Snippet<[SelectRow<Item>[]]>;
|
|
517
|
+
/** Replaces the spinner shown while `loading` is true (or a `loadOptions` fetch is in flight). */
|
|
198
518
|
loadingIconSnippet?: Snippet;
|
|
519
|
+
/** Replaces the icon inside each tag's remove button in multiple mode (the button and its `ariaRemoveItemLabel` stay). */
|
|
199
520
|
multiClearIconSnippet?: Snippet;
|
|
521
|
+
/** Rendered inside the control before the selection and input — e.g. a search icon. */
|
|
200
522
|
prependSnippet?: Snippet;
|
|
201
|
-
|
|
202
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Replaces the hidden native `<select required>` fallback that blocks form
|
|
525
|
+
* submission while `required` and empty; receives the current value. An
|
|
526
|
+
* override owns the constraint-validation behavior.
|
|
527
|
+
*/
|
|
528
|
+
requiredSnippet?: Snippet<[SelectValueProp<Item, Multiple> | undefined]>;
|
|
529
|
+
/** Receives one item at a time: the value in single mode, each tag in multiple mode. */
|
|
530
|
+
selectionSnippet?: Snippet<[NoInfer<Item>, number?]>;
|
|
531
|
+
/** Bindable, read-only: the container element, once mounted. */
|
|
203
532
|
container?: HTMLDivElement;
|
|
533
|
+
/** Bindable, read-only: the text input element, once mounted. */
|
|
204
534
|
input?: HTMLInputElement;
|
|
205
535
|
}
|