@weichenlin/swell-ui 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
2
+ import { Children, Fragment, cloneElement, createContext, isValidElement, use, useCallback, useContext, useEffect, useEffectEvent, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
3
  import { clsx } from "clsx";
3
4
  import { extendTailwindMerge } from "tailwind-merge";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
6
  import { cva } from "class-variance-authority";
5
- import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
7
  import { createPortal } from "react-dom";
7
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  //#region src/lib/cn.ts
9
9
  const tone = (name) => [
10
10
  name,
@@ -78,6 +78,595 @@ function cn(...inputs) {
78
78
  return twMerge(clsx(inputs));
79
79
  }
80
80
  //#endregion
81
+ //#region src/components/input/field.tsx
82
+ const controlHeight = {
83
+ sm: "h-[34px]",
84
+ md: "h-[38px]",
85
+ lg: "h-[42px]"
86
+ };
87
+ const textSize = {
88
+ sm: "text-sm",
89
+ md: "text-sm",
90
+ lg: "text-base"
91
+ };
92
+ const iconSize$1 = {
93
+ sm: "[&_svg]:size-4",
94
+ md: "[&_svg]:size-4",
95
+ lg: "[&_svg]:size-[18px]"
96
+ };
97
+ const inputPadding = {
98
+ sm: "px-3",
99
+ md: "px-3",
100
+ lg: "px-4"
101
+ };
102
+ const startSlotPadding = {
103
+ sm: "ps-2",
104
+ md: "ps-2.5",
105
+ lg: "ps-3"
106
+ };
107
+ const endSlotPadding = {
108
+ sm: "pe-2",
109
+ md: "pe-2.5",
110
+ lg: "pe-3"
111
+ };
112
+ const actionsPadding = {
113
+ sm: "pe-[3px]",
114
+ md: "pe-1",
115
+ lg: "pe-1"
116
+ };
117
+ /** Compact Button square that fits inside the field's inner height with an even inset. */
118
+ const compactButton = {
119
+ sm: "size-[26px]",
120
+ md: "size-7",
121
+ lg: "size-8"
122
+ };
123
+ const labelStart = {
124
+ sm: "start-2",
125
+ md: "start-2",
126
+ lg: "start-3"
127
+ };
128
+ const floatShift = {
129
+ sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,17px))]",
130
+ md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,19px))]",
131
+ lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,21px))]"
132
+ };
133
+ const isText = (node) => typeof node === "string" || typeof node === "number";
134
+ const isPresent = (node) => node != null && node !== false && node !== "";
135
+ function Adornment({ node, side, size }) {
136
+ const text = isText(node);
137
+ return /* @__PURE__ */ jsx("span", {
138
+ "aria-hidden": text ? void 0 : true,
139
+ className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize$1[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
140
+ children: node
141
+ });
142
+ }
143
+ const notchPadding = {
144
+ border: {
145
+ sm: "ps-2 pe-2",
146
+ md: "ps-2 pe-2",
147
+ lg: "ps-3 pe-1"
148
+ },
149
+ halo: {
150
+ sm: "ps-[9px] pe-[9px]",
151
+ md: "ps-[9px] pe-[9px]",
152
+ lg: "ps-[13px] pe-[5px]"
153
+ }
154
+ };
155
+ /**
156
+ * MUI-style notched outline: a fieldset draws the ring and an invisible legend holding the label
157
+ * text at its floated size cuts the gap. The legend collapses while the label rests in the field.
158
+ * A legend as tall as the border keeps the border at the fieldset's top edge.
159
+ */
160
+ function Notch({ ring, size, disabled, children }) {
161
+ const halo = ring === "halo";
162
+ return /* @__PURE__ */ jsx("fieldset", {
163
+ "aria-hidden": true,
164
+ className: cn("pointer-events-none absolute m-0 min-w-0 p-0", "transition-[border-color] duration-200 ease-in-out motion-reduce:transition-none", notchPadding[ring][size], halo ? ["-inset-1 rounded-[calc(var(--radius-lg)+3px)] border-[3px] border-(--field-halo)", "forced-colors:invisible forced-colors:group-has-[:is(input,select,textarea):is(:focus,:open)]/control:visible forced-colors:border-[Highlight]"] : ["-inset-px rounded-[inherit] border border-(--field-border)", disabled && "forced-colors:border-[GrayText]"]),
165
+ children: /* @__PURE__ */ jsx("legend", {
166
+ className: cn("invisible float-none block w-auto max-w-[0.01px] overflow-hidden p-0 text-xs whitespace-nowrap", halo ? "h-0.75" : "h-px", "transition-[max-width] duration-50 ease-out motion-reduce:transition-none", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-full", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:delay-50", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:duration-100"),
167
+ children: /* @__PURE__ */ jsx("span", {
168
+ className: "px-1",
169
+ children
170
+ })
171
+ })
172
+ });
173
+ }
174
+ /** Keeps an internal ref (focus forwarding, clear, reset) alongside the caller's ref. */
175
+ function useMergedRef(ownRef, ref) {
176
+ return useCallback((node) => {
177
+ ownRef.current = node;
178
+ if (typeof ref === "function") return ref(node);
179
+ if (ref) ref.current = node;
180
+ }, [ownRef, ref]);
181
+ }
182
+ const focusControl = (control) => control.focus();
183
+ /**
184
+ * The frame every field shares (input family, Select): label above or floating with a notched
185
+ * outline, the bordered control with its border/halo/hover states, description and live region.
186
+ * State selectors match `input`, `textarea` or `select`, never the inner buttons, so a focused clear or copy
187
+ * button shows only its own ring. `:open` too: an open base-select picker moves focus onto its
188
+ * options, and the select then matches neither `:focus` nor `:focus-within`. Inside `:is()` an
189
+ * unsupported `:open` is dropped alone instead of invalidating the rule.
190
+ */
191
+ function FieldShell({ label, floating = false, helperText, error, size, id: idProp, required, disabled, readOnly, "aria-describedby": describedByProp, hasStart = false, multiline = false, footer, className, status = "", controlRef, activate = focusControl, children }) {
192
+ const autoId = useId();
193
+ const id = idProp ?? `${autoId}-control`;
194
+ const descriptionId = `${autoId}-description`;
195
+ const invalid = Boolean(error);
196
+ const description = invalid ? error === true ? null : error : helperText;
197
+ const hasDescription = isPresent(description);
198
+ const describedBy = [describedByProp, hasDescription ? descriptionId : null].filter(Boolean).join(" ") || void 0;
199
+ const hasLabel = Boolean(label);
200
+ const floatingLabel = floating && hasLabel;
201
+ const asterisk = required ? /* @__PURE__ */ jsx("span", {
202
+ "aria-hidden": true,
203
+ className: "text-danger",
204
+ children: " *"
205
+ }) : null;
206
+ const forwardClick = (e) => {
207
+ const control = controlRef.current;
208
+ const target = e.target;
209
+ if (!control || control.contains(target) || target.closest("button")) return;
210
+ e.preventDefault();
211
+ activate(control);
212
+ };
213
+ const renderFloatingLabel = (restingWidth) => floatingLabel ? /* @__PURE__ */ jsxs("label", {
214
+ htmlFor: id,
215
+ className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2 truncate px-1 leading-normal", "text-fg-muted pointer-coarse:text-base", "transition-[translate,font-size,color] duration-150 ease-out motion-reduce:transition-none", multiline && {
216
+ sm: "top-[18px]",
217
+ md: "top-5",
218
+ lg: "top-6"
219
+ }[size], labelStart[size], textSize[size], restingWidth, floatShift[size], "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-[calc(100%-1rem)]", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:text-xs", invalid ? "text-danger" : ["[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:group-has-[:is(input,select,textarea):not(:is(:focus,:open))]/control:text-fg-strong", "group-has-[:is(input,select,textarea):is(:focus,:open)]/control:text-input-focus"]),
220
+ children: [label, asterisk]
221
+ }) : null;
222
+ return /* @__PURE__ */ jsxs("div", {
223
+ "data-slot": "input-field",
224
+ className: cn("flex w-full flex-col gap-1.5", disabled && "opacity-50", className),
225
+ children: [
226
+ hasLabel && !floating && /* @__PURE__ */ jsxs("label", {
227
+ htmlFor: id,
228
+ className: "self-start text-sm font-medium text-fg",
229
+ children: [label, asterisk]
230
+ }),
231
+ /* @__PURE__ */ jsxs("div", {
232
+ "data-slot": "input-control",
233
+ "data-start": hasStart ? "" : void 0,
234
+ onMouseDown: forwardClick,
235
+ className: cn("group/control relative isolate flex items-center rounded-lg border", floatingLabel ? "bg-transparent" : "[--field-bg:var(--color-input-bg)] bg-(--field-bg)", "transition-[border-color,box-shadow,background-color] duration-200 ease-in-out motion-reduce:transition-none", multiline ? {
236
+ sm: "[--field-label-shift:19px]",
237
+ md: "[--field-label-shift:21px]",
238
+ lg: "[--field-label-shift:25px]"
239
+ }[size] : controlHeight[size], invalid ? ["[--field-border:var(--color-danger)] [--field-halo:var(--color-input-halo-invalid)]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo-invalid-focus)]"] : ["[--field-border:var(--color-input-border)] [--field-halo:transparent]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-focus)] has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo)]"], "[--field-lift:0_0_#0000]", !invalid && !disabled && !readOnly && [
240
+ "hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-border-hover)]",
241
+ !floatingLabel && "hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-bg:var(--color-input-bg-hover)]",
242
+ "hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-lift:var(--shadow-input-lift)]"
243
+ ], floatingLabel ? ["border-transparent forced-colors:border-[Canvas]", !readOnly && "[box-shadow:var(--field-lift)]"] : [
244
+ "border-(--field-border)",
245
+ readOnly ? "[box-shadow:0_0_0_3px_var(--field-halo)]" : "[box-shadow:var(--shadow-input),var(--field-lift),0_0_0_3px_var(--field-halo)]",
246
+ "has-[:is(input,select,textarea):is(:focus,:open)]:outline-hidden",
247
+ disabled && "forced-colors:border-[GrayText]"
248
+ ], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
249
+ children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Notch, {
250
+ size,
251
+ ring: "halo",
252
+ disabled,
253
+ children: [label, asterisk]
254
+ }), /* @__PURE__ */ jsxs(Notch, {
255
+ size,
256
+ ring: "border",
257
+ disabled,
258
+ children: [label, asterisk]
259
+ })] }), children({
260
+ id,
261
+ invalid,
262
+ describedBy,
263
+ floating: floatingLabel,
264
+ floatingLabel: renderFloatingLabel
265
+ })]
266
+ }),
267
+ (hasDescription || isPresent(footer)) && /* @__PURE__ */ jsxs("div", {
268
+ className: "flex items-start justify-between gap-3",
269
+ children: [hasDescription && /* @__PURE__ */ jsxs("p", {
270
+ id: descriptionId,
271
+ className: cn("m-0 flex min-w-0 flex-1 items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
272
+ children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
273
+ "aria-hidden": true,
274
+ className: "mt-px size-3.5 shrink-0"
275
+ }), /* @__PURE__ */ jsx("span", { children: description })]
276
+ }), footer]
277
+ }),
278
+ /* @__PURE__ */ jsx("span", {
279
+ "aria-live": "polite",
280
+ "aria-atomic": true,
281
+ className: "sr-only",
282
+ children: status
283
+ })
284
+ ]
285
+ });
286
+ }
287
+ /** The input family's frame: adornments, actions and the input itself inside FieldShell. */
288
+ function FieldFrame({ label, floating = false, helperText, error, size = "md", start, end, mono = false, validating = false, validatingText = "Checking…", className, inputClassName, inputRef: inputRefProp, actions, status, ref, id, required, disabled, readOnly, placeholder, "aria-describedby": describedByProp, "aria-invalid": ariaInvalid, ...props }) {
289
+ const ownRef = useRef(null);
290
+ const inputRef = inputRefProp ?? ownRef;
291
+ const setRef = useMergedRef(inputRef, ref);
292
+ const hasStart = isPresent(start);
293
+ const hasEnd = isPresent(end);
294
+ return /* @__PURE__ */ jsx(FieldShell, {
295
+ label,
296
+ floating,
297
+ helperText,
298
+ error,
299
+ size,
300
+ id,
301
+ required,
302
+ disabled,
303
+ readOnly,
304
+ "aria-describedby": describedByProp,
305
+ hasStart,
306
+ className,
307
+ status: validating ? validatingText : status ?? "",
308
+ controlRef: inputRef,
309
+ children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
310
+ hasStart ? /* @__PURE__ */ jsx(Adornment, {
311
+ node: start,
312
+ side: "start",
313
+ size
314
+ }) : null,
315
+ /* @__PURE__ */ jsxs("span", {
316
+ className: "@container flex min-w-0 flex-1 self-stretch rounded-[inherit]",
317
+ children: [field.floatingLabel("max-w-[calc(100cqw-1rem)]"), /* @__PURE__ */ jsx("input", {
318
+ ...props,
319
+ ref: setRef,
320
+ id: field.id,
321
+ required,
322
+ disabled,
323
+ readOnly,
324
+ placeholder: field.floating ? placeholder || " " : placeholder,
325
+ "aria-invalid": field.invalid ? true : ariaInvalid,
326
+ "aria-describedby": field.describedBy,
327
+ spellCheck: mono ? false : props.spellCheck,
328
+ autoCapitalize: mono ? "off" : props.autoCapitalize,
329
+ autoCorrect: mono ? "off" : props.autoCorrect,
330
+ className: cn("h-full min-w-0 flex-1 rounded-[inherit] bg-transparent text-fg outline-none", "placeholder:text-input-placeholder pointer-coarse:text-base", "disabled:cursor-not-allowed", "autofill:[transition:background-color_0s_600000s] autofill:[-webkit-text-fill-color:var(--color-fg)]", textSize[size], inputPadding[size], hasStart && (isText(start) ? "ps-1" : "ps-2"), !validating && hasEnd && isText(end) ? "pe-1" : (validating || hasEnd || actions) && "pe-2", mono && "font-mono", field.floating && ["placeholder:opacity-0", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:placeholder:opacity-100"], inputClassName)
331
+ })]
332
+ }),
333
+ validating ? /* @__PURE__ */ jsx("span", {
334
+ className: cn("flex shrink-0 items-center self-stretch text-fg-muted", iconSize$1[size], endSlotPadding[size]),
335
+ children: /* @__PURE__ */ jsx(LoaderCircle, {
336
+ "aria-hidden": true,
337
+ className: "animate-spin motion-reduce:[animation-duration:2.5s]"
338
+ })
339
+ }) : hasEnd ? /* @__PURE__ */ jsx(Adornment, {
340
+ node: end,
341
+ side: "end",
342
+ size
343
+ }) : null,
344
+ actions ? /* @__PURE__ */ jsx("span", {
345
+ className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
346
+ children: actions
347
+ }) : null
348
+ ] })
349
+ });
350
+ }
351
+ //#endregion
352
+ //#region src/components/accordion/use-accordion-motion.ts
353
+ /** Animate measured heights; never cap content with an arbitrary max-height. */
354
+ function useAccordionMotion(open, itemRef, restoreFocus) {
355
+ const panelRef = useRef(null);
356
+ const innerRef = useRef(null);
357
+ const previousOpen = useRef(open);
358
+ const [present, setPresent] = useState(open);
359
+ useLayoutEffect(() => {
360
+ const panel = panelRef.current;
361
+ const inner = innerRef.current;
362
+ if (!panel || !inner) return;
363
+ const changed = previousOpen.current !== open;
364
+ previousOpen.current = open;
365
+ let active = true;
366
+ let animation;
367
+ let observer;
368
+ let target = -1;
369
+ let settled = false;
370
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)");
371
+ if (!open && panel.contains(panel.ownerDocument.activeElement) && itemRef.current) restoreFocus(itemRef.current);
372
+ panel.inert = !open;
373
+ panel.toggleAttribute("inert", !open);
374
+ if (open) panel.removeAttribute("aria-hidden");
375
+ else panel.setAttribute("aria-hidden", "true");
376
+ const finish = () => {
377
+ if (!active) return;
378
+ settled = true;
379
+ observer?.disconnect();
380
+ animation?.cancel();
381
+ animation = void 0;
382
+ panel.style.removeProperty("height");
383
+ panel.style.removeProperty("overflow");
384
+ panel.hidden = !open;
385
+ setPresent(open);
386
+ };
387
+ const run = () => {
388
+ if (!active || settled) return;
389
+ const nextTarget = open ? inner.getBoundingClientRect().height : 0;
390
+ if (nextTarget === target && animation) return;
391
+ target = nextTarget;
392
+ const from = panel.getBoundingClientRect().height;
393
+ animation?.cancel();
394
+ panel.style.height = `${from}px`;
395
+ const current = panel.animate([{ height: `${from}px` }, { height: `${target}px` }], {
396
+ duration: open ? 200 : 160,
397
+ easing: "cubic-bezier(.16,1,.3,1)",
398
+ fill: "both"
399
+ });
400
+ animation = current;
401
+ current.finished.then(() => {
402
+ if (active && animation === current) finish();
403
+ }, () => {});
404
+ };
405
+ if (!changed || reduced?.matches || typeof panel.animate !== "function") finish();
406
+ else {
407
+ if (open && !panel.style.height) panel.style.height = "0px";
408
+ panel.hidden = false;
409
+ panel.style.overflow = "hidden";
410
+ setPresent(true);
411
+ run();
412
+ if (open && typeof ResizeObserver !== "undefined") {
413
+ observer = new ResizeObserver(run);
414
+ observer.observe(inner);
415
+ }
416
+ }
417
+ const onReducedMotion = () => {
418
+ if (reduced?.matches) finish();
419
+ };
420
+ reduced?.addEventListener("change", onReducedMotion);
421
+ return () => {
422
+ active = false;
423
+ observer?.disconnect();
424
+ reduced?.removeEventListener("change", onReducedMotion);
425
+ if (animation) {
426
+ panel.style.height = `${panel.getBoundingClientRect().height}px`;
427
+ animation.cancel();
428
+ }
429
+ };
430
+ }, [
431
+ open,
432
+ itemRef,
433
+ restoreFocus
434
+ ]);
435
+ return {
436
+ panelRef,
437
+ innerRef,
438
+ present
439
+ };
440
+ }
441
+ //#endregion
442
+ //#region src/components/accordion/accordion.tsx
443
+ const AccordionContext = createContext(null);
444
+ const ItemContext = createContext(null);
445
+ function useAccordionContext(part) {
446
+ const context = useContext(AccordionContext);
447
+ if (!context) throw new Error(`${part} must be rendered inside <Accordion>`);
448
+ return context;
449
+ }
450
+ function useItemContext(part) {
451
+ const context = useContext(ItemContext);
452
+ if (!context) throw new Error(`${part} must be rendered inside <AccordionItem>`);
453
+ return context;
454
+ }
455
+ const valuesOf = (value) => typeof value === "string" ? [value] : [...new Set(value ?? [])];
456
+ const ownTriggers = (root) => [...root.querySelectorAll("[data-slot=\"accordion-trigger\"]")].filter((node) => node.closest("[data-slot=\"accordion\"]") === root);
457
+ /** Vertical disclosure group. Changing type requires a new React key. */
458
+ function Accordion({ type = "single", value, defaultValue, onValueChange, collapsible = true, variant = "line", headingLevel = 3, disabled = false, className, children, ref, onKeyDown, ...props }) {
459
+ const [initialType] = useState(type);
460
+ if (initialType !== type) throw new Error("Changing Accordion type requires a new key");
461
+ const controlled = value !== void 0;
462
+ const [internal, setInternal] = useState(() => valuesOf(defaultValue));
463
+ const selected = controlled ? valuesOf(value) : internal;
464
+ const rootRef = useRef(null);
465
+ const mergedRef = useMergedRef(rootRef, ref);
466
+ const items = useRef(/* @__PURE__ */ new Map());
467
+ const internalRef = useRef(internal);
468
+ useLayoutEffect(() => {
469
+ internalRef.current = internal;
470
+ }, [internal]);
471
+ const restoreFocus = useCallback((item, removing = false) => {
472
+ const root = rootRef.current;
473
+ if (!root) return;
474
+ const triggers = ownTriggers(root);
475
+ const own = triggers.find((node) => node.closest("[data-slot=\"accordion-item\"]") === item);
476
+ const index = own ? triggers.indexOf(own) : -1;
477
+ const next = (!removing && own && !own.disabled ? own : void 0) ?? triggers.slice(index + 1).find((node) => !node.disabled) ?? triggers.slice(0, Math.max(index, 0)).reverse().find((node) => !node.disabled);
478
+ if (next) next.focus({ preventScroll: true });
479
+ else {
480
+ const previous = root.getAttribute("tabindex");
481
+ root.tabIndex = -1;
482
+ root.focus({ preventScroll: true });
483
+ if (previous === null) root.removeAttribute("tabindex");
484
+ else root.setAttribute("tabindex", previous);
485
+ }
486
+ }, []);
487
+ const register = useCallback((itemValue, node) => {
488
+ const members = items.current;
489
+ if (members.has(itemValue)) throw new Error(`Accordion item values must be unique: "${itemValue}"`);
490
+ members.set(itemValue, node);
491
+ const reconcile = () => {
492
+ queueMicrotask(() => {
493
+ if (controlled || !rootRef.current) return;
494
+ if (internalRef.current.every((entry) => members.has(entry))) return;
495
+ setInternal((current) => {
496
+ const next = current.filter((entry) => members.has(entry));
497
+ return next.length === current.length ? current : next;
498
+ });
499
+ });
500
+ };
501
+ reconcile();
502
+ return () => {
503
+ members.delete(itemValue);
504
+ const focused = node.ownerDocument.activeElement;
505
+ if (focused && node.contains(focused)) {
506
+ const root = rootRef.current;
507
+ const triggers = root ? ownTriggers(root) : [];
508
+ const index = triggers.findIndex((trigger) => node.contains(trigger));
509
+ const candidates = [...triggers.slice(index + 1), ...triggers.slice(0, index).reverse()];
510
+ queueMicrotask(() => {
511
+ const active = node.ownerDocument.activeElement;
512
+ if (node.isConnected || !rootRef.current || active !== node.ownerDocument.body && active !== focused) return;
513
+ const next = candidates.find((trigger) => trigger.isConnected && !trigger.disabled);
514
+ if (next) next.focus({ preventScroll: true });
515
+ else restoreFocus(node, true);
516
+ });
517
+ }
518
+ reconcile();
519
+ };
520
+ }, [controlled, restoreFocus]);
521
+ const toggle = (itemValue) => {
522
+ const open = selected.includes(itemValue);
523
+ if (disabled || type === "single" && open && !collapsible) return;
524
+ const next = open ? selected.filter((entry) => entry !== itemValue) : type === "single" ? [itemValue] : [...selected, itemValue];
525
+ if (!controlled) setInternal(next);
526
+ if (type === "multiple") onValueChange?.(next);
527
+ else onValueChange?.(next[0] ?? null);
528
+ };
529
+ return /* @__PURE__ */ jsx(AccordionContext, {
530
+ value: {
531
+ selected,
532
+ variant,
533
+ headingLevel,
534
+ disabled,
535
+ collapsible: type === "multiple" || collapsible,
536
+ toggle,
537
+ register,
538
+ restoreFocus
539
+ },
540
+ children: /* @__PURE__ */ jsx(ItemContext, {
541
+ value: null,
542
+ children: /* @__PURE__ */ jsx("div", {
543
+ ...props,
544
+ ref: mergedRef,
545
+ "data-slot": "accordion",
546
+ "data-variant": variant,
547
+ "data-disabled": disabled || void 0,
548
+ className: cn("min-w-0", variant === "outline" && "flex flex-col gap-2", className),
549
+ onKeyDown: (event) => {
550
+ onKeyDown?.(event);
551
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
552
+ if (![
553
+ "ArrowDown",
554
+ "ArrowUp",
555
+ "Home",
556
+ "End"
557
+ ].includes(event.key)) return;
558
+ const triggers = ownTriggers(event.currentTarget).filter((node) => !node.disabled);
559
+ const index = triggers.indexOf(event.target);
560
+ if (index === -1) return;
561
+ event.preventDefault();
562
+ triggers[event.key === "Home" ? 0 : event.key === "End" ? triggers.length - 1 : Math.max(0, Math.min(triggers.length - 1, index + (event.key === "ArrowDown" ? 1 : -1)))]?.focus();
563
+ },
564
+ children
565
+ })
566
+ })
567
+ });
568
+ }
569
+ function AccordionItem({ value, disabled = false, className, children, ref, ...props }) {
570
+ const group = useAccordionContext("AccordionItem");
571
+ if (!value.trim()) throw new Error("AccordionItem value must be a non-empty string");
572
+ const id = useId();
573
+ const itemRef = useRef(null);
574
+ const mergedRef = useMergedRef(itemRef, ref);
575
+ const register = group.register;
576
+ useLayoutEffect(() => {
577
+ if (itemRef.current) return register(value, itemRef.current);
578
+ }, [register, value]);
579
+ const open = group.selected.includes(value);
580
+ const isDisabled = group.disabled || disabled;
581
+ return /* @__PURE__ */ jsx(ItemContext, {
582
+ value: {
583
+ id,
584
+ value,
585
+ open,
586
+ disabled: isDisabled,
587
+ itemRef
588
+ },
589
+ children: /* @__PURE__ */ jsx("div", {
590
+ ...props,
591
+ ref: mergedRef,
592
+ "data-slot": "accordion-item",
593
+ "data-state": open ? "open" : "closed",
594
+ "data-disabled": isDisabled || void 0,
595
+ className: cn("min-w-0 border-input-border", group.variant === "outline" ? "rounded-xl border bg-surface" : "border-b", className),
596
+ children
597
+ })
598
+ });
599
+ }
600
+ function AccordionTrigger({ children, description, className, onClick, "aria-describedby": describedBy, ...props }) {
601
+ const group = useAccordionContext("AccordionTrigger");
602
+ const item = useItemContext("AccordionTrigger");
603
+ const Heading = `h${group.headingLevel}`;
604
+ const hasDescription = isPresent(description);
605
+ const locked = item.open && !group.collapsible;
606
+ return /* @__PURE__ */ jsx(Heading, {
607
+ "data-slot": "accordion-heading",
608
+ className: "m-0 text-inherit",
609
+ children: /* @__PURE__ */ jsxs("button", {
610
+ ...props,
611
+ id: `${item.id}-trigger`,
612
+ type: "button",
613
+ disabled: item.disabled,
614
+ "aria-expanded": item.open,
615
+ "aria-controls": `${item.id}-content`,
616
+ "aria-disabled": locked || void 0,
617
+ "aria-labelledby": props["aria-label"] ? void 0 : `${item.id}-title`,
618
+ "aria-describedby": [describedBy, hasDescription && `${item.id}-description`].filter(Boolean).join(" ") || void 0,
619
+ "data-slot": "accordion-trigger",
620
+ "data-state": item.open ? "open" : "closed",
621
+ className: cn("group/accordion-trigger flex min-h-12 w-full cursor-pointer items-start gap-3 rounded-sm py-3 text-start text-sm/6 font-medium text-fg outline-none", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", "disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-default", "forced-colors:disabled:text-[GrayText]", group.variant === "outline" && "rounded-xl px-4", className),
622
+ onClick: (event) => {
623
+ onClick?.(event);
624
+ if (!event.defaultPrevented && !item.disabled && !locked) group.toggle(item.value);
625
+ },
626
+ children: [/* @__PURE__ */ jsxs("span", {
627
+ className: "min-w-0 flex-1 wrap-anywhere",
628
+ children: [/* @__PURE__ */ jsx("span", {
629
+ id: `${item.id}-title`,
630
+ className: "underline-offset-4 group-enabled/accordion-trigger:group-hover/accordion-trigger:underline",
631
+ children
632
+ }), hasDescription && /* @__PURE__ */ jsx("span", {
633
+ id: `${item.id}-description`,
634
+ className: "mt-0.5 block text-xs/4.5 font-normal text-fg-muted",
635
+ children: description
636
+ })]
637
+ }), /* @__PURE__ */ jsx(ChevronDown, {
638
+ "aria-hidden": true,
639
+ className: cn("mt-1 size-4 shrink-0 text-fg-muted transition-transform duration-200 ease-out motion-reduce:transition-none", item.open && "rotate-180")
640
+ })]
641
+ })
642
+ });
643
+ }
644
+ function AccordionContent({ keepMounted = true, region = false, className, contentClassName, children, ref, ...props }) {
645
+ const group = useAccordionContext("AccordionContent");
646
+ const item = useItemContext("AccordionContent");
647
+ const { panelRef, innerRef, present } = useAccordionMotion(item.open, item.itemRef, group.restoreFocus);
648
+ const mergedRef = useMergedRef(panelRef, ref);
649
+ return /* @__PURE__ */ jsx("div", {
650
+ ...props,
651
+ ref: mergedRef,
652
+ id: `${item.id}-content`,
653
+ role: region ? "region" : void 0,
654
+ "aria-labelledby": region ? `${item.id}-title` : void 0,
655
+ hidden: !item.open && !present,
656
+ inert: !item.open && !present,
657
+ "aria-hidden": !item.open && !present ? true : void 0,
658
+ "data-slot": "accordion-content",
659
+ "data-state": item.open ? "open" : "closed",
660
+ className: cn("min-w-0", contentClassName),
661
+ children: /* @__PURE__ */ jsx("div", {
662
+ ref: innerRef,
663
+ "data-slot": "accordion-content-inner",
664
+ className: cn("flow-root min-w-0 pb-4 text-sm/5.5 wrap-anywhere text-fg-muted", group.variant === "outline" ? "ps-4 pe-11" : "pe-7", className),
665
+ children: (keepMounted || item.open || present) && children
666
+ })
667
+ });
668
+ }
669
+ //#endregion
81
670
  //#region src/hooks/use-press-feedback.ts
82
671
  const GROW_MS = 450;
83
672
  const FADE_MS = 300;
@@ -221,7 +810,7 @@ function Slot({ children, ref, ...props }) {
221
810
  const OPEN_DELAY_MS = 300;
222
811
  const GAP_PX = 6;
223
812
  const VIEWPORT_PADDING_PX = 8;
224
- const opposite = {
813
+ const opposite$1 = {
225
814
  top: "bottom",
226
815
  bottom: "top",
227
816
  left: "right",
@@ -234,7 +823,7 @@ function isFocusVisible$1(el) {
234
823
  return true;
235
824
  }
236
825
  }
237
- function clamp(value, min, max) {
826
+ function clamp$2(value, min, max) {
238
827
  return Math.max(min, Math.min(value, Math.max(min, max)));
239
828
  }
240
829
  /**
@@ -251,15 +840,15 @@ function place(anchor, width, height, preferred) {
251
840
  right: vw - anchor.right
252
841
  };
253
842
  const need = (s) => (s === "top" || s === "bottom" ? height : width) + GAP_PX + VIEWPORT_PADDING_PX;
254
- const side = room[preferred] < need(preferred) && room[opposite[preferred]] > room[preferred] ? opposite[preferred] : preferred;
843
+ const side = room[preferred] < need(preferred) && room[opposite$1[preferred]] > room[preferred] ? opposite$1[preferred] : preferred;
255
844
  if (side === "top" || side === "bottom") return {
256
845
  side,
257
- left: clamp(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
846
+ left: clamp$2(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
258
847
  top: side === "top" ? anchor.top - height - GAP_PX : anchor.bottom
259
848
  };
260
849
  return {
261
850
  side,
262
- top: clamp(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
851
+ top: clamp$2(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
263
852
  left: side === "left" ? anchor.left - width - GAP_PX : anchor.right
264
853
  };
265
854
  }
@@ -452,7 +1041,7 @@ const spinnerSize = {
452
1041
  md: 18,
453
1042
  lg: 20
454
1043
  };
455
- const iconSize$1 = {
1044
+ const iconSize = {
456
1045
  sm: "[&_svg]:size-4",
457
1046
  md: "[&_svg]:size-4",
458
1047
  lg: "[&_svg]:size-[18px]"
@@ -470,7 +1059,7 @@ const iconEndPadding = {
470
1059
  /** Press layer + content + spinner; shared by every button-styled host. */
471
1060
  function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, children }) {
472
1061
  const layerRef = usePressFeedback(ripple);
473
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1062
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
474
1063
  /* @__PURE__ */ jsx("span", {
475
1064
  ref: layerRef,
476
1065
  "data-ripple": ripple ? "" : void 0,
@@ -478,7 +1067,7 @@ function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, chi
478
1067
  className: "pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit] forced-colors:hidden"
479
1068
  }),
480
1069
  /* @__PURE__ */ jsxs("span", {
481
- className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize$1[size], loading && "opacity-0"),
1070
+ className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize[size], loading && "opacity-0"),
482
1071
  children: [
483
1072
  iconLeft && /* @__PURE__ */ jsx("span", {
484
1073
  "aria-hidden": true,
@@ -569,7 +1158,7 @@ function ButtonImpl({ asChild = false, className, intent, size, tone, fullWidth,
569
1158
  },
570
1159
  children: content(children)
571
1160
  });
572
- return /* @__PURE__ */ jsxs(Fragment, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
1161
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
573
1162
  ...tooltip,
574
1163
  className: fullWidth ? "flex w-full" : void 0,
575
1164
  children: button
@@ -667,395 +1256,135 @@ function Alert({ tone = "neutral", title, action, onDismiss, dismissLabel = "Dis
667
1256
  children: action
668
1257
  })
669
1258
  ]
670
- }), onDismiss && /* @__PURE__ */ jsx(Button, {
671
- "aria-label": dismissLabel,
672
- className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
673
- icon: /* @__PURE__ */ jsx(X, {}),
674
- intent: "ghost",
675
- size: "sm",
676
- onClick: onDismiss
677
- })]
678
- });
679
- }
680
- //#endregion
681
- //#region src/components/badge/badge.tsx
682
- const toneClasses$1 = {
683
- neutral: {
684
- subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
685
- outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
686
- solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
687
- },
688
- info: {
689
- subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
690
- outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
691
- solid: ["border-info bg-info text-info-fg", "text-info-fg"]
692
- },
693
- success: {
694
- subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
695
- outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
696
- solid: ["border-success bg-success text-success-fg", "text-success-fg"]
697
- },
698
- warning: {
699
- subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
700
- outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
701
- solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
702
- },
703
- danger: {
704
- subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
705
- outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
706
- solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
707
- }
708
- };
709
- const sizeClasses$3 = {
710
- sm: {
711
- base: "h-5 px-1.5",
712
- withIcon: "ps-1"
713
- },
714
- md: {
715
- base: "h-6 px-2",
716
- withIcon: "ps-1.5"
717
- }
718
- };
719
- function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
720
- const [toneClass, iconClass] = toneClasses$1[tone][variant];
721
- const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
722
- return /* @__PURE__ */ jsxs("span", {
723
- ...props,
724
- "data-slot": "badge",
725
- className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
726
- children: [hasIcon && /* @__PURE__ */ jsx("span", {
727
- "aria-hidden": "true",
728
- "data-slot": "badge-icon",
729
- className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
730
- children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
731
- }), /* @__PURE__ */ jsxs("span", {
732
- "data-slot": "badge-label",
733
- className: "min-w-0 truncate leading-4",
734
- children: [context && /* @__PURE__ */ jsxs("span", {
735
- className: "sr-only",
736
- children: [context, ": "]
737
- }), children]
738
- })]
739
- });
740
- }
741
- //#endregion
742
- //#region src/components/card/card.tsx
743
- const CardContext = createContext({
744
- titleId: "",
745
- setTitled: () => {}
746
- });
747
- /**
748
- * Bordered surface for a group of related content. No padding of its own: CardHeader and
749
- * CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
750
- */
751
- function Card({ as: Comp = "div", className, ...props }) {
752
- const titleId = useId();
753
- const [titled, setTitled] = useState(false);
754
- return /* @__PURE__ */ jsx(CardContext, {
755
- value: {
756
- titleId,
757
- setTitled
758
- },
759
- children: /* @__PURE__ */ jsx(Comp, {
760
- "aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
761
- ...props,
762
- "data-slot": "card",
763
- className: cn("rounded-xl border border-border-strong bg-surface", className)
764
- })
765
- });
766
- }
767
- function CardHeader({ title, description, action, headingLevel = 2, className, ...props }) {
768
- const { titleId, setTitled } = useContext(CardContext);
769
- useLayoutEffect(() => {
770
- setTitled(true);
771
- return () => setTitled(false);
772
- }, [setTitled]);
773
- const Heading = headingLevel === 2 ? "h2" : "h3";
774
- return /* @__PURE__ */ jsxs("div", {
775
- ...props,
776
- "data-slot": "card-header",
777
- className: cn("flex items-start justify-between gap-4 px-5 pt-5", className),
778
- children: [/* @__PURE__ */ jsxs("div", {
779
- className: "min-w-0",
780
- children: [/* @__PURE__ */ jsx(Heading, {
781
- id: titleId,
782
- className: "font-sans text-sm font-medium",
783
- children: title
784
- }), description && /* @__PURE__ */ jsx("p", {
785
- className: "text-xs text-fg-muted",
786
- children: description
787
- })]
788
- }), action && /* @__PURE__ */ jsx("div", {
789
- className: "shrink-0",
790
- children: action
791
- })]
792
- });
793
- }
794
- function CardContent({ className, ...props }) {
795
- return /* @__PURE__ */ jsx("div", {
796
- ...props,
797
- "data-slot": "card-content",
798
- className: cn("p-5 [[data-slot=card-header]+&]:pt-4", className)
799
- });
800
- }
801
- //#endregion
802
- //#region src/components/input/field.tsx
803
- const controlHeight = {
804
- sm: "h-[34px]",
805
- md: "h-[38px]",
806
- lg: "h-[42px]"
807
- };
808
- const textSize = {
809
- sm: "text-sm",
810
- md: "text-sm",
811
- lg: "text-base"
812
- };
813
- const iconSize = {
814
- sm: "[&_svg]:size-4",
815
- md: "[&_svg]:size-4",
816
- lg: "[&_svg]:size-[18px]"
817
- };
818
- const inputPadding = {
819
- sm: "px-3",
820
- md: "px-3",
821
- lg: "px-4"
822
- };
823
- const startSlotPadding = {
824
- sm: "ps-2",
825
- md: "ps-2.5",
826
- lg: "ps-3"
827
- };
828
- const endSlotPadding = {
829
- sm: "pe-2",
830
- md: "pe-2.5",
831
- lg: "pe-3"
832
- };
833
- const actionsPadding = {
834
- sm: "pe-[3px]",
835
- md: "pe-1",
836
- lg: "pe-1"
837
- };
838
- /** Compact Button square that fits inside the field's inner height with an even inset. */
839
- const compactButton = {
840
- sm: "size-[26px]",
841
- md: "size-7",
842
- lg: "size-8"
843
- };
844
- const labelStart = {
845
- sm: "start-2",
846
- md: "start-2",
847
- lg: "start-3"
848
- };
849
- const floatShift = {
850
- sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+17px)]",
851
- md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+19px)]",
852
- lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+21px)]"
853
- };
854
- const isText = (node) => typeof node === "string" || typeof node === "number";
855
- const isPresent = (node) => node != null && node !== false && node !== "";
856
- function Adornment({ node, side, size }) {
857
- const text = isText(node);
858
- return /* @__PURE__ */ jsx("span", {
859
- "aria-hidden": text ? void 0 : true,
860
- className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
861
- children: node
1259
+ }), onDismiss && /* @__PURE__ */ jsx(Button, {
1260
+ "aria-label": dismissLabel,
1261
+ className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
1262
+ icon: /* @__PURE__ */ jsx(X, {}),
1263
+ intent: "ghost",
1264
+ size: "sm",
1265
+ onClick: onDismiss
1266
+ })]
862
1267
  });
