@ansh-ui/react 0.2.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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-5CFQFMP5.js +178 -0
  3. package/dist/chunk-5CFQFMP5.js.map +1 -0
  4. package/dist/chunk-6VRF7X5C.cjs +164 -0
  5. package/dist/chunk-6VRF7X5C.cjs.map +1 -0
  6. package/dist/chunk-AVPEA5PB.cjs +718 -0
  7. package/dist/chunk-AVPEA5PB.cjs.map +1 -0
  8. package/dist/chunk-C32LKF23.cjs +198 -0
  9. package/dist/chunk-C32LKF23.cjs.map +1 -0
  10. package/dist/chunk-C5JG6TMM.cjs +1200 -0
  11. package/dist/chunk-C5JG6TMM.cjs.map +1 -0
  12. package/dist/chunk-DATBU46V.cjs +614 -0
  13. package/dist/chunk-DATBU46V.cjs.map +1 -0
  14. package/dist/chunk-EGG4CP7C.js +320 -0
  15. package/dist/chunk-EGG4CP7C.js.map +1 -0
  16. package/dist/chunk-H34OXXJU.js +55 -0
  17. package/dist/chunk-H34OXXJU.js.map +1 -0
  18. package/dist/chunk-HO2YSF5O.js +187 -0
  19. package/dist/chunk-HO2YSF5O.js.map +1 -0
  20. package/dist/chunk-HPGCILO4.js +760 -0
  21. package/dist/chunk-HPGCILO4.js.map +1 -0
  22. package/dist/chunk-IBXLC5IE.js +154 -0
  23. package/dist/chunk-IBXLC5IE.js.map +1 -0
  24. package/dist/chunk-LDCZQP3R.js +1185 -0
  25. package/dist/chunk-LDCZQP3R.js.map +1 -0
  26. package/dist/chunk-LXCIXR5T.js +27 -0
  27. package/dist/chunk-LXCIXR5T.js.map +1 -0
  28. package/dist/chunk-O2PX54KX.js +94 -0
  29. package/dist/chunk-O2PX54KX.js.map +1 -0
  30. package/dist/chunk-OHBWBXLO.cjs +101 -0
  31. package/dist/chunk-OHBWBXLO.cjs.map +1 -0
  32. package/dist/chunk-OJN5JBNP.cjs +776 -0
  33. package/dist/chunk-OJN5JBNP.cjs.map +1 -0
  34. package/dist/chunk-OLTS6G5M.js +606 -0
  35. package/dist/chunk-OLTS6G5M.js.map +1 -0
  36. package/dist/chunk-PO5DU3FU.cjs +29 -0
  37. package/dist/chunk-PO5DU3FU.cjs.map +1 -0
  38. package/dist/chunk-QO3E7TJS.js +2940 -0
  39. package/dist/chunk-QO3E7TJS.js.map +1 -0
  40. package/dist/chunk-RHTLNSVW.cjs +2976 -0
  41. package/dist/chunk-RHTLNSVW.cjs.map +1 -0
  42. package/dist/chunk-SFU5EPSN.cjs +61 -0
  43. package/dist/chunk-SFU5EPSN.cjs.map +1 -0
  44. package/dist/chunk-T4AK2KLM.js +699 -0
  45. package/dist/chunk-T4AK2KLM.js.map +1 -0
  46. package/dist/chunk-WGYNCSTP.cjs +333 -0
  47. package/dist/chunk-WGYNCSTP.cjs.map +1 -0
  48. package/dist/chunk-XEP6P7E6.js +279 -0
  49. package/dist/chunk-XEP6P7E6.js.map +1 -0
  50. package/dist/chunk-XMXECSQO.cjs +286 -0
  51. package/dist/chunk-XMXECSQO.cjs.map +1 -0
  52. package/dist/chunk-ZWAFLHC2.cjs +181 -0
  53. package/dist/chunk-ZWAFLHC2.cjs.map +1 -0
  54. package/dist/data.cjs +71 -0
  55. package/dist/data.cjs.map +1 -0
  56. package/dist/data.d.cts +316 -0
  57. package/dist/data.d.ts +316 -0
  58. package/dist/data.js +6 -0
  59. package/dist/data.js.map +1 -0
  60. package/dist/form.cjs +153 -0
  61. package/dist/form.cjs.map +1 -0
  62. package/dist/form.d.cts +660 -0
  63. package/dist/form.d.ts +660 -0
  64. package/dist/form.js +8 -0
  65. package/dist/form.js.map +1 -0
  66. package/dist/index.cjs +1641 -0
  67. package/dist/index.cjs.map +1 -0
  68. package/dist/index.d.cts +552 -0
  69. package/dist/index.d.ts +552 -0
  70. package/dist/index.js +956 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/layout.cjs +68 -0
  73. package/dist/layout.cjs.map +1 -0
  74. package/dist/layout.d.cts +169 -0
  75. package/dist/layout.d.ts +169 -0
  76. package/dist/layout.js +7 -0
  77. package/dist/layout.js.map +1 -0
  78. package/dist/motion-CQmCq6_k.d.ts +395 -0
  79. package/dist/motion-CSX4g2N8.d.cts +395 -0
  80. package/dist/motion.cjs +79 -0
  81. package/dist/motion.cjs.map +1 -0
  82. package/dist/motion.d.cts +4 -0
  83. package/dist/motion.d.ts +4 -0
  84. package/dist/motion.js +6 -0
  85. package/dist/motion.js.map +1 -0
  86. package/dist/navigation.cjs +48 -0
  87. package/dist/navigation.cjs.map +1 -0
  88. package/dist/navigation.d.cts +210 -0
  89. package/dist/navigation.d.ts +210 -0
  90. package/dist/navigation.js +7 -0
  91. package/dist/navigation.js.map +1 -0
  92. package/dist/overlay.cjs +69 -0
  93. package/dist/overlay.cjs.map +1 -0
  94. package/dist/overlay.d.cts +330 -0
  95. package/dist/overlay.d.ts +330 -0
  96. package/dist/overlay.js +8 -0
  97. package/dist/overlay.js.map +1 -0
  98. package/dist/position-DVScKcaa.d.cts +66 -0
  99. package/dist/position-DVScKcaa.d.ts +66 -0
  100. package/dist/primitives.cjs +162 -0
  101. package/dist/primitives.cjs.map +1 -0
  102. package/dist/primitives.d.cts +294 -0
  103. package/dist/primitives.d.ts +294 -0
  104. package/dist/primitives.js +9 -0
  105. package/dist/primitives.js.map +1 -0
  106. package/dist/sidebar-D-PafAXw.d.cts +71 -0
  107. package/dist/sidebar-D-PafAXw.d.ts +71 -0
  108. package/dist/styles.css +5575 -0
  109. package/dist/use-observers-B9qtimrk.d.cts +99 -0
  110. package/dist/use-observers-B9qtimrk.d.ts +99 -0
  111. package/package.json +92 -0
