gridsmith-ui 0.15.0 → 0.17.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 (51) hide show
  1. package/dist/charts.d.ts +20 -1
  2. package/dist/charts.js +64 -28
  3. package/dist/charts.js.map +1 -1
  4. package/dist/chat.js +3 -3
  5. package/dist/{chunk-X4CNZFWC.js → chunk-4LDYR7QP.js} +7 -7
  6. package/dist/chunk-4LDYR7QP.js.map +1 -0
  7. package/dist/{chunk-ATONRIEB.js → chunk-J43HCK7Q.js} +36 -13
  8. package/dist/chunk-J43HCK7Q.js.map +1 -0
  9. package/dist/{chunk-XCSTU6IJ.js → chunk-PVTGMCVR.js} +3 -3
  10. package/dist/{chunk-XCSTU6IJ.js.map → chunk-PVTGMCVR.js.map} +1 -1
  11. package/dist/{chunk-LU3YRNES.js → chunk-XGSQOMEY.js} +4 -4
  12. package/dist/{chunk-LU3YRNES.js.map → chunk-XGSQOMEY.js.map} +1 -1
  13. package/dist/{chunk-VFAKME7Q.js → chunk-YQABRUWD.js} +156 -40
  14. package/dist/chunk-YQABRUWD.js.map +1 -0
  15. package/dist/{chunk-M3AARGLI.js → chunk-Z63OSN64.js} +9 -8
  16. package/dist/chunk-Z63OSN64.js.map +1 -0
  17. package/dist/data-grid.js +1 -1
  18. package/dist/editor.js +11 -5
  19. package/dist/editor.js.map +1 -1
  20. package/dist/{feather-B65FI6CY.js → feather-2JFSFLXN.js} +3 -2
  21. package/dist/feather-2JFSFLXN.js.map +1 -0
  22. package/dist/{heroicons-DIR7PXCR.js → heroicons-4AQ4YBVG.js} +3 -2
  23. package/dist/heroicons-4AQ4YBVG.js.map +1 -0
  24. package/dist/icons.d.ts +1 -1
  25. package/dist/icons.js +1 -1
  26. package/dist/index.d.ts +528 -6
  27. package/dist/index.js +2145 -506
  28. package/dist/index.js.map +1 -1
  29. package/dist/kanban.d.ts +11 -1
  30. package/dist/kanban.js +225 -113
  31. package/dist/kanban.js.map +1 -1
  32. package/dist/{lucide-JH2R3TFV.js → lucide-RZC2FEMS.js} +3 -2
  33. package/dist/lucide-RZC2FEMS.js.map +1 -0
  34. package/dist/{phosphor-HCVUMVT5.js → phosphor-CEN25FLM.js} +3 -2
  35. package/dist/phosphor-CEN25FLM.js.map +1 -0
  36. package/dist/resizable.d.ts +11 -1
  37. package/dist/resizable.js +79 -21
  38. package/dist/resizable.js.map +1 -1
  39. package/dist/roots.css +20 -8
  40. package/dist/{tabler-HHIXSM73.js → tabler-YXXC7Y6B.js} +3 -2
  41. package/dist/tabler-YXXC7Y6B.js.map +1 -0
  42. package/package.json +1 -1
  43. package/dist/chunk-ATONRIEB.js.map +0 -1
  44. package/dist/chunk-M3AARGLI.js.map +0 -1
  45. package/dist/chunk-VFAKME7Q.js.map +0 -1
  46. package/dist/chunk-X4CNZFWC.js.map +0 -1
  47. package/dist/feather-B65FI6CY.js.map +0 -1
  48. package/dist/heroicons-DIR7PXCR.js.map +0 -1
  49. package/dist/lucide-JH2R3TFV.js.map +0 -1
  50. package/dist/phosphor-HCVUMVT5.js.map +0 -1
  51. package/dist/tabler-HHIXSM73.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,25 +1,26 @@
1
- import { useAppShell, ControlSizeContext, Button, Spinner, OverlayCloseCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, useDirtyGuard, DiscardConfirm, defaultMaxWidth } from './chunk-ATONRIEB.js';
2
- export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell } from './chunk-ATONRIEB.js';
3
- import { Skeleton } from './chunk-M3AARGLI.js';
4
- export { Skeleton } from './chunk-M3AARGLI.js';
5
- import { usePopupFocus, focusInitial, focusAfterRemoval, useOverlayLayer, rovingTabStop, useGridKeyboard, OVERLAY_CLOSE_ATTR, useNonModalDialog, useFocusTrap, useFocusBeforeOpen, rememberFocus, useReturnFocus } from './chunk-VFAKME7Q.js';
6
- import { Link } from './chunk-LU3YRNES.js';
7
- export { CodeBlock, Divider, Link } from './chunk-LU3YRNES.js';
8
- import { Badge } from './chunk-XCSTU6IJ.js';
9
- export { Badge } from './chunk-XCSTU6IJ.js';
1
+ import { Button, useAppShell, ControlSizeContext, useDirtyGuard, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-J43HCK7Q.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-J43HCK7Q.js';
3
+ import { Skeleton } from './chunk-Z63OSN64.js';
4
+ export { Skeleton } from './chunk-Z63OSN64.js';
5
+ import { FOCUSABLE, usePopupFocus, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, focusAfterRemoval, rovingTabStop, useGridKeyboard, useNonModalDialog, usePressMarks, useReturnFocus, useCloseOnFocusOut } from './chunk-YQABRUWD.js';
6
+ export { useGridKeyboard } from './chunk-YQABRUWD.js';
7
+ import { Link } from './chunk-XGSQOMEY.js';
8
+ export { CodeBlock, Divider, Link } from './chunk-XGSQOMEY.js';
9
+ import { Badge } from './chunk-PVTGMCVR.js';
10
+ export { Badge } from './chunk-PVTGMCVR.js';
10
11
  import { inferColumnKinds, CardValue } from './chunk-OENJCGTJ.js';
11
12
  import { SplitPaneCtx, useShellPreferences, TableCardCtx, PREFERENCE_HOUSE_DEFAULTS, preferenceActionKey } from './chunk-TKVTZWBN.js';
12
13
  export { PreferencesContext, preferenceActionKey, usePreferences } from './chunk-TKVTZWBN.js';
13
- import { useBreakpoint } from './chunk-CRQ6KCRD.js';
14
+ import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
14
15
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
15
16
  import { useTextOverflow, Tooltip, usePosition } from './chunk-EMCSARQO.js';
16
17
  export { Tooltip, useTextOverflow } from './chunk-EMCSARQO.js';
17
18
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
18
19
  import { useLatest } from './chunk-WH2FUFAC.js';
19
- import { Icon } from './chunk-X4CNZFWC.js';
20
- import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, ERROR_ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ERROR_FOCUS_WITHIN_RING, FOCUS_WITHIN_RING, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY } from './chunk-4KVFH5KC.js';
21
- import { createContext, forwardRef, useState, useContext, memo, useEffect, useRef, useCallback, useLayoutEffect, Children, useMemo, useId, isValidElement, cloneElement, Fragment as Fragment$1, useSyncExternalStore } from 'react';
22
- import { createPortal } from 'react-dom';
20
+ import { Icon } from './chunk-4LDYR7QP.js';
21
+ import { cn, GLASS_SURFACE, ACTIVE_RING, GLASS_BACKDROP, GLASS_DROPDOWN, lockBodyScroll, unlockBodyScroll, GLASS_OVERLAY, ERROR_ACTIVE_RING, ERROR_FOCUS_RING, FOCUS_RING, ERROR_HAS_FOCUS_RING, HAS_FOCUS_RING, ERROR_FOCUS_WITHIN_RING, FOCUS_WITHIN_RING } from './chunk-4KVFH5KC.js';
22
+ import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, useId, memo, useEffect, useImperativeHandle, Children, isValidElement, cloneElement, Fragment as Fragment$1, useSyncExternalStore } from 'react';
23
+ import { createPortal, flushSync } from 'react-dom';
23
24
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
24
25
 
25
26
  function isDivider(entry) {
@@ -44,10 +45,10 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
44
45
  useEffect(() => {
45
46
  if (!open) return;
46
47
  const onKeyDown = (e) => {
47
- if (e.key === "Escape" && isTopLayer()) {
48
- returnFocus();
49
- setOpen(false);
50
- }
48
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
49
+ e.preventDefault();
50
+ returnFocus();
51
+ setOpen(false);
51
52
  };
52
53
  const onClick = (e) => {
53
54
  const target = e.target;
@@ -523,7 +524,7 @@ function Dropdown({
523
524
  const els = optionButtons();
524
525
  const current = els.findIndex((el) => el === document.activeElement);
525
526
  if (e.key === "Escape") {
526
- if (!isTopLayer()) return;
527
+ if (e.defaultPrevented || !isTopLayer()) return;
527
528
  e.preventDefault();
528
529
  returnFocus();
529
530
  setOpen(false);
@@ -585,14 +586,14 @@ function Dropdown({
585
586
  };
586
587
  const chipsRef = useRef(null);
587
588
  const pendingChipFocus = useRef(null);
588
- const chipCount = selectedValues.length;
589
589
  useLayoutEffect(() => {
590
- const index = pendingChipFocus.current;
591
- if (index === null) return;
590
+ const pending = pendingChipFocus.current;
591
+ if (pending === null) return;
592
592
  pendingChipFocus.current = null;
593
+ if (selectedValues.includes(pending.value)) return;
593
594
  const chips = Array.from(chipsRef.current?.querySelectorAll("[data-chip-remove]") ?? []);
594
- focusAfterRemoval(chips, index, triggerRef.current);
595
- }, [chipCount]);
595
+ focusAfterRemoval(chips, pending.index, triggerRef.current);
596
+ });
596
597
  const displayLabel = multiple ? selectedValues.length > 0 ? `${selectedValues.length} selected` : placeholder : options.find((o) => o.id === selectedValues[0])?.label ?? placeholder;
597
598
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1, content: displayLabel });
598
599
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
@@ -705,7 +706,7 @@ function Dropdown({
705
706
  {
706
707
  "data-chip-remove": "",
707
708
  onClick: () => {
708
- pendingChipFocus.current = i;
709
+ pendingChipFocus.current = { index: i, value: v };
709
710
  handleSelect(v);
710
711
  },
711
712
  className: "hover:text-[var(--ds-text-primary)] cursor-pointer",
@@ -845,9 +846,14 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
845
846
  } else if (e.key === "Enter" && open && filtered[highlightIdx]) {
846
847
  e.preventDefault();
847
848
  handleSelect(filtered[highlightIdx].value);
848
- } else if (e.key === "Tab" && open && moved && filtered[highlightIdx]) {
849
- onChange(filtered[highlightIdx].value);
850
- } else if (e.key === "Escape") {
849
+ } else if (e.key === "Tab" && open) {
850
+ if (moved && filtered[highlightIdx]) onChange(filtered[highlightIdx].value);
851
+ flushSync(() => {
852
+ setOpen(false);
853
+ setQuery("");
854
+ });
855
+ } else if (e.key === "Escape" && open) {
856
+ e.preventDefault();
851
857
  setOpen(false);
852
858
  setQuery("");
853
859
  }
@@ -1306,12 +1312,12 @@ function parseTypedDate(text, dayFirst = localeDayFirst()) {
1306
1312
  }
1307
1313
  return null;
1308
1314
  }
1309
- function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range }) {
1315
+ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, className, ref, container, error = false, label, id, size: sizeProp, min, max, range: range2 }) {
1310
1316
  const contextSize = useContext(ControlSizeContext);
1311
1317
  const size = sizeProp ?? contextSize ?? "md";
1312
1318
  const [open, setOpen] = useState(false);
1313
1319
  const [hover, setHover] = useState(null);
1314
- const [viewDate, setViewDate] = useState(() => value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date());
1320
+ const [viewDate, setViewDate] = useState(() => value ?? range2?.start ?? range2?.end ?? /* @__PURE__ */ new Date());
1315
1321
  const triggerRef = useRef(null);
1316
1322
  const buttonRef = useRef(null);
1317
1323
  const panelRef = useRef(null);
@@ -1326,12 +1332,12 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1326
1332
  const today = useMemo(() => /* @__PURE__ */ new Date(), []);
1327
1333
  const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
1328
1334
  const isDisabledDay = (d) => !!min && d < startOf(min) || !!max && d > startOf(max);
1329
- const [focusDate, setFocusDate] = useState(() => startOf(value ?? range?.start ?? range?.end ?? /* @__PURE__ */ new Date()));
1335
+ const [focusDate, setFocusDate] = useState(() => startOf(value ?? range2?.start ?? range2?.end ?? /* @__PURE__ */ new Date()));
1330
1336
  const [prevOpen, setPrevOpen] = useState(open);
1331
1337
  if (open !== prevOpen) {
1332
1338
  setPrevOpen(open);
1333
1339
  if (open) {
1334
- const start = startOf(value ?? range?.start ?? range?.end ?? today);
1340
+ const start = startOf(value ?? range2?.start ?? range2?.end ?? today);
1335
1341
  setFocusDate(start);
1336
1342
  setViewDate(new Date(start.getFullYear(), start.getMonth(), 1));
1337
1343
  }
@@ -1362,8 +1368,9 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1362
1368
  const raf = requestAnimationFrame(() => focusActiveDay.current());
1363
1369
  return () => cancelAnimationFrame(raf);
1364
1370
  }, [open, focusActiveDay]);
1365
- const closeAndRefocus = () => {
1371
+ const choose = (d) => {
1366
1372
  returnFocus();
1373
+ onChange(d);
1367
1374
  setOpen(false);
1368
1375
  };
1369
1376
  const dayFirst = useMemo(() => localeDayFirst(), []);
@@ -1388,10 +1395,10 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1388
1395
  setOpen(false);
1389
1396
  };
1390
1397
  const keyHandler = (e) => {
1391
- if (e.key === "Escape" && isTopLayer()) {
1392
- returnFocus();
1393
- setOpen(false);
1394
- }
1398
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
1399
+ e.preventDefault();
1400
+ returnFocus();
1401
+ setOpen(false);
1395
1402
  };
1396
1403
  document.addEventListener("mousedown", handler);
1397
1404
  document.addEventListener("keydown", keyHandler);
@@ -1485,11 +1492,10 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1485
1492
  const selectDay = (day) => {
1486
1493
  const d = new Date(year, month, day);
1487
1494
  if (isDisabledDay(d)) return;
1488
- onChange(d);
1489
- closeAndRefocus();
1495
+ choose(d);
1490
1496
  };
1491
- const rangeA = range?.start ?? null;
1492
- const rangeB = range?.end ?? null;
1497
+ const rangeA = range2?.start ?? null;
1498
+ const rangeB = range2?.end ?? null;
1493
1499
  const otherEnd = value ? rangeA && !isSameDay(rangeA, value) ? rangeA : rangeB : rangeA ?? rangeB;
1494
1500
  const thisEnd = value ?? (otherEnd && hover ? hover : null);
1495
1501
  const [bandStart, bandEnd] = otherEnd && thisEnd ? otherEnd <= thisEnd ? [otherEnd, thisEnd] : [thisEnd, otherEnd] : [null, null];
@@ -1676,8 +1682,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1676
1682
  onClick: () => {
1677
1683
  const now = /* @__PURE__ */ new Date();
1678
1684
  setViewDate(now);
1679
- onChange(now);
1680
- closeAndRefocus();
1685
+ choose(now);
1681
1686
  },
1682
1687
  children: "Today"
1683
1688
  }
@@ -1687,10 +1692,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1687
1692
  {
1688
1693
  variant: "ghost",
1689
1694
  size: "sm",
1690
- onClick: () => {
1691
- onChange(null);
1692
- closeAndRefocus();
1693
- },
1695
+ onClick: () => choose(null),
1694
1696
  children: "Clear"
1695
1697
  }
1696
1698
  )
@@ -1962,6 +1964,10 @@ function DateRangePicker({
1962
1964
  keyOf: dayKey,
1963
1965
  move: (from, e) => moveDay(from, e.key, e.shiftKey, weekStartsOn)
1964
1966
  });
1967
+ const pageMonths = (n) => {
1968
+ setViewDate(addMonths(viewDate, n));
1969
+ setFocusDate(addMonthsClamped(focusDate, n));
1970
+ };
1965
1971
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
1966
1972
  const focusActiveDay = useLatest(grid.focusActive);
1967
1973
  useEffect(() => {
@@ -1977,10 +1983,10 @@ function DateRangePicker({
1977
1983
  setOpen(false);
1978
1984
  };
1979
1985
  const onKey = (e) => {
1980
- if (e.key === "Escape" && isTopLayer()) {
1981
- returnFocus();
1982
- setOpen(false);
1983
- }
1986
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
1987
+ e.preventDefault();
1988
+ returnFocus();
1989
+ setOpen(false);
1984
1990
  };
1985
1991
  document.addEventListener("mousedown", onMouse);
1986
1992
  document.addEventListener("keydown", onKey);
@@ -2071,7 +2077,7 @@ function DateRangePicker({
2071
2077
  }, children: p.label }, p.label)) }),
2072
2078
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
2073
2079
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between bg-[var(--ds-bg-muted)] rounded-tr-[var(--ds-radius-md)] px-ds-3 py-ds-2", children: [
2074
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => setViewDate(addMonths(viewDate, -1)), "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
2080
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => pageMonths(-1), "aria-label": "Previous month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: "rotate-180" }) }),
2075
2081
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: [
2076
2082
  MONTHS2[months[0].getMonth()],
2077
2083
  " ",
@@ -2081,7 +2087,7 @@ function DateRangePicker({
2081
2087
  " ",
2082
2088
  months[1].getFullYear()
2083
2089
  ] }),
2084
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => setViewDate(addMonths(viewDate, 1)), "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
2090
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => pageMonths(1), "aria-label": "Next month", children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) })
2085
2091
  ] }),
2086
2092
  /* @__PURE__ */ jsx("div", { ref: gridRef, onKeyDown: grid.onKeyDown, className: "flex gap-ds-4 px-ds-3 py-ds-2", onMouseLeave: () => setHover(null), children: months.map((m) => /* @__PURE__ */ jsxs("div", { role: "grid", "aria-label": `${MONTHS2[m.getMonth()]} ${m.getFullYear()}`, className: "w-[224px] flex flex-col gap-ds-0.5", children: [
2087
2093
  /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 gap-ds-0.5 mb-ds-0.5", children: dayNames.map((d) => /* @__PURE__ */ jsx("div", { role: "columnheader", className: "text-center text-2xs font-medium text-[var(--ds-text-muted)] py-ds-1", children: d }, d)) }),
@@ -2135,6 +2141,96 @@ function DateRangePicker({
2135
2141
  )
2136
2142
  ] });
2137
2143
  }
2144
+
2145
+ // ../../showcase/src/lib/file-accept.ts
2146
+ var MIME_NAMES = {
2147
+ "text/plain": "TXT",
2148
+ "audio/mpeg": "MP3",
2149
+ "video/quicktime": "MOV",
2150
+ "application/msword": "DOC",
2151
+ "application/vnd.ms-excel": "XLS",
2152
+ "application/vnd.ms-powerpoint": "PPT",
2153
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
2154
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
2155
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "PPTX"
2156
+ };
2157
+ var EXT_NAMES = { jpeg: "JPG", tif: "TIFF", htm: "HTML", yml: "YAML" };
2158
+ var FAMILIES = {
2159
+ image: { one: "an image", many: "images" },
2160
+ video: { one: "a video", many: "videos" },
2161
+ audio: { one: "an audio file", many: "audio files" },
2162
+ text: { one: "a text file", many: "text files" }
2163
+ };
2164
+ var READ_AS_WORD = /* @__PURE__ */ new Set(["FLAC", "HEIC", "HEIF", "LOG", "MIDI", "MOV", "RAR", "RAW"]);
2165
+ var extName = (ext) => EXT_NAMES[ext] ?? ext.toUpperCase();
2166
+ function mimeName(mime) {
2167
+ if (MIME_NAMES[mime]) return MIME_NAMES[mime];
2168
+ const subtype = mime.slice(mime.indexOf("/") + 1).replace(/\+.*$/, "").replace(/^(?:x-|vnd\.)/, "");
2169
+ return extName(subtype.slice(subtype.lastIndexOf(".") + 1));
2170
+ }
2171
+ function parseAccept(accept) {
2172
+ return (accept ?? "").split(",").map((t) => t.trim().toLowerCase()).filter(Boolean).map((t) => {
2173
+ if (t === "*" || t === "*/*") return { kind: "any" };
2174
+ if (t.startsWith(".")) return { kind: "format", name: extName(t.slice(1)), suffix: t };
2175
+ if (t.endsWith("/*")) return { kind: "family", family: t.slice(0, -2) };
2176
+ return { kind: "format", name: mimeName(t), mime: t };
2177
+ });
2178
+ }
2179
+ function fileFormat(file) {
2180
+ const dot = file.name.lastIndexOf(".");
2181
+ return dot > 0 ? extName(file.name.slice(dot + 1).toLowerCase()) : null;
2182
+ }
2183
+ function matches(file, tokens) {
2184
+ if (tokens.length === 0 || tokens.some((t) => t.kind === "any")) return true;
2185
+ const name = file.name.toLowerCase();
2186
+ const format2 = fileFormat(file);
2187
+ const type = file.type.toLowerCase();
2188
+ return tokens.some((t) => {
2189
+ if (t.kind === "family") return type === "" || type.startsWith(`${t.family}/`);
2190
+ if (t.kind !== "format") return false;
2191
+ return t.name === format2 || t.suffix != null && name.endsWith(t.suffix) || t.mime != null && t.mime === type;
2192
+ });
2193
+ }
2194
+ var joinOr = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
2195
+ var article = (name) => {
2196
+ const asWord = name.replace(/[^A-Z]/g, "").length >= 5 || READ_AS_WORD.has(name);
2197
+ return (asWord ? /^[AEIOU]/ : /^[AEFHILMNORSX]/).test(name) ? "an" : "a";
2198
+ };
2199
+ function typeWords(accept) {
2200
+ const tokens = parseAccept(accept);
2201
+ if (tokens.length === 0 || tokens.some((t) => t.kind === "any" || t.kind === "family" && !FAMILIES[t.family])) return null;
2202
+ const families = [...new Set(tokens.flatMap((t) => t.kind === "family" ? [t.family] : []))].map((f) => FAMILIES[f]);
2203
+ const formats2 = [...new Set(tokens.flatMap((t) => t.kind === "format" ? [t.name] : []))];
2204
+ const many = `${joinOr([...families.map((f) => f.many), ...formats2])}${formats2.length ? " files" : ""}`;
2205
+ const one = families.length === 0 ? `${article(formats2[0])} ${joinOr(formats2)}` : joinOr([...families.map((f) => f.one), ...formats2.map((f) => `${article(f)} ${f}`)]);
2206
+ return { one, many };
2207
+ }
2208
+ function sizeWords(bytes) {
2209
+ const units = [["GB", 1024 ** 3], ["MB", 1024 ** 2], ["KB", 1024]];
2210
+ for (const [unit, size] of units) if (bytes >= size) return `${Math.round(bytes / size * 10) / 10} ${unit}`;
2211
+ return `${bytes} bytes`;
2212
+ }
2213
+ var capitalise = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2214
+ function fileConstraints(accept, maxSize, multiple) {
2215
+ const types = typeWords(accept)?.many;
2216
+ const size = maxSize != null ? `up to ${sizeWords(maxSize)}${multiple ? " each" : ""}` : null;
2217
+ const line = [types, size].filter(Boolean).join(" ");
2218
+ return line ? capitalise(line) : null;
2219
+ }
2220
+ function checkFiles(files, { accept, maxSize, multiple }) {
2221
+ if (!multiple && files.length > 1) return { accepted: [], message: "You can only select one file" };
2222
+ const tokens = parseAccept(accept);
2223
+ const types = typeWords(accept);
2224
+ const accepted = [];
2225
+ const reasons = [];
2226
+ for (const file of files) {
2227
+ const subject = multiple ? file.name : "The selected file";
2228
+ if (!matches(file, tokens)) reasons.push(types ? `${subject} must be ${types.one}` : `${subject} is not a file type that can be added here`);
2229
+ else if (maxSize != null && file.size > maxSize) reasons.push(`${subject} must be smaller than ${sizeWords(maxSize)}`);
2230
+ else accepted.push(file);
2231
+ }
2232
+ return { accepted, message: reasons.length ? reasons.join("; ") : null };
2233
+ }
2138
2234
  var variantFills = {
2139
2235
  default: "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]",
2140
2236
  success: "bg-[var(--ds-status-success)]",
@@ -2182,6 +2278,66 @@ function Progress({ value, max = 100, variant = "default", size = "md", indeterm
2182
2278
  ] })
2183
2279
  ] });
2184
2280
  }