863
1268
  }
864
- const notchPadding = {
865
- border: {
866
- sm: "ps-2 pe-2",
867
- md: "ps-2 pe-2",
868
- lg: "ps-3 pe-1"
1269
+ //#endregion
1270
+ //#region src/components/badge/badge.tsx
1271
+ const toneClasses$1 = {
1272
+ neutral: {
1273
+ subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
1274
+ outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
1275
+ solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
869
1276
  },
870
- halo: {
871
- sm: "ps-[9px] pe-[9px]",
872
- md: "ps-[9px] pe-[9px]",
873
- lg: "ps-[13px] pe-[5px]"
1277
+ info: {
1278
+ subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
1279
+ outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
1280
+ solid: ["border-info bg-info text-info-fg", "text-info-fg"]
1281
+ },
1282
+ success: {
1283
+ subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
1284
+ outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
1285
+ solid: ["border-success bg-success text-success-fg", "text-success-fg"]
1286
+ },
1287
+ warning: {
1288
+ subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
1289
+ outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
1290
+ solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
1291
+ },
1292
+ danger: {
1293
+ subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
1294
+ outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
1295
+ solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
874
1296
  }
875
1297
  };
876
- /**
877
- * MUI-style notched outline: a fieldset draws the ring and an invisible legend holding the label
878
- * text at its floated size cuts the gap. The legend collapses while the label rests in the field.
879
- * A legend as tall as the border keeps the border at the fieldset's top edge.
880
- */
881
- function Notch({ ring, size, disabled, children }) {
882
- const halo = ring === "halo";
883
- return /* @__PURE__ */ jsx("fieldset", {
884
- "aria-hidden": true,
885
- className: cn("pointer-events-none absolute m-0 min-w-0 p-0", "transition-[border-color] duration-200 ease-in-out motion-reduce:transition-none", notchPadding[ring][size], halo ? ["-inset-1 rounded-[calc(var(--radius-lg)+3px)] border-[3px] border-(--field-halo)", "forced-colors:invisible forced-colors:group-has-[:is(input,select):is(:focus,:open)]/control:visible forced-colors:border-[Highlight]"] : ["-inset-px rounded-[inherit] border border-(--field-border)", disabled && "forced-colors:border-[GrayText]"]),
886
- children: /* @__PURE__ */ jsx("legend", {
887
- className: cn("invisible float-none block w-auto max-w-[0.01px] overflow-hidden p-0 text-xs whitespace-nowrap", halo ? "h-0.75" : "h-px", "transition-[max-width] duration-50 ease-out motion-reduce:transition-none", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-full", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:delay-50", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:duration-100"),
888
- children: /* @__PURE__ */ jsx("span", {
889
- className: "px-1",
890
- children
891
- })
892
- })
1298
+ const sizeClasses$3 = {
1299
+ sm: {
1300
+ base: "h-5 px-1.5",
1301
+ withIcon: "ps-1"
1302
+ },
1303
+ md: {
1304
+ base: "h-6 px-2",
1305
+ withIcon: "ps-1.5"
1306
+ }
1307
+ };
1308
+ function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
1309
+ const [toneClass, iconClass] = toneClasses$1[tone][variant];
1310
+ const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
1311
+ return /* @__PURE__ */ jsxs("span", {
1312
+ ...props,
1313
+ "data-slot": "badge",
1314
+ className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
1315
+ children: [hasIcon && /* @__PURE__ */ jsx("span", {
1316
+ "aria-hidden": "true",
1317
+ "data-slot": "badge-icon",
1318
+ className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
1319
+ children: busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
1320
+ }), /* @__PURE__ */ jsxs("span", {
1321
+ "data-slot": "badge-label",
1322
+ className: "min-w-0 truncate leading-4",
1323
+ children: [context && /* @__PURE__ */ jsxs("span", {
1324
+ className: "sr-only",
1325
+ children: [context, ": "]
1326
+ }), children]
1327
+ })]
893
1328
  });
894
1329
  }
895
- /** Keeps an internal ref (focus forwarding, clear, reset) alongside the caller's ref. */
896
- function useMergedRef(ownRef, ref) {
897
- return useCallback((node) => {
898
- ownRef.current = node;
899
- if (typeof ref === "function") return ref(node);
900
- if (ref) ref.current = node;
901
- }, [ownRef, ref]);
902
- }
903
- const focusControl = (control) => control.focus();
1330
+ //#endregion
1331
+ //#region src/components/card/card.tsx
1332
+ const CardContext = createContext({
1333
+ titleId: "",
1334
+ setTitled: () => {}
1335
+ });
904
1336
  /**
905
- * The frame every field shares (input family, Select): label above or floating with a notched
906
- * outline, the bordered control with its border/halo/hover states, description and live region.
907
- * State selectors match `input` or `select`, never the inner buttons, so a focused clear or copy
908
- * button shows only its own ring. `:open` too: an open base-select picker moves focus onto its
909
- * options, and the select then matches neither `:focus` nor `:focus-within`. Inside `:is()` an
910
- * unsupported `:open` is dropped alone instead of invalidating the rule.
1337
+ * Bordered surface for a group of related content. No padding of its own: CardHeader and
1338
+ * CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
911
1339
  */
912
- function FieldShell({ label, floating = false, helperText, error, size, id: idProp, required, disabled, readOnly, "aria-describedby": describedByProp, hasStart = false, className, status = "", controlRef, activate = focusControl, children }) {
913
- const autoId = useId();
914
- const id = idProp ?? `${autoId}-control`;
915
- const descriptionId = `${autoId}-description`;
916
- const invalid = Boolean(error);
917
- const description = invalid ? error === true ? null : error : helperText;
918
- const hasDescription = isPresent(description);
919
- const describedBy = [describedByProp, hasDescription ? descriptionId : null].filter(Boolean).join(" ") || void 0;
920
- const hasLabel = Boolean(label);
921
- const floatingLabel = floating && hasLabel;
922
- const asterisk = required ? /* @__PURE__ */ jsx("span", {
923
- "aria-hidden": true,
924
- className: "text-danger",
925
- children: " *"
926
- }) : null;
927
- const forwardClick = (e) => {
928
- const control = controlRef.current;
929
- const target = e.target;
930
- if (!control || control.contains(target) || target.closest("button")) return;
931
- e.preventDefault();
932
- activate(control);
933
- };
934
- const renderFloatingLabel = (restingWidth) => floatingLabel ? /* @__PURE__ */ jsxs("label", {
935
- htmlFor: id,
936
- className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2 truncate px-1 leading-normal", "text-fg-muted pointer-coarse:text-base", "transition-[translate,font-size,color] duration-150 ease-out motion-reduce:transition-none", labelStart[size], textSize[size], restingWidth, floatShift[size], "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-[calc(100%-1rem)]", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:text-xs", invalid ? "text-danger" : ["[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:group-has-[:is(input,select):not(:is(:focus,:open))]/control:text-fg-strong", "group-has-[:is(input,select):is(:focus,:open)]/control:text-input-focus"]),
937
- children: [label, asterisk]
938
- }) : null;
939
- return /* @__PURE__ */ jsxs("div", {
940
- "data-slot": "input-field",
941
- className: cn("flex w-full flex-col gap-1.5", disabled && "opacity-50", className),
942
- children: [
943
- hasLabel && !floating && /* @__PURE__ */ jsxs("label", {
944
- htmlFor: id,
945
- className: "self-start text-sm font-medium text-fg",
946
- children: [label, asterisk]
947
- }),
948
- /* @__PURE__ */ jsxs("div", {
949
- "data-slot": "input-control",
950
- "data-start": hasStart ? "" : void 0,
951
- onMouseDown: forwardClick,
952
- className: cn("group/control relative isolate flex items-center rounded-lg border", floatingLabel ? "bg-transparent" : "[--field-bg:var(--color-input-bg)] bg-(--field-bg)", "transition-[border-color,box-shadow,background-color] duration-200 ease-in-out motion-reduce:transition-none", controlHeight[size], invalid ? ["[--field-border:var(--color-danger)] [--field-halo:var(--color-input-halo-invalid)]", "has-[:is(input,select):is(:focus,:open)]:[--field-halo:var(--color-input-halo-invalid-focus)]"] : ["[--field-border:var(--color-input-border)] [--field-halo:transparent]", "has-[:is(input,select):is(:focus,:open)]:[--field-border:var(--color-input-focus)] has-[:is(input,select):is(:focus,:open)]:[--field-halo:var(--color-input-halo)]"], "[--field-lift:0_0_#0000]", !invalid && !disabled && !readOnly && [
953
- "hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-border:var(--color-input-border-hover)]",
954
- !floatingLabel && "hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-bg:var(--color-input-bg-hover)]",
955
- "hover:not-has-[:is(input,select):is(:focus,:open)]:[--field-lift:var(--shadow-input-lift)]"
956
- ], floatingLabel ? ["border-transparent forced-colors:border-[Canvas]", !readOnly && "[box-shadow:var(--field-lift)]"] : [
957
- "border-(--field-border)",
958
- readOnly ? "[box-shadow:0_0_0_3px_var(--field-halo)]" : "[box-shadow:var(--shadow-input),var(--field-lift),0_0_0_3px_var(--field-halo)]",
959
- "has-[:is(input,select):is(:focus,:open)]:outline-hidden",
960
- disabled && "forced-colors:border-[GrayText]"
961
- ], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
962
- children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Notch, {
963
- size,
964
- ring: "halo",
965
- disabled,
966
- children: [label, asterisk]
967
- }), /* @__PURE__ */ jsxs(Notch, {
968
- size,
969
- ring: "border",
970
- disabled,
971
- children: [label, asterisk]
972
- })] }), children({
973
- id,
974
- invalid,
975
- describedBy,
976
- floating: floatingLabel,
977
- floatingLabel: renderFloatingLabel
978
- })]
979
- }),
980
- hasDescription && /* @__PURE__ */ jsxs("p", {
981
- id: descriptionId,
982
- className: cn("m-0 flex items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
983
- children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
984
- "aria-hidden": true,
985
- className: "mt-px size-3.5 shrink-0"
986
- }), /* @__PURE__ */ jsx("span", { children: description })]
987
- }),
988
- /* @__PURE__ */ jsx("span", {
989
- "aria-live": "polite",
990
- "aria-atomic": true,
991
- className: "sr-only",
992
- children: status
993
- })
994
- ]
995
- });
996
- }
997
- /** The input family's frame: adornments, actions and the input itself inside FieldShell. */
998
- function FieldFrame({ label, floating = false, helperText, error, size = "md", start, end, mono = false, validating = false, validatingText = "Checking…", className, inputClassName, inputRef: inputRefProp, actions, status, ref, id, required, disabled, readOnly, placeholder, "aria-describedby": describedByProp, "aria-invalid": ariaInvalid, ...props }) {
999
- const ownRef = useRef(null);
1000
- const inputRef = inputRefProp ?? ownRef;
1001
- const setRef = useMergedRef(inputRef, ref);
1002
- const hasStart = isPresent(start);
1003
- const hasEnd = isPresent(end);
1004
- return /* @__PURE__ */ jsx(FieldShell, {
1005
- label,
1006
- floating,
1007
- helperText,
1008
- error,
1009
- size,
1010
- id,
1011
- required,
1012
- disabled,
1013
- readOnly,
1014
- "aria-describedby": describedByProp,
1015
- hasStart,
1016
- className,
1017
- status: validating ? validatingText : status ?? "",
1018
- controlRef: inputRef,
1019
- children: (field) => /* @__PURE__ */ jsxs(Fragment, { children: [
1020
- hasStart ? /* @__PURE__ */ jsx(Adornment, {
1021
- node: start,
1022
- side: "start",
1023
- size
1024
- }) : null,
1025
- /* @__PURE__ */ jsxs("span", {
1026
- className: "@container flex min-w-0 flex-1 self-stretch rounded-[inherit]",
1027
- children: [field.floatingLabel("max-w-[calc(100cqw-1rem)]"), /* @__PURE__ */ jsx("input", {
1028
- ...props,
1029
- ref: setRef,
1030
- id: field.id,
1031
- required,
1032
- disabled,
1033
- readOnly,
1034
- placeholder: field.floating ? placeholder || " " : placeholder,
1035
- "aria-invalid": field.invalid ? true : ariaInvalid,
1036
- "aria-describedby": field.describedBy,
1037
- spellCheck: mono ? false : props.spellCheck,
1038
- autoCapitalize: mono ? "off" : props.autoCapitalize,
1039
- autoCorrect: mono ? "off" : props.autoCorrect,
1040
- className: cn("h-full min-w-0 flex-1 rounded-[inherit] bg-transparent text-fg outline-none", "placeholder:text-input-placeholder pointer-coarse:text-base", "disabled:cursor-not-allowed", "autofill:[transition:background-color_0s_600000s] autofill:[-webkit-text-fill-color:var(--color-fg)]", textSize[size], inputPadding[size], hasStart && (isText(start) ? "ps-1" : "ps-2"), !validating && hasEnd && isText(end) ? "pe-1" : (validating || hasEnd || actions) && "pe-2", mono && "font-mono", field.floating && ["placeholder:opacity-0", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:placeholder:opacity-100"], inputClassName)
1041
- })]
1042
- }),
1043
- validating ? /* @__PURE__ */ jsx("span", {
1044
- className: cn("flex shrink-0 items-center self-stretch text-fg-muted", iconSize[size], endSlotPadding[size]),
1045
- children: /* @__PURE__ */ jsx(LoaderCircle, {
1046
- "aria-hidden": true,
1047
- className: "animate-spin motion-reduce:[animation-duration:2.5s]"
1048
- })
1049
- }) : hasEnd ? /* @__PURE__ */ jsx(Adornment, {
1050
- node: end,
1051
- side: "end",
1052
- size
1053
- }) : null,
1054
- actions ? /* @__PURE__ */ jsx("span", {
1055
- className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
1056
- children: actions
1057
- }) : null
1058
- ] })
1340
+ function Card({ as: Comp = "div", className, ...props }) {
1341
+ const titleId = useId();
1342
+ const [titled, setTitled] = useState(false);
1343
+ return /* @__PURE__ */ jsx(CardContext, {
1344
+ value: {
1345
+ titleId,
1346
+ setTitled
1347
+ },
1348
+ children: /* @__PURE__ */ jsx(Comp, {
1349
+ "aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
1350
+ ...props,
1351
+ "data-slot": "card",
1352
+ className: cn("rounded-xl border border-border-strong bg-surface", className)
1353
+ })
1354
+ });
1355
+ }
1356
+ function CardHeader({ title, description, action, headingLevel = 2, className, ...props }) {
1357
+ const { titleId, setTitled } = useContext(CardContext);
1358
+ useLayoutEffect(() => {
1359
+ setTitled(true);
1360
+ return () => setTitled(false);
1361
+ }, [setTitled]);
1362
+ const Heading = headingLevel === 2 ? "h2" : "h3";
1363
+ return /* @__PURE__ */ jsxs("div", {
1364
+ ...props,
1365
+ "data-slot": "card-header",
1366
+ className: cn("flex items-start justify-between gap-4 px-5 pt-5", className),
1367
+ children: [/* @__PURE__ */ jsxs("div", {
1368
+ className: "min-w-0",
1369
+ children: [/* @__PURE__ */ jsx(Heading, {
1370
+ id: titleId,
1371
+ className: "font-sans text-sm font-medium",
1372
+ children: title
1373
+ }), description && /* @__PURE__ */ jsx("p", {
1374
+ className: "text-xs text-fg-muted",
1375
+ children: description
1376
+ })]
1377
+ }), action && /* @__PURE__ */ jsx("div", {
1378
+ className: "shrink-0",
1379
+ children: action
1380
+ })]
1381
+ });
1382
+ }
1383
+ function CardContent({ className, ...props }) {
1384
+ return /* @__PURE__ */ jsx("div", {
1385
+ ...props,
1386
+ "data-slot": "card-content",
1387
+ className: cn("p-5 [[data-slot=card-header]+&]:pt-4", className)
1059
1388
  });
1060
1389
  }
1061
1390
  //#endregion
@@ -1113,7 +1442,7 @@ function ChoiceRow({ slot, label, description, error, labelPosition = "end", gap
1113
1442
  return /* @__PURE__ */ jsx("label", {
1114
1443
  "data-slot": slot,
1115
1444
  className: cn("relative isolate flex min-h-6 min-w-6 items-start text-sm/6 text-fg", "cursor-pointer has-[input:disabled]:cursor-not-allowed has-[input[aria-busy=true]]:cursor-progress", "has-[input:disabled]:opacity-50", "forced-colors:has-[input:disabled]:text-[GrayText] forced-colors:has-[input:disabled]:opacity-100", "pointer-coarse:before:absolute pointer-coarse:before:top-1/2 pointer-coarse:before:left-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:size-full pointer-coarse:before:min-h-11", "pointer-coarse:before:min-w-11 pointer-coarse:before:-translate-1/2", gap, labelPosition === "start" ? "w-full justify-between" : "w-fit max-w-full", className),
1116
- children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment, { children: [controlSlot, text] })
1445
+ children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [controlSlot, text] })
1117
1446
  });
1118
1447
  }
1119
1448
  //#endregion
@@ -1236,6 +1565,515 @@ function CheckboxGroup({ legend, description, error, orientation = "vertical", c
1236
1565
  });
1237
1566
  }
1238
1567
  //#endregion
1568
+ //#region src/components/combobox/types.ts
1569
+ const isGroup$1 = (item) => "options" in item;
1570
+ const normalize = (text) => text.trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase();
1571
+ const defaultFilter = (option, query) => normalize([option.label, ...option.keywords ?? []].join(" ")).includes(normalize(query));
1572
+ //#endregion
1573
+ //#region src/components/combobox/use-combobox-position.ts
1574
+ const clamp$1 = (n, min, max) => Math.max(min, Math.min(n, Math.max(min, max)));
1575
+ /** Top-layer positioning only: unlike Menu, a combobox never transfers focus into the list. */
1576
+ function useComboboxPosition(open, inputRef, contentRef) {
1577
+ useLayoutEffect(() => {
1578
+ const content = contentRef.current;
1579
+ const anchor = inputRef.current?.closest("[data-slot=\"input-control\"]");
1580
+ if (!open || !content || !anchor) return;
1581
+ content.showPopover?.();
1582
+ const update = () => {
1583
+ const rect = anchor.getBoundingClientRect();
1584
+ const viewport = window.visualViewport;
1585
+ const left = viewport?.offsetLeft ?? 0;
1586
+ const top = viewport?.offsetTop ?? 0;
1587
+ const width = viewport?.width ?? document.documentElement.clientWidth;
1588
+ const height = viewport?.height ?? document.documentElement.clientHeight;
1589
+ content.style.width = `${Math.min(rect.width, Math.max(0, width - 16))}px`;
1590
+ const below = top + height - rect.bottom - 12;
1591
+ const above = rect.top - top - 12;
1592
+ const side = below < Math.min(content.scrollHeight, 280) && above > below ? "top" : "bottom";
1593
+ content.style.maxHeight = `${Math.max(0, Math.min(280, side === "top" ? above : below))}px`;
1594
+ const w = content.offsetWidth;
1595
+ const h = content.offsetHeight;
1596
+ const rtl = getComputedStyle(anchor).direction === "rtl";
1597
+ content.style.left = `${Math.round(clamp$1(rtl ? rect.right - w : rect.left, left + 8, left + width - w - 8))}px`;
1598
+ content.style.top = `${Math.round(clamp$1(side === "top" ? rect.top - h - 4 : rect.bottom + 4, top + 8, top + height - h - 8))}px`;
1599
+ content.dataset.side = side;
1600
+ };
1601
+ update();
1602
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(update);
1603
+ observer?.observe(anchor);
1604
+ observer?.observe(content);
1605
+ const mutation = new MutationObserver(update);
1606
+ mutation.observe(content, {
1607
+ childList: true,
1608
+ subtree: true,
1609
+ characterData: true
1610
+ });
1611
+ window.addEventListener("resize", update);
1612
+ window.addEventListener("scroll", update, true);
1613
+ window.visualViewport?.addEventListener("resize", update);
1614
+ window.visualViewport?.addEventListener("scroll", update);
1615
+ return () => {
1616
+ observer?.disconnect();
1617
+ mutation.disconnect();
1618
+ window.removeEventListener("resize", update);
1619
+ window.removeEventListener("scroll", update, true);
1620
+ window.visualViewport?.removeEventListener("resize", update);
1621
+ window.visualViewport?.removeEventListener("scroll", update);
1622
+ content.hidePopover?.();
1623
+ };
1624
+ }, [
1625
+ open,
1626
+ inputRef,
1627
+ contentRef
1628
+ ]);
1629
+ }
1630
+ //#endregion
1631
+ //#region src/components/combobox/combobox.tsx
1632
+ const resultsText = (count) => `${count} ${count === 1 ? "option" : "options"} available.`;
1633
+ function Combobox({ options, value, defaultValue = null, onValueChange, inputValue, onInputValueChange, open: openProp, defaultOpen = false, onOpenChange, filter = defaultFilter, selectedOption, loading = false, loadingText = "Loading…", searchError, onRetry, retryText = "Retry", emptyText = "No matching options.", clearable = true, openOnFocus = false, clearLabel = "Clear selection", openLabel = "Open options", closeLabel = "Close options", requiredText = "Choose an option from the list.", unresolvedText = "The selected option is unavailable.", getResultsText = resultsText, label, floating, helperText, error, size = "md", start, mono, inputClassName, contentClassName, className, name, form, disabled, readOnly, required, ref, id: idProp, onClick, onFocus, onBlur, onKeyDown, onCompositionStart, onCompositionEnd, onInvalid, ...props }) {
1634
+ const autoId = useId();
1635
+ const id = idProp ?? `${autoId}-input`;
1636
+ const listId = `${autoId}-list`;
1637
+ const rootRef = useRef(null);
1638
+ const inputRef = useRef(null);
1639
+ const contentRef = useRef(null);
1640
+ const hiddenRef = useRef(null);
1641
+ const composing = useRef(false);
1642
+ const skipFocusOpen = useRef(false);
1643
+ const [internalValue, setInternalValue] = useState(defaultValue);
1644
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
1645
+ const [draft, setDraft] = useState(null);
1646
+ const [dirty, setDirty] = useState(false);
1647
+ const [compositionText, setCompositionText] = useState(null);
1648
+ const [active, setActive] = useState(null);
1649
+ const [nativeInvalid, setNativeInvalid] = useState(false);
1650
+ const selectedValue = value === void 0 ? internalValue : value;
1651
+ const open = !disabled && !readOnly && (openProp ?? internalOpen);
1652
+ const flat = useMemo(() => {
1653
+ const items = options.flatMap((item) => isGroup$1(item) ? [...item.options] : [item]);
1654
+ const seen = /* @__PURE__ */ new Set();
1655
+ for (const item of items) {
1656
+ if (!item.value || seen.has(item.value)) throw new Error("Combobox option values must be unique, non-empty strings");
1657
+ seen.add(item.value);
1658
+ }
1659
+ return items;
1660
+ }, [options]);
1661
+ const resolved = flat.find((item) => item.value === selectedValue) ?? (selectedOption?.value === selectedValue ? selectedOption : null);
1662
+ const [cached, setCached] = useState(resolved);
1663
+ const selected = resolved ?? (cached?.value === selectedValue ? cached : null);
1664
+ const [initialOption, setInitialOption] = useState(resolved?.value === defaultValue ? resolved : null);
1665
+ if (resolved?.value === defaultValue && resolved !== initialOption) setInitialOption(resolved);
1666
+ const text = compositionText !== null ? draft ?? inputValue ?? selected?.label ?? "" : inputValue ?? draft ?? selected?.label ?? "";
1667
+ const query = dirty ? compositionText ?? text : "";
1668
+ const blocked = loading || Boolean(searchError);
1669
+ const [previousResults, setPreviousResults] = useState({
1670
+ options,
1671
+ blocked,
1672
+ filter
1673
+ });
1674
+ if (previousResults.options !== options || previousResults.blocked !== blocked || previousResults.filter !== filter) {
1675
+ setPreviousResults({
1676
+ options,
1677
+ blocked,
1678
+ filter
1679
+ });
1680
+ setActive(null);
1681
+ }
1682
+ const visible = flat.filter((item) => filter === false || !query || filter(item, query));
1683
+ const activeItem = !blocked && active?.options === options && active.query === query ? visible.find((item) => item.value === active.value && !item.disabled) : void 0;
1684
+ const optionId = (item) => `${autoId}-option-${encodeURIComponent(item.value)}`;
1685
+ const activeId = open && activeItem ? optionId(activeItem) : void 0;
1686
+ const invalidMessage = selectedValue !== null && !selected ? unresolvedText : required && selectedValue === null ? requiredText : "";
1687
+ const fieldError = error || (nativeInvalid && invalidMessage ? invalidMessage : void 0);
1688
+ const count = visible.filter((item) => !item.disabled).length;
1689
+ const status = open ? searchError ? typeof searchError === "string" ? searchError : "Unable to load options." : loading ? loadingText : getResultsText(count) : "";
1690
+ const [previousSelection, setPreviousSelection] = useState({
1691
+ value: selectedValue,
1692
+ label: selected?.label
1693
+ });
1694
+ if (resolved && resolved !== cached) setCached(resolved);
1695
+ if (previousSelection.value !== selectedValue || previousSelection.label !== selected?.label) {
1696
+ setPreviousSelection({
1697
+ value: selectedValue,
1698
+ label: selected?.label
1699
+ });
1700
+ setDraft(null);
1701
+ setDirty(false);
1702
+ setActive(null);
1703
+ }
1704
+ if (nativeInvalid && !invalidMessage) setNativeInvalid(false);
1705
+ useLayoutEffect(() => {
1706
+ inputRef.current?.setCustomValidity(disabled || readOnly ? "" : invalidMessage);
1707
+ }, [
1708
+ invalidMessage,
1709
+ disabled,
1710
+ readOnly
1711
+ ]);
1712
+ useComboboxPosition(open, inputRef, contentRef);
1713
+ useLayoutEffect(() => {
1714
+ if (activeId) document.getElementById(activeId)?.scrollIntoView?.({ block: "nearest" });
1715
+ }, [activeId]);
1716
+ const updateText = (next, reason, fallback = false) => {
1717
+ setDraft(fallback ? null : next);
1718
+ if (next !== text || reason === "input") onInputValueChange?.(next, { reason });
1719
+ };
1720
+ const changeOpen = (next, reason) => {
1721
+ if (openProp === void 0) setInternalOpen(next);
1722
+ if (next !== open) onOpenChange?.(next, { reason });
1723
+ };
1724
+ const close = (reason) => {
1725
+ changeOpen(false, reason);
1726
+ updateText(selected?.label ?? "", "revert", true);
1727
+ setDirty(false);
1728
+ setActive(null);
1729
+ };
1730
+ const show = (reason, direction) => {
1731
+ if (disabled || readOnly) return;
1732
+ changeOpen(true, reason);
1733
+ setDirty(false);
1734
+ const items = flat.filter((item) => !item.disabled);
1735
+ const initial = items.find((item) => item.value === selectedValue) ?? (direction === 1 ? items[0] : direction === -1 ? items.at(-1) : void 0);
1736
+ setActive(initial ? {
1737
+ value: initial.value,
1738
+ options,
1739
+ query: ""
1740
+ } : null);
1741
+ };
1742
+ const focusInput = () => {
1743
+ skipFocusOpen.current = true;
1744
+ inputRef.current?.focus({ preventScroll: true });
1745
+ skipFocusOpen.current = false;
1746
+ };
1747
+ const clear = (reason) => {
1748
+ if (value === void 0) setInternalValue(null);
1749
+ if (selectedValue !== null) onValueChange?.(null, {
1750
+ reason: "clear",
1751
+ option: null
1752
+ });
1753
+ updateText("", reason);
1754
+ setDirty(true);
1755
+ setActive(null);
1756
+ changeOpen(true, "clear");
1757
+ };
1758
+ const edit = (next) => {
1759
+ if (disabled || readOnly) return;
1760
+ if (next === "") clear("input");
1761
+ else {
1762
+ updateText(next, "input");
1763
+ setDirty(true);
1764
+ setActive(null);
1765
+ changeOpen(true, "input");
1766
+ }
1767
+ };
1768
+ const choose = (item) => {
1769
+ if (blocked || disabled || readOnly || item.disabled) return;
1770
+ setCached(item);
1771
+ if (value === void 0) setInternalValue(item.value);
1772
+ if (selectedValue !== item.value) onValueChange?.(item.value, {
1773
+ reason: "select",
1774
+ option: item
1775
+ });
1776
+ updateText(item.label, "selection", true);
1777
+ setDirty(false);
1778
+ setActive(null);
1779
+ changeOpen(false, "select");
1780
+ focusInput();
1781
+ };
1782
+ const dismiss = useEffectEvent(close);
1783
+ useLayoutEffect(() => {
1784
+ if (!open) return;
1785
+ const outside = (event) => {
1786
+ if (!event.composedPath().includes(rootRef.current)) dismiss("outside");
1787
+ };
1788
+ document.addEventListener("pointerdown", outside, true);
1789
+ return () => {
1790
+ document.removeEventListener("pointerdown", outside, true);
1791
+ };
1792
+ }, [open]);
1793
+ const wasOpen = useRef(open);
1794
+ const settle = useEffectEvent(() => {
1795
+ updateText(selected?.label ?? "", "revert", true);
1796
+ setDirty(false);
1797
+ setActive(null);
1798
+ });
1799
+ useLayoutEffect(() => {
1800
+ if (wasOpen.current && !open) settle();
1801
+ wasOpen.current = open;
1802
+ }, [open]);
1803
+ const reset = useEffectEvent(() => {
1804
+ const nextValue = value === void 0 ? defaultValue : value;
1805
+ const nextOption = flat.find((item) => item.value === nextValue) ?? (selectedOption?.value === nextValue ? selectedOption : null) ?? (cached?.value === nextValue ? cached : null) ?? (initialOption?.value === nextValue ? initialOption : null);
1806
+ if (nextOption) setCached(nextOption);
1807
+ const nextText = inputValue ?? nextOption?.label ?? "";
1808
+ if (value === void 0) setInternalValue(nextValue);
1809
+ if (openProp === void 0) setInternalOpen(false);
1810
+ updateText(nextText, "reset", true);
1811
+ setDirty(false);
1812
+ setCompositionText(null);
1813
+ composing.current = false;
1814
+ setActive(null);
1815
+ setNativeInvalid(false);
1816
+ if (inputRef.current) inputRef.current.value = nextText;
1817
+ if (hiddenRef.current) hiddenRef.current.value = nextValue ?? "";
1818
+ });
1819
+ useLayoutEffect(() => {
1820
+ const owner = inputRef.current?.form;
1821
+ if (!owner) return;
1822
+ let timer;
1823
+ const handleReset = (event) => {
1824
+ clearTimeout(timer);
1825
+ timer = setTimeout(() => {
1826
+ if (!event.defaultPrevented) reset();
1827
+ }, 0);
1828
+ };
1829
+ owner.addEventListener("reset", handleReset);
1830
+ return () => {
1831
+ clearTimeout(timer);
1832
+ owner.removeEventListener("reset", handleReset);
1833
+ };
1834
+ }, [form]);
1835
+ const renderOption = (item) => /* @__PURE__ */ jsxs("div", {
1836
+ id: optionId(item),
1837
+ role: "option",
1838
+ "aria-selected": item.value === selectedValue,
1839
+ "aria-disabled": item.disabled || void 0,
1840
+ "aria-labelledby": `${optionId(item)}-label`,
1841
+ "aria-describedby": item.description ? `${optionId(item)}-description` : void 0,
1842
+ "data-slot": "combobox-option",
1843
+ "data-active": activeItem?.value === item.value ? "" : void 0,
1844
+ className: cn("flex min-h-8.5 cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm/5 text-fg outline-none select-none pointer-coarse:min-h-11", "data-active:bg-popover-highlight data-active:outline-1 data-active:-outline-offset-1 data-active:outline-input-border", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 forced-colors:data-active:outline-[Highlight]"),
1845
+ onPointerMove: (event) => {
1846
+ if (event.pointerType !== "touch" && !item.disabled) setActive({
1847
+ value: item.value,
1848
+ options,
1849
+ query
1850
+ });
1851
+ },
1852
+ onMouseDown: (event) => event.preventDefault(),
1853
+ onClick: () => choose(item),
1854
+ children: [
1855
+ item.icon && /* @__PURE__ */ jsx("span", {
1856
+ "aria-hidden": true,
1857
+ className: "shrink-0 text-fg-muted [&_svg]:size-4",
1858
+ children: item.icon
1859
+ }),
1860
+ /* @__PURE__ */ jsxs("span", {
1861
+ className: "min-w-0 flex-1 wrap-anywhere",
1862
+ children: [/* @__PURE__ */ jsx("span", {
1863
+ id: `${optionId(item)}-label`,
1864
+ children: item.label
1865
+ }), item.description && /* @__PURE__ */ jsx("span", {
1866
+ id: `${optionId(item)}-description`,
1867
+ className: "mt-0.5 block text-xs/snug text-fg-muted",
1868
+ children: item.description
1869
+ })]
1870
+ }),
1871
+ /* @__PURE__ */ jsx(Check, {
1872
+ "aria-hidden": true,
1873
+ className: cn("size-4 shrink-0", item.value !== selectedValue && "invisible")
1874
+ })
1875
+ ]
1876
+ }, item.value);
1877
+ return /* @__PURE__ */ jsxs("div", {
1878
+ ref: rootRef,
1879
+ onBlur: (event) => {
1880
+ if ((open || dirty) && !rootRef.current?.contains(event.relatedTarget)) close("blur");
1881
+ },
1882
+ "data-slot": "combobox",
1883
+ "data-state": open ? "open" : "closed",
1884
+ className: cn("w-full", className),
1885
+ children: [
1886
+ /* @__PURE__ */ jsx(FieldFrame, {
1887
+ ...props,
1888
+ ref,
1889
+ inputRef,
1890
+ id,
1891
+ type: "text",
1892
+ form,
1893
+ label,
1894
+ floating,
1895
+ helperText,
1896
+ error: fieldError,
1897
+ required,
1898
+ disabled,
1899
+ readOnly,
1900
+ size,
1901
+ start,
1902
+ mono,
1903
+ value: text,
1904
+ role: "combobox",
1905
+ "aria-autocomplete": "list",
1906
+ "aria-expanded": open,
1907
+ "aria-controls": open ? listId : void 0,
1908
+ "aria-activedescendant": activeId,
1909
+ autoComplete: props.autoComplete ?? "off",
1910
+ status,
1911
+ inputClassName,
1912
+ className: "pointer-coarse:**:data-[slot=input-control]:min-h-11",
1913
+ onChange: (event) => {
1914
+ if (composing.current) setDraft(event.currentTarget.value);
1915
+ else edit(event.currentTarget.value);
1916
+ },
1917
+ onCompositionStart: (event) => {
1918
+ onCompositionStart?.(event);
1919
+ composing.current = true;
1920
+ setCompositionText(query);
1921
+ },
1922
+ onCompositionEnd: (event) => {
1923
+ onCompositionEnd?.(event);
1924
+ composing.current = false;
1925
+ setCompositionText(null);
1926
+ edit(event.currentTarget.value);
1927
+ },
1928
+ onClick: (event) => {
1929
+ onClick?.(event);
1930
+ if (!event.defaultPrevented && !open) show("trigger");
1931
+ },
1932
+ onFocus: (event) => {
1933
+ onFocus?.(event);
1934
+ if (!event.defaultPrevented && openOnFocus && !open && !skipFocusOpen.current) show("input");
1935
+ },
1936
+ onBlur,
1937
+ onInvalid: (event) => {
1938
+ setNativeInvalid(true);
1939
+ onInvalid?.(event);
1940
+ },
1941
+ onKeyDown: (event) => {
1942
+ onKeyDown?.(event);
1943
+ if (event.defaultPrevented || disabled || readOnly || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
1944
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1945
+ event.preventDefault();
1946
+ const direction = event.key === "ArrowDown" ? 1 : -1;
1947
+ if (!open) {
1948
+ show("keyboard", direction);
1949
+ return;
1950
+ }
1951
+ if (blocked) return;
1952
+ const enabled = visible.filter((item) => !item.disabled);
1953
+ const index = enabled.findIndex((item) => item.value === activeItem?.value);
1954
+ const next = index < 0 ? direction === 1 ? enabled[0] : enabled.at(-1) : enabled[index + direction];
1955
+ if (next) setActive({
1956
+ value: next.value,
1957
+ options,
1958
+ query
1959
+ });
1960
+ } else if (event.key === "Enter" && open) {
1961
+ event.preventDefault();
1962
+ if (activeItem) choose(activeItem);
1963
+ } else if (event.key === "Escape" && open) {
1964
+ event.preventDefault();
1965
+ event.stopPropagation();
1966
+ close("escape");
1967
+ } else if (event.key === "Tab" && !(searchError && onRetry)) close("blur");
1968
+ },
1969
+ actions: !readOnly && /* @__PURE__ */ jsxs(Fragment$1, { children: [clearable && (text !== "" || selectedValue !== null) && /* @__PURE__ */ jsx(Button, {
1970
+ type: "button",
1971
+ icon: /* @__PURE__ */ jsx(X, {}),
1972
+ "aria-label": clearLabel,
1973
+ intent: "ghost",
1974
+ size,
1975
+ disabled,
1976
+ className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
1977
+ onMouseDown: (event) => event.preventDefault(),
1978
+ onClick: () => {
1979
+ clear("clear");
1980
+ focusInput();
1981
+ }
1982
+ }), /* @__PURE__ */ jsx(Button, {
1983
+ type: "button",
1984
+ icon: /* @__PURE__ */ jsx(ChevronDown, {}),
1985
+ tabIndex: -1,
1986
+ "aria-label": open ? closeLabel : openLabel,
1987
+ "aria-expanded": open,
1988
+ "aria-controls": open ? listId : void 0,
1989
+ disabled,
1990
+ intent: "ghost",
1991
+ size,
1992
+ className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
1993
+ onMouseDown: (event) => event.preventDefault(),
1994
+ onClick: () => {
1995
+ focusInput();
1996
+ if (open) close("trigger");
1997
+ else show("trigger");
1998
+ }
1999
+ })] })
2000
+ }),
2001
+ name && /* @__PURE__ */ jsx("input", {
2002
+ ref: hiddenRef,
2003
+ type: "hidden",
2004
+ name,
2005
+ form,
2006
+ disabled,
2007
+ value: selectedValue ?? ""
2008
+ }),
2009
+ open && /* @__PURE__ */ jsxs("div", {
2010
+ ref: contentRef,
2011
+ popover: "manual",
2012
+ "data-slot": "combobox-content",
2013
+ className: cn("fixed inset-auto z-50 m-0 box-border max-h-70 overflow-y-auto rounded-xl border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none scrollbar-subtle", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", contentClassName),
2014
+ onKeyDown: (event) => {
2015
+ if (event.key === "Escape") {
2016
+ event.preventDefault();
2017
+ event.stopPropagation();
2018
+ close("escape");
2019
+ focusInput();
2020
+ }
2021
+ },
2022
+ children: [/* @__PURE__ */ jsx("div", {
2023
+ id: listId,
2024
+ role: "listbox",
2025
+ "aria-label": label ?? props["aria-label"],
2026
+ "aria-busy": loading || void 0,
2027
+ children: !blocked && options.map((item, index) => {
2028
+ if (!isGroup$1(item)) return visible.includes(item) ? renderOption(item) : null;
2029
+ const members = item.options.filter((option) => visible.includes(option));
2030
+ if (!members.length) return null;
2031
+ return /* @__PURE__ */ jsxs("div", {
2032
+ role: "group",
2033
+ "aria-labelledby": `${autoId}-group-${index}`,
2034
+ "data-slot": "combobox-group",
2035
+ className: "not-first:mt-1 not-first:border-t not-first:border-popover-border not-first:pt-1",
2036
+ children: [/* @__PURE__ */ jsx("div", {
2037
+ id: `${autoId}-group-${index}`,
2038
+ className: "px-2 pt-1.5 pb-1 text-xs font-medium text-fg-muted",
2039
+ children: item.label
2040
+ }), members.map(renderOption)]
2041
+ }, `${item.label}-${index}`);
2042
+ })
2043
+ }), searchError ? /* @__PURE__ */ jsxs("div", {
2044
+ className: "flex items-start gap-2 p-3 text-xs text-danger",
2045
+ children: [
2046
+ /* @__PURE__ */ jsx(CircleAlert, {
2047
+ "aria-hidden": true,
2048
+ className: "mt-px size-3.5 shrink-0"
2049
+ }),
2050
+ /* @__PURE__ */ jsx("span", {
2051
+ className: "flex-1",
2052
+ children: searchError
2053
+ }),
2054
+ onRetry && /* @__PURE__ */ jsx(Button, {
2055
+ type: "button",
2056
+ size: "sm",
2057
+ intent: "secondary",
2058
+ onClick: onRetry,
2059
+ children: retryText
2060
+ })
2061
+ ]
2062
+ }) : loading ? /* @__PURE__ */ jsxs("div", {
2063
+ className: "flex items-center justify-center gap-2 p-4 text-sm text-fg-muted",
2064
+ children: [/* @__PURE__ */ jsx(LoaderCircle, {
2065
+ "aria-hidden": true,
2066
+ className: "size-4 animate-spin motion-reduce:[animation-duration:2.5s]"
2067
+ }), loadingText]
2068
+ }) : visible.length === 0 ? /* @__PURE__ */ jsx("div", {
2069
+ className: "p-4 text-center text-sm text-fg-muted",
2070
+ children: emptyText
2071
+ }) : null]
2072
+ })
2073
+ ]
2074
+ });
2075
+ }
2076
+ //#endregion
1239
2077
  //#region src/components/modal/modal-shell.tsx
1240
2078
  const ModalContext = createContext(null);
1241
2079
  /** Parts of one wrapper must not work inside the other: a DialogHeader in a Drawer throws too. */
@@ -1410,7 +2248,7 @@ function ModalShell({ kind, open, onOpenChange, alert = false, dismissible = tru
1410
2248
  onMouseDown: handleMouseDown,
1411
2249
  onClick: handleClick,
1412
2250
  className,
1413
- children: rendered && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2251
+ children: rendered && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1414
2252
  hidden: true,
1415
2253
  ref: showOnAttach
1416
2254
  }, closeCount), children] })
@@ -1617,32 +2455,352 @@ function DrawerHeader({ title, description, className, ...props }) {
1617
2455
  })]