@@ -0,0 +1,2940 @@
1
+ import { useAnchoredPosition, useDismiss, Portal } from './chunk-XEP6P7E6.js';
2
+ import { useControllableState } from './chunk-LXCIXR5T.js';
3
+ import { useMergedRef } from './chunk-IBXLC5IE.js';
4
+ import { cx } from './chunk-H34OXXJU.js';
5
+ import { createContext, forwardRef, useRef, useState, useEffect, useCallback, useId, useContext, useMemo } from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { tryParseColor, parseColor, rgbToHsv, hsvToRgb, toHex, toOklchString, contrastRatio } from '@ansh-ui/core';
8
+
9
+ var FieldContext = createContext(null);
10
+ function useFieldControl() {
11
+ const field = useContext(FieldContext);
12
+ if (!field) return {};
13
+ return {
14
+ id: field.id,
15
+ ...field.describedBy ? { "aria-describedby": field.describedBy } : null,
16
+ ...field.invalid ? { "aria-invalid": true } : null,
17
+ ...field.required ? { "aria-required": true } : null,
18
+ ...field.disabled ? { disabled: true } : null
19
+ };
20
+ }
21
+ function useFieldContext() {
22
+ return useContext(FieldContext);
23
+ }
24
+ function Field({
25
+ label,
26
+ description,
27
+ error,
28
+ required = false,
29
+ disabled = false,
30
+ orientation = "vertical",
31
+ className,
32
+ children
33
+ }) {
34
+ const id = useId();
35
+ const descriptionId = `${id}-description`;
36
+ const errorId = `${id}-error`;
37
+ const describedBy = [error ? errorId : null, description ? descriptionId : null].filter(Boolean).join(" ") || void 0;
38
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value: { id, describedBy, invalid: Boolean(error), required, disabled }, children: /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ className: cx(
42
+ "ansh-field",
43
+ `ansh-field--${orientation}`,
44
+ disabled && "ansh-field--disabled",
45
+ className
46
+ ),
47
+ children: [
48
+ label ? /* @__PURE__ */ jsxs("label", { className: "ansh-field__label", htmlFor: id, children: [
49
+ label,
50
+ required ? /* @__PURE__ */ jsxs(Fragment, { children: [
51
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ansh-field__required", children: "*" }),
52
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", children: " (required)" })
53
+ ] }) : null
54
+ ] }) : null,
55
+ /* @__PURE__ */ jsx("div", { className: "ansh-field__control", children }),
56
+ description ? /* @__PURE__ */ jsx("p", { className: "ansh-field__description", id: descriptionId, children: description }) : null,
57
+ error ? (
58
+ // `role="alert"` announces the message when it appears after a failed submit, which is
59
+ // the moment it matters. `aria-describedby` alone would only be read on re-focus.
60
+ /* @__PURE__ */ jsx("p", { className: "ansh-field__error", id: errorId, role: "alert", children: error })
61
+ ) : null
62
+ ]
63
+ }
64
+ ) });
65
+ }
66
+ function Fieldset({ legend, description, className, children }) {
67
+ const id = useId();
68
+ return /* @__PURE__ */ jsxs(
69
+ "fieldset",
70
+ {
71
+ className: cx("ansh-fieldset", className),
72
+ ...description ? { "aria-describedby": id } : null,
73
+ children: [
74
+ /* @__PURE__ */ jsx("legend", { className: "ansh-fieldset__legend", children: legend }),
75
+ description ? /* @__PURE__ */ jsx("p", { className: "ansh-fieldset__description", id, children: description }) : null,
76
+ children
77
+ ]
78
+ }
79
+ );
80
+ }
81
+ function VisuallyHidden({
82
+ children,
83
+ ...rest
84
+ }) {
85
+ return /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", ...rest, children });
86
+ }
87
+ var Input = forwardRef(function Input2({
88
+ size = "md",
89
+ prefix,
90
+ suffix,
91
+ clearable = false,
92
+ onClear,
93
+ invalid,
94
+ className,
95
+ wrapperClassName,
96
+ disabled,
97
+ ...rest
98
+ }, forwardedRef) {
99
+ const fieldProps = useFieldControl();
100
+ const localRef = useRef(null);
101
+ const ref = useMergedRef(localRef, forwardedRef);
102
+ const [hasValue, setHasValue] = useState(
103
+ () => String(rest.value ?? rest.defaultValue ?? "").length > 0
104
+ );
105
+ useEffect(() => {
106
+ if (rest.value !== void 0) setHasValue(String(rest.value).length > 0);
107
+ }, [rest.value]);
108
+ const showClear = clearable && hasValue && !disabled && !rest.readOnly;
109
+ const grouped = Boolean(prefix || suffix || clearable);
110
+ const control = /* @__PURE__ */ jsx(
111
+ "input",
112
+ {
113
+ ref,
114
+ className: cx("ansh-input", `ansh-input--${size}`, !grouped && className),
115
+ disabled,
116
+ ...fieldProps,
117
+ ...rest,
118
+ ...invalid ? { "aria-invalid": true } : null,
119
+ onChange: (event) => {
120
+ setHasValue(event.target.value.length > 0);
121
+ rest.onChange?.(event);
122
+ }
123
+ }
124
+ );
125
+ if (!grouped) return control;
126
+ return /* @__PURE__ */ jsxs(
127
+ "div",
128
+ {
129
+ className: cx(
130
+ "ansh-input-group",
131
+ `ansh-input-group--${size}`,
132
+ disabled && "ansh-input-group--disabled",
133
+ (invalid || fieldProps["aria-invalid"]) && "ansh-input-group--invalid",
134
+ wrapperClassName,
135
+ className
136
+ ),
137
+ onMouseDown: (event) => {
138
+ if (event.target === event.currentTarget) {
139
+ event.preventDefault();
140
+ localRef.current?.focus();
141
+ }
142
+ },
143
+ children: [
144
+ prefix ? /* @__PURE__ */ jsx("span", { className: "ansh-input-group__affix", "aria-hidden": "true", children: prefix }) : null,
145
+ control,
146
+ showClear ? /* @__PURE__ */ jsx(
147
+ "button",
148
+ {
149
+ type: "button",
150
+ className: "ansh-input-group__clear ansh-focusable",
151
+ "aria-label": "Clear",
152
+ onClick: () => {
153
+ const node = localRef.current;
154
+ if (node) {
155
+ const setter = Object.getOwnPropertyDescriptor(
156
+ window.HTMLInputElement.prototype,
157
+ "value"
158
+ )?.set;
159
+ setter?.call(node, "");
160
+ node.dispatchEvent(new Event("input", { bubbles: true }));
161
+ node.focus();
162
+ }
163
+ setHasValue(false);
164
+ onClear?.();
165
+ },
166
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
167
+ "path",
168
+ {
169
+ d: "M4 4l8 8M12 4l-8 8",
170
+ stroke: "currentColor",
171
+ strokeWidth: "1.75",
172
+ strokeLinecap: "round",
173
+ fill: "none"
174
+ }
175
+ ) })
176
+ }
177
+ ) : null,
178
+ suffix ? /* @__PURE__ */ jsx("span", { className: "ansh-input-group__affix", children: suffix }) : null
179
+ ]
180
+ }
181
+ );
182
+ });
183
+ var Textarea = forwardRef(function Textarea2({
184
+ autoResize = false,
185
+ maxRows = 12,
186
+ showCount = false,
187
+ invalid,
188
+ className,
189
+ ...rest
190
+ }, forwardedRef) {
191
+ const fieldProps = useFieldControl();
192
+ const localRef = useRef(null);
193
+ const ref = useMergedRef(localRef, forwardedRef);
194
+ const [count, setCount] = useState(() => String(rest.value ?? rest.defaultValue ?? "").length);
195
+ const resize = useCallback(() => {
196
+ const node = localRef.current;
197
+ if (!node || !autoResize) return;
198
+ node.style.height = "auto";
199
+ const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;
200
+ const max = lineHeight * maxRows;
201
+ node.style.height = `${Math.min(node.scrollHeight, max)}px`;
202
+ node.style.overflowY = node.scrollHeight > max ? "auto" : "hidden";
203
+ }, [autoResize, maxRows]);
204
+ useEffect(resize, [resize, rest.value]);
205
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-textarea-wrap", className), children: [
206
+ /* @__PURE__ */ jsx(
207
+ "textarea",
208
+ {
209
+ ref,
210
+ className: cx("ansh-input", "ansh-textarea", autoResize && "ansh-textarea--auto"),
211
+ ...fieldProps,
212
+ ...rest,
213
+ ...invalid ? { "aria-invalid": true } : null,
214
+ onChange: (event) => {
215
+ setCount(event.target.value.length);
216
+ resize();
217
+ rest.onChange?.(event);
218
+ }
219
+ }
220
+ ),
221
+ showCount ? /* @__PURE__ */ jsx(
222
+ "span",
223
+ {
224
+ className: cx(
225
+ "ansh-textarea__count",
226
+ rest.maxLength !== void 0 && count > rest.maxLength * 0.9 && "ansh-textarea__count--near"
227
+ ),
228
+ "aria-live": "polite",
229
+ children: rest.maxLength !== void 0 ? `${count} / ${rest.maxLength}` : count
230
+ }
231
+ ) : null
232
+ ] });
233
+ });
234
+ var NumberInput = forwardRef(function NumberInput2({
235
+ value,
236
+ defaultValue = 0,
237
+ onChange,
238
+ min = Number.NEGATIVE_INFINITY,
239
+ max = Number.POSITIVE_INFINITY,
240
+ step = 1,
241
+ precision,
242
+ size = "md",
243
+ unit,
244
+ steppers = true,
245
+ className,
246
+ disabled,
247
+ ...rest
248
+ }, forwardedRef) {
249
+ const fieldProps = useFieldControl();
250
+ const [current, setCurrent] = useControllableState({
251
+ value,
252
+ defaultValue,
253
+ onChange
254
+ });
255
+ const decimals = precision ?? (String(step).includes(".") ? String(step).split(".")[1].length : 0);
256
+ const [text, setText] = useState(() => current.toFixed(decimals));
257
+ const [editing, setEditing] = useState(false);
258
+ useEffect(() => {
259
+ if (!editing) setText(current.toFixed(decimals));
260
+ }, [current, decimals, editing]);
261
+ const clamp = (n) => Math.min(Math.max(n, min), max);
262
+ const nudge = (direction, multiplier = 1) => {
263
+ const next = clamp(current + step * direction * multiplier);
264
+ setCurrent(Number(next.toFixed(decimals)));
265
+ };
266
+ const commit = (raw) => {
267
+ const parsed = Number.parseFloat(raw);
268
+ const next = Number.isFinite(parsed) ? clamp(parsed) : current;
269
+ setCurrent(Number(next.toFixed(decimals)));
270
+ setText(next.toFixed(decimals));
271
+ };
272
+ return /* @__PURE__ */ jsxs(
273
+ "div",
274
+ {
275
+ className: cx(
276
+ "ansh-number",
277
+ `ansh-number--${size}`,
278
+ disabled && "ansh-number--disabled",
279
+ className
280
+ ),
281
+ children: [
282
+ /* @__PURE__ */ jsx(
283
+ "input",
284
+ {
285
+ ref: forwardedRef,
286
+ type: "text",
287
+ inputMode: "decimal",
288
+ className: "ansh-input ansh-number__input",
289
+ value: text,
290
+ disabled,
291
+ role: "spinbutton",
292
+ "aria-valuenow": current,
293
+ ...Number.isFinite(min) ? { "aria-valuemin": min } : null,
294
+ ...Number.isFinite(max) ? { "aria-valuemax": max } : null,
295
+ ...fieldProps,
296
+ ...rest,
297
+ onFocus: (event) => {
298
+ setEditing(true);
299
+ rest.onFocus?.(event);
300
+ },
301
+ onChange: (event) => setText(event.target.value),
302
+ onBlur: (event) => {
303
+ setEditing(false);
304
+ commit(event.target.value);
305
+ rest.onBlur?.(event);
306
+ },
307
+ onKeyDown: (event) => {
308
+ if (event.key === "ArrowUp") {
309
+ event.preventDefault();
310
+ nudge(1, event.shiftKey ? 10 : 1);
311
+ } else if (event.key === "ArrowDown") {
312
+ event.preventDefault();
313
+ nudge(-1, event.shiftKey ? 10 : 1);
314
+ } else if (event.key === "PageUp") {
315
+ event.preventDefault();
316
+ nudge(1, 10);
317
+ } else if (event.key === "PageDown") {
318
+ event.preventDefault();
319
+ nudge(-1, 10);
320
+ } else if (event.key === "Home" && Number.isFinite(min)) {
321
+ event.preventDefault();
322
+ setCurrent(min);
323
+ } else if (event.key === "End" && Number.isFinite(max)) {
324
+ event.preventDefault();
325
+ setCurrent(max);
326
+ } else if (event.key === "Enter") {
327
+ commit(event.target.value);
328
+ }
329
+ rest.onKeyDown?.(event);
330
+ }
331
+ }
332
+ ),
333
+ unit ? /* @__PURE__ */ jsx("span", { className: "ansh-number__unit", children: unit }) : null,
334
+ steppers ? (
335
+ // `tabIndex={-1}` and `aria-hidden`: the input is already a spinbutton with arrow-key
336
+ // support, so these buttons would be redundant tab stops announcing nothing new.
337
+ /* @__PURE__ */ jsxs("span", { className: "ansh-number__steppers", "aria-hidden": "true", children: [
338
+ /* @__PURE__ */ jsx(
339
+ "button",
340
+ {
341
+ type: "button",
342
+ tabIndex: -1,
343
+ className: "ansh-number__step",
344
+ disabled: disabled || current >= max,
345
+ onClick: () => nudge(1),
346
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 6", width: "10", height: "6", children: /* @__PURE__ */ jsx(
347
+ "path",
348
+ {
349
+ d: "M1 5l4-4 4 4",
350
+ stroke: "currentColor",
351
+ strokeWidth: "1.5",
352
+ fill: "none",
353
+ strokeLinecap: "round"
354
+ }
355
+ ) })
356
+ }
357
+ ),
358
+ /* @__PURE__ */ jsx(
359
+ "button",
360
+ {
361
+ type: "button",
362
+ tabIndex: -1,
363
+ className: "ansh-number__step",
364
+ disabled: disabled || current <= min,
365
+ onClick: () => nudge(-1),
366
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 6", width: "10", height: "6", children: /* @__PURE__ */ jsx(
367
+ "path",
368
+ {
369
+ d: "M1 1l4 4 4-4",
370
+ stroke: "currentColor",
371
+ strokeWidth: "1.5",
372
+ fill: "none",
373
+ strokeLinecap: "round"
374
+ }
375
+ ) })
376
+ }
377
+ )
378
+ ] })
379
+ ) : null
380
+ ]
381
+ }
382
+ );
383
+ });
384
+ var Checkbox = forwardRef(function Checkbox2({
385
+ checked,
386
+ defaultChecked = false,
387
+ onChange,
388
+ indeterminate = false,
389
+ label,
390
+ description,
391
+ size = "md",
392
+ invalid,
393
+ className,
394
+ disabled,
395
+ ...rest
396
+ }, forwardedRef) {
397
+ const fieldProps = useFieldControl();
398
+ const localRef = useRef(null);
399
+ const ref = useMergedRef(localRef, forwardedRef);
400
+ const generatedId = useId();
401
+ const id = rest.id ?? fieldProps.id ?? generatedId;
402
+ const descriptionId = `${id}-hint`;
403
+ const [isChecked, setChecked] = useControllableState({
404
+ value: checked,
405
+ defaultValue: defaultChecked,
406
+ onChange
407
+ });
408
+ useEffect(() => {
409
+ if (localRef.current) localRef.current.indeterminate = indeterminate;
410
+ }, [indeterminate]);
411
+ return /* @__PURE__ */ jsxs(
412
+ "div",
413
+ {
414
+ className: cx(
415
+ "ansh-choice",
416
+ `ansh-choice--${size}`,
417
+ disabled && "ansh-choice--disabled",
418
+ className
419
+ ),
420
+ children: [
421
+ /* @__PURE__ */ jsxs("span", { className: "ansh-choice__box", children: [
422
+ /* @__PURE__ */ jsx(
423
+ "input",
424
+ {
425
+ ref,
426
+ type: "checkbox",
427
+ className: "ansh-choice__input",
428
+ checked: isChecked,
429
+ disabled: disabled ?? fieldProps.disabled,
430
+ onChange: (event) => setChecked(event.target.checked),
431
+ ...indeterminate ? { "aria-checked": "mixed" } : null,
432
+ ...invalid ? { "aria-invalid": true } : null,
433
+ ...description ? { "aria-describedby": descriptionId } : null,
434
+ ...rest,
435
+ id
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx("span", { className: "ansh-choice__indicator ansh-choice__indicator--check", "aria-hidden": "true", children: indeterminate ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 14 14", width: "10", height: "10", children: /* @__PURE__ */ jsx("path", { d: "M3 7h8", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 14 14", width: "10", height: "10", children: /* @__PURE__ */ jsx(
439
+ "path",
440
+ {
441
+ d: "M2.5 7.5l3 3 6-6.5",
442
+ stroke: "currentColor",
443
+ strokeWidth: "2",
444
+ strokeLinecap: "round",
445
+ strokeLinejoin: "round",
446
+ fill: "none"
447
+ }
448
+ ) }) })
449
+ ] }),
450
+ label || description ? /* @__PURE__ */ jsxs("span", { className: "ansh-choice__text", children: [
451
+ label ? /* @__PURE__ */ jsx("label", { className: "ansh-choice__label", htmlFor: id, children: label }) : null,
452
+ description ? /* @__PURE__ */ jsx("span", { className: "ansh-choice__description", id: descriptionId, children: description }) : null
453
+ ] }) : null
454
+ ]
455
+ }
456
+ );
457
+ });
458
+ var Switch = forwardRef(function Switch2({
459
+ checked,
460
+ defaultChecked = false,
461
+ onChange,
462
+ label,
463
+ description,
464
+ labelPosition = "end",
465
+ size = "md",
466
+ className,
467
+ disabled,
468
+ ...rest
469
+ }, forwardedRef) {
470
+ const fieldProps = useFieldControl();
471
+ const generatedId = useId();
472
+ const id = rest.id ?? fieldProps.id ?? generatedId;
473
+ const descriptionId = `${id}-hint`;
474
+ const [isChecked, setChecked] = useControllableState({
475
+ value: checked,
476
+ defaultValue: defaultChecked,
477
+ onChange
478
+ });
479
+ return /* @__PURE__ */ jsxs(
480
+ "div",
481
+ {
482
+ className: cx(
483
+ "ansh-switch-row",
484
+ `ansh-switch-row--${labelPosition}`,
485
+ disabled && "ansh-choice--disabled",
486
+ className
487
+ ),
488
+ children: [
489
+ /* @__PURE__ */ jsxs("span", { className: "ansh-switch", "data-size": size, children: [
490
+ /* @__PURE__ */ jsx(
491
+ "input",
492
+ {
493
+ ref: forwardedRef,
494
+ type: "checkbox",
495
+ role: "switch",
496
+ className: "ansh-choice__input",
497
+ checked: isChecked,
498
+ disabled: disabled ?? fieldProps.disabled,
499
+ onChange: (event) => setChecked(event.target.checked),
500
+ ...description ? { "aria-describedby": descriptionId } : null,
501
+ ...rest,
502
+ id
503
+ }
504
+ ),
505
+ /* @__PURE__ */ jsx("span", { className: "ansh-switch__track", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "ansh-switch__thumb" }) })
506
+ ] }),
507
+ label || description ? /* @__PURE__ */ jsxs("span", { className: "ansh-choice__text", children: [
508
+ label ? /* @__PURE__ */ jsx("label", { className: "ansh-choice__label", htmlFor: id, children: label }) : null,
509
+ description ? /* @__PURE__ */ jsx("span", { className: "ansh-choice__description", id: descriptionId, children: description }) : null
510
+ ] }) : null
511
+ ]
512
+ }
513
+ );
514
+ });
515
+ var RadioGroupContext = createContext(null);
516
+ function RadioGroup({
517
+ value,
518
+ defaultValue,
519
+ onChange,
520
+ name,
521
+ label,
522
+ orientation = "vertical",
523
+ disabled = false,
524
+ className,
525
+ children
526
+ }) {
527
+ const generatedName = useId();
528
+ const [current, setCurrent] = useControllableState({
529
+ value,
530
+ defaultValue,
531
+ onChange
532
+ });
533
+ return /* @__PURE__ */ jsx(
534
+ RadioGroupContext.Provider,
535
+ {
536
+ value: {
537
+ name: name ?? generatedName,
538
+ value: current,
539
+ setValue: setCurrent,
540
+ disabled
541
+ },
542
+ children: /* @__PURE__ */ jsx(
543
+ "div",
544
+ {
545
+ role: "radiogroup",
546
+ ...label && typeof label === "string" ? { "aria-label": label } : null,
547
+ className: cx("ansh-radio-group", `ansh-radio-group--${orientation}`, className),
548
+ children
549
+ }
550
+ )
551
+ }
552
+ );
553
+ }
554
+ var Radio = forwardRef(function Radio2({ value, label, description, size = "md", className, disabled, ...rest }, forwardedRef) {
555
+ const group = useContext(RadioGroupContext);
556
+ const generatedId = useId();
557
+ const id = rest.id ?? generatedId;
558
+ const descriptionId = `${id}-hint`;
559
+ const isDisabled = disabled || group?.disabled;
560
+ return /* @__PURE__ */ jsxs(
561
+ "div",
562
+ {
563
+ className: cx(
564
+ "ansh-choice",
565
+ `ansh-choice--${size}`,
566
+ isDisabled && "ansh-choice--disabled",
567
+ className
568
+ ),
569
+ children: [
570
+ /* @__PURE__ */ jsxs("span", { className: "ansh-choice__box", children: [
571
+ /* @__PURE__ */ jsx(
572
+ "input",
573
+ {
574
+ ref: forwardedRef,
575
+ type: "radio",
576
+ className: "ansh-choice__input",
577
+ name: group?.name,
578
+ value,
579
+ checked: group ? group.value === value : void 0,
580
+ disabled: isDisabled,
581
+ onChange: () => group?.setValue(value),
582
+ ...description ? { "aria-describedby": descriptionId } : null,
583
+ ...rest,
584
+ id
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx("span", { className: "ansh-choice__indicator ansh-choice__indicator--dot", "aria-hidden": "true" })
588
+ ] }),
589
+ label || description ? /* @__PURE__ */ jsxs("span", { className: "ansh-choice__text", children: [
590
+ label ? /* @__PURE__ */ jsx("label", { className: "ansh-choice__label", htmlFor: id, children: label }) : null,
591
+ description ? /* @__PURE__ */ jsx("span", { className: "ansh-choice__description", id: descriptionId, children: description }) : null
592
+ ] }) : null
593
+ ]
594
+ }
595
+ );
596
+ });
597
+ var ChoiceCard = forwardRef(function ChoiceCard2({ icon, label, description, value, className, ...rest }, forwardedRef) {
598
+ const group = useContext(RadioGroupContext);
599
+ const selected = group?.value === value;
600
+ return /* @__PURE__ */ jsxs("label", { className: cx("ansh-choice-card", selected && "ansh-choice-card--selected", className), children: [
601
+ /* @__PURE__ */ jsx(Radio, { ref: forwardedRef, value, className: "ansh-choice-card__radio", ...rest }),
602
+ icon ? /* @__PURE__ */ jsx("span", { className: "ansh-choice-card__icon", children: icon }) : null,
603
+ /* @__PURE__ */ jsxs("span", { className: "ansh-choice-card__body", children: [
604
+ /* @__PURE__ */ jsx("span", { className: "ansh-choice-card__label", children: label }),
605
+ description ? /* @__PURE__ */ jsx("span", { className: "ansh-choice-card__description", children: description }) : null
606
+ ] })
607
+ ] });
608
+ });
609
+ function stepActive(options, active, delta, loop = true) {
610
+ if (options.length === 0) return -1;
611
+ let index = active;
612
+ for (let attempts = 0; attempts < options.length; attempts++) {
613
+ index += delta;
614
+ if (index < 0) {
615
+ if (!loop) return active;
616
+ index = options.length - 1;
617
+ }
618
+ if (index >= options.length) {
619
+ if (!loop) return active;
620
+ index = 0;
621
+ }
622
+ if (!options[index]?.disabled) return index;
623
+ }
624
+ return active;
625
+ }
626
+ function firstEnabled(options, from = 0) {
627
+ for (let i = from; i < options.length; i++) {
628
+ if (!options[i]?.disabled) return i;
629
+ }
630
+ return -1;
631
+ }
632
+ function useTypeahead(options, onMatch, timeout = 600) {
633
+ const bufferRef = useRef("");
634
+ const timerRef = useRef(void 0);
635
+ return useCallback(
636
+ (key) => {
637
+ if (key.length !== 1 || key === " ") return false;
638
+ clearTimeout(timerRef.current);
639
+ bufferRef.current += key.toLowerCase();
640
+ timerRef.current = setTimeout(() => {
641
+ bufferRef.current = "";
642
+ }, timeout);
643
+ const buffer = bufferRef.current;
644
+ const index = options.findIndex(
645
+ (option) => !option.disabled && option.label.toLowerCase().startsWith(buffer)
646
+ );
647
+ if (index >= 0) {
648
+ onMatch(index);
649
+ return true;
650
+ }
651
+ return false;
652
+ },
653
+ [options, onMatch, timeout]
654
+ );
655
+ }
656
+ function scoreOption(query, option) {
657
+ if (query.trim() === "") return 0;
658
+ const q = query.toLowerCase();
659
+ const haystacks = [option.label, option.keywords ?? "", option.description ?? ""];
660
+ let best = -1;
661
+ for (let h = 0; h < haystacks.length; h++) {
662
+ const text = haystacks[h].toLowerCase();
663
+ let queryIndex = 0;
664
+ let total = 0;
665
+ let run = 0;
666
+ for (let i = 0; i < text.length && queryIndex < q.length; i++) {
667
+ if (text[i] === q[queryIndex]) {
668
+ run++;
669
+ total += run * 2 + (i === 0 ? 8 : 0);
670
+ queryIndex++;
671
+ } else {
672
+ run = 0;
673
+ }
674
+ }
675
+ if (queryIndex === q.length) best = Math.max(best, total - h * 4);
676
+ }
677
+ return best;
678
+ }
679
+ function filterOptions(options, query) {
680
+ if (query.trim() === "") return options;
681
+ return options.map((option) => ({ option, score: scoreOption(query, option) })).filter((entry) => entry.score >= 0).sort((a, b) => b.score - a.score).map((entry) => entry.option);
682
+ }
683
+ function OptionList({
684
+ options,
685
+ active,
686
+ selected,
687
+ listId,
688
+ multiple = false,
689
+ emptyMessage = "No results",
690
+ onSelect,
691
+ onActiveChange,
692
+ query
693
+ }) {
694
+ const selectedSet = useMemo(() => new Set(selected), [selected]);
695
+ if (options.length === 0) {
696
+ return /* @__PURE__ */ jsx("div", { className: "ansh-listbox__empty", children: emptyMessage });
697
+ }
698
+ let lastGroup;
699
+ return /* @__PURE__ */ jsx(Fragment, { children: options.map((option, index) => {
700
+ const heading = option.group && option.group !== lastGroup ? option.group : null;
701
+ lastGroup = option.group;
702
+ const isSelected = selectedSet.has(option.value);
703
+ return /* @__PURE__ */ jsxs("div", { role: "presentation", children: [
704
+ heading ? /* @__PURE__ */ jsx("div", { className: "ansh-listbox__group", role: "presentation", children: heading }) : null,
705
+ /* @__PURE__ */ jsxs(
706
+ "div",
707
+ {
708
+ id: `${listId}-option-${index}`,
709
+ role: "option",
710
+ "aria-selected": isSelected,
711
+ "aria-disabled": option.disabled || void 0,
712
+ className: cx(
713
+ "ansh-listbox__option",
714
+ index === active && "ansh-listbox__option--active",
715
+ isSelected && "ansh-listbox__option--selected",
716
+ option.disabled && "ansh-listbox__option--disabled"
717
+ ),
718
+ onMouseDown: (event) => event.preventDefault(),
719
+ onMouseEnter: () => !option.disabled && onActiveChange(index),
720
+ onClick: () => !option.disabled && onSelect(option),
721
+ children: [
722
+ multiple ? /* @__PURE__ */ jsx(
723
+ "span",
724
+ {
725
+ className: cx("ansh-listbox__check", isSelected && "ansh-listbox__check--on"),
726
+ "aria-hidden": "true",
727
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 14 14", width: "10", height: "10", children: /* @__PURE__ */ jsx(
728
+ "path",
729
+ {
730
+ d: "M2.5 7.5l3 3 6-6.5",
731
+ stroke: "currentColor",
732
+ strokeWidth: "2",
733
+ strokeLinecap: "round",
734
+ strokeLinejoin: "round",
735
+ fill: "none"
736
+ }
737
+ ) })
738
+ }
739
+ ) : null,
740
+ option.icon ? /* @__PURE__ */ jsx("span", { className: "ansh-listbox__icon", children: option.icon }) : null,
741
+ /* @__PURE__ */ jsxs("span", { className: "ansh-listbox__text", children: [
742
+ /* @__PURE__ */ jsx("span", { className: "ansh-listbox__label", children: query ? /* @__PURE__ */ jsx(Highlight, { text: option.label, query }) : option.label }),
743
+ option.description ? /* @__PURE__ */ jsx("span", { className: "ansh-listbox__description", children: option.description }) : null
744
+ ] }),
745
+ !multiple && isSelected ? /* @__PURE__ */ jsx("span", { className: "ansh-listbox__tick", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 14 14", width: "12", height: "12", children: /* @__PURE__ */ jsx(
746
+ "path",
747
+ {
748
+ d: "M2.5 7.5l3 3 6-6.5",
749
+ stroke: "currentColor",
750
+ strokeWidth: "2",
751
+ strokeLinecap: "round",
752
+ strokeLinejoin: "round",
753
+ fill: "none"
754
+ }
755
+ ) }) }) : null
756
+ ]
757
+ }
758
+ )
759
+ ] }, option.value);
760
+ }) });
761
+ }
762
+ function Highlight({ text, query }) {
763
+ const q = query.toLowerCase();
764
+ const lower = text.toLowerCase();
765
+ const parts = [];
766
+ let queryIndex = 0;
767
+ let plain = "";
768
+ for (let i = 0; i < text.length; i++) {
769
+ if (queryIndex < q.length && lower[i] === q[queryIndex]) {
770
+ if (plain) {
771
+ parts.push(plain);
772
+ plain = "";
773
+ }
774
+ parts.push(
775
+ /* @__PURE__ */ jsx("mark", { className: "ansh-listbox__mark", children: text[i] }, i)
776
+ );
777
+ queryIndex++;
778
+ } else {
779
+ plain += text[i];
780
+ }
781
+ }
782
+ if (plain) parts.push(plain);
783
+ return /* @__PURE__ */ jsx(Fragment, { children: parts });
784
+ }
785
+ function Select({
786
+ options,
787
+ value,
788
+ defaultValue = null,
789
+ onChange,
790
+ placeholder = "Select\u2026",
791
+ disabled = false,
792
+ invalid = false,
793
+ size = "md",
794
+ clearable = false,
795
+ label,
796
+ className,
797
+ name,
798
+ maxHeight = "18rem",
799
+ emptyMessage
800
+ }) {
801
+ const fieldProps = useFieldControl();
802
+ const [open, setOpen] = useState(false);
803
+ const [current, setCurrent] = useControllableState({
804
+ value,
805
+ defaultValue,
806
+ onChange
807
+ });
808
+ const triggerRef = useRef(null);
809
+ const listRef = useRef(null);
810
+ const listId = useId();
811
+ const selectedIndex = useMemo(
812
+ () => options.findIndex((option) => option.value === current),
813
+ [options, current]
814
+ );
815
+ const selected = selectedIndex >= 0 ? options[selectedIndex] : void 0;
816
+ const [active, setActive] = useState(0);
817
+ const position = useAnchoredPosition(triggerRef, listRef, open, {
818
+ placement: "bottom-start",
819
+ offset: 6,
820
+ matchWidth: true
821
+ });
822
+ useDismiss(listRef, open, () => setOpen(false), { ignore: [triggerRef] });
823
+ const openList = useCallback(() => {
824
+ if (disabled) return;
825
+ setActive(selectedIndex >= 0 ? selectedIndex : firstEnabled(options));
826
+ setOpen(true);
827
+ }, [disabled, selectedIndex, options]);
828
+ const commit = useCallback(
829
+ (option) => {
830
+ setCurrent(option.value);
831
+ setOpen(false);
832
+ triggerRef.current?.focus();
833
+ },
834
+ [setCurrent]
835
+ );
836
+ const typeahead = useTypeahead(options, (index) => {
837
+ setActive(index);
838
+ if (!open) setCurrent(options[index].value);
839
+ });
840
+ const onTriggerKeyDown = (event) => {
841
+ if (["ArrowDown", "ArrowUp", "Enter", " "].includes(event.key)) {
842
+ event.preventDefault();
843
+ openList();
844
+ return;
845
+ }
846
+ if (typeahead(event.key)) event.preventDefault();
847
+ };
848
+ const onListKeyDown = (event) => {
849
+ switch (event.key) {
850
+ case "ArrowDown":
851
+ event.preventDefault();
852
+ setActive((i) => stepActive(options, i, 1));
853
+ break;
854
+ case "ArrowUp":
855
+ event.preventDefault();
856
+ setActive((i) => stepActive(options, i, -1));
857
+ break;
858
+ case "Home":
859
+ event.preventDefault();
860
+ setActive(firstEnabled(options));
861
+ break;
862
+ case "End":
863
+ event.preventDefault();
864
+ setActive(stepActive(options, options.length, -1, false));
865
+ break;
866
+ case "Enter":
867
+ case " ": {
868
+ event.preventDefault();
869
+ const option = options[active];
870
+ if (option && !option.disabled) commit(option);
871
+ break;
872
+ }
873
+ case "Escape":
874
+ event.preventDefault();
875
+ setOpen(false);
876
+ triggerRef.current?.focus();
877
+ break;
878
+ case "Tab":
879
+ event.preventDefault();
880
+ setOpen(false);
881
+ triggerRef.current?.focus();
882
+ break;
883
+ default:
884
+ if (typeahead(event.key)) event.preventDefault();
885
+ }
886
+ };
887
+ const attachList = useCallback((node) => {
888
+ listRef.current = node;
889
+ node?.focus({ preventScroll: true });
890
+ }, []);
891
+ useEffect(() => {
892
+ if (!open) return;
893
+ document.getElementById(`${listId}-option-${active}`)?.scrollIntoView?.({ block: "nearest" });
894
+ }, [active, listId, open]);
895
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-select", `ansh-select--${size}`, className), children: [
896
+ /* @__PURE__ */ jsxs(
897
+ "button",
898
+ {
899
+ ref: triggerRef,
900
+ type: "button",
901
+ className: cx(
902
+ "ansh-select__trigger",
903
+ "ansh-focusable",
904
+ open && "ansh-select__trigger--open",
905
+ (invalid || fieldProps["aria-invalid"]) && "ansh-select__trigger--invalid",
906
+ !selected && "ansh-select__trigger--placeholder"
907
+ ),
908
+ disabled: disabled || fieldProps.disabled,
909
+ "aria-haspopup": "listbox",
910
+ "aria-expanded": open,
911
+ "aria-controls": open ? listId : void 0,
912
+ ...label ? { "aria-label": label } : null,
913
+ ...fieldProps,
914
+ onClick: () => open ? setOpen(false) : openList(),
915
+ onKeyDown: onTriggerKeyDown,
916
+ children: [
917
+ selected?.icon ? /* @__PURE__ */ jsx("span", { className: "ansh-select__icon", children: selected.icon }) : null,
918
+ /* @__PURE__ */ jsx("span", { className: "ansh-select__value", children: selected?.label ?? placeholder }),
919
+ clearable && selected && !disabled ? (
920
+ // A `<span role="button">` rather than a nested `<button>`: nesting interactive
921
+ // elements is invalid HTML and browsers resolve it by dropping the inner one.
922
+ /* @__PURE__ */ jsx(
923
+ "span",
924
+ {
925
+ role: "button",
926
+ tabIndex: -1,
927
+ "aria-label": "Clear selection",
928
+ className: "ansh-select__clear",
929
+ onClick: (event) => {
930
+ event.stopPropagation();
931
+ setCurrent(null);
932
+ },
933
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
934
+ "path",
935
+ {
936
+ d: "M4 4l8 8M12 4l-8 8",
937
+ stroke: "currentColor",
938
+ strokeWidth: "1.75",
939
+ strokeLinecap: "round",
940
+ fill: "none"
941
+ }
942
+ ) })
943
+ }
944
+ )
945
+ ) : null,
946
+ /* @__PURE__ */ jsx("span", { className: "ansh-select__arrow", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "12", height: "12", children: /* @__PURE__ */ jsx(
947
+ "path",
948
+ {
949
+ d: "M3 4.5L6 7.5l3-3",
950
+ stroke: "currentColor",
951
+ strokeWidth: "1.5",
952
+ fill: "none",
953
+ strokeLinecap: "round",
954
+ strokeLinejoin: "round"
955
+ }
956
+ ) }) })
957
+ ]
958
+ }
959
+ ),
960
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "" }) : null,
961
+ open ? /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
962
+ "div",
963
+ {
964
+ ref: attachList,
965
+ id: listId,
966
+ role: "listbox",
967
+ tabIndex: -1,
968
+ "aria-activedescendant": options[active] ? `${listId}-option-${active}` : void 0,
969
+ ...label ? { "aria-label": label } : null,
970
+ className: "ansh-listbox",
971
+ style: { ...position.style, maxHeight },
972
+ "data-side": position.side,
973
+ onKeyDown: onListKeyDown,
974
+ children: /* @__PURE__ */ jsx(
975
+ OptionList,
976
+ {
977
+ options,
978
+ active,
979
+ selected: current ? [current] : [],
980
+ listId,
981
+ onSelect: commit,
982
+ onActiveChange: setActive,
983
+ ...emptyMessage !== void 0 ? { emptyMessage } : null
984
+ }
985
+ )
986
+ }
987
+ ) }) : null
988
+ ] });
989
+ }
990
+ function Combobox({
991
+ options,
992
+ value,
993
+ defaultValue = [],
994
+ onChange,
995
+ multiple = false,
996
+ placeholder = "Search\u2026",
997
+ disabled = false,
998
+ invalid = false,
999
+ size = "md",
1000
+ label,
1001
+ className,
1002
+ maxHeight = "18rem",
1003
+ emptyMessage,
1004
+ onCreate,
1005
+ createLabel = (query) => `Create \u201C${query}\u201D`,
1006
+ onSearch,
1007
+ filter = true,
1008
+ maxVisibleChips = Number.POSITIVE_INFINITY
1009
+ }) {
1010
+ const fieldProps = useFieldControl();
1011
+ const [open, setOpen] = useState(false);
1012
+ const [query, setQuery] = useState("");
1013
+ const [active, setActive] = useState(0);
1014
+ const [selected, setSelected] = useControllableState({
1015
+ value,
1016
+ defaultValue,
1017
+ onChange
1018
+ });
1019
+ const inputRef = useRef(null);
1020
+ const wrapRef = useRef(null);
1021
+ const listRef = useRef(null);
1022
+ const listId = useId();
1023
+ const visible = useMemo(
1024
+ () => filter ? filterOptions(options, query) : options,
1025
+ [options, query, filter]
1026
+ );
1027
+ const byValue = useMemo(() => new Map(options.map((o) => [o.value, o])), [options]);
1028
+ const canCreate = Boolean(onCreate) && query.trim().length > 0 && !options.some((option) => option.label.toLowerCase() === query.trim().toLowerCase());
1029
+ const CREATE_VALUE = "__au_create__";
1030
+ const rows = useMemo(
1031
+ () => canCreate ? [...visible, { value: CREATE_VALUE, label: createLabel(query.trim()) }] : visible,
1032
+ [visible, canCreate, createLabel, query]
1033
+ );
1034
+ const position = useAnchoredPosition(wrapRef, listRef, open, {
1035
+ placement: "bottom-start",
1036
+ offset: 6,
1037
+ matchWidth: true
1038
+ });
1039
+ useDismiss(listRef, open, () => setOpen(false), { ignore: [wrapRef] });
1040
+ useEffect(() => setActive(firstEnabled(rows)), [rows]);
1041
+ const toggle = useCallback(
1042
+ (option) => {
1043
+ let optionValue = option.value;
1044
+ if (optionValue === CREATE_VALUE && onCreate) {
1045
+ optionValue = onCreate(query.trim());
1046
+ }
1047
+ if (multiple) {
1048
+ const next = selected.includes(optionValue) ? selected.filter((v) => v !== optionValue) : [...selected, optionValue];
1049
+ setSelected(next);
1050
+ setQuery("");
1051
+ inputRef.current?.focus();
1052
+ } else {
1053
+ setSelected([optionValue]);
1054
+ setQuery("");
1055
+ setOpen(false);
1056
+ }
1057
+ },
1058
+ [multiple, onCreate, query, selected, setSelected]
1059
+ );
1060
+ const remove = useCallback(
1061
+ (optionValue) => setSelected(selected.filter((v) => v !== optionValue)),
1062
+ [selected, setSelected]
1063
+ );
1064
+ const onKeyDown = (event) => {
1065
+ switch (event.key) {
1066
+ case "ArrowDown":
1067
+ event.preventDefault();
1068
+ if (!open) {
1069
+ setOpen(true);
1070
+ return;
1071
+ }
1072
+ setActive((i) => stepActive(rows, i, 1));
1073
+ break;
1074
+ case "ArrowUp":
1075
+ event.preventDefault();
1076
+ if (!open) {
1077
+ setOpen(true);
1078
+ return;
1079
+ }
1080
+ setActive((i) => stepActive(rows, i, -1));
1081
+ break;
1082
+ case "Home":
1083
+ if (!open) return;
1084
+ event.preventDefault();
1085
+ setActive(firstEnabled(rows));
1086
+ break;
1087
+ case "End":
1088
+ if (!open) return;
1089
+ event.preventDefault();
1090
+ setActive(stepActive(rows, rows.length, -1, false));
1091
+ break;
1092
+ case "Enter": {
1093
+ if (!open) return;
1094
+ const option = rows[active];
1095
+ if (option && !option.disabled) {
1096
+ event.preventDefault();
1097
+ toggle(option);
1098
+ }
1099
+ break;
1100
+ }
1101
+ case "Escape":
1102
+ if (!open) return;
1103
+ event.preventDefault();
1104
+ setOpen(false);
1105
+ break;
1106
+ case "Backspace":
1107
+ if (multiple && query === "" && selected.length > 0) {
1108
+ remove(selected[selected.length - 1]);
1109
+ }
1110
+ break;
1111
+ }
1112
+ };
1113
+ useEffect(() => {
1114
+ if (!open) return;
1115
+ document.getElementById(`${listId}-option-${active}`)?.scrollIntoView?.({ block: "nearest" });
1116
+ }, [active, listId, open]);
1117
+ const chips = multiple ? selected : [];
1118
+ const overflow = Math.max(0, chips.length - maxVisibleChips);
1119
+ const shownChips = overflow > 0 ? chips.slice(0, maxVisibleChips) : chips;
1120
+ const singleLabel = !multiple && selected[0] ? byValue.get(selected[0])?.label ?? "" : "";
1121
+ const inputValue = open || multiple ? query : singleLabel;
1122
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-combobox", `ansh-combobox--${size}`, className), children: [
1123
+ /* @__PURE__ */ jsxs(
1124
+ "div",
1125
+ {
1126
+ ref: wrapRef,
1127
+ className: cx(
1128
+ "ansh-combobox__control",
1129
+ "ansh-focusable-within",
1130
+ open && "ansh-combobox__control--open",
1131
+ (invalid || fieldProps["aria-invalid"]) && "ansh-combobox__control--invalid",
1132
+ disabled && "ansh-combobox__control--disabled"
1133
+ ),
1134
+ onMouseDown: (event) => {
1135
+ if (event.target === event.currentTarget) {
1136
+ event.preventDefault();
1137
+ inputRef.current?.focus();
1138
+ setOpen(true);
1139
+ }
1140
+ },
1141
+ children: [
1142
+ shownChips.map((chipValue) => {
1143
+ const option = byValue.get(chipValue);
1144
+ return /* @__PURE__ */ jsxs("span", { className: "ansh-chip", children: [
1145
+ option?.icon ? /* @__PURE__ */ jsx("span", { className: "ansh-chip__icon", children: option.icon }) : null,
1146
+ /* @__PURE__ */ jsx("span", { className: "ansh-chip__label", children: option?.label ?? chipValue }),
1147
+ /* @__PURE__ */ jsx(
1148
+ "button",
1149
+ {
1150
+ type: "button",
1151
+ className: "ansh-chip__remove",
1152
+ "aria-label": `Remove ${option?.label ?? chipValue}`,
1153
+ disabled,
1154
+ onClick: () => remove(chipValue),
1155
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "10", height: "10", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1156
+ "path",
1157
+ {
1158
+ d: "M4 4l8 8M12 4l-8 8",
1159
+ stroke: "currentColor",
1160
+ strokeWidth: "2",
1161
+ strokeLinecap: "round",
1162
+ fill: "none"
1163
+ }
1164
+ ) })
1165
+ }
1166
+ )
1167
+ ] }, chipValue);
1168
+ }),
1169
+ overflow > 0 ? /* @__PURE__ */ jsxs("span", { className: "ansh-chip ansh-chip--count", children: [
1170
+ "+",
1171
+ overflow
1172
+ ] }) : null,
1173
+ /* @__PURE__ */ jsx(
1174
+ "input",
1175
+ {
1176
+ ref: inputRef,
1177
+ type: "text",
1178
+ role: "combobox",
1179
+ className: "ansh-combobox__input",
1180
+ value: inputValue,
1181
+ placeholder: chips.length > 0 ? "" : placeholder,
1182
+ disabled: disabled || fieldProps.disabled,
1183
+ autoComplete: "off",
1184
+ spellCheck: false,
1185
+ "aria-expanded": open,
1186
+ "aria-controls": open ? listId : void 0,
1187
+ "aria-autocomplete": "list",
1188
+ "aria-activedescendant": open && rows[active] ? `${listId}-option-${active}` : void 0,
1189
+ ...label ? { "aria-label": label } : null,
1190
+ ...fieldProps,
1191
+ onChange: (event) => {
1192
+ setQuery(event.target.value);
1193
+ setOpen(true);
1194
+ onSearch?.(event.target.value);
1195
+ },
1196
+ onFocus: () => setOpen(true),
1197
+ onKeyDown
1198
+ }
1199
+ ),
1200
+ /* @__PURE__ */ jsx("span", { className: "ansh-select__arrow", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "12", height: "12", children: /* @__PURE__ */ jsx(
1201
+ "path",
1202
+ {
1203
+ d: "M3 4.5L6 7.5l3-3",
1204
+ stroke: "currentColor",
1205
+ strokeWidth: "1.5",
1206
+ fill: "none",
1207
+ strokeLinecap: "round",
1208
+ strokeLinejoin: "round"
1209
+ }
1210
+ ) }) })
1211
+ ]
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", "aria-live": "polite", children: open ? `${visible.length} result${visible.length === 1 ? "" : "s"} available` : "" }),
1215
+ open ? /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
1216
+ "div",
1217
+ {
1218
+ ref: listRef,
1219
+ id: listId,
1220
+ role: "listbox",
1221
+ "aria-multiselectable": multiple || void 0,
1222
+ ...label ? { "aria-label": label } : null,
1223
+ className: "ansh-listbox",
1224
+ style: { ...position.style, maxHeight },
1225
+ "data-side": position.side,
1226
+ children: /* @__PURE__ */ jsx(
1227
+ OptionList,
1228
+ {
1229
+ options: rows,
1230
+ active,
1231
+ selected,
1232
+ listId,
1233
+ multiple,
1234
+ query,
1235
+ onSelect: toggle,
1236
+ onActiveChange: setActive,
1237
+ ...emptyMessage !== void 0 ? { emptyMessage } : null
1238
+ }
1239
+ )
1240
+ }
1241
+ ) }) : null
1242
+ ] });
1243
+ }
1244
+ function snap(value, min, max, step) {
1245
+ if (step <= 0) return Math.min(Math.max(value, min), max);
1246
+ const stepped = Math.round((value - min) / step) * step + min;
1247
+ const decimals = String(step).includes(".") ? String(step).split(".")[1].length : 0;
1248
+ return Number(Math.min(Math.max(stepped, min), max).toFixed(decimals));
1249
+ }
1250
+ function Slider({
1251
+ value,
1252
+ defaultValue = 0,
1253
+ onChange,
1254
+ onCommit,
1255
+ min = 0,
1256
+ max = 100,
1257
+ step = 1,
1258
+ disabled = false,
1259
+ orientation = "horizontal",
1260
+ marks,
1261
+ showValue = false,
1262
+ formatValue = String,
1263
+ label,
1264
+ className,
1265
+ name
1266
+ }) {
1267
+ const fieldProps = useFieldControl();
1268
+ const [current, setCurrent] = useControllableState({ value, defaultValue, onChange });
1269
+ const trackRef = useRef(null);
1270
+ const [dragging, setDragging] = useState(false);
1271
+ const id = useId();
1272
+ const percent = (current - min) / (max - min) * 100;
1273
+ const valueFromPointer = useCallback(
1274
+ (event) => {
1275
+ const track = trackRef.current;
1276
+ if (!track) return current;
1277
+ const rect = track.getBoundingClientRect();
1278
+ const fraction = orientation === "horizontal" ? (event.clientX - rect.left) / rect.width : (
1279
+ // Inverted: in a vertical slider, up is more.
1280
+ 1 - (event.clientY - rect.top) / rect.height
1281
+ );
1282
+ return snap(min + fraction * (max - min), min, max, step);
1283
+ },
1284
+ [current, min, max, step, orientation]
1285
+ );
1286
+ const onPointerDown = (event) => {
1287
+ if (disabled) return;
1288
+ event.preventDefault();
1289
+ event.currentTarget.setPointerCapture(event.pointerId);
1290
+ setDragging(true);
1291
+ let latest = valueFromPointer(event);
1292
+ setCurrent(latest);
1293
+ const onMove = (moveEvent) => {
1294
+ latest = valueFromPointer(moveEvent);
1295
+ setCurrent(latest);
1296
+ };
1297
+ const onUp = () => {
1298
+ setDragging(false);
1299
+ onCommit?.(latest);
1300
+ window.removeEventListener("pointermove", onMove);
1301
+ window.removeEventListener("pointerup", onUp);
1302
+ window.removeEventListener("pointercancel", onUp);
1303
+ };
1304
+ window.addEventListener("pointermove", onMove);
1305
+ window.addEventListener("pointerup", onUp);
1306
+ window.addEventListener("pointercancel", onUp);
1307
+ };
1308
+ const onKeyDown = (event) => {
1309
+ if (disabled) return;
1310
+ const large = (max - min) / 10;
1311
+ let next = null;
1312
+ switch (event.key) {
1313
+ case "ArrowRight":
1314
+ case "ArrowUp":
1315
+ next = current + step;
1316
+ break;
1317
+ case "ArrowLeft":
1318
+ case "ArrowDown":
1319
+ next = current - step;
1320
+ break;
1321
+ case "PageUp":
1322
+ next = current + large;
1323
+ break;
1324
+ case "PageDown":
1325
+ next = current - large;
1326
+ break;
1327
+ case "Home":
1328
+ next = min;
1329
+ break;
1330
+ case "End":
1331
+ next = max;
1332
+ break;
1333
+ }
1334
+ if (next === null) return;
1335
+ event.preventDefault();
1336
+ const snapped = snap(next, min, max, step);
1337
+ setCurrent(snapped);
1338
+ onCommit?.(snapped);
1339
+ };
1340
+ return /* @__PURE__ */ jsxs(
1341
+ "div",
1342
+ {
1343
+ className: cx(
1344
+ "ansh-slider",
1345
+ `ansh-slider--${orientation}`,
1346
+ disabled && "ansh-slider--disabled",
1347
+ dragging && "ansh-slider--dragging",
1348
+ className
1349
+ ),
1350
+ style: { ["--ansh-slider-percent"]: `${percent}%` },
1351
+ children: [
1352
+ /* @__PURE__ */ jsxs("div", { ref: trackRef, className: "ansh-slider__track", onPointerDown, children: [
1353
+ /* @__PURE__ */ jsx("div", { className: "ansh-slider__fill" }),
1354
+ marks?.map((mark) => {
1355
+ const markPercent = (mark.value - min) / (max - min) * 100;
1356
+ return /* @__PURE__ */ jsx(
1357
+ "span",
1358
+ {
1359
+ className: cx(
1360
+ "ansh-slider__mark",
1361
+ current >= mark.value && "ansh-slider__mark--passed"
1362
+ ),
1363
+ style: { ["--ansh-mark"]: `${markPercent}%` },
1364
+ children: mark.label ? /* @__PURE__ */ jsx("span", { className: "ansh-slider__mark-label", children: mark.label }) : null
1365
+ },
1366
+ mark.value
1367
+ );
1368
+ }),
1369
+ /* @__PURE__ */ jsx(
1370
+ "div",
1371
+ {
1372
+ role: "slider",
1373
+ tabIndex: disabled ? -1 : 0,
1374
+ className: "ansh-slider__thumb ansh-focusable",
1375
+ "aria-valuenow": current,
1376
+ "aria-valuemin": min,
1377
+ "aria-valuemax": max,
1378
+ ...formatValue(current) !== String(current) ? { "aria-valuetext": formatValue(current) } : null,
1379
+ "aria-orientation": orientation,
1380
+ "aria-disabled": disabled || void 0,
1381
+ ...label ? { "aria-label": label } : null,
1382
+ ...fieldProps.id ? { "aria-labelledby": void 0, id: fieldProps.id } : null,
1383
+ ...fieldProps["aria-describedby"] ? { "aria-describedby": fieldProps["aria-describedby"] } : null,
1384
+ onPointerDown,
1385
+ onKeyDown,
1386
+ children: showValue === "always" || showValue && dragging ? /* @__PURE__ */ jsx("span", { className: "ansh-slider__bubble", "aria-hidden": "true", children: formatValue(current) }) : null
1387
+ }
1388
+ )
1389
+ ] }),
1390
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current, id: `${id}-hidden` }) : null
1391
+ ]
1392
+ }
1393
+ );
1394
+ }
1395
+ function RangeSlider({
1396
+ value,
1397
+ defaultValue = [0, 100],
1398
+ onChange,
1399
+ onCommit,
1400
+ min = 0,
1401
+ max = 100,
1402
+ step = 1,
1403
+ minDistance = 0,
1404
+ disabled = false,
1405
+ marks,
1406
+ showValue = false,
1407
+ formatValue = String,
1408
+ label,
1409
+ className
1410
+ }) {
1411
+ const [range, setRange] = useControllableState({
1412
+ value,
1413
+ defaultValue,
1414
+ onChange
1415
+ });
1416
+ const trackRef = useRef(null);
1417
+ const [dragging, setDragging] = useState(null);
1418
+ const [low, high] = range;
1419
+ const lowPercent = (low - min) / (max - min) * 100;
1420
+ const highPercent = (high - min) / (max - min) * 100;
1421
+ const valueFromPointer = (event) => {
1422
+ const track = trackRef.current;
1423
+ if (!track) return low;
1424
+ const rect = track.getBoundingClientRect();
1425
+ return snap(min + (event.clientX - rect.left) / rect.width * (max - min), min, max, step);
1426
+ };
1427
+ const apply = (thumb2, next) => {
1428
+ if (thumb2 === 0) {
1429
+ return next > high - minDistance ? [high - minDistance, high] : [next, high];
1430
+ }
1431
+ return next < low + minDistance ? [low, low + minDistance] : [low, next];
1432
+ };
1433
+ const startDrag = (thumb2) => (event) => {
1434
+ if (disabled) return;
1435
+ event.preventDefault();
1436
+ event.currentTarget.setPointerCapture(event.pointerId);
1437
+ setDragging(thumb2);
1438
+ let latest = range;
1439
+ const onMove = (moveEvent) => {
1440
+ latest = apply(thumb2, valueFromPointer(moveEvent));
1441
+ setRange(latest);
1442
+ };
1443
+ const onUp = () => {
1444
+ setDragging(null);
1445
+ onCommit?.(latest);
1446
+ window.removeEventListener("pointermove", onMove);
1447
+ window.removeEventListener("pointerup", onUp);
1448
+ };
1449
+ window.addEventListener("pointermove", onMove);
1450
+ window.addEventListener("pointerup", onUp);
1451
+ };
1452
+ const keyHandler = (thumb2) => (event) => {
1453
+ if (disabled) return;
1454
+ const currentValue = range[thumb2];
1455
+ const large = (max - min) / 10;
1456
+ let next = null;
1457
+ switch (event.key) {
1458
+ case "ArrowRight":
1459
+ case "ArrowUp":
1460
+ next = currentValue + step;
1461
+ break;
1462
+ case "ArrowLeft":
1463
+ case "ArrowDown":
1464
+ next = currentValue - step;
1465
+ break;
1466
+ case "PageUp":
1467
+ next = currentValue + large;
1468
+ break;
1469
+ case "PageDown":
1470
+ next = currentValue - large;
1471
+ break;
1472
+ case "Home":
1473
+ next = min;
1474
+ break;
1475
+ case "End":
1476
+ next = max;
1477
+ break;
1478
+ }
1479
+ if (next === null) return;
1480
+ event.preventDefault();
1481
+ const applied = apply(thumb2, snap(next, min, max, step));
1482
+ setRange(applied);
1483
+ onCommit?.(applied);
1484
+ };
1485
+ const thumb = (index, thumbLabel) => /* @__PURE__ */ jsx(
1486
+ "div",
1487
+ {
1488
+ role: "slider",
1489
+ tabIndex: disabled ? -1 : 0,
1490
+ className: "ansh-slider__thumb ansh-focusable",
1491
+ style: { ["--ansh-thumb"]: `${index === 0 ? lowPercent : highPercent}%` },
1492
+ "aria-label": label ? `${label} ${thumbLabel}` : thumbLabel,
1493
+ "aria-valuenow": range[index],
1494
+ "aria-valuemin": index === 0 ? min : low + minDistance,
1495
+ "aria-valuemax": index === 0 ? high - minDistance : max,
1496
+ ...formatValue(range[index]) !== String(range[index]) ? { "aria-valuetext": formatValue(range[index]) } : null,
1497
+ "aria-disabled": disabled || void 0,
1498
+ onPointerDown: startDrag(index),
1499
+ onKeyDown: keyHandler(index),
1500
+ children: showValue === "always" || showValue && dragging === index ? /* @__PURE__ */ jsx("span", { className: "ansh-slider__bubble", "aria-hidden": "true", children: formatValue(range[index]) }) : null
1501
+ },
1502
+ index
1503
+ );
1504
+ return /* @__PURE__ */ jsx(
1505
+ "div",
1506
+ {
1507
+ className: cx(
1508
+ "ansh-slider",
1509
+ "ansh-slider--horizontal",
1510
+ "ansh-slider--range",
1511
+ disabled && "ansh-slider--disabled",
1512
+ className
1513
+ ),
1514
+ style: {
1515
+ ["--ansh-range-start"]: `${lowPercent}%`,
1516
+ ["--ansh-range-end"]: `${highPercent}%`
1517
+ },
1518
+ children: /* @__PURE__ */ jsxs("div", { ref: trackRef, className: "ansh-slider__track", children: [
1519
+ /* @__PURE__ */ jsx("div", { className: "ansh-slider__fill ansh-slider__fill--range" }),
1520
+ marks?.map((mark) => /* @__PURE__ */ jsx(
1521
+ "span",
1522
+ {
1523
+ className: cx(
1524
+ "ansh-slider__mark",
1525
+ mark.value >= low && mark.value <= high && "ansh-slider__mark--passed"
1526
+ ),
1527
+ style: { ["--ansh-mark"]: `${(mark.value - min) / (max - min) * 100}%` },
1528
+ children: mark.label ? /* @__PURE__ */ jsx("span", { className: "ansh-slider__mark-label", children: mark.label }) : null
1529
+ },
1530
+ mark.value
1531
+ )),
1532
+ thumb(0, "minimum"),
1533
+ thumb(1, "maximum")
1534
+ ] })
1535
+ }
1536
+ );
1537
+ }
1538
+ function PinInput({
1539
+ length = 6,
1540
+ value,
1541
+ defaultValue = "",
1542
+ onChange,
1543
+ onComplete,
1544
+ type = "numeric",
1545
+ mask = false,
1546
+ disabled = false,
1547
+ invalid = false,
1548
+ autoFocus = false,
1549
+ label = "Verification code",
1550
+ className
1551
+ }) {
1552
+ const [code, setCode] = useControllableState({ value, defaultValue, onChange });
1553
+ const inputsRef = useRef([]);
1554
+ const id = useId();
1555
+ const pattern = type === "numeric" ? /[^0-9]/g : /[^a-zA-Z0-9]/g;
1556
+ const completedRef = useRef(false);
1557
+ useEffect(() => {
1558
+ if (code.length === length && !completedRef.current) {
1559
+ completedRef.current = true;
1560
+ onComplete?.(code);
1561
+ } else if (code.length < length) {
1562
+ completedRef.current = false;
1563
+ }
1564
+ }, [code, length, onComplete]);
1565
+ const setCharAt = (index, char) => {
1566
+ const chars = code.padEnd(length, " ").split("");
1567
+ chars[index] = char;
1568
+ setCode(chars.join("").trimEnd());
1569
+ };
1570
+ const focusCell = (index) => {
1571
+ const target = inputsRef.current[Math.min(Math.max(index, 0), length - 1)];
1572
+ target?.focus();
1573
+ target?.select();
1574
+ };
1575
+ const onCellChange = (index, raw) => {
1576
+ const cleaned = raw.replace(pattern, "");
1577
+ if (cleaned === "") {
1578
+ setCharAt(index, " ");
1579
+ return;
1580
+ }
1581
+ if (cleaned.length > 1) {
1582
+ const chars = code.padEnd(length, " ").split("");
1583
+ for (let i = 0; i < cleaned.length && index + i < length; i++) {
1584
+ chars[index + i] = cleaned[i];
1585
+ }
1586
+ setCode(chars.join("").trimEnd());
1587
+ focusCell(index + cleaned.length);
1588
+ return;
1589
+ }
1590
+ setCharAt(index, cleaned);
1591
+ focusCell(index + 1);
1592
+ };
1593
+ const onKeyDown = (index) => (event) => {
1594
+ switch (event.key) {
1595
+ case "Backspace": {
1596
+ event.preventDefault();
1597
+ if ((code[index] ?? " ").trim() === "") {
1598
+ setCharAt(index - 1, " ");
1599
+ focusCell(index - 1);
1600
+ } else {
1601
+ setCharAt(index, " ");
1602
+ }
1603
+ break;
1604
+ }
1605
+ case "Delete":
1606
+ event.preventDefault();
1607
+ setCharAt(index, " ");
1608
+ break;
1609
+ case "ArrowLeft":
1610
+ event.preventDefault();
1611
+ focusCell(index - 1);
1612
+ break;
1613
+ case "ArrowRight":
1614
+ event.preventDefault();
1615
+ focusCell(index + 1);
1616
+ break;
1617
+ case "Home":
1618
+ event.preventDefault();
1619
+ focusCell(0);
1620
+ break;
1621
+ case "End":
1622
+ event.preventDefault();
1623
+ focusCell(length - 1);
1624
+ break;
1625
+ }
1626
+ };
1627
+ return /* @__PURE__ */ jsxs(
1628
+ "div",
1629
+ {
1630
+ className: cx(
1631
+ "ansh-pin",
1632
+ invalid && "ansh-pin--invalid",
1633
+ disabled && "ansh-pin--disabled",
1634
+ className
1635
+ ),
1636
+ role: "group",
1637
+ "aria-label": label,
1638
+ "aria-describedby": `${id}-status`,
1639
+ children: [
1640
+ Array.from({ length }, (_, index) => /* @__PURE__ */ jsx(
1641
+ "input",
1642
+ {
1643
+ ref: (node) => {
1644
+ inputsRef.current[index] = node;
1645
+ },
1646
+ className: "ansh-pin__cell ansh-focusable",
1647
+ type: mask ? "password" : "text",
1648
+ inputMode: type === "numeric" ? "numeric" : "text",
1649
+ autoComplete: index === 0 ? "one-time-code" : "off",
1650
+ autoFocus: autoFocus && index === 0,
1651
+ maxLength: length,
1652
+ disabled,
1653
+ "aria-label": `Digit ${index + 1} of ${length}`,
1654
+ "aria-invalid": invalid || void 0,
1655
+ value: (code[index] ?? "").trim(),
1656
+ onChange: (event) => onCellChange(index, event.target.value),
1657
+ onKeyDown: onKeyDown(index),
1658
+ onFocus: (event) => event.target.select()
1659
+ },
1660
+ index
1661
+ )),
1662
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", id: `${id}-status`, "aria-live": "polite", children: code.trim().length === length ? "Code complete" : `${code.trim().length} of ${length} digits entered` })
1663
+ ]
1664
+ }
1665
+ );
1666
+ }
1667
+ function TagsInput({
1668
+ value,
1669
+ defaultValue = [],
1670
+ onChange,
1671
+ placeholder = "Add and press Enter",
1672
+ max = Number.POSITIVE_INFINITY,
1673
+ delimiters = ["Enter", "Tab", ","],
1674
+ validate,
1675
+ dedupe = true,
1676
+ disabled = false,
1677
+ invalid = false,
1678
+ label = "Tags",
1679
+ className
1680
+ }) {
1681
+ const fieldProps = useFieldControl();
1682
+ const [tags, setTags] = useControllableState({
1683
+ value,
1684
+ defaultValue,
1685
+ onChange
1686
+ });
1687
+ const [draft, setDraft] = useState("");
1688
+ const [error, setError] = useState(null);
1689
+ const inputRef = useRef(null);
1690
+ const tagRefs = useRef([]);
1691
+ const commit = (raw) => {
1692
+ const text = raw.trim();
1693
+ if (text === "") return false;
1694
+ if (tags.length >= max) {
1695
+ setError(`Up to ${max} allowed`);
1696
+ return false;
1697
+ }
1698
+ if (dedupe && tags.includes(text)) {
1699
+ setDraft("");
1700
+ return true;
1701
+ }
1702
+ const failure = validate?.(text);
1703
+ if (failure) {
1704
+ setError(failure);
1705
+ return false;
1706
+ }
1707
+ setTags([...tags, text]);
1708
+ setDraft("");
1709
+ setError(null);
1710
+ return true;
1711
+ };
1712
+ const removeAt = (index) => {
1713
+ setTags(tags.filter((_, i) => i !== index));
1714
+ setError(null);
1715
+ queueMicrotask(() => {
1716
+ const next = tagRefs.current[index] ?? tagRefs.current[index - 1];
1717
+ if (next?.isConnected) next.focus();
1718
+ else inputRef.current?.focus();
1719
+ });
1720
+ };
1721
+ const onKeyDown = (event) => {
1722
+ if (event.key === "Tab" && draft.trim() === "") return;
1723
+ if (delimiters.includes(event.key)) {
1724
+ event.preventDefault();
1725
+ commit(draft);
1726
+ return;
1727
+ }
1728
+ if (event.key === "Backspace" && draft === "" && tags.length > 0) {
1729
+ event.preventDefault();
1730
+ removeAt(tags.length - 1);
1731
+ }
1732
+ };
1733
+ const atLimit = tags.length >= max;
1734
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-tags", className), children: [
1735
+ /* @__PURE__ */ jsxs(
1736
+ "div",
1737
+ {
1738
+ className: cx(
1739
+ "ansh-tags__control",
1740
+ "ansh-focusable-within",
1741
+ (invalid || error) && "ansh-tags__control--invalid",
1742
+ disabled && "ansh-tags__control--disabled"
1743
+ ),
1744
+ onMouseDown: (event) => {
1745
+ if (event.target === event.currentTarget) {
1746
+ event.preventDefault();
1747
+ inputRef.current?.focus();
1748
+ }
1749
+ },
1750
+ children: [
1751
+ /* @__PURE__ */ jsx("ul", { className: "ansh-tags__list", "aria-label": `${label}: selected`, children: tags.map((tag, index) => /* @__PURE__ */ jsxs("li", { className: "ansh-chip", children: [
1752
+ /* @__PURE__ */ jsx("span", { className: "ansh-chip__label", children: tag }),
1753
+ /* @__PURE__ */ jsx(
1754
+ "button",
1755
+ {
1756
+ ref: (node) => {
1757
+ tagRefs.current[index] = node;
1758
+ },
1759
+ type: "button",
1760
+ className: "ansh-chip__remove",
1761
+ "aria-label": `Remove ${tag}`,
1762
+ disabled,
1763
+ onClick: () => removeAt(index),
1764
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "10", height: "10", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1765
+ "path",
1766
+ {
1767
+ d: "M4 4l8 8M12 4l-8 8",
1768
+ stroke: "currentColor",
1769
+ strokeWidth: "2",
1770
+ strokeLinecap: "round",
1771
+ fill: "none"
1772
+ }
1773
+ ) })
1774
+ }
1775
+ )
1776
+ ] }, `${tag}-${index}`)) }),
1777
+ !atLimit ? /* @__PURE__ */ jsx(
1778
+ "input",
1779
+ {
1780
+ ref: inputRef,
1781
+ type: "text",
1782
+ className: "ansh-tags__input",
1783
+ value: draft,
1784
+ placeholder: tags.length === 0 ? placeholder : "",
1785
+ disabled,
1786
+ "aria-label": label,
1787
+ "aria-invalid": invalid || Boolean(error) || void 0,
1788
+ ...fieldProps,
1789
+ onChange: (event) => {
1790
+ setDraft(event.target.value);
1791
+ setError(null);
1792
+ },
1793
+ onKeyDown,
1794
+ onBlur: () => commit(draft),
1795
+ onPaste: (event) => {
1796
+ const text = event.clipboardData.getData("text");
1797
+ if (!/[,\n\t]/.test(text)) return;
1798
+ event.preventDefault();
1799
+ for (const part of text.split(/[,\n\t]+/)) commit(part);
1800
+ }
1801
+ }
1802
+ ) : null
1803
+ ]
1804
+ }
1805
+ ),
1806
+ error ? /* @__PURE__ */ jsx("p", { className: "ansh-field__error", role: "alert", children: error }) : null
1807
+ ] });
1808
+ }
1809
+ function Rating({
1810
+ value,
1811
+ defaultValue = 0,
1812
+ onChange,
1813
+ max = 5,
1814
+ allowHalf = false,
1815
+ readOnly = false,
1816
+ disabled = false,
1817
+ size = "md",
1818
+ icon,
1819
+ label = "Rating",
1820
+ className,
1821
+ name
1822
+ }) {
1823
+ const [current, setCurrent] = useControllableState({ value, defaultValue, onChange });
1824
+ const [preview, setPreview] = useState(null);
1825
+ const groupName = useId();
1826
+ const shown = preview ?? current;
1827
+ const symbol = icon ?? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", className: "ansh-rating__glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
1828
+ "path",
1829
+ {
1830
+ d: "M10 1.6l2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8L1.6 7.7l5.8-.8z",
1831
+ fill: "currentColor"
1832
+ }
1833
+ ) });
1834
+ if (readOnly) {
1835
+ return /* @__PURE__ */ jsx(
1836
+ "span",
1837
+ {
1838
+ className: cx("ansh-rating", `ansh-rating--${size}`, "ansh-rating--readonly", className),
1839
+ role: "img",
1840
+ "aria-label": `${current} out of ${max}`,
1841
+ children: Array.from({ length: max }, (_, index) => /* @__PURE__ */ jsx(
1842
+ "span",
1843
+ {
1844
+ className: "ansh-rating__item",
1845
+ style: {
1846
+ ["--ansh-fill"]: `${Math.min(Math.max(current - index, 0), 1) * 100}%`
1847
+ },
1848
+ children: symbol
1849
+ },
1850
+ index
1851
+ ))
1852
+ }
1853
+ );
1854
+ }
1855
+ const steps = allowHalf ? max * 2 : max;
1856
+ return /* @__PURE__ */ jsx(
1857
+ "span",
1858
+ {
1859
+ role: "radiogroup",
1860
+ "aria-label": label,
1861
+ className: cx(
1862
+ "ansh-rating",
1863
+ `ansh-rating--${size}`,
1864
+ disabled && "ansh-rating--disabled",
1865
+ className
1866
+ ),
1867
+ onMouseLeave: () => setPreview(null),
1868
+ children: Array.from({ length: steps }, (_, step) => {
1869
+ const stepValue = allowHalf ? (step + 1) / 2 : step + 1;
1870
+ const index = Math.ceil(stepValue) - 1;
1871
+ const isHalf = allowHalf && stepValue % 1 !== 0;
1872
+ return /* @__PURE__ */ jsxs(
1873
+ "label",
1874
+ {
1875
+ className: cx("ansh-rating__hit", isHalf && "ansh-rating__hit--half"),
1876
+ onMouseEnter: () => !disabled && setPreview(stepValue),
1877
+ children: [
1878
+ /* @__PURE__ */ jsx(
1879
+ "input",
1880
+ {
1881
+ type: "radio",
1882
+ className: "ansh-visually-hidden",
1883
+ name: name ?? groupName,
1884
+ value: stepValue,
1885
+ checked: current === stepValue,
1886
+ disabled,
1887
+ "aria-label": `${stepValue} ${stepValue === 1 ? "star" : "stars"}`,
1888
+ onChange: () => setCurrent(stepValue)
1889
+ }
1890
+ ),
1891
+ !isHalf ? /* @__PURE__ */ jsx(
1892
+ "span",
1893
+ {
1894
+ className: "ansh-rating__item",
1895
+ style: {
1896
+ ["--ansh-fill"]: `${Math.min(Math.max(shown - index, 0), 1) * 100}%`
1897
+ },
1898
+ children: symbol
1899
+ }
1900
+ ) : null
1901
+ ]
1902
+ },
1903
+ stepValue
1904
+ );
1905
+ })
1906
+ }
1907
+ );
1908
+ }
1909
+ function formatBytes(bytes) {
1910
+ if (bytes < 1024) return `${bytes} B`;
1911
+ const units = ["KB", "MB", "GB"];
1912
+ let size = bytes / 1024;
1913
+ let unit = 0;
1914
+ while (size >= 1024 && unit < units.length - 1) {
1915
+ size /= 1024;
1916
+ unit++;
1917
+ }
1918
+ return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`;
1919
+ }
1920
+ function Dropzone({
1921
+ onFiles,
1922
+ accept,
1923
+ multiple = false,
1924
+ maxSize,
1925
+ maxFiles = Number.POSITIVE_INFINITY,
1926
+ disabled = false,
1927
+ previews = true,
1928
+ label = "Drop files here, or browse",
1929
+ hint,
1930
+ className,
1931
+ children
1932
+ }) {
1933
+ const [files, setFiles] = useState([]);
1934
+ const [dragging, setDragging] = useState(false);
1935
+ const depthRef = useRef(0);
1936
+ const inputRef = useRef(null);
1937
+ const id = useId();
1938
+ const validate = useCallback(
1939
+ (file) => {
1940
+ if (maxSize !== void 0 && file.size > maxSize) {
1941
+ return `Larger than ${formatBytes(maxSize)}`;
1942
+ }
1943
+ if (accept) {
1944
+ const patterns = accept.split(",").map((part) => part.trim().toLowerCase());
1945
+ const name = file.name.toLowerCase();
1946
+ const type = file.type.toLowerCase();
1947
+ const ok = patterns.some((pattern) => {
1948
+ if (pattern.startsWith(".")) return name.endsWith(pattern);
1949
+ if (pattern.endsWith("/*")) return type.startsWith(pattern.slice(0, -1));
1950
+ return type === pattern;
1951
+ });
1952
+ if (!ok) return "Unsupported file type";
1953
+ }
1954
+ return void 0;
1955
+ },
1956
+ [accept, maxSize]
1957
+ );
1958
+ const add = useCallback(
1959
+ (incoming) => {
1960
+ if (!incoming || disabled) return;
1961
+ const accepted = [];
1962
+ const room = maxFiles - files.length;
1963
+ for (const file of Array.from(incoming).slice(0, multiple ? room : 1)) {
1964
+ const error = validate(file);
1965
+ accepted.push({
1966
+ file,
1967
+ id: `${file.name}-${file.size}-${file.lastModified}`,
1968
+ ...previews && file.type.startsWith("image/") && !error ? { preview: URL.createObjectURL(file) } : null,
1969
+ ...error ? { error } : null
1970
+ });
1971
+ }
1972
+ const next = multiple ? [...files, ...accepted] : accepted;
1973
+ if (!multiple)
1974
+ for (const previous of files) {
1975
+ if (previous.preview) URL.revokeObjectURL(previous.preview);
1976
+ }
1977
+ setFiles(next);
1978
+ onFiles?.(next);
1979
+ },
1980
+ [disabled, files, maxFiles, multiple, onFiles, previews, validate]
1981
+ );
1982
+ const remove = (fileId) => {
1983
+ const target = files.find((entry) => entry.id === fileId);
1984
+ if (target?.preview) URL.revokeObjectURL(target.preview);
1985
+ const next = files.filter((entry) => entry.id !== fileId);
1986
+ setFiles(next);
1987
+ onFiles?.(next);
1988
+ if (inputRef.current) inputRef.current.value = "";
1989
+ };
1990
+ const rejected = files.filter((entry) => entry.error);
1991
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-dropzone-wrap", className), children: [
1992
+ /* @__PURE__ */ jsxs(
1993
+ "div",
1994
+ {
1995
+ className: cx(
1996
+ "ansh-dropzone",
1997
+ dragging && "ansh-dropzone--active",
1998
+ disabled && "ansh-dropzone--disabled"
1999
+ ),
2000
+ onDragEnter: (event) => {
2001
+ event.preventDefault();
2002
+ depthRef.current++;
2003
+ if (!disabled) setDragging(true);
2004
+ },
2005
+ onDragOver: (event) => {
2006
+ event.preventDefault();
2007
+ event.dataTransfer.dropEffect = disabled ? "none" : "copy";
2008
+ },
2009
+ onDragLeave: (event) => {
2010
+ event.preventDefault();
2011
+ depthRef.current--;
2012
+ if (depthRef.current <= 0) {
2013
+ depthRef.current = 0;
2014
+ setDragging(false);
2015
+ }
2016
+ },
2017
+ onDrop: (event) => {
2018
+ event.preventDefault();
2019
+ depthRef.current = 0;
2020
+ setDragging(false);
2021
+ add(event.dataTransfer.files);
2022
+ },
2023
+ children: [
2024
+ /* @__PURE__ */ jsx(
2025
+ "input",
2026
+ {
2027
+ ref: inputRef,
2028
+ id,
2029
+ type: "file",
2030
+ className: "ansh-dropzone__input",
2031
+ accept,
2032
+ multiple,
2033
+ disabled,
2034
+ onChange: (event) => add(event.target.files)
2035
+ }
2036
+ ),
2037
+ /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "ansh-dropzone__label", children: [
2038
+ /* @__PURE__ */ jsx("span", { className: "ansh-dropzone__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxs(
2039
+ "svg",
2040
+ {
2041
+ viewBox: "0 0 24 24",
2042
+ width: "28",
2043
+ height: "28",
2044
+ fill: "none",
2045
+ stroke: "currentColor",
2046
+ strokeWidth: "1.5",
2047
+ children: [
2048
+ /* @__PURE__ */ jsx("path", { d: "M12 16V4m0 0L8 8m4-4l4 4", strokeLinecap: "round", strokeLinejoin: "round" }),
2049
+ /* @__PURE__ */ jsx("path", { d: "M3 15v3a3 3 0 003 3h12a3 3 0 003-3v-3", strokeLinecap: "round" })
2050
+ ]
2051
+ }
2052
+ ) }),
2053
+ /* @__PURE__ */ jsx("span", { className: "ansh-dropzone__text", children: label }),
2054
+ hint ? /* @__PURE__ */ jsx("span", { className: "ansh-dropzone__hint", children: hint }) : null
2055
+ ] }),
2056
+ children
2057
+ ]
2058
+ }
2059
+ ),
2060
+ files.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "ansh-dropzone__files", children: files.map((entry) => /* @__PURE__ */ jsxs(
2061
+ "li",
2062
+ {
2063
+ className: cx("ansh-dropzone__file", entry.error && "ansh-dropzone__file--error"),
2064
+ children: [
2065
+ entry.preview ? (
2066
+ // Decorative: the file name beside it is the accessible content.
2067
+ /* @__PURE__ */ jsx("img", { src: entry.preview, alt: "", className: "ansh-dropzone__thumb" })
2068
+ ) : /* @__PURE__ */ jsx(
2069
+ "span",
2070
+ {
2071
+ className: "ansh-dropzone__thumb ansh-dropzone__thumb--generic",
2072
+ "aria-hidden": "true",
2073
+ children: entry.file.name.split(".").pop()?.slice(0, 4).toUpperCase()
2074
+ }
2075
+ ),
2076
+ /* @__PURE__ */ jsxs("span", { className: "ansh-dropzone__meta", children: [
2077
+ /* @__PURE__ */ jsx("span", { className: "ansh-dropzone__name", children: entry.file.name }),
2078
+ /* @__PURE__ */ jsx("span", { className: "ansh-dropzone__size", children: entry.error ?? formatBytes(entry.file.size) })
2079
+ ] }),
2080
+ /* @__PURE__ */ jsx(
2081
+ "button",
2082
+ {
2083
+ type: "button",
2084
+ className: "ansh-dropzone__remove ansh-focusable",
2085
+ "aria-label": `Remove ${entry.file.name}`,
2086
+ onClick: () => remove(entry.id),
2087
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
2088
+ "path",
2089
+ {
2090
+ d: "M4 4l8 8M12 4l-8 8",
2091
+ stroke: "currentColor",
2092
+ strokeWidth: "1.75",
2093
+ strokeLinecap: "round",
2094
+ fill: "none"
2095
+ }
2096
+ ) })
2097
+ }
2098
+ )
2099
+ ]
2100
+ },
2101
+ entry.id
2102
+ )) }) : null,
2103
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", role: "status", "aria-live": "polite", children: rejected.length > 0 ? `${rejected.length} file${rejected.length === 1 ? "" : "s"} rejected` : files.length > 0 ? `${files.length} file${files.length === 1 ? "" : "s"} ready` : "" })
2104
+ ] });
2105
+ }
2106
+ function ColorPicker({
2107
+ value,
2108
+ defaultValue = "#0275f4",
2109
+ onChange,
2110
+ format = "hex",
2111
+ alpha = false,
2112
+ presets,
2113
+ contrastAgainst,
2114
+ disabled = false,
2115
+ label = "Colour",
2116
+ className
2117
+ }) {
2118
+ const [current, setCurrent] = useControllableState({ value, defaultValue, onChange });
2119
+ const [open, setOpen] = useState(false);
2120
+ const triggerRef = useRef(null);
2121
+ const panelRef = useRef(null);
2122
+ const planeRef = useRef(null);
2123
+ const id = useId();
2124
+ const parsed = useMemo(() => {
2125
+ const rgb = tryParseColor(current) ?? parseColor(defaultValue);
2126
+ return rgbToHsv(rgb);
2127
+ }, [current, defaultValue]);
2128
+ const hueRef = useRef(parsed.h);
2129
+ if (parsed.s > 1e-3 && parsed.v > 1e-3) hueRef.current = parsed.h;
2130
+ const hsv = {
2131
+ ...parsed,
2132
+ h: parsed.s < 1e-3 || parsed.v < 1e-3 ? hueRef.current : parsed.h
2133
+ };
2134
+ const emit = useCallback(
2135
+ (next) => {
2136
+ const rgb = hsvToRgb(next);
2137
+ switch (format) {
2138
+ case "rgb":
2139
+ setCurrent(
2140
+ next.a < 1 ? `rgba(${Math.round(rgb.r * 255)}, ${Math.round(rgb.g * 255)}, ${Math.round(rgb.b * 255)}, ${Number(next.a.toFixed(2))})` : `rgb(${Math.round(rgb.r * 255)}, ${Math.round(rgb.g * 255)}, ${Math.round(rgb.b * 255)})`
2141
+ );
2142
+ break;
2143
+ case "hsl":
2144
+ setCurrent(
2145
+ `hsl(${Math.round(next.h)} ${Math.round(next.s * 100)}% ${Math.round(next.v * (1 - next.s / 2) * 100)}%${next.a < 1 ? ` / ${Number(next.a.toFixed(2))}` : ""})`
2146
+ );
2147
+ break;
2148
+ case "oklch":
2149
+ setCurrent(toOklchString(rgb));
2150
+ break;
2151
+ default:
2152
+ setCurrent(toHex(rgb, next.a < 1));
2153
+ }
2154
+ },
2155
+ [format, setCurrent]
2156
+ );
2157
+ const position = useAnchoredPosition(triggerRef, panelRef, open, {
2158
+ placement: "bottom-start",
2159
+ offset: 8
2160
+ });
2161
+ useDismiss(panelRef, open, () => setOpen(false), { ignore: [triggerRef] });
2162
+ const dragPlane = (event) => {
2163
+ if (disabled) return;
2164
+ const plane = planeRef.current;
2165
+ if (!plane) return;
2166
+ event.currentTarget.setPointerCapture(event.pointerId);
2167
+ const apply = (clientX, clientY) => {
2168
+ const rect = plane.getBoundingClientRect();
2169
+ const s = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);
2170
+ const v = 1 - Math.min(Math.max((clientY - rect.top) / rect.height, 0), 1);
2171
+ emit({ ...hsv, s, v });
2172
+ };
2173
+ apply(event.clientX, event.clientY);
2174
+ const onMove = (moveEvent) => apply(moveEvent.clientX, moveEvent.clientY);
2175
+ const onUp = () => {
2176
+ window.removeEventListener("pointermove", onMove);
2177
+ window.removeEventListener("pointerup", onUp);
2178
+ };
2179
+ window.addEventListener("pointermove", onMove);
2180
+ window.addEventListener("pointerup", onUp);
2181
+ };
2182
+ const onPlaneKeyDown = (event) => {
2183
+ const step = event.shiftKey ? 0.1 : 0.02;
2184
+ let { s, v } = hsv;
2185
+ switch (event.key) {
2186
+ case "ArrowRight":
2187
+ s = Math.min(1, s + step);
2188
+ break;
2189
+ case "ArrowLeft":
2190
+ s = Math.max(0, s - step);
2191
+ break;
2192
+ case "ArrowUp":
2193
+ v = Math.min(1, v + step);
2194
+ break;
2195
+ case "ArrowDown":
2196
+ v = Math.max(0, v - step);
2197
+ break;
2198
+ default:
2199
+ return;
2200
+ }
2201
+ event.preventDefault();
2202
+ emit({ ...hsv, s, v });
2203
+ };
2204
+ const swatch = toHex(hsvToRgb(hsv), hsv.a < 1);
2205
+ const pureHue = toHex(hsvToRgb({ h: hsv.h, s: 1, v: 1, a: 1 }));
2206
+ const contrast = contrastAgainst ? contrastRatio(hsvToRgb(hsv), parseColor(contrastAgainst)) : null;
2207
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-colorpicker", className), children: [
2208
+ /* @__PURE__ */ jsxs(
2209
+ "button",
2210
+ {
2211
+ ref: triggerRef,
2212
+ type: "button",
2213
+ className: "ansh-colorpicker__trigger ansh-focusable",
2214
+ disabled,
2215
+ "aria-haspopup": "dialog",
2216
+ "aria-expanded": open,
2217
+ "aria-label": `${label}: ${swatch}`,
2218
+ onClick: () => setOpen((v) => !v),
2219
+ children: [
2220
+ /* @__PURE__ */ jsx(
2221
+ "span",
2222
+ {
2223
+ className: "ansh-colorpicker__swatch",
2224
+ style: { background: swatch },
2225
+ "aria-hidden": "true"
2226
+ }
2227
+ ),
2228
+ /* @__PURE__ */ jsx("span", { className: "ansh-colorpicker__value", children: current })
2229
+ ]
2230
+ }
2231
+ ),
2232
+ open ? /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
2233
+ "div",
2234
+ {
2235
+ ref: panelRef,
2236
+ role: "dialog",
2237
+ "aria-label": label,
2238
+ className: "ansh-colorpicker__panel",
2239
+ style: position.style,
2240
+ children: [
2241
+ /* @__PURE__ */ jsx(
2242
+ "div",
2243
+ {
2244
+ ref: planeRef,
2245
+ className: "ansh-colorpicker__plane",
2246
+ style: { ["--ansh-hue"]: pureHue },
2247
+ onPointerDown: dragPlane,
2248
+ children: /* @__PURE__ */ jsx(
2249
+ "div",
2250
+ {
2251
+ role: "slider",
2252
+ tabIndex: 0,
2253
+ className: "ansh-colorpicker__knob ansh-focusable",
2254
+ style: {
2255
+ left: `${hsv.s * 100}%`,
2256
+ top: `${(1 - hsv.v) * 100}%`,
2257
+ background: swatch
2258
+ },
2259
+ "aria-label": "Saturation and brightness",
2260
+ "aria-valuetext": `Saturation ${Math.round(hsv.s * 100)}%, brightness ${Math.round(hsv.v * 100)}%`,
2261
+ "aria-valuenow": Math.round(hsv.s * 100),
2262
+ "aria-valuemin": 0,
2263
+ "aria-valuemax": 100,
2264
+ onKeyDown: onPlaneKeyDown
2265
+ }
2266
+ )
2267
+ }
2268
+ ),
2269
+ /* @__PURE__ */ jsxs("div", { className: "ansh-colorpicker__rails", children: [
2270
+ /* @__PURE__ */ jsx(
2271
+ "div",
2272
+ {
2273
+ className: "ansh-colorpicker__preview",
2274
+ style: { background: swatch },
2275
+ "aria-hidden": "true"
2276
+ }
2277
+ ),
2278
+ /* @__PURE__ */ jsxs("div", { className: "ansh-colorpicker__rail-group", children: [
2279
+ /* @__PURE__ */ jsx(
2280
+ "input",
2281
+ {
2282
+ type: "range",
2283
+ className: "ansh-colorpicker__rail ansh-colorpicker__rail--hue ansh-focusable",
2284
+ min: 0,
2285
+ max: 360,
2286
+ step: 1,
2287
+ value: Math.round(hsv.h),
2288
+ "aria-label": "Hue",
2289
+ "aria-valuetext": `${Math.round(hsv.h)} degrees`,
2290
+ onChange: (event) => emit({ ...hsv, h: Number(event.target.value) })
2291
+ }
2292
+ ),
2293
+ alpha ? /* @__PURE__ */ jsx(
2294
+ "input",
2295
+ {
2296
+ type: "range",
2297
+ className: "ansh-colorpicker__rail ansh-colorpicker__rail--alpha ansh-focusable",
2298
+ style: { ["--ansh-solid"]: toHex(hsvToRgb({ ...hsv, a: 1 })) },
2299
+ min: 0,
2300
+ max: 100,
2301
+ step: 1,
2302
+ value: Math.round(hsv.a * 100),
2303
+ "aria-label": "Opacity",
2304
+ "aria-valuetext": `${Math.round(hsv.a * 100)} percent`,
2305
+ onChange: (event) => emit({ ...hsv, a: Number(event.target.value) / 100 })
2306
+ }
2307
+ ) : null
2308
+ ] })
2309
+ ] }),
2310
+ /* @__PURE__ */ jsx("div", { className: "ansh-colorpicker__row", children: /* @__PURE__ */ jsx(
2311
+ "input",
2312
+ {
2313
+ type: "text",
2314
+ className: "ansh-input ansh-input--sm ansh-colorpicker__text",
2315
+ value: current,
2316
+ "aria-label": `${label} value`,
2317
+ spellCheck: false,
2318
+ onChange: (event) => {
2319
+ const text = event.target.value;
2320
+ setCurrent(text);
2321
+ const rgb = tryParseColor(text);
2322
+ if (rgb) hueRef.current = rgbToHsv(rgb).h;
2323
+ }
2324
+ }
2325
+ ) }),
2326
+ contrast !== null ? /* @__PURE__ */ jsxs(
2327
+ "p",
2328
+ {
2329
+ className: cx(
2330
+ "ansh-colorpicker__contrast",
2331
+ contrast >= 4.5 && "ansh-colorpicker__contrast--pass",
2332
+ contrast < 3 && "ansh-colorpicker__contrast--fail"
2333
+ ),
2334
+ "aria-live": "polite",
2335
+ children: [
2336
+ contrast.toFixed(2),
2337
+ ":1 against ",
2338
+ contrastAgainst,
2339
+ " \u2014",
2340
+ " ",
2341
+ contrast >= 7 ? "AAA" : contrast >= 4.5 ? "AA" : contrast >= 3 ? "AA large only" : "fails"
2342
+ ]
2343
+ }
2344
+ ) : null,
2345
+ presets && presets.length > 0 ? /* @__PURE__ */ jsx("div", { className: "ansh-colorpicker__presets", role: "group", "aria-label": "Preset colours", children: presets.map((preset) => /* @__PURE__ */ jsx(
2346
+ "button",
2347
+ {
2348
+ type: "button",
2349
+ className: cx(
2350
+ "ansh-colorpicker__preset",
2351
+ preset.toLowerCase() === swatch.toLowerCase() && "ansh-colorpicker__preset--on"
2352
+ ),
2353
+ style: { background: preset },
2354
+ "aria-label": preset,
2355
+ "aria-pressed": preset.toLowerCase() === swatch.toLowerCase(),
2356
+ onClick: () => {
2357
+ const rgb = tryParseColor(preset);
2358
+ if (rgb) {
2359
+ hueRef.current = rgbToHsv(rgb).h;
2360
+ setCurrent(preset);
2361
+ }
2362
+ }
2363
+ },
2364
+ preset
2365
+ )) }) : null,
2366
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", "aria-live": "polite", id: `${id}-live`, children: swatch })
2367
+ ]
2368
+ }
2369
+ ) }) : null
2370
+ ] });
2371
+ }
2372
+ function startOfDay(date) {
2373
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
2374
+ }
2375
+ function addDays(date, days) {
2376
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
2377
+ }
2378
+ function addMonths(date, months) {
2379
+ const target = new Date(date.getFullYear(), date.getMonth() + months, 1);
2380
+ const lastDay = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
2381
+ return new Date(target.getFullYear(), target.getMonth(), Math.min(date.getDate(), lastDay));
2382
+ }
2383
+ function isSameDay(a, b) {
2384
+ if (!a || !b) return false;
2385
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2386
+ }
2387
+ function defaultWeekStart(locale) {
2388
+ const region = (locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-GB")).split("-")[1]?.toUpperCase();
2389
+ return region && ["US", "CA", "JP", "IL", "BR", "MX", "PH", "KR"].includes(region) ? 0 : 1;
2390
+ }
2391
+ function isoWeek(date) {
2392
+ const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2393
+ target.setDate(target.getDate() + 3 - (target.getDay() + 6) % 7);
2394
+ const firstThursday = new Date(target.getFullYear(), 0, 4);
2395
+ firstThursday.setDate(firstThursday.getDate() + 3 - (firstThursday.getDay() + 6) % 7);
2396
+ return 1 + Math.round((target.getTime() - firstThursday.getTime()) / (7 * 864e5));
2397
+ }
2398
+ function Calendar({
2399
+ mode = "single",
2400
+ value,
2401
+ defaultValue,
2402
+ onChange,
2403
+ min,
2404
+ max,
2405
+ isDateDisabled,
2406
+ weekStartsOn,
2407
+ locale,
2408
+ months = 1,
2409
+ showWeekNumbers = false,
2410
+ className
2411
+ }) {
2412
+ const [selection, setSelection] = useControllableState({
2413
+ value,
2414
+ defaultValue: defaultValue ?? (mode === "range" ? { start: null, end: null } : null),
2415
+ onChange
2416
+ });
2417
+ const single = mode === "single" ? selection : null;
2418
+ const range = mode === "range" ? selection : { start: null, end: null };
2419
+ const today = useMemo(() => startOfDay(/* @__PURE__ */ new Date()), []);
2420
+ const [viewMonth, setViewMonth] = useState(() => startOfDay(single ?? range.start ?? /* @__PURE__ */ new Date()));
2421
+ const [cursor, setCursor] = useState(() => startOfDay(single ?? range.start ?? /* @__PURE__ */ new Date()));
2422
+ const [hovered, setHovered] = useState(null);
2423
+ const gridRef = useRef(null);
2424
+ const shouldFocusRef = useRef(false);
2425
+ const firstWeekday = weekStartsOn ?? defaultWeekStart(locale);
2426
+ const dayNames = useMemo(() => {
2427
+ const formatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
2428
+ const longFormatter = new Intl.DateTimeFormat(locale, { weekday: "long" });
2429
+ return Array.from({ length: 7 }, (_, i) => {
2430
+ const date = new Date(2024, 0, 7 + (firstWeekday + i) % 7);
2431
+ return { short: formatter.format(date), long: longFormatter.format(date) };
2432
+ });
2433
+ }, [locale, firstWeekday]);
2434
+ const monthLabel = useMemo(
2435
+ () => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }),
2436
+ [locale]
2437
+ );
2438
+ const fullDate = useMemo(
2439
+ () => new Intl.DateTimeFormat(locale, {
2440
+ weekday: "long",
2441
+ day: "numeric",
2442
+ month: "long",
2443
+ year: "numeric"
2444
+ }),
2445
+ [locale]
2446
+ );
2447
+ const disabled = (date) => {
2448
+ if (min && date < startOfDay(min)) return true;
2449
+ if (max && date > startOfDay(max)) return true;
2450
+ return isDateDisabled?.(date) ?? false;
2451
+ };
2452
+ const moveCursor = (next) => {
2453
+ shouldFocusRef.current = true;
2454
+ setCursor(next);
2455
+ const firstVisible = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), 1);
2456
+ const lastVisible = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + months, 0);
2457
+ if (next < firstVisible || next > lastVisible) setViewMonth(next);
2458
+ };
2459
+ useEffect(() => {
2460
+ if (!shouldFocusRef.current) return;
2461
+ shouldFocusRef.current = false;
2462
+ gridRef.current?.querySelector('[data-ansh-day][tabindex="0"]')?.focus({ preventScroll: true });
2463
+ }, [cursor, viewMonth]);
2464
+ const commit = (date) => {
2465
+ if (disabled(date)) return;
2466
+ if (mode === "single") {
2467
+ setSelection(date);
2468
+ return;
2469
+ }
2470
+ if (!range.start || range.end || date < range.start) {
2471
+ setSelection({ start: date, end: null });
2472
+ } else {
2473
+ setSelection({ start: range.start, end: date });
2474
+ }
2475
+ };
2476
+ const onKeyDown = (event) => {
2477
+ let next = null;
2478
+ switch (event.key) {
2479
+ case "ArrowLeft":
2480
+ next = addDays(cursor, -1);
2481
+ break;
2482
+ case "ArrowRight":
2483
+ next = addDays(cursor, 1);
2484
+ break;
2485
+ case "ArrowUp":
2486
+ next = addDays(cursor, -7);
2487
+ break;
2488
+ case "ArrowDown":
2489
+ next = addDays(cursor, 7);
2490
+ break;
2491
+ case "Home":
2492
+ next = addDays(cursor, -((cursor.getDay() - firstWeekday + 7) % 7));
2493
+ break;
2494
+ case "End":
2495
+ next = addDays(cursor, 6 - (cursor.getDay() - firstWeekday + 7) % 7);
2496
+ break;
2497
+ case "PageUp":
2498
+ next = addMonths(cursor, event.shiftKey ? -12 : -1);
2499
+ break;
2500
+ case "PageDown":
2501
+ next = addMonths(cursor, event.shiftKey ? 12 : 1);
2502
+ break;
2503
+ case "Enter":
2504
+ case " ":
2505
+ event.preventDefault();
2506
+ commit(cursor);
2507
+ return;
2508
+ default:
2509
+ return;
2510
+ }
2511
+ event.preventDefault();
2512
+ moveCursor(next);
2513
+ };
2514
+ const inRange = (date) => {
2515
+ if (mode !== "range" || !range.start) return false;
2516
+ const end = range.end ?? hovered;
2517
+ if (!end) return false;
2518
+ const [low, high] = range.start <= end ? [range.start, end] : [end, range.start];
2519
+ return date > low && date < high;
2520
+ };
2521
+ const renderMonth = (offset) => {
2522
+ const monthDate = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + offset, 1);
2523
+ const firstOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
2524
+ const leading = (firstOfMonth.getDay() - firstWeekday + 7) % 7;
2525
+ const gridStart = addDays(firstOfMonth, -leading);
2526
+ const days = Array.from({ length: 42 }, (_, i) => addDays(gridStart, i));
2527
+ const weeks = Array.from({ length: 6 }, (_, w) => days.slice(w * 7, w * 7 + 7));
2528
+ return /* @__PURE__ */ jsxs("div", { className: "ansh-calendar__month", children: [
2529
+ /* @__PURE__ */ jsx("div", { className: "ansh-calendar__caption", "aria-hidden": "true", children: monthLabel.format(monthDate) }),
2530
+ /* @__PURE__ */ jsxs(
2531
+ "table",
2532
+ {
2533
+ className: "ansh-calendar__grid",
2534
+ role: "grid",
2535
+ "aria-label": monthLabel.format(monthDate),
2536
+ children: [
2537
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
2538
+ showWeekNumbers ? /* @__PURE__ */ jsx("th", { scope: "col", className: "ansh-calendar__weekno-head", children: /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", children: "Week" }) }) : null,
2539
+ dayNames.map((day) => /* @__PURE__ */ jsxs("th", { scope: "col", className: "ansh-calendar__weekday", abbr: day.long, children: [
2540
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: day.short }),
2541
+ /* @__PURE__ */ jsx("span", { className: "ansh-visually-hidden", children: day.long })
2542
+ ] }, day.long))
2543
+ ] }) }),
2544
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week) => /* @__PURE__ */ jsxs("tr", { children: [
2545
+ showWeekNumbers ? /* @__PURE__ */ jsx("td", { className: "ansh-calendar__weekno", children: isoWeek(week[0]) }) : null,
2546
+ week.map((date) => {
2547
+ const outside = date.getMonth() !== monthDate.getMonth();
2548
+ const isDisabled = disabled(date);
2549
+ const isSelected = mode === "single" ? isSameDay(date, single) : isSameDay(date, range.start) || isSameDay(date, range.end);
2550
+ const isCursor = isSameDay(date, cursor);
2551
+ return /* @__PURE__ */ jsx("td", { className: "ansh-calendar__cell", role: "gridcell", children: /* @__PURE__ */ jsx(
2552
+ "button",
2553
+ {
2554
+ type: "button",
2555
+ "data-ansh-day": "",
2556
+ className: cx(
2557
+ "ansh-calendar__day",
2558
+ outside && "ansh-calendar__day--outside",
2559
+ isSelected && "ansh-calendar__day--selected",
2560
+ inRange(date) && "ansh-calendar__day--in-range",
2561
+ isSameDay(date, today) && "ansh-calendar__day--today"
2562
+ ),
2563
+ tabIndex: isCursor ? 0 : -1,
2564
+ disabled: isDisabled,
2565
+ "aria-selected": isSelected,
2566
+ "aria-current": isSameDay(date, today) ? "date" : void 0,
2567
+ "aria-label": fullDate.format(date),
2568
+ onClick: () => {
2569
+ setCursor(date);
2570
+ commit(date);
2571
+ },
2572
+ onMouseEnter: () => mode === "range" && setHovered(date),
2573
+ onFocus: () => setCursor(date),
2574
+ children: date.getDate()
2575
+ }
2576
+ ) }, date.toISOString());
2577
+ })
2578
+ ] }, week[0].toISOString())) })
2579
+ ]
2580
+ }
2581
+ )
2582
+ ] }, offset);
2583
+ };
2584
+ return /* @__PURE__ */ jsxs(
2585
+ "div",
2586
+ {
2587
+ className: cx("ansh-calendar", months > 1 && "ansh-calendar--multi", className),
2588
+ onKeyDown,
2589
+ onMouseLeave: () => setHovered(null),
2590
+ children: [
2591
+ /* @__PURE__ */ jsxs("div", { className: "ansh-calendar__header", children: [
2592
+ /* @__PURE__ */ jsx(
2593
+ "button",
2594
+ {
2595
+ type: "button",
2596
+ className: "ansh-calendar__nav ansh-focusable",
2597
+ "aria-label": "Previous month",
2598
+ onClick: () => setViewMonth(addMonths(viewMonth, -1)),
2599
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
2600
+ "path",
2601
+ {
2602
+ d: "M7.5 2.5L4 6l3.5 3.5",
2603
+ stroke: "currentColor",
2604
+ strokeWidth: "1.5",
2605
+ fill: "none",
2606
+ strokeLinecap: "round",
2607
+ strokeLinejoin: "round"
2608
+ }
2609
+ ) })
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx("div", { className: "ansh-calendar__title", "aria-live": "polite", children: months > 1 ? `${monthLabel.format(viewMonth)} \u2013 ${monthLabel.format(new Date(viewMonth.getFullYear(), viewMonth.getMonth() + months - 1, 1))}` : monthLabel.format(viewMonth) }),
2613
+ /* @__PURE__ */ jsx(
2614
+ "button",
2615
+ {
2616
+ type: "button",
2617
+ className: "ansh-calendar__nav ansh-focusable",
2618
+ "aria-label": "Next month",
2619
+ onClick: () => setViewMonth(addMonths(viewMonth, 1)),
2620
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 12 12", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
2621
+ "path",
2622
+ {
2623
+ d: "M4.5 2.5L8 6l-3.5 3.5",
2624
+ stroke: "currentColor",
2625
+ strokeWidth: "1.5",
2626
+ fill: "none",
2627
+ strokeLinecap: "round",
2628
+ strokeLinejoin: "round"
2629
+ }
2630
+ ) })
2631
+ }
2632
+ )
2633
+ ] }),
2634
+ /* @__PURE__ */ jsx("div", { className: "ansh-calendar__months", ref: gridRef, children: Array.from({ length: months }, (_, index) => renderMonth(index)) })
2635
+ ]
2636
+ }
2637
+ );
2638
+ }
2639
+ function DatePicker({
2640
+ value,
2641
+ defaultValue = null,
2642
+ onChange,
2643
+ placeholder,
2644
+ disabled = false,
2645
+ invalid = false,
2646
+ clearable = true,
2647
+ format,
2648
+ label = "Date",
2649
+ className,
2650
+ name,
2651
+ locale,
2652
+ ...calendarProps
2653
+ }) {
2654
+ const fieldProps = useFieldControl();
2655
+ const [selected, setSelected] = useControllableState({
2656
+ value,
2657
+ defaultValue,
2658
+ onChange
2659
+ });
2660
+ const [open, setOpen] = useState(false);
2661
+ const [text, setText] = useState("");
2662
+ const [editing, setEditing] = useState(false);
2663
+ const anchorRef = useRef(null);
2664
+ const panelRef = useRef(null);
2665
+ const inputRef = useRef(null);
2666
+ const formatter = useMemo(
2667
+ () => new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" }),
2668
+ [locale]
2669
+ );
2670
+ const order = useMemo(() => {
2671
+ const parts = formatter.formatToParts(new Date(2026, 2, 14));
2672
+ return parts.filter((p) => ["day", "month", "year"].includes(p.type)).map((p) => p.type);
2673
+ }, [formatter]);
2674
+ const display = format ? selected ? format(selected) : "" : selected ? formatter.format(selected) : "";
2675
+ const parse = (raw) => {
2676
+ const numbers = raw.split(/\D+/).filter(Boolean).map(Number);
2677
+ if (numbers.length < 3) return null;
2678
+ const parts = {};
2679
+ order.forEach((type, index) => {
2680
+ parts[type] = numbers[index];
2681
+ });
2682
+ const { month = 0, day = 0 } = parts;
2683
+ let { year = 0 } = parts;
2684
+ if (year < 100) year += year < 70 ? 2e3 : 1900;
2685
+ const date = new Date(year, month - 1, day);
2686
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
2687
+ return null;
2688
+ }
2689
+ return startOfDay(date);
2690
+ };
2691
+ const commitText = () => {
2692
+ setEditing(false);
2693
+ if (text.trim() === "") {
2694
+ setSelected(null);
2695
+ return;
2696
+ }
2697
+ const parsed = parse(text);
2698
+ if (parsed) setSelected(parsed);
2699
+ setText("");
2700
+ };
2701
+ const position = useAnchoredPosition(anchorRef, panelRef, open, {
2702
+ placement: "bottom-start",
2703
+ offset: 6
2704
+ });
2705
+ useDismiss(panelRef, open, () => setOpen(false), { ignore: [anchorRef] });
2706
+ const placeholderText = placeholder ?? order.map((type) => type === "year" ? "yyyy" : type === "month" ? "mm" : "dd").join("/");
2707
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-datepicker", className), children: [
2708
+ /* @__PURE__ */ jsxs(
2709
+ "div",
2710
+ {
2711
+ ref: anchorRef,
2712
+ className: cx(
2713
+ "ansh-input-group",
2714
+ "ansh-focusable-within",
2715
+ (invalid || fieldProps["aria-invalid"]) && "ansh-input-group--invalid",
2716
+ disabled && "ansh-input-group--disabled"
2717
+ ),
2718
+ children: [
2719
+ /* @__PURE__ */ jsx(
2720
+ "input",
2721
+ {
2722
+ ref: inputRef,
2723
+ type: "text",
2724
+ inputMode: "numeric",
2725
+ className: "ansh-input",
2726
+ value: editing ? text : display,
2727
+ placeholder: placeholderText,
2728
+ disabled: disabled || fieldProps.disabled,
2729
+ "aria-label": label,
2730
+ "aria-invalid": invalid || void 0,
2731
+ autoComplete: "off",
2732
+ ...fieldProps,
2733
+ onChange: (event) => {
2734
+ setEditing(true);
2735
+ setText(event.target.value);
2736
+ },
2737
+ onBlur: commitText,
2738
+ onKeyDown: (event) => {
2739
+ if (event.key === "Enter") {
2740
+ event.preventDefault();
2741
+ commitText();
2742
+ setOpen(false);
2743
+ } else if (event.key === "ArrowDown" && !open) {
2744
+ event.preventDefault();
2745
+ setOpen(true);
2746
+ } else if (event.key === "Escape" && open) {
2747
+ event.preventDefault();
2748
+ setOpen(false);
2749
+ }
2750
+ }
2751
+ }
2752
+ ),
2753
+ clearable && selected && !disabled ? /* @__PURE__ */ jsx(
2754
+ "button",
2755
+ {
2756
+ type: "button",
2757
+ className: "ansh-input-group__clear ansh-focusable",
2758
+ "aria-label": "Clear date",
2759
+ onClick: () => {
2760
+ setSelected(null);
2761
+ setText("");
2762
+ inputRef.current?.focus();
2763
+ },
2764
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
2765
+ "path",
2766
+ {
2767
+ d: "M4 4l8 8M12 4l-8 8",
2768
+ stroke: "currentColor",
2769
+ strokeWidth: "1.75",
2770
+ strokeLinecap: "round",
2771
+ fill: "none"
2772
+ }
2773
+ ) })
2774
+ }
2775
+ ) : null,
2776
+ /* @__PURE__ */ jsx(
2777
+ "button",
2778
+ {
2779
+ type: "button",
2780
+ className: "ansh-input-group__button ansh-focusable",
2781
+ "aria-label": "Open calendar",
2782
+ "aria-haspopup": "dialog",
2783
+ "aria-expanded": open,
2784
+ disabled,
2785
+ onClick: () => setOpen((v) => !v),
2786
+ children: /* @__PURE__ */ jsxs(
2787
+ "svg",
2788
+ {
2789
+ viewBox: "0 0 20 20",
2790
+ width: "16",
2791
+ height: "16",
2792
+ fill: "none",
2793
+ stroke: "currentColor",
2794
+ strokeWidth: "1.5",
2795
+ "aria-hidden": "true",
2796
+ children: [
2797
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4.5", width: "14", height: "13", rx: "2.5" }),
2798
+ /* @__PURE__ */ jsx("path", { d: "M3 8.5h14M7 2.5v3M13 2.5v3", strokeLinecap: "round" })
2799
+ ]
2800
+ }
2801
+ )
2802
+ }
2803
+ )
2804
+ ]
2805
+ }
2806
+ ),
2807
+ name ? (
2808
+ // ISO in the hidden field. A server parsing "14/03/2026" has to guess the locale; an ISO
2809
+ // date is unambiguous everywhere.
2810
+ /* @__PURE__ */ jsx(
2811
+ "input",
2812
+ {
2813
+ type: "hidden",
2814
+ name,
2815
+ value: selected ? `${selected.getFullYear()}-${String(selected.getMonth() + 1).padStart(2, "0")}-${String(selected.getDate()).padStart(2, "0")}` : ""
2816
+ }
2817
+ )
2818
+ ) : null,
2819
+ open ? /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
2820
+ "div",
2821
+ {
2822
+ ref: panelRef,
2823
+ role: "dialog",
2824
+ "aria-label": label,
2825
+ className: "ansh-datepicker__panel",
2826
+ style: position.style,
2827
+ children: /* @__PURE__ */ jsx(
2828
+ Calendar,
2829
+ {
2830
+ mode: "single",
2831
+ value: selected,
2832
+ onChange: (next) => {
2833
+ setSelected(next);
2834
+ setOpen(false);
2835
+ inputRef.current?.focus();
2836
+ },
2837
+ ...locale ? { locale } : null,
2838
+ ...calendarProps
2839
+ }
2840
+ )
2841
+ }
2842
+ ) }) : null
2843
+ ] });
2844
+ }
2845
+ function DateRangePicker({
2846
+ value,
2847
+ defaultValue = { start: null, end: null },
2848
+ onChange,
2849
+ disabled = false,
2850
+ invalid = false,
2851
+ label = "Date range",
2852
+ className,
2853
+ months = 2,
2854
+ locale,
2855
+ ...calendarProps
2856
+ }) {
2857
+ const [range, setRange] = useControllableState({ value, defaultValue, onChange });
2858
+ const [open, setOpen] = useState(false);
2859
+ const anchorRef = useRef(null);
2860
+ const panelRef = useRef(null);
2861
+ const formatter = useMemo(
2862
+ () => new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", year: "numeric" }),
2863
+ [locale]
2864
+ );
2865
+ const position = useAnchoredPosition(anchorRef, panelRef, open, {
2866
+ placement: "bottom-start",
2867
+ offset: 6
2868
+ });
2869
+ useDismiss(panelRef, open, () => setOpen(false), { ignore: [anchorRef] });
2870
+ const summary = range.start && range.end ? `${formatter.format(range.start)} \u2013 ${formatter.format(range.end)}` : range.start ? `${formatter.format(range.start)} \u2013 \u2026` : "Select dates";
2871
+ return /* @__PURE__ */ jsxs("div", { className: cx("ansh-datepicker", className), children: [
2872
+ /* @__PURE__ */ jsxs(
2873
+ "button",
2874
+ {
2875
+ ref: anchorRef,
2876
+ type: "button",
2877
+ className: cx(
2878
+ "ansh-select__trigger",
2879
+ "ansh-focusable",
2880
+ open && "ansh-select__trigger--open",
2881
+ invalid && "ansh-select__trigger--invalid",
2882
+ !range.start && "ansh-select__trigger--placeholder"
2883
+ ),
2884
+ disabled,
2885
+ "aria-haspopup": "dialog",
2886
+ "aria-expanded": open,
2887
+ "aria-label": `${label}: ${summary}`,
2888
+ onClick: () => setOpen((v) => !v),
2889
+ children: [
2890
+ /* @__PURE__ */ jsxs(
2891
+ "svg",
2892
+ {
2893
+ viewBox: "0 0 20 20",
2894
+ width: "16",
2895
+ height: "16",
2896
+ fill: "none",
2897
+ stroke: "currentColor",
2898
+ strokeWidth: "1.5",
2899
+ "aria-hidden": "true",
2900
+ children: [
2901
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4.5", width: "14", height: "13", rx: "2.5" }),
2902
+ /* @__PURE__ */ jsx("path", { d: "M3 8.5h14M7 2.5v3M13 2.5v3", strokeLinecap: "round" })
2903
+ ]
2904
+ }
2905
+ ),
2906
+ /* @__PURE__ */ jsx("span", { className: "ansh-select__value", children: summary })
2907
+ ]
2908
+ }
2909
+ ),
2910
+ open ? /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
2911
+ "div",
2912
+ {
2913
+ ref: panelRef,
2914
+ role: "dialog",
2915
+ "aria-label": label,
2916
+ className: "ansh-datepicker__panel",
2917
+ style: position.style,
2918
+ children: /* @__PURE__ */ jsx(
2919
+ Calendar,
2920
+ {
2921
+ mode: "range",
2922
+ months,
2923
+ value: range,
2924
+ onChange: (next) => {
2925
+ const nextRange = next;
2926
+ setRange(nextRange);
2927
+ if (nextRange.start && nextRange.end) setOpen(false);
2928
+ },
2929
+ ...locale ? { locale } : null,
2930
+ ...calendarProps
2931
+ }
2932
+ )
2933
+ }
2934
+ ) }) : null
2935
+ ] });
2936
+ }
2937
+
2938
+ export { Calendar, Checkbox, ChoiceCard, ColorPicker, Combobox, DatePicker, DateRangePicker, Dropzone, Field, Fieldset, Input, NumberInput, OptionList, PinInput, Radio, RadioGroup, RangeSlider, Rating, Select, Slider, Switch, TagsInput, Textarea, VisuallyHidden, addDays, addMonths, filterOptions, firstEnabled, isSameDay, scoreOption, startOfDay, stepActive, useFieldContext, useFieldControl, useTypeahead };
2939
+ //# sourceMappingURL=chunk-QO3E7TJS.js.map
2940
+ //# sourceMappingURL=chunk-QO3E7TJS.js.map