svelte-comp 1.2.5 → 1.2.6
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 +1 -1
- package/package.json +1 -1
- package/dist/App.svelte +0 -551
- package/dist/App.svelte.d.ts +0 -3
- package/dist/Container.svelte +0 -60
- package/dist/Container.svelte.d.ts +0 -12
- package/dist/app.css +0 -235
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -6
- package/dist/lang.d.ts +0 -1081
- package/dist/lang.js +0 -1096
- package/dist/lib/Accordion.svelte +0 -155
- package/dist/lib/Accordion.svelte.d.ts +0 -40
- package/dist/lib/Button.svelte +0 -170
- package/dist/lib/Button.svelte.d.ts +0 -53
- package/dist/lib/Card.svelte +0 -103
- package/dist/lib/Card.svelte.d.ts +0 -42
- package/dist/lib/Carousel.svelte +0 -293
- package/dist/lib/Carousel.svelte.d.ts +0 -13
- package/dist/lib/CheckBox.svelte +0 -210
- package/dist/lib/CheckBox.svelte.d.ts +0 -53
- package/dist/lib/CodeView.svelte +0 -307
- package/dist/lib/CodeView.svelte.d.ts +0 -64
- package/dist/lib/ColorPicker.svelte +0 -161
- package/dist/lib/ColorPicker.svelte.d.ts +0 -40
- package/dist/lib/DatePicker.svelte +0 -170
- package/dist/lib/DatePicker.svelte.d.ts +0 -53
- package/dist/lib/Dialog.svelte +0 -235
- package/dist/lib/Dialog.svelte.d.ts +0 -58
- package/dist/lib/Field.svelte +0 -299
- package/dist/lib/Field.svelte.d.ts +0 -8
- package/dist/lib/FilePicker.svelte +0 -241
- package/dist/lib/FilePicker.svelte.d.ts +0 -52
- package/dist/lib/Form.svelte +0 -438
- package/dist/lib/Form.svelte.d.ts +0 -20
- package/dist/lib/Hamburger.svelte +0 -211
- package/dist/lib/Hamburger.svelte.d.ts +0 -52
- package/dist/lib/Menu.svelte +0 -623
- package/dist/lib/Menu.svelte.d.ts +0 -33
- package/dist/lib/PaginatedCard.svelte +0 -73
- package/dist/lib/PaginatedCard.svelte.d.ts +0 -11
- package/dist/lib/Pagination.svelte +0 -119
- package/dist/lib/Pagination.svelte.d.ts +0 -9
- package/dist/lib/PrimaryColorSelect.svelte +0 -113
- package/dist/lib/PrimaryColorSelect.svelte.d.ts +0 -9
- package/dist/lib/ProgressBar.svelte +0 -141
- package/dist/lib/ProgressBar.svelte.d.ts +0 -48
- package/dist/lib/ProgressCircle.svelte +0 -192
- package/dist/lib/ProgressCircle.svelte.d.ts +0 -39
- package/dist/lib/Radio.svelte +0 -189
- package/dist/lib/Radio.svelte.d.ts +0 -55
- package/dist/lib/SearchInput.svelte +0 -106
- package/dist/lib/SearchInput.svelte.d.ts +0 -13
- package/dist/lib/Select.svelte +0 -524
- package/dist/lib/Select.svelte.d.ts +0 -21
- package/dist/lib/Slider.svelte +0 -253
- package/dist/lib/Slider.svelte.d.ts +0 -56
- package/dist/lib/Splitter.svelte +0 -150
- package/dist/lib/Splitter.svelte.d.ts +0 -43
- package/dist/lib/Switch.svelte +0 -167
- package/dist/lib/Switch.svelte.d.ts +0 -42
- package/dist/lib/Table.svelte +0 -299
- package/dist/lib/Table.svelte.d.ts +0 -17
- package/dist/lib/Tabs.svelte +0 -213
- package/dist/lib/Tabs.svelte.d.ts +0 -48
- package/dist/lib/ThemeToggle.svelte +0 -127
- package/dist/lib/ThemeToggle.svelte.d.ts +0 -32
- package/dist/lib/TimePicker.svelte +0 -269
- package/dist/lib/TimePicker.svelte.d.ts +0 -48
- package/dist/lib/Toast.svelte +0 -226
- package/dist/lib/Toast.svelte.d.ts +0 -14
- package/dist/lib/Tooltip.svelte +0 -110
- package/dist/lib/Tooltip.svelte.d.ts +0 -40
- package/dist/lib/index.d.ts +0 -32
- package/dist/lib/index.js +0 -33
- package/dist/lib/lang.d.ts +0 -158
- package/dist/lib/lang.js +0 -150
- package/dist/lib/types/index.d.ts +0 -111
- package/dist/lib/types/index.js +0 -26
- package/dist/main.d.ts +0 -3
- package/dist/main.js +0 -7
- package/dist/styles.css +0 -232
- package/dist/utils/index.d.ts +0 -34
- package/dist/utils/index.js +0 -268
package/dist/lib/Select.svelte
DELETED
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
<!-- src/lib/Select.svelte -->
|
|
2
|
-
<script lang="ts">
|
|
3
|
-
/**
|
|
4
|
-
* @component Select
|
|
5
|
-
* @description Accessible custom combobox with label, portal listbox, hidden form input, and configurable sizing.
|
|
6
|
-
*
|
|
7
|
-
* @prop label {string} - Field label rendered above the trigger
|
|
8
|
-
*
|
|
9
|
-
* @prop help {string} - Optional helper copy rendered below the field
|
|
10
|
-
*
|
|
11
|
-
* @prop placeholder {string} - Text shown when nothing is selected
|
|
12
|
-
*
|
|
13
|
-
* @prop options {SelectOption[]} - Available options (`{ label, value, disabled? }`)
|
|
14
|
-
* @default []
|
|
15
|
-
*
|
|
16
|
-
* @prop sz {SizeKey} - Sizing preset
|
|
17
|
-
* @options xs|sm|md|lg|xl
|
|
18
|
-
* @default md
|
|
19
|
-
*
|
|
20
|
-
* @prop variant {FieldVariant} - Surface style preset
|
|
21
|
-
* @options default|filled|neutral
|
|
22
|
-
* @default default
|
|
23
|
-
*
|
|
24
|
-
* @prop value {string} - Selected value (bindable)
|
|
25
|
-
* @default ""
|
|
26
|
-
*
|
|
27
|
-
* @prop onChange {(val: string) => void} - Fired when a new option is chosen
|
|
28
|
-
*
|
|
29
|
-
* @prop class {string} - Extra classes for the root `<label>`
|
|
30
|
-
* @default ""
|
|
31
|
-
*
|
|
32
|
-
* @prop id {string} - Custom id for the field
|
|
33
|
-
*
|
|
34
|
-
* @prop invalid {boolean} - Shows invalid state and sets `aria-invalid`
|
|
35
|
-
* @default false
|
|
36
|
-
*
|
|
37
|
-
* @prop describedBy {string} - Links to helper or error text ids
|
|
38
|
-
*
|
|
39
|
-
* @prop open {boolean} - Controlled dropdown visibility (bindable)
|
|
40
|
-
* @default false
|
|
41
|
-
*
|
|
42
|
-
* @prop maxHeight {number} - Max dropdown height before scrolling
|
|
43
|
-
*
|
|
44
|
-
* @note Fully keyboard navigable (`Arrow`, `Home/End`, `Enter/Space`, looped `Tab`) with roving tabindex buttons inside a listbox.
|
|
45
|
-
* @note Dropdown is portalled with `position: fixed` math to stay aligned with the trigger (including scroll/resize listeners).
|
|
46
|
-
* @note Hidden `<input type=\"hidden\">` mirrors `bind:value` so forms and contextual `BaseField` integrations keep working.
|
|
47
|
-
* @note Disabled options remain focus-skippable and never call `onChange`.
|
|
48
|
-
* @note Size + variant tokens control both trigger padding and icon scale through the same Tailwind-driven design tokens.
|
|
49
|
-
*/
|
|
50
|
-
import { getContext } from "svelte";
|
|
51
|
-
import type { HTMLSelectAttributes } from "svelte/elements";
|
|
52
|
-
import type { SizeKey, FieldVariant, SelectOption } from "./types";
|
|
53
|
-
import { TEXT } from "./types";
|
|
54
|
-
import { uid, cx } from "../utils";
|
|
55
|
-
|
|
56
|
-
type Props = HTMLSelectAttributes & {
|
|
57
|
-
label?: string;
|
|
58
|
-
help?: string;
|
|
59
|
-
placeholder?: string;
|
|
60
|
-
options?: SelectOption[];
|
|
61
|
-
sz?: SizeKey;
|
|
62
|
-
variant?: FieldVariant;
|
|
63
|
-
value?: string;
|
|
64
|
-
onChange?: (val: string) => void;
|
|
65
|
-
class?: string;
|
|
66
|
-
id?: string;
|
|
67
|
-
invalid?: boolean;
|
|
68
|
-
describedBy?: string;
|
|
69
|
-
open?: boolean;
|
|
70
|
-
maxHeight?: number;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
let {
|
|
74
|
-
label,
|
|
75
|
-
help,
|
|
76
|
-
placeholder,
|
|
77
|
-
options = [],
|
|
78
|
-
sz = "md",
|
|
79
|
-
variant = "default",
|
|
80
|
-
value = $bindable(""),
|
|
81
|
-
onChange,
|
|
82
|
-
class: externalClass = "",
|
|
83
|
-
id: externalId,
|
|
84
|
-
invalid = false,
|
|
85
|
-
describedBy,
|
|
86
|
-
open = $bindable(false),
|
|
87
|
-
maxHeight,
|
|
88
|
-
...rest
|
|
89
|
-
}: Props = $props();
|
|
90
|
-
|
|
91
|
-
type BaseFieldCtx = { readonly id: string; readonly name: string };
|
|
92
|
-
const bf = getContext<BaseFieldCtx>("BaseField");
|
|
93
|
-
|
|
94
|
-
const fieldId = $derived(externalId ?? bf?.id ?? uid("sel-"));
|
|
95
|
-
const fieldName = $derived(bf?.name ?? fieldId);
|
|
96
|
-
const listboxId = $derived(`${fieldId}-listbox`);
|
|
97
|
-
|
|
98
|
-
let listEl = $state<HTMLUListElement | null>(null);
|
|
99
|
-
let triggerEl = $state<HTMLButtonElement | null>(null);
|
|
100
|
-
let highlighted = $state(-1);
|
|
101
|
-
let itemBtns = $state<Array<HTMLButtonElement>>([]);
|
|
102
|
-
let menuPosition = $state<"top" | "bottom">("bottom");
|
|
103
|
-
|
|
104
|
-
const base =
|
|
105
|
-
"relative w-full outline-none appearance-none cursor-pointer transition-colors duration-[var(--transition-fast)] ease-[var(--timing-default)] box-border rounded-[var(--radius-md)] border focus:border-[var(--border-color-focus)] focus:ring-2 focus:ring-[var(--border-color-focus)] [color:var(--color-text-default)] disabled:opacity-[var(--opacity-disabled)] disabled:cursor-not-allowed";
|
|
106
|
-
|
|
107
|
-
const sizes: Record<SizeKey, string> = {
|
|
108
|
-
xs: "px-2 pr-6 h-6",
|
|
109
|
-
sm: "px-3 pr-8 h-7",
|
|
110
|
-
md: "px-4 pr-10 h-8",
|
|
111
|
-
lg: "px-5 pr-12 h-9",
|
|
112
|
-
xl: "px-6 pr-14 h-10",
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const heights: Record<SizeKey, number> = {
|
|
116
|
-
xs: 120,
|
|
117
|
-
sm: 144,
|
|
118
|
-
md: 168,
|
|
119
|
-
lg: 192,
|
|
120
|
-
xl: 216,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const variants: Record<FieldVariant, string> = {
|
|
124
|
-
default:
|
|
125
|
-
"border-[var(--border-color-default)] bg-[var(--color-bg-surface)]",
|
|
126
|
-
filled: "border-[var(--border-color-default)] bg-[var(--color-bg-muted)]",
|
|
127
|
-
neutral:
|
|
128
|
-
"border-transparent bg-transparent hover:border-[var(--border-color-default)] focus:border-[var(--border-color-focus)]",
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const iconsSizes: Record<SizeKey, string> = {
|
|
132
|
-
xs: "w-3 h-3",
|
|
133
|
-
sm: "w-3.5 h-3.5",
|
|
134
|
-
md: "w-4 h-4",
|
|
135
|
-
lg: "w-[18px] h-[18px]",
|
|
136
|
-
xl: "w-5 h-5",
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const rootClass = $derived(cx("flex flex-col gap-1", externalClass));
|
|
140
|
-
|
|
141
|
-
const triggerClass = $derived(
|
|
142
|
-
cx(base, sizes[sz], TEXT[sz], variants[variant], "text-left")
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const menuMaxHeight = $derived(maxHeight ?? heights[sz]);
|
|
146
|
-
|
|
147
|
-
const listClass = $derived(
|
|
148
|
-
cx(
|
|
149
|
-
"z-50 border border-[var(--border-color-default)] bg-[var(--color-bg-surface)] rounded-[var(--radius-md)] shadow-lg overflow-auto focus:outline-none"
|
|
150
|
-
)
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const itemBaseClass = $derived(
|
|
154
|
-
cx(
|
|
155
|
-
"w-full text-left px-4 py-2 transition-colors duration-[var(--transition-fast)] cursor-pointer",
|
|
156
|
-
TEXT[sz]
|
|
157
|
-
)
|
|
158
|
-
);
|
|
159
|
-
const itemDisabledClass =
|
|
160
|
-
"opacity-[var(--opacity-disabled)] cursor-not-allowed";
|
|
161
|
-
|
|
162
|
-
let menuTop = $state(0);
|
|
163
|
-
let menuLeft = $state(0);
|
|
164
|
-
let menuWidth = $state(0);
|
|
165
|
-
|
|
166
|
-
const listStyle = $derived(
|
|
167
|
-
`position:fixed;top:${menuTop}px;left:${menuLeft}px;min-width:${menuWidth}px;max-height:${menuMaxHeight}px;`
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
const selectedOption = $derived(options.find((o) => o.value === value));
|
|
171
|
-
|
|
172
|
-
$effect(() => {
|
|
173
|
-
const currentTriggerEl = triggerEl;
|
|
174
|
-
const currentListEl = listEl;
|
|
175
|
-
|
|
176
|
-
if (open && currentTriggerEl && currentListEl) {
|
|
177
|
-
const updatePosition = () => {
|
|
178
|
-
const triggerRect = currentTriggerEl.getBoundingClientRect();
|
|
179
|
-
const spaceBelow = window.innerHeight - triggerRect.bottom;
|
|
180
|
-
const spaceAbove = triggerRect.top;
|
|
181
|
-
const menuHeight = Math.min(currentListEl.scrollHeight, menuMaxHeight);
|
|
182
|
-
|
|
183
|
-
const pos =
|
|
184
|
-
spaceBelow < menuHeight && spaceAbove > spaceBelow ? "top" : "bottom";
|
|
185
|
-
|
|
186
|
-
menuPosition = pos;
|
|
187
|
-
currentListEl.dataset.position = pos;
|
|
188
|
-
|
|
189
|
-
if (pos === "top") {
|
|
190
|
-
menuTop = Math.max(0, triggerRect.top - menuHeight);
|
|
191
|
-
} else {
|
|
192
|
-
menuTop = triggerRect.bottom;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
menuLeft = triggerRect.left;
|
|
196
|
-
menuWidth = triggerRect.width;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
queueMicrotask(updatePosition);
|
|
200
|
-
|
|
201
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
202
|
-
if (
|
|
203
|
-
currentTriggerEl &&
|
|
204
|
-
currentListEl &&
|
|
205
|
-
!currentTriggerEl.contains(event.target as Node) &&
|
|
206
|
-
!currentListEl.contains(event.target as Node)
|
|
207
|
-
) {
|
|
208
|
-
closeMenu();
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const handleScroll = () => updatePosition();
|
|
213
|
-
const handleResize = () => updatePosition();
|
|
214
|
-
|
|
215
|
-
document.addEventListener("mousedown", handleClickOutside);
|
|
216
|
-
window.addEventListener("scroll", handleScroll, true);
|
|
217
|
-
window.addEventListener("resize", handleResize);
|
|
218
|
-
|
|
219
|
-
return () => {
|
|
220
|
-
document.removeEventListener("mousedown", handleClickOutside);
|
|
221
|
-
window.removeEventListener("scroll", handleScroll, true);
|
|
222
|
-
window.removeEventListener("resize", handleResize);
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
$effect(() => {
|
|
228
|
-
const currentListEl = listEl;
|
|
229
|
-
if (!open || !currentListEl) return;
|
|
230
|
-
|
|
231
|
-
queueMicrotask(() => {
|
|
232
|
-
if (currentListEl) {
|
|
233
|
-
itemBtns = Array.from(
|
|
234
|
-
currentListEl.querySelectorAll<HTMLButtonElement>("button")
|
|
235
|
-
);
|
|
236
|
-
} else {
|
|
237
|
-
itemBtns = [];
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
function focusEnabled(index: number | null) {
|
|
243
|
-
if (!itemBtns.length || !options.length) return;
|
|
244
|
-
const len = options.length;
|
|
245
|
-
let start = typeof index === "number" && index >= 0 ? index : 0;
|
|
246
|
-
for (let step = 0; step < len; step++) {
|
|
247
|
-
const i = (start + step) % len;
|
|
248
|
-
if (!options[i]?.disabled && itemBtns[i]) {
|
|
249
|
-
itemBtns[i].focus();
|
|
250
|
-
highlighted = i;
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function focusFirstEnabled() {
|
|
257
|
-
focusEnabled(0);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function focusLastEnabled() {
|
|
261
|
-
if (!options.length || !itemBtns.length) return;
|
|
262
|
-
for (let i = options.length - 1; i >= 0; i--) {
|
|
263
|
-
if (!options[i]?.disabled && itemBtns[i]) {
|
|
264
|
-
itemBtns[i].focus();
|
|
265
|
-
highlighted = i;
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function openMenu() {
|
|
272
|
-
if (rest.disabled) return;
|
|
273
|
-
open = true;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function closeMenu() {
|
|
277
|
-
open = false;
|
|
278
|
-
highlighted = -1;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function toggleMenu() {
|
|
282
|
-
if (open) {
|
|
283
|
-
closeMenu();
|
|
284
|
-
} else {
|
|
285
|
-
openMenu();
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function move(delta: number) {
|
|
290
|
-
if (!open || !options.length) return;
|
|
291
|
-
const len = options.length;
|
|
292
|
-
let i = highlighted;
|
|
293
|
-
for (let step = 0; step < len; step++) {
|
|
294
|
-
i = (i + delta + len) % len;
|
|
295
|
-
if (!options[i].disabled) break;
|
|
296
|
-
}
|
|
297
|
-
highlighted = i;
|
|
298
|
-
if (itemBtns[i]) itemBtns[i].focus();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function choose(i: number) {
|
|
302
|
-
const opt = options[i];
|
|
303
|
-
if (!opt || opt.disabled) return;
|
|
304
|
-
value = opt.value;
|
|
305
|
-
onChange?.(opt.value);
|
|
306
|
-
closeMenu();
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function onTriggerKeydown(e: KeyboardEvent) {
|
|
310
|
-
if (rest.disabled) return;
|
|
311
|
-
if (e.key === " " || e.key === "Enter") {
|
|
312
|
-
e.preventDefault();
|
|
313
|
-
toggleMenu();
|
|
314
|
-
} else if (e.key === "ArrowDown") {
|
|
315
|
-
e.preventDefault();
|
|
316
|
-
if (!open) openMenu();
|
|
317
|
-
move(1);
|
|
318
|
-
} else if (e.key === "ArrowUp") {
|
|
319
|
-
e.preventDefault();
|
|
320
|
-
if (!open) openMenu();
|
|
321
|
-
move(-1);
|
|
322
|
-
} else if (e.key === "Escape" && open) {
|
|
323
|
-
e.preventDefault();
|
|
324
|
-
closeMenu();
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function onListKeydown(e: KeyboardEvent) {
|
|
329
|
-
if (!open) return;
|
|
330
|
-
|
|
331
|
-
if (e.key === "Tab") {
|
|
332
|
-
const active = document.activeElement as HTMLElement | null;
|
|
333
|
-
const currentIndex = itemBtns.findIndex((btn) => btn === active);
|
|
334
|
-
let lastEnabledIndex = -1;
|
|
335
|
-
for (let i = options.length - 1; i >= 0; i--) {
|
|
336
|
-
if (!options[i]?.disabled) {
|
|
337
|
-
lastEnabledIndex = i;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
if (e.shiftKey) {
|
|
342
|
-
if (currentIndex <= 0) {
|
|
343
|
-
e.preventDefault();
|
|
344
|
-
focusLastEnabled();
|
|
345
|
-
}
|
|
346
|
-
} else {
|
|
347
|
-
if (currentIndex === lastEnabledIndex) {
|
|
348
|
-
e.preventDefault();
|
|
349
|
-
focusFirstEnabled();
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (e.key === "ArrowDown") {
|
|
356
|
-
e.preventDefault();
|
|
357
|
-
move(1);
|
|
358
|
-
} else if (e.key === "ArrowUp") {
|
|
359
|
-
e.preventDefault();
|
|
360
|
-
move(-1);
|
|
361
|
-
} else if (e.key === "Home") {
|
|
362
|
-
e.preventDefault();
|
|
363
|
-
focusFirstEnabled();
|
|
364
|
-
} else if (e.key === "End") {
|
|
365
|
-
e.preventDefault();
|
|
366
|
-
focusLastEnabled();
|
|
367
|
-
} else if (e.key === "Enter" || e.key === " ") {
|
|
368
|
-
e.preventDefault();
|
|
369
|
-
if (highlighted >= 0) choose(highlighted);
|
|
370
|
-
} else if (e.key === "Escape") {
|
|
371
|
-
e.preventDefault();
|
|
372
|
-
closeMenu();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function onOverlayKeydown(e: KeyboardEvent) {
|
|
377
|
-
if (e.key === "Escape") {
|
|
378
|
-
e.preventDefault();
|
|
379
|
-
closeMenu();
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
</script>
|
|
383
|
-
|
|
384
|
-
<div class={rootClass}>
|
|
385
|
-
{#if label}
|
|
386
|
-
<label
|
|
387
|
-
id={`${fieldId}-hidden-label`}
|
|
388
|
-
for={`${fieldId}-hidden`}
|
|
389
|
-
class={cx(TEXT[sz], "font-medium [color:var(--color-text-muted)]")}
|
|
390
|
-
>
|
|
391
|
-
{label}
|
|
392
|
-
</label>
|
|
393
|
-
{/if}
|
|
394
|
-
|
|
395
|
-
<div class="w-full">
|
|
396
|
-
<button
|
|
397
|
-
bind:this={triggerEl}
|
|
398
|
-
id={fieldId}
|
|
399
|
-
type="button"
|
|
400
|
-
role="combobox"
|
|
401
|
-
class={triggerClass}
|
|
402
|
-
aria-haspopup="listbox"
|
|
403
|
-
aria-expanded={open}
|
|
404
|
-
aria-controls={listboxId}
|
|
405
|
-
aria-describedby={describedBy}
|
|
406
|
-
aria-invalid={invalid}
|
|
407
|
-
aria-required={rest.required}
|
|
408
|
-
disabled={rest.disabled}
|
|
409
|
-
onclick={() => {
|
|
410
|
-
toggleMenu();
|
|
411
|
-
}}
|
|
412
|
-
onkeydown={onTriggerKeydown}
|
|
413
|
-
>
|
|
414
|
-
<span class="min-w-0 grow truncate">
|
|
415
|
-
{#if selectedOption}
|
|
416
|
-
<span class="inline-flex items-center gap-2 min-w-0">
|
|
417
|
-
{#if selectedOption.swatch}
|
|
418
|
-
<span
|
|
419
|
-
aria-hidden="true"
|
|
420
|
-
class="block w-3 h-3 rounded-[var(--radius-xs)] border border-[var(--border-color-default)] shadow-sm shrink-0"
|
|
421
|
-
style={`background:${selectedOption.swatch}`}
|
|
422
|
-
></span>
|
|
423
|
-
{/if}
|
|
424
|
-
<span class="truncate">{selectedOption.label}</span>
|
|
425
|
-
</span>
|
|
426
|
-
{:else}
|
|
427
|
-
<span class="[color:var(--color-text-muted)]">{placeholder}</span>
|
|
428
|
-
{/if}
|
|
429
|
-
</span>
|
|
430
|
-
<span
|
|
431
|
-
class={cx(
|
|
432
|
-
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 [color:var(--color-text-default)]"
|
|
433
|
-
)}
|
|
434
|
-
>
|
|
435
|
-
<svg
|
|
436
|
-
class={iconsSizes[sz]}
|
|
437
|
-
fill="none"
|
|
438
|
-
stroke="currentColor"
|
|
439
|
-
viewBox="0 0 24 24"
|
|
440
|
-
aria-hidden="true"
|
|
441
|
-
>
|
|
442
|
-
<path
|
|
443
|
-
stroke-linecap="round"
|
|
444
|
-
stroke-linejoin="round"
|
|
445
|
-
stroke-width="2"
|
|
446
|
-
d="M19 9l-7 7-7-7"
|
|
447
|
-
/>
|
|
448
|
-
</svg>
|
|
449
|
-
</span>
|
|
450
|
-
</button>
|
|
451
|
-
</div>
|
|
452
|
-
|
|
453
|
-
{#if open}
|
|
454
|
-
<div
|
|
455
|
-
role="presentation"
|
|
456
|
-
tabindex="-1"
|
|
457
|
-
class="fixed inset-0 z-40"
|
|
458
|
-
onclick={closeMenu}
|
|
459
|
-
onkeydown={onOverlayKeydown}
|
|
460
|
-
></div>
|
|
461
|
-
|
|
462
|
-
<ul
|
|
463
|
-
bind:this={listEl}
|
|
464
|
-
id={listboxId}
|
|
465
|
-
role="listbox"
|
|
466
|
-
class={listClass}
|
|
467
|
-
style={listStyle}
|
|
468
|
-
data-position={menuPosition}
|
|
469
|
-
aria-labelledby={label ? `${fieldId}-hidden-label` : undefined}
|
|
470
|
-
aria-activedescendant={highlighted >= 0
|
|
471
|
-
? `${fieldId}-opt-${highlighted}`
|
|
472
|
-
: undefined}
|
|
473
|
-
tabindex="-1"
|
|
474
|
-
onkeydown={onListKeydown}
|
|
475
|
-
>
|
|
476
|
-
{#each options as opt, i (opt.value)}
|
|
477
|
-
<li
|
|
478
|
-
id={`${fieldId}-opt-${i}`}
|
|
479
|
-
role="option"
|
|
480
|
-
aria-selected={!opt.disabled && opt.value === value}
|
|
481
|
-
aria-disabled={opt.disabled}
|
|
482
|
-
class={cx(itemBaseClass, opt.disabled ? itemDisabledClass : "")}
|
|
483
|
-
>
|
|
484
|
-
<button
|
|
485
|
-
type="button"
|
|
486
|
-
tabindex="0"
|
|
487
|
-
class={cx(
|
|
488
|
-
"w-full text-left focus:outline-[3px] focus:outline-offset-3 focus:outline-[var(--border-color-focus)] rounded flex items-center gap-2"
|
|
489
|
-
)}
|
|
490
|
-
disabled={opt.disabled}
|
|
491
|
-
onclick={() => choose(i)}
|
|
492
|
-
onfocus={() => (highlighted = i)}
|
|
493
|
-
onmouseenter={() => (highlighted = i)}
|
|
494
|
-
>
|
|
495
|
-
{#if opt.swatch}
|
|
496
|
-
<span
|
|
497
|
-
aria-hidden="true"
|
|
498
|
-
class="block w-3 h-3 rounded-[var(--radius-xs)] border border-[var(--border-color-default)] shadow-sm shrink-0"
|
|
499
|
-
style={`background:${opt.swatch}`}
|
|
500
|
-
></span>
|
|
501
|
-
{/if}
|
|
502
|
-
<span class="truncate">{opt.label}</span>
|
|
503
|
-
</button>
|
|
504
|
-
</li>
|
|
505
|
-
{/each}
|
|
506
|
-
</ul>
|
|
507
|
-
{/if}
|
|
508
|
-
|
|
509
|
-
<input
|
|
510
|
-
id={`${fieldId}-hidden`}
|
|
511
|
-
type="hidden"
|
|
512
|
-
name={fieldName}
|
|
513
|
-
value={value ?? ""}
|
|
514
|
-
/>
|
|
515
|
-
|
|
516
|
-
{#if help}
|
|
517
|
-
<div
|
|
518
|
-
id={describedBy}
|
|
519
|
-
class={cx("[color:var(--color-text-muted)]", TEXT.sm)}
|
|
520
|
-
>
|
|
521
|
-
{help}
|
|
522
|
-
</div>
|
|
523
|
-
{/if}
|
|
524
|
-
</div>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { HTMLSelectAttributes } from "svelte/elements";
|
|
2
|
-
import type { SizeKey, FieldVariant, SelectOption } from "./types";
|
|
3
|
-
type Props = HTMLSelectAttributes & {
|
|
4
|
-
label?: string;
|
|
5
|
-
help?: string;
|
|
6
|
-
placeholder?: string;
|
|
7
|
-
options?: SelectOption[];
|
|
8
|
-
sz?: SizeKey;
|
|
9
|
-
variant?: FieldVariant;
|
|
10
|
-
value?: string;
|
|
11
|
-
onChange?: (val: string) => void;
|
|
12
|
-
class?: string;
|
|
13
|
-
id?: string;
|
|
14
|
-
invalid?: boolean;
|
|
15
|
-
describedBy?: string;
|
|
16
|
-
open?: boolean;
|
|
17
|
-
maxHeight?: number;
|
|
18
|
-
};
|
|
19
|
-
declare const Select: import("svelte").Component<Props, {}, "open" | "value">;
|
|
20
|
-
type Select = ReturnType<typeof Select>;
|
|
21
|
-
export default Select;
|