1618
2456
  });
1619
2457
  }
1620
- /** Fills the space between header and footer and scrolls on its own; they stay put. */
1621
- function DrawerBody({ className, ...props }) {
2458
+ /** Fills the space between header and footer and scrolls on its own; they stay put. */
2459
+ function DrawerBody({ className, ...props }) {
2460
+ return /* @__PURE__ */ jsx("div", {
2461
+ ...props,
2462
+ "data-slot": "drawer-body",
2463
+ className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-1 text-sm text-fg", className)
2464
+ });
2465
+ }
2466
+ /** Actions, right-aligned: Cancel first, the primary action last (rightmost). */
2467
+ function DrawerFooter({ className, ...props }) {
2468
+ return /* @__PURE__ */ jsx("div", {
2469
+ ...props,
2470
+ "data-slot": "drawer-footer",
2471
+ className: cn("flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pt-5 pb-6", className)
2472
+ });
2473
+ }
2474
+ /** A ghost icon button that closes the drawer, for custom headers. */
2475
+ function DrawerClose({ size = "sm", icon, ...props }) {
2476
+ const { requestClose } = useDrawerContext("DrawerClose");
2477
+ return /* @__PURE__ */ jsx(Button, {
2478
+ ...props,
2479
+ intent: "ghost",
2480
+ size,
2481
+ icon: icon ?? /* @__PURE__ */ jsx(X, {}),
2482
+ tooltip: false,
2483
+ onClick: () => requestClose("close-button")
2484
+ });
2485
+ }
2486
+ //#endregion
2487
+ //#region src/components/dropdown-menu/dropdown-menu.tsx
2488
+ const MenuContext = createContext(null);
2489
+ function useMenu() {
2490
+ const value = use(MenuContext);
2491
+ if (!value) throw new Error("DropdownMenu parts must be inside DropdownMenu");
2492
+ return value;
2493
+ }
2494
+ function DropdownMenu({ children, open: controlled, defaultOpen = false, onOpenChange }) {
2495
+ const [internal, setInternal] = useState(defaultOpen);
2496
+ const id = useId();
2497
+ const trigger = useRef(null);
2498
+ const content = useRef(null);
2499
+ const initialFocus = useRef("first");
2500
+ const reason = useRef("trigger");
2501
+ return /* @__PURE__ */ jsx(MenuContext, {
2502
+ value: {
2503
+ open: controlled ?? internal,
2504
+ triggerId: `${id}-trigger`,
2505
+ contentId: `${id}-menu`,
2506
+ trigger,
2507
+ content,
2508
+ initialFocus,
2509
+ reason,
2510
+ change(next, why, focus = "first") {
2511
+ if (next) initialFocus.current = focus;
2512
+ reason.current = why;
2513
+ if (controlled === void 0) setInternal(next);
2514
+ onOpenChange?.(next, why);
2515
+ }
2516
+ },
2517
+ children
2518
+ });
2519
+ }
2520
+ function DropdownMenuTrigger({ asChild = false, ref, onClick, onClickCapture, onKeyDown, disabled, ...props }) {
2521
+ const menu = useMenu();
2522
+ const mergedRef = useMergedRef(menu.trigger, ref);
2523
+ return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
2524
+ ...props,
2525
+ ref: mergedRef,
2526
+ id: menu.triggerId,
2527
+ type: "button",
2528
+ "data-slot": "dropdown-menu-trigger",
2529
+ "data-state": menu.open ? "open" : "closed",
2530
+ disabled: asChild ? disabled : void 0,
2531
+ "aria-haspopup": "menu",
2532
+ "aria-expanded": menu.open,
2533
+ "aria-controls": menu.open ? menu.contentId : void 0,
2534
+ "aria-disabled": disabled || props["aria-disabled"] || void 0,
2535
+ onClickCapture: (event) => {
2536
+ if (disabled) {
2537
+ event.preventDefault();
2538
+ event.stopPropagation();
2539
+ return;
2540
+ }
2541
+ onClickCapture?.(event);
2542
+ },
2543
+ onClick: (event) => {
2544
+ onClick?.(event);
2545
+ if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
2546
+ menu.change(!menu.open, "trigger");
2547
+ },
2548
+ onKeyDown: (event) => {
2549
+ onKeyDown?.(event);
2550
+ if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
2551
+ if (["ArrowDown", "ArrowUp"].includes(event.key)) {
2552
+ event.preventDefault();
2553
+ menu.change(true, "trigger", event.key === "ArrowUp" ? "last" : "first");
2554
+ }
2555
+ }
2556
+ });
2557
+ }
2558
+ const opposite = {
2559
+ top: "bottom",
2560
+ bottom: "top",
2561
+ left: "right",
2562
+ right: "left"
2563
+ };
2564
+ const clamp = (value, low, high) => Math.max(low, Math.min(value, Math.max(low, high)));
2565
+ function items(content) {
2566
+ return Array.from(content.querySelectorAll("[role=\"menuitem\"]")).filter((item) => !item.hidden && item.getAttribute("aria-hidden") !== "true");
2567
+ }
2568
+ function DropdownMenuContent(props) {
2569
+ const { open } = useMenu();
2570
+ return open ? /* @__PURE__ */ jsx(OpenContent, { ...props }) : null;
2571
+ }
2572
+ function OpenContent({ side = "bottom", align = "end", sideOffset = 6, className, ref, onKeyDown, style, ...props }) {
2573
+ const menu = useMenu();
2574
+ const { content: contentRef, trigger: triggerRef, initialFocus, reason: closeReason } = menu;
2575
+ const mergedRef = useMergedRef(contentRef, ref);
2576
+ const query = useRef({
2577
+ text: "",
2578
+ time: 0
2579
+ });
2580
+ const dismiss = useEffectEvent((reason) => menu.change(false, reason));
2581
+ useLayoutEffect(() => {
2582
+ const content = contentRef.current;
2583
+ const trigger = triggerRef.current;
2584
+ if (!content || !trigger) return;
2585
+ content.showPopover?.();
2586
+ const entries = items(content);
2587
+ ((initialFocus.current === "last" ? entries.at(-1) : entries[0]) ?? content).focus({ preventScroll: true });
2588
+ const outside = (event) => {
2589
+ const path = event.composedPath();
2590
+ if (!path.includes(content) && !path.includes(trigger)) dismiss("outside");
2591
+ };
2592
+ const focusOutside = (event) => {
2593
+ if (closeReason.current === "tab") return;
2594
+ if (!content.contains(event.target) && !trigger.contains(event.target)) dismiss("outside");
2595
+ };
2596
+ const reasonRef = closeReason;
2597
+ document.addEventListener("pointerdown", outside, true);
2598
+ document.addEventListener("focusin", focusOutside);
2599
+ return () => {
2600
+ document.removeEventListener("pointerdown", outside, true);
2601
+ document.removeEventListener("focusin", focusOutside);
2602
+ const restore = content.contains(document.activeElement) && !["outside", "tab"].includes(reasonRef.current);
2603
+ content.hidePopover?.();
2604
+ if (restore && trigger.isConnected) trigger.focus({ preventScroll: true });
2605
+ };
2606
+ }, [
2607
+ contentRef,
2608
+ triggerRef,
2609
+ initialFocus,
2610
+ closeReason
2611
+ ]);
2612
+ useLayoutEffect(() => {
2613
+ const content = contentRef.current;
2614
+ const trigger = triggerRef.current;
2615
+ if (!content || !trigger) return;
2616
+ const update = () => {
2617
+ const rect = trigger.getBoundingClientRect();
2618
+ const viewport = window.visualViewport;
2619
+ const left = viewport?.offsetLeft ?? 0;
2620
+ const top = viewport?.offsetTop ?? 0;
2621
+ const width = viewport?.width ?? document.documentElement.clientWidth;
2622
+ const height = viewport?.height ?? document.documentElement.clientHeight;
2623
+ const gap = Math.max(0, sideOffset);
2624
+ content.style.maxWidth = `${Math.max(0, width - 16)}px`;
2625
+ content.style.maxHeight = `${Math.max(0, height - 16)}px`;
2626
+ const room = {
2627
+ top: rect.top - top,
2628
+ bottom: top + height - rect.bottom,
2629
+ left: rect.left - left,
2630
+ right: left + width - rect.right
2631
+ };
2632
+ const vertical = side === "top" || side === "bottom";
2633
+ const needed = (vertical ? content.scrollHeight : content.offsetWidth) + gap + 8;
2634
+ const actual = room[side] < needed && room[opposite[side]] > room[side] ? opposite[side] : side;
2635
+ if (vertical) content.style.maxHeight = `${Math.max(0, room[actual] - gap - 8)}px`;
2636
+ else content.style.maxWidth = `${Math.max(0, room[actual] - gap - 8)}px`;
2637
+ const w = content.offsetWidth;
2638
+ const h = content.offsetHeight;
2639
+ const rtl = getComputedStyle(trigger).direction === "rtl";
2640
+ const fraction = {
2641
+ start: 0,
2642
+ center: .5,
2643
+ end: 1
2644
+ }[align];
2645
+ const horizontalFraction = rtl ? 1 - fraction : fraction;
2646
+ const x = vertical ? rect.left + (rect.width - w) * horizontalFraction : actual === "left" ? rect.left - w - gap : rect.right + gap;
2647
+ const y = vertical ? actual === "top" ? rect.top - h - gap : rect.bottom + gap : rect.top + (rect.height - h) * fraction;
2648
+ content.style.left = `${Math.round(clamp(x, left + 8, left + width - w - 8))}px`;
2649
+ content.style.top = `${Math.round(clamp(y, top + 8, top + height - h - 8))}px`;
2650
+ content.dataset.side = actual;
2651
+ };
2652
+ update();
2653
+ const observer = new ResizeObserver(update);
2654
+ observer.observe(trigger);
2655
+ observer.observe(content);
2656
+ window.addEventListener("resize", update);
2657
+ window.addEventListener("scroll", update, true);
2658
+ window.visualViewport?.addEventListener("resize", update);
2659
+ window.visualViewport?.addEventListener("scroll", update);
2660
+ return () => {
2661
+ observer.disconnect();
2662
+ window.removeEventListener("resize", update);
2663
+ window.removeEventListener("scroll", update, true);
2664
+ window.visualViewport?.removeEventListener("resize", update);
2665
+ window.visualViewport?.removeEventListener("scroll", update);
2666
+ };
2667
+ }, [
2668
+ side,
2669
+ align,
2670
+ sideOffset,
2671
+ contentRef,
2672
+ triggerRef
2673
+ ]);
2674
+ return /* @__PURE__ */ jsx("div", {
2675
+ ...props,
2676
+ ref: mergedRef,
2677
+ id: menu.contentId,
2678
+ role: "menu",
2679
+ tabIndex: -1,
2680
+ "aria-labelledby": props["aria-label"] ? void 0 : menu.triggerId,
2681
+ popover: "manual",
2682
+ "data-slot": "dropdown-menu-content",
2683
+ "data-state": "open",
2684
+ style,
2685
+ className: cn("fixed inset-auto z-50 m-0 box-border w-max min-w-50 overflow-y-auto rounded-lg border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", className),
2686
+ onKeyDown: (event) => {
2687
+ onKeyDown?.(event);
2688
+ if (event.defaultPrevented) return;
2689
+ const entries = items(event.currentTarget);
2690
+ const index = entries.indexOf(document.activeElement);
2691
+ if (event.key === "Escape") {
2692
+ event.preventDefault();
2693
+ event.stopPropagation();
2694
+ menu.change(false, "escape");
2695
+ } else if (event.key === "Tab") {
2696
+ event.preventDefault();
2697
+ const trigger = menu.trigger.current;
2698
+ if (!trigger) return;
2699
+ const scope = trigger.closest("dialog[open]") ?? document.body;
2700
+ const tabbables = Array.from(scope.querySelectorAll("a[href], button, input, select, textarea, [tabindex]")).filter((el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[inert]") && !event.currentTarget.contains(el) && el.getClientRects().length > 0);
2701
+ const target = tabbables[tabbables.indexOf(trigger) + (event.shiftKey ? -1 : 1)];
2702
+ menu.change(false, "tab");
2703
+ (target ?? trigger).focus();
2704
+ } else if ([
2705
+ "ArrowDown",
2706
+ "ArrowUp",
2707
+ "Home",
2708
+ "End"
2709
+ ].includes(event.key)) {
2710
+ event.preventDefault();
2711
+ entries[event.key === "Home" ? 0 : event.key === "End" ? entries.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + entries.length) % entries.length]?.focus();
2712
+ } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
2713
+ event.preventDefault();
2714
+ const now = Date.now();
2715
+ query.current.text = (now - query.current.time > 500 ? "" : query.current.text) + event.key.toLocaleLowerCase();
2716
+ query.current.time = now;
2717
+ const text = query.current.text;
2718
+ const term = Array.from(text).every((char) => char === text[0]) ? text[0] : text;
2719
+ [...entries.slice(index + 1), ...entries.slice(0, index + 1)].find((item) => (item.dataset.textValue ?? item.textContent ?? "").trim().toLocaleLowerCase().startsWith(term))?.focus();
2720
+ }
2721
+ }
2722
+ });
2723
+ }
2724
+ function DropdownMenuItem({ asChild = false, iconLeft, tone = "default", textValue, disabled, onSelect, onClick, onClickCapture, onAuxClickCapture, onKeyDown, onPointerMove, children, className, ...props }) {
2725
+ const menu = useMenu();
2726
+ const Component = asChild ? Slot : "button";
2727
+ const renderContent = (label) => /* @__PURE__ */ jsxs(Fragment$1, { children: [iconLeft && /* @__PURE__ */ jsx("span", {
2728
+ "aria-hidden": true,
2729
+ children: iconLeft
2730
+ }), label] });
2731
+ const child = asChild ? Children.only(children) : null;
2732
+ const content = isValidElement(child) ? cloneElement(child, { children: wrapChildren(child.props.children, renderContent) }) : renderContent(children);
2733
+ return /* @__PURE__ */ jsx(Component, {
2734
+ ...props,
2735
+ type: asChild ? void 0 : "button",
2736
+ role: "menuitem",
2737
+ tabIndex: -1,
2738
+ "aria-disabled": disabled || void 0,
2739
+ "data-slot": "dropdown-menu-item",
2740
+ "data-tone": tone,
2741
+ "data-text-value": textValue,
2742
+ className: cn("flex min-h-8 w-full cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm/snug outline-none select-none pointer-coarse:min-h-10", "focus:bg-popover-highlight focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", tone === "danger" ? "text-danger-subtle-fg focus:bg-danger-subtle-bg" : "text-fg [&_svg]:text-fg-muted", className),
2743
+ onClickCapture: (event) => {
2744
+ if (disabled) {
2745
+ event.preventDefault();
2746
+ event.stopPropagation();
2747
+ return;
2748
+ }
2749
+ onClickCapture?.(event);
2750
+ },
2751
+ onAuxClickCapture: (event) => {
2752
+ if (disabled) {
2753
+ event.preventDefault();
2754
+ event.stopPropagation();
2755
+ return;
2756
+ }
2757
+ onAuxClickCapture?.(event);
2758
+ },
2759
+ onPointerMove: (event) => {
2760
+ onPointerMove?.(event);
2761
+ if (!event.defaultPrevented && event.pointerType !== "touch") event.currentTarget.focus({ preventScroll: true });
2762
+ },
2763
+ onKeyDown: (event) => {
2764
+ onKeyDown?.(event);
2765
+ if (event.defaultPrevented) return;
2766
+ if (event.key === " " || event.key === "Enter") {
2767
+ event.preventDefault();
2768
+ event.currentTarget.click();
2769
+ }
2770
+ },
2771
+ onClick: (event) => {
2772
+ if (disabled) {
2773
+ event.preventDefault();
2774
+ return;
2775
+ }
2776
+ onClick?.(event);
2777
+ const selection = new Event("select", { cancelable: true });
2778
+ onSelect?.(selection);
2779
+ if (!selection.defaultPrevented) menu.change(false, "item-select");
2780
+ },
2781
+ children: content
2782
+ });
2783
+ }
2784
+ function DropdownMenuGroup({ className, ...props }) {
1622
2785
  return /* @__PURE__ */ jsx("div", {
1623
2786
  ...props,
1624
- "data-slot": "drawer-body",
1625
- className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-1 text-sm text-fg", className)
2787
+ role: "group",
2788
+ "data-slot": "dropdown-menu-group",
2789
+ className
1626
2790
  });
1627
2791
  }