2281
+ function InlineFieldError({ message, id, className }) {
2282
+ return /* @__PURE__ */ jsxs(
2283
+ "p",
2284
+ {
2285
+ id,
2286
+ role: "alert",
2287
+ className: cn(
2288
+ "flex items-center gap-ds-1.5 text-xs text-[var(--ds-status-error)]",
2289
+ "mt-ds-1.5",
2290
+ className
2291
+ ),
2292
+ children: [
2293
+ /* @__PURE__ */ jsx(Icon, { name: "error", size: 14, className: "shrink-0" }),
2294
+ /* @__PURE__ */ jsx("span", { children: message })
2295
+ ]
2296
+ }
2297
+ );
2298
+ }
2299
+ var FieldContext = createContext(null);
2300
+ function useField() {
2301
+ return useContext(FieldContext);
2302
+ }
2303
+ function Field({ label, children, hint, error, required, labelAction, id: idProp, className }) {
2304
+ const generated = useId();
2305
+ const id = idProp ?? `field-${generated}`;
2306
+ const hintId = `${id}-hint`;
2307
+ const errorId = `${id}-error`;
2308
+ const hasError = typeof error === "string" && error.length > 0;
2309
+ const describedBy = [hint ? hintId : null, hasError ? errorId : null].filter(Boolean).join(" ") || void 0;
2310
+ const labelId = `${id}-label`;
2311
+ const fieldContext = useMemo(() => ({ controlId: id, labelId, describedBy }), [id, labelId, describedBy]);
2312
+ const controlProps = {
2313
+ id,
2314
+ "aria-describedby": describedBy,
2315
+ ...hasError ? { "aria-invalid": true, error: true } : {},
2316
+ ...required ? { "aria-required": true, required: true } : {}
2317
+ };
2318
+ let control;
2319
+ if (typeof children === "function") {
2320
+ control = children(controlProps);
2321
+ } else {
2322
+ const child = Children.only(children);
2323
+ if (!isValidElement(child)) throw new Error("Field expects a single form control as its child");
2324
+ const isNative = typeof child.type === "string";
2325
+ const { error: errorFlag, ...ariaOnly } = controlProps;
2326
+ control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {} });
2327
+ }
2328
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-ds-1.5", className), children: [
2329
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
2330
+ /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, children: [
2331
+ label,
2332
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
2333
+ ] }),
2334
+ labelAction
2335
+ ] }),
2336
+ /* @__PURE__ */ jsx(FieldContext.Provider, { value: fieldContext, children: control }),
2337
+ hint && /* @__PURE__ */ jsx("p", { id: hintId, className: "text-xs text-[var(--ds-text-muted)]", children: hint }),
2338
+ hasError && /* @__PURE__ */ jsx(InlineFieldError, { id: errorId, message: error, className: "mt-0" })
2339
+ ] });
2340
+ }
2185
2341
  function TruncFileName({ children, className }) {
2186
2342
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
2187
2343
  const el = /* @__PURE__ */ jsx("p", { ref, className: cn("truncate", className), children });
@@ -2193,37 +2349,57 @@ function formatSize(bytes) {
2193
2349
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2194
2350
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2195
2351
  }
2196
- function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label }) {
2352
+ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label, id }) {
2197
2353
  const [files, setFiles] = useState([]);
2198
2354
  const [dragOver, setDragOver] = useState(false);
2199
- const inputId = useId();
2355
+ const [rejected, setRejected] = useState(null);
2200
2356
  const inputRef = useRef(null);
2357
+ const nextEntryId = useRef(0);
2358
+ const timers = useRef(/* @__PURE__ */ new Map());
2359
+ useEffect(() => {
2360
+ const running = timers.current;
2361
+ return () => running.forEach(clearInterval);
2362
+ }, []);
2363
+ const field = useField();
2364
+ const ownId = useId();
2365
+ const inputId = id ?? `${ownId}-input`;
2366
+ const constraintsId = `${ownId}-constraints`;
2367
+ const rejectedId = `${ownId}-rejected`;
2368
+ const showLabel = label != null && !field;
2369
+ const constraints = fileConstraints(accept, maxSize, multiple);
2370
+ const invalid = error || rejected != null;
2371
+ const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null].filter(Boolean).join(" ") || void 0;
2372
+ const stopTimer = (id2) => {
2373
+ clearInterval(timers.current.get(id2));
2374
+ timers.current.delete(id2);
2375
+ };
2201
2376
  const addFiles = useCallback(
2202
2377
  (fileList) => {
2203
- const arr = Array.from(fileList);
2204
- const valid = maxSize ? arr.filter((f) => f.size <= maxSize) : arr;
2205
- const entries = valid.map((f) => ({ file: f, progress: 0, status: "uploading" }));
2206
- setFiles((prev) => [...prev, ...entries]);
2207
- onFiles(valid);
2208
- entries.forEach((_, i) => {
2378
+ const { accepted, message } = checkFiles(Array.from(fileList), { accept, maxSize, multiple });
2379
+ setRejected(message);
2380
+ if (accepted.length === 0) return;
2381
+ const entries = accepted.map((file) => ({ id: nextEntryId.current++, file, progress: 0, status: "uploading" }));
2382
+ if (!multiple) {
2383
+ timers.current.forEach(clearInterval);
2384
+ timers.current.clear();
2385
+ }
2386
+ setFiles((prev) => multiple ? [...prev, ...entries] : entries);
2387
+ onFiles(accepted);
2388
+ for (const { id: id2 } of entries) {
2209
2389
  let progress = 0;
2210
- const interval = setInterval(() => {
2211
- progress += Math.random() * 30 + 10;
2212
- if (progress >= 100) {
2213
- progress = 100;
2214
- clearInterval(interval);
2215
- setFiles(
2216
- (prev) => prev.map((f, fi) => fi === prev.length - entries.length + i ? { ...f, progress: 100, status: "complete" } : f)
2217
- );
2218
- } else {
2219
- setFiles(
2220
- (prev) => prev.map((f, fi) => fi === prev.length - entries.length + i ? { ...f, progress } : f)
2221
- );
2390
+ const timer = setInterval(() => {
2391
+ progress = Math.min(100, progress + Math.random() * 30 + 10);
2392
+ const done = progress >= 100;
2393
+ if (done) {
2394
+ clearInterval(timer);
2395
+ timers.current.delete(id2);
2222
2396
  }
2397
+ setFiles((prev) => prev.map((f) => f.id === id2 ? { ...f, progress, status: done ? "complete" : "uploading" } : f));
2223
2398
  }, 300);
2224
- });
2399
+ timers.current.set(id2, timer);
2400
+ }
2225
2401
  },
2226
- [maxSize, onFiles]
2402
+ [accept, maxSize, multiple, onFiles]
2227
2403
  );
2228
2404
  const handleDrop = useCallback(
2229
2405
  (e) => {
@@ -2233,63 +2409,66 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2233
2409
  },
2234
2410
  [addFiles]
2235
2411
  );
2236
- const removeFile = (index) => {
2237
- setFiles((prev) => prev.filter((_, i) => i !== index));
2412
+ const removeFile = (id2) => {
2413
+ stopTimer(id2);
2414
+ setFiles((prev) => prev.filter((f) => f.id !== id2));
2238
2415
  };
2239
2416
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-ds-3", className), children: [
2240
- label && /* @__PURE__ */ jsx("label", { id: inputId, className: "block text-sm font-medium text-[var(--ds-text-primary)]", children: label }),
2241
- /* @__PURE__ */ jsxs(
2242
- "div",
2243
- {
2244
- "aria-labelledby": label ? inputId : void 0,
2245
- onDragOver: (e) => {
2246
- e.preventDefault();
2247
- setDragOver(true);
2248
- },
2249
- onDragLeave: () => setDragOver(false),
2250
- onDrop: handleDrop,
2251
- onClick: () => inputRef.current?.click(),
2252
- "aria-invalid": error || void 0,
2253
- className: cn(
2254
- "flex flex-col items-center justify-center gap-ds-2 cursor-pointer group",
2255
- "border-2 border-dashed rounded-[var(--ds-radius-md)]",
2256
- "transition-colors",
2257
- // The real file input is visually hidden inside; Tab lands on it and
2258
- // Enter/Space open the picker natively, so the zone shows its focus.
2259
- HAS_FOCUS_RING,
2260
- error ? "border-[var(--ds-status-error)]" : dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : "border-[var(--ds-border-default)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
2261
- ),
2262
- style: { padding: "var(--ds-space-8) var(--ds-space-4)" },
2263
- children: [
2264
- /* @__PURE__ */ jsx("div", { className: cn(
2265
- "flex items-center justify-center w-10 h-10 rounded-[var(--ds-radius-full)] transition-colors",
2266
- "bg-[var(--ds-bg-muted)] group-hover:bg-[var(--ds-accent-subtle)]"
2267
- ), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 20, className: "text-[var(--ds-text-muted)] group-hover:text-[var(--ds-accent-primary)] transition-colors" }) }),
2268
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2269
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: "Drop files here or click to browse" }),
2270
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-[var(--ds-text-muted)] mt-ds-1", children: [
2271
- accept ? `Accepted: ${accept}` : "Any file type",
2272
- maxSize && ` \u2022 Max ${formatSize(maxSize)}`
2273
- ] })
2274
- ] }),
2275
- /* @__PURE__ */ jsx(
2276
- "input",
2277
- {
2278
- ref: inputRef,
2279
- type: "file",
2280
- accept,
2281
- multiple,
2282
- className: "sr-only",
2283
- onChange: (e) => {
2284
- if (e.target.files?.length) addFiles(e.target.files);
2285
- e.target.value = "";
2417
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-1.5", children: [
2418
+ showLabel && /* @__PURE__ */ jsx(Label, { htmlFor: inputId, children: label }),
2419
+ /* @__PURE__ */ jsxs(
2420
+ "div",
2421
+ {
2422
+ onDragOver: (e) => {
2423
+ e.preventDefault();
2424
+ setDragOver(true);
2425
+ },
2426
+ onDragLeave: () => setDragOver(false),
2427
+ onDrop: handleDrop,
2428
+ onClick: () => inputRef.current?.click(),
2429
+ className: cn(
2430
+ "flex flex-col items-center justify-center gap-ds-2 cursor-pointer group",
2431
+ "border-2 border-dashed rounded-[var(--ds-radius-md)]",
2432
+ "transition-colors",
2433
+ // The real file input is visually hidden inside; Tab lands on it and
2434
+ // Enter/Space open the picker natively, so the zone shows its focus.
2435
+ invalid ? ERROR_HAS_FOCUS_RING : HAS_FOCUS_RING,
2436
+ dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
2437
+ ),
2438
+ style: { padding: "var(--ds-space-8) var(--ds-space-4)" },
2439
+ children: [
2440
+ /* @__PURE__ */ jsx("div", { className: cn(
2441
+ "flex items-center justify-center w-10 h-10 rounded-[var(--ds-radius-full)] transition-colors",
2442
+ "bg-[var(--ds-bg-muted)] group-hover:bg-[var(--ds-accent-subtle)]"
2443
+ ), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 20, className: "text-[var(--ds-text-muted)] group-hover:text-[var(--ds-accent-primary)] transition-colors" }) }),
2444
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2445
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: multiple ? "Drop files here or browse" : "Drop a file here or browse" }),
2446
+ constraints && /* @__PURE__ */ jsx("p", { id: constraintsId, className: "text-xs text-[var(--ds-text-muted)] mt-ds-1", children: constraints })
2447
+ ] }),
2448
+ /* @__PURE__ */ jsx(
2449
+ "input",
2450
+ {
2451
+ ref: inputRef,
2452
+ id: inputId,
2453
+ type: "file",
2454
+ accept,
2455
+ multiple,
2456
+ className: "sr-only",
2457
+ "aria-labelledby": field ? field.labelId : void 0,
2458
+ "aria-describedby": describedBy,
2459
+ "aria-invalid": invalid || void 0,
2460
+ onChange: (e) => {
2461
+ if (e.target.files?.length) addFiles(e.target.files);
2462
+ e.target.value = "";
2463
+ }
2286
2464
  }
2287
- }
2288
- )
2289
- ]
2290
- }
2291
- ),
2292
- files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-ds-2", children: files.map((entry, i) => /* @__PURE__ */ jsxs(
2465
+ )
2466
+ ]
2467
+ }
2468
+ ),
2469
+ rejected && /* @__PURE__ */ jsx(InlineFieldError, { id: rejectedId, message: rejected, className: "mt-0" })
2470
+ ] }),
2471
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-ds-2", children: files.map((entry) => /* @__PURE__ */ jsxs(
2293
2472
  "li",
2294
2473
  {
2295
2474
  className: cn(
@@ -2306,10 +2485,10 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2306
2485
  entry.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: entry.progress, size: "sm", className: "mt-ds-1" })
2307
2486
  ] }),
2308
2487
  entry.status === "complete" && /* @__PURE__ */ jsx(Icon, { name: "check", size: 16, className: "text-[var(--ds-status-success)] shrink-0" }),
2309
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => removeFile(i), "aria-label": "Remove file", children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) })
2488
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: () => removeFile(entry.id), "aria-label": "Remove file", children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 14 }) })
2310
2489
  ]
2311
2490
  },
2312
- `${entry.file.name}-${i}`
2491
+ entry.id
2313
2492
  )) })
2314
2493
  ] });
2315
2494
  }
@@ -2324,9 +2503,17 @@ function SliderRange({
2324
2503
  className,
2325
2504
  ref,
2326
2505
  error = false,
2327
- label = "Value",
2506
+ label,
2507
+ "aria-label": ariaLabel,
2508
+ "aria-describedby": describedByProp,
2328
2509
  trackStyle
2329
2510
  }) {
2511
+ const field = useField();
2512
+ const ownId = useId();
2513
+ const showLabel = label != null && !field;
2514
+ const nameId = field ? field.labelId : showLabel ? `${ownId}-label` : void 0;
2515
+ const describedBy = [...new Set([describedByProp, field?.describedBy].join(" ").split(" ").filter(Boolean))].join(" ") || void 0;
2516
+ const thumbName = (part) => nameId ? { "aria-labelledby": part ? `${nameId} ${ownId}-${part}` : nameId } : { "aria-label": ariaLabel ? part ? `${ariaLabel} ${part}` : ariaLabel : void 0 };
2330
2517
  const isRange = Array.isArray(value);
2331
2518
  const low = isRange ? value[0] : value;
2332
2519
  const high = isRange ? value[1] : value;
@@ -2382,7 +2569,14 @@ function SliderRange({
2382
2569
  [disabled, min, max, handleChange]
2383
2570
  );
2384
2571
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("space-y-ds-1", disabled && "opacity-disabled", className), children: [
2385
- showValue && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-mono tabular-nums text-[var(--ds-text-muted)]", children: isRange ? `${low} \u2013 ${high}` : low }) }),
2572
+ (showLabel || showValue) && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-ds-2", showLabel ? "justify-between" : "justify-end"), children: [
2573
+ showLabel && /* @__PURE__ */ jsx("span", { id: `${ownId}-label`, className: "text-sm font-medium leading-none text-[var(--ds-text-secondary)]", children: label }),
2574
+ showValue && /* @__PURE__ */ jsx("span", { className: "text-xs font-mono tabular-nums text-[var(--ds-text-muted)]", children: isRange ? `${low} \u2013 ${high}` : low })
2575
+ ] }),
2576
+ isRange && nameId && /* @__PURE__ */ jsxs(Fragment, { children: [
2577
+ /* @__PURE__ */ jsx("span", { id: `${ownId}-minimum`, hidden: true, children: "minimum" }),
2578
+ /* @__PURE__ */ jsx("span", { id: `${ownId}-maximum`, hidden: true, children: "maximum" })
2579
+ ] }),
2386
2580
  /* @__PURE__ */ jsxs(
2387
2581
  "div",
2388
2582
  {
@@ -2405,7 +2599,8 @@ function SliderRange({
2405
2599
  {
2406
2600
  role: "slider",
2407
2601
  tabIndex: disabled ? -1 : 0,
2408
- "aria-label": `${label} minimum`,
2602
+ ...thumbName("minimum"),
2603
+ "aria-describedby": describedBy,
2409
2604
  "aria-valuemin": min,
2410
2605
  "aria-valuemax": high,
2411
2606
  "aria-valuenow": low,
@@ -2429,7 +2624,8 @@ function SliderRange({
2429
2624
  {
2430
2625
  role: "slider",
2431
2626
  tabIndex: disabled ? -1 : 0,
2432
- "aria-label": isRange ? `${label} maximum` : label,
2627
+ ...thumbName(isRange ? "maximum" : void 0),
2628
+ "aria-describedby": describedBy,
2433
2629
  "aria-valuemin": isRange ? low : min,
2434
2630
  "aria-valuemax": max,
2435
2631
  "aria-valuenow": high,
@@ -2605,10 +2801,10 @@ function TimePicker({
2605
2801
  setOpen(false);
2606
2802
  };
2607
2803
  const keyHandler = (e) => {
2608
- if (e.key === "Escape" && isTopLayer()) {
2609
- returnFocus();
2610
- setOpen(false);
2611
- }
2804
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
2805
+ e.preventDefault();
2806
+ returnFocus();
2807
+ setOpen(false);
2612
2808
  };
2613
2809
  document.addEventListener("mousedown", handler);
2614
2810
  document.addEventListener("keydown", keyHandler);
@@ -3246,14 +3442,14 @@ function TagInput({
3246
3442
  );
3247
3443
  const containerRef = useRef(null);
3248
3444
  const pendingFocus = useRef(null);
3249
- const tagCount = value.length;
3250
3445
  useLayoutEffect(() => {
3251
- const index = pendingFocus.current;
3252
- if (index === null) return;
3446
+ const pending = pendingFocus.current;
3447
+ if (pending === null) return;
3253
3448
  pendingFocus.current = null;
3449
+ if (value.includes(pending.tag)) return;
3254
3450
  const buttons = Array.from(containerRef.current?.querySelectorAll("[data-tag-remove]") ?? []);
3255
- focusAfterRemoval(buttons, index, inputRef.current);
3256
- }, [tagCount]);
3451
+ focusAfterRemoval(buttons, pending.index, inputRef.current);
3452
+ });
3257
3453
  const handleKeyDown = useCallback(
3258
3454
  (e) => {
3259
3455
  if ((e.key === "Enter" || e.key === ",") && input.trim()) {
@@ -3308,7 +3504,7 @@ function TagInput({
3308
3504
  Tag,
3309
3505
  {
3310
3506
  onRemove: disabled ? void 0 : () => {
3311
- pendingFocus.current = i;
3507
+ pendingFocus.current = { index: i, tag };
3312
3508
  removeTag(tag);
3313
3509
  },
3314
3510
  disabled,
@@ -3837,60 +4033,6 @@ function ColorPicker({
3837
4033
  }
3838
4034
  );
3839
4035
  }
3840
- function InlineFieldError({ message, id, className }) {
3841
- return /* @__PURE__ */ jsxs(
3842
- "p",
3843
- {
3844
- id,
3845
- role: "alert",
3846
- className: cn(
3847
- "flex items-center gap-ds-1.5 text-xs text-[var(--ds-status-error)]",
3848
- "mt-ds-1.5",
3849
- className
3850
- ),
3851
- children: [
3852
- /* @__PURE__ */ jsx(Icon, { name: "error", size: 14, className: "shrink-0" }),
3853
- /* @__PURE__ */ jsx("span", { children: message })
3854
- ]
3855
- }
3856
- );
3857
- }
3858
- function Field({ label, children, hint, error, required, labelAction, id: idProp, className }) {
3859
- const generated = useId();
3860
- const id = idProp ?? `field-${generated}`;
3861
- const hintId = `${id}-hint`;
3862
- const errorId = `${id}-error`;
3863
- const hasError = typeof error === "string" && error.length > 0;
3864
- const describedBy = [hint ? hintId : null, hasError ? errorId : null].filter(Boolean).join(" ") || void 0;
3865
- const controlProps = {
3866
- id,
3867
- "aria-describedby": describedBy,
3868
- ...hasError ? { "aria-invalid": true, error: true } : {},
3869
- ...required ? { "aria-required": true, required: true } : {}
3870
- };
3871
- let control;
3872
- if (typeof children === "function") {
3873
- control = children(controlProps);
3874
- } else {
3875
- const child = Children.only(children);
3876
- if (!isValidElement(child)) throw new Error("Field expects a single form control as its child");
3877
- const isNative = typeof child.type === "string";
3878
- const { error: errorFlag, ...ariaOnly } = controlProps;
3879
- control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {} });
3880
- }
3881
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-ds-1.5", className), children: [
3882
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
3883
- /* @__PURE__ */ jsxs(Label, { htmlFor: id, children: [
3884
- label,
3885
- required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
3886
- ] }),
3887
- labelAction
3888
- ] }),
3889
- control,
3890
- hint && /* @__PURE__ */ jsx("p", { id: hintId, className: "text-xs text-[var(--ds-text-muted)]", children: hint }),
3891
- hasError && /* @__PURE__ */ jsx(InlineFieldError, { id: errorId, message: error, className: "mt-0" })
3892
- ] });
3893
- }
3894
4036
  var variantStyles2 = {
3895
4037
  info: "border-[var(--ds-accent-primary)] bg-[var(--ds-accent-subtle)]",
3896
4038
  success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
@@ -4566,7 +4708,6 @@ function MentionInput({
4566
4708
  const value = controlledValue ?? internalValue;
4567
4709
  const [query, setQuery] = useState("");
4568
4710
  const [showPopup, setShowPopup] = useState(false);
4569
- useOverlayLayer(showPopup);
4570
4711
  const [activeIndex, setActiveIndex] = useState(0);
4571
4712
  const [triggerStart, setTriggerStart] = useState(-1);
4572
4713
  const [popupPos, setPopupPos] = useState({ top: 0, left: 0 });
@@ -4576,6 +4717,7 @@ function MentionInput({
4576
4717
  const filtered = suggestions.filter(
4577
4718
  (s) => s.label.toLowerCase().includes(query.toLowerCase()) || s.description?.toLowerCase().includes(query.toLowerCase())
4578
4719
  );
4720
+ useOverlayLayer(showPopup && filtered.length > 0);
4579
4721
  const updateValue = useCallback(
4580
4722
  (v) => {
4581
4723
  if (controlledValue === void 0) setInternalValue(v);
@@ -4654,6 +4796,7 @@ function MentionInput({
4654
4796
  e.preventDefault();
4655
4797
  insertMention(filtered[activeIndex]);
4656
4798
  } else if (e.key === "Escape") {
4799
+ e.preventDefault();
4657
4800
  setShowPopup(false);
4658
4801
  }
4659
4802
  };
@@ -4757,6 +4900,8 @@ function MentionInput({
4757
4900
  ] });
4758
4901
  }
4759
4902
  var CardCtx = createContext(false);
4903
+ var CardActionCtx = createContext(null);
4904
+ var CARD_ACTION_ATTR = "data-card-action";
4760
4905
  var CardSurfaceCtx = createContext(false);
4761
4906
  var variantStyles3 = {
4762
4907
  default: `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
@@ -4876,11 +5021,33 @@ var Card = forwardRef(function Card2({
4876
5021
  const isSelectable = selectable || !!selectType;
4877
5022
  const isToggle = selectType === "toggle";
4878
5023
  const hasSelectType = !!selectType;
5024
+ const isAction = !isSelectable && variant === "interactive" && !!onClick;
5025
+ const rootRef = useRef(null);
5026
+ const setRoot = useCallback((node) => {
5027
+ rootRef.current = node;
5028
+ if (typeof ref === "function") ref(node);
5029
+ else if (ref) ref.current = node;
5030
+ }, [ref]);
5031
+ const [inner, setInner] = useState({ controls: false, titleId: null });
5032
+ useLayoutEffect(() => {
5033
+ const root = rootRef.current;
5034
+ if (!root || !isAction && !checkable) return;
5035
+ const controls = Array.from(root.querySelectorAll(FOCUSABLE)).some((el) => !el.hasAttribute(CARD_ACTION_ATTR));
5036
+ const titleId = root.querySelector("[data-card-title]")?.id || null;
5037
+ setInner((cur) => cur.controls === controls && cur.titleId === titleId ? cur : { controls, titleId });
5038
+ }, [isAction, checkable, loading, children]);
5039
+ const holdsControls = isAction && inner.controls;
5040
+ const action = useMemo(() => holdsControls ? { activate: () => rootRef.current?.click(), disabled } : null, [holdsControls, disabled]);
4879
5041
  const handleClick = isSelectable && !disabled ? (e) => {
4880
5042
  onSelectedChange?.(!selected);
4881
5043
  onClick?.(e);
4882
- } : onClick;
4883
- const isAction = !isSelectable && variant === "interactive" && !!onClick;
5044
+ } : isAction && disabled ? void 0 : onClick;
5045
+ const handleCardClick = (e) => {
5046
+ if (disabled || !(e.target instanceof Element) || !e.currentTarget.contains(e.target)) return;
5047
+ const control = e.target.closest(FOCUSABLE);
5048
+ if (control && control !== e.currentTarget && e.currentTarget.contains(control)) return;
5049
+ onClick?.(e);
5050
+ };
4884
5051
  const handleKeyDown = isSelectable && !disabled ? (e) => {
4885
5052
  if (e.key === "Enter" || e.key === " ") {
4886
5053
  e.preventDefault();
@@ -4903,7 +5070,8 @@ var Card = forwardRef(function Card2({
4903
5070
  e.stopPropagation();
4904
5071
  onSelectedChange?.(!selected);
4905
5072
  };
4906
- const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isSelectable || isAction ? "button" : void 0;
5073
+ const isButton = isSelectable || isAction && !holdsControls;
5074
+ const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isButton ? "button" : void 0;
4907
5075
  const ariaAttr = selectType === "radio" || selectType === "checkbox" || selectType === "toggle" ? { "aria-checked": !!selected } : isSelectable ? { "aria-pressed": !!selected } : {};
4908
5076
  const showRing = isSelectable && !isToggle && !hasSelectType && selected;
4909
5077
  const indicator = selectType === "radio" ? /* @__PURE__ */ jsx(RadioIndicator, { checked: !!selected, disabled }) : selectType === "checkbox" ? /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected, indeterminate, disabled }) : selectType === "toggle" ? /* @__PURE__ */ jsx(ToggleIndicator, { checked: !!selected, disabled }) : null;
@@ -4911,11 +5079,11 @@ var Card = forwardRef(function Card2({
4911
5079
  return /* @__PURE__ */ jsxs(
4912
5080
  "div",
4913
5081
  {
4914
- ref,
5082
+ ref: setRoot,
4915
5083
  "data-card": "",
4916
5084
  role,
4917
- tabIndex: isSelectable || isAction && !disabled ? 0 : void 0,
4918
- "aria-disabled": (isSelectable || isAction) && disabled ? true : void 0,
5085
+ tabIndex: isSelectable || isButton && !disabled ? 0 : void 0,
5086
+ "aria-disabled": isButton && disabled ? true : void 0,
4919
5087
  ...name && selectType === "radio" ? { "data-radio-group": name } : {},
4920
5088
  style: variant === "interactive" ? { ...INTERACTIVE_SCALE_CAP, ...style } : style,
4921
5089
  className: cn(
@@ -4923,15 +5091,15 @@ var Card = forwardRef(function Card2({
4923
5091
  variantStyles3[variant],
4924
5092
  isSelectable && !disabled && "cursor-pointer select-none transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
4925
5093
  isSelectable && !disabled && !selected && "hover:border-[var(--ds-accent-primary)]",
4926
- (isSelectable || isAction) && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
5094
+ isButton && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
4927
5095
  (showRing || hasSelectType && selected) && ACTIVE_RING,
4928
5096
  hasSelectType && selected && "bg-[var(--ds-accent-subtle)]",
4929
5097
  disabled && "opacity-disabled cursor-not-allowed",
4930
5098
  className
4931
5099
  ),
4932
- onClick: handleClick,
4933
- onKeyDown: handleKeyDown,
4934
- onKeyUp: handleKeyUp,
5100
+ onClick: holdsControls ? handleCardClick : handleClick,
5101
+ onKeyDown: holdsControls ? void 0 : handleKeyDown,
5102
+ onKeyUp: holdsControls ? void 0 : handleKeyUp,
4935
5103
  ...ariaAttr,
4936
5104
  ...props,
4937
5105
  children: [
@@ -4944,6 +5112,7 @@ var Card = forwardRef(function Card2({
4944
5112
  onClick: handleCheckableClick,
4945
5113
  role: "checkbox",
4946
5114
  "aria-checked": !!selected,
5115
+ ...inner.titleId ? { "aria-labelledby": inner.titleId } : { "aria-label": "Select" },
4947
5116
  tabIndex: 0,
4948
5117
  onKeyDown: (e) => {
4949
5118
  if (e.key === "Enter" || e.key === " ") {
@@ -4955,7 +5124,7 @@ var Card = forwardRef(function Card2({
4955
5124
  children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected })
4956
5125
  }
4957
5126
  ),
4958
- /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
5127
+ /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
4959
5128
  // Round 15 (the owner's 52.1): a placeholder stands where the content will stand — inside the card's
4960
5129
  // content inset (CardContent's padding), a title-height line then two body lines — never flush against the border.
4961
5130
  /* @__PURE__ */ jsx("div", { "data-card-loading": "", "aria-busy": "true", className: cn("pr-ds-5 py-ds-4", checkable ? "pl-ds-10" : "pl-ds-5"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-ds-3", children: [
@@ -4963,7 +5132,7 @@ var Card = forwardRef(function Card2({
4963
5132
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
4964
5133
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
4965
5134
  ] }) })
4966
- ) : children }) })
5135
+ ) : children }) }) })
4967
5136
  ]
4968
5137
  }
4969
5138
  );
@@ -4981,6 +5150,7 @@ var CardHeader = forwardRef(function CardHeader2({ className, children, actions,
4981
5150
  "button",
4982
5151
  {
4983
5152
  type: "button",
5153
+ "aria-label": "More actions",
4984
5154
  className: cn(
4985
5155
  "inline-flex items-center justify-center h-8 w-8 rounded-[var(--ds-radius-md)]",
4986
5156
  "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
@@ -5017,20 +5187,42 @@ var cardLineClampClasses = {
5017
5187
  5: "line-clamp-5",
5018
5188
  6: "line-clamp-6"
5019
5189
  };
5020
- var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, ...props }, forwardedRef) {
5190
+ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, id, ...props }, forwardedRef) {
5191
+ const action = useContext(CardActionCtx);
5192
+ const generatedId = useId();
5021
5193
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
5022
5194
  const lineClampClass = lines > 0 ? cardLineClampClasses[lines] || `line-clamp-[${lines}]` : "";
5023
5195
  const heading = /* @__PURE__ */ jsx(
5024
5196
  "h3",
5025
5197
  {
5026
5198
  ref: (node) => {
5027
- overflowRef(lines > 0 ? node : null);
5199
+ if (!action) overflowRef(lines > 0 ? node : null);
5028
5200
  if (typeof forwardedRef === "function") forwardedRef(node);
5029
5201
  else if (forwardedRef) forwardedRef.current = node;
5030
5202
  },
5031
- className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", lines > 0 && lineClampClass, className),
5203
+ id: id ?? generatedId,
5204
+ "data-card-title": "",
5205
+ className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", !action && lines > 0 && lineClampClass, className),
5032
5206
  ...props,
5033
- children
5207
+ children: action ? (
5208
+ // The card's action when the card holds controls of its own (Sprint 25.7): a real button, which a click
5209
+ // elsewhere on the card also runs. Its ::after spans the card to draw the card's focus ring, taking no clicks.
5210
+ // The clamp moves inside, since a button lays out its own lines.
5211
+ /* @__PURE__ */ jsx(
5212
+ "button",
5213
+ {
5214
+ type: "button",
5215
+ ...{ [CARD_ACTION_ATTR]: "" },
5216
+ disabled: action.disabled,
5217
+ onClick: action.activate,
5218
+ className: cn(
5219
+ "block w-full text-left cursor-pointer [text-overflow:inherit] focus-visible:outline-none disabled:cursor-not-allowed",
5220
+ "after:absolute after:inset-0 after:rounded-[var(--ds-radius-md)] after:pointer-events-none focus-visible:after:shadow-[var(--ds-shadow-focus)]"
5221
+ ),
5222
+ children: /* @__PURE__ */ jsx("span", { ref: (node) => overflowRef(lines > 0 ? node : null), className: cn("block [text-overflow:inherit]", lines > 0 && lineClampClass), children })
5223
+ }
5224
+ )
5225
+ ) : children
5034
5226
  }
5035
5227
  );
5036
5228
  if (!isTruncated || lines === 0) return heading;
@@ -6047,10 +6239,10 @@ function OverflowPopover({
6047
6239
  if (!open) return;
6048
6240
  const raf = requestAnimationFrame(() => focusInitial(floatingRef.current));
6049
6241
  const onKey = (e) => {
6050
- if (e.key === "Escape" && isTop()) {
6051
- returnFocus();
6052
- setOpen(false);
6053
- }
6242
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
6243
+ e.preventDefault();
6244
+ returnFocus();
6245
+ setOpen(false);
6054
6246
  };
6055
6247
  const onDown = (e) => {
6056
6248
  if (!triggerRef.current?.contains(e.target)) setOpen(false);
@@ -6705,6 +6897,9 @@ function CompactDayCell({
6705
6897
  const today = isCurrentMonth && isToday(day);
6706
6898
  const selected = isCurrentMonth && selectedDate && isSameDay2(day, selectedDate);
6707
6899
  const hasEvents = dayEvents.length > 0;
6900
+ const shown = open && hasEvents;
6901
+ const isTop = useOverlayLayer(shown);
6902
+ useCloseOnFocusOut(shown, cellRef, floatingRef, () => setOpen(false), isTop);
6708
6903
  const eventColors = dayEvents.slice(0, 3).map((e) => categories[e.category ?? ""]?.color ?? "var(--ds-accent-primary)");
6709
6904
  const handleMouseEnter = () => {
6710
6905
  if (!hasEvents) return;
@@ -6723,7 +6918,9 @@ function CompactDayCell({
6723
6918
  useEffect(() => {
6724
6919
  if (!open) return;
6725
6920
  const onKey = (e) => {
6726
- if (e.key === "Escape") setOpen(false);
6921
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
6922
+ e.preventDefault();
6923
+ setOpen(false);
6727
6924
  };
6728
6925
  const onClick = (e) => {
6729
6926
  const t = e.target;
@@ -6736,7 +6933,7 @@ function CompactDayCell({
6736
6933
  document.removeEventListener("keydown", onKey);
6737
6934
  document.removeEventListener("mousedown", onClick);
6738
6935
  };
6739
- }, [open]);
6936
+ }, [open, isTop]);
6740
6937
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6741
6938
  /* @__PURE__ */ jsxs(
6742
6939
  "button",
@@ -6908,10 +7105,10 @@ function MonthDropdown({
6908
7105
  (panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
6909
7106
  });
6910
7107
  const onKey = (e) => {
6911
- if (e.key === "Escape" && isTop()) {
6912
- returnFocus();
6913
- setOpen(false);
6914
- }
7108
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
7109
+ e.preventDefault();
7110
+ returnFocus();
7111
+ setOpen(false);
6915
7112
  };
6916
7113
  const onClick = (e) => {
6917
7114
  const t = e.target;
@@ -7042,10 +7239,10 @@ function YearDropdown({
7042
7239
  (panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
7043
7240
  });
7044
7241
  const onKey = (e) => {
7045
- if (e.key === "Escape" && isTop()) {
7046
- returnFocus();
7047
- setOpen(false);
7048
- }
7242
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
7243
+ e.preventDefault();
7244
+ returnFocus();
7245
+ setOpen(false);
7049
7246
  };
7050
7247
  const onClick = (e) => {
7051
7248
  const t = e.target;
@@ -9127,9 +9324,1234 @@ var StackedList = forwardRef(function StackedList2({
9127
9324
  }
9128
9325
  );
9129
9326
  });
9130
- var DEFAULT_OFFSET2 = "var(--ds-space-4)";
9131
- var LAUNCHER_SIZES = {
9132
- md: "var(--ds-space-10)",
9327
+ var TIME_ZOOMS = ["day", "week", "month", "quarter"];
9328
+ var DAY_MS = 864e5;
9329
+ var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
9330
+ var utcDay = (y, m, d) => Math.round(Date.UTC(y, m, d) / DAY_MS);
9331
+ function dayOfDate(d) {
9332
+ if (Number.isNaN(d.getTime())) return NaN;
9333
+ const ms = ((d.getHours() * 60 + d.getMinutes()) * 60 + d.getSeconds()) * 1e3 + d.getMilliseconds();
9334
+ return utcDay(d.getFullYear(), d.getMonth(), d.getDate()) + ms / DAY_MS;
9335
+ }
9336
+ function dayOf(value) {
9337
+ if (value instanceof Date) return dayOfDate(value);
9338
+ const m = DATE_ONLY.exec(value.trim());
9339
+ if (!m) return dayOfDate(new Date(value));
9340
+ const [y, mo, d] = [Number(m[1]), Number(m[2]) - 1, Number(m[3])];
9341
+ const day = utcDay(y, mo, d);
9342
+ const back = new Date(day * DAY_MS);
9343
+ return back.getUTCFullYear() === y && back.getUTCMonth() === mo && back.getUTCDate() === d ? day : NaN;
9344
+ }
9345
+ function dateOfDay(day) {
9346
+ const whole = Math.floor(day);
9347
+ const u = new Date(whole * DAY_MS);
9348
+ return new Date(u.getUTCFullYear(), u.getUTCMonth(), u.getUTCDate(), 0, 0, 0, Math.round((day - whole) * DAY_MS));
9349
+ }
9350
+ function endDayOf(value) {
9351
+ const day = dayOf(value);
9352
+ const wholeDay2 = typeof value === "string" ? DATE_ONLY.test(value.trim()) : Number.isInteger(day);
9353
+ return wholeDay2 ? day + 1 : day;
9354
+ }
9355
+ var MINOR = { day: "day", week: "week", month: "month", quarter: "quarter" };
9356
+ var MAJOR = { day: "month", week: "month", month: "year", quarter: "year" };
9357
+ var weekday = (day) => (day % 7 + 7 + 4) % 7;
9358
+ function periodStart(unit, day, weekStartsOn) {
9359
+ const d = Math.floor(day);
9360
+ if (unit === "day") return d;
9361
+ if (unit === "week") return d - (weekday(d) - weekStartsOn + 7) % 7;
9362
+ const u = new Date(d * DAY_MS);
9363
+ const y = u.getUTCFullYear();
9364
+ const m = u.getUTCMonth();
9365
+ if (unit === "month") return utcDay(y, m, 1);
9366
+ if (unit === "quarter") return utcDay(y, m - m % 3, 1);
9367
+ return utcDay(y, 0, 1);
9368
+ }
9369
+ function periodAfter(unit, start) {
9370
+ if (unit === "day") return start + 1;
9371
+ if (unit === "week") return start + 7;
9372
+ const u = new Date(start * DAY_MS);
9373
+ const y = u.getUTCFullYear();
9374
+ const m = u.getUTCMonth();
9375
+ if (unit === "month") return utcDay(y, m + 1, 1);
9376
+ if (unit === "quarter") return utcDay(y, m + 3, 1);
9377
+ return utcDay(y + 1, 0, 1);
9378
+ }
9379
+ var formats = /* @__PURE__ */ new Map();
9380
+ function formatter(locale, options) {
9381
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
9382
+ let f = formats.get(key);
9383
+ if (!f) {
9384
+ f = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC" });
9385
+ formats.set(key, f);
9386
+ }
9387
+ return f;
9388
+ }
9389
+ function format(day, locale, options) {
9390
+ return formatter(locale, options).format(new Date(day * DAY_MS));
9391
+ }
9392
+ var pad22 = (n) => String(n).padStart(2, "0");
9393
+ function keyOf(unit, day) {
9394
+ const u = new Date(day * DAY_MS);
9395
+ const y = u.getUTCFullYear();
9396
+ const m = u.getUTCMonth();
9397
+ if (unit === "year") return String(y);
9398
+ if (unit === "quarter") return `${y}-Q${Math.floor(m / 3) + 1}`;
9399
+ if (unit === "month") return `${y}-${pad22(m + 1)}`;
9400
+ return `${y}-${pad22(m + 1)}-${pad22(u.getUTCDate())}`;
9401
+ }
9402
+ function labelsOf(unit, role, day, locale) {
9403
+ const f = (options) => format(day, locale, options);
9404
+ let forms;
9405
+ if (unit === "day") forms = [f({ weekday: "short", day: "numeric" }), f({ day: "numeric" })];
9406
+ else if (unit === "week") forms = [f({ day: "numeric", month: "short" }), f({ day: "numeric" })];
9407
+ else if (unit === "month") {
9408
+ forms = role === "major" ? [f({ month: "long", year: "numeric" }), f({ month: "short", year: "numeric" }), f({ month: "short" }), f({ month: "narrow" })] : [f({ month: "long" }), f({ month: "short" }), f({ month: "narrow" })];
9409
+ } else if (unit === "quarter") forms = [`Q${Math.floor(new Date(day * DAY_MS).getUTCMonth() / 3) + 1}`];
9410
+ else forms = [f({ year: "numeric" }), f({ year: "2-digit" })];
9411
+ return [...new Set(forms)];
9412
+ }
9413
+ var DENSITY = { day: 56, week: 16, month: 4, quarter: 1.5 };
9414
+ var todays = /* @__PURE__ */ new WeakMap();
9415
+ function calendarDay(day) {
9416
+ const d = Math.floor(day);
9417
+ return { day: d, weekday: weekday(d), key: keyOf("day", d) };
9418
+ }
9419
+ function ticks(unit, role, s) {
9420
+ const out = [];
9421
+ for (let p = periodStart(unit, s.startDay, s.weekStartsOn); p < s.endDay; p = periodAfter(unit, p)) {
9422
+ const from = Math.max(p, s.startDay);
9423
+ const to = Math.min(periodAfter(unit, p), s.endDay);
9424
+ out.push({ key: keyOf(unit, p), day: from, x: (from - s.startDay) * s.pxPerDay, width: (to - from) * s.pxPerDay, weekday: weekday(from), labels: labelsOf(unit, role, p, s.locale) });
9425
+ }
9426
+ return out;
9427
+ }
9428
+ function build(s) {
9429
+ const { startDay, pxPerDay } = s;
9430
+ const x = (value) => (startIndex(value) - startDay) * pxPerDay;
9431
+ const inRange = (d) => d >= startDay && d < s.endDay;
9432
+ const todayDay = Math.floor(s.todayValue);
9433
+ const scale = {
9434
+ zoom: s.zoom,
9435
+ startDay,
9436
+ endDay: s.endDay,
9437
+ pxPerDay,
9438
+ width: (s.endDay - startDay) * pxPerDay,
9439
+ x,
9440
+ span: (start, end = start) => {
9441
+ const x0 = x(start);
9442
+ return { x: x0, width: Math.max(0, (endIndex(end) - startDay) * pxPerDay - x0) };
9443
+ },
9444
+ dayAt: (px) => startDay + px / pxPerDay,
9445
+ major: ticks(MAJOR[s.zoom], "major", s),
9446
+ minor: ticks(MINOR[s.zoom], "minor", s),
9447
+ today: inRange(todayDay) ? todayDay : null,
9448
+ now: inRange(s.todayValue) ? s.todayValue : null,
9449
+ weekStartsOn: s.weekStartsOn,
9450
+ locale: s.locale
9451
+ };
9452
+ todays.set(scale, s.todayValue);
9453
+ return scale;
9454
+ }
9455
+ var toMinute = (day) => Math.round(day * 1440) / 1440;
9456
+ var startIndex = (value) => typeof value === "number" ? value : dayOf(value);
9457
+ var endIndex = (value) => typeof value === "number" ? value : endDayOf(value);
9458
+ function scaleOver(from, to, zoom, todayValue, weekStartsOn, locale, pxPerDay, pad3) {
9459
+ const fallback = Number.isFinite(todayValue) ? Math.floor(todayValue) : 0;
9460
+ const a = Number.isFinite(from) ? from : fallback;
9461
+ const b = Number.isFinite(to) ? to : Math.floor(a) + 1;
9462
+ const lo = Math.floor(Math.min(a, b));
9463
+ const hi = Math.max(Math.ceil(Math.max(a, b)), lo + 1);
9464
+ const unit = MINOR[zoom];
9465
+ let startDay = periodStart(unit, lo, weekStartsOn);
9466
+ let endDay = periodAfter(unit, periodStart(unit, hi - 1, weekStartsOn));
9467
+ for (let i = 0; i < pad3; i++) {
9468
+ startDay = periodStart(unit, startDay - 1, weekStartsOn);
9469
+ endDay = periodAfter(unit, endDay);
9470
+ }
9471
+ return build({ zoom, startDay, endDay, pxPerDay: pxPerDay > 0 ? pxPerDay : DENSITY[zoom], todayValue, weekStartsOn, locale });
9472
+ }
9473
+ function extendTimeScale(scale, width) {
9474
+ if (!(width > scale.width) || !(scale.pxPerDay > 0)) return scale;
9475
+ const unit = MINOR[scale.zoom];
9476
+ const last = scale.startDay + Math.ceil(width / scale.pxPerDay) - 1;
9477
+ const endDay = periodAfter(unit, periodStart(unit, last, scale.weekStartsOn));
9478
+ const todayValue = todays.get(scale) ?? scale.now ?? scale.today ?? NaN;
9479
+ return build({ zoom: scale.zoom, startDay: scale.startDay, endDay, pxPerDay: scale.pxPerDay, todayValue, weekStartsOn: scale.weekStartsOn, locale: scale.locale });
9480
+ }
9481
+ function useTimeScale({ start, end, zoom, today, weekStartsOn = 1, locale, pxPerDay, pad: pad3 = 0 }) {
9482
+ const [mounted] = useState(() => toMinute(dayOf(/* @__PURE__ */ new Date())));
9483
+ const from = startIndex(start);
9484
+ const to = endIndex(end);
9485
+ const todayValue = today === void 0 ? mounted : toMinute(dayOf(today));
9486
+ const density = pxPerDay?.[zoom] ?? DENSITY[zoom];
9487
+ return useMemo(() => scaleOver(from, to, zoom, todayValue, weekStartsOn, locale, density, pad3), [from, to, zoom, todayValue, weekStartsOn, locale, density, pad3]);
9488
+ }
9489
+ function timeExtent(ranges) {
9490
+ let start = Infinity;
9491
+ let end = -Infinity;
9492
+ for (const r of ranges) {
9493
+ const s = dayOf(r.start);
9494
+ const e = endDayOf(r.end ?? r.start);
9495
+ if (Number.isFinite(s)) start = Math.min(start, s);
9496
+ if (Number.isFinite(e)) end = Math.max(end, e);
9497
+ }
9498
+ return Number.isFinite(start) && Number.isFinite(end) ? { start, end: Math.max(start, end) } : { start: NaN, end: NaN };
9499
+ }
9500
+ var DAY_WORDS = { day: "numeric", month: "long" };
9501
+ var TIME_WORDS = { hour: "2-digit", minute: "2-digit" };
9502
+ var wholeDay = (value) => typeof value === "string" ? DATE_ONLY.test(value.trim()) : Number.isInteger(value instanceof Date ? dayOf(value) : value);
9503
+ var SEPARATOR = /\s*[\u2010-\u2015\u2212~\uff5e\u301c-]\s*/;
9504
+ var english = (f) => new Intl.Locale(f.resolvedOptions().locale).language === "en";
9505
+ function range(f, a, b, to) {
9506
+ if (typeof f.formatRangeToParts !== "function") return to === void 0 ? f.formatRange(a, b) : `${f.format(a)}${to}${f.format(b)}`;
9507
+ const parts = f.formatRangeToParts(a, b);
9508
+ const lastOfStart = parts.map((p) => p.source).lastIndexOf("startRange");
9509
+ const firstOfEnd = parts.findIndex((p) => p.source === "endRange");
9510
+ if (lastOfStart < 0 || firstOfEnd < 0) return f.format(a);
9511
+ if (to === void 0) return parts.map((p) => p.value).join("");
9512
+ let joined = false;
9513
+ const out = parts.map((p, i) => {
9514
+ if (joined || i <= lastOfStart || i >= firstOfEnd || !SEPARATOR.test(p.value)) return p.value;
9515
+ joined = true;
9516
+ return p.value.replace(SEPARATOR, to);
9517
+ });
9518
+ if (!joined) out.splice(lastOfStart + 1, 0, to);
9519
+ return out.join("");
9520
+ }
9521
+ function formatDateRange(start, end = start, { locale, to, day = DAY_WORDS, time = TIME_WORDS } = {}) {
9522
+ const from = startIndex(start);
9523
+ const stop = endIndex(end);
9524
+ if (!Number.isFinite(from) || !Number.isFinite(stop)) return "";
9525
+ const timed = !wholeDay(start) || !wholeDay(end);
9526
+ const first = timed ? from : Math.floor(from);
9527
+ const last = timed ? Math.max(from, stop) : Math.max(first, Math.ceil(stop) - 1);
9528
+ const a = new Date(first * DAY_MS);
9529
+ const b = new Date(last * DAY_MS);
9530
+ const years = a.getUTCFullYear() !== b.getUTCFullYear() && day.year === void 0;
9531
+ const f = formatter(locale, { ...day, ...years ? { year: "numeric" } : {}, ...timed ? time : {} });
9532
+ return first === last ? f.format(a) : range(f, a, b, to ?? (english(f) ? " to " : void 0));
9533
+ }
9534
+ function createScrollStore() {
9535
+ let current = { scrollLeft: 0, visibleWidth: 0 };
9536
+ const listeners = /* @__PURE__ */ new Set();
9537
+ return {
9538
+ get: () => current,
9539
+ set(next) {
9540
+ if (next.scrollLeft === current.scrollLeft && next.visibleWidth === current.visibleWidth) return;
9541
+ current = next;
9542
+ listeners.forEach((l) => l());
9543
+ },
9544
+ subscribe(listener) {
9545
+ listeners.add(listener);
9546
+ return () => {
9547
+ listeners.delete(listener);
9548
+ };
9549
+ }
9550
+ };
9551
+ }
9552
+ var TimeViewportCtx = createContext(null);
9553
+ var TimeViewportScrollCtx = createContext(null);
9554
+ var UNMEASURED = { scrollLeft: 0, visibleWidth: 0 };
9555
+ var noSubscription = () => () => {
9556
+ };
9557
+ var unmeasured = () => UNMEASURED;
9558
+ function useTimeViewportLayout() {
9559
+ const layout = useContext(TimeViewportCtx);
9560
+ if (!layout) throw new Error("A time-axis row or header must be inside a <TimeViewport>.");
9561
+ return layout;
9562
+ }
9563
+ function useTimeViewport() {
9564
+ const store = useContext(TimeViewportScrollCtx);
9565
+ const scroll = useSyncExternalStore(store?.subscribe ?? noSubscription, store?.get ?? unmeasured, unmeasured);
9566
+ return { ...useTimeViewportLayout(), ...scroll };
9567
+ }
9568
+ function fitLabels(boxes, measure, { gap = 8, align = "start", visible } = {}) {
9569
+ const out = boxes.map((b) => ({ key: b.key, x: b.x, width: 0, text: null }));
9570
+ const n = boxes.length;
9571
+ const levels = boxes.reduce((most, b) => Math.max(most, b.labels.length), 0);
9572
+ if (!n || !levels) return out;
9573
+ const widths = /* @__PURE__ */ new Map();
9574
+ const w = (text) => {
9575
+ let v = widths.get(text);
9576
+ if (v === void 0) {
9577
+ v = Math.max(0, measure(text) || 0);
9578
+ widths.set(text, v);
9579
+ }
9580
+ return v;
9581
+ };
9582
+ const order = boxes.map((_, i) => i).sort((a, b) => boxes[a].x - boxes[b].x || a - b);
9583
+ const rowEnd = order.reduce((end, i) => Math.max(end, boxes[i].x + Math.max(0, boxes[i].width)), -Infinity);
9584
+ const form = (b, level) => b.labels.length ? b.labels[Math.min(level, b.labels.length - 1)] : null;
9585
+ for (let step = 1; step <= n; step++) {
9586
+ const shown = [];
9587
+ for (let k = 0; k < n; k += step) shown.push(order[k]);
9588
+ const room = shown.map((i, s) => ({ start: boxes[i].x, end: s + 1 < shown.length ? boxes[shown[s + 1]].x : rowEnd }));
9589
+ const fits = (s, text) => text === null || w(text) + gap <= room[s].end - room[s].start;
9590
+ const decides = (s) => shown.length < 3 || s > 0 && s < shown.length - 1;
9591
+ let level = -1;
9592
+ for (let l = 0; l < levels && level < 0; l++) {
9593
+ if (shown.every((i, s) => !decides(s) || fits(s, form(boxes[i], l)))) level = l;
9594
+ }
9595
+ if (level < 0) continue;
9596
+ shown.forEach((i, s) => {
9597
+ const b = boxes[i];
9598
+ const lo = Math.max(room[s].start + gap / 2, visible ? visible.start + gap / 2 : -Infinity);
9599
+ const hi = Math.min(room[s].end - gap / 2, visible ? visible.end - gap / 2 : Infinity);
9600
+ let text = null;
9601
+ for (let l = level; l < Math.max(level + 1, b.labels.length) && text === null; l++) {
9602
+ const t = form(b, l);
9603
+ if (t !== null && fits(s, t) && w(t) <= hi - lo) text = t;
9604
+ }
9605
+ if (text === null) return;
9606
+ const tw = w(text);
9607
+ const from = visible ? Math.max(b.x, visible.start) : b.x;
9608
+ const to = Math.min(b.x + Math.max(0, b.width), visible ? visible.end : Infinity);
9609
+ const x = align === "center" ? (from + to) / 2 - tw / 2 : lo;
9610
+ out[i] = { key: b.key, x: Math.min(Math.max(x, lo), hi - tw), width: tw, text };
9611
+ });
9612
+ return out;
9613
+ }
9614
+ return out;
9615
+ }
9616
+ function fitLabel(labels, width, measure, { gap = 8 } = {}) {
9617
+ return labels.find((text) => Math.max(0, measure(text) || 0) + gap <= width) ?? null;
9618
+ }
9619
+ var context;
9620
+ function canvasContext() {
9621
+ if (context !== void 0) return context;
9622
+ context = null;
9623
+ if (typeof document === "undefined" || typeof navigator !== "undefined" && /jsdom/i.test(navigator.userAgent)) return context;
9624
+ try {
9625
+ context = document.createElement("canvas").getContext("2d");
9626
+ } catch {
9627
+ context = null;
9628
+ }
9629
+ return context;
9630
+ }
9631
+ function fontOf(el) {
9632
+ const cs = getComputedStyle(el);
9633
+ const size = parseFloat(cs.fontSize) || 12;
9634
+ return { font: `${cs.fontStyle || "normal"} ${cs.fontWeight || "400"} ${size}px ${cs.fontFamily || "sans-serif"}`, size, letterSpacing: cs.letterSpacing || "normal" };
9635
+ }
9636
+ function measurer(info) {
9637
+ const cache = /* @__PURE__ */ new Map();
9638
+ return (text) => {
9639
+ let v = cache.get(text);
9640
+ if (v !== void 0) return v;
9641
+ const ctx = info ? canvasContext() : null;
9642
+ if (ctx && info) {
9643
+ ctx.font = info.font;
9644
+ if ("letterSpacing" in ctx) ctx.letterSpacing = info.letterSpacing === "normal" ? "0px" : info.letterSpacing;
9645
+ v = ctx.measureText(text).width;
9646
+ } else {
9647
+ v = text.length * (info?.size ?? 12) * 0.6;
9648
+ }
9649
+ cache.set(text, v);
9650
+ return v;
9651
+ };
9652
+ }
9653
+ function useTextMeasure(ref) {
9654
+ const [info, setInfo] = useState(null);
9655
+ useLayoutEffect(() => {
9656
+ const el = ref.current;
9657
+ if (!el) return;
9658
+ let live = true;
9659
+ const read = (fresh) => {
9660
+ if (!live) return;
9661
+ const next = fontOf(el);
9662
+ setInfo((cur) => !fresh && cur && cur.font === next.font && cur.letterSpacing === next.letterSpacing ? cur : next);
9663
+ };
9664
+ read(false);
9665
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => read(false));
9666
+ ro?.observe(el);
9667
+ const fonts = typeof document === "undefined" ? void 0 : document.fonts;
9668
+ const loaded = () => read(true);
9669
+ fonts?.addEventListener?.("loadingdone", loaded);
9670
+ fonts?.ready?.then(loaded, () => {
9671
+ });
9672
+ return () => {
9673
+ live = false;
9674
+ ro?.disconnect();
9675
+ fonts?.removeEventListener?.("loadingdone", loaded);
9676
+ };
9677
+ }, [ref]);
9678
+ return useMemo(() => measurer(info), [info]);
9679
+ }
9680
+ var fontGeneration = 0;
9681
+ var fontListeners = /* @__PURE__ */ new Set();
9682
+ var fontsWatched = false;
9683
+ function watchFonts() {
9684
+ if (fontsWatched || typeof document === "undefined" || !document.fonts) return;
9685
+ fontsWatched = true;
9686
+ const moveOn = () => {
9687
+ fontGeneration++;
9688
+ shared.clear();
9689
+ fontListeners.forEach((l) => l());
9690
+ };
9691
+ document.fonts.addEventListener?.("loadingdone", moveOn);
9692
+ document.fonts.ready?.then(moveOn, () => {
9693
+ });
9694
+ }
9695
+ var shared = /* @__PURE__ */ new Map();
9696
+ function sharedMeasurer(info) {
9697
+ const key = `${fontGeneration}|${info.font}|${info.letterSpacing}`;
9698
+ let m = shared.get(key);
9699
+ if (!m) {
9700
+ m = measurer(info);
9701
+ shared.set(key, m);
9702
+ }
9703
+ return m;
9704
+ }
9705
+ var textWatchers = /* @__PURE__ */ new Map();
9706
+ var textObserver = null;
9707
+ function watchText(el, onResize) {
9708
+ if (typeof ResizeObserver === "undefined") return () => {
9709
+ };
9710
+ textObserver ??= new ResizeObserver((entries) => entries.forEach((e) => textWatchers.get(e.target)?.()));
9711
+ textWatchers.set(el, onResize);
9712
+ textObserver.observe(el);
9713
+ return () => {
9714
+ textWatchers.delete(el);
9715
+ textObserver?.unobserve(el);
9716
+ };
9717
+ }
9718
+ var NO_BORDERS = { start: 0, end: 0 };
9719
+ function useElementMetrics(ref, deps, text) {
9720
+ const [read, setRead] = useState({ info: null, borders: NO_BORDERS });
9721
+ const [generation, setGeneration] = useState(fontGeneration);
9722
+ useLayoutEffect(() => {
9723
+ const el = ref.current;
9724
+ if (!el) return;
9725
+ const readNow = () => {
9726
+ const cs = getComputedStyle(el);
9727
+ const next = fontOf(el);
9728
+ const start = parseFloat(cs.borderInlineStartWidth || cs.borderLeftWidth) || 0;
9729
+ const end = parseFloat(cs.borderInlineEndWidth || cs.borderRightWidth) || 0;
9730
+ setRead(
9731
+ (cur) => cur.info?.font === next.font && cur.info.letterSpacing === next.letterSpacing && cur.borders.start === start && cur.borders.end === end ? cur : { info: next, borders: { start, end } }
9732
+ );
9733
+ };
9734
+ readNow();
9735
+ const watched = text?.current;
9736
+ return watched ? watchText(watched, readNow) : void 0;
9737
+ }, deps);
9738
+ useLayoutEffect(() => {
9739
+ watchFonts();
9740
+ const listener = () => setGeneration(fontGeneration);
9741
+ fontListeners.add(listener);
9742
+ return () => {
9743
+ fontListeners.delete(listener);
9744
+ };
9745
+ }, []);
9746
+ const measure = useMemo(() => read.info ? sharedMeasurer(read.info) : measurer(null), [read.info, generation]);
9747
+ return { measure, borders: read.borders };
9748
+ }
9749
+ function useFittedLabels(boxes, ref, options = {}) {
9750
+ const measure = useTextMeasure(ref);
9751
+ const { gap, align } = options;
9752
+ const start = options.visible?.start;
9753
+ const end = options.visible?.end;
9754
+ return useMemo(
9755
+ () => fitLabels(boxes, measure, { gap, align, visible: start === void 0 || end === void 0 ? void 0 : { start, end } }),
9756
+ [boxes, measure, gap, align, start, end]
9757
+ );
9758
+ }
9759
+ var TODAY_INSET = 4;
9760
+ var TODAY_PAD_X = 6;
9761
+ function TimeScaleHeader({ todayLabel = "Today", className }) {
9762
+ const { scale, scrollLeft, visibleWidth, todayX: line } = useTimeViewport();
9763
+ const majorRef = useRef(null);
9764
+ const minorRef = useRef(null);
9765
+ const todayRef = useRef(null);
9766
+ const measured = visibleWidth > 0;
9767
+ const view = { start: scrollLeft, end: scrollLeft + visibleWidth };
9768
+ const major = useFittedLabels(scale.major, majorRef, measured ? { visible: view } : {});
9769
+ const minor = useFittedLabels(scale.minor, minorRef, measured ? { align: "center", visible: view } : { align: "center" });
9770
+ const measureToday = useTextMeasure(todayRef);
9771
+ const todayWidth = measureToday(todayLabel) + TODAY_PAD_X * 2;
9772
+ const todayShown = line !== null && (!measured || line >= view.start && line <= view.end);
9773
+ let todayX = line === null ? 0 : line - todayWidth / 2;
9774
+ if (measured) todayX = Math.max(view.start + TODAY_INSET, Math.min(todayX, view.end - TODAY_INSET - todayWidth));
9775
+ const underToday = (x, width) => todayShown && x < todayX + todayWidth + TODAY_INSET && x + width > todayX - TODAY_INSET;
9776
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", "data-time-scale": "", className: cn("relative select-none text-xs", className), style: { width: scale.width }, children: [
9777
+ /* @__PURE__ */ jsxs("div", { "data-scale-row": "major", className: "relative h-7 font-medium text-[var(--ds-text-primary)]", children: [
9778
+ /* @__PURE__ */ jsx("span", { ref: majorRef, className: "invisible absolute whitespace-nowrap leading-none", children: "0" }),
9779
+ scale.major.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, `line-${t.key}`)),
9780
+ major.map((l) => l.text && /* @__PURE__ */ jsx("span", { "data-scale-label": "", className: "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none", style: { insetInlineStart: l.x }, children: l.text }, l.key))
9781
+ ] }),
9782
+ /* @__PURE__ */ jsxs("div", { "data-scale-row": "minor", className: "relative h-7 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] text-[var(--ds-text-secondary)]", children: [
9783
+ /* @__PURE__ */ jsx("span", { ref: minorRef, className: "invisible absolute whitespace-nowrap leading-none tabular-nums", children: "0" }),
9784
+ scale.minor.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, `line-${t.key}`)),
9785
+ minor.map((l) => l.text && !underToday(l.x, l.width) && /* @__PURE__ */ jsx("span", { "data-scale-label": "", className: "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none tabular-nums", style: { insetInlineStart: l.x }, children: l.text }, l.key)),
9786
+ /* @__PURE__ */ jsx(
9787
+ "span",
9788
+ {
9789
+ ref: todayRef,
9790
+ "data-scale-today": "",
9791
+ className: cn(
9792
+ "absolute top-1/2 -translate-y-1/2 whitespace-nowrap leading-none font-medium",
9793
+ // the transparent border draws the pill's outline in forced-colours mode
9794
+ "rounded-[var(--ds-radius-full)] py-ds-0.5 border-[length:1px] border-transparent bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
9795
+ !todayShown && "invisible"
9796
+ ),
9797
+ style: { insetInlineStart: todayX, paddingInline: TODAY_PAD_X },
9798
+ children: todayLabel
9799
+ }
9800
+ )
9801
+ ] })
9802
+ ] });
9803
+ }
9804
+ var ZOOM_NAMES = { day: "Days", week: "Weeks", month: "Months", quarter: "Quarters" };
9805
+ var SHADE_MIN = 4;
9806
+ var INSET = 8;
9807
+ var MIN_INSET = 2;
9808
+ var MIN_TITLE = 2 * INSET + 12;
9809
+ var PHONE_BELOW = 640;
9810
+ var MIN_LABEL = 112;
9811
+ var ROW_HEIGHT = 40;
9812
+ var MARGIN = 16;
9813
+ var WHEEL_STEP = 50;
9814
+ var WHEEL_PAUSE = 250;
9815
+ var FIELD = "input, select, textarea, [contenteditable='true']";
9816
+ var border = "border-[var(--ds-border-subtle)]";
9817
+ function writeScroll(el, visibleWidth) {
9818
+ el.style.setProperty("--time-scroll", String(el.scrollLeft));
9819
+ el.style.setProperty("--time-view-end", String(el.scrollLeft + visibleWidth));
9820
+ }
9821
+ var TimeViewport = forwardRef(function TimeViewport2({
9822
+ scale,
9823
+ onZoomChange,
9824
+ zooms = TIME_ZOOMS,
9825
+ labelWidth: labelWidthProp = 240,
9826
+ labelHeader,
9827
+ header,
9828
+ toolbar,
9829
+ initialDate,
9830
+ showToday = true,
9831
+ todayAt = "day",
9832
+ shadeDays,
9833
+ tabStop = true,
9834
+ children,
9835
+ "aria-label": ariaLabel,
9836
+ className
9837
+ }, ref) {
9838
+ const scrollerRef = useRef(null);
9839
+ const headerRef = useRef(null);
9840
+ const [frame, setFrame] = useState({ width: 0, header: 0 });
9841
+ useLayoutEffect(() => {
9842
+ const el = scrollerRef.current;
9843
+ if (!el) return;
9844
+ const read = () => {
9845
+ const width = el.clientWidth;
9846
+ const headerHeight = headerRef.current?.offsetHeight ?? 0;
9847
+ setFrame((cur) => cur.width === width && cur.header === headerHeight ? cur : { width, header: headerHeight });
9848
+ };
9849
+ read();
9850
+ if (typeof ResizeObserver === "undefined") return;
9851
+ const ro = new ResizeObserver(read);
9852
+ ro.observe(el);
9853
+ if (headerRef.current) ro.observe(headerRef.current);
9854
+ return () => ro.disconnect();
9855
+ }, []);
9856
+ const phone = frame.width > 0 && frame.width < PHONE_BELOW;
9857
+ const labelWidth = phone ? Math.min(labelWidthProp, Math.max(MIN_LABEL, Math.round(frame.width * 0.36))) : labelWidthProp;
9858
+ const visibleWidth = Math.max(0, frame.width - labelWidth);
9859
+ const drawn = useMemo(() => extendTimeScale(scale, visibleWidth), [scale, visibleWidth]);
9860
+ const [store] = useState(createScrollStore);
9861
+ const lastLeft = useRef(0);
9862
+ const latest = useLatest({ drawn, visibleWidth, labelWidth, headerHeight: frame.header });
9863
+ const setLeft = useCallback((left) => {
9864
+ const el = scrollerRef.current;
9865
+ if (!el) return;
9866
+ el.scrollLeft = Math.max(0, left);
9867
+ lastLeft.current = el.scrollLeft;
9868
+ writeScroll(el, latest.current.visibleWidth);
9869
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth: latest.current.visibleWidth });
9870
+ }, [store, latest]);
9871
+ const api = useMemo(() => {
9872
+ const ensureVisible = (x0, x1) => {
9873
+ const el = scrollerRef.current;
9874
+ if (!el) return;
9875
+ const vw = latest.current.visibleWidth;
9876
+ const left = el.scrollLeft;
9877
+ const lo = Math.min(x0, x1);
9878
+ const hi = Math.max(x0, x1);
9879
+ if (hi - lo > vw - 2 * MARGIN) {
9880
+ if (hi < left + MARGIN || lo > left + vw - MARGIN) setLeft(lo - MARGIN);
9881
+ } else if (lo < left + MARGIN) setLeft(lo - MARGIN);
9882
+ else if (hi > left + vw - MARGIN) setLeft(hi - vw + MARGIN);
9883
+ };
9884
+ return {
9885
+ scrollToDate(value, align = "start") {
9886
+ const { drawn: s, visibleWidth: vw } = latest.current;
9887
+ const x = s.x(value);
9888
+ setLeft(align === "center" ? x - vw / 2 : x - MARGIN);
9889
+ },
9890
+ page(direction) {
9891
+ const { drawn: s, visibleWidth: vw } = latest.current;
9892
+ const target = (scrollerRef.current?.scrollLeft ?? 0) + direction * vw;
9893
+ const tick = s.minor.reduce((best, t) => Math.abs(t.x - target) < Math.abs(best - target) ? t.x : best, Infinity);
9894
+ setLeft(Number.isFinite(tick) ? tick : target);
9895
+ },
9896
+ ensureVisible,
9897
+ reveal(target) {
9898
+ const el = scrollerRef.current;
9899
+ if (!el || !el.contains(target)) return;
9900
+ const { labelWidth: lw, headerHeight } = latest.current;
9901
+ const box = el.getBoundingClientRect();
9902
+ const r = target.getBoundingClientRect();
9903
+ const top = box.top + headerHeight;
9904
+ const bottom = box.top + el.clientHeight;
9905
+ if (r.top < top) el.scrollTop -= top - r.top;
9906
+ else if (r.bottom > bottom) el.scrollTop += Math.min(r.bottom - bottom, r.top - top);
9907
+ const x0 = r.left - box.left - lw + el.scrollLeft;
9908
+ ensureVisible(x0, x0 + r.width);
9909
+ }
9910
+ };
9911
+ }, [latest, setLeft]);
9912
+ useImperativeHandle(ref, () => api, [api]);
9913
+ const todayAtX = todayAt === "now" ? drawn.now === null ? null : drawn.x(drawn.now) : drawn.today === null ? null : drawn.x(drawn.today) + drawn.pxPerDay / 2;
9914
+ const todayX = showToday ? todayAtX : null;
9915
+ const layout = useMemo(() => ({ scale: drawn, labelWidth, showToday, todayX, compact: phone, ...api }), [drawn, labelWidth, showToday, todayX, phone, api]);
9916
+ const pending = useRef(null);
9917
+ const applied = useRef(null);
9918
+ useLayoutEffect(() => {
9919
+ const el = scrollerRef.current;
9920
+ if (!el || visibleWidth <= 0) return;
9921
+ writeScroll(el, visibleWidth);
9922
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth });
9923
+ const prev = applied.current;
9924
+ applied.current = { startDay: drawn.startDay, pxPerDay: drawn.pxPerDay, visibleWidth };
9925
+ if (!prev) {
9926
+ const at = initialDate !== void 0 ? drawn.x(initialDate) : todayAtX;
9927
+ if (at !== null && Number.isFinite(at)) setLeft(at - visibleWidth / 2);
9928
+ return;
9929
+ }
9930
+ const rescaled = prev.startDay !== drawn.startDay || prev.pxPerDay !== drawn.pxPerDay;
9931
+ if (!rescaled && prev.visibleWidth === visibleWidth) return;
9932
+ const want = pending.current;
9933
+ pending.current = null;
9934
+ const anchor = want && want.zoom === drawn.zoom ? want : { day: prev.startDay + (lastLeft.current + prev.visibleWidth / 2) / prev.pxPerDay, at: visibleWidth / 2 };
9935
+ setLeft(drawn.x(anchor.day) - anchor.at);
9936
+ const focused = document.activeElement;
9937
+ if (focused instanceof HTMLElement && focused !== el && el.contains(focused)) api.reveal(focused);
9938
+ }, [drawn, visibleWidth, initialDate, todayAtX, setLeft, store, api]);
9939
+ const shaded = [];
9940
+ if (shadeDays && drawn.pxPerDay >= SHADE_MIN) {
9941
+ for (let d = drawn.startDay; d < drawn.endDay; d++) {
9942
+ if (!shadeDays(calendarDay(d))) continue;
9943
+ const last = shaded[shaded.length - 1];
9944
+ if (last && Math.abs(last.x + last.width - drawn.x(d)) < 0.5) last.width += drawn.pxPerDay;
9945
+ else shaded.push({ key: String(d), x: drawn.x(d), width: drawn.pxPerDay });
9946
+ }
9947
+ }
9948
+ const levels = TIME_ZOOMS.filter((z) => zooms.includes(z));
9949
+ const zoomTo = (direction) => {
9950
+ if (!onZoomChange) return null;
9951
+ const at = TIME_ZOOMS.indexOf(scale.zoom);
9952
+ const side = levels.filter((z) => direction < 0 ? TIME_ZOOMS.indexOf(z) < at : TIME_ZOOMS.indexOf(z) > at);
9953
+ return (direction < 0 ? side[side.length - 1] : side[0]) ?? null;
9954
+ };
9955
+ const focusedAt = (el) => {
9956
+ const f = document.activeElement;
9957
+ if (!(f instanceof HTMLElement) || f === el || !el.contains(f)) return void 0;
9958
+ const r = f.getBoundingClientRect();
9959
+ const x = (r.left + r.right) / 2 - el.getBoundingClientRect().left - labelWidth;
9960
+ return x >= 0 && x <= visibleWidth ? x : void 0;
9961
+ };
9962
+ const zoom = (direction, at) => {
9963
+ const next = zoomTo(direction);
9964
+ if (!next || !onZoomChange) return false;
9965
+ const el = scrollerRef.current;
9966
+ if (el && visibleWidth > 0) {
9967
+ const x = at ?? focusedAt(el) ?? visibleWidth / 2;
9968
+ pending.current = { zoom: next, day: drawn.dayAt(el.scrollLeft + x), at: x };
9969
+ }
9970
+ onZoomChange(next);
9971
+ return true;
9972
+ };
9973
+ const canZoomIn = zoomTo(-1) !== null;
9974
+ const canZoomOut = zoomTo(1) !== null;
9975
+ const panByPeriod = (direction) => {
9976
+ const left = scrollerRef.current?.scrollLeft ?? 0;
9977
+ const xs = drawn.minor.map((t) => t.x);
9978
+ const target = direction > 0 ? xs.find((x) => x > left + 1) ?? drawn.width : [...xs].reverse().find((x) => x < left - 1) ?? 0;
9979
+ setLeft(target);
9980
+ };
9981
+ const onKeyDown = (e) => {
9982
+ if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;
9983
+ if (e.target instanceof HTMLElement && e.target.closest(FIELD)) return;
9984
+ if (e.key === "+" || e.key === "=") {
9985
+ if (zoom(-1)) e.preventDefault();
9986
+ } else if (e.key === "-" || e.key === "\u2212") {
9987
+ if (zoom(1)) e.preventDefault();
9988
+ } else if (e.shiftKey && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
9989
+ e.preventDefault();
9990
+ panByPeriod(e.key === "ArrowRight" ? 1 : -1);
9991
+ }
9992
+ };
9993
+ const wheel = useRef({ sum: 0, at: -Infinity });
9994
+ const onWheel = useLatest((e) => {
9995
+ if (!(e.ctrlKey || e.metaKey) || !onZoomChange) return;
9996
+ e.preventDefault();
9997
+ const w = wheel.current;
9998
+ if (e.timeStamp - w.at < WHEEL_PAUSE) return;
9999
+ w.sum += e.deltaY * (e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? 400 : 1);
10000
+ if (Math.abs(w.sum) < WHEEL_STEP) return;
10001
+ const el = scrollerRef.current;
10002
+ const x = el ? e.clientX - el.getBoundingClientRect().left - labelWidth : NaN;
10003
+ zoom(w.sum < 0 ? -1 : 1, x >= 0 && x <= visibleWidth ? x : void 0);
10004
+ wheel.current = { sum: 0, at: e.timeStamp };
10005
+ });
10006
+ useEffect(() => {
10007
+ const el = scrollerRef.current;
10008
+ if (!el) return;
10009
+ const listener = (e) => onWheel.current(e);
10010
+ el.addEventListener("wheel", listener, { passive: false });
10011
+ return () => el.removeEventListener("wheel", listener);
10012
+ }, [onWheel]);
10013
+ const drag = useRef(null);
10014
+ const [dragging, setDragging] = useState(false);
10015
+ const onScaleDown = (e) => {
10016
+ if (e.button !== 0 || e.pointerType === "touch" || !scrollerRef.current) return;
10017
+ drag.current = { id: e.pointerId, x: e.clientX, left: scrollerRef.current.scrollLeft };
10018
+ e.currentTarget.setPointerCapture?.(e.pointerId);
10019
+ setDragging(true);
10020
+ };
10021
+ const onScaleMove = (e) => {
10022
+ const d = drag.current;
10023
+ if (d && d.id === e.pointerId) setLeft(d.left - (e.clientX - d.x));
10024
+ };
10025
+ const onScaleUp = (e) => {
10026
+ if (drag.current?.id !== e.pointerId) return;
10027
+ drag.current = null;
10028
+ setDragging(false);
10029
+ };
10030
+ const onScroll = () => {
10031
+ const el = scrollerRef.current;
10032
+ if (!el) return;
10033
+ lastLeft.current = el.scrollLeft;
10034
+ writeScroll(el, visibleWidth);
10035
+ store.set({ scrollLeft: el.scrollLeft, visibleWidth });
10036
+ };
10037
+ const headerNode = header === void 0 ? /* @__PURE__ */ jsx(TimeScaleHeader, {}) : header;
10038
+ const hasHeader = headerNode != null || labelHeader != null;
10039
+ const bar = toolbar === false ? null : /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 flex-wrap items-center gap-ds-1 px-ds-2 py-ds-1.5 border-b-[length:var(--ds-border-width)]", border), children: [
10040
+ onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10041
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10042
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "min-w-[4.5rem] text-center text-xs text-[var(--ds-text-secondary)]", children: ZOOM_NAMES[scale.zoom] }),
10043
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10044
+ ] }),
10045
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10046
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10047
+ if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10048
+ }, children: "Today" }),
10049
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10050
+ toolbar
10051
+ ] });
10052
+ return (
10053
+ // Inline-size containment: the rows' width never widens the frame (in a grid or flex parent it would, and the
10054
+ // wider frame would extend the scale again). The viewport takes the width it is given and scrolls inside it.
10055
+ /* @__PURE__ */ jsx("div", { "data-time-viewport": "", "data-compact": phone || void 0, className: cn("flex h-full min-h-0 w-full min-w-0 flex-col [contain:inline-size] bg-[var(--ds-bg-surface)]", className), onKeyDown, children: /* @__PURE__ */ jsx(TimeViewportCtx, { value: layout, children: /* @__PURE__ */ jsxs(TimeViewportScrollCtx, { value: store, children: [
10056
+ bar,
10057
+ /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1", children: [
10058
+ /* @__PURE__ */ jsx(
10059
+ "div",
10060
+ {
10061
+ ref: scrollerRef,
10062
+ role: "region",
10063
+ "aria-label": ariaLabel,
10064
+ tabIndex: tabStop ? 0 : void 0,
10065
+ onScroll,
10066
+ className: "peer h-full overflow-auto isolate outline-none",
10067
+ style: { scrollPaddingInlineStart: labelWidth, scrollPaddingTop: frame.header },
10068
+ children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", style: { width: labelWidth + drawn.width }, children: [
10069
+ hasHeader && /* @__PURE__ */ jsxs("div", { ref: headerRef, className: cn("sticky top-0 z-[3] flex shrink-0 bg-[var(--ds-bg-surface)] border-b-[length:var(--ds-border-width)]", border), children: [
10070
+ /* @__PURE__ */ jsx(
10071
+ "div",
10072
+ {
10073
+ className: cn("sticky start-0 z-[1] flex shrink-0 items-end px-ds-3 py-ds-1.5 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)] text-xs font-medium text-[var(--ds-text-secondary)]", border),
10074
+ style: { width: labelWidth },
10075
+ children: labelHeader
10076
+ }
10077
+ ),
10078
+ /* @__PURE__ */ jsx(
10079
+ "div",
10080
+ {
10081
+ "data-time-scale-area": "",
10082
+ className: cn("shrink-0 select-none", dragging ? "cursor-grabbing" : "cursor-grab"),
10083
+ style: { width: drawn.width },
10084
+ onPointerDown: onScaleDown,
10085
+ onPointerMove: onScaleMove,
10086
+ onPointerUp: onScaleUp,
10087
+ onPointerCancel: onScaleUp,
10088
+ children: headerNode
10089
+ }
10090
+ )
10091
+ ] }),
10092
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col", children: [
10093
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-y-0", style: { insetInlineStart: labelWidth, width: drawn.width }, children: [
10094
+ shaded.map((b) => /* @__PURE__ */ jsx("span", { "data-shaded-days": "", className: "absolute inset-y-0 bg-[var(--ds-bg-muted)] opacity-70", style: { insetInlineStart: b.x, width: b.width } }, b.key)),
10095
+ drawn.minor.slice(1).map((t) => /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 border-s-[length:1px] border-[var(--ds-border-subtle)]", style: { insetInlineStart: t.x } }, t.key)),
10096
+ todayX !== null && /* @__PURE__ */ jsx("span", { "data-today-line": "", className: "absolute inset-y-0 -translate-x-1/2 border-s-2 border-[var(--ds-accent-primary)]", style: { insetInlineStart: todayX } })
10097
+ ] }),
10098
+ children,
10099
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: cn("sticky start-0 z-[2] min-h-0 flex-1 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)]", border), style: { width: labelWidth } })
10100
+ ] })
10101
+ ] })
10102
+ }
10103
+ ),
10104
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 peer-focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]" })
10105
+ ] })
10106
+ ] }) }) })
10107
+ );
10108
+ });
10109
+ function TimeRow({ label, children, height = ROW_HEIGHT, labelClassName, className, style, ...props }) {
10110
+ const { scale, labelWidth } = useTimeViewportLayout();
10111
+ return /* @__PURE__ */ jsxs("div", { ...props, className: cn("flex shrink-0", className), style: { height, ...style }, children: [
10112
+ /* @__PURE__ */ jsx(
10113
+ "div",
10114
+ {
10115
+ className: cn("sticky start-0 z-[2] flex shrink-0 items-center px-ds-3 bg-[var(--ds-bg-surface)] border-e-[length:var(--ds-border-width)] border-b-[length:var(--ds-border-width)] text-sm text-[var(--ds-text-primary)]", border, labelClassName),
10116
+ style: { width: labelWidth },
10117
+ children: /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 truncate", children: label })
10118
+ }
10119
+ ),
10120
+ /* @__PURE__ */ jsx("div", { className: cn("relative shrink-0 border-b-[length:var(--ds-border-width)]", border), style: { width: scale.width }, children })
10121
+ ] });
10122
+ }
10123
+ function TimeBar({ start, end, minWidth = 0, labels, children, className, style, ...props }) {
10124
+ const { scale, labelWidth } = useTimeViewportLayout();
10125
+ const barRef = useRef(null);
10126
+ const textRef = useRef(null);
10127
+ const hasLabels = !!labels?.length;
10128
+ const { measure, borders } = useElementMetrics(barRef, [className, hasLabels], textRef);
10129
+ const { x, width } = scale.span(start, end ?? start);
10130
+ const drawnWidth = Math.max(width, minWidth);
10131
+ const inView = `(min(${x + drawnWidth - borders.end}, var(--time-view-end, 1e9)) - max(${x + borders.start}, var(--time-scroll, 0)))`;
10132
+ const sticky = { insetInlineStart: labelWidth, maxWidth: `min(100%, calc(${inView} * 1px))` };
10133
+ let label = null;
10134
+ if (labels && hasLabels) {
10135
+ const widths = labels.map(measure);
10136
+ const inset = Math.min(INSET, Math.max(MIN_INSET, (drawnWidth - borders.start - borders.end - Math.min(...widths)) / 2 - 0.5));
10137
+ const fits = (i) => `clamp(0, calc((${inView} - ${widths[i] + 2 * inset} + 0.25) * 1000), 1)`;
10138
+ const shows = (i) => i === 0 ? fits(0) : `calc(${[fits(i), ...labels.slice(0, i).map((_, j) => `(1 - ${fits(j)})`)].join(" * ")})`;
10139
+ const named = props["aria-label"] !== void 0 || props["aria-labelledby"] !== void 0;
10140
+ label = /* @__PURE__ */ jsxs(Fragment, { children: [
10141
+ /* @__PURE__ */ jsx("span", { "data-time-bar-label": "", "aria-hidden": "true", className: "sticky grid overflow-hidden whitespace-nowrap", style: { ...sticky, paddingInline: inset }, children: labels.map((text, i) => /* @__PURE__ */ jsx("span", { ref: i === 0 ? textRef : void 0, "data-time-bar-form": "", className: "justify-self-start [grid-area:1/1]", style: { opacity: shows(i) }, children: text }, i)) }),
10142
+ !named && /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels[0] })
10143
+ ] });
10144
+ } else if (children != null && children !== false) {
10145
+ label = /* @__PURE__ */ jsx(
10146
+ "span",
10147
+ {
10148
+ "data-time-bar-label": "",
10149
+ className: "sticky flex min-w-0 items-center gap-ds-1 overflow-hidden whitespace-nowrap",
10150
+ style: { ...sticky, paddingInline: INSET, opacity: `clamp(0, calc((${inView} - ${MIN_TITLE}) * 1000), 1)` },
10151
+ children: typeof children === "string" ? /* @__PURE__ */ jsx("span", { className: "truncate", children }) : children
10152
+ }
10153
+ );
10154
+ }
10155
+ return /* @__PURE__ */ jsx(
10156
+ "div",
10157
+ {
10158
+ ...props,
10159
+ ref: barRef,
10160
+ className: cn("absolute top-ds-2 bottom-ds-2 flex items-center overflow-clip rounded-[var(--ds-radius-sm)] outline-none focus-visible:shadow-[var(--ds-shadow-focus)]", className),
10161
+ style: { insetInlineStart: x, width: drawnWidth, ...style },
10162
+ children: label
10163
+ }
10164
+ );
10165
+ }
10166
+
10167
+ // ../../showcase/src/lib/gantt-layout.ts
10168
+ var ROW_HEIGHT2 = 40;
10169
+ var BAR_HEIGHT = 24;
10170
+ var MIN_BAR = 6;
10171
+ var DIAMOND = 12;
10172
+ var TIP = DIAMOND * Math.SQRT2 / 2;
10173
+ var STUB = 8;
10174
+ var HEAD = 6;
10175
+ var BESIDE = STUB + 6;
10176
+ var groupKey = (id) => `group:${id}`;
10177
+ var itemKey = (id) => `item:${id}`;
10178
+ function ganttRows(items, groups, collapsed) {
10179
+ const known = new Set(groups.map((g) => g.id));
10180
+ const byGroup = /* @__PURE__ */ new Map();
10181
+ const loose = [];
10182
+ for (const item of items) {
10183
+ if (item.group === void 0 || !known.has(item.group)) loose.push(item);
10184
+ else byGroup.set(item.group, [...byGroup.get(item.group) ?? [], item]);
10185
+ }
10186
+ const shown = groups.filter((g) => byGroup.has(g.id));
10187
+ const topLevel = shown.length + loose.length;
10188
+ const rows = [];
10189
+ const push = (row) => rows.push({ ...row, index: rows.length, top: rows.length * ROW_HEIGHT2 });
10190
+ let pos = 0;
10191
+ for (const group of shown) {
10192
+ const children = byGroup.get(group.id);
10193
+ const key = groupKey(group.id);
10194
+ push({ key, kind: "group", level: 1, setSize: topLevel, posInSet: ++pos, group, children });
10195
+ if (collapsed.has(group.id)) continue;
10196
+ children.forEach((item, i) => push({ key: itemKey(item.id), kind: "item", level: 2, setSize: children.length, posInSet: i + 1, parentKey: key, item }));
10197
+ }
10198
+ for (const item of loose) push({ key: itemKey(item.id), kind: "item", level: 1, setSize: topLevel, posInSet: ++pos, item });
10199
+ return rows;
10200
+ }
10201
+ function itemShape(item, scale) {
10202
+ if (item.milestone) {
10203
+ const centre = scale.x((dayOf(item.start) + endDayOf(item.start)) / 2);
10204
+ return { kind: "milestone", x0: centre - TIP, x1: centre + TIP, in: centre - TIP, out: centre + TIP };
10205
+ }
10206
+ const { x, width } = scale.span(item.start, item.end ?? item.start);
10207
+ const x1 = x + Math.max(width, MIN_BAR);
10208
+ return { kind: "bar", x0: x, x1, in: x, out: x1 };
10209
+ }
10210
+ function groupExtent(items) {
10211
+ return timeExtent(items.map((i) => ({ start: i.start, end: i.milestone ? void 0 : i.end })));
10212
+ }
10213
+ var drawable = (shape) => !!shape && Number.isFinite(shape.x0) && Number.isFinite(shape.x1);
10214
+ function ganttArrows(items, rows, shapeOf, collapsed) {
10215
+ const rowOf = /* @__PURE__ */ new Map();
10216
+ const groupRow = /* @__PURE__ */ new Map();
10217
+ for (const row of rows) {
10218
+ if (row.item) rowOf.set(row.item.id, row);
10219
+ if (row.group) groupRow.set(row.group.id, row);
10220
+ }
10221
+ const drawnRow = (item) => rowOf.get(item.id) ?? (item.group !== void 0 && collapsed.has(item.group) ? groupRow.get(item.group) : void 0);
10222
+ const byId = new Map(items.map((i) => [i.id, i]));
10223
+ const arrows = [];
10224
+ for (const succ of items) {
10225
+ for (const id of new Set(succ.dependsOn ?? [])) {
10226
+ const pred = byId.get(id);
10227
+ if (!pred || pred === succ) continue;
10228
+ const from = drawnRow(pred);
10229
+ const to = drawnRow(succ);
10230
+ const a = shapeOf(pred);
10231
+ const b = shapeOf(succ);
10232
+ if (!from || !to || from === to || !drawable(a) || !drawable(b)) continue;
10233
+ const x1 = a.out;
10234
+ const y1 = from.top + ROW_HEIGHT2 / 2;
10235
+ const x2 = b.in;
10236
+ const y2 = to.top + ROW_HEIGHT2 / 2;
10237
+ const routed = x2 < x1 + STUB + HEAD;
10238
+ const edge = y2 > y1 ? from.top + ROW_HEIGHT2 : from.top;
10239
+ const d = routed ? `M${x1} ${y1}H${x1 + STUB}V${edge}H${x2 - STUB - HEAD}V${y2}H${x2}` : `M${x1} ${y1}H${x1 + STUB}V${y2}H${x2}`;
10240
+ const head = `M${x2} ${y2}L${x2 - HEAD} ${y2 - HEAD / 2}L${x2 - HEAD} ${y2 + HEAD / 2}Z`;
10241
+ arrows.push({ key: `${pred.id}>${succ.id}`, from: pred.id, to: succ.id, d, head, routed, x1, y1, x2, y2 });
10242
+ }
10243
+ }
10244
+ return arrows;
10245
+ }
10246
+ function labelPlace(shape, textWidth, scaleWidth, { pad: pad3 = 8, gap = BESIDE } = {}) {
10247
+ if (shape.kind === "bar" && textWidth + 2 * pad3 <= shape.x1 - shape.x0) return "inside";
10248
+ if (shape.x1 + gap + textWidth <= scaleWidth) return "after";
10249
+ return shape.x0 - gap - textWidth >= 0 ? "before" : "after";
10250
+ }
10251
+ var TONES = {
10252
+ neutral: { fill: "bg-[var(--ds-status-neutral)]", on: "text-[var(--ds-text-on-neutral)]" },
10253
+ info: { fill: "bg-[var(--ds-status-info)]", on: "text-[var(--ds-text-on-info)]" },
10254
+ success: { fill: "bg-[var(--ds-status-success)]", on: "text-[var(--ds-text-on-success)]", icon: "check" },
10255
+ warning: { fill: "bg-[var(--ds-status-warning)]", on: "text-[var(--ds-text-on-warning)]", icon: "warning" },
10256
+ error: { fill: "bg-[var(--ds-status-error)]", on: "text-[var(--ds-text-on-error)]", icon: "error" },
10257
+ accent: { fill: "bg-[var(--ds-accent-primary)]", on: "text-[var(--ds-text-on-primary)]" }
10258
+ };
10259
+ var ICON = 12;
10260
+ var ICON_GAP = 4;
10261
+ var LEVEL_INDENT = 16;
10262
+ var TARGET = 24;
10263
+ var KNOCKOUT = 2;
10264
+ var SELECTED = "outline-solid outline-2 outline-offset-2 outline-[var(--ds-text-primary)]";
10265
+ function GanttArrows({ arrows, width, height }) {
10266
+ const { labelWidth } = useTimeViewportLayout();
10267
+ return /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", "data-gantt-arrows": "", className: "pointer-events-none absolute top-0 overflow-visible", style: { insetInlineStart: labelWidth, width, height }, children: arrows.map((a) => /* @__PURE__ */ jsxs("g", { "data-from": a.from, "data-to": a.to, "data-routed": a.routed || void 0, className: "text-[var(--ds-text-muted)]", children: [
10268
+ /* @__PURE__ */ jsx("path", { d: a.d, fill: "none", stroke: "currentColor", strokeWidth: 1.5 }),
10269
+ /* @__PURE__ */ jsx("path", { d: a.head, fill: "currentColor" })
10270
+ ] }, a.key)) });
10271
+ }
10272
+ function Legend({ statuses }) {
10273
+ const entries = Object.entries(statuses);
10274
+ if (!entries.length) return null;
10275
+ return /* @__PURE__ */ jsx("ul", { "aria-label": "Status key", className: "ms-auto flex flex-wrap items-center gap-x-ds-3 gap-y-ds-1 ps-ds-2 text-xs text-[var(--ds-text-secondary)]", children: entries.map(([key, s]) => {
10276
+ const tone = TONES[s.tone] ?? TONES.neutral;
10277
+ return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
10278
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("grid size-4 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]", tone.fill, tone.on), children: tone.icon && /* @__PURE__ */ jsx(Icon, { name: tone.icon, size: 10 }) }),
10279
+ s.label
10280
+ ] }, key);
10281
+ }) });
10282
+ }
10283
+ function Gantt({
10284
+ items,
10285
+ groups = [],
10286
+ selectedId,
10287
+ onSelect,
10288
+ statuses = {},
10289
+ zoom: zoomProp,
10290
+ defaultZoom = "week",
10291
+ onZoomChange,
10292
+ today,
10293
+ weekStartsOn,
10294
+ locale,
10295
+ renderLabel,
10296
+ labelHeader = "Task",
10297
+ toolbar,
10298
+ "aria-label": ariaLabel,
10299
+ className
10300
+ }) {
10301
+ const [zoomState, setZoomState] = useState(defaultZoom);
10302
+ const zoom = zoomProp ?? zoomState;
10303
+ const changeZoom = (next) => {
10304
+ if (zoomProp === void 0) setZoomState(next);
10305
+ onZoomChange?.(next);
10306
+ };
10307
+ const [selectedState, setSelectedState] = useState(null);
10308
+ const selected = selectedId !== void 0 ? selectedId : selectedState;
10309
+ const select = (item) => {
10310
+ if (selectedId === void 0) setSelectedState(item?.id ?? null);
10311
+ onSelect?.(item);
10312
+ };
10313
+ const [collapsed, setCollapsed] = useState(() => new Set(groups.filter((g) => g.defaultCollapsed).map((g) => g.id)));
10314
+ const extent = useMemo(() => timeExtent(items.map((i) => ({ start: i.start, end: i.milestone ? void 0 : i.end }))), [items]);
10315
+ const scale = useTimeScale({ start: extent.start, end: extent.end, zoom, today, weekStartsOn, locale, pad: 1 });
10316
+ const rows = useMemo(() => ganttRows(items, groups, collapsed), [items, groups, collapsed]);
10317
+ const shapes = useMemo(() => new Map(items.map((i) => [i.id, itemShape(i, scale)])), [items, scale]);
10318
+ const extents = useMemo(() => new Map(rows.filter((r) => r.children).map((r) => [r.group.id, groupExtent(r.children)])), [rows]);
10319
+ const arrows = useMemo(() => ganttArrows(items, rows, (i) => shapes.get(i.id), collapsed), [items, rows, shapes, collapsed]);
10320
+ const byKey = useMemo(() => new Map(rows.map((r) => [r.key, r])), [rows]);
10321
+ const names = useMemo(() => {
10322
+ const byId = new Map(items.map((i) => [i.id, i]));
10323
+ const list = new Intl.ListFormat(locale, { type: "conjunction" });
10324
+ const out = /* @__PURE__ */ new Map();
10325
+ for (const item of items) {
10326
+ const after = (item.dependsOn ?? []).flatMap((id) => byId.has(id) && id !== item.id ? [byId.get(id).title] : []);
10327
+ const status = item.status === void 0 ? void 0 : statuses[item.status];
10328
+ out.set(itemKey(item.id), [
10329
+ item.title,
10330
+ item.milestone ? "milestone" : null,
10331
+ formatDateRange(item.start, item.milestone ? item.start : item.end ?? item.start, { locale }),
10332
+ status?.label ?? null,
10333
+ after.length ? `after ${list.format(after)}` : null
10334
+ ].filter((part) => part).join(", "));
10335
+ }
10336
+ for (const row of rows) {
10337
+ if (!row.group || !row.children) continue;
10338
+ const e = extents.get(row.group.id);
10339
+ const count = row.children.length === 1 ? "1 item" : `${row.children.length} items`;
10340
+ out.set(row.key, [row.group.title, formatDateRange(e.start, e.end, { locale }), count].filter((part) => part).join(", "));
10341
+ }
10342
+ return out;
10343
+ }, [items, rows, extents, statuses, locale]);
10344
+ const probeRef = useRef(null);
10345
+ const measure = useTextMeasure(probeRef);
10346
+ const treeRef = useRef(null);
10347
+ const viewportRef = useRef(null);
10348
+ const [activeState, setActive] = useState(null);
10349
+ const selectedKey = selected === null ? null : itemKey(selected);
10350
+ const active = activeState !== null && byKey.has(activeState) ? activeState : selectedKey !== null && byKey.has(selectedKey) ? selectedKey : rows[0]?.key ?? "";
10351
+ const toggle = (id) => {
10352
+ const key = groupKey(id);
10353
+ const closing = !collapsed.has(id);
10354
+ setCollapsed((cur) => {
10355
+ const next = new Set(cur);
10356
+ if (next.has(id)) next.delete(id);
10357
+ else next.add(id);
10358
+ return next;
10359
+ });
10360
+ if (closing && byKey.get(active)?.parentKey === key) {
10361
+ setActive(key);
10362
+ if (treeRef.current?.contains(document.activeElement)) grid.focus(key);
10363
+ }
10364
+ };
10365
+ const activate = (row) => {
10366
+ if (row.group) toggle(row.group.id);
10367
+ else if (row.item) select(row.item);
10368
+ };
10369
+ const grid = useGridKeyboard({
10370
+ container: treeRef,
10371
+ active,
10372
+ setActive,
10373
+ keyOf: (key) => key,
10374
+ move: (from, e) => {
10375
+ if (e.shiftKey) return null;
10376
+ const row = byKey.get(from);
10377
+ if (!row) return null;
10378
+ const next = rows[row.index + 1];
10379
+ switch (e.key) {
10380
+ case "ArrowDown":
10381
+ return next?.key ?? from;
10382
+ case "ArrowUp":
10383
+ return rows[row.index - 1]?.key ?? from;
10384
+ case "Home":
10385
+ return rows[0].key;
10386
+ case "End":
10387
+ return rows[rows.length - 1].key;
10388
+ case "ArrowRight":
10389
+ if (!row.group) return from;
10390
+ if (collapsed.has(row.group.id)) {
10391
+ toggle(row.group.id);
10392
+ return from;
10393
+ }
10394
+ return next?.parentKey === row.key ? next.key : from;
10395
+ case "ArrowLeft":
10396
+ if (row.group) {
10397
+ if (!collapsed.has(row.group.id)) toggle(row.group.id);
10398
+ return from;
10399
+ }
10400
+ return row.parentKey ?? from;
10401
+ default:
10402
+ return null;
10403
+ }
10404
+ },
10405
+ onActivate: (key) => {
10406
+ const row = byKey.get(key);
10407
+ if (row) activate(row);
10408
+ },
10409
+ typeahead: { items: rows.map((r) => r.key), labelOf: (key) => byKey.get(key)?.item?.title ?? byKey.get(key)?.group?.title ?? "" },
10410
+ scrollIntoView: (_key, el) => viewportRef.current?.reveal(el)
10411
+ });
10412
+ const onKeyDown = (e) => {
10413
+ if (e.key === "Escape" && selected !== null && !e.defaultPrevented && !e.altKey && !e.ctrlKey && !e.metaKey) {
10414
+ e.preventDefault();
10415
+ select(null);
10416
+ return;
10417
+ }
10418
+ grid.onKeyDown(e);
10419
+ };
10420
+ const onRowClick = (row) => {
10421
+ if (row.item) select(row.item.id === selected ? null : row.item);
10422
+ else if (row.group) toggle(row.group.id);
10423
+ setActive(row.key);
10424
+ grid.focus(row.key);
10425
+ };
10426
+ const treeitem = (row) => ({
10427
+ role: "treeitem",
10428
+ "aria-level": row.level,
10429
+ "aria-setsize": row.setSize,
10430
+ "aria-posinset": row.posInSet,
10431
+ "aria-label": names.get(row.key),
10432
+ "data-grid-key": row.key,
10433
+ tabIndex: grid.tabIndexOf(row.key),
10434
+ onFocus: () => setActive(row.key)
10435
+ });
10436
+ const renderItem = (row, item) => {
10437
+ const shape = shapes.get(item.id);
10438
+ if (!drawable(shape)) return null;
10439
+ const tone = TONES[item.status !== void 0 && statuses[item.status]?.tone || "neutral"] ?? TONES.neutral;
10440
+ const isSelected = item.id === selected;
10441
+ const textWidth = measure(item.title) + (tone.icon ? ICON + ICON_GAP : 0) + 2 * KNOCKOUT;
10442
+ const place = labelPlace(shape, textWidth, scale.width);
10443
+ const beside = place === "after" ? shape.x1 + BESIDE : shape.x0 - BESIDE - textWidth;
10444
+ const icon = tone.icon && /* @__PURE__ */ jsx(Icon, { name: tone.icon, size: ICON, className: "shrink-0" });
10445
+ const title = /* @__PURE__ */ jsxs(Fragment, { children: [
10446
+ icon,
10447
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: item.title })
10448
+ ] });
10449
+ const shapeBox = shape.kind === "milestone" ? /* @__PURE__ */ jsx("div", { ...treeitem(row), "aria-selected": isSelected, className: cn("group absolute grid cursor-pointer place-items-center", "outline-none"), style: { insetInlineStart: (shape.x0 + shape.x1) / 2 - TARGET / 2, top: (ROW_HEIGHT2 - TARGET) / 2, width: TARGET, height: TARGET }, children: /* @__PURE__ */ jsx("span", { "data-gantt-shape": "milestone", "data-item": item.id, className: cn("block rotate-45 rounded-[2px] group-focus-visible:shadow-[var(--ds-shadow-focus)]", tone.fill, isSelected && SELECTED), style: { width: DIAMOND, height: DIAMOND } }) }) : (
10450
+ // TimeBar keeps the title in view beside the pinned column while the bar's start is scrolled away
10451
+ /* @__PURE__ */ jsx(
10452
+ TimeBar,
10453
+ {
10454
+ ...treeitem(row),
10455
+ "aria-selected": isSelected,
10456
+ "data-gantt-shape": "bar",
10457
+ "data-item": item.id,
10458
+ start: item.start,
10459
+ end: item.end ?? item.start,
10460
+ minWidth: MIN_BAR,
10461
+ className: cn("cursor-pointer text-xs font-medium", tone.fill, tone.on, isSelected && SELECTED),
10462
+ style: { top: (ROW_HEIGHT2 - BAR_HEIGHT) / 2, bottom: "auto", height: BAR_HEIGHT },
10463
+ children: place === "inside" && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "flex min-w-0 items-center gap-ds-1 overflow-hidden", children: title })
10464
+ }
10465
+ )
10466
+ );
10467
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
10468
+ shapeBox,
10469
+ place !== "inside" && // Beside the bar, past the turn an arrow makes; its own background keeps lines that cross the row off the
10470
+ // text. Once it is partly under the pinned column or past the view's end it is hidden, never a fragment.
10471
+ /* @__PURE__ */ jsx(
10472
+ "span",
10473
+ {
10474
+ "aria-hidden": "true",
10475
+ className: "pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center gap-ds-1 whitespace-nowrap rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-surface)] text-xs font-medium leading-4 text-[var(--ds-text-primary)]",
10476
+ style: { insetInlineStart: beside, paddingInline: KNOCKOUT, opacity: `clamp(0, calc(min(${beside} - var(--time-scroll, 0), var(--time-view-end, 1e9) - ${beside + textWidth}) * 1000), 1)` },
10477
+ children: title
10478
+ }
10479
+ )
10480
+ ] });
10481
+ };
10482
+ const renderGroup = (row, extent2) => /* @__PURE__ */ jsxs(Fragment, { children: [
10483
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "absolute inset-0 bg-[var(--ds-bg-muted)] opacity-60" }),
10484
+ extent2 && Number.isFinite(extent2.start) && // a 24 px target around the thin summary bar it draws
10485
+ /* @__PURE__ */ jsx(
10486
+ TimeBar,
10487
+ {
10488
+ ...treeitem(row),
10489
+ "aria-expanded": !collapsed.has(row.group.id),
10490
+ "data-gantt-shape": "summary",
10491
+ start: extent2.start,
10492
+ end: extent2.end,
10493
+ minWidth: MIN_BAR,
10494
+ className: "cursor-pointer before:absolute before:inset-x-0 before:top-1/2 before:h-ds-2 before:-translate-y-1/2 before:rounded-[var(--ds-radius-full)] before:bg-[var(--ds-text-secondary)]",
10495
+ style: { top: (ROW_HEIGHT2 - TARGET) / 2, bottom: "auto", height: TARGET }
10496
+ }
10497
+ )
10498
+ ] });
10499
+ const label = (row) => {
10500
+ if (row.group) {
10501
+ return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "flex min-w-0 items-center gap-ds-1.5 font-medium", children: [
10502
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: cn("shrink-0 transition-transform", !collapsed.has(row.group.id) && "rotate-90") }),
10503
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: row.group.title })
10504
+ ] });
10505
+ }
10506
+ const item = row.item;
10507
+ return (
10508
+ // a container, so what renderLabel adds gives way to the title when the column is narrow (a phone)
10509
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "@container flex min-w-0 items-center gap-ds-2", style: { paddingInlineStart: row.level === 2 ? LEVEL_INDENT : 0 }, children: [
10510
+ item.milestone && /* @__PURE__ */ jsx("span", { className: "size-2 shrink-0 rotate-45 rounded-[1px] bg-[var(--ds-text-secondary)]" }),
10511
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.title }),
10512
+ renderLabel && /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center @max-[10rem]:hidden", children: renderLabel(item) })
10513
+ ] })
10514
+ );
10515
+ };
10516
+ return /* @__PURE__ */ jsx(
10517
+ TimeViewport,
10518
+ {
10519
+ ref: viewportRef,
10520
+ scale,
10521
+ onZoomChange: changeZoom,
10522
+ labelHeader,
10523
+ toolbar: toolbar === false ? false : /* @__PURE__ */ jsxs(Fragment, { children: [
10524
+ toolbar,
10525
+ /* @__PURE__ */ jsx(Legend, { statuses })
10526
+ ] }),
10527
+ tabStop: false,
10528
+ "aria-label": ariaLabel,
10529
+ className,
10530
+ children: /* @__PURE__ */ jsxs("div", { ref: treeRef, role: "tree", "aria-label": ariaLabel, className: "relative", onKeyDown, children: [
10531
+ /* @__PURE__ */ jsx("span", { ref: probeRef, "aria-hidden": "true", className: "pointer-events-none invisible absolute whitespace-nowrap text-xs font-medium", children: "0" }),
10532
+ /* @__PURE__ */ jsx(GanttArrows, { arrows, width: scale.width, height: rows.length * ROW_HEIGHT2 }),
10533
+ rows.map((row) => /* @__PURE__ */ jsx(
10534
+ TimeRow,
10535
+ {
10536
+ "data-row-key": row.key,
10537
+ label: label(row),
10538
+ className: "cursor-pointer",
10539
+ labelClassName: cn(
10540
+ row.group && "bg-[image:linear-gradient(var(--ds-bg-muted),var(--ds-bg-muted))]",
10541
+ row.item && row.item.id === selected && "bg-[image:linear-gradient(var(--ds-accent-subtle),var(--ds-accent-subtle))] text-[var(--ds-accent-subtle-text)]"
10542
+ ),
10543
+ onClick: () => onRowClick(row),
10544
+ children: row.group ? renderGroup(row, extents.get(row.group.id)) : renderItem(row, row.item)
10545
+ },
10546
+ row.key
10547
+ ))
10548
+ ] })
10549
+ }
10550
+ );
10551
+ }
10552
+ var DEFAULT_OFFSET2 = "var(--ds-space-4)";
10553
+ var LAUNCHER_SIZES = {
10554
+ md: "var(--ds-space-10)",
9133
10555
  lg: "var(--ds-space-12)"
9134
10556
  };
9135
10557
  function launcherSize(size = "lg") {
@@ -9877,10 +11299,10 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
9877
11299
  setDropdownOpen(false);
9878
11300
  };
9879
11301
  const onKeyDown = (e) => {
9880
- if (e.key === "Escape" && isTop()) {
9881
- returnFocus();
9882
- setDropdownOpen(false);
9883
- }
11302
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
11303
+ e.preventDefault();
11304
+ returnFocus();
11305
+ setDropdownOpen(false);
9884
11306
  };
9885
11307
  document.addEventListener("mousedown", handler);
9886
11308
  document.addEventListener("keydown", onKeyDown);
@@ -10080,27 +11502,31 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
10080
11502
  setTriggerWidth(node?.offsetWidth);
10081
11503
  }, []);
10082
11504
  const pos = usePosition(triggerRef, floatingRef, open, { side, align, offset: 8, flip: true });
10083
- const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChange(false), tabExits: "edges" });
11505
+ const onOpenChangeRef = useLatest(onOpenChange);
11506
+ const { props: pressMarks, marked } = usePressMarks();
11507
+ const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChangeRef.current(false), tabExits: "edges", marked });
10084
11508
  useEffect(() => {
10085
11509
  if (!open) return;
10086
11510
  const onKeyDown = (e) => {
10087
- if (e.key === "Escape" && isTopLayer()) {
10088
- returnFocus();
10089
- onOpenChange(false);
10090
- }
11511
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
11512
+ const active = document.activeElement;
11513
+ if (active && active !== document.body && !triggerRef.current?.contains(active) && !floatingRef.current?.contains(active)) return;
11514
+ e.preventDefault();
11515
+ returnFocus();
11516
+ onOpenChangeRef.current(false);
10091
11517
  };
10092
- const onClick = (e) => {
11518
+ const onPress = (e) => {
10093
11519
  const target = e.target;
10094
- if (triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
10095
- onOpenChange(false);
11520
+ if (marked(e) || triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
11521
+ onOpenChangeRef.current(false);
10096
11522
  };
10097
11523
  document.addEventListener("keydown", onKeyDown);
10098
- document.addEventListener("mousedown", onClick);
11524
+ document.addEventListener("mousedown", onPress);
10099
11525
  return () => {
10100
11526
  document.removeEventListener("keydown", onKeyDown);
10101
- document.removeEventListener("mousedown", onClick);
11527
+ document.removeEventListener("mousedown", onPress);
10102
11528
  };
10103
- }, [open, onOpenChange, isTopLayer, returnFocus]);
11529
+ }, [open, onOpenChangeRef, isTopLayer, returnFocus, marked]);
10104
11530
  useEffect(() => {
10105
11531
  if (open) focusInitial(floatingRef.current);
10106
11532
  }, [open]);
@@ -10122,6 +11548,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
10122
11548
  {
10123
11549
  ref: floatingRef,
10124
11550
  "data-gs": "popover",
11551
+ ...pressMarks,
10125
11552
  onClick: (e) => e.stopPropagation(),
10126
11553
  className: cn(
10127
11554
  "fixed z-[var(--ds-z-popover)]",
@@ -10337,10 +11764,87 @@ function EllipsisJumper({ totalPages, onPageChange }) {
10337
11764
  }
10338
11765
  );
10339
11766
  }
11767
+ var HeaderTabRowContext = createContext(null);
11768
+ function useHeaderTabRow() {
11769
+ return useContext(HeaderTabRowContext);
11770
+ }
11771
+ function useNarrowRow(row) {
11772
+ const [narrow, setNarrow] = useState(false);
11773
+ useLayoutEffect(() => {
11774
+ if (!row) return;
11775
+ const limit = readBreakpointPx("sm");
11776
+ const measure = () => setNarrow(row.clientWidth < limit);
11777
+ measure();
11778
+ if (typeof ResizeObserver === "undefined") return;
11779
+ const ro = new ResizeObserver(measure);
11780
+ ro.observe(row);
11781
+ return () => ro.disconnect();
11782
+ }, [row]);
11783
+ return narrow;
11784
+ }
10340
11785
  function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal", className }) {
10341
11786
  const isHorizontal = orientation === "horizontal";
11787
+ const tabRow = useHeaderTabRow();
11788
+ const narrow = useNarrowRow(tabRow?.row ?? null);
11789
+ const inHeader = isHorizontal && tabRow != null;
11790
+ if (inHeader && narrow) {
11791
+ const current = steps[currentStep];
11792
+ const where = `Step ${Math.min(currentStep + 1, steps.length)} of ${steps.length}`;
11793
+ return /* @__PURE__ */ jsxs("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("mx-ds-1", className), children: [
11794
+ /* @__PURE__ */ jsxs("p", { className: cn("mb-ds-2 text-sm font-medium text-[var(--ds-text-secondary)]", tabRow.hasCaption && "sr-only"), children: [
11795
+ where,
11796
+ current ? /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
11797
+ ": ",
11798
+ current.label
11799
+ ] }) : null
11800
+ ] }),
11801
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "flex gap-ds-1", children: steps.map((step, i) => /* @__PURE__ */ jsx(
11802
+ "div",
11803
+ {
11804
+ className: cn(
11805
+ "h-1.5 flex-1 rounded-[var(--ds-radius-full)] transition-colors",
11806
+ step.status === "error" ? "bg-[var(--ds-status-error)]" : i <= currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
11807
+ )
11808
+ },
11809
+ i
11810
+ )) })
11811
+ ] });
11812
+ }
11813
+ if (inHeader) {
11814
+ return /* @__PURE__ */ jsx("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("flex items-start mx-ds-1", className), children: steps.map((step, i) => {
11815
+ const status = step.status ?? (i < currentStep ? "complete" : i === currentStep ? "current" : "upcoming");
11816
+ const isLast = i === steps.length - 1;
11817
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-start min-w-0", isLast ? "flex-none" : "flex-1"), children: [
11818
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
11819
+ /* @__PURE__ */ jsx(StepCircle, { status, index: i, onClick: onStepChange ? () => onStepChange(i) : void 0, clickable: !!onStepChange }),
11820
+ !isLast && /* @__PURE__ */ jsx(
11821
+ "div",
11822
+ {
11823
+ className: cn(
11824
+ "flex-1 h-0.5 mx-ds-2 transition-colors",
11825
+ i < currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
11826
+ )
11827
+ }
11828
+ )
11829
+ ] }),
11830
+ /* @__PURE__ */ jsxs("div", { className: "mt-ds-2 pr-ds-2", children: [
11831
+ /* @__PURE__ */ jsx(
11832
+ "p",
11833
+ {
11834
+ className: cn(
11835
+ "text-xs font-medium",
11836
+ status === "current" ? "text-[var(--ds-accent-primary)]" : "text-[var(--ds-text-primary)]"
11837
+ ),
11838
+ children: step.label
11839
+ }
11840
+ ),
11841
+ step.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--ds-text-muted)] mt-ds-0.5", children: step.description })
11842
+ ] })
11843
+ ] }, i);
11844
+ }) });
11845
+ }
10342
11846
  if (!isHorizontal) {
10343
- return /* @__PURE__ */ jsx("nav", { "aria-label": "Progress", className: cn("flex flex-col", className), children: steps.map((step, i) => {
11847
+ return /* @__PURE__ */ jsx("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("flex flex-col", className), children: steps.map((step, i) => {
10344
11848
  const status = step.status ?? (i < currentStep ? "complete" : i === currentStep ? "current" : "upcoming");
10345
11849
  const isLast = i === steps.length - 1;
10346
11850
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-ds-3", children: [
@@ -10375,6 +11879,7 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
10375
11879
  return /* @__PURE__ */ jsx(
10376
11880
  "nav",
10377
11881
  {
11882
+ "data-gs": "stepper",
10378
11883
  "aria-label": "Progress",
10379
11884
  className: cn("flex items-start", className),
10380
11885
  children: steps.map((step, i) => {
@@ -10795,6 +12300,7 @@ function computeCompactFlyoutPos(anchorEl, itemsLength) {
10795
12300
  return { top: Math.max(8, top), left: rect.right - (FLYOUT_BRIDGE_PX - 4) };
10796
12301
  }
10797
12302
  function CompactFlyout({
12303
+ id,
10798
12304
  parentLabel,
10799
12305
  items,
10800
12306
  anchorEl,
@@ -10808,22 +12314,23 @@ function CompactFlyout({
10808
12314
  const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
10809
12315
  const anchorRef = useRef(anchorEl);
10810
12316
  const panelRef = useRef(null);
12317
+ const headingId = useId();
10811
12318
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(true, { trigger: anchorRef, panel: panelRef, close: onClose, tabExits: "edges" });
10812
12319
  useEffect(() => {
10813
12320
  if (!focusIn) return;
10814
- const raf = requestAnimationFrame(() => panelRef.current?.querySelector('[role="menuitem"]')?.focus());
12321
+ const raf = requestAnimationFrame(() => panelRef.current?.querySelector("a[href]")?.focus());
10815
12322
  return () => cancelAnimationFrame(raf);
10816
12323
  }, [focusIn]);
10817
12324
  useEffect(() => {
10818
12325
  function handler(e) {
10819
- if (!isTopLayer()) return;
12326
+ if (e.defaultPrevented || !isTopLayer()) return;
10820
12327
  const inside = !!panelRef.current?.contains(document.activeElement);
10821
12328
  if (e.key === "Escape" || e.key === "ArrowLeft" && inside) {
10822
12329
  e.preventDefault();
10823
12330
  if (inside) returnFocus();
10824
12331
  onClose();
10825
12332
  } else if (inside && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
10826
- const links = Array.from(panelRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
12333
+ const links = Array.from(panelRef.current?.querySelectorAll("a[href]") ?? []);
10827
12334
  const at = links.indexOf(document.activeElement);
10828
12335
  const next = links[(at + (e.key === "ArrowDown" ? 1 : -1) + links.length) % links.length];
10829
12336
  if (next) {
@@ -10846,8 +12353,7 @@ function CompactFlyout({
10846
12353
  "div",
10847
12354
  {
10848
12355
  ref: panelRef,
10849
- role: "menu",
10850
- "aria-label": parentLabel,
12356
+ id,
10851
12357
  className: cn(
10852
12358
  "min-w-[160px] py-ds-1",
10853
12359
  "bg-[var(--ds-sidebar-bg)] rounded-[var(--ds-radius-md)]",
@@ -10855,12 +12361,11 @@ function CompactFlyout({
10855
12361
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]"
10856
12362
  ),
10857
12363
  children: [
10858
- /* @__PURE__ */ jsx("p", { className: "px-ds-2 pt-ds-1 pb-ds-1.5 text-2xs font-semibold uppercase tracking-wider text-[var(--ds-sidebar-text-muted)]", children: parentLabel }),
10859
- /* @__PURE__ */ jsx("div", { className: "flex flex-col px-ds-1", style: { gap: "var(--ds-space-0\\.5)" }, children: items.map((child, i) => /* @__PURE__ */ jsxs(
12364
+ /* @__PURE__ */ jsx("p", { id: headingId, className: "px-ds-2 pt-ds-1 pb-ds-1.5 text-2xs font-semibold uppercase tracking-wider text-[var(--ds-sidebar-text-muted)]", children: parentLabel }),
12365
+ /* @__PURE__ */ jsx("ul", { "aria-labelledby": headingId, className: "flex flex-col px-ds-1", style: { gap: "var(--ds-space-0\\.5)" }, children: items.map((child, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
10860
12366
  "a",
10861
12367
  {
10862
12368
  href: child.href ?? "#",
10863
- role: "menuitem",
10864
12369
  onClick: (e) => {
10865
12370
  e.preventDefault();
10866
12371
  onItemClick?.(child, i);
@@ -10875,9 +12380,8 @@ function CompactFlyout({
10875
12380
  /* @__PURE__ */ jsx(TruncLabel3, { children: child.label }),
10876
12381
  child.badge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: child.badge })
10877
12382
  ]
10878
- },
10879
- child.label
10880
- )) })
12383
+ }
12384
+ ) }, child.label)) })
10881
12385
  ]
10882
12386
  }
10883
12387
  )
@@ -11224,6 +12728,7 @@ function CompactNavItem({
11224
12728
  onToggleExpand
11225
12729
  }) {
11226
12730
  const [triggerEl, setTriggerEl] = useState(null);
12731
+ const flyoutId = useId();
11227
12732
  const [keyboardOpen, setKeyboardOpen] = useState(false);
11228
12733
  if (!isOpen && keyboardOpen) setKeyboardOpen(false);
11229
12734
  const handleMouseEnter = useCallback(() => {
@@ -11255,8 +12760,8 @@ function CompactNavItem({
11255
12760
  setKeyboardOpen(true);
11256
12761
  onOpen(flyoutKey);
11257
12762
  } : void 0,
11258
- "aria-haspopup": !mobile && hasChildren ? "menu" : void 0,
11259
12763
  "aria-expanded": hasChildren ? mobile ? expanded : isOpen : void 0,
12764
+ "aria-controls": !mobile && hasChildren && isOpen ? flyoutId : void 0,
11260
12765
  className: cn(
11261
12766
  "relative flex flex-col items-center justify-center gap-[3px] w-full py-ds-3 px-ds-1 rounded-[var(--ds-radius-md)] transition-colors cursor-pointer",
11262
12767
  // Compact tiles have no edge to stroke: they read the fill and text
@@ -11291,6 +12796,7 @@ function CompactNavItem({
11291
12796
  !mobile && isOpen && hasChildren && item.children && triggerEl && /* @__PURE__ */ jsx(
11292
12797
  CompactFlyout,
11293
12798
  {
12799
+ id: flyoutId,
11294
12800
  parentLabel: item.label,
11295
12801
  items: item.children,
11296
12802
  anchorEl: triggerEl,
@@ -11766,6 +13272,35 @@ function BackButton({ onLight, onClick }) {
11766
13272
  }
11767
13273
  );
11768
13274
  }
13275
+ function BackLink({ onClick, onLight }) {
13276
+ return /* @__PURE__ */ jsxs(
13277
+ "button",
13278
+ {
13279
+ type: "button",
13280
+ onClick,
13281
+ className: cn(
13282
+ "inline-flex items-center gap-ds-1 mb-ds-3 text-sm underline underline-offset-4 cursor-pointer",
13283
+ "rounded-[var(--ds-radius-sm)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
13284
+ onLight ? "text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-primary)] hover:text-[var(--ds-accent-primary)]"
13285
+ ),
13286
+ children: [
13287
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }),
13288
+ "Back"
13289
+ ]
13290
+ }
13291
+ );
13292
+ }
13293
+ function Caption({ children, onLight, onTint }) {
13294
+ return /* @__PURE__ */ jsx("p", { className: cn(
13295
+ "mb-ds-1 text-sm font-medium",
13296
+ onLight ? "text-[var(--ds-text-on-primary)]/80" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]"
13297
+ ), children });
13298
+ }
13299
+ function TabRow({ children, hasCaption, className, onBand }) {
13300
+ const [row, setRow] = useState(null);
13301
+ const value = useMemo(() => ({ row, hasCaption }), [row, hasCaption]);
13302
+ return /* @__PURE__ */ jsx("div", { ref: setRow, className: cn(className, onBand && "mx-0 rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)] px-ds-3 py-ds-2"), children: /* @__PURE__ */ jsx(HeaderTabRowContext.Provider, { value, children }) });
13303
+ }
11769
13304
  var FLOATING_HEADER = cn(
11770
13305
  "rounded-[var(--ds-radius-md)]",
11771
13306
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
@@ -11783,9 +13318,10 @@ function computeFloatingPadding(navbarFloating, sidebarFloating, overlapActive,
11783
13318
  var PageHeader = forwardRef(function PageHeader2({
11784
13319
  title,
11785
13320
  description,
13321
+ caption,
11786
13322
  badge,
11787
13323
  breadcrumbs,
11788
- onBack,
13324
+ onBack: onBackProp,
11789
13325
  actions,
11790
13326
  avatar,
11791
13327
  filters,
@@ -11844,12 +13380,16 @@ var PageHeader = forwardRef(function PageHeader2({
11844
13380
  const containedBreadcrumbs = containedBreadcrumbsProp ?? (container?.breadcrumbsInsideHeader && breadcrumbs?.length ? /* @__PURE__ */ jsx(Breadcrumbs, { variant: "contained", itemVariant: r.breadcrumbStyle, bleed: true, contentMaxWidth: gutterWidth, separator: bcSep, items: [...breadcrumbs, { label: title }] }) : void 0);
11845
13381
  const noBottomMargin = noBottomMarginProp ?? container?.noBottomMargin;
11846
13382
  const overlapActive = container?.overlap ?? false;
13383
+ const focusBack = shell?.focus ? onBackProp : void 0;
13384
+ const onBack = focusBack ? void 0 : onBackProp;
11847
13385
  if (behavior === "snapping") {
11848
13386
  return /* @__PURE__ */ jsx(
11849
13387
  SnappingHeader,
11850
13388
  {
11851
13389
  title,
11852
13390
  description,
13391
+ caption,
13392
+ focusBack,
11853
13393
  badge,
11854
13394
  breadcrumbs: inlineBreadcrumbs,
11855
13395
  onBack,
@@ -11881,13 +13421,15 @@ var PageHeader = forwardRef(function PageHeader2({
11881
13421
  }
11882
13422
  let header;
11883
13423
  if (variant === "minimal") {
11884
- header = /* @__PURE__ */ jsx(MinimalHeader, { title, badge, breadcrumbs: inlineBreadcrumbs, actions, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: floating ? void 0 : ref, isFloating: floating, contentMaxWidth: gutterWidth });
13424
+ header = /* @__PURE__ */ jsx(MinimalHeader, { title, caption, focusBack, badge, breadcrumbs: inlineBreadcrumbs, actions, tabs, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: floating ? void 0 : ref, isFloating: floating, contentMaxWidth: gutterWidth });
11885
13425
  } else if (variant === "tabs-integrated") {
11886
13426
  header = /* @__PURE__ */ jsx(
11887
13427
  TabsIntegratedHeader,
11888
13428
  {
11889
13429
  title,
11890
13430
  description,
13431
+ caption,
13432
+ focusBack,
11891
13433
  badge,
11892
13434
  breadcrumbs: inlineBreadcrumbs,
11893
13435
  onBack,
@@ -11910,6 +13452,9 @@ var PageHeader = forwardRef(function PageHeader2({
11910
13452
  gutter: !insidePaddedColumn,
11911
13453
  title,
11912
13454
  description,
13455
+ caption,
13456
+ focusBack,
13457
+ tabs,
11913
13458
  badge,
11914
13459
  breadcrumbs: inlineBreadcrumbs,
11915
13460
  onBack,
@@ -11935,6 +13480,9 @@ var PageHeader = forwardRef(function PageHeader2({
11935
13480
  {
11936
13481
  title,
11937
13482
  description,
13483
+ caption,
13484
+ focusBack,
13485
+ tabs,
11938
13486
  badge,
11939
13487
  breadcrumbs: inlineBreadcrumbs,
11940
13488
  onBack,
@@ -11975,6 +13523,9 @@ function floatingContentProps(isFloating, contentMaxWidth, extraClassName) {
11975
13523
  function DefaultHeader({
11976
13524
  title,
11977
13525
  description,
13526
+ caption,
13527
+ focusBack,
13528
+ tabs,
11978
13529
  badge,
11979
13530
  breadcrumbs,
11980
13531
  onBack,
@@ -11997,6 +13548,7 @@ function DefaultHeader({
11997
13548
  return /* @__PURE__ */ jsxs(
11998
13549
  "header",
11999
13550
  {
13551
+ "data-gs": "page-header",
12000
13552
  ref: headerRef,
12001
13553
  className: cn(
12002
13554
  !hasBanner && (hasBreadcrumbs ? "pt-ds-3 pb-ds-6" : "py-ds-6"),
@@ -12015,6 +13567,7 @@ function DefaultHeader({
12015
13567
  children: [
12016
13568
  hasBanner && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[var(--ds-bg-canvas)]/85" }),
12017
13569
  /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
13570
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12018
13571
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12019
13572
  /* @__PURE__ */ jsxs("div", { className: cn(
12020
13573
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12024,6 +13577,7 @@ function DefaultHeader({
12024
13577
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack }),
12025
13578
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12026
13579
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
13580
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12027
13581
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12028
13582
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, className: "font-[family-name:var(--ds-font-display)] text-3xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
12029
13583
  badge
@@ -12033,7 +13587,8 @@ function DefaultHeader({
12033
13587
  ] }),
12034
13588
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12035
13589
  ] }),
12036
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) })
13590
+ filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
13591
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
12037
13592
  ] })
12038
13593
  ]
12039
13594
  }
@@ -12041,9 +13596,12 @@ function DefaultHeader({
12041
13596
  }
12042
13597
  function MinimalHeader({
12043
13598
  title,
13599
+ caption,
13600
+ focusBack,
12044
13601
  badge,
12045
13602
  breadcrumbs,
12046
13603
  actions,
13604
+ tabs,
12047
13605
  breadcrumbPill,
12048
13606
  breadcrumbSeparator,
12049
13607
  as: Heading = "h1",
@@ -12054,20 +13612,26 @@ function MinimalHeader({
12054
13612
  }) {
12055
13613
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
12056
13614
  const inner = floatingContentProps(isFloating, contentMaxWidth);
12057
- return /* @__PURE__ */ jsx("header", { ref: headerRef, className: cn("py-ds-2", hasBreadcrumbs && "pt-ds-1", isFloating && FLOATING_HEADER, className), children: /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
13615
+ return /* @__PURE__ */ jsx("header", { "data-gs": "page-header", ref: headerRef, className: cn("py-ds-2", hasBreadcrumbs && "pt-ds-1", tabs && "pb-ds-4", isFloating && FLOATING_HEADER, className), children: /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
13616
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12058
13617
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
13618
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12059
13619
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && "mt-ds-0.5"), children: [
12060
13620
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
12061
13621
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, lines: 1, className: "font-[family-name:var(--ds-font-display)] text-xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
12062
13622
  badge
12063
13623
  ] }),
12064
13624
  actions && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) })
12065
- ] })
13625
+ ] }),
13626
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
12066
13627
  ] }) });
12067
13628
  }
12068
13629
  function ColourfulHeader({
12069
13630
  title,
12070
13631
  description,
13632
+ caption,
13633
+ focusBack,
13634
+ tabs,
12071
13635
  badge,
12072
13636
  breadcrumbs,
12073
13637
  onBack,
@@ -12092,6 +13656,7 @@ function ColourfulHeader({
12092
13656
  return /* @__PURE__ */ jsxs(
12093
13657
  "header",
12094
13658
  {
13659
+ "data-gs": "page-header",
12095
13660
  ref: headerRef,
12096
13661
  className: cn(
12097
13662
  "relative overflow-hidden w-full",
@@ -12135,6 +13700,7 @@ function ColourfulHeader({
12135
13700
  ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
12136
13701
  },
12137
13702
  children: [
13703
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight: isBold }),
12138
13704
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight: isBold, onTint: !isBold, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12139
13705
  /* @__PURE__ */ jsxs("div", { className: cn(
12140
13706
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12144,6 +13710,7 @@ function ColourfulHeader({
12144
13710
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack, onLight: isBold }),
12145
13711
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12146
13712
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
13713
+ caption && /* @__PURE__ */ jsx(Caption, { onLight: isBold, onTint: !isBold, children: caption }),
12147
13714
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12148
13715
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, className: cn(
12149
13716
  "font-[family-name:var(--ds-font-display)] text-3xl font-[var(--ds-weight-heading)]",
@@ -12159,7 +13726,8 @@ function ColourfulHeader({
12159
13726
  ] }),
12160
13727
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12161
13728
  ] }),
12162
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) })
13729
+ filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
13730
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBold, children: tabs })
12163
13731
  ]
12164
13732
  }
12165
13733
  )
@@ -12170,6 +13738,8 @@ function ColourfulHeader({
12170
13738
  function SnappingHeader({
12171
13739
  title,
12172
13740
  description,
13741
+ caption,
13742
+ focusBack,
12173
13743
  badge,
12174
13744
  breadcrumbs,
12175
13745
  onBack,
@@ -12293,6 +13863,7 @@ function SnappingHeader({
12293
13863
  const headerEl = /* @__PURE__ */ jsxs(
12294
13864
  "header",
12295
13865
  {
13866
+ "data-gs": "page-header",
12296
13867
  ref: !isFloating ? mergedRef : void 0,
12297
13868
  className: cn(
12298
13869
  "transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
@@ -12329,8 +13900,10 @@ function SnappingHeader({
12329
13900
  onTint && !backgroundImage && /* @__PURE__ */ jsx(TintWash, {}),
12330
13901
  (() => {
12331
13902
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
12332
- const innerContent = isMinimal && !collapsed ? /* @__PURE__ */ jsxs("div", { className: cn("py-ds-2", hasBreadcrumbs && !hideExpandedBreadcrumbs && "pt-ds-1"), children: [
13903
+ const innerContent = isMinimal && !collapsed ? /* @__PURE__ */ jsxs("div", { className: cn("py-ds-2", hasBreadcrumbs && !hideExpandedBreadcrumbs && "pt-ds-1", tabs && "pb-ds-4"), children: [
13904
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12333
13905
  hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
13906
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12334
13907
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && !hideExpandedBreadcrumbs && "mt-ds-0.5"), children: [
12335
13908
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
12336
13909
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, className: "font-[family-name:var(--ds-font-display)] text-xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
@@ -12340,7 +13913,7 @@ function SnappingHeader({
12340
13913
  ] }),
12341
13914
  filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-3", children: filters }) }),
12342
13915
  expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-3", children: expandedContent }),
12343
- tabs && /* @__PURE__ */ jsx("div", { className: "mt-ds-3 -mx-ds-1", children: tabs })
13916
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
12344
13917
  ] }) : /* @__PURE__ */ jsxs(
12345
13918
  "div",
12346
13919
  {
@@ -12354,6 +13927,7 @@ function SnappingHeader({
12354
13927
  ),
12355
13928
  style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : void 0,
12356
13929
  children: [
13930
+ !collapsed && focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight }),
12357
13931
  !collapsed && hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight, onTint, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12358
13932
  /* @__PURE__ */ jsxs("div", { className: cn(
12359
13933
  "flex justify-between gap-ds-4",
@@ -12365,6 +13939,7 @@ function SnappingHeader({
12365
13939
  onBack && !collapsed && /* @__PURE__ */ jsx(BackButton, { onClick: onBack, onLight }),
12366
13940
  avatar && !collapsed && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12367
13941
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
13942
+ !collapsed && caption && /* @__PURE__ */ jsx(Caption, { onLight, onTint, children: caption }),
12368
13943
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12369
13944
  collapsed && hasBreadcrumbs && (() => {
12370
13945
  const lastCrumb = breadcrumbs[breadcrumbs.length - 1];
@@ -12407,7 +13982,7 @@ function SnappingHeader({
12407
13982
  ] }),
12408
13983
  !collapsed && filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
12409
13984
  !collapsed && expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: expandedContent }),
12410
- !collapsed && tabs && /* @__PURE__ */ jsx("div", { className: "mt-ds-4 -mx-ds-1", children: tabs })
13985
+ !collapsed && tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBoldColourful, children: tabs })
12411
13986
  ]
12412
13987
  }
12413
13988
  );
@@ -12455,6 +14030,8 @@ function SnappingHeader({
12455
14030
  function TabsIntegratedHeader({
12456
14031
  title,
12457
14032
  description,
14033
+ caption,
14034
+ focusBack,
12458
14035
  badge,
12459
14036
  breadcrumbs,
12460
14037
  onBack,
@@ -12471,7 +14048,8 @@ function TabsIntegratedHeader({
12471
14048
  }) {
12472
14049
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
12473
14050
  const inner = floatingContentProps(isFloating, contentMaxWidth);
12474
- return /* @__PURE__ */ jsx("header", { ref: headerRef, className: cn(hasBreadcrumbs ? "pt-ds-3" : "pt-ds-6", "pb-ds-4", isFloating && cn(FLOATING_HEADER, "overflow-hidden"), className), children: /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
14051
+ return /* @__PURE__ */ jsx("header", { "data-gs": "page-header", ref: headerRef, className: cn(hasBreadcrumbs ? "pt-ds-3" : "pt-ds-6", "pb-ds-4", isFloating && cn(FLOATING_HEADER, "overflow-hidden"), className), children: /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
14052
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12475
14053
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12476
14054
  /* @__PURE__ */ jsxs("div", { className: cn(
12477
14055
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12481,6 +14059,7 @@ function TabsIntegratedHeader({
12481
14059
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack }),
12482
14060
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12483
14061
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
14062
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12484
14063
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12485
14064
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, className: "font-[family-name:var(--ds-font-display)] text-3xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: title }),
12486
14065
  badge
@@ -12490,7 +14069,7 @@ function TabsIntegratedHeader({
12490
14069
  ] }),
12491
14070
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12492
14071
  ] }),
12493
- tabs && /* @__PURE__ */ jsx("div", { className: "mt-ds-4 -mx-ds-1", children: tabs })
14072
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
12494
14073
  ] }) });
12495
14074
  }
12496
14075
  var DEFAULT_BASE = 48;
@@ -12655,6 +14234,7 @@ function NavMenu({
12655
14234
  const [open, setOpen] = useState(false);
12656
14235
  const triggerRef = useRef(null);
12657
14236
  const panelRef = useRef(null);
14237
+ const panelId = useId();
12658
14238
  const [pos, setPos] = useState({ top: 0, left: 0, minWidth: 0 });
12659
14239
  const close = useCallback(() => setOpen(false), []);
12660
14240
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close, tabExits: "edges" });
@@ -12680,10 +14260,10 @@ function NavMenu({
12680
14260
  }
12681
14261
  }
12682
14262
  function handleKey(e) {
12683
- if (e.key === "Escape" && isTopLayer()) {
12684
- returnFocus();
12685
- close();
12686
- }
14263
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
14264
+ e.preventDefault();
14265
+ returnFocus();
14266
+ close();
12687
14267
  }
12688
14268
  document.addEventListener("mousedown", handleClick);
12689
14269
  document.addEventListener("keydown", handleKey);
@@ -12725,7 +14305,7 @@ function NavMenu({
12725
14305
  ref: triggerRef,
12726
14306
  onClick: () => setOpen(!open),
12727
14307
  "aria-expanded": open,
12728
- "aria-haspopup": "true",
14308
+ "aria-controls": open ? panelId : void 0,
12729
14309
  className: triggerClasses,
12730
14310
  children: [
12731
14311
  label,
@@ -12745,7 +14325,7 @@ function NavMenu({
12745
14325
  "div",
12746
14326
  {
12747
14327
  ref: panelRef,
12748
- role: "menu",
14328
+ id: panelId,
12749
14329
  "data-gs": "menu",
12750
14330
  className: cn(
12751
14331
  "fixed z-[var(--ds-z-popover)] py-ds-1.5",
@@ -12754,7 +14334,7 @@ function NavMenu({
12754
14334
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]"
12755
14335
  ),
12756
14336
  style: { top: pos.top, left: pos.left, minWidth: pos.minWidth },
12757
- children: items.map((item, i) => {
14337
+ children: /* @__PURE__ */ jsx("ul", { children: items.map((item, i) => {
12758
14338
  if (item.children?.length) {
12759
14339
  return /* @__PURE__ */ jsx(
12760
14340
  NavMenuGroup,
@@ -12780,7 +14360,7 @@ function NavMenu({
12780
14360
  },
12781
14361
  item.label
12782
14362
  );
12783
- })
14363
+ }) })
12784
14364
  }
12785
14365
  ),
12786
14366
  document.body
@@ -12791,11 +14371,10 @@ function NavMenuItemRow({
12791
14371
  item,
12792
14372
  onClick
12793
14373
  }) {
12794
- return /* @__PURE__ */ jsxs(
14374
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
12795
14375
  "a",
12796
14376
  {
12797
14377
  href: item.href ?? "#",
12798
- role: "menuitem",
12799
14378
  onClick: (e) => {
12800
14379
  e.preventDefault();
12801
14380
  onClick();
@@ -12821,14 +14400,15 @@ function NavMenuItemRow({
12821
14400
  ] })
12822
14401
  ]
12823
14402
  }
12824
- );
14403
+ ) });
12825
14404
  }
12826
14405
  function NavMenuGroup({
12827
14406
  group,
12828
14407
  isFirst,
12829
14408
  onItemClick
12830
14409
  }) {
12831
- return /* @__PURE__ */ jsxs("div", { children: [
14410
+ const headingId = useId();
14411
+ return /* @__PURE__ */ jsxs("li", { children: [
12832
14412
  !isFirst && /* @__PURE__ */ jsx(
12833
14413
  "div",
12834
14414
  {
@@ -12836,15 +14416,15 @@ function NavMenuGroup({
12836
14416
  style: { height: "var(--ds-divider-width, 1px)", marginTop: "var(--ds-space-1)", marginBottom: "var(--ds-space-1)" }
12837
14417
  }
12838
14418
  ),
12839
- /* @__PURE__ */ jsx("p", { className: "px-ds-3 pt-ds-1.5 pb-ds-1 text-2xs font-semibold uppercase tracking-wider text-[var(--ds-text-muted)]", children: group.label }),
12840
- group.children.map((child) => /* @__PURE__ */ jsx(
14419
+ /* @__PURE__ */ jsx("p", { id: headingId, className: "px-ds-3 pt-ds-1.5 pb-ds-1 text-2xs font-semibold uppercase tracking-wider text-[var(--ds-text-muted)]", children: group.label }),
14420
+ /* @__PURE__ */ jsx("ul", { "aria-labelledby": headingId, children: group.children.map((child) => /* @__PURE__ */ jsx(
12841
14421
  NavMenuItemRow,
12842
14422
  {
12843
14423
  item: child,
12844
14424
  onClick: () => onItemClick(child)
12845
14425
  },
12846
14426
  child.label
12847
- ))
14427
+ )) })
12848
14428
  ] });
12849
14429
  }
12850
14430
  function resolveNavItemType(type) {
@@ -12883,30 +14463,198 @@ var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href,
12883
14463
  ]
12884
14464
  }
12885
14465
  );
12886
- });
12887
- function getTypeClasses(type, active) {
12888
- switch (type) {
12889
- case "pill":
12890
- return cn(
12891
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
12892
- active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
12893
- );
12894
- case "underline":
12895
- return cn(
12896
- "px-ds-2 py-ds-2 relative",
12897
- active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
12898
- );
12899
- case "minimal":
12900
- return cn(
12901
- "px-ds-2 py-ds-1",
12902
- active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
12903
- );
12904
- case "tint":
12905
- return cn(
12906
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
12907
- active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
12908
- );
12909
- }
14466
+ });
14467
+ function getTypeClasses(type, active) {
14468
+ switch (type) {
14469
+ case "pill":
14470
+ return cn(
14471
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
14472
+ active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
14473
+ );
14474
+ case "underline":
14475
+ return cn(
14476
+ "px-ds-2 py-ds-2 relative",
14477
+ active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
14478
+ );
14479
+ case "minimal":
14480
+ return cn(
14481
+ "px-ds-2 py-ds-1",
14482
+ active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
14483
+ );
14484
+ case "tint":
14485
+ return cn(
14486
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
14487
+ active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
14488
+ );
14489
+ }
14490
+ }
14491
+ var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
14492
+ var sizeClasses = {
14493
+ // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
14494
+ sm: "max-w-md",
14495
+ md: "max-w-lg",
14496
+ lg: "max-w-2xl",
14497
+ xl: "max-w-4xl",
14498
+ full: "max-w-[calc(100vw-2rem)]"
14499
+ };
14500
+ function Dialog({ open, onClose: onCloseProp, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit, dirty, onDiscard }) {
14501
+ const guard = useDirtyGuard(dirty, onCloseProp, onDiscard);
14502
+ const onClose = guard.requestClose;
14503
+ const titleId = useId();
14504
+ const descriptionId = useId();
14505
+ const isMobile = useBreakpoint("sm");
14506
+ const overlayRef = useRef(null);
14507
+ const dialogRef = useRef(null);
14508
+ const opener = useRef(null);
14509
+ const defaultButtonRef = useRef(null);
14510
+ const onCloseRef = useLatest(onClose);
14511
+ const onSubmitRef = useLatest(onSubmit);
14512
+ const isTopLayer = useOverlayLayer(open);
14513
+ useFocusTrap(dialogRef, open, isTopLayer);
14514
+ const before = useFocusBeforeOpen(open);
14515
+ useEffect(() => {
14516
+ if (!open) return;
14517
+ const active = before.current;
14518
+ opener.current = active instanceof HTMLElement && active !== document.body && active.isConnected ? rememberFocus(active) : null;
14519
+ }, [open, before]);
14520
+ useEffect(() => {
14521
+ if (!open) return;
14522
+ requestAnimationFrame(() => {
14523
+ focusInitial(dialogRef.current, { destructive: variant === "destructive" });
14524
+ });
14525
+ }, [open, variant]);
14526
+ useEffect(() => {
14527
+ if (!open) return;
14528
+ const handler = (e) => {
14529
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
14530
+ e.preventDefault();
14531
+ onCloseRef.current();
14532
+ };
14533
+ document.addEventListener("keydown", handler);
14534
+ return () => {
14535
+ document.removeEventListener("keydown", handler);
14536
+ };
14537
+ }, [open, onCloseRef, isTopLayer]);
14538
+ useEffect(() => {
14539
+ if (!open) return;
14540
+ lockBodyScroll();
14541
+ return () => {
14542
+ unlockBodyScroll();
14543
+ const r = opener.current;
14544
+ opener.current = null;
14545
+ const active = document.activeElement;
14546
+ if (!active || active === document.body || !active.isConnected) r?.restore();
14547
+ };
14548
+ }, [open]);
14549
+ if (!open) return null;
14550
+ return createPortal(
14551
+ /* @__PURE__ */ jsxs(
14552
+ "div",
14553
+ {
14554
+ ref: overlayRef,
14555
+ className: "fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center p-ds-4",
14556
+ children: [
14557
+ /* @__PURE__ */ jsx(
14558
+ "div",
14559
+ {
14560
+ className: "fixed inset-0 bg-black/50 [backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))] [-webkit-backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))]",
14561
+ onClick: alert ? void 0 : onClose,
14562
+ "aria-hidden": "true"
14563
+ }
14564
+ ),
14565
+ /* @__PURE__ */ jsxs(
14566
+ "form",
14567
+ {
14568
+ onSubmit: (e) => {
14569
+ e.preventDefault();
14570
+ if (variant === "destructive") return;
14571
+ const submitter = e.nativeEvent.submitter;
14572
+ const implicit = !submitter || submitter === defaultButtonRef.current;
14573
+ if (!implicit && submitter.getAttribute("type") !== "submit") return;
14574
+ if (implicit) {
14575
+ const explicit = e.currentTarget.querySelector(`button[type="submit"]:not([${DEFAULT_BUTTON_ATTR}])`);
14576
+ if (explicit?.disabled) return;
14577
+ }
14578
+ onSubmitRef.current?.();
14579
+ },
14580
+ className: "contents",
14581
+ children: [
14582
+ /* @__PURE__ */ jsx(
14583
+ "button",
14584
+ {
14585
+ ref: defaultButtonRef,
14586
+ type: "submit",
14587
+ tabIndex: -1,
14588
+ "aria-hidden": "true",
14589
+ className: "sr-only",
14590
+ ...{ [DEFAULT_BUTTON_ATTR]: "" }
14591
+ }
14592
+ ),
14593
+ /* @__PURE__ */ jsxs(
14594
+ "div",
14595
+ {
14596
+ ref: dialogRef,
14597
+ role: alert ? "alertdialog" : "dialog",
14598
+ "aria-modal": "true",
14599
+ "data-gs": "dialog",
14600
+ "aria-labelledby": titleId,
14601
+ "aria-describedby": description ? descriptionId : void 0,
14602
+ style: { containerType: "inline-size" },
14603
+ className: cn(
14604
+ "relative z-10 w-full",
14605
+ isMobile ? "max-w-none h-full" : sizeClasses[size],
14606
+ "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
14607
+ GLASS_OVERLAY,
14608
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
14609
+ isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)]",
14610
+ "overflow-hidden",
14611
+ "animate-in fade-in-0 zoom-in-95"
14612
+ ),
14613
+ children: [
14614
+ /* @__PURE__ */ jsx(
14615
+ "button",
14616
+ {
14617
+ type: "button",
14618
+ onClick: onClose,
14619
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
14620
+ className: "absolute right-4 top-4 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
14621
+ "aria-label": "Close",
14622
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
14623
+ }
14624
+ ),
14625
+ /* @__PURE__ */ jsx("div", { className: "p-ds-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-ds-3", children: [
14626
+ variant === "destructive" && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center shrink-0 w-10 h-10 rounded-[var(--ds-radius-full)] bg-[var(--ds-status-error-bg)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 20, className: "text-[var(--ds-status-error)]" }) }),
14627
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
14628
+ /* @__PURE__ */ jsx(
14629
+ "h2",
14630
+ {
14631
+ id: titleId,
14632
+ className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] leading-none tracking-tight",
14633
+ children: title
14634
+ }
14635
+ ),
14636
+ description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
14637
+ children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) })
14638
+ ] })
14639
+ ] }) }),
14640
+ /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
14641
+ footer && /* @__PURE__ */ jsx("div", { className: cn(
14642
+ "flex justify-end gap-ds-2 p-ds-4",
14643
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
14644
+ "bg-[var(--ds-bg-muted)]",
14645
+ "rounded-b-[var(--ds-radius-xl)]"
14646
+ ), children: footer })
14647
+ ]
14648
+ }
14649
+ )
14650
+ ]
14651
+ }
14652
+ )
14653
+ ]
14654
+ }
14655
+ ),
14656
+ container ?? document.body
14657
+ );
12910
14658
  }
12911
14659
  function HamburgerButton({ onClick, open, className: cls }) {
12912
14660
  return /* @__PURE__ */ jsx(
@@ -12963,9 +14711,11 @@ var Navbar = forwardRef(function Navbar2({
12963
14711
  const [visibleCount, setVisibleCount] = useState(null);
12964
14712
  const links = useMemo(() => flattenLinks(navLinks), [navLinks]);
12965
14713
  const mobileHidesLinks = !!appShell?.isMobile && (appShell.hasMobileMenu || appShell.hasMobileNav);
14714
+ const focus = appShell?.focus ?? null;
14715
+ const [confirmExit, setConfirmExit] = useState(false);
12966
14716
  useEffect(() => {
12967
14717
  const header = headerRef.current;
12968
- if (!header || mobileHidesLinks || links.length === 0 || typeof ResizeObserver === "undefined") return;
14718
+ if (!header || mobileHidesLinks || focus || links.length === 0 || typeof ResizeObserver === "undefined") return;
12969
14719
  const measure = () => {
12970
14720
  const nav = navRef.current;
12971
14721
  if (!nav) return;
@@ -13002,7 +14752,7 @@ var Navbar = forwardRef(function Navbar2({
13002
14752
  const ro = new ResizeObserver(measure);
13003
14753
  ro.observe(header);
13004
14754
  return () => ro.disconnect();
13005
- }, [mobileHidesLinks, links]);
14755
+ }, [mobileHidesLinks, focus, links]);
13006
14756
  const [topInset, setTopInset] = useState(null);
13007
14757
  const shellEl = appShell?.container ?? null;
13008
14758
  useLayoutEffect(() => {
@@ -13034,7 +14784,40 @@ var Navbar = forwardRef(function Navbar2({
13034
14784
  }, [visibleCount, links]);
13035
14785
  const showHamburger = appShell?.isMobile && appShell.hasMobileMenu && !(isSmallPhone && appShell.hasMobileNav) || !!onMenuClick;
13036
14786
  const handleHamburger = () => onMenuClick ? onMenuClick() : appShell?.setSidebarOpen(!appShell.sidebarOpen);
13037
- const hideNavLinks = mobileHidesLinks;
14787
+ const hideNavLinks = mobileHidesLinks || !!focus;
14788
+ const shownSearch = focus ? null : search;
14789
+ const requestExit = () => {
14790
+ if (!focus) return;
14791
+ if (focus.kind === "in-app" && appShell?.hasUnsavedChanges) {
14792
+ setConfirmExit(true);
14793
+ return;
14794
+ }
14795
+ focus.onExit?.();
14796
+ };
14797
+ const exitControl = focus ? /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", onClick: requestExit, children: focus.exitLabel ?? (focus.kind === "before-app" ? "Save and log out" : "Exit") }) : null;
14798
+ const shownUserMenu = focus?.kind === "before-app" ? null : userMenu;
14799
+ const asking = confirmExit && focus?.kind === "in-app" && !!appShell?.hasUnsavedChanges;
14800
+ if (confirmExit && !asking) setConfirmExit(false);
14801
+ const exitDialog = focus?.kind === "in-app" ? /* @__PURE__ */ jsx(
14802
+ Dialog,
14803
+ {
14804
+ open: asking,
14805
+ onClose: () => setConfirmExit(false),
14806
+ title: "You have unsaved changes",
14807
+ description: "Exit now and your changes are lost.",
14808
+ variant: "destructive",
14809
+ size: "sm",
14810
+ alert: true,
14811
+ container: shellEl ?? void 0,
14812
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
14813
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setConfirmExit(false), children: "Keep editing" }),
14814
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => {
14815
+ setConfirmExit(false);
14816
+ focus.onExit?.();
14817
+ }, children: "Discard changes" })
14818
+ ] })
14819
+ }
14820
+ ) : null;
13038
14821
  const type = resolveNavItemType(typeProp);
13039
14822
  const isMinimal = type === "minimal";
13040
14823
  const isFloating = position === "floating";
@@ -13100,9 +14883,10 @@ var Navbar = forwardRef(function Navbar2({
13100
14883
  ] }),
13101
14884
  navLinks && !hideNavLinks ? /* @__PURE__ */ jsx(NavbarTypeContext.Provider, { value: type, children: /* @__PURE__ */ jsx("nav", { ref: navRef, className: "flex items-center gap-ds-2 min-w-0 overflow-x-clip justify-center", "data-navbar-type": type, children: linkRow }) }) : /* @__PURE__ */ jsx("div", { "data-navbar-spacer": "" }),
13102
14885
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-4 justify-end", children: [
13103
- search && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: search }),
14886
+ shownSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: shownSearch }),
13104
14887
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }),
13105
- userMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: userMenu })
14888
+ exitControl && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: exitControl }),
14889
+ shownUserMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: shownUserMenu })
13106
14890
  ] })
13107
14891
  ]
13108
14892
  }
@@ -13127,15 +14911,16 @@ var Navbar = forwardRef(function Navbar2({
13127
14911
  align === "right" && /* @__PURE__ */ jsx("div", { "data-navbar-spacer": "", className: "flex-1" }),
13128
14912
  navLinks && !hideNavLinks && /* @__PURE__ */ jsx(NavbarTypeContext.Provider, { value: type, children: /* @__PURE__ */ jsx("nav", { ref: navRef, className: cn("flex items-center gap-ds-2 min-w-0 overflow-x-clip", align === "left" && "flex-1"), "data-navbar-type": type, children: linkRow }) }),
13129
14913
  (!navLinks || hideNavLinks) && /* @__PURE__ */ jsx("div", { "data-navbar-spacer": "", className: "flex-1" }),
13130
- search && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: search }),
14914
+ shownSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: shownSearch }),
13131
14915
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }),
13132
- userMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: userMenu })
14916
+ exitControl && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: exitControl }),
14917
+ shownUserMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: shownUserMenu })
13133
14918
  ]
13134
14919
  }
13135
14920
  );
13136
14921
  }
13137
14922
  if (isFloating) {
13138
- return /* @__PURE__ */ jsx(
14923
+ return /* @__PURE__ */ jsxs(
13139
14924
  "div",
13140
14925
  {
13141
14926
  ref,
@@ -13145,11 +14930,17 @@ var Navbar = forwardRef(function Navbar2({
13145
14930
  className
13146
14931
  ),
13147
14932
  style: { padding: "var(--ds-space-3)" },
13148
- children: headerEl
14933
+ children: [
14934
+ headerEl,
14935
+ exitDialog
14936
+ ]
13149
14937
  }
13150
14938
  );
13151
14939
  }
13152
- return headerEl;
14940
+ return exitDialog ? /* @__PURE__ */ jsxs(Fragment, { children: [
14941
+ headerEl,
14942
+ exitDialog
14943
+ ] }) : headerEl;
13153
14944
  });
13154
14945
  function MobileHeaderMenu({ items, type = "tint", onItemClick, className }) {
13155
14946
  return /* @__PURE__ */ jsx("nav", { className: cn("flex flex-col", className), style: { padding: "var(--ds-space-4)" }, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col", style: { gap: type === "underline" ? 0 : "var(--ds-space-1)" }, children: items.map((item, i) => /* @__PURE__ */ jsx(
@@ -13241,172 +15032,6 @@ function MobileMenuItem({
13241
15032
  }
13242
15033
  );
13243
15034
  }
13244
- var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
13245
- var sizeClasses = {
13246
- // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
13247
- sm: "max-w-md",
13248
- md: "max-w-lg",
13249
- lg: "max-w-2xl",
13250
- xl: "max-w-4xl",
13251
- full: "max-w-[calc(100vw-2rem)]"
13252
- };
13253
- function Dialog({ open, onClose: onCloseProp, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit, dirty, onDiscard }) {
13254
- const guard = useDirtyGuard(dirty, onCloseProp, onDiscard);
13255
- const onClose = guard.requestClose;
13256
- const titleId = useId();
13257
- const descriptionId = useId();
13258
- const isMobile = useBreakpoint("sm");
13259
- const overlayRef = useRef(null);
13260
- const dialogRef = useRef(null);
13261
- const opener = useRef(null);
13262
- const defaultButtonRef = useRef(null);
13263
- const onCloseRef = useLatest(onClose);
13264
- const onSubmitRef = useLatest(onSubmit);
13265
- const isTopLayer = useOverlayLayer(open);
13266
- useFocusTrap(dialogRef, open, isTopLayer);
13267
- const before = useFocusBeforeOpen(open);
13268
- useEffect(() => {
13269
- if (!open) return;
13270
- const active = before.current;
13271
- opener.current = active instanceof HTMLElement && active !== document.body && active.isConnected ? rememberFocus(active) : null;
13272
- }, [open, before]);
13273
- useEffect(() => {
13274
- if (!open) return;
13275
- requestAnimationFrame(() => {
13276
- focusInitial(dialogRef.current, { destructive: variant === "destructive" });
13277
- });
13278
- }, [open, variant]);
13279
- useEffect(() => {
13280
- if (!open) return;
13281
- const handler = (e) => {
13282
- if (e.key === "Escape" && !alert && isTopLayer()) onCloseRef.current();
13283
- };
13284
- document.addEventListener("keydown", handler);
13285
- return () => {
13286
- document.removeEventListener("keydown", handler);
13287
- };
13288
- }, [open, alert, onCloseRef, isTopLayer]);
13289
- useEffect(() => {
13290
- if (!open) return;
13291
- lockBodyScroll();
13292
- return () => {
13293
- unlockBodyScroll();
13294
- const r = opener.current;
13295
- opener.current = null;
13296
- const active = document.activeElement;
13297
- if (!active || active === document.body || !active.isConnected) r?.restore();
13298
- };
13299
- }, [open]);
13300
- if (!open) return null;
13301
- return createPortal(
13302
- /* @__PURE__ */ jsxs(
13303
- "div",
13304
- {
13305
- ref: overlayRef,
13306
- className: "fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center p-ds-4",
13307
- children: [
13308
- /* @__PURE__ */ jsx(
13309
- "div",
13310
- {
13311
- className: "fixed inset-0 bg-black/50 [backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))] [-webkit-backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))]",
13312
- onClick: alert ? void 0 : onClose,
13313
- "aria-hidden": "true"
13314
- }
13315
- ),
13316
- /* @__PURE__ */ jsxs(
13317
- "form",
13318
- {
13319
- onSubmit: (e) => {
13320
- e.preventDefault();
13321
- if (variant === "destructive") return;
13322
- const submitter = e.nativeEvent.submitter;
13323
- const implicit = !submitter || submitter === defaultButtonRef.current;
13324
- if (!implicit && submitter.getAttribute("type") !== "submit") return;
13325
- if (implicit) {
13326
- const explicit = e.currentTarget.querySelector(`button[type="submit"]:not([${DEFAULT_BUTTON_ATTR}])`);
13327
- if (explicit?.disabled) return;
13328
- }
13329
- onSubmitRef.current?.();
13330
- },
13331
- className: "contents",
13332
- children: [
13333
- /* @__PURE__ */ jsx(
13334
- "button",
13335
- {
13336
- ref: defaultButtonRef,
13337
- type: "submit",
13338
- tabIndex: -1,
13339
- "aria-hidden": "true",
13340
- className: "sr-only",
13341
- ...{ [DEFAULT_BUTTON_ATTR]: "" }
13342
- }
13343
- ),
13344
- /* @__PURE__ */ jsxs(
13345
- "div",
13346
- {
13347
- ref: dialogRef,
13348
- role: alert ? "alertdialog" : "dialog",
13349
- "aria-modal": "true",
13350
- "data-gs": "dialog",
13351
- "aria-labelledby": titleId,
13352
- "aria-describedby": description ? descriptionId : void 0,
13353
- style: { containerType: "inline-size" },
13354
- className: cn(
13355
- "relative z-10 w-full",
13356
- isMobile ? "max-w-none h-full" : sizeClasses[size],
13357
- "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
13358
- GLASS_OVERLAY,
13359
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
13360
- isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)]",
13361
- "overflow-hidden",
13362
- "animate-in fade-in-0 zoom-in-95"
13363
- ),
13364
- children: [
13365
- /* @__PURE__ */ jsx(
13366
- "button",
13367
- {
13368
- type: "button",
13369
- onClick: onClose,
13370
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
13371
- className: "absolute right-4 top-4 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
13372
- "aria-label": "Close",
13373
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
13374
- }
13375
- ),
13376
- /* @__PURE__ */ jsx("div", { className: "p-ds-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-ds-3", children: [
13377
- variant === "destructive" && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center shrink-0 w-10 h-10 rounded-[var(--ds-radius-full)] bg-[var(--ds-status-error-bg)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 20, className: "text-[var(--ds-status-error)]" }) }),
13378
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
13379
- /* @__PURE__ */ jsx(
13380
- "h2",
13381
- {
13382
- id: titleId,
13383
- className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] leading-none tracking-tight",
13384
- children: title
13385
- }
13386
- ),
13387
- description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
13388
- children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) })
13389
- ] })
13390
- ] }) }),
13391
- /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
13392
- footer && /* @__PURE__ */ jsx("div", { className: cn(
13393
- "flex justify-end gap-ds-2 p-ds-4",
13394
- "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
13395
- "bg-[var(--ds-bg-muted)]",
13396
- "rounded-b-[var(--ds-radius-xl)]"
13397
- ), children: footer })
13398
- ]
13399
- }
13400
- )
13401
- ]
13402
- }
13403
- )
13404
- ]
13405
- }
13406
- ),
13407
- container ?? document.body
13408
- );
13409
- }
13410
15035
  function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a command\u2026", loading = false, container }) {
13411
15036
  const [query, setQuery] = useState("");
13412
15037
  const [selectedIdx, setSelectedIdx] = useState(0);
@@ -13443,14 +15068,17 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
13443
15068
  const isTopLayer = useOverlayLayer(open);
13444
15069
  useFocusTrap(dialogRef, open, isTopLayer);
13445
15070
  const returnFocus = useReturnFocus(open, dialogRef);
15071
+ const onOpenChangeRef = useLatest(onOpenChange);
13446
15072
  useEffect(() => {
13447
15073
  if (!open) return;
13448
15074
  const handler = (e) => {
13449
- if (e.key === "Escape" && isTopLayer()) onOpenChange(false);
15075
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
15076
+ e.preventDefault();
15077
+ onOpenChangeRef.current(false);
13450
15078
  };
13451
15079
  document.addEventListener("keydown", handler);
13452
15080
  return () => document.removeEventListener("keydown", handler);
13453
- }, [open, onOpenChange, isTopLayer]);
15081
+ }, [open, onOpenChangeRef, isTopLayer]);
13454
15082
  const handleSelect = useCallback(
13455
15083
  (cmd) => {
13456
15084
  returnFocus();
@@ -13664,7 +15292,9 @@ function ContextMenu({ items, children, className, ref, container }) {
13664
15292
  };
13665
15293
  const keyHandler = (e) => {
13666
15294
  if (e.key === "Escape") {
13667
- if (isTopLayer()) setOpen(false);
15295
+ if (e.defaultPrevented || !isTopLayer()) return;
15296
+ e.preventDefault();
15297
+ setOpen(false);
13668
15298
  return;
13669
15299
  }
13670
15300
  const actionable = items.filter((it) => !it.separator && !it.disabled);
@@ -13801,10 +15431,11 @@ function NotificationCenter({
13801
15431
  const [internalFilter, setInternalFilter] = useState("all");
13802
15432
  const isControlled = controlledOpen !== void 0;
13803
15433
  const open = isControlled ? controlledOpen : internalOpen;
15434
+ const onOpenChangeRef = useLatest(onOpenChange);
13804
15435
  const setOpen = useCallback((v) => {
13805
- if (isControlled) onOpenChange?.(v);
15436
+ if (isControlled) onOpenChangeRef.current?.(v);
13806
15437
  else setInternalOpen(v);
13807
- }, [isControlled, onOpenChange]);
15438
+ }, [isControlled, onOpenChangeRef]);
13808
15439
  const triggerRef = useRef(null);
13809
15440
  const panelRef = useRef(null);
13810
15441
  const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "end", offset: 8, flip: true });
@@ -13821,10 +15452,10 @@ function NotificationCenter({
13821
15452
  useEffect(() => {
13822
15453
  if (!open) return;
13823
15454
  const onKey = (e) => {
13824
- if (e.key === "Escape" && isTopLayer()) {
13825
- returnFocus();
13826
- setOpen(false);
13827
- }
15455
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
15456
+ e.preventDefault();
15457
+ returnFocus();
15458
+ setOpen(false);
13828
15459
  };
13829
15460
  const onClick = (e) => {
13830
15461
  const target = e.target;
@@ -13946,6 +15577,8 @@ function NotificationCenter({
13946
15577
  ref: triggerRef,
13947
15578
  onClick: () => setOpen(!open),
13948
15579
  "aria-label": `Notifications${unreadCount ? ` (${unreadCount} unread)` : ""}`,
15580
+ "aria-haspopup": "dialog",
15581
+ "aria-expanded": open,
13949
15582
  className: cn(
13950
15583
  "relative flex items-center justify-center w-9 h-9",
13951
15584
  "rounded-[var(--ds-radius-md)]",
@@ -13964,6 +15597,8 @@ function NotificationCenter({
13964
15597
  "div",
13965
15598
  {
13966
15599
  ref: panelRef,
15600
+ role: "dialog",
15601
+ "aria-label": "Notifications",
13967
15602
  className: cn(
13968
15603
  "fixed z-[var(--ds-z-popover)] w-[360px] max-h-[480px] flex flex-col",
13969
15604
  "bg-[var(--ds-bg-surface-elevated)]",
@@ -14119,17 +15754,21 @@ function Tour({ steps, open, onClose, onComplete, container }) {
14119
15754
  const raf = requestAnimationFrame(() => nextRef.current?.focus());
14120
15755
  return () => cancelAnimationFrame(raf);
14121
15756
  }, [open, current]);
15757
+ const onCloseRef = useLatest(onClose);
15758
+ const handleNextRef = useLatest(handleNext);
15759
+ const handlePrevRef = useLatest(handlePrev);
14122
15760
  useEffect(() => {
14123
15761
  if (!open) return;
14124
15762
  const onKey = (e) => {
14125
- if (!isTopLayer()) return;
14126
- if (e.key === "Escape") onClose();
14127
- else if (e.key === "ArrowRight") handleNext();
14128
- else if (e.key === "ArrowLeft") handlePrev();
15763
+ if (e.defaultPrevented || !isTopLayer()) return;
15764
+ const act = e.key === "Escape" ? onCloseRef.current : e.key === "ArrowRight" ? handleNextRef.current : e.key === "ArrowLeft" ? handlePrevRef.current : null;
15765
+ if (!act) return;
15766
+ e.preventDefault();
15767
+ act();
14129
15768
  };
14130
15769
  document.addEventListener("keydown", onKey);
14131
15770
  return () => document.removeEventListener("keydown", onKey);
14132
- }, [open, onClose, handleNext, handlePrev, isTopLayer]);
15771
+ }, [open, isTopLayer, onCloseRef, handleNextRef, handlePrevRef]);
14133
15772
  if (!open || !step) return null;
14134
15773
  const padding = 8;
14135
15774
  const spotlightRadius = 8;
@@ -14522,7 +16161,7 @@ function usePagination(list, options = {}) {
14522
16161
  const grows = style !== "pagination";
14523
16162
  const start = grows ? 0 : (current - 1) * pageSize;
14524
16163
  const items = list.slice(start, current * pageSize);
14525
- const range = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
16164
+ const range2 = items.length === 0 ? [0, 0] : [start + 1, start + items.length];
14526
16165
  const hasMore = current < totalPages;
14527
16166
  const setPage = (next) => setPageState(Math.min(Math.max(1, Math.floor(next)), totalPages));
14528
16167
  const loadMore = () => setPageState((p) => Math.min(p + 1, totalPages));
@@ -14537,7 +16176,7 @@ function usePagination(list, options = {}) {
14537
16176
  pageSize,
14538
16177
  totalItems,
14539
16178
  totalPages,
14540
- range,
16179
+ range: range2,
14541
16180
  shown: items.length,
14542
16181
  hasMore,
14543
16182
  loadMore,
@@ -14593,7 +16232,7 @@ function useDestructiveAction(options) {
14593
16232
  }
14594
16233
  setOpen(true);
14595
16234
  };
14596
- const matches = !typedRequired || typed.trim().toLowerCase() === typedText.trim().toLowerCase();
16235
+ const matches2 = !typedRequired || typed.trim().toLowerCase() === typedText.trim().toLowerCase();
14597
16236
  const element = open ? /* @__PURE__ */ jsx(
14598
16237
  Dialog,
14599
16238
  {
@@ -14604,7 +16243,7 @@ function useDestructiveAction(options) {
14604
16243
  variant: "destructive",
14605
16244
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
14606
16245
  /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: close, disabled: busy, children: "Cancel" }),
14607
- /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void confirm(), disabled: !matches || busy, loading: busy, children: label })
16246
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void confirm(), disabled: !matches2 || busy, loading: busy, children: label })
14608
16247
  ] }),
14609
16248
  children: typedRequired && /* @__PURE__ */ jsx(Field, { label: /* @__PURE__ */ jsxs(Fragment, { children: [
14610
16249
  "Type ",
@@ -14754,6 +16393,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
14754
16393
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
14755
16394
  });
14756
16395
 
14757
- export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimePicker, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, useContentWidth, useDestructiveAction, useFormContext, usePagination, useSidebar, useToast, useToastOptional };
16396
+ export { Accordion, ActionPanel, ActionPanelGroup, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, Dropdown, DropdownMenu, EmptyState, EntityCell, Field, FileUpload, FormWrapper, Gantt, HoverCard, InlineFieldError, Input, Kbd, Label, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, NumberInput, PageContainer, PageHeader, Pagination, PinInput, Popover, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, Textarea, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Toggle, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useFormContext, usePagination, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
14758
16397
  //# sourceMappingURL=index.js.map
14759
16398
  //# sourceMappingURL=index.js.map