svelte-5-select 1.0.2 → 2.1.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 +255 -115
- package/dist/ChevronIcon.svelte +14 -13
- package/dist/ChevronIcon.svelte.d.ts +6 -14
- package/dist/ClearIcon.svelte +9 -11
- package/dist/ClearIcon.svelte.d.ts +6 -14
- package/dist/LoadingIcon.svelte +15 -2
- package/dist/LoadingIcon.svelte.d.ts +6 -14
- package/dist/Select.svelte +1048 -442
- package/dist/Select.svelte.d.ts +37 -5
- package/dist/aria-handlers.svelte.d.ts +5 -2
- package/dist/aria-handlers.svelte.js +30 -8
- package/dist/filter.d.ts +10 -2
- package/dist/filter.js +15 -8
- package/dist/index.d.ts +3 -4
- package/dist/index.js +2 -3
- package/dist/keyboard-navigation.svelte.d.ts +7 -2
- package/dist/keyboard-navigation.svelte.js +293 -47
- package/dist/no-styles/ChevronIcon.svelte +17 -0
- package/dist/no-styles/ChevronIcon.svelte.d.ts +18 -0
- package/dist/no-styles/ClearIcon.svelte +14 -0
- package/dist/no-styles/ClearIcon.svelte.d.ts +18 -0
- package/dist/no-styles/LoadingIcon.svelte +19 -0
- package/dist/no-styles/LoadingIcon.svelte.d.ts +18 -0
- package/dist/no-styles/Select.svelte +1452 -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 +131 -71
- package/dist/tailwind.css +139 -17
- package/dist/types.d.ts +488 -129
- package/dist/use-hover.svelte.d.ts +3 -7
- package/dist/use-hover.svelte.js +110 -36
- package/dist/use-load-options.svelte.d.ts +18 -3
- package/dist/use-load-options.svelte.js +362 -42
- package/dist/use-value.svelte.d.ts +2 -11
- package/dist/use-value.svelte.js +334 -111
- package/dist/utils.d.ts +19 -8
- package/dist/utils.js +52 -8
- package/package.json +121 -94
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ItemLike, SelectProps, SelectRow } from '../types.js';
|
|
2
|
+
import type { SelectItem } from '../types.js';
|
|
3
|
+
declare function $$render<Item extends ItemLike = SelectItem, Multiple extends boolean = false>(): {
|
|
4
|
+
props: SelectProps<Item, Multiple>;
|
|
5
|
+
exports: {
|
|
6
|
+
getFilteredItems: () => SelectRow<Item>[];
|
|
7
|
+
reset: () => void;
|
|
8
|
+
};
|
|
9
|
+
bindings: "loading" | "value" | "hoverItemIndex" | "listOpen" | "filterText" | "items" | "justValue" | "focused" | "container" | "input";
|
|
10
|
+
slots: {};
|
|
11
|
+
events: {};
|
|
12
|
+
};
|
|
13
|
+
declare class __sveltets_Render<Item extends ItemLike = SelectItem, Multiple extends boolean = false> {
|
|
14
|
+
props(): ReturnType<typeof $$render<Item, Multiple>>['props'];
|
|
15
|
+
events(): ReturnType<typeof $$render<Item, Multiple>>['events'];
|
|
16
|
+
slots(): ReturnType<typeof $$render<Item, Multiple>>['slots'];
|
|
17
|
+
bindings(): "loading" | "value" | "hoverItemIndex" | "listOpen" | "filterText" | "items" | "justValue" | "focused" | "container" | "input";
|
|
18
|
+
exports(): {
|
|
19
|
+
getFilteredItems: () => SelectRow<Item>[];
|
|
20
|
+
reset: () => void;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface $$IsomorphicComponent {
|
|
24
|
+
new <Item extends ItemLike = SelectItem, Multiple extends boolean = false>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Item, Multiple>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Item, Multiple>['props']>, ReturnType<__sveltets_Render<Item, Multiple>['events']>, ReturnType<__sveltets_Render<Item, Multiple>['slots']>> & {
|
|
25
|
+
$$bindings?: ReturnType<__sveltets_Render<Item, Multiple>['bindings']>;
|
|
26
|
+
} & ReturnType<__sveltets_Render<Item, Multiple>['exports']>;
|
|
27
|
+
<Item extends ItemLike = SelectItem, Multiple extends boolean = false>(internal: unknown, props: ReturnType<__sveltets_Render<Item, Multiple>['props']> & {}): ReturnType<__sveltets_Render<Item, Multiple>['exports']>;
|
|
28
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A select/autocomplete/typeahead control: a WAI-ARIA combobox with a floating
|
|
32
|
+
* listbox popup. Generic over your item type; supports multi-select, async
|
|
33
|
+
* loading (`loadOptions`), grouping (`groupBy`), and snippet-based customization.
|
|
34
|
+
* Bind `value` (and optionally `justValue`, `filterText`, `listOpen`, `focused`).
|
|
35
|
+
*/
|
|
36
|
+
declare const Select: $$IsomorphicComponent;
|
|
37
|
+
type Select<Item extends ItemLike = SelectItem, Multiple extends boolean = false> = InstanceType<typeof Select<Item, Multiple>>;
|
|
38
|
+
export default Select;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ItemLike, SelectItem, SelectState } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The prop-backed part of the store: Select.svelte supplies live getter/setter
|
|
4
|
+
* accessors over its `$props()` bindings and derived values, so reads stay
|
|
5
|
+
* reactive and writes flow back into the bound props.
|
|
6
|
+
*/
|
|
7
|
+
export type SelectStateProps<Item extends ItemLike = SelectItem> = Omit<SelectState<Item>, 'activeValue' | 'isScrolling' | 'clearState' | 'prevValue' | 'prevFilterText' | 'prevMultiple' | 'suppressValueHoverSnap' | 'userNavigatedSinceOpen'>;
|
|
8
|
+
/**
|
|
9
|
+
* Builds the single shared state object passed to every composable (in place of
|
|
10
|
+
* the former per-composable getState()/setter bundles). Internal shared state
|
|
11
|
+
* lives here; prop-backed fields are forwarded to the accessors from Select.svelte.
|
|
12
|
+
* Per-field access means a read tracks only that one signal, so composables no
|
|
13
|
+
* longer create blanket reactive dependencies on all state at once.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSelectState<Item extends ItemLike = SelectItem>(props: SelectStateProps<Item>): SelectState<Item>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the single shared state object passed to every composable (in place of
|
|
3
|
+
* the former per-composable getState()/setter bundles). Internal shared state
|
|
4
|
+
* lives here; prop-backed fields are forwarded to the accessors from Select.svelte.
|
|
5
|
+
* Per-field access means a read tracks only that one signal, so composables no
|
|
6
|
+
* longer create blanket reactive dependencies on all state at once.
|
|
7
|
+
*/
|
|
8
|
+
export function createSelectState(props) {
|
|
9
|
+
let activeValue = $state(undefined);
|
|
10
|
+
let isScrolling = $state(false);
|
|
11
|
+
let clearState = $state(false);
|
|
12
|
+
// Comparison scratch fields, only read inside untrack()ed code — deliberately non-reactive.
|
|
13
|
+
// prevValue is seeded with the initial value so mount does not dispatch onValueChange;
|
|
14
|
+
// prevFilterText is seeded so an initial filterText does not open the list (and
|
|
15
|
+
// steal focus through the list-open effect) on mount.
|
|
16
|
+
let prevValue = props.value;
|
|
17
|
+
let prevFilterText = props.filterText;
|
|
18
|
+
let prevMultiple = undefined;
|
|
19
|
+
let suppressValueHoverSnap = false;
|
|
20
|
+
let userNavigatedSinceOpen = false;
|
|
21
|
+
return {
|
|
22
|
+
get value() {
|
|
23
|
+
return props.value;
|
|
24
|
+
},
|
|
25
|
+
set value(v) {
|
|
26
|
+
props.value = v;
|
|
27
|
+
},
|
|
28
|
+
get items() {
|
|
29
|
+
return props.items;
|
|
30
|
+
},
|
|
31
|
+
set items(v) {
|
|
32
|
+
props.items = v;
|
|
33
|
+
},
|
|
34
|
+
get filterText() {
|
|
35
|
+
return props.filterText;
|
|
36
|
+
},
|
|
37
|
+
set filterText(v) {
|
|
38
|
+
props.filterText = v;
|
|
39
|
+
},
|
|
40
|
+
get justValue() {
|
|
41
|
+
return props.justValue;
|
|
42
|
+
},
|
|
43
|
+
set justValue(v) {
|
|
44
|
+
props.justValue = v;
|
|
45
|
+
},
|
|
46
|
+
get listOpen() {
|
|
47
|
+
return props.listOpen;
|
|
48
|
+
},
|
|
49
|
+
set listOpen(v) {
|
|
50
|
+
props.listOpen = v;
|
|
51
|
+
},
|
|
52
|
+
get loading() {
|
|
53
|
+
return props.loading;
|
|
54
|
+
},
|
|
55
|
+
set loading(v) {
|
|
56
|
+
props.loading = v;
|
|
57
|
+
},
|
|
58
|
+
get focused() {
|
|
59
|
+
return props.focused;
|
|
60
|
+
},
|
|
61
|
+
set focused(v) {
|
|
62
|
+
props.focused = v;
|
|
63
|
+
},
|
|
64
|
+
get hoverItemIndex() {
|
|
65
|
+
return props.hoverItemIndex;
|
|
66
|
+
},
|
|
67
|
+
set hoverItemIndex(v) {
|
|
68
|
+
props.hoverItemIndex = v;
|
|
69
|
+
},
|
|
70
|
+
get multiple() {
|
|
71
|
+
return props.multiple;
|
|
72
|
+
},
|
|
73
|
+
get itemId() {
|
|
74
|
+
return props.itemId;
|
|
75
|
+
},
|
|
76
|
+
get label() {
|
|
77
|
+
return props.label;
|
|
78
|
+
},
|
|
79
|
+
get searchable() {
|
|
80
|
+
return props.searchable;
|
|
81
|
+
},
|
|
82
|
+
get disabled() {
|
|
83
|
+
return props.disabled;
|
|
84
|
+
},
|
|
85
|
+
get useJustValue() {
|
|
86
|
+
return props.useJustValue;
|
|
87
|
+
},
|
|
88
|
+
get closeListOnChange() {
|
|
89
|
+
return props.closeListOnChange;
|
|
90
|
+
},
|
|
91
|
+
get debounceWait() {
|
|
92
|
+
return props.debounceWait;
|
|
93
|
+
},
|
|
94
|
+
get groupBy() {
|
|
95
|
+
return props.groupBy;
|
|
96
|
+
},
|
|
97
|
+
get loadOptions() {
|
|
98
|
+
return props.loadOptions;
|
|
99
|
+
},
|
|
100
|
+
get loadOptionsDeps() {
|
|
101
|
+
return props.loadOptionsDeps;
|
|
102
|
+
},
|
|
103
|
+
get filteredItems() {
|
|
104
|
+
return props.filteredItems;
|
|
105
|
+
},
|
|
106
|
+
get normalizedValue() {
|
|
107
|
+
return props.normalizedValue;
|
|
108
|
+
},
|
|
109
|
+
get hasValue() {
|
|
110
|
+
return props.hasValue;
|
|
111
|
+
},
|
|
112
|
+
get activeValue() {
|
|
113
|
+
return activeValue;
|
|
114
|
+
},
|
|
115
|
+
set activeValue(v) {
|
|
116
|
+
activeValue = v;
|
|
117
|
+
},
|
|
118
|
+
get isScrolling() {
|
|
119
|
+
return isScrolling;
|
|
120
|
+
},
|
|
121
|
+
set isScrolling(v) {
|
|
122
|
+
isScrolling = v;
|
|
123
|
+
},
|
|
124
|
+
get clearState() {
|
|
125
|
+
return clearState;
|
|
126
|
+
},
|
|
127
|
+
set clearState(v) {
|
|
128
|
+
clearState = v;
|
|
129
|
+
},
|
|
130
|
+
get prevValue() {
|
|
131
|
+
return prevValue;
|
|
132
|
+
},
|
|
133
|
+
set prevValue(v) {
|
|
134
|
+
prevValue = v;
|
|
135
|
+
},
|
|
136
|
+
get prevFilterText() {
|
|
137
|
+
return prevFilterText;
|
|
138
|
+
},
|
|
139
|
+
set prevFilterText(v) {
|
|
140
|
+
prevFilterText = v;
|
|
141
|
+
},
|
|
142
|
+
get prevMultiple() {
|
|
143
|
+
return prevMultiple;
|
|
144
|
+
},
|
|
145
|
+
set prevMultiple(v) {
|
|
146
|
+
prevMultiple = v;
|
|
147
|
+
},
|
|
148
|
+
get suppressValueHoverSnap() {
|
|
149
|
+
return suppressValueHoverSnap;
|
|
150
|
+
},
|
|
151
|
+
set suppressValueHoverSnap(v) {
|
|
152
|
+
suppressValueHoverSnap = v;
|
|
153
|
+
},
|
|
154
|
+
get userNavigatedSinceOpen() {
|
|
155
|
+
return userNavigatedSinceOpen;
|
|
156
|
+
},
|
|
157
|
+
set userNavigatedSinceOpen(v) {
|
|
158
|
+
userNavigatedSinceOpen = v;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/dist/styles/default.css
CHANGED
|
@@ -1,71 +1,10 @@
|
|
|
1
1
|
.svelte-select {
|
|
2
|
-
/* deprecating camelCase custom props in favour of kebab-case for v5 */
|
|
3
|
-
--borderRadius: var(--border-radius);
|
|
4
|
-
--clearSelectColor: var(--clear-select-color);
|
|
5
|
-
--clearSelectWidth: var(--clear-select-width);
|
|
6
|
-
--disabledBackground: var(--disabled-background);
|
|
7
|
-
--disabledBorderColor: var(--disabled-border-color);
|
|
8
|
-
--disabledColor: var(--disabled-color);
|
|
9
|
-
--disabledPlaceholderColor: var(--disabled-placeholder-color);
|
|
10
|
-
--disabledPlaceholderOpacity: var(--disabled-placeholder-opacity);
|
|
11
|
-
--errorBackground: var(--error-background);
|
|
12
|
-
--errorBorder: var(--error-border);
|
|
13
|
-
--groupItemPaddingLeft: var(--group-item-padding-left);
|
|
14
|
-
--groupTitleColor: var(--group-title-color);
|
|
15
|
-
--groupTitleFontSize: var(--group-title-font-size);
|
|
16
|
-
--groupTitleFontWeight: var(--group-title-font-weight);
|
|
17
|
-
--groupTitlePadding: var(--group-title-padding);
|
|
18
|
-
--groupTitleTextTransform: var(--group-title-text-transform);
|
|
19
|
-
--groupTitleBorderColor: var(--group-title-border-color);
|
|
20
|
-
--groupTitleBorderWidth: var(--group-title-border-width);
|
|
21
|
-
--groupTitleBorderStyle: var(--group-title-border-style);
|
|
22
|
-
--indicatorColor: var(--chevron-color);
|
|
23
|
-
--indicatorHeight: var(--chevron-height);
|
|
24
|
-
--indicatorWidth: var(--chevron-width);
|
|
25
|
-
--inputColor: var(--input-color);
|
|
26
|
-
--inputLeft: var(--input-left);
|
|
27
|
-
--inputLetterSpacing: var(--input-letter-spacing);
|
|
28
|
-
--inputMargin: var(--input-margin);
|
|
29
|
-
--inputPadding: var(--input-padding);
|
|
30
|
-
--itemActiveBackground: var(--item-active-background);
|
|
31
|
-
--itemColor: var(--item-color);
|
|
32
|
-
--itemFirstBorderRadius: var(--item-first-border-radius);
|
|
33
|
-
--itemHoverBG: var(--item-hover-bg);
|
|
34
|
-
--itemHoverColor: var(--item-hover-color);
|
|
35
|
-
--itemIsActiveBG: var(--item-is-active-bg);
|
|
36
|
-
--itemIsActiveColor: var(--item-is-active-color);
|
|
37
|
-
--itemIsNotSelectableColor: var(--item-is-not-selectable-color);
|
|
38
|
-
--itemPadding: var(--item-padding);
|
|
39
|
-
--listBackground: var(--list-background);
|
|
40
|
-
--listBorder: var(--list-border);
|
|
41
|
-
--listBorderRadius: var(--list-border-radius);
|
|
42
|
-
--listEmptyColor: var(--list-empty-color);
|
|
43
|
-
--listEmptyPadding: var(--list-empty-padding);
|
|
44
|
-
--listEmptyTextAlign: var(--list-empty-text-align);
|
|
45
|
-
--listMaxHeight: var(--list-max-height);
|
|
46
|
-
--listPosition: var(--list-position);
|
|
47
|
-
--listShadow: var(--list-shadow);
|
|
48
|
-
--listZIndex: var(--list-z-index);
|
|
49
|
-
--multiItemBG: var(--multi-item-bg);
|
|
50
|
-
--multiItemBorderRadius: var(--multi-item-border-radius);
|
|
51
|
-
--multiItemDisabledHoverBg: var(--multi-item-disabled-hover-bg);
|
|
52
|
-
--multiItemDisabledHoverColor: var(--multi-item-disabled-hover-color);
|
|
53
|
-
--multiItemHeight: var(--multi-item-height);
|
|
54
|
-
--multiItemMargin: var(--multi-item-margin);
|
|
55
|
-
--multiItemPadding: var(--multi-item-padding);
|
|
56
|
-
--multiSelectInputMargin: var(--multi-select-input-margin);
|
|
57
|
-
--multiSelectInputPadding: var(--multi-select-input-padding);
|
|
58
|
-
--multiSelectPadding: var(--multi-select-padding);
|
|
59
|
-
--placeholderColor: var(--placeholder-color);
|
|
60
|
-
--placeholderOpacity: var(--placeholder-opacity);
|
|
61
|
-
--selectedItemPadding: var(--selected-item-padding);
|
|
62
|
-
--spinnerColor: var(--spinner-color);
|
|
63
|
-
--spinnerHeight: var(--spinner-height);
|
|
64
|
-
--spinnerWidth: var(--spinner-width);
|
|
65
|
-
|
|
66
2
|
--internal-padding: 0 0 0 16px;
|
|
67
3
|
|
|
68
|
-
|
|
4
|
+
/* #858a93 is 3.47:1 on the default #fff background. The old #d8dbdf was 1.39:1 —
|
|
5
|
+
the border is what identifies the control's boundary, so it needs 3:1
|
|
6
|
+
(WCAG 1.4.11 Non-text Contrast). Override --border to restyle. */
|
|
7
|
+
border: var(--border, 1px solid #858a93);
|
|
69
8
|
border-radius: var(--border-radius, 6px);
|
|
70
9
|
min-height: var(--height, 42px);
|
|
71
10
|
position: relative;
|
|
@@ -84,7 +23,9 @@
|
|
|
84
23
|
}
|
|
85
24
|
|
|
86
25
|
.svelte-select:hover {
|
|
87
|
-
|
|
26
|
+
/* Darker than --border so hover stays a visible change, and 4.91:1 on #fff
|
|
27
|
+
(the old #b2b8bf was 2.00:1) */
|
|
28
|
+
border: var(--border-hover, 1px solid #67727d);
|
|
88
29
|
}
|
|
89
30
|
|
|
90
31
|
.value-container {
|
|
@@ -136,7 +77,7 @@ input {
|
|
|
136
77
|
}
|
|
137
78
|
|
|
138
79
|
input::placeholder {
|
|
139
|
-
color: var(--placeholder-color, #
|
|
80
|
+
color: var(--placeholder-color, #67727d);
|
|
140
81
|
opacity: var(--placeholder-opacity, 1);
|
|
141
82
|
}
|
|
142
83
|
|
|
@@ -147,6 +88,10 @@ input:focus {
|
|
|
147
88
|
.svelte-select.focused {
|
|
148
89
|
border: var(--border-focused, 1px solid #006fe8);
|
|
149
90
|
border-radius: var(--border-radius-focused, var(--border-radius, 6px));
|
|
91
|
+
/* A focus ring in addition to the border colour change: the input's own
|
|
92
|
+
outline is suppressed, so relying on the 1px border alone was a
|
|
93
|
+
colour-only cue (WCAG 2.4.7/2.4.11) that vanished if --border was overridden. */
|
|
94
|
+
box-shadow: var(--focused-box-shadow, 0 0 0 2px rgba(0, 111, 232, 0.4));
|
|
150
95
|
}
|
|
151
96
|
|
|
152
97
|
.disabled {
|
|
@@ -203,7 +148,7 @@ input:focus {
|
|
|
203
148
|
flex-shrink: 0;
|
|
204
149
|
}
|
|
205
150
|
|
|
206
|
-
.clear-select:focus {
|
|
151
|
+
.clear-select:focus-visible {
|
|
207
152
|
outline: var(--clear-select-focus-outline, 1px solid #006fe8);
|
|
208
153
|
}
|
|
209
154
|
|
|
@@ -283,12 +228,33 @@ input:focus {
|
|
|
283
228
|
}
|
|
284
229
|
|
|
285
230
|
.multi-item-clear {
|
|
231
|
+
all: unset;
|
|
286
232
|
display: flex;
|
|
287
233
|
align-items: center;
|
|
288
234
|
justify-content: center;
|
|
235
|
+
cursor: pointer;
|
|
236
|
+
/* The button stretches to the chip's height (25px); without a width floor
|
|
237
|
+
it shrinks to the 20px icon, under the 24px activation-target minimum
|
|
238
|
+
(WCAG 2.2 2.5.8) */
|
|
239
|
+
min-width: var(--multi-item-clear-min-width, 24px);
|
|
289
240
|
--clear-icon-color: var(--multi-item-clear-icon-color, #000);
|
|
290
241
|
}
|
|
291
242
|
|
|
243
|
+
.multi-item-clear:focus-visible {
|
|
244
|
+
outline: var(--multi-item-clear-focus-outline, 1px solid #006fe8);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* With multiFullItemClearable the whole chip is a real tab stop (role="button",
|
|
248
|
+
tabindex="0"). .multi-item sets `outline` unconditionally, and an author outline
|
|
249
|
+
beats the UA's :focus-visible ring by cascade origin, so without this rule a
|
|
250
|
+
keyboard-focused chip looked identical to an unfocused one (WCAG 2.4.7 Focus
|
|
251
|
+
Visible). Distinct from .multi-item.active below, which is the arrow-key tag
|
|
252
|
+
cursor, not DOM focus. */
|
|
253
|
+
.multi-item:focus-visible {
|
|
254
|
+
outline: var(--multi-item-focus-outline, 2px solid #006fe8);
|
|
255
|
+
outline-offset: -1px;
|
|
256
|
+
}
|
|
257
|
+
|
|
292
258
|
.multi-item.active {
|
|
293
259
|
outline: var(--multi-item-active-outline, 1px solid #006fe8);
|
|
294
260
|
}
|
|
@@ -329,7 +295,7 @@ input:focus {
|
|
|
329
295
|
.empty {
|
|
330
296
|
text-align: var(--list-empty-text-align, center);
|
|
331
297
|
padding: var(--list-empty-padding, 20px 0);
|
|
332
|
-
color: var(--list-empty-color, #
|
|
298
|
+
color: var(--list-empty-color, #67727d);
|
|
333
299
|
}
|
|
334
300
|
|
|
335
301
|
.item {
|
|
@@ -355,7 +321,7 @@ input:focus {
|
|
|
355
321
|
}
|
|
356
322
|
|
|
357
323
|
.item.active {
|
|
358
|
-
background: var(--item-is-active-bg, #
|
|
324
|
+
background: var(--item-is-active-bg, #006fe8);
|
|
359
325
|
color: var(--item-is-active-color, #fff);
|
|
360
326
|
}
|
|
361
327
|
|
|
@@ -363,9 +329,28 @@ input:focus {
|
|
|
363
329
|
border-radius: var(--item-first-border-radius, 4px 4px 0 0);
|
|
364
330
|
}
|
|
365
331
|
|
|
332
|
+
/* Mirror of .item.first: without it the last item's hover/selection background
|
|
333
|
+
(and the keyboard-cursor outline) square off against the list's rounded
|
|
334
|
+
bottom corners */
|
|
335
|
+
.item.last {
|
|
336
|
+
border-radius: var(--item-last-border-radius, 0 0 4px 4px);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* A single-option list is both first and last: round all corners (the two
|
|
340
|
+
shorthand variables above cannot compose, so this matches their defaults) */
|
|
341
|
+
.item.first.last {
|
|
342
|
+
border-radius: 4px;
|
|
343
|
+
}
|
|
344
|
+
|
|
366
345
|
.item.hover:not(.active) {
|
|
367
346
|
background: var(--item-hover-bg, #e7f2ff);
|
|
368
347
|
color: var(--item-hover-color, inherit);
|
|
348
|
+
/* The background alone is ~1.13:1 against the default #fff list — a
|
|
349
|
+
colour-only cue for the keyboard cursor (WCAG 1.4.11 Non-text Contrast).
|
|
350
|
+
#006fe8 is 4.5:1 on #fff and ~4.1:1 on the hover background; set
|
|
351
|
+
--item-hover-outline to `none` to opt out. */
|
|
352
|
+
outline: var(--item-hover-outline, 2px solid #006fe8);
|
|
353
|
+
outline-offset: -2px;
|
|
369
354
|
}
|
|
370
355
|
|
|
371
356
|
.item.not-selectable,
|
|
@@ -374,6 +359,22 @@ input:focus {
|
|
|
374
359
|
.item.not-selectable:active {
|
|
375
360
|
color: var(--item-is-not-selectable-color, #999);
|
|
376
361
|
background: transparent;
|
|
362
|
+
/* Not actionable, so it must not show the keyboard-cursor outline either */
|
|
363
|
+
outline: none;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Group titles are informative text, not disabled controls, so WCAG 1.4.3's
|
|
367
|
+
inactive-component exemption does not apply to them: the not-selectable
|
|
368
|
+
dimming above (#999 = 2.84:1) must not reach a header. Keep them at the
|
|
369
|
+
group-title color (default #000 = 21:1 on white). The hover variant must
|
|
370
|
+
win over `.item.hover.item.not-selectable` too: the first frame parks the
|
|
371
|
+
keyboard cursor on index 0 before the keep-hover effect moves it off a
|
|
372
|
+
header, and without it that frame paints #999 which then *animates* back
|
|
373
|
+
through `transition: all` — mid-transition greys fail contrast when
|
|
374
|
+
sampled (the CI-only axe flake). */
|
|
375
|
+
.item.list-group-title.not-selectable,
|
|
376
|
+
.item.hover.item.list-group-title.not-selectable {
|
|
377
|
+
color: var(--group-title-color, #000000);
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
.required {
|
|
@@ -384,4 +385,63 @@ input:focus {
|
|
|
384
385
|
left: 0;
|
|
385
386
|
bottom: 0;
|
|
386
387
|
right: 0;
|
|
387
|
-
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@media (prefers-reduced-motion: reduce) {
|
|
391
|
+
.item {
|
|
392
|
+
transition: none;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@media (forced-colors: active) {
|
|
397
|
+
/* box-shadow is dropped in forced-colors mode, so the focus ring above is
|
|
398
|
+
invisible there — restore a real outline using a system colour. */
|
|
399
|
+
.svelte-select.focused {
|
|
400
|
+
outline: 2px solid Highlight;
|
|
401
|
+
outline-offset: 1px;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/* Custom background colours are also flattened, so distinguish the selected
|
|
405
|
+
option (filled) from the option under the keyboard cursor (outlined). */
|
|
406
|
+
.item.active {
|
|
407
|
+
background: Highlight;
|
|
408
|
+
color: HighlightText;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.item.hover:not(.active) {
|
|
412
|
+
outline: 2px solid Highlight;
|
|
413
|
+
outline-offset: -2px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* The chip's focus ring is an author colour, which is flattened here */
|
|
417
|
+
.multi-item:focus-visible {
|
|
418
|
+
outline: 2px solid Highlight;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Every chip carries a 1px outline and the arrow-key tag cursor differs
|
|
422
|
+
only by outline colour — flattened to the same system colour here, the
|
|
423
|
+
Backspace target vanished. Width + style survive the forced palette. */
|
|
424
|
+
.multi-item.active {
|
|
425
|
+
outline: 2px dashed Highlight;
|
|
426
|
+
outline-offset: -2px;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* The error state is conveyed only by an author border colour, which is
|
|
430
|
+
flattened to the standard border — border width + style survive the
|
|
431
|
+
forced palette, so a double border keeps the state visible. */
|
|
432
|
+
.svelte-select.error {
|
|
433
|
+
border: 3px double CanvasText;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Disabled is aria-disabled + readonly, not :disabled, so the UA never
|
|
437
|
+
applies its own GrayText treatment — do it explicitly. */
|
|
438
|
+
.disabled,
|
|
439
|
+
.disabled input,
|
|
440
|
+
.disabled input::placeholder {
|
|
441
|
+
color: GrayText;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.disabled {
|
|
445
|
+
border-color: GrayText;
|
|
446
|
+
}
|
|
447
|
+
}
|