1628
- /** Actions, right-aligned: Cancel first, the primary action last (rightmost). */
1629
- function DrawerFooter({ className, ...props }) {
2792
+ function DropdownMenuLabel({ className, ...props }) {
1630
2793
  return /* @__PURE__ */ jsx("div", {
1631
2794
  ...props,
1632
- "data-slot": "drawer-footer",
1633
- className: cn("flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pt-5 pb-6", className)
2795
+ "data-slot": "dropdown-menu-label",
2796
+ className: cn("px-2 py-1.5 text-xs text-fg-muted", className)
1634
2797
  });
1635
2798
  }
1636
- /** A ghost icon button that closes the drawer, for custom headers. */
1637
- function DrawerClose({ size = "sm", icon, ...props }) {
1638
- const { requestClose } = useDrawerContext("DrawerClose");
1639
- return /* @__PURE__ */ jsx(Button, {
2799
+ function DropdownMenuSeparator({ className, ...props }) {
2800
+ return /* @__PURE__ */ jsx("hr", {
1640
2801
  ...props,
1641
- intent: "ghost",
1642
- size,
1643
- icon: icon ?? /* @__PURE__ */ jsx(X, {}),
1644
- tooltip: false,
1645
- onClick: () => requestClose("close-button")
2802
+ "data-slot": "dropdown-menu-separator",
2803
+ className: cn("my-1 h-px border-0 bg-popover-border", className)
1646
2804
  });
1647
2805
  }
1648
2806
  //#endregion
@@ -1830,6 +2988,197 @@ function Meter({ label, summary, segments, className, ...props }) {
1830
2988
  });
1831
2989
  }
1832
2990
  //#endregion
2991
+ //#region src/components/radio-group/radio-group.tsx
2992
+ const RadioContext = createContext(null);
2993
+ function RadioGroup({ name, legend, description, error, value, defaultValue, onValueChange, variant = "default", orientation = "vertical", itemsClassName, children, className, disabled, required, form, ref, "aria-invalid": ariaInvalid, "aria-describedby": describedByProp, ...props }) {
2994
+ const id = useId();
2995
+ const descriptionId = `${id}-description`;
2996
+ const errorId = `${id}-error`;
2997
+ const legendId = `${id}-legend`;
2998
+ const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
2999
+ const hasDescription = isPresent(description);
3000
+ const hasError = Boolean(error) && error !== true && isPresent(error);
3001
+ const describedBy = [
3002
+ describedByProp,
3003
+ hasDescription && descriptionId,
3004
+ hasError && errorId
3005
+ ].filter(Boolean).join(" ") || void 0;
3006
+ const controlled = value !== void 0;
3007
+ const [internalValue, setInternalValue] = useState(defaultValue ?? null);
3008
+ const selected = controlled ? value : internalValue;
3009
+ const groupRef = useRef(null);
3010
+ const mergedRef = useMergedRef(groupRef, ref);
3011
+ const inputs = useRef(/* @__PURE__ */ new Set());
3012
+ const currentSelection = useRef(selected);
3013
+ useLayoutEffect(() => {
3014
+ currentSelection.current = selected;
3015
+ }, [selected]);
3016
+ const register = useCallback((node) => {
3017
+ const members = inputs.current;
3018
+ if ([...members].some((input) => input.value === node.value)) throw new Error(`Radio values must be unique within a RadioGroup: "${node.value}"`);
3019
+ members.add(node);
3020
+ const reconcile = () => {
3021
+ queueMicrotask(() => {
3022
+ if (controlled || !groupRef.current) return;
3023
+ setInternalValue((current) => [...members].some((input) => input.value === current) ? current : null);
3024
+ });
3025
+ };
3026
+ reconcile();
3027
+ return () => {
3028
+ members.delete(node);
3029
+ reconcile();
3030
+ };
3031
+ }, [controlled]);
3032
+ useLayoutEffect(() => {
3033
+ const owner = groupRef.current?.form;
3034
+ if (!owner) return;
3035
+ let timer;
3036
+ const reset = (event) => {
3037
+ clearTimeout(timer);
3038
+ timer = setTimeout(() => {
3039
+ if (event.defaultPrevented) return;
3040
+ const next = controlled ? currentSelection.current : defaultValue ?? null;
3041
+ const members = [...inputs.current];
3042
+ if (!controlled) setInternalValue(members.some((input) => input.value === next) ? next : null);
3043
+ for (const input of members) input.checked = input.value === next;
3044
+ }, 0);
3045
+ };
3046
+ owner.addEventListener("reset", reset);
3047
+ return () => {
3048
+ clearTimeout(timer);
3049
+ owner.removeEventListener("reset", reset);
3050
+ };
3051
+ }, [
3052
+ controlled,
3053
+ defaultValue,
3054
+ form
3055
+ ]);
3056
+ return /* @__PURE__ */ jsx(RadioContext, {
3057
+ value: {
3058
+ name,
3059
+ form,
3060
+ selected,
3061
+ disabled,
3062
+ required,
3063
+ invalid,
3064
+ describedBy,
3065
+ variant,
3066
+ orientation,
3067
+ register,
3068
+ select: (event) => {
3069
+ if (!event.currentTarget.checked) return;
3070
+ const next = event.currentTarget.value;
3071
+ if (!controlled) setInternalValue(next);
3072
+ onValueChange?.(next, event);
3073
+ }
3074
+ },
3075
+ children: /* @__PURE__ */ jsxs("fieldset", {
3076
+ ...props,
3077
+ ref: mergedRef,
3078
+ name,
3079
+ form,
3080
+ disabled,
3081
+ "data-slot": "radio-group",
3082
+ "data-variant": variant,
3083
+ role: "radiogroup",
3084
+ "aria-labelledby": props["aria-labelledby"] ?? legendId,
3085
+ "aria-describedby": describedBy,
3086
+ "aria-invalid": invalid ? true : ariaInvalid,
3087
+ "aria-required": required || void 0,
3088
+ "aria-orientation": orientation,
3089
+ className: cn("group/radio-group m-0 min-w-0 border-0 p-0", className),
3090
+ children: [/* @__PURE__ */ jsxs("legend", {
3091
+ id: legendId,
3092
+ className: "float-start mb-1.5 w-full p-0 text-sm font-medium text-fg group-disabled/radio-group:opacity-50",
3093
+ children: [legend, required && /* @__PURE__ */ jsx("span", {
3094
+ "aria-hidden": true,
3095
+ className: "text-danger",
3096
+ children: " *"
3097
+ })]
3098
+ }), /* @__PURE__ */ jsxs("div", {
3099
+ className: "clear-both flex flex-col",
3100
+ children: [
3101
+ hasDescription && /* @__PURE__ */ jsx("p", {
3102
+ id: descriptionId,
3103
+ className: "m-0 -mt-1 mb-2.5 text-xs/snug text-fg-muted group-disabled/radio-group:opacity-50",
3104
+ children: description
3105
+ }),
3106
+ /* @__PURE__ */ jsx("div", {
3107
+ "data-slot": "radio-group-items",
3108
+ "data-orientation": orientation,
3109
+ className: cn("flex gap-2", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap gap-x-6", variant === "card" && orientation === "horizontal" && "gap-3", itemsClassName),
3110
+ children
3111
+ }),
3112
+ hasError && /* @__PURE__ */ jsxs("p", {
3113
+ id: errorId,
3114
+ className: "m-0 mt-2 flex items-start gap-1 text-xs/snug text-danger",
3115
+ children: [/* @__PURE__ */ jsx(CircleAlert, {
3116
+ "aria-hidden": true,
3117
+ className: "mt-px size-3.5 shrink-0"
3118
+ }), /* @__PURE__ */ jsx("span", { children: error })]
3119
+ })
3120
+ ]
3121
+ })]
3122
+ })
3123
+ });
3124
+ }
3125
+ function Radio({ value, label, description, disabled, className, inputClassName, ref, onChange, "aria-describedby": describedBy, ...props }) {
3126
+ const group = useContext(RadioContext);
3127
+ if (!group) throw new Error("Radio must be rendered inside <RadioGroup>");
3128
+ if (value === "") throw new Error("Radio value must be a non-empty string");
3129
+ const ownRef = useRef(null);
3130
+ const mergedRef = useMergedRef(ownRef, ref);
3131
+ const register = group.register;
3132
+ useLayoutEffect(() => {
3133
+ if (ownRef.current) return register(ownRef.current);
3134
+ }, [register, value]);
3135
+ const card = group.variant === "card";
3136
+ const invalid = group.invalid;
3137
+ return /* @__PURE__ */ jsx(ChoiceRow, {
3138
+ slot: "radio",
3139
+ label,
3140
+ description,
3141
+ gap: card ? "gap-3" : "gap-2",
3142
+ controlOffset: "mt-1",
3143
+ "aria-describedby": [describedBy, group.describedBy].filter(Boolean).join(" ") || void 0,
3144
+ className: cn("group/radio wrap-anywhere motion-reduce:**:transition-none", "pointer-coarse:min-h-11 pointer-coarse:py-2.5", card && [
3145
+ "w-full rounded-lg border bg-surface p-3 pointer-coarse:p-3",
3146
+ "transition-[border-color,background-color] duration-150 ease-out",
3147
+ "[&>span:last-child>span:first-child]:font-medium",
3148
+ "has-[input:checked]:bg-[color-mix(in_oklab,var(--color-control-checked)_4%,var(--color-surface))]",
3149
+ "has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2",
3150
+ invalid ? "border-danger has-[input:focus-visible]:outline-danger" : "border-input-border has-[input:checked]:border-control-checked has-[input:enabled]:hover:border-input-border-hover has-[input:focus-visible]:outline-control-checked",
3151
+ "forced-colors:has-[input:checked]:border-[Highlight] forced-colors:has-[input:focus-visible]:outline-[Highlight]",
3152
+ group.orientation === "horizontal" && "flex-[1_1_14rem]"
3153
+ ], className),
3154
+ control: (row) => /* @__PURE__ */ jsxs("span", {
3155
+ className: "relative inline-grid size-4 place-items-center transition-[scale] duration-100 ease-out has-[input:enabled:active]:scale-[0.92]",
3156
+ children: [/* @__PURE__ */ jsx("input", {
3157
+ ...props,
3158
+ ref: mergedRef,
3159
+ "data-slot": "radio-input",
3160
+ type: "radio",
3161
+ name: group.name,
3162
+ form: group.form,
3163
+ value,
3164
+ checked: group.selected === value,
3165
+ disabled: group.disabled || disabled,
3166
+ required: group.required,
3167
+ "aria-labelledby": props["aria-labelledby"] ?? row.labelledBy,
3168
+ "aria-describedby": row.describedBy,
3169
+ onChange: (event) => {
3170
+ onChange?.(event);
3171
+ group.select(event);
3172
+ },
3173
+ className: cn("peer col-start-1 row-start-1 m-0 size-4 cursor-pointer appearance-none rounded-full border outline-hidden disabled:cursor-not-allowed", "bg-input-bg checked:bg-control-checked", "transition-[border-color,background-color,box-shadow] duration-150 ease-out", invalid ? "border-danger [box-shadow:0_0_0_3px_var(--color-input-halo-invalid)]" : "border-input-border checked:border-control-checked group-hover/radio:enabled:not-checked:border-input-border-hover", !card && [invalid ? "focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo-invalid-focus)] not-forced-colors:focus-visible:outline-danger" : "focus-visible:border-input-focus focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo)] not-forced-colors:focus-visible:outline-control-checked", "checked:focus-visible:shadow-none checked:focus-visible:outline-solid checked:focus-visible:outline-2 checked:focus-visible:outline-offset-2"], card && "focus-visible:outline-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:disabled:checked:bg-[GrayText]", !card && "forced-colors:focus-visible:outline-[Highlight]", inputClassName)
3174
+ }), /* @__PURE__ */ jsx("span", {
3175
+ "aria-hidden": true,
3176
+ className: "pointer-events-none col-start-1 row-start-1 size-1.5 scale-0 rounded-full bg-control-checked-fg transition-[scale] duration-150 ease-out peer-checked:scale-100 forced-colors:forced-color-adjust-none forced-colors:bg-[HighlightText] forced-colors:peer-disabled:bg-[Canvas]"
3177
+ })]
3178
+ })
3179
+ });
3180
+ }
3181
+ //#endregion
1833
3182
  //#region src/components/select/select.tsx
1834
3183
  const selectPadding = {
1835
3184
  sm: "px-3",
@@ -1914,7 +3263,7 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
1914
3263
  className,
1915
3264
  controlRef: selectRef,
1916
3265
  activate: openPicker,
1917
- children: (field) => /* @__PURE__ */ jsxs(Fragment, { children: [
3266
+ children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
1918
3267
  field.floatingLabel(labelRestingWidth[size]),
1919
3268
  /* @__PURE__ */ jsxs("select", {
1920
3269
  ...props,
@@ -1943,12 +3292,12 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
1943
3292
  }),
1944
3293
  hasLeading && /* @__PURE__ */ jsx("span", {
1945
3294
  "aria-hidden": true,
1946
- className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize[size], startSlotPadding[size]),
3295
+ className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize$1[size], startSlotPadding[size]),
1947
3296
  children: leading
1948
3297
  }),
1949
3298
  /* @__PURE__ */ jsx("span", {
1950
3299
  "aria-hidden": true,
1951
- className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize[size], endSlotPadding[size]),
3300
+ className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize$1[size], endSlotPadding[size]),
1952
3301
  children: /* @__PURE__ */ jsx(ChevronDown, { className: "transition-transform duration-150 ease-out group-has-[select:open]/control:rotate-180 motion-reduce:transition-none" })
1953
3302
  })
1954
3303
  ] })
@@ -2063,7 +3412,7 @@ function TabContent({ icon, badge, children }) {
2063
3412
  children
2064
3413
  })]
2065
3414
  }),
2066
- badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("span", {
3415
+ badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx("span", {
2067
3416
  "data-slot": "tab-badge",
2068
3417
  className: "inline-flex",
2069
3418
  children: badge
@@ -2251,7 +3600,7 @@ function SidebarProvider({ children }) {
2251
3600
  function Sidebar({ className, children, ...props }) {
2252
3601
  const { open, setOpen, drawerId } = useSidebarContext("Sidebar");
2253
3602
  const label = props["aria-label"] ? { "aria-label": props["aria-label"] } : { "aria-labelledby": props["aria-labelledby"] };
2254
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelContext, {
3603
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PanelContext, {
2255
3604
  value: { drawer: false },
2256
3605
  children: /* @__PURE__ */ jsx("nav", {
2257
3606
  ...props,
@@ -2471,13 +3820,586 @@ function SidebarIcon({ className, ...props }) {
2471
3820
  }
2472
3821
  /** Count at the end of a SidebarItem, as a small neutral Badge; part of the name ("Projects 8"). */
2473
3822
  function SidebarBadge({ className, ...props }) {
2474
- return /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
3823
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx(Badge, {
2475
3824
  size: "sm",
2476
3825
  ...props,
2477
3826
  className: cn("shrink-0", className)
2478
3827
  })] });
2479
3828
  }
2480
3829
  //#endregion
3830
+ //#region src/components/slider/scale.ts
3831
+ const fail = (message) => {
3832
+ throw new Error(`Slider: ${message}`);
3833
+ };
3834
+ const close = (value, integer) => Math.abs(value - integer) <= 1e-7;
3835
+ const decimals = (value) => {
3836
+ const [digits, exponent = "0"] = String(value).toLowerCase().split("e");
3837
+ return Math.max(0, (digits.split(".")[1]?.length ?? 0) - Number(exponent));
3838
+ };
3839
+ /** Work in integer ticks, not repeated floating point additions. */
3840
+ function createScale(min, max, step, gap) {
3841
+ if (![
3842
+ min,
3843
+ max,
3844
+ step,
3845
+ gap
3846
+ ].every(Number.isFinite)) fail("bounds, step and gap must be finite");
3847
+ if (min >= max || step <= 0) fail("min must be less than max and step must be positive");
3848
+ if (!Number.isInteger(gap) || gap < 0) fail("minStepsBetweenThumbs must be a non-negative integer");
3849
+ const factor = 10 ** Math.max(decimals(min), decimals(max), decimals(step));
3850
+ const start = Math.round(min * factor), end = Math.round(max * factor), increment = Math.round(step * factor);
3851
+ if (![
3852
+ start,
3853
+ end,
3854
+ increment,
3855
+ end - start
3856
+ ].every(Number.isSafeInteger) || increment < 1) fail("bounds and step exceed safe numeric precision");
3857
+ const count = (end - start) / increment;
3858
+ if (!Number.isSafeInteger(count)) fail("(max - min) must be evenly divisible by step");
3859
+ if (gap > count) fail("minimum thumb distance exceeds the available range");
3860
+ const fromTick = (tick) => (start + tick * increment) / factor;
3861
+ const toTick = (value) => (value * factor - start) / increment;
3862
+ const validate = (values, length) => {
3863
+ if (values.length !== length) fail(`expected ${length} value${length === 1 ? "" : "s"}`);
3864
+ const ticks = values.map((value) => {
3865
+ if (!Number.isFinite(value)) fail("values must be finite");
3866
+ const tick = toTick(value), rounded = Math.round(tick);
3867
+ if (!close(tick, rounded) || rounded < 0 || rounded > count) fail("values must be on a step within min and max");
3868
+ return rounded;
3869
+ });
3870
+ if (ticks.length === 2 && ticks[1] - ticks[0] < gap) fail("range values must be ordered and satisfy the minimum distance");
3871
+ return ticks.map(fromTick);
3872
+ };
3873
+ const normalize = (values) => {
3874
+ const ticks = values.map((value) => Math.max(0, Math.min(count, Math.round(toTick(value)))));
3875
+ if (ticks.length === 2) {
3876
+ ticks[0] = Math.min(ticks[0], count - gap);
3877
+ ticks[1] = Math.max(ticks[1], ticks[0] + gap);
3878
+ }
3879
+ return ticks.map(fromTick);
3880
+ };
3881
+ const limits = (values, index) => values.length === 1 ? [0, count] : index === 0 ? [0, Math.round(toTick(values[1])) - gap] : [Math.round(toTick(values[0])) + gap, count];
3882
+ const move = (values, index, tick) => {
3883
+ const [low, high] = limits(values, index);
3884
+ return values.map((value, i) => i === index ? fromTick(Math.max(low, Math.min(high, Math.round(tick)))) : value);
3885
+ };
3886
+ return {
3887
+ min,
3888
+ max,
3889
+ step,
3890
+ gap,
3891
+ count,
3892
+ fromTick,
3893
+ toTick,
3894
+ validate,
3895
+ normalize,
3896
+ limits,
3897
+ move,
3898
+ key: `${min}/${max}/${step}/${gap}`
3899
+ };
3900
+ }
3901
+ const sameValues = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
3902
+ //#endregion
3903
+ //#region src/components/slider/use-slider.ts
3904
+ const keys = [
3905
+ "ArrowRight",
3906
+ "ArrowLeft",
3907
+ "ArrowUp",
3908
+ "ArrowDown",
3909
+ "Home",
3910
+ "End",
3911
+ "PageUp",
3912
+ "PageDown"
3913
+ ];
3914
+ function useSlider(options) {
3915
+ const { scale, value, defaultValue, disabled, form } = options;
3916
+ const controlled = value !== void 0;
3917
+ const [internal, setInternal] = useState(() => [...defaultValue]);
3918
+ const values = controlled ? value : scale.normalize(internal);
3919
+ if (!controlled && !sameValues(internal, values)) setInternal([...values]);
3920
+ const [dragging, setDragging] = useState(false);
3921
+ const [activeIndex, setActiveIndex] = useState(0);
3922
+ const inputs = useRef([]);
3923
+ const controlRef = useRef(null);
3924
+ const railRef = useRef(null);
3925
+ const session = useRef(null);
3926
+ const lastIndex = useRef(0);
3927
+ const mounted = useRef(false);
3928
+ const generation = useRef(0);
3929
+ const latest = useRef({
3930
+ ...options,
3931
+ values,
3932
+ controlled
3933
+ });
3934
+ function release() {
3935
+ const current = session.current;
3936
+ session.current = null;
3937
+ if (!current) return null;
3938
+ current.removeListeners?.();
3939
+ if (current.pointerId !== void 0 && current.control?.hasPointerCapture?.(current.pointerId)) current.control.releasePointerCapture(current.pointerId);
3940
+ if (mounted.current) setDragging(false);
3941
+ return current;
3942
+ }
3943
+ function canUse(index) {
3944
+ return !latest.current.disabled && !!inputs.current[index] && !inputs.current[index].matches(":disabled");
3945
+ }
3946
+ function publish(next, details) {
3947
+ const state = latest.current;
3948
+ if (sameValues(state.values, next)) return;
3949
+ if (session.current) session.current.expected = next;
3950
+ if (!state.controlled) {
3951
+ latest.current = {
3952
+ ...state,
3953
+ values: next
3954
+ };
3955
+ setInternal(next);
3956
+ }
3957
+ state.onValueChange?.(next, details);
3958
+ }
3959
+ function commit(start, expected, details, key, defer = false) {
3960
+ const token = generation.current;
3961
+ const notify = () => {
3962
+ const state = latest.current;
3963
+ if (!mounted.current || token !== generation.current || state.scale.key !== key || !canUse(details.thumbIndex)) return;
3964
+ if (!sameValues(state.values, expected) || sameValues(start, state.values)) return;
3965
+ state.onValueCommit?.([...state.values], details);
3966
+ };
3967
+ if (defer) queueMicrotask(notify);
3968
+ else notify();
3969
+ }
3970
+ function finish(canceled = false) {
3971
+ const current = release();
3972
+ if (!current) return;
3973
+ if (canceled) {
3974
+ generation.current++;
3975
+ publish(current.start, {
3976
+ thumbIndex: current.index,
3977
+ reason: "cancel"
3978
+ });
3979
+ } else commit(current.start, current.expected, {
3980
+ thumbIndex: current.index,
3981
+ reason: current.reason
3982
+ }, current.scaleKey);
3983
+ }
3984
+ function begin(index, reason) {
3985
+ const state = latest.current;
3986
+ const current = {
3987
+ index,
3988
+ reason,
3989
+ start: [...state.values],
3990
+ expected: [...state.values],
3991
+ scaleKey: state.scale.key,
3992
+ keys: /* @__PURE__ */ new Set(),
3993
+ offset: 0,
3994
+ startX: 0,
3995
+ pending: false
3996
+ };
3997
+ session.current = current;
3998
+ return current;
3999
+ }
4000
+ function move(index, tick, reason) {
4001
+ if (!canUse(index)) {
4002
+ release();
4003
+ return;
4004
+ }
4005
+ const state = latest.current;
4006
+ publish(state.scale.move(state.values, index, tick), {
4007
+ thumbIndex: index,
4008
+ reason
4009
+ });
4010
+ }
4011
+ function direction() {
4012
+ const node = controlRef.current;
4013
+ return node ? getComputedStyle(node).direction || node.closest("[dir]")?.getAttribute("dir") || "ltr" : "ltr";
4014
+ }
4015
+ function tickAt(clientX) {
4016
+ const rect = railRef.current?.getBoundingClientRect();
4017
+ if (!rect || rect.width <= 0) return null;
4018
+ let ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
4019
+ if (direction() === "rtl") ratio = 1 - ratio;
4020
+ return ratio * latest.current.scale.count;
4021
+ }
4022
+ function focus(index) {
4023
+ lastIndex.current = index;
4024
+ setActiveIndex(index);
4025
+ inputs.current[index]?.focus({ preventScroll: true });
4026
+ }
4027
+ function onPointerDown(event) {
4028
+ if (event.defaultPrevented || event.button !== 0 || event.isPrimary === false || session.current?.reason === "pointer") return;
4029
+ if (!canUse(0)) return;
4030
+ const tick = tickAt(event.clientX);
4031
+ if (tick === null) return;
4032
+ if (session.current) finish();
4033
+ const state = latest.current;
4034
+ const distances = state.values.map((v) => Math.abs(state.scale.toTick(v) - tick));
4035
+ const index = distances.length === 1 ? 0 : distances[0] === distances[1] ? lastIndex.current : distances[0] < distances[1] ? 0 : 1;
4036
+ event.preventDefault();
4037
+ focus(index);
4038
+ const current = begin(index, "pointer");
4039
+ current.pointerId = event.pointerId;
4040
+ current.control = event.currentTarget;
4041
+ current.startX = event.clientX;
4042
+ const rect = railRef.current.getBoundingClientRect();
4043
+ const ratio = state.scale.toTick(state.values[index]) / state.scale.count;
4044
+ const center = rect.left + (direction() === "rtl" ? 1 - ratio : ratio) * rect.width;
4045
+ const grabbed = Math.abs(event.clientX - center) <= 22;
4046
+ current.offset = grabbed ? event.clientX - center : 0;
4047
+ current.pending = state.values.length === 2 && state.values[0] === state.values[1] && grabbed;
4048
+ setDragging(true);
4049
+ const owner = event.currentTarget.ownerDocument.defaultView;
4050
+ const pointerMove = (next) => {
4051
+ if (session.current !== current || next.pointerId !== current.pointerId) return;
4052
+ const target = tickAt(next.clientX - current.offset);
4053
+ if (target === null) return;
4054
+ if (current.pending) {
4055
+ if (Math.abs(next.clientX - current.startX) < 2) return;
4056
+ current.index = target < latest.current.scale.toTick(current.start[0]) ? 0 : 1;
4057
+ current.pending = false;
4058
+ focus(current.index);
4059
+ }
4060
+ move(current.index, target, "pointer");
4061
+ };
4062
+ const pointerEnd = (next) => {
4063
+ if (session.current === current && next.pointerId === current.pointerId) finish(next.type !== "pointerup");
4064
+ };
4065
+ owner.addEventListener("pointermove", pointerMove);
4066
+ owner.addEventListener("pointerup", pointerEnd);
4067
+ owner.addEventListener("pointercancel", pointerEnd);
4068
+ current.control.addEventListener("lostpointercapture", pointerEnd);
4069
+ current.removeListeners = () => {
4070
+ owner.removeEventListener("pointermove", pointerMove);
4071
+ owner.removeEventListener("pointerup", pointerEnd);
4072
+ owner.removeEventListener("pointercancel", pointerEnd);
4073
+ current.control?.removeEventListener("lostpointercapture", pointerEnd);
4074
+ };
4075
+ event.currentTarget.setPointerCapture?.(event.pointerId);
4076
+ if (!grabbed) move(index, tick, "pointer");
4077
+ }
4078
+ function onKeyDown(event, index) {
4079
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || !keys.includes(event.key) || !canUse(index)) return;
4080
+ event.preventDefault();
4081
+ if (session.current?.reason === "pointer") return;
4082
+ (session.current ?? begin(index, "keyboard")).keys.add(event.key);
4083
+ const state = latest.current;
4084
+ const right = direction() === "rtl" ? -1 : 1;
4085
+ const deltas = {
4086
+ ArrowRight: right,
4087
+ ArrowLeft: -right,
4088
+ ArrowUp: 1,
4089
+ ArrowDown: -1,
4090
+ PageUp: 10,
4091
+ PageDown: -10
4092
+ };
4093
+ const [low, high] = state.scale.limits(state.values, index);
4094
+ move(index, event.key === "Home" ? low : event.key === "End" ? high : state.scale.toTick(state.values[index]) + deltas[event.key], "keyboard");
4095
+ }
4096
+ function onKeyUp(event) {
4097
+ const current = session.current;
4098
+ if (current?.reason !== "keyboard" || !current.keys.has(event.key)) return;
4099
+ current.keys.delete(event.key);
4100
+ if (!current.keys.size) finish();
4101
+ }
4102
+ function onInput(value, index) {
4103
+ if (session.current || !canUse(index) || !Number.isFinite(value)) return;
4104
+ const state = latest.current, start = [...state.values];
4105
+ const next = state.scale.move(start, index, state.scale.toTick(value));
4106
+ publish(next, {
4107
+ thumbIndex: index,
4108
+ reason: "input"
4109
+ });
4110
+ commit(start, next, {
4111
+ thumbIndex: index,
4112
+ reason: "input"
4113
+ }, state.scale.key, true);
4114
+ }
4115
+ useLayoutEffect(() => {
4116
+ latest.current = {
4117
+ ...options,
4118
+ values,
4119
+ controlled
4120
+ };
4121
+ const current = session.current;
4122
+ if (current && (current.scaleKey !== scale.key || disabled || !sameValues(values, current.expected))) {
4123
+ generation.current++;
4124
+ release();
4125
+ }
4126
+ });
4127
+ const cancelRef = useRef(() => {});
4128
+ useLayoutEffect(() => {
4129
+ cancelRef.current = () => finish(true);
4130
+ });
4131
+ useLayoutEffect(() => {
4132
+ mounted.current = true;
4133
+ const owner = controlRef.current?.ownerDocument.defaultView;
4134
+ const blur = () => cancelRef.current();
4135
+ owner?.addEventListener("blur", blur);
4136
+ return () => {
4137
+ mounted.current = false;
4138
+ generation.current++;
4139
+ release();
4140
+ owner?.removeEventListener("blur", blur);
4141
+ };
4142
+ }, []);
4143
+ useLayoutEffect(() => {
4144
+ const owner = inputs.current[0]?.form;
4145
+ if (!owner) return;
4146
+ let timer;
4147
+ const reset = (event) => {
4148
+ clearTimeout(timer);
4149
+ timer = setTimeout(() => {
4150
+ if (event.defaultPrevented) return;
4151
+ generation.current++;
4152
+ release();
4153
+ const state = latest.current;
4154
+ const next = state.controlled ? state.values : state.defaultValue;
4155
+ if (!state.controlled) {
4156
+ latest.current = {
4157
+ ...state,
4158
+ values: next
4159
+ };
4160
+ setInternal([...next]);
4161
+ }
4162
+ inputs.current.forEach((input, index) => {
4163
+ if (!input) return;
4164
+ const [low, high] = state.scale.limits(next, index);
4165
+ input.min = String(state.scale.fromTick(low));
4166
+ input.max = String(state.scale.fromTick(high));
4167
+ input.value = String(next[index]);
4168
+ });
4169
+ }, 0);
4170
+ };
4171
+ owner.addEventListener("reset", reset);
4172
+ return () => {
4173
+ clearTimeout(timer);
4174
+ owner.removeEventListener("reset", reset);
4175
+ };
4176
+ }, [form]);
4177
+ return {
4178
+ values,
4179
+ dragging,
4180
+ activeIndex,
4181
+ inputs,
4182
+ controlRef,
4183
+ railRef,
4184
+ onPointerDown,
4185
+ onKeyDown,
4186
+ onKeyUp,
4187
+ onInput,
4188
+ onFocus: (index) => {
4189
+ lastIndex.current = index;
4190
+ setActiveIndex(index);
4191
+ },
4192
+ onBlur: () => {
4193
+ if (session.current?.reason === "keyboard") finish(!controlRef.current?.ownerDocument.hasFocus());
4194
+ }
4195
+ };
4196
+ }
4197
+ //#endregion
4198
+ //#region src/components/slider/slider.tsx
4199
+ function SliderControl({ values, defaults, names, thumbLabels, range = false, minStepsBetweenThumbs = 0, min = 0, max = 100, step = 1, disabled = false, form, label, helperText, error, showValue = true, formatValue = String, getValueText, marks, change, commit, className, ref, onPointerDown, onKeyDown, onKeyUp, onBlur, onFocus, "aria-label": ariaLabel, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
4200
+ const id = useId();
4201
+ const scale = createScale(min, max, step, minStepsBetweenThumbs);
4202
+ const length = range ? 2 : 1;
4203
+ const initial = scale.validate(defaults ?? (range ? [min, max] : [min]), length);
4204
+ const selected = values === void 0 ? void 0 : scale.validate(values, length);
4205
+ const hasLabel = isPresent(label);
4206
+ if (!hasLabel && !ariaLabel?.trim()) throw new Error("Slider requires a label or aria-label");
4207
+ if (range && (!thumbLabels || thumbLabels.length !== 2 || thumbLabels.some((text) => !text.trim()))) throw new Error("RangeSlider requires two non-empty thumbLabels");
4208
+ if (names && (names.length !== length || names.some((name) => !name.trim()))) throw new Error("Slider requires one non-empty name per thumb");
4209
+ if (marks) {
4210
+ const seen = /* @__PURE__ */ new Set();
4211
+ for (const mark of marks) {
4212
+ const normalized = scale.validate([mark.value], 1)[0];
4213
+ if (seen.has(normalized)) throw new Error("Slider mark values must be unique");
4214
+ seen.add(normalized);
4215
+ }
4216
+ }
4217
+ const { inputs, controlRef, railRef, ...slider } = useSlider({
4218
+ scale,
4219
+ value: selected,
4220
+ defaultValue: initial,
4221
+ disabled,
4222
+ form,
4223
+ onValueChange: change,
4224
+ onValueCommit: commit
4225
+ });
4226
+ const mergedRef = useMergedRef(useRef(null), ref);
4227
+ const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
4228
+ const message = error && error !== true ? error : helperText;
4229
+ const description = [describedBy, isPresent(message) && `${id}-description`].filter(Boolean).join(" ") || void 0;
4230
+ const percentages = slider.values.map((value) => scale.toTick(value) / scale.count * 100);
4231
+ const start = range ? percentages[0] : 0;
4232
+ const end = percentages.at(-1);
4233
+ return /* @__PURE__ */ jsxs("div", {
4234
+ ...props,
4235
+ ref: mergedRef,
4236
+ role: "group",
4237
+ "aria-label": hasLabel ? void 0 : ariaLabel,
4238
+ "aria-labelledby": hasLabel ? `${id}-label` : void 0,
4239
+ "data-slot": "slider",
4240
+ "data-range": range || void 0,
4241
+ "data-disabled": disabled || void 0,
4242
+ "data-invalid": invalid || void 0,
4243
+ "data-dragging": slider.dragging || void 0,
4244
+ className: cn("min-w-0 has-[input:disabled]:opacity-50 forced-colors:has-[input:disabled]:opacity-100", className),
4245
+ onPointerDown: (event) => {
4246
+ onPointerDown?.(event);
4247
+ if (!event.defaultPrevented && controlRef.current?.contains(event.target)) slider.onPointerDown(event);
4248
+ },
4249
+ onKeyDown: (event) => {
4250
+ onKeyDown?.(event);
4251
+ const index = inputs.current.indexOf(event.target);
4252
+ if (!event.defaultPrevented && index !== -1) slider.onKeyDown(event, index);
4253
+ },
4254
+ onKeyUp: (event) => {
4255
+ onKeyUp?.(event);
4256
+ if (inputs.current.includes(event.target)) slider.onKeyUp(event);
4257
+ },
4258
+ onBlur: (event) => {
4259
+ onBlur?.(event);
4260
+ if (inputs.current.includes(event.target)) slider.onBlur();
4261
+ },
4262
+ onFocus: (event) => {
4263
+ onFocus?.(event);
4264
+ const index = inputs.current.indexOf(event.target);
4265
+ if (index !== -1) slider.onFocus(index);
4266
+ },
4267
+ children: [
4268
+ (hasLabel || showValue) && /* @__PURE__ */ jsxs("div", {
4269
+ className: "mb-0.5 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",
4270
+ children: [hasLabel && /* @__PURE__ */ jsx("span", {
4271
+ id: `${id}-label`,
4272
+ className: "min-w-0 text-sm font-medium wrap-anywhere text-fg",
4273
+ children: label
4274
+ }), showValue && /* @__PURE__ */ jsx("span", {
4275
+ "data-slot": "slider-value",
4276
+ "aria-hidden": true,
4277
+ className: "ms-auto text-sm font-medium wrap-anywhere text-fg tabular-nums",
4278
+ children: /* @__PURE__ */ jsx("bdi", {
4279
+ dir: "ltr",
4280
+ children: slider.values.map(formatValue).join(" – ")
4281
+ })
4282
+ })]
4283
+ }),
4284
+ /* @__PURE__ */ jsx("div", {
4285
+ ref: controlRef,
4286
+ "data-slot": "slider-control",
4287
+ className: cn("group/slider-control relative h-11 touch-pan-y touch-pinch-zoom select-none", disabled ? "cursor-not-allowed" : "cursor-pointer"),
4288
+ children: /* @__PURE__ */ jsxs("div", {
4289
+ ref: railRef,
4290
+ className: "pointer-events-none absolute inset-x-5.5 top-0 h-full",
4291
+ children: [
4292
+ /* @__PURE__ */ jsx("div", {
4293
+ "aria-hidden": true,
4294
+ "data-slot": "slider-track",
4295
+ className: "absolute inset-x-0 top-5 h-1 rounded-full border border-input-border bg-surface-muted forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]"
4296
+ }),
4297
+ /* @__PURE__ */ jsx("div", {
4298
+ "aria-hidden": true,
4299
+ "data-slot": "slider-range",
4300
+ className: cn("absolute top-5 h-1 rounded-full forced-colors:forced-color-adjust-none forced-colors:bg-[Highlight]", invalid ? "bg-danger" : "bg-control-checked", disabled && "forced-colors:bg-[GrayText]"),
4301
+ style: {
4302
+ insetInlineStart: `${start}%`,
4303
+ width: `${end - start}%`
4304
+ }
4305
+ }),
4306
+ slider.values.map((value, i) => {
4307
+ const index = i;
4308
+ const [low, high] = scale.limits(slider.values, index);
4309
+ return /* @__PURE__ */ jsxs(Fragment, { children: [range && /* @__PURE__ */ jsx("span", {
4310
+ id: `${id}-thumb-label-${index}`,
4311
+ className: "sr-only",
4312
+ children: thumbLabels[index]
4313
+ }), /* @__PURE__ */ jsxs("span", {
4314
+ className: "absolute top-0 size-11 -translate-x-1/2 rtl:translate-x-1/2",
4315
+ style: {
4316
+ insetInlineStart: `${percentages[index]}%`,
4317
+ zIndex: slider.activeIndex === index ? 2 : 1
4318
+ },
4319
+ children: [/* @__PURE__ */ jsx("input", {
4320
+ ref: (node) => {
4321
+ inputs.current[index] = node;
4322
+ },
4323
+ type: "range",
4324
+ "data-slot": "slider-input",
4325
+ name: names?.[index],
4326
+ form,
4327
+ min: scale.fromTick(low),
4328
+ max: scale.fromTick(high),
4329
+ step,
4330
+ value,
4331
+ disabled,
4332
+ "aria-label": hasLabel ? void 0 : [ariaLabel, thumbLabels?.[index]].filter(Boolean).join(" "),
4333
+ "aria-labelledby": hasLabel ? [`${id}-label`, range && `${id}-thumb-label-${index}`].filter(Boolean).join(" ") : void 0,
4334
+ "aria-describedby": description,
4335
+ "aria-invalid": invalid ? true : ariaInvalid,
4336
+ "aria-valuetext": getValueText?.(value, index) ?? formatValue(value),
4337
+ className: "peer pointer-events-none absolute inset-0 m-0 size-full opacity-0",
4338
+ onChange: (event) => slider.onInput(event.currentTarget.valueAsNumber, index)
4339
+ }), /* @__PURE__ */ jsx("span", {
4340
+ "aria-hidden": true,
4341
+ "data-slot": "slider-thumb",
4342
+ "data-active": slider.dragging && slider.activeIndex === index || void 0,
4343
+ className: cn("pointer-events-none absolute inset-s-3.25 top-3.25 size-4.5 rounded-full border bg-surface-raised", "peer-focus-visible:outline-solid peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-focus", "transition-[border-color,box-shadow] duration-100 ease-out motion-reduce:transition-none", "data-active:[box-shadow:0_0_0_4px_var(--color-input-halo)]", invalid ? "border-danger" : "border-control-checked peer-enabled:group-hover/slider-control:border-input-border-hover", "forced-colors:forced-color-adjust-none forced-colors:border-[ButtonText] forced-colors:bg-[ButtonFace] forced-colors:shadow-none", "forced-colors:peer-focus-visible:outline-[Highlight] forced-colors:peer-disabled:border-[GrayText]")
4344
+ })]
4345
+ })] }, index);
4346
+ })
4347
+ ]
4348
+ })
4349
+ }),
4350
+ marks?.length ? /* @__PURE__ */ jsx("div", {
4351
+ "aria-hidden": true,
4352
+ "data-slot": "slider-marks",
4353
+ className: "relative mx-5.5 min-h-6 text-xs text-fg-muted tabular-nums",
4354
+ children: marks.map((mark) => {
4355
+ const fraction = scale.toTick(mark.value) / scale.count;
4356
+ return /* @__PURE__ */ jsxs("span", {
4357
+ className: "absolute top-0 w-0",
4358
+ style: { insetInlineStart: `${fraction * 100}%` },
4359
+ children: [/* @__PURE__ */ jsx("span", { className: "block h-1 w-px bg-input-border forced-colors:bg-[CanvasText]" }), /* @__PURE__ */ jsx("span", {
4360
+ className: cn("mt-0.5 inline-block whitespace-nowrap", fraction === 1 ? "-translate-x-full rtl:translate-x-full" : fraction !== 0 && "-translate-x-1/2 rtl:translate-x-1/2"),
4361
+ children: mark.label
4362
+ })]
4363
+ }, mark.value);
4364
+ })
4365
+ }) : /* @__PURE__ */ jsxs("div", {
4366
+ "aria-hidden": true,
4367
+ className: "mx-5.5 flex justify-between gap-2 text-xs text-fg-muted tabular-nums",
4368
+ children: [/* @__PURE__ */ jsx("span", { children: formatValue(min) }), /* @__PURE__ */ jsx("span", { children: formatValue(max) })]
4369
+ }),
4370
+ isPresent(message) && /* @__PURE__ */ jsxs("p", {
4371
+ id: `${id}-description`,
4372
+ className: cn("mt-2 mb-0 flex items-start gap-1 text-xs/relaxed", invalid ? "text-danger" : "text-fg-muted"),
4373
+ children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
4374
+ "aria-hidden": true,
4375
+ className: "mt-0.5 size-3.5 shrink-0"
4376
+ }), /* @__PURE__ */ jsx("span", { children: message })]
4377
+ })
4378
+ ]
4379
+ });
4380
+ }
4381
+ function Slider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
4382
+ return /* @__PURE__ */ jsx(SliderControl, {
4383
+ ...props,
4384
+ values: value === void 0 ? void 0 : [value],
4385
+ defaults: defaultValue === void 0 ? void 0 : [defaultValue],
4386
+ names: name === void 0 ? void 0 : [name],
4387
+ change: (next, details) => onValueChange?.(next[0], details),
4388
+ commit: (next, details) => onValueCommit?.(next[0], details)
4389
+ });
4390
+ }
4391
+ function RangeSlider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
4392
+ return /* @__PURE__ */ jsx(SliderControl, {
4393
+ ...props,
4394
+ range: true,
4395
+ values: value,
4396
+ defaults: defaultValue,
4397
+ names: name,
4398
+ change: (next, details) => onValueChange?.([next[0], next[1]], details),
4399
+ commit: (next, details) => onValueCommit?.([next[0], next[1]], details)
4400
+ });
4401
+ }
4402
+ //#endregion
2481
4403
  //#region src/components/switch/switch.tsx
2482
4404
  const sizeClasses = {
2483
4405
  sm: {
@@ -2531,7 +4453,7 @@ function Switch({ label, description, error, size = "md", loading = false, label
2531
4453
  }), /* @__PURE__ */ jsx("span", {
2532
4454
  "aria-hidden": true,
2533
4455
  className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-switch-handle peer-checked:bg-switch-handle-checked text-control-checked shadow-switch-thumb", "[--switch-dir:1] rtl:[--switch-dir:-1] [translate:calc(var(--switch-x)*var(--switch-dir))_0]", "[--switch-x:0px] [--switch-thumb-w:var(--switch-thumb)] peer-checked:[--switch-x:var(--switch-travel)]", "transition-[translate,width] duration-150 ease-in-out", pressable && ["peer-active:[--switch-thumb-w:calc(var(--switch-thumb)+var(--switch-stretch))]", "peer-checked:peer-active:[--switch-x:calc(var(--switch-travel)-var(--switch-stretch))]"], "peer-disabled:shadow-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none", "forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]", "forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-checked:text-[Highlight]", "forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-disabled:peer-checked:bg-[Canvas]", s.icon),
2534
- children: loading && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
4456
+ children: loading && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
2535
4457
  })]
2536
4458
  })
2537
4459
  });
@@ -2635,7 +4557,7 @@ function Table({ data, columns, getRowId, title, emptyState, noResults, filtered
2635
4557
  tabIndex: -1,
2636
4558
  className: "grid min-h-48 place-items-center px-4 py-8 text-center text-sm text-fg-muted outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
2637
4559
  children: emptyState === void 0 ? text.emptyState : emptyState
2638
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("section", {
4560
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("section", {
2639
4561
  ref: scrollRef,
2640
4562
  "data-slot": "table-scroll",
2641
4563
  "aria-labelledby": tableName,
@@ -2970,6 +4892,155 @@ function TabPanel({ value, keepMounted = false, className, ...props }) {
2970
4892
  });
2971
4893
  }
2972
4894
  //#endregion
4895
+ //#region src/components/textarea/use-auto-resize.ts
4896
+ /** Measure the native control so wrapping, fonts and IME text keep their browser behavior. */
4897
+ function useAutoResize(ref, enabled, minRows, maxRows) {
4898
+ const resize = useCallback(() => {
4899
+ const node = ref.current;
4900
+ if (!enabled || !node || node.getClientRects().length === 0) return;
4901
+ const style = getComputedStyle(node);
4902
+ const lineHeight = Number.parseFloat(style.lineHeight);
4903
+ if (!Number.isFinite(lineHeight)) return;
4904
+ const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
4905
+ const border = Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth);
4906
+ const min = minRows * lineHeight + padding;
4907
+ const max = maxRows == null ? Infinity : maxRows * lineHeight + padding;
4908
+ const scrollTop = node.scrollTop;
4909
+ node.style.overflowY = "hidden";
4910
+ node.style.height = "0px";
4911
+ const contentHeight = node.scrollHeight;
4912
+ const height = Math.max(min, Math.min(max, contentHeight));
4913
+ node.style.height = `${height + (style.boxSizing === "border-box" ? border : -padding)}px`;
4914
+ node.style.overflowY = contentHeight > max ? "auto" : "hidden";
4915
+ node.scrollTop = scrollTop;
4916
+ }, [
4917
+ enabled,
4918
+ ref,
4919
+ minRows,
4920
+ maxRows
4921
+ ]);
4922
+ useLayoutEffect(resize);
4923
+ useLayoutEffect(() => {
4924
+ if (!enabled) return;
4925
+ const node = ref.current;
4926
+ if (!node) return;
4927
+ let width = node.getBoundingClientRect().width;
4928
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(() => {
4929
+ const nextWidth = node.getBoundingClientRect().width;
4930
+ if (nextWidth !== width) {
4931
+ width = nextWidth;
4932
+ resize();
4933
+ }
4934
+ });
4935
+ observer?.observe(node);
4936
+ window.addEventListener("resize", resize);
4937
+ document.fonts?.addEventListener("loadingdone", resize);
4938
+ let active = true;
4939
+ document.fonts?.ready.then(() => {
4940
+ if (active) resize();
4941
+ });
4942
+ return () => {
4943
+ active = false;
4944
+ observer?.disconnect();
4945
+ window.removeEventListener("resize", resize);
4946
+ document.fonts?.removeEventListener("loadingdone", resize);
4947
+ node.style.removeProperty("height");
4948
+ node.style.removeProperty("overflow-y");
4949
+ };
4950
+ }, [
4951
+ enabled,
4952
+ ref,
4953
+ resize
4954
+ ]);
4955
+ return resize;
4956
+ }
4957
+ //#endregion
4958
+ //#region src/components/textarea/textarea.tsx
4959
+ const sizes = {
4960
+ sm: "px-3 py-2 text-sm/5",
4961
+ md: "px-3 py-2.5 text-sm/5",
4962
+ lg: "px-4 py-3 text-base/6"
4963
+ };
4964
+ function positiveRows(value, name) {
4965
+ if (!Number.isInteger(value) || value < 1) throw new RangeError(`Textarea ${name} must be a positive integer.`);
4966
+ return value;
4967
+ }
4968
+ const textLength = (value) => String(value ?? "").replace(/\r\n?/g, "\n").length;
4969
+ function Textarea({ label, floating = false, helperText, error, size = "md", autoResize = false, rows = 3, minRows = 3, maxRows, resize = "vertical", showCount = false, mono = false, className, textareaClassName, ref, id, required, disabled, readOnly, placeholder, value, defaultValue, maxLength, onChange, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
4970
+ const nativeRows = positiveRows(autoResize ? minRows : rows, autoResize ? "minRows" : "rows");
4971
+ if (autoResize && maxRows != null && positiveRows(maxRows, "maxRows") < nativeRows) throw new RangeError("Textarea maxRows must be greater than or equal to minRows.");
4972
+ const controlRef = useRef(null);
4973
+ const mergedRef = useMergedRef(controlRef, ref);
4974
+ const [uncontrolledLength, setUncontrolledLength] = useState(() => textLength(defaultValue));
4975
+ const length = value == null ? uncontrolledLength : textLength(value);
4976
+ const overLimit = maxLength != null && maxLength >= 0 && length > maxLength;
4977
+ const countId = `${useId()}-count`;
4978
+ const resizeToContent = useAutoResize(controlRef, autoResize, nativeRows, maxRows);
4979
+ useLayoutEffect(() => {
4980
+ const node = controlRef.current;
4981
+ const form = node?.form;
4982
+ if (!form || !node) return;
4983
+ let resetTimer;
4984
+ const reset = (event) => {
4985
+ clearTimeout(resetTimer);
4986
+ resetTimer = setTimeout(() => {
4987
+ if (event.defaultPrevented) return;
4988
+ setUncontrolledLength(node.value.length);
4989
+ resizeToContent();
4990
+ }, 0);
4991
+ };
4992
+ form.addEventListener("reset", reset);
4993
+ return () => {
4994
+ clearTimeout(resetTimer);
4995
+ form.removeEventListener("reset", reset);
4996
+ };
4997
+ }, [props.form, resizeToContent]);
4998
+ return /* @__PURE__ */ jsx(FieldShell, {
4999
+ multiline: true,
5000
+ label,
5001
+ floating,
5002
+ helperText,
5003
+ error: error || overLimit,
5004
+ size,
5005
+ id,
5006
+ required,
5007
+ disabled,
5008
+ readOnly,
5009
+ "aria-describedby": [describedBy, showCount ? countId : null].filter(Boolean).join(" ") || void 0,
5010
+ className,
5011
+ controlRef,
5012
+ footer: showCount ? /* @__PURE__ */ jsxs("span", {
5013
+ id: countId,
5014
+ className: cn("ms-auto shrink-0 text-xs/snug tabular-nums", overLimit ? "text-danger" : "text-fg-muted"),
5015
+ children: [length, maxLength != null && maxLength >= 0 ? ` / ${maxLength}` : ""]
5016
+ }) : void 0,
5017
+ children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [field.floatingLabel("max-w-[calc(100%-1rem)]"), /* @__PURE__ */ jsx("textarea", {
5018
+ ...props,
5019
+ ref: mergedRef,
5020
+ id: field.id,
5021
+ rows: nativeRows,
5022
+ value,
5023
+ defaultValue,
5024
+ maxLength,
5025
+ required,
5026
+ disabled,
5027
+ readOnly,
5028
+ placeholder: field.floating ? placeholder || " " : placeholder,
5029
+ "aria-invalid": field.invalid ? true : ariaInvalid,
5030
+ "aria-describedby": field.describedBy,
5031
+ spellCheck: mono ? false : props.spellCheck,
5032
+ autoCapitalize: mono ? "off" : props.autoCapitalize,
5033
+ autoCorrect: mono ? "off" : props.autoCorrect,
5034
+ onChange: (event) => {
5035
+ setUncontrolledLength(event.currentTarget.value.length);
5036
+ resizeToContent();
5037
+ onChange?.(event);
5038
+ },
5039
+ className: cn("block w-full min-w-0 flex-1 rounded-[inherit] border-0 bg-transparent text-fg outline-none", "placeholder:text-input-placeholder disabled:cursor-not-allowed pointer-coarse:text-base", sizes[size], autoResize || resize === "none" ? "resize-none" : "resize-y", mono && "font-mono", field.floating && "placeholder:opacity-0 focus:placeholder:opacity-100", textareaClassName)
5040
+ })] })
5041
+ });
5042
+ }
5043
+ //#endregion
2973
5044
  //#region src/components/theme-switcher/theme-switcher.tsx
2974
5045
  const systemThemeQuery = "(prefers-color-scheme: dark)";
2975
5046
  function subscribeSystemTheme(onChange) {
@@ -3438,7 +5509,7 @@ function Viewport({ id, position = "bottom-right", visibleToasts = 3, host }) {
3438
5509
  };
3439
5510
  const [y, x] = position.split("-");
3440
5511
  const directions = x === "center" ? [y] : [y, x];
3441
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5512
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
3442
5513
  /* @__PURE__ */ jsx("section", {
3443
5514
  ref: viewportRef,
3444
5515
  popover: "manual",
@@ -3775,4 +5846,4 @@ function ToastCard({ record, layout, total, expanded, paused, frontHeight, ownHe
3775
5846
  });
3776
5847
  }
3777
5848
  //#endregion
3778
- export { Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, Input, Meter, PasswordInput, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
5849
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, Combobox, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, Meter, PasswordInput, Radio, RadioGroup, RangeSlider, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Slider, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, Textarea, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };