@ceebee/ui 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.
package/dist/client.js ADDED
@@ -0,0 +1,2104 @@
1
+ "use client";
2
+ import { createContext, forwardRef, useContext, useMemo, useState, useRef, useEffect, useCallback, useId, useReducer, Children } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { motion } from 'motion/react';
5
+ import { Select as Select$1 } from '@base-ui/react/select';
6
+ import { ChevronLeft, ChevronRight, ChevronsUpDown, Check, Minus, X, CalendarDays, Clock, Upload, Plus, Search, PanelLeftOpen, PanelLeftClose, ArrowUp, ArrowDown, XCircle, AlertTriangle, CheckCircle2, Info } from 'lucide-react';
7
+ import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
8
+ import { Radio } from '@base-ui/react/radio';
9
+ import { RadioGroup as RadioGroup$1 } from '@base-ui/react/radio-group';
10
+ import { Switch as Switch$1 } from '@base-ui/react/switch';
11
+ import { Combobox as Combobox$1 } from '@base-ui/react/combobox';
12
+ import { Popover as Popover$1 } from '@base-ui/react/popover';
13
+ import { Dialog as Dialog$1 } from '@base-ui/react/dialog';
14
+ import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
15
+ import { Toast } from '@base-ui/react/toast';
16
+ import { Tabs as Tabs$1 } from '@base-ui/react/tabs';
17
+ import { Menu } from '@base-ui/react/menu';
18
+ import useEmblaCarousel from 'embla-carousel-react';
19
+
20
+ var DEFAULT_LABELS = {
21
+ dismiss: "Dismiss",
22
+ close: "Close",
23
+ clear: "Clear",
24
+ open: "Open",
25
+ previousSlide: "Previous slide",
26
+ nextSlide: "Next slide",
27
+ goToSlide: (index) => `Go to slide ${index}`,
28
+ previousPage: "Previous page",
29
+ nextPage: "Next page",
30
+ page: (index) => `Page ${index}`,
31
+ pageSummary: (from, to, total) => `${from}\u2013${to} of ${total}`,
32
+ chooseDate: "Choose a date",
33
+ chooseTime: "Choose a time",
34
+ previousMonth: "Previous month",
35
+ nextMonth: "Next month",
36
+ chooseFiles: "Choose files",
37
+ chooseFile: "Choose a file",
38
+ dropFilesHere: "or drop them here",
39
+ dropFileHere: "or drop it here",
40
+ removeFile: (name) => `Remove ${name}`,
41
+ increase: "Increase",
42
+ decrease: "Decrease",
43
+ expandNavigation: "Expand navigation",
44
+ collapseNavigation: "Collapse navigation",
45
+ back: "Back",
46
+ next: "Next",
47
+ done: "Done",
48
+ skip: "Skip",
49
+ progress: (current, total) => `${current} of ${total}`
50
+ };
51
+ var LabelsContext = createContext(DEFAULT_LABELS);
52
+ function LabelsProvider({ children, labels }) {
53
+ const value = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
54
+ return /* @__PURE__ */ jsx(LabelsContext.Provider, { value, children });
55
+ }
56
+ function useLabels() {
57
+ return useContext(LabelsContext);
58
+ }
59
+ var DURATIONS = {
60
+ instant: 0.08,
61
+ fast: 0.14,
62
+ base: 0.22,
63
+ slow: 0.38,
64
+ deliberate: 0.62
65
+ };
66
+ var SPRINGS = {
67
+ snappy: { type: "spring", stiffness: 520, damping: 34, mass: 0.8 },
68
+ soft: { type: "spring", stiffness: 220, damping: 30, mass: 1 },
69
+ bouncy: { type: "spring", stiffness: 420, damping: 16, mass: 0.9 }
70
+ };
71
+ var MotionContext = createContext({ enabled: true, scale: 1 });
72
+ function MotionProvider({ children, enabled = true, scale = 1 }) {
73
+ const [systemReduced, setSystemReduced] = useState(false);
74
+ useEffect(() => {
75
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
76
+ setSystemReduced(query.matches);
77
+ const onChange = (event) => setSystemReduced(event.matches);
78
+ query.addEventListener("change", onChange);
79
+ return () => query.removeEventListener("change", onChange);
80
+ }, []);
81
+ const value = useMemo(
82
+ () => ({ enabled: enabled && !systemReduced && scale > 0, scale: systemReduced ? 0 : scale }),
83
+ [enabled, scale, systemReduced]
84
+ );
85
+ return /* @__PURE__ */ jsx(MotionContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { style: { display: "contents", ["--cb-motion-scale"]: String(value.scale) }, children }) });
86
+ }
87
+ function useMotionSettings() {
88
+ const settings = useContext(MotionContext);
89
+ return useMemo(
90
+ () => ({
91
+ ...settings,
92
+ duration: (token) => settings.enabled ? DURATIONS[token] * settings.scale : 0,
93
+ spring: (preset = "snappy") => settings.enabled ? SPRINGS[preset] : { duration: 0 }
94
+ }),
95
+ [settings]
96
+ );
97
+ }
98
+ var ThemeContext = createContext(null);
99
+ var STORAGE_KEY = "cb-theme";
100
+ function ThemeProvider({
101
+ children,
102
+ defaultChoice = "system",
103
+ persist = true
104
+ }) {
105
+ const [choice, setChoiceState] = useState(defaultChoice);
106
+ const [systemDark, setSystemDark] = useState(false);
107
+ useEffect(() => {
108
+ const query = window.matchMedia("(prefers-color-scheme: dark)");
109
+ setSystemDark(query.matches);
110
+ const onChange = (event) => setSystemDark(event.matches);
111
+ query.addEventListener("change", onChange);
112
+ return () => query.removeEventListener("change", onChange);
113
+ }, []);
114
+ useEffect(() => {
115
+ if (!persist) return;
116
+ const stored = window.localStorage.getItem(STORAGE_KEY);
117
+ if (stored === "light" || stored === "dark" || stored === "system") setChoiceState(stored);
118
+ }, [persist]);
119
+ useEffect(() => {
120
+ const root = document.documentElement;
121
+ if (choice === "system") root.removeAttribute("data-theme");
122
+ else root.setAttribute("data-theme", choice);
123
+ }, [choice]);
124
+ const setChoice = useCallback(
125
+ (next) => {
126
+ setChoiceState(next);
127
+ if (persist) window.localStorage.setItem(STORAGE_KEY, next);
128
+ },
129
+ [persist]
130
+ );
131
+ const resolved = choice === "system" ? systemDark ? "dark" : "light" : choice;
132
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value: { choice, setChoice, resolved }, children });
133
+ }
134
+ function useTheme() {
135
+ const value = useContext(ThemeContext);
136
+ if (!value) throw new Error("useTheme must be used inside <ThemeProvider>");
137
+ return value;
138
+ }
139
+
140
+ // src/lib/cn.ts
141
+ function cn(...parts) {
142
+ return parts.filter(Boolean).join(" ");
143
+ }
144
+ var Button = forwardRef(function Button2({
145
+ variant = "solid",
146
+ tone = "brand",
147
+ size = "md",
148
+ loading = false,
149
+ iconStart,
150
+ iconEnd,
151
+ motion: motionProp = true,
152
+ className,
153
+ children,
154
+ disabled,
155
+ ...rest
156
+ }, ref) {
157
+ const { enabled, spring } = useMotionSettings();
158
+ const animate = enabled && motionProp;
159
+ const iconOnly = children === void 0 || children === null || children === false || children === "";
160
+ return /* @__PURE__ */ jsxs(
161
+ motion.button,
162
+ {
163
+ ref,
164
+ className: cn(
165
+ "cb-button",
166
+ `cb-button--${variant}`,
167
+ `cb-button--${size}`,
168
+ iconOnly && "cb-button--icon",
169
+ className
170
+ ),
171
+ "data-tone": tone,
172
+ "data-loading": loading || void 0,
173
+ disabled: disabled ?? loading,
174
+ "aria-busy": loading || void 0,
175
+ whileTap: animate ? { scale: 0.97 } : void 0,
176
+ whileHover: animate ? { y: -1 } : void 0,
177
+ transition: spring("snappy"),
178
+ ...rest,
179
+ children: [
180
+ loading ? /* @__PURE__ */ jsx("span", { className: "cb-button__spinner", "aria-hidden": "true" }) : iconStart,
181
+ iconOnly ? null : /* @__PURE__ */ jsx("span", { className: "cb-button__label", children }),
182
+ loading || iconOnly ? null : iconEnd
183
+ ]
184
+ }
185
+ );
186
+ });
187
+ var FieldContext = createContext(null);
188
+ function useFieldWiring() {
189
+ return useContext(FieldContext);
190
+ }
191
+ function Field({ label, hint, error, required = false, labelHidden = false, className, children }) {
192
+ const base = useId();
193
+ const controlId = `${base}-control`;
194
+ const hintId = hint ? `${base}-hint` : void 0;
195
+ const errorId = error && error !== true ? `${base}-error` : void 0;
196
+ const describedBy = [hintId, errorId].filter(Boolean).join(" ") || void 0;
197
+ return /* @__PURE__ */ jsx(FieldContext.Provider, { value: { controlId, describedBy, invalid: Boolean(error), required }, children: /* @__PURE__ */ jsxs("div", { className: cn("cb-field", className), "data-invalid": error ? "" : void 0, children: [
198
+ /* @__PURE__ */ jsxs("label", { className: cn("cb-field__label", labelHidden && "cb-visually-hidden"), htmlFor: controlId, children: [
199
+ label,
200
+ required ? /* @__PURE__ */ jsx("span", { className: "cb-field__required", "aria-hidden": "true", children: "*" }) : null
201
+ ] }),
202
+ children,
203
+ hint ? /* @__PURE__ */ jsx("p", { className: "cb-field__hint", id: hintId, children: hint }) : null,
204
+ error && error !== true ? /* @__PURE__ */ jsx("p", { className: "cb-field__error", id: errorId, role: "alert", children: error }) : null
205
+ ] }) });
206
+ }
207
+ var TextInput = forwardRef(function TextInput2({ size = "md", invalid, className, ...rest }, ref) {
208
+ const field = useFieldWiring();
209
+ return /* @__PURE__ */ jsx(
210
+ "input",
211
+ {
212
+ ref,
213
+ className: cn("cb-input", `cb-input--${size}`, className),
214
+ id: rest.id ?? field?.controlId,
215
+ "aria-describedby": rest["aria-describedby"] ?? field?.describedBy,
216
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
217
+ required: rest.required ?? field?.required,
218
+ ...rest
219
+ }
220
+ );
221
+ });
222
+ var Textarea = forwardRef(function Textarea2({ invalid, className, ...rest }, ref) {
223
+ const field = useFieldWiring();
224
+ return /* @__PURE__ */ jsx(
225
+ "textarea",
226
+ {
227
+ ref,
228
+ className: cn("cb-input", "cb-input--textarea", className),
229
+ id: rest.id ?? field?.controlId,
230
+ "aria-describedby": rest["aria-describedby"] ?? field?.describedBy,
231
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
232
+ required: rest.required ?? field?.required,
233
+ ...rest
234
+ }
235
+ );
236
+ });
237
+ function Select({
238
+ items,
239
+ value,
240
+ defaultValue,
241
+ onValueChange,
242
+ placeholder = "Select\u2026",
243
+ size = "md",
244
+ disabled,
245
+ invalid,
246
+ name,
247
+ id,
248
+ className
249
+ }) {
250
+ const field = useFieldWiring();
251
+ return /* @__PURE__ */ jsxs(
252
+ Select$1.Root,
253
+ {
254
+ items,
255
+ value,
256
+ defaultValue,
257
+ onValueChange: (next) => onValueChange?.(next),
258
+ disabled,
259
+ name,
260
+ children: [
261
+ /* @__PURE__ */ jsxs(
262
+ Select$1.Trigger,
263
+ {
264
+ id: id ?? field?.controlId,
265
+ "aria-describedby": field?.describedBy,
266
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
267
+ className: cn("cb-select", `cb-select--${size}`, className),
268
+ children: [
269
+ /* @__PURE__ */ jsx(Select$1.Value, { placeholder }),
270
+ /* @__PURE__ */ jsx(Select$1.Icon, { className: "cb-select__icon", children: /* @__PURE__ */ jsx(ChevronsUpDown, { size: 16 }) })
271
+ ]
272
+ }
273
+ ),
274
+ /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(Select$1.Positioner, { sideOffset: 6, alignItemWithTrigger: false, children: /* @__PURE__ */ jsx(Select$1.Popup, { className: "cb-select__popup", children: items.map((item) => /* @__PURE__ */ jsxs(Select$1.Item, { value: item.value, disabled: item.disabled, className: "cb-select__item", children: [
275
+ /* @__PURE__ */ jsx("span", { className: "cb-select__check", children: /* @__PURE__ */ jsx(Select$1.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { size: 14 }) }) }),
276
+ /* @__PURE__ */ jsx(Select$1.ItemText, { children: item.label })
277
+ ] }, item.value)) }) }) })
278
+ ]
279
+ }
280
+ );
281
+ }
282
+ function Checkbox({
283
+ label,
284
+ checked,
285
+ defaultChecked,
286
+ indeterminate,
287
+ onCheckedChange,
288
+ disabled,
289
+ name,
290
+ value,
291
+ description,
292
+ className
293
+ }) {
294
+ const generated = useId();
295
+ const field = useFieldWiring();
296
+ const id = field?.controlId ?? generated;
297
+ const descriptionId = description ? `${id}-description` : void 0;
298
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-choice", className), children: [
299
+ /* @__PURE__ */ jsx(
300
+ Checkbox$1.Root,
301
+ {
302
+ id,
303
+ checked,
304
+ defaultChecked,
305
+ indeterminate,
306
+ onCheckedChange: (next) => onCheckedChange?.(next),
307
+ disabled,
308
+ name,
309
+ value,
310
+ "aria-describedby": [descriptionId, field?.describedBy].filter(Boolean).join(" ") || void 0,
311
+ className: "cb-checkbox",
312
+ children: /* @__PURE__ */ jsx(Checkbox$1.Indicator, { className: "cb-checkbox__indicator", children: indeterminate ? /* @__PURE__ */ jsx(Minus, { size: 12, strokeWidth: 3 }) : /* @__PURE__ */ jsx(Check, { size: 12, strokeWidth: 3 }) })
313
+ }
314
+ ),
315
+ /* @__PURE__ */ jsxs("div", { className: "cb-choice__text", children: [
316
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "cb-choice__label", children: label }),
317
+ description ? /* @__PURE__ */ jsx("p", { className: "cb-choice__description", id: descriptionId, children: description }) : null
318
+ ] })
319
+ ] });
320
+ }
321
+ function RadioGroup({
322
+ options,
323
+ value,
324
+ defaultValue,
325
+ onValueChange,
326
+ name,
327
+ label,
328
+ direction = "column",
329
+ disabled,
330
+ className
331
+ }) {
332
+ const groupId = useId();
333
+ const field = useFieldWiring();
334
+ return /* @__PURE__ */ jsx(
335
+ RadioGroup$1,
336
+ {
337
+ value,
338
+ defaultValue,
339
+ onValueChange: (next) => onValueChange?.(next),
340
+ name,
341
+ disabled,
342
+ "aria-label": label,
343
+ "aria-describedby": field?.describedBy,
344
+ className: cn("cb-radio-group", `cb-radio-group--${direction}`, className),
345
+ children: options.map((option) => {
346
+ const id = `${groupId}-${option.value}`;
347
+ const descriptionId = option.description ? `${id}-description` : void 0;
348
+ return /* @__PURE__ */ jsxs("div", { className: "cb-choice", children: [
349
+ /* @__PURE__ */ jsx(
350
+ Radio.Root,
351
+ {
352
+ id,
353
+ value: option.value,
354
+ disabled: option.disabled,
355
+ "aria-describedby": descriptionId,
356
+ className: "cb-radio",
357
+ children: /* @__PURE__ */ jsx(Radio.Indicator, { className: "cb-radio__indicator" })
358
+ }
359
+ ),
360
+ /* @__PURE__ */ jsxs("div", { className: "cb-choice__text", children: [
361
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "cb-choice__label", children: option.label }),
362
+ option.description ? /* @__PURE__ */ jsx("p", { className: "cb-choice__description", id: descriptionId, children: option.description }) : null
363
+ ] })
364
+ ] }, option.value);
365
+ })
366
+ }
367
+ );
368
+ }
369
+ function Switch({
370
+ label,
371
+ checked,
372
+ defaultChecked,
373
+ onCheckedChange,
374
+ disabled,
375
+ name,
376
+ description,
377
+ justified = false,
378
+ className
379
+ }) {
380
+ const generated = useId();
381
+ const field = useFieldWiring();
382
+ const id = field?.controlId ?? generated;
383
+ const descriptionId = description ? `${id}-description` : void 0;
384
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-choice", justified && "cb-choice--justified", className), children: [
385
+ justified ? null : /* @__PURE__ */ jsx(
386
+ Switch$1.Root,
387
+ {
388
+ id,
389
+ checked,
390
+ defaultChecked,
391
+ onCheckedChange: (next) => onCheckedChange?.(next),
392
+ disabled,
393
+ name,
394
+ "aria-describedby": [descriptionId, field?.describedBy].filter(Boolean).join(" ") || void 0,
395
+ className: "cb-switch",
396
+ children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: "cb-switch__thumb" })
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsxs("div", { className: "cb-choice__text", children: [
400
+ /* @__PURE__ */ jsx("label", { htmlFor: id, className: "cb-choice__label", children: label }),
401
+ description ? /* @__PURE__ */ jsx("p", { className: "cb-choice__description", id: descriptionId, children: description }) : null
402
+ ] }),
403
+ justified ? /* @__PURE__ */ jsx(
404
+ Switch$1.Root,
405
+ {
406
+ id,
407
+ checked,
408
+ defaultChecked,
409
+ onCheckedChange: (next) => onCheckedChange?.(next),
410
+ disabled,
411
+ name,
412
+ "aria-describedby": [descriptionId, field?.describedBy].filter(Boolean).join(" ") || void 0,
413
+ className: "cb-switch",
414
+ children: /* @__PURE__ */ jsx(Switch$1.Thumb, { className: "cb-switch__thumb" })
415
+ }
416
+ ) : null
417
+ ] });
418
+ }
419
+ function Combobox({
420
+ items,
421
+ value,
422
+ defaultValue,
423
+ onValueChange,
424
+ placeholder = "Search\u2026",
425
+ emptyMessage = "Nothing matched",
426
+ size = "md",
427
+ disabled,
428
+ invalid,
429
+ name,
430
+ className
431
+ }) {
432
+ const field = useFieldWiring();
433
+ const labels = useLabels();
434
+ const optionFor = (candidate) => items.find((item) => item.value === candidate) ?? null;
435
+ return /* @__PURE__ */ jsxs(
436
+ Combobox$1.Root,
437
+ {
438
+ items,
439
+ value: value === void 0 ? void 0 : optionFor(value),
440
+ defaultValue: defaultValue === void 0 ? void 0 : optionFor(defaultValue),
441
+ onValueChange: (next) => onValueChange?.(next?.value ?? null),
442
+ isItemEqualToValue: (a, b) => a?.value === b?.value,
443
+ itemToStringLabel: (item) => typeof item === "string" ? item : item.label,
444
+ disabled,
445
+ name,
446
+ children: [
447
+ /* @__PURE__ */ jsxs("div", { className: cn("cb-combobox", `cb-combobox--${size}`, className), "data-disabled": disabled || void 0, children: [
448
+ /* @__PURE__ */ jsx(
449
+ Combobox$1.Input,
450
+ {
451
+ className: "cb-combobox__input",
452
+ placeholder,
453
+ id: field?.controlId,
454
+ "aria-describedby": field?.describedBy,
455
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0
456
+ }
457
+ ),
458
+ /* @__PURE__ */ jsx(Combobox$1.Clear, { className: "cb-combobox__clear", "aria-label": labels.clear, children: /* @__PURE__ */ jsx(X, { size: 14 }) }),
459
+ /* @__PURE__ */ jsx(Combobox$1.Trigger, { className: "cb-combobox__trigger", "aria-label": labels.open, children: /* @__PURE__ */ jsx(ChevronsUpDown, { size: 16 }) })
460
+ ] }),
461
+ /* @__PURE__ */ jsx(Combobox$1.Portal, { children: /* @__PURE__ */ jsx(Combobox$1.Positioner, { sideOffset: 6, children: /* @__PURE__ */ jsxs(Combobox$1.Popup, { className: "cb-combobox__popup", children: [
462
+ /* @__PURE__ */ jsx(Combobox$1.Empty, { className: "cb-combobox__empty", children: emptyMessage }),
463
+ /* @__PURE__ */ jsx(Combobox$1.List, { children: (item) => /* @__PURE__ */ jsxs(Combobox$1.Item, { value: item, className: "cb-combobox__item", children: [
464
+ /* @__PURE__ */ jsx("span", { className: "cb-combobox__check", children: /* @__PURE__ */ jsx(Combobox$1.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { size: 14 }) }) }),
465
+ /* @__PURE__ */ jsxs("span", { className: "cb-combobox__text", children: [
466
+ /* @__PURE__ */ jsx("span", { className: "cb-combobox__label", children: item.label }),
467
+ item.description ? /* @__PURE__ */ jsx("span", { className: "cb-combobox__description", children: item.description }) : null
468
+ ] })
469
+ ] }, item.value) })
470
+ ] }) }) })
471
+ ]
472
+ }
473
+ );
474
+ }
475
+
476
+ // src/form/date.util.ts
477
+ function isSameDay(a, b) {
478
+ if (!a || !b) return false;
479
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
480
+ }
481
+ function startOfDay(date) {
482
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
483
+ }
484
+ function monthMatrix(year, month, weekStartsOn = 1) {
485
+ const first = new Date(year, month, 1);
486
+ const offset = (first.getDay() - weekStartsOn + 7) % 7;
487
+ const start = new Date(year, month, 1 - offset);
488
+ const weeks = [];
489
+ for (let week = 0; week < 6; week += 1) {
490
+ const cells = [];
491
+ for (let day = 0; day < 7; day += 1) {
492
+ const date = new Date(start.getFullYear(), start.getMonth(), start.getDate() + week * 7 + day);
493
+ cells.push({ date, inMonth: date.getMonth() === month });
494
+ }
495
+ weeks.push(cells);
496
+ }
497
+ return weeks;
498
+ }
499
+ function parseDateInput(input) {
500
+ const text2 = input.trim();
501
+ if (!text2) return null;
502
+ const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(text2);
503
+ if (iso) return build(Number(iso[1]), Number(iso[2]), Number(iso[3]));
504
+ const dayFirst = /^(\d{1,2})[/.-](\d{1,2})[/.-](\d{2,4})$/.exec(text2);
505
+ if (dayFirst) {
506
+ const year = Number(dayFirst[3]);
507
+ return build(year < 100 ? 2e3 + year : year, Number(dayFirst[2]), Number(dayFirst[1]));
508
+ }
509
+ return null;
510
+ }
511
+ function build(year, month, day) {
512
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
513
+ const date = new Date(year, month - 1, day);
514
+ if (date.getMonth() !== month - 1 || date.getDate() !== day) return null;
515
+ return date;
516
+ }
517
+ function formatISO(date) {
518
+ if (!date) return "";
519
+ const month = String(date.getMonth() + 1).padStart(2, "0");
520
+ const day = String(date.getDate()).padStart(2, "0");
521
+ return `${date.getFullYear()}-${month}-${day}`;
522
+ }
523
+ function isOutOfRange(date, min, max) {
524
+ if (min && startOfDay(date) < startOfDay(min)) return true;
525
+ if (max && startOfDay(date) > startOfDay(max)) return true;
526
+ return false;
527
+ }
528
+ var WEEKDAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
529
+ function DateInput({
530
+ value,
531
+ defaultValue = null,
532
+ onValueChange,
533
+ min,
534
+ max,
535
+ format,
536
+ weekStartsOn = 1,
537
+ size = "md",
538
+ disabled,
539
+ invalid,
540
+ placeholder = "dd/mm/yyyy",
541
+ className
542
+ }) {
543
+ const field = useFieldWiring();
544
+ const labels = useLabels();
545
+ const controlled = value !== void 0;
546
+ const [internal, setInternal] = useState(defaultValue);
547
+ const current = controlled ? value ?? null : internal;
548
+ const display = (date) => format ? format(date) : date.toLocaleDateString(void 0, { day: "2-digit", month: "short", year: "numeric" });
549
+ const [text2, setText] = useState(() => current ? display(current) : "");
550
+ const [open, setOpen] = useState(false);
551
+ const [cursor, setCursor] = useState(() => current ?? /* @__PURE__ */ new Date());
552
+ const fieldRef = useRef(null);
553
+ const commit = (next) => {
554
+ if (!controlled) setInternal(next);
555
+ setText(next ? display(next) : "");
556
+ onValueChange?.(next);
557
+ };
558
+ const weeks = monthMatrix(cursor.getFullYear(), cursor.getMonth(), weekStartsOn);
559
+ const weekdays = weekStartsOn === 1 ? WEEKDAYS : [WEEKDAYS[6], ...WEEKDAYS.slice(0, 6)];
560
+ return /* @__PURE__ */ jsxs(Popover$1.Root, { open, onOpenChange: setOpen, children: [
561
+ /* @__PURE__ */ jsxs(
562
+ "div",
563
+ {
564
+ ref: fieldRef,
565
+ className: cn("cb-date", `cb-date--${size}`, className),
566
+ "data-disabled": disabled || void 0,
567
+ children: [
568
+ /* @__PURE__ */ jsx(
569
+ "input",
570
+ {
571
+ className: "cb-date__input",
572
+ id: field?.controlId,
573
+ value: controlled ? current ? display(current) : text2 : text2,
574
+ placeholder,
575
+ disabled,
576
+ "aria-describedby": field?.describedBy,
577
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
578
+ onClick: () => !disabled && setOpen(true),
579
+ onChange: (event) => setText(event.target.value),
580
+ onBlur: () => {
581
+ const parsed = parseDateInput(text2);
582
+ if (parsed && !isOutOfRange(parsed, min, max)) {
583
+ commit(parsed);
584
+ setCursor(parsed);
585
+ } else {
586
+ commit(null);
587
+ }
588
+ }
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx(
592
+ Popover$1.Trigger,
593
+ {
594
+ className: "cb-date__trigger",
595
+ "aria-label": labels.chooseDate,
596
+ disabled,
597
+ render: /* @__PURE__ */ jsx("button", { type: "button" }),
598
+ children: /* @__PURE__ */ jsx(CalendarDays, { size: 16 })
599
+ }
600
+ )
601
+ ]
602
+ }
603
+ ),
604
+ /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { anchor: fieldRef, side: "bottom", align: "start", sideOffset: 6, children: /* @__PURE__ */ jsxs(Popover$1.Popup, { className: "cb-calendar", children: [
605
+ /* @__PURE__ */ jsxs("div", { className: "cb-calendar__head", children: [
606
+ /* @__PURE__ */ jsx(
607
+ "button",
608
+ {
609
+ type: "button",
610
+ className: "cb-calendar__nav",
611
+ "aria-label": labels.previousMonth,
612
+ onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() - 1, 1)),
613
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
614
+ }
615
+ ),
616
+ /* @__PURE__ */ jsx("p", { className: "cb-calendar__month", "aria-live": "polite", children: cursor.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }),
617
+ /* @__PURE__ */ jsx(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ className: "cb-calendar__nav",
622
+ "aria-label": labels.nextMonth,
623
+ onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)),
624
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
625
+ }
626
+ )
627
+ ] }),
628
+ /* @__PURE__ */ jsxs("table", { className: "cb-calendar__grid", children: [
629
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: weekdays.map((day) => /* @__PURE__ */ jsx("th", { scope: "col", abbr: day, children: day }, day)) }) }),
630
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx("tr", { children: week.map((cell) => {
631
+ const selected = isSameDay(cell.date, current);
632
+ const outside = isOutOfRange(cell.date, min, max);
633
+ return /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(
634
+ "button",
635
+ {
636
+ type: "button",
637
+ className: "cb-calendar__day",
638
+ "data-outside": !cell.inMonth || void 0,
639
+ "data-selected": selected || void 0,
640
+ "data-today": isSameDay(cell.date, /* @__PURE__ */ new Date()) || void 0,
641
+ disabled: outside,
642
+ "aria-pressed": selected,
643
+ "aria-label": formatISO(cell.date),
644
+ onClick: () => {
645
+ commit(cell.date);
646
+ setCursor(cell.date);
647
+ setOpen(false);
648
+ },
649
+ children: cell.date.getDate()
650
+ }
651
+ ) }, cell.date.toISOString());
652
+ }) }, weekIndex)) })
653
+ ] })
654
+ ] }) }) })
655
+ ] });
656
+ }
657
+
658
+ // src/form/time.util.ts
659
+ function parseTime(input) {
660
+ const text2 = input.trim().toLowerCase().replace(/\s+/g, "");
661
+ if (!text2) return null;
662
+ const suffix = text2.endsWith("am") ? "am" : text2.endsWith("pm") ? "pm" : null;
663
+ const body = suffix ? text2.slice(0, -2) : text2;
664
+ let hours;
665
+ let minutes = 0;
666
+ const separated = /^(\d{1,2})[:.](\d{1,2})$/.exec(body);
667
+ const compact = /^(\d{3,4})$/.exec(body);
668
+ const hourOnly = /^(\d{1,2})$/.exec(body);
669
+ if (separated) {
670
+ hours = Number(separated[1]);
671
+ minutes = Number(separated[2]);
672
+ } else if (compact) {
673
+ const digits = compact[1].padStart(4, "0");
674
+ hours = Number(digits.slice(0, 2));
675
+ minutes = Number(digits.slice(2));
676
+ } else if (hourOnly) {
677
+ hours = Number(hourOnly[1]);
678
+ } else {
679
+ return null;
680
+ }
681
+ if (suffix === "pm" && hours < 12) hours += 12;
682
+ if (suffix === "am" && hours === 12) hours = 0;
683
+ if (hours > 23 || minutes > 59) return null;
684
+ return { hours, minutes };
685
+ }
686
+ function formatTime({ hours, minutes }) {
687
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
688
+ }
689
+ function timeToMinutes({ hours, minutes }) {
690
+ return hours * 60 + minutes;
691
+ }
692
+ function timeOptions(step, min, max) {
693
+ const safeStep = step > 0 ? step : 30;
694
+ const from = min ? timeToMinutes(min) : 0;
695
+ const to = max ? timeToMinutes(max) : 24 * 60 - 1;
696
+ const options = [];
697
+ for (let value = from; value <= to; value += safeStep) {
698
+ options.push({ hours: Math.floor(value / 60) % 24, minutes: value % 60 });
699
+ }
700
+ return options;
701
+ }
702
+ function isTimeOutOfRange(value, min, max) {
703
+ const minutes = timeToMinutes(value);
704
+ if (min && minutes < timeToMinutes(min)) return true;
705
+ if (max && minutes > timeToMinutes(max)) return true;
706
+ return false;
707
+ }
708
+ function TimeInput({
709
+ value,
710
+ defaultValue = null,
711
+ onValueChange,
712
+ min,
713
+ max,
714
+ step = 30,
715
+ size = "md",
716
+ disabled,
717
+ invalid,
718
+ placeholder = "hh:mm",
719
+ className
720
+ }) {
721
+ const field = useFieldWiring();
722
+ const labels = useLabels();
723
+ const controlled = value !== void 0;
724
+ const [internal, setInternal] = useState(defaultValue);
725
+ const current = controlled ? value ?? null : internal;
726
+ const [text2, setText] = useState(() => current ? formatTime(current) : "");
727
+ const [open, setOpen] = useState(false);
728
+ const fieldRef = useRef(null);
729
+ const commit = (next) => {
730
+ if (!controlled) setInternal(next);
731
+ setText(next ? formatTime(next) : "");
732
+ onValueChange?.(next);
733
+ };
734
+ const options = timeOptions(step, min, max);
735
+ return /* @__PURE__ */ jsxs(Popover$1.Root, { open, onOpenChange: setOpen, children: [
736
+ /* @__PURE__ */ jsxs(
737
+ "div",
738
+ {
739
+ ref: fieldRef,
740
+ className: cn("cb-date", `cb-date--${size}`, className),
741
+ "data-disabled": disabled || void 0,
742
+ children: [
743
+ /* @__PURE__ */ jsx(
744
+ "input",
745
+ {
746
+ className: "cb-date__input",
747
+ id: field?.controlId,
748
+ value: controlled ? current ? formatTime(current) : text2 : text2,
749
+ placeholder,
750
+ disabled,
751
+ inputMode: "numeric",
752
+ "aria-describedby": field?.describedBy,
753
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
754
+ onClick: () => !disabled && setOpen(true),
755
+ onChange: (event) => setText(event.target.value),
756
+ onBlur: () => {
757
+ const parsed = parseTime(text2);
758
+ commit(parsed && !isTimeOutOfRange(parsed, min, max) ? parsed : null);
759
+ }
760
+ }
761
+ ),
762
+ /* @__PURE__ */ jsx(
763
+ Popover$1.Trigger,
764
+ {
765
+ className: "cb-date__trigger",
766
+ "aria-label": labels.chooseTime,
767
+ disabled,
768
+ render: /* @__PURE__ */ jsx("button", { type: "button" }),
769
+ children: /* @__PURE__ */ jsx(Clock, { size: 16 })
770
+ }
771
+ )
772
+ ]
773
+ }
774
+ ),
775
+ /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { anchor: fieldRef, side: "bottom", align: "start", sideOffset: 6, children: /* @__PURE__ */ jsx(Popover$1.Popup, { className: "cb-times", children: /* @__PURE__ */ jsx("ul", { className: "cb-times__list", children: options.map((option) => {
776
+ const selected = current ? timeToMinutes(current) === timeToMinutes(option) : false;
777
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
778
+ "button",
779
+ {
780
+ type: "button",
781
+ className: "cb-times__option",
782
+ "data-selected": selected || void 0,
783
+ "aria-pressed": selected,
784
+ onClick: () => {
785
+ commit(option);
786
+ setOpen(false);
787
+ },
788
+ children: formatTime(option)
789
+ }
790
+ ) }, formatTime(option));
791
+ }) }) }) }) })
792
+ ] });
793
+ }
794
+
795
+ // src/form/file-drop.util.ts
796
+ function matchesAccept(file, accept) {
797
+ if (!accept || accept.length === 0) return true;
798
+ const name = file.name.toLowerCase();
799
+ return accept.some((pattern) => {
800
+ const rule = pattern.trim().toLowerCase();
801
+ if (rule.startsWith(".")) return name.endsWith(rule);
802
+ if (rule.endsWith("/*")) return file.type.startsWith(rule.slice(0, -1));
803
+ return file.type === rule;
804
+ });
805
+ }
806
+ function partitionFiles(incoming, existing, rules) {
807
+ const accepted = [];
808
+ const rejected = [];
809
+ const limit = rules.multiple === false ? 1 : rules.maxFiles;
810
+ for (const file of incoming) {
811
+ if (!matchesAccept(file, rules.accept)) {
812
+ rejected.push({ file, reason: "type" });
813
+ continue;
814
+ }
815
+ if (typeof rules.maxSize === "number" && file.size > rules.maxSize) {
816
+ rejected.push({ file, reason: "size" });
817
+ continue;
818
+ }
819
+ if (typeof limit === "number" && existing.length + accepted.length >= limit) {
820
+ rejected.push({ file, reason: "count" });
821
+ continue;
822
+ }
823
+ accepted.push(file);
824
+ }
825
+ return { accepted, rejected };
826
+ }
827
+ function describeAccept(rules) {
828
+ const parts = [];
829
+ if (rules.accept?.length) parts.push(rules.accept.join(", "));
830
+ if (typeof rules.maxSize === "number") parts.push(`up to ${Math.round(rules.maxSize / (1024 * 1024))} MB`);
831
+ return parts.length > 0 ? ` \u2014 ${parts.join(" ")}` : "";
832
+ }
833
+ function FileDrop({
834
+ onFilesChange,
835
+ files = [],
836
+ onReject,
837
+ accept,
838
+ maxSize,
839
+ maxFiles,
840
+ multiple = true,
841
+ disabled,
842
+ children,
843
+ className
844
+ }) {
845
+ const [over, setOver] = useState(false);
846
+ const field = useFieldWiring();
847
+ const labels = useLabels();
848
+ const rules = { accept, maxSize, maxFiles, multiple };
849
+ const take = (incoming) => {
850
+ if (!incoming) return;
851
+ const { accepted, rejected } = partitionFiles(Array.from(incoming), files, rules);
852
+ if (accepted.length > 0) onFilesChange([...files, ...accepted]);
853
+ if (rejected.length > 0) onReject?.(rejected);
854
+ };
855
+ const onDrop = (event) => {
856
+ event.preventDefault();
857
+ setOver(false);
858
+ if (!disabled) take(event.dataTransfer.files);
859
+ };
860
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-filedrop", className), children: [
861
+ /* @__PURE__ */ jsxs(
862
+ "label",
863
+ {
864
+ className: "cb-filedrop__zone",
865
+ "data-over": over || void 0,
866
+ "data-disabled": disabled || void 0,
867
+ onDragOver: (event) => {
868
+ event.preventDefault();
869
+ if (!disabled) setOver(true);
870
+ },
871
+ onDragLeave: () => setOver(false),
872
+ onDrop,
873
+ children: [
874
+ /* @__PURE__ */ jsx(
875
+ "input",
876
+ {
877
+ type: "file",
878
+ className: "cb-visually-hidden",
879
+ id: field?.controlId,
880
+ accept: accept?.join(","),
881
+ multiple,
882
+ disabled,
883
+ "aria-describedby": field?.describedBy,
884
+ onChange: (event) => {
885
+ take(event.target.files);
886
+ event.target.value = "";
887
+ }
888
+ }
889
+ ),
890
+ /* @__PURE__ */ jsx("span", { className: "cb-filedrop__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Upload, { size: 20 }) }),
891
+ /* @__PURE__ */ jsx("span", { className: "cb-filedrop__button", children: multiple ? labels.chooseFiles : labels.chooseFile }),
892
+ /* @__PURE__ */ jsxs("p", { className: "cb-filedrop__hint", children: [
893
+ multiple ? labels.dropFilesHere : labels.dropFileHere,
894
+ describeAccept(rules)
895
+ ] }),
896
+ children
897
+ ]
898
+ }
899
+ ),
900
+ files.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "cb-filedrop__list", children: files.map((file, index) => /* @__PURE__ */ jsxs("li", { className: "cb-filedrop__file", children: [
901
+ /* @__PURE__ */ jsx("span", { className: "cb-filedrop__name", children: file.name }),
902
+ /* @__PURE__ */ jsx("span", { className: "cb-filedrop__size", children: formatSize(file.size) }),
903
+ /* @__PURE__ */ jsx(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ className: "cb-filedrop__remove",
908
+ "aria-label": labels.removeFile(file.name),
909
+ onClick: () => onFilesChange(files.filter((_, i) => i !== index)),
910
+ children: /* @__PURE__ */ jsx(X, { size: 14 })
911
+ }
912
+ )
913
+ ] }, `${file.name}-${index}`)) }) : null
914
+ ] });
915
+ }
916
+ function formatSize(bytes) {
917
+ if (bytes < 1024) return `${bytes} B`;
918
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
919
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
920
+ }
921
+
922
+ // src/form/number.ts
923
+ function parseNumber(input) {
924
+ const trimmed = input.trim().replace(/\s/g, "").replace(",", ".");
925
+ if (trimmed === "" || trimmed === "-" || trimmed === ".") return null;
926
+ const parsed = Number(trimmed);
927
+ return Number.isFinite(parsed) ? parsed : null;
928
+ }
929
+ function clamp(value, { min, max }) {
930
+ let next = value;
931
+ if (typeof min === "number") next = Math.max(next, min);
932
+ if (typeof max === "number") next = Math.min(next, max);
933
+ return next;
934
+ }
935
+ function stepBy(value, direction, bounds) {
936
+ const step = bounds.step ?? 1;
937
+ const base = value ?? bounds.min ?? 0;
938
+ const next = base + direction * step;
939
+ const decimals = decimalPlaces(step);
940
+ const rounded = Number(next.toFixed(decimals));
941
+ return clamp(rounded, bounds);
942
+ }
943
+ function decimalPlaces(step) {
944
+ const text2 = String(step);
945
+ const dot = text2.indexOf(".");
946
+ return dot === -1 ? 0 : text2.length - dot - 1;
947
+ }
948
+ function canStep(value, direction, bounds) {
949
+ if (value === null) return true;
950
+ if (direction === 1) return typeof bounds.max !== "number" || value < bounds.max;
951
+ return typeof bounds.min !== "number" || value > bounds.min;
952
+ }
953
+ function NumberInput({
954
+ value,
955
+ defaultValue = null,
956
+ onValueChange,
957
+ min,
958
+ max,
959
+ step = 1,
960
+ size = "md",
961
+ disabled,
962
+ invalid,
963
+ name,
964
+ placeholder,
965
+ suffix,
966
+ className
967
+ }) {
968
+ const field = useFieldWiring();
969
+ const labels = useLabels();
970
+ const controlled = value !== void 0;
971
+ const [internal, setInternal] = useState(defaultValue);
972
+ const [text2, setText] = useState(() => formatValue(controlled ? value ?? null : defaultValue));
973
+ const current = controlled ? value ?? null : internal;
974
+ const bounds = { min, max, step };
975
+ const commit = (next) => {
976
+ if (!controlled) setInternal(next);
977
+ setText(formatValue(next));
978
+ onValueChange?.(next);
979
+ };
980
+ const onType = (event) => {
981
+ setText(event.target.value);
982
+ const parsed = parseNumber(event.target.value);
983
+ if (parsed === null) {
984
+ onValueChange?.(null);
985
+ if (!controlled) setInternal(null);
986
+ return;
987
+ }
988
+ if (!controlled) setInternal(parsed);
989
+ onValueChange?.(parsed);
990
+ };
991
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-number", `cb-number--${size}`, className), "data-disabled": disabled || void 0, children: [
992
+ /* @__PURE__ */ jsx(
993
+ "input",
994
+ {
995
+ className: "cb-number__input",
996
+ inputMode: "decimal",
997
+ id: field?.controlId,
998
+ name,
999
+ value: controlled ? formatValue(value ?? null) : text2,
1000
+ placeholder,
1001
+ disabled,
1002
+ "aria-describedby": field?.describedBy,
1003
+ "aria-invalid": invalid ?? field?.invalid ? true : void 0,
1004
+ onChange: onType,
1005
+ onBlur: () => {
1006
+ const parsed = parseNumber(text2);
1007
+ commit(parsed === null ? null : clamp(parsed, bounds));
1008
+ }
1009
+ }
1010
+ ),
1011
+ suffix ? /* @__PURE__ */ jsx("span", { className: "cb-number__suffix", "aria-hidden": "true", children: suffix }) : null,
1012
+ /* @__PURE__ */ jsxs("span", { className: "cb-number__steppers", children: [
1013
+ /* @__PURE__ */ jsx(
1014
+ "button",
1015
+ {
1016
+ type: "button",
1017
+ className: "cb-number__step",
1018
+ "aria-label": labels.decrease,
1019
+ disabled: disabled || !canStep(current, -1, bounds),
1020
+ onClick: () => commit(stepBy(current, -1, bounds)),
1021
+ children: /* @__PURE__ */ jsx(Minus, { size: 14 })
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsx(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ className: "cb-number__step",
1029
+ "aria-label": labels.increase,
1030
+ disabled: disabled || !canStep(current, 1, bounds),
1031
+ onClick: () => commit(stepBy(current, 1, bounds)),
1032
+ children: /* @__PURE__ */ jsx(Plus, { size: 14 })
1033
+ }
1034
+ )
1035
+ ] })
1036
+ ] });
1037
+ }
1038
+ function formatValue(value) {
1039
+ return value === null ? "" : String(value);
1040
+ }
1041
+ function Dialog({
1042
+ open,
1043
+ defaultOpen,
1044
+ onOpenChange,
1045
+ title,
1046
+ description,
1047
+ children,
1048
+ footer,
1049
+ size = "md",
1050
+ placement = "center",
1051
+ trigger,
1052
+ className
1053
+ }) {
1054
+ return /* @__PURE__ */ jsxs(Dialog$1.Root, { open, defaultOpen, onOpenChange, children: [
1055
+ trigger ? /* @__PURE__ */ jsx(Dialog$1.Trigger, { render: trigger }) : null,
1056
+ /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [
1057
+ /* @__PURE__ */ jsx(Dialog$1.Backdrop, { className: "cb-dialog__backdrop" }),
1058
+ /* @__PURE__ */ jsxs(
1059
+ Dialog$1.Popup,
1060
+ {
1061
+ className: cn("cb-dialog", `cb-dialog--${size}`, `cb-dialog--${placement}`, className),
1062
+ children: [
1063
+ /* @__PURE__ */ jsx(Dialog$1.Title, { className: "cb-dialog__title", children: title }),
1064
+ description ? /* @__PURE__ */ jsx(Dialog$1.Description, { className: "cb-dialog__description", children: description }) : null,
1065
+ children ? /* @__PURE__ */ jsx("div", { className: "cb-dialog__body", children }) : null,
1066
+ footer ? /* @__PURE__ */ jsx("div", { className: "cb-dialog__footer", children: footer }) : null
1067
+ ]
1068
+ }
1069
+ )
1070
+ ] })
1071
+ ] });
1072
+ }
1073
+ var DialogClose = Dialog$1.Close;
1074
+
1075
+ // src/overlay/command.util.ts
1076
+ function rankCommand(command, query) {
1077
+ const needle = query.trim().toLowerCase();
1078
+ if (!needle) return 1;
1079
+ const label = command.label.toLowerCase();
1080
+ if (label === needle) return 100;
1081
+ if (label.startsWith(needle)) return 80;
1082
+ if (label.split(/\s+/).some((word) => word.startsWith(needle))) return 60;
1083
+ if (label.includes(needle)) return 40;
1084
+ if (command.keywords?.some((keyword) => keyword.toLowerCase().includes(needle))) return 20;
1085
+ return 0;
1086
+ }
1087
+ function filterCommands(commands, query) {
1088
+ return commands.map((command, index) => ({ command, score: rankCommand(command, query), index })).filter((entry) => entry.score > 0).sort((a, b) => b.score === a.score ? a.index - b.index : b.score - a.score).map((entry) => entry.command);
1089
+ }
1090
+ function groupCommands(commands) {
1091
+ const groups = [];
1092
+ for (const command of commands) {
1093
+ const existing = groups.find((entry) => entry.group === command.group);
1094
+ if (existing) existing.items.push(command);
1095
+ else groups.push({ group: command.group, items: [command] });
1096
+ }
1097
+ return groups;
1098
+ }
1099
+ function CommandPalette({
1100
+ open,
1101
+ onOpenChange,
1102
+ commands,
1103
+ placeholder = "Type a command\u2026",
1104
+ emptyMessage = "No matching command",
1105
+ className
1106
+ }) {
1107
+ const [query, setQuery] = useState("");
1108
+ const [active, setActive] = useState(0);
1109
+ const results = useMemo(() => filterCommands(commands, query), [commands, query]);
1110
+ const groups = useMemo(() => groupCommands(results), [results]);
1111
+ useEffect(() => {
1112
+ if (!open) {
1113
+ setQuery("");
1114
+ setActive(0);
1115
+ }
1116
+ }, [open]);
1117
+ useEffect(() => setActive(0), [query]);
1118
+ const run = (command) => {
1119
+ onOpenChange(false);
1120
+ command.onRun();
1121
+ };
1122
+ return /* @__PURE__ */ jsx(Dialog$1.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs(Dialog$1.Portal, { children: [
1123
+ /* @__PURE__ */ jsx(Dialog$1.Backdrop, { className: "cb-dialog__backdrop" }),
1124
+ /* @__PURE__ */ jsxs(Dialog$1.Popup, { className: cn("cb-palette", className), "aria-label": "Command palette", children: [
1125
+ /* @__PURE__ */ jsxs("div", { className: "cb-palette__search", children: [
1126
+ /* @__PURE__ */ jsx(Search, { size: 16, className: "cb-palette__search-icon" }),
1127
+ /* @__PURE__ */ jsx(
1128
+ "input",
1129
+ {
1130
+ className: "cb-palette__input",
1131
+ value: query,
1132
+ placeholder,
1133
+ autoFocus: true,
1134
+ role: "combobox",
1135
+ "aria-expanded": true,
1136
+ "aria-controls": "cb-palette-list",
1137
+ "aria-activedescendant": results[active] ? `cb-palette-${results[active].id}` : void 0,
1138
+ onChange: (event) => setQuery(event.target.value),
1139
+ onKeyDown: (event) => {
1140
+ if (event.key === "ArrowDown") {
1141
+ setActive((index) => Math.min(index + 1, results.length - 1));
1142
+ event.preventDefault();
1143
+ }
1144
+ if (event.key === "ArrowUp") {
1145
+ setActive((index) => Math.max(index - 1, 0));
1146
+ event.preventDefault();
1147
+ }
1148
+ if (event.key === "Enter" && results[active]) {
1149
+ run(results[active]);
1150
+ event.preventDefault();
1151
+ }
1152
+ }
1153
+ }
1154
+ )
1155
+ ] }),
1156
+ /* @__PURE__ */ jsx("div", { className: "cb-palette__list", id: "cb-palette-list", role: "listbox", children: results.length === 0 ? /* @__PURE__ */ jsx("p", { className: "cb-palette__empty", children: emptyMessage }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "cb-palette__group", children: [
1157
+ group.group ? /* @__PURE__ */ jsx("p", { className: "cb-palette__group-label", children: group.group }) : null,
1158
+ group.items.map((command) => {
1159
+ const index = results.indexOf(command);
1160
+ return /* @__PURE__ */ jsxs(
1161
+ "div",
1162
+ {
1163
+ id: `cb-palette-${command.id}`,
1164
+ role: "option",
1165
+ "aria-selected": index === active,
1166
+ className: "cb-palette__item",
1167
+ "data-active": index === active || void 0,
1168
+ onMouseMove: () => setActive(index),
1169
+ onClick: () => run(command),
1170
+ children: [
1171
+ /* @__PURE__ */ jsx("span", { className: "cb-palette__icon", children: command.icon }),
1172
+ /* @__PURE__ */ jsx("span", { className: "cb-palette__label", children: command.label }),
1173
+ command.shortcut ? /* @__PURE__ */ jsx("kbd", { className: "cb-palette__shortcut", children: command.shortcut }) : null
1174
+ ]
1175
+ },
1176
+ command.id
1177
+ );
1178
+ })
1179
+ ] }, group.group ?? "ungrouped")) })
1180
+ ] })
1181
+ ] }) });
1182
+ }
1183
+ function ProgressBar({ value, max = 100, tone = "brand", size = "md", label, showValue, className }) {
1184
+ const indeterminate = value === void 0;
1185
+ const clamped = indeterminate ? 0 : Math.min(Math.max(value, 0), max);
1186
+ const percent = max > 0 ? Math.round(clamped / max * 100) : 0;
1187
+ return /* @__PURE__ */ jsxs("div", { className: cn("cb-progress", className), "data-tone": tone, "data-size": size, children: [
1188
+ /* @__PURE__ */ jsx(
1189
+ "div",
1190
+ {
1191
+ className: "cb-progress__track",
1192
+ role: "progressbar",
1193
+ "aria-label": label,
1194
+ "aria-valuemin": indeterminate ? void 0 : 0,
1195
+ "aria-valuemax": indeterminate ? void 0 : max,
1196
+ "aria-valuenow": indeterminate ? void 0 : clamped,
1197
+ children: /* @__PURE__ */ jsx(
1198
+ "div",
1199
+ {
1200
+ className: "cb-progress__fill",
1201
+ "data-indeterminate": indeterminate || void 0,
1202
+ style: indeterminate ? void 0 : { width: `${percent}%` }
1203
+ }
1204
+ )
1205
+ }
1206
+ ),
1207
+ showValue && !indeterminate ? /* @__PURE__ */ jsxs("span", { className: "cb-progress__value", children: [
1208
+ percent,
1209
+ "%"
1210
+ ] }) : null
1211
+ ] });
1212
+ }
1213
+ function Checklist({ title = "Get started", tasks, completeSlot, className }) {
1214
+ const done = tasks.filter((task) => task.done).length;
1215
+ const complete = tasks.length > 0 && done === tasks.length;
1216
+ return /* @__PURE__ */ jsxs("section", { className: cn("cb-checklist", className), "aria-label": typeof title === "string" ? title : "Checklist", children: [
1217
+ /* @__PURE__ */ jsxs("header", { className: "cb-checklist__head", children: [
1218
+ /* @__PURE__ */ jsx("p", { className: "cb-checklist__title", children: title }),
1219
+ /* @__PURE__ */ jsxs("span", { className: "cb-checklist__count", children: [
1220
+ done,
1221
+ " of ",
1222
+ tasks.length
1223
+ ] })
1224
+ ] }),
1225
+ /* @__PURE__ */ jsx(ProgressBar, { value: done, max: Math.max(tasks.length, 1), size: "sm", label: `${done} of ${tasks.length} tasks done` }),
1226
+ complete && completeSlot ? /* @__PURE__ */ jsx("div", { className: "cb-checklist__complete", children: completeSlot }) : /* @__PURE__ */ jsx("ul", { className: "cb-checklist__list", children: tasks.map((task) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
1227
+ "button",
1228
+ {
1229
+ type: "button",
1230
+ className: "cb-checklist__task",
1231
+ "data-done": task.done || void 0,
1232
+ onClick: task.onSelect,
1233
+ disabled: !task.onSelect,
1234
+ children: [
1235
+ /* @__PURE__ */ jsx("span", { className: "cb-checklist__marker", "aria-hidden": "true", children: task.done ? /* @__PURE__ */ jsx(Check, { size: 12, strokeWidth: 3 }) : null }),
1236
+ /* @__PURE__ */ jsxs("span", { className: "cb-checklist__text", children: [
1237
+ /* @__PURE__ */ jsxs("span", { className: "cb-checklist__label", children: [
1238
+ task.label,
1239
+ /* @__PURE__ */ jsx("span", { className: "cb-visually-hidden", children: task.done ? " (done)" : "" })
1240
+ ] }),
1241
+ task.description ? /* @__PURE__ */ jsx("span", { className: "cb-checklist__description", children: task.description }) : null
1242
+ ] }),
1243
+ task.onSelect && !task.done ? /* @__PURE__ */ jsx(ChevronRight, { size: 16, className: "cb-checklist__chevron" }) : null
1244
+ ]
1245
+ }
1246
+ ) }, task.id)) })
1247
+ ] });
1248
+ }
1249
+ function Popover({
1250
+ trigger,
1251
+ children,
1252
+ side = "bottom",
1253
+ align = "center",
1254
+ sideOffset = 8,
1255
+ showArrow = true,
1256
+ open,
1257
+ onOpenChange,
1258
+ className
1259
+ }) {
1260
+ return /* @__PURE__ */ jsxs(Popover$1.Root, { open, onOpenChange, children: [
1261
+ /* @__PURE__ */ jsx(Popover$1.Trigger, { render: trigger }),
1262
+ /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { side, align, sideOffset, children: /* @__PURE__ */ jsxs(Popover$1.Popup, { className: cn("cb-popover", className), children: [
1263
+ showArrow ? /* @__PURE__ */ jsx(Popover$1.Arrow, { className: "cb-popover__arrow", children: /* @__PURE__ */ jsx(ArrowShape, {}) }) : null,
1264
+ children
1265
+ ] }) }) })
1266
+ ] });
1267
+ }
1268
+ function Tooltip({ children, label, side = "top", delay = 400 }) {
1269
+ return /* @__PURE__ */ jsx(Tooltip$1.Provider, { delay, children: /* @__PURE__ */ jsxs(Tooltip$1.Root, { children: [
1270
+ /* @__PURE__ */ jsx(Tooltip$1.Trigger, { render: children }),
1271
+ /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(Tooltip$1.Positioner, { side, sideOffset: 6, children: /* @__PURE__ */ jsx(Tooltip$1.Popup, { className: "cb-tooltip", children: label }) }) })
1272
+ ] }) });
1273
+ }
1274
+ function ArrowShape() {
1275
+ return /* @__PURE__ */ jsx("span", { className: "cb-arrow", "aria-hidden": "true" });
1276
+ }
1277
+ var ICONS = {
1278
+ neutral: /* @__PURE__ */ jsx(Info, { size: 18 }),
1279
+ brand: /* @__PURE__ */ jsx(Info, { size: 18 }),
1280
+ info: /* @__PURE__ */ jsx(Info, { size: 18 }),
1281
+ success: /* @__PURE__ */ jsx(CheckCircle2, { size: 18 }),
1282
+ warning: /* @__PURE__ */ jsx(AlertTriangle, { size: 18 }),
1283
+ danger: /* @__PURE__ */ jsx(XCircle, { size: 18 })
1284
+ };
1285
+ function Alert({ title, children, tone = "info", icon, actions, onDismiss, className }) {
1286
+ const labels = useLabels();
1287
+ const assertive = tone === "danger" || tone === "warning";
1288
+ return /* @__PURE__ */ jsxs(
1289
+ "div",
1290
+ {
1291
+ className: cn("cb-alert", className),
1292
+ "data-tone": tone,
1293
+ role: assertive ? "alert" : "status",
1294
+ "aria-live": assertive ? "assertive" : "polite",
1295
+ children: [
1296
+ icon === null ? null : /* @__PURE__ */ jsx("span", { className: "cb-alert__icon", children: icon ?? ICONS[tone] }),
1297
+ /* @__PURE__ */ jsxs("div", { className: "cb-alert__body", children: [
1298
+ title ? /* @__PURE__ */ jsx("p", { className: "cb-alert__title", children: title }) : null,
1299
+ children ? /* @__PURE__ */ jsx("div", { className: "cb-alert__text", children }) : null,
1300
+ actions ? /* @__PURE__ */ jsx("div", { className: "cb-alert__actions", children: actions }) : null
1301
+ ] }),
1302
+ onDismiss ? /* @__PURE__ */ jsx("button", { type: "button", className: "cb-alert__close", "aria-label": labels.dismiss, onClick: onDismiss, children: /* @__PURE__ */ jsx(X, { size: 16 }) }) : null
1303
+ ]
1304
+ }
1305
+ );
1306
+ }
1307
+ function ToastProvider({
1308
+ children,
1309
+ timeout = 5e3,
1310
+ limit = 4,
1311
+ position = "bottom-end"
1312
+ }) {
1313
+ return /* @__PURE__ */ jsxs(Toast.Provider, { timeout, limit, children: [
1314
+ children,
1315
+ /* @__PURE__ */ jsx(Toast.Portal, { children: /* @__PURE__ */ jsx(Toast.Viewport, { className: "cb-toast__viewport", "data-position": position, children: /* @__PURE__ */ jsx(ToastList, {}) }) })
1316
+ ] });
1317
+ }
1318
+ var ICONS2 = {
1319
+ success: /* @__PURE__ */ jsx(CheckCircle2, { size: 16 }),
1320
+ warning: /* @__PURE__ */ jsx(AlertTriangle, { size: 16 }),
1321
+ danger: /* @__PURE__ */ jsx(XCircle, { size: 16 }),
1322
+ error: /* @__PURE__ */ jsx(XCircle, { size: 16 }),
1323
+ info: /* @__PURE__ */ jsx(Info, { size: 16 })
1324
+ };
1325
+ function ToastList() {
1326
+ const { toasts } = Toast.useToastManager();
1327
+ const labels = useLabels();
1328
+ return toasts.map((toast) => /* @__PURE__ */ jsxs(Toast.Root, { toast, className: cn("cb-toast"), "data-tone": toast.type ?? "neutral", children: [
1329
+ /* @__PURE__ */ jsx("span", { className: "cb-toast__icon", children: ICONS2[toast.type ?? "info"] ?? ICONS2.info }),
1330
+ /* @__PURE__ */ jsxs(Toast.Content, { className: "cb-toast__content", children: [
1331
+ /* @__PURE__ */ jsx(Toast.Title, { className: "cb-toast__title" }),
1332
+ /* @__PURE__ */ jsx(Toast.Description, { className: "cb-toast__description" })
1333
+ ] }),
1334
+ /* @__PURE__ */ jsx(Toast.Action, { className: "cb-toast__action" }),
1335
+ /* @__PURE__ */ jsx(Toast.Close, { className: "cb-toast__close", "aria-label": labels.dismiss, children: /* @__PURE__ */ jsx(X, { size: 14 }) })
1336
+ ] }, toast.id));
1337
+ }
1338
+ function useToast() {
1339
+ const manager = Toast.useToastManager();
1340
+ return {
1341
+ show: ({ title, description, tone = "info", timeout, action }) => manager.add({
1342
+ title,
1343
+ description,
1344
+ type: tone,
1345
+ // Errors do not time out: something went wrong is not a thing to miss by looking away.
1346
+ timeout: timeout ?? (tone === "danger" ? 0 : void 0),
1347
+ ...action ? { actionProps: { children: action.label, onClick: action.onClick } } : {}
1348
+ }),
1349
+ close: (id) => manager.close(id),
1350
+ promise: manager.promise
1351
+ };
1352
+ }
1353
+ function Tabs({ items, value, defaultValue, onValueChange, variant = "underline", className }) {
1354
+ return /* @__PURE__ */ jsxs(
1355
+ Tabs$1.Root,
1356
+ {
1357
+ value,
1358
+ defaultValue: defaultValue ?? items[0]?.value,
1359
+ onValueChange: (next) => onValueChange?.(String(next)),
1360
+ className: cn("cb-tabs", `cb-tabs--${variant}`, className),
1361
+ children: [
1362
+ /* @__PURE__ */ jsxs(Tabs$1.List, { className: "cb-tabs__list", children: [
1363
+ items.map((item) => /* @__PURE__ */ jsxs(Tabs$1.Tab, { value: item.value, disabled: item.disabled, className: "cb-tabs__tab", children: [
1364
+ item.label,
1365
+ item.adornment ? /* @__PURE__ */ jsx("span", { className: "cb-tabs__adornment", children: item.adornment }) : null
1366
+ ] }, item.value)),
1367
+ /* @__PURE__ */ jsx(Tabs$1.Indicator, { className: "cb-tabs__indicator" })
1368
+ ] }),
1369
+ items.map((item) => /* @__PURE__ */ jsx(Tabs$1.Panel, { value: item.value, className: "cb-tabs__panel", children: item.content }, item.value))
1370
+ ]
1371
+ }
1372
+ );
1373
+ }
1374
+ function DropdownMenu({ trigger, items, align = "end", side = "bottom", className }) {
1375
+ return /* @__PURE__ */ jsxs(Menu.Root, { children: [
1376
+ /* @__PURE__ */ jsx(Menu.Trigger, { render: trigger }),
1377
+ /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { side, align, sideOffset: 6, children: /* @__PURE__ */ jsx(Menu.Popup, { className: cn("cb-menu", className), children: items.map(
1378
+ (item, index) => item.separator ? /* @__PURE__ */ jsx(Menu.Separator, { className: "cb-menu__separator" }, `separator-${index}`) : /* @__PURE__ */ jsxs(
1379
+ Menu.Item,
1380
+ {
1381
+ className: "cb-menu__item",
1382
+ "data-tone": item.tone,
1383
+ disabled: item.disabled,
1384
+ onClick: item.onSelect,
1385
+ children: [
1386
+ /* @__PURE__ */ jsx("span", { className: "cb-menu__icon", children: item.checked ? /* @__PURE__ */ jsx(Check, { size: 14 }) : item.icon }),
1387
+ /* @__PURE__ */ jsx("span", { className: "cb-menu__label", children: item.label }),
1388
+ item.shortcut ? /* @__PURE__ */ jsx("kbd", { className: "cb-menu__shortcut", children: item.shortcut }) : null
1389
+ ]
1390
+ },
1391
+ `${String(item.label)}-${index}`
1392
+ )
1393
+ ) }) }) })
1394
+ ] });
1395
+ }
1396
+ var hasActiveChild = (item) => Boolean(item.items?.some((child) => child.active));
1397
+ function Sidebar({ sections, header, footer, collapsed = false, onCollapsedChange, className }) {
1398
+ const labels = useLabels();
1399
+ return /* @__PURE__ */ jsxs(
1400
+ "nav",
1401
+ {
1402
+ className: cn("cb-sidebar", collapsed && "cb-sidebar--collapsed", className),
1403
+ "aria-label": "Main",
1404
+ "data-collapsed": collapsed || void 0,
1405
+ children: [
1406
+ header ? /* @__PURE__ */ jsx("div", { className: "cb-sidebar__header", children: header }) : null,
1407
+ /* @__PURE__ */ jsx("div", { className: "cb-sidebar__body", children: sections.map((section, index) => /* @__PURE__ */ jsxs("div", { className: "cb-sidebar__section", children: [
1408
+ section.title && !collapsed ? /* @__PURE__ */ jsx("p", { className: "cb-sidebar__title", children: section.title }) : null,
1409
+ section.items.map((item, itemIndex) => /* @__PURE__ */ jsx(SidebarEntry, { item, collapsed }, itemIndex))
1410
+ ] }, index)) }),
1411
+ footer ? /* @__PURE__ */ jsx("div", { className: "cb-sidebar__footer", children: footer }) : null,
1412
+ onCollapsedChange ? /* @__PURE__ */ jsx(
1413
+ "button",
1414
+ {
1415
+ type: "button",
1416
+ className: "cb-sidebar__toggle",
1417
+ "aria-label": collapsed ? labels.expandNavigation : labels.collapseNavigation,
1418
+ "aria-expanded": !collapsed,
1419
+ onClick: () => onCollapsedChange(!collapsed),
1420
+ children: collapsed ? /* @__PURE__ */ jsx(PanelLeftOpen, { size: 16 }) : /* @__PURE__ */ jsx(PanelLeftClose, { size: 16 })
1421
+ }
1422
+ ) : null
1423
+ ]
1424
+ }
1425
+ );
1426
+ }
1427
+ function SidebarEntry({ item, collapsed }) {
1428
+ const children = item.items ?? [];
1429
+ const [open, setOpen] = useState(() => hasActiveChild(item));
1430
+ if (children.length === 0) {
1431
+ const row = /* @__PURE__ */ jsx(SidebarRow, { item, collapsed });
1432
+ return collapsed ? /* @__PURE__ */ jsx(Flyout, { label: item.label, children: row }) : row;
1433
+ }
1434
+ if (collapsed) {
1435
+ return /* @__PURE__ */ jsx(Flyout, { label: item.label, items: children, children: /* @__PURE__ */ jsx(SidebarRow, { item: { ...item, active: item.active || hasActiveChild(item) }, collapsed: true }) });
1436
+ }
1437
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1438
+ /* @__PURE__ */ jsxs(
1439
+ "button",
1440
+ {
1441
+ type: "button",
1442
+ className: "cb-sidebar__item",
1443
+ "data-active": item.active || hasActiveChild(item) || void 0,
1444
+ "aria-expanded": open,
1445
+ onClick: () => setOpen((value) => !value),
1446
+ children: [
1447
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "cb-sidebar__icon", children: item.icon }) : null,
1448
+ /* @__PURE__ */ jsx("span", { className: "cb-sidebar__label", children: item.label }),
1449
+ item.adornment ? /* @__PURE__ */ jsx("span", { className: "cb-sidebar__adornment", children: item.adornment }) : null,
1450
+ /* @__PURE__ */ jsx(ChevronRight, { size: 14, className: "cb-sidebar__chevron", "data-open": open || void 0 })
1451
+ ]
1452
+ }
1453
+ ),
1454
+ open ? /* @__PURE__ */ jsx("div", { className: "cb-sidebar__children", children: children.map((child, index) => /* @__PURE__ */ jsx(SidebarRow, { item: child, collapsed: false, nested: true }, index)) }) : null
1455
+ ] });
1456
+ }
1457
+ function SidebarRow({ item, collapsed, nested }) {
1458
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1459
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "cb-sidebar__icon", children: item.icon }) : null,
1460
+ /* @__PURE__ */ jsx("span", { className: "cb-sidebar__label", children: item.label }),
1461
+ item.adornment && !collapsed ? /* @__PURE__ */ jsx("span", { className: "cb-sidebar__adornment", children: item.adornment }) : null
1462
+ ] });
1463
+ const props = {
1464
+ className: cn("cb-sidebar__item", nested && "cb-sidebar__item--nested"),
1465
+ "data-active": item.active || void 0,
1466
+ "aria-current": item.active ? "page" : void 0
1467
+ };
1468
+ return item.href ? /* @__PURE__ */ jsx("a", { href: item.href, ...props, children: content }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: item.onClick, ...props, children: content });
1469
+ }
1470
+ function Flyout({ label, items, children }) {
1471
+ return /* @__PURE__ */ jsxs(Popover$1.Root, { children: [
1472
+ /* @__PURE__ */ jsx(
1473
+ Popover$1.Trigger,
1474
+ {
1475
+ openOnHover: true,
1476
+ delay: 120,
1477
+ nativeButton: false,
1478
+ render: /* @__PURE__ */ jsx("span", { className: "cb-sidebar__flyout-trigger", children })
1479
+ }
1480
+ ),
1481
+ /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(Popover$1.Positioner, { side: "right", align: "start", sideOffset: 8, className: "cb-sidebar__flyout-positioner", children: /* @__PURE__ */ jsxs(Popover$1.Popup, { className: "cb-sidebar__flyout", children: [
1482
+ /* @__PURE__ */ jsx("p", { className: "cb-sidebar__flyout-title", children: label }),
1483
+ items?.length ? /* @__PURE__ */ jsx("div", { className: "cb-sidebar__flyout-items", children: items.map((child, index) => /* @__PURE__ */ jsx(SidebarRow, { item: child, collapsed: false }, index)) }) : null
1484
+ ] }) }) })
1485
+ ] });
1486
+ }
1487
+ function TopBar({ title, subtitle, center, actions, sticky = true, className }) {
1488
+ return /* @__PURE__ */ jsxs("header", { className: cn("cb-topbar", sticky && "cb-topbar--sticky", className), children: [
1489
+ /* @__PURE__ */ jsxs("div", { className: "cb-topbar__lead", children: [
1490
+ title ? /* @__PURE__ */ jsx("div", { className: "cb-topbar__title", children: title }) : null,
1491
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "cb-topbar__subtitle", children: subtitle }) : null
1492
+ ] }),
1493
+ center ? /* @__PURE__ */ jsx("div", { className: "cb-topbar__center", children: center }) : null,
1494
+ actions ? /* @__PURE__ */ jsx("div", { className: "cb-topbar__actions", children: actions }) : null
1495
+ ] });
1496
+ }
1497
+ function box({ width = "100%", height = "1rem", radius = "md", className }) {
1498
+ const style = { "--cb-skeleton-w": width, "--cb-skeleton-h": height };
1499
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("cb-skeleton", `cb-radius--${radius}`, className), style });
1500
+ }
1501
+ function text({ lines = 3, lastLineWidth = "62%", className }) {
1502
+ return /* @__PURE__ */ jsx("span", { className: cn("cb-skeleton-text", className), children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx(SkeletonBox, { height: "0.75rem", width: i === lines - 1 ? lastLineWidth : "100%" }, i)) });
1503
+ }
1504
+ function circle({ size = "2.5rem", className }) {
1505
+ return /* @__PURE__ */ jsx(SkeletonBox, { width: size, height: size, radius: "full", className });
1506
+ }
1507
+ var SkeletonBox = box;
1508
+ var Skeleton = Object.assign(box, {
1509
+ Text: text,
1510
+ Circle: circle,
1511
+ Rect: box
1512
+ });
1513
+
1514
+ // src/data/table-sort.ts
1515
+ function nextSort(current, column) {
1516
+ if (!current || current.column !== column) return { column, direction: "asc" };
1517
+ if (current.direction === "asc") return { column, direction: "desc" };
1518
+ return null;
1519
+ }
1520
+ function ariaSortFor(current, column) {
1521
+ if (!current || current.column !== column) return "none";
1522
+ return current.direction === "asc" ? "ascending" : "descending";
1523
+ }
1524
+ function pageRange(page, pageSize, total, siblings = 1) {
1525
+ const totalPages = Math.max(Math.ceil(total / Math.max(pageSize, 1)), 1);
1526
+ const current = Math.min(Math.max(page, 1), totalPages);
1527
+ const from = total === 0 ? 0 : (current - 1) * pageSize + 1;
1528
+ const to = Math.min(current * pageSize, total);
1529
+ const pages = /* @__PURE__ */ new Set([1, totalPages]);
1530
+ for (let offset = -siblings; offset <= siblings; offset += 1) {
1531
+ const candidate = current + offset;
1532
+ if (candidate >= 1 && candidate <= totalPages) pages.add(candidate);
1533
+ }
1534
+ const sorted = [...pages].sort((a, b) => a - b);
1535
+ const items = [];
1536
+ sorted.forEach((value, index) => {
1537
+ const previous = sorted[index - 1];
1538
+ if (previous !== void 0 && value - previous === 2) items.push(previous + 1);
1539
+ else if (previous !== void 0 && value - previous > 2) items.push(null);
1540
+ items.push(value);
1541
+ });
1542
+ return { items, totalPages, from, to };
1543
+ }
1544
+ function DataTable({
1545
+ columns,
1546
+ rows,
1547
+ rowKey,
1548
+ label,
1549
+ sort = null,
1550
+ onSortChange,
1551
+ onRowClick,
1552
+ empty,
1553
+ className
1554
+ }) {
1555
+ if (rows.length === 0 && empty) {
1556
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-table__empty", className), children: empty });
1557
+ }
1558
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-table__scroll", className), children: /* @__PURE__ */ jsxs("table", { className: "cb-table", "aria-label": label, children: [
1559
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
1560
+ "th",
1561
+ {
1562
+ scope: "col",
1563
+ style: column.width ? { width: column.width } : void 0,
1564
+ "data-align": column.align,
1565
+ "data-secondary": column.secondary || void 0,
1566
+ "aria-sort": column.sortable ? ariaSortFor(sort, column.key) : void 0,
1567
+ children: column.sortable && onSortChange ? /* @__PURE__ */ jsxs(
1568
+ "button",
1569
+ {
1570
+ type: "button",
1571
+ className: "cb-table__sort",
1572
+ onClick: () => onSortChange(nextSort(sort, column.key)),
1573
+ children: [
1574
+ column.header,
1575
+ /* @__PURE__ */ jsx(SortIcon, { state: sort, column: column.key })
1576
+ ]
1577
+ }
1578
+ ) : column.header
1579
+ },
1580
+ column.key
1581
+ )) }) }),
1582
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsx(
1583
+ "tr",
1584
+ {
1585
+ "data-clickable": onRowClick ? "" : void 0,
1586
+ onClick: onRowClick ? () => onRowClick(row) : void 0,
1587
+ children: columns.map((column) => /* @__PURE__ */ jsx(
1588
+ "td",
1589
+ {
1590
+ "data-align": column.align,
1591
+ "data-secondary": column.secondary || void 0,
1592
+ "data-truncate": column.truncate || void 0,
1593
+ children: column.cell(row)
1594
+ },
1595
+ column.key
1596
+ ))
1597
+ },
1598
+ rowKey(row)
1599
+ )) })
1600
+ ] }) });
1601
+ }
1602
+ function SortIcon({ state, column }) {
1603
+ if (!state || state.column !== column) return /* @__PURE__ */ jsx(ChevronsUpDown, { size: 13, className: "cb-table__sort-idle" });
1604
+ return state.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { size: 13 }) : /* @__PURE__ */ jsx(ArrowDown, { size: 13 });
1605
+ }
1606
+ function DataTableSkeleton({ columns, rows = 5, className }) {
1607
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-table__scroll", className), "aria-hidden": "true", children: /* @__PURE__ */ jsxs("table", { className: "cb-table", children: [
1608
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: Array.from({ length: columns }, (_, index) => /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx(Skeleton, { width: "60%", height: "0.75rem" }) }, index)) }) }),
1609
+ /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ jsx("tr", { children: Array.from({ length: columns }, (_2, cellIndex) => /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Skeleton, { width: cellIndex === 0 ? "70%" : "45%", height: "0.875rem" }) }, cellIndex)) }, rowIndex)) })
1610
+ ] }) });
1611
+ }
1612
+ DataTable.Skeleton = DataTableSkeleton;
1613
+ function Pagination({
1614
+ page,
1615
+ pageSize,
1616
+ total,
1617
+ onPageChange,
1618
+ showSummary = true,
1619
+ siblings = 1,
1620
+ className
1621
+ }) {
1622
+ const labels = useLabels();
1623
+ const { items, totalPages, from, to } = pageRange(page, pageSize, total, siblings);
1624
+ const current = Math.min(Math.max(page, 1), totalPages);
1625
+ return /* @__PURE__ */ jsxs("nav", { className: cn("cb-pagination", className), "aria-label": "Pagination", children: [
1626
+ showSummary ? /* @__PURE__ */ jsx("p", { className: "cb-pagination__summary", children: labels.pageSummary(from, to, total) }) : null,
1627
+ /* @__PURE__ */ jsxs("div", { className: "cb-pagination__controls", children: [
1628
+ /* @__PURE__ */ jsx(
1629
+ "button",
1630
+ {
1631
+ type: "button",
1632
+ className: "cb-pagination__step",
1633
+ "aria-label": labels.previousPage,
1634
+ disabled: current <= 1,
1635
+ onClick: () => onPageChange(current - 1),
1636
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
1637
+ }
1638
+ ),
1639
+ items.map(
1640
+ (item, index) => item === null ? /* @__PURE__ */ jsx("span", { className: "cb-pagination__gap", "aria-hidden": "true", children: "\u2026" }, `gap-${index}`) : /* @__PURE__ */ jsx(
1641
+ "button",
1642
+ {
1643
+ type: "button",
1644
+ className: "cb-pagination__page",
1645
+ "data-active": item === current || void 0,
1646
+ "aria-label": labels.page(item),
1647
+ "aria-current": item === current ? "page" : void 0,
1648
+ onClick: () => onPageChange(item),
1649
+ children: item
1650
+ },
1651
+ item
1652
+ )
1653
+ ),
1654
+ /* @__PURE__ */ jsx(
1655
+ "button",
1656
+ {
1657
+ type: "button",
1658
+ className: "cb-pagination__step",
1659
+ "aria-label": labels.nextPage,
1660
+ disabled: current >= totalPages,
1661
+ onClick: () => onPageChange(current + 1),
1662
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
1663
+ }
1664
+ )
1665
+ ] })
1666
+ ] });
1667
+ }
1668
+ function Image({
1669
+ src,
1670
+ alt,
1671
+ aspectRatio,
1672
+ blurDataUrl,
1673
+ background,
1674
+ fit = "cover",
1675
+ radius = "md",
1676
+ loading = "lazy",
1677
+ className
1678
+ }) {
1679
+ const [loaded, setLoaded] = useState(false);
1680
+ const style = {
1681
+ ...aspectRatio ? { aspectRatio: String(aspectRatio) } : {},
1682
+ ...background ? { background } : {},
1683
+ ...blurDataUrl ? { backgroundImage: `url(${blurDataUrl})` } : {}
1684
+ };
1685
+ return /* @__PURE__ */ jsx(
1686
+ "span",
1687
+ {
1688
+ className: cn("cb-image", `cb-radius--${radius}`, className),
1689
+ "data-loaded": loaded || void 0,
1690
+ "data-blurred": blurDataUrl ? "" : void 0,
1691
+ style,
1692
+ children: /* @__PURE__ */ jsx(
1693
+ "img",
1694
+ {
1695
+ className: "cb-image__img",
1696
+ src,
1697
+ alt,
1698
+ loading,
1699
+ decoding: "async",
1700
+ style: { objectFit: fit },
1701
+ onLoad: () => setLoaded(true),
1702
+ ref: (node) => {
1703
+ if (node?.complete) setLoaded(true);
1704
+ }
1705
+ }
1706
+ )
1707
+ }
1708
+ );
1709
+ }
1710
+
1711
+ // src/media/autoplay.ts
1712
+ function shouldAutoplay(conditions) {
1713
+ if (!conditions.requested) return false;
1714
+ if (conditions.reducedMotion) return false;
1715
+ if (conditions.documentHidden) return false;
1716
+ if (conditions.pointerInside) return false;
1717
+ if (conditions.focusInside) return false;
1718
+ return true;
1719
+ }
1720
+ function CarouselRoot({
1721
+ children,
1722
+ label,
1723
+ slideWidth = "18rem",
1724
+ gap = 4,
1725
+ loop = false,
1726
+ align = "start",
1727
+ autoplay,
1728
+ showArrows = true,
1729
+ showDots = true,
1730
+ className
1731
+ }) {
1732
+ const { enabled: motionEnabled } = useMotionSettings();
1733
+ const labels = useLabels();
1734
+ const [emblaRef, embla] = useEmblaCarousel({ loop, align, containScroll: "trimSnaps" });
1735
+ const [selected, setSelected] = useState(0);
1736
+ const [snapCount, setSnapCount] = useState(0);
1737
+ const [pointerInside, setPointerInside] = useState(false);
1738
+ const [focusInside, setFocusInside] = useState(false);
1739
+ const [documentHidden, setDocumentHidden] = useState(false);
1740
+ const rootRef = useRef(null);
1741
+ useEffect(() => {
1742
+ if (!embla) return;
1743
+ const sync = () => {
1744
+ setSelected(embla.selectedScrollSnap());
1745
+ setSnapCount(embla.scrollSnapList().length);
1746
+ };
1747
+ sync();
1748
+ embla.on("select", sync).on("reInit", sync);
1749
+ return () => {
1750
+ embla.off("select", sync).off("reInit", sync);
1751
+ };
1752
+ }, [embla]);
1753
+ useEffect(() => {
1754
+ const onVisibility = () => setDocumentHidden(document.hidden);
1755
+ onVisibility();
1756
+ document.addEventListener("visibilitychange", onVisibility);
1757
+ return () => document.removeEventListener("visibilitychange", onVisibility);
1758
+ }, []);
1759
+ const running = shouldAutoplay({
1760
+ requested: Boolean(autoplay),
1761
+ pointerInside,
1762
+ focusInside,
1763
+ documentHidden,
1764
+ reducedMotion: !motionEnabled
1765
+ });
1766
+ useEffect(() => {
1767
+ if (!embla || !running || !autoplay) return;
1768
+ const timer = window.setInterval(() => {
1769
+ if (embla.canScrollNext()) embla.scrollNext();
1770
+ else embla.scrollTo(0);
1771
+ }, autoplay);
1772
+ return () => window.clearInterval(timer);
1773
+ }, [embla, running, autoplay]);
1774
+ const onKeyDown = useCallback(
1775
+ (event) => {
1776
+ if (!embla) return;
1777
+ if (event.key === "ArrowRight") {
1778
+ embla.scrollNext();
1779
+ event.preventDefault();
1780
+ }
1781
+ if (event.key === "ArrowLeft") {
1782
+ embla.scrollPrev();
1783
+ event.preventDefault();
1784
+ }
1785
+ },
1786
+ [embla]
1787
+ );
1788
+ const style = {
1789
+ "--cb-carousel-slide": slideWidth,
1790
+ "--cb-carousel-gap": `var(--cb-space-${gap})`
1791
+ };
1792
+ return /* @__PURE__ */ jsxs(
1793
+ "div",
1794
+ {
1795
+ ref: rootRef,
1796
+ className: cn("cb-carousel", className),
1797
+ style,
1798
+ role: "region",
1799
+ "aria-roledescription": "carousel",
1800
+ "aria-label": label,
1801
+ tabIndex: 0,
1802
+ onKeyDown,
1803
+ onPointerEnter: () => setPointerInside(true),
1804
+ onPointerLeave: () => setPointerInside(false),
1805
+ onFocus: () => setFocusInside(true),
1806
+ onBlur: (event) => {
1807
+ if (!rootRef.current?.contains(event.relatedTarget)) setFocusInside(false);
1808
+ },
1809
+ children: [
1810
+ /* @__PURE__ */ jsx("div", { className: "cb-carousel__viewport", ref: emblaRef, children: /* @__PURE__ */ jsx("div", { className: "cb-carousel__track", children }) }),
1811
+ showArrows ? /* @__PURE__ */ jsxs("div", { className: "cb-carousel__arrows", children: [
1812
+ /* @__PURE__ */ jsx(
1813
+ "button",
1814
+ {
1815
+ type: "button",
1816
+ className: "cb-carousel__arrow",
1817
+ "aria-label": labels.previousSlide,
1818
+ onClick: () => embla?.scrollPrev(),
1819
+ disabled: !loop && selected === 0,
1820
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 18 })
1821
+ }
1822
+ ),
1823
+ /* @__PURE__ */ jsx(
1824
+ "button",
1825
+ {
1826
+ type: "button",
1827
+ className: "cb-carousel__arrow",
1828
+ "aria-label": labels.nextSlide,
1829
+ onClick: () => embla?.scrollNext(),
1830
+ disabled: !loop && snapCount > 0 && selected === snapCount - 1,
1831
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 18 })
1832
+ }
1833
+ )
1834
+ ] }) : null,
1835
+ showDots && snapCount > 1 ? /* @__PURE__ */ jsx("div", { className: "cb-carousel__dots", children: Array.from({ length: snapCount }, (_, index) => /* @__PURE__ */ jsx(
1836
+ "button",
1837
+ {
1838
+ type: "button",
1839
+ className: "cb-carousel__dot",
1840
+ "data-active": index === selected || void 0,
1841
+ "aria-label": labels.goToSlide(index + 1),
1842
+ "aria-current": index === selected || void 0,
1843
+ onClick: () => embla?.scrollTo(index)
1844
+ },
1845
+ index
1846
+ )) }) : null
1847
+ ]
1848
+ }
1849
+ );
1850
+ }
1851
+ function CarouselSlide({ children, className }) {
1852
+ return /* @__PURE__ */ jsx("div", { className: cn("cb-carousel__slide", className), role: "group", "aria-roledescription": "slide", children });
1853
+ }
1854
+ function CarouselSkeleton({
1855
+ slides = 3,
1856
+ slideWidth = "18rem",
1857
+ slideHeight = "10rem",
1858
+ gap = 4
1859
+ }) {
1860
+ const style = {
1861
+ "--cb-carousel-slide": slideWidth,
1862
+ "--cb-carousel-gap": `var(--cb-space-${gap})`
1863
+ };
1864
+ return /* @__PURE__ */ jsx("div", { className: "cb-carousel", style, "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "cb-carousel__viewport", children: /* @__PURE__ */ jsx("div", { className: "cb-carousel__track", children: Array.from({ length: slides }, (_, index) => /* @__PURE__ */ jsx("div", { className: "cb-carousel__slide", children: /* @__PURE__ */ jsx(Skeleton, { height: slideHeight, radius: "lg" }) }, index)) }) }) });
1865
+ }
1866
+ var Carousel = Object.assign(CarouselRoot, {
1867
+ Slide: CarouselSlide,
1868
+ Skeleton: CarouselSkeleton
1869
+ });
1870
+ function Coachmark({
1871
+ open,
1872
+ anchor,
1873
+ title,
1874
+ children,
1875
+ side = "bottom",
1876
+ align = "center",
1877
+ spotlight = true,
1878
+ progress,
1879
+ actions,
1880
+ onDismiss,
1881
+ className
1882
+ }) {
1883
+ const labels = useLabels();
1884
+ const rect = useAnchorRect(anchor, open && spotlight !== false);
1885
+ const padding = typeof spotlight === "number" ? spotlight : 8;
1886
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1887
+ rect ? /* @__PURE__ */ jsx(
1888
+ "div",
1889
+ {
1890
+ className: "cb-spotlight",
1891
+ "aria-hidden": "true",
1892
+ onClick: onDismiss,
1893
+ style: {
1894
+ "--cb-spotlight-x": `${rect.left - padding}px`,
1895
+ "--cb-spotlight-y": `${rect.top - padding}px`,
1896
+ "--cb-spotlight-w": `${rect.width + padding * 2}px`,
1897
+ "--cb-spotlight-h": `${rect.height + padding * 2}px`
1898
+ }
1899
+ }
1900
+ ) : null,
1901
+ /* @__PURE__ */ jsx(Popover$1.Root, { open: open && Boolean(anchor), onOpenChange: (next) => !next && onDismiss?.(), children: /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(
1902
+ Popover$1.Positioner,
1903
+ {
1904
+ className: "cb-coachmark__positioner",
1905
+ anchor,
1906
+ side,
1907
+ align,
1908
+ sideOffset: 12,
1909
+ children: /* @__PURE__ */ jsxs(Popover$1.Popup, { className: cn("cb-coachmark", className), children: [
1910
+ /* @__PURE__ */ jsx(Popover$1.Arrow, { className: "cb-popover__arrow", children: /* @__PURE__ */ jsx(ArrowShape, {}) }),
1911
+ /* @__PURE__ */ jsxs("div", { className: "cb-coachmark__head", children: [
1912
+ /* @__PURE__ */ jsx("p", { className: "cb-coachmark__title", children: title }),
1913
+ onDismiss ? /* @__PURE__ */ jsx(
1914
+ "button",
1915
+ {
1916
+ type: "button",
1917
+ className: "cb-coachmark__close",
1918
+ "aria-label": labels.dismiss,
1919
+ onClick: onDismiss,
1920
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
1921
+ }
1922
+ ) : null
1923
+ ] }),
1924
+ children ? /* @__PURE__ */ jsx("div", { className: "cb-coachmark__body", children }) : null,
1925
+ progress || actions ? /* @__PURE__ */ jsxs("div", { className: "cb-coachmark__foot", children: [
1926
+ progress ? /* @__PURE__ */ jsx("span", { className: "cb-coachmark__progress", children: labels.progress(progress.current, progress.total) }) : /* @__PURE__ */ jsx("span", {}),
1927
+ actions ? /* @__PURE__ */ jsx("div", { className: "cb-coachmark__actions", children: actions }) : null
1928
+ ] }) : null
1929
+ ] })
1930
+ }
1931
+ ) }) })
1932
+ ] });
1933
+ }
1934
+ function useAnchorRect(anchor, active) {
1935
+ const [rect, setRect] = useState(null);
1936
+ useEffect(() => {
1937
+ if (!anchor || !active) {
1938
+ setRect(null);
1939
+ return;
1940
+ }
1941
+ const measure = () => setRect(anchor.getBoundingClientRect());
1942
+ measure();
1943
+ const observer = new ResizeObserver(measure);
1944
+ observer.observe(anchor);
1945
+ window.addEventListener("scroll", measure, true);
1946
+ window.addEventListener("resize", measure);
1947
+ return () => {
1948
+ observer.disconnect();
1949
+ window.removeEventListener("scroll", measure, true);
1950
+ window.removeEventListener("resize", measure);
1951
+ };
1952
+ }, [anchor, active]);
1953
+ return rect;
1954
+ }
1955
+
1956
+ // src/onboarding/tour-machine.ts
1957
+ var initialTourState = { index: 0, status: "idle" };
1958
+ function tourReducer(state, action, stepCount) {
1959
+ const lastIndex = Math.max(stepCount - 1, 0);
1960
+ switch (action.type) {
1961
+ case "start":
1962
+ return stepCount === 0 ? { index: 0, status: "finished" } : { index: 0, status: "running" };
1963
+ case "next":
1964
+ if (state.status !== "running") return state;
1965
+ return state.index >= lastIndex ? { index: state.index, status: "finished" } : { index: state.index + 1, status: "running" };
1966
+ case "prev":
1967
+ if (state.status !== "running") return state;
1968
+ return { index: Math.max(state.index - 1, 0), status: "running" };
1969
+ case "goto":
1970
+ if (state.status !== "running") return state;
1971
+ return { index: Math.min(Math.max(action.index, 0), lastIndex), status: "running" };
1972
+ case "skip":
1973
+ return { index: state.index, status: "skipped" };
1974
+ case "finish":
1975
+ return { index: state.index, status: "finished" };
1976
+ default:
1977
+ return state;
1978
+ }
1979
+ }
1980
+ function hasEnded(status) {
1981
+ return status === "finished" || status === "skipped";
1982
+ }
1983
+ function resolveTarget(target, root) {
1984
+ if (!target) return null;
1985
+ if (typeof target === "string") return root.querySelector(target);
1986
+ if (typeof target === "function") return target();
1987
+ if (target instanceof Element) return target;
1988
+ if ("current" in target) return target.current;
1989
+ return null;
1990
+ }
1991
+ function Tour({ id, steps, open = false, onOpenChange, seenStore, onFinish, onSkip, labels }) {
1992
+ const [state, rawDispatch] = useReducer(
1993
+ (current, action) => tourReducer(current, action, steps.length),
1994
+ initialTourState
1995
+ );
1996
+ const [anchor, setAnchor] = useState(null);
1997
+ const { enabled: motionEnabled } = useMotionSettings();
1998
+ const provided = useLabels();
1999
+ const [allowed, setAllowed] = useState(seenStore ? null : true);
2000
+ const marked = useRef(false);
2001
+ const text2 = { back: provided.back, next: provided.next, done: provided.done, skip: provided.skip, ...labels };
2002
+ useEffect(() => {
2003
+ if (!seenStore) return;
2004
+ let cancelled = false;
2005
+ void Promise.resolve(seenStore.has(id)).then((seen) => {
2006
+ if (!cancelled) setAllowed(!seen);
2007
+ });
2008
+ return () => {
2009
+ cancelled = true;
2010
+ };
2011
+ }, [seenStore, id]);
2012
+ useEffect(() => {
2013
+ if (open && allowed && state.status === "idle") rawDispatch({ type: "start" });
2014
+ }, [open, allowed, state.status]);
2015
+ useEffect(() => {
2016
+ if (state.status !== "running") {
2017
+ setAnchor(null);
2018
+ return;
2019
+ }
2020
+ const step2 = steps[state.index];
2021
+ if (!step2) return;
2022
+ let frame = 0;
2023
+ let raf = 0;
2024
+ const look = () => {
2025
+ const found = resolveTarget(step2.target, document);
2026
+ if (found) {
2027
+ setAnchor(found);
2028
+ found.scrollIntoView?.({ block: "center", behavior: motionEnabled ? "smooth" : "auto" });
2029
+ return;
2030
+ }
2031
+ if (frame++ < 30) raf = requestAnimationFrame(look);
2032
+ else setAnchor(null);
2033
+ };
2034
+ look();
2035
+ return () => cancelAnimationFrame(raf);
2036
+ }, [state.status, state.index, steps, motionEnabled]);
2037
+ useEffect(() => {
2038
+ if (!hasEnded(state.status) || marked.current) return;
2039
+ marked.current = true;
2040
+ void Promise.resolve(seenStore?.mark(id));
2041
+ onOpenChange?.(false);
2042
+ if (state.status === "finished") onFinish?.();
2043
+ else onSkip?.();
2044
+ }, [state.status, seenStore, id, onOpenChange, onFinish, onSkip]);
2045
+ const skip = useCallback(() => rawDispatch({ type: "skip" }), []);
2046
+ if (state.status !== "running" || !allowed) return null;
2047
+ const step = steps[state.index];
2048
+ if (!step) return null;
2049
+ const isLast = state.index === steps.length - 1;
2050
+ return /* @__PURE__ */ jsx(
2051
+ Coachmark,
2052
+ {
2053
+ open: true,
2054
+ anchor,
2055
+ title: step.title,
2056
+ side: step.side,
2057
+ align: step.align,
2058
+ spotlight: step.spotlight,
2059
+ progress: { current: state.index + 1, total: steps.length },
2060
+ onDismiss: skip,
2061
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
2062
+ state.index > 0 ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", tone: "neutral", onClick: () => rawDispatch({ type: "prev" }), children: text2.back }) : /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", tone: "neutral", onClick: skip, children: text2.skip }),
2063
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => rawDispatch({ type: "next" }), children: isLast ? text2.done : text2.next })
2064
+ ] }),
2065
+ children: step.content
2066
+ }
2067
+ );
2068
+ }
2069
+ var OFFSETS = {
2070
+ below: (d) => ({ y: d }),
2071
+ above: (d) => ({ y: -d }),
2072
+ left: (d) => ({ x: -d }),
2073
+ right: (d) => ({ x: d }),
2074
+ none: () => ({})
2075
+ };
2076
+ function Reveal({
2077
+ children,
2078
+ from = "below",
2079
+ distance = 12,
2080
+ delay = 0,
2081
+ spring = "soft",
2082
+ onView = false,
2083
+ className
2084
+ }) {
2085
+ const { enabled, spring: transition } = useMotionSettings();
2086
+ const offset = enabled ? OFFSETS[from](distance) : {};
2087
+ const hidden = { opacity: 0, ...offset };
2088
+ const shown = { opacity: 1, x: 0, y: 0 };
2089
+ return /* @__PURE__ */ jsx(
2090
+ motion.div,
2091
+ {
2092
+ className,
2093
+ initial: hidden,
2094
+ ...onView ? { whileInView: shown, viewport: { once: true, margin: "-10% 0px" } } : { animate: shown },
2095
+ transition: { ...transition(spring), delay: enabled ? delay : 0 },
2096
+ children
2097
+ }
2098
+ );
2099
+ }
2100
+ function Stagger({ children, step = 0.06, from = "below", onView = false, className }) {
2101
+ return /* @__PURE__ */ jsx("div", { className, children: Children.map(children, (child, index) => /* @__PURE__ */ jsx(Reveal, { from, delay: index * step, onView, children: child })) });
2102
+ }
2103
+
2104
+ export { Alert, Button, Carousel, Checkbox, Checklist, Coachmark, Combobox, CommandPalette, DEFAULT_LABELS, DataTable, DateInput, Dialog, DialogClose, DropdownMenu, Field, FileDrop, Image, LabelsProvider, MotionProvider, NumberInput, Pagination, Popover, RadioGroup, Reveal, Select, Sidebar, Stagger, Switch, Tabs, TextInput, Textarea, ThemeProvider, TimeInput, ToastProvider, Tooltip, TopBar, Tour, ariaSortFor, canStep, clamp, decimalPlaces, describeAccept, filterCommands, formatISO, formatTime, groupCommands, hasEnded, initialTourState, isOutOfRange, isSameDay, isTimeOutOfRange, matchesAccept, monthMatrix, nextSort, pageRange, parseDateInput, parseNumber, parseTime, partitionFiles, rankCommand, resolveTarget, shouldAutoplay, startOfDay, stepBy, timeOptions, timeToMinutes, tourReducer, useFieldWiring, useLabels, useMotionSettings, useTheme, useToast };