gridsmith-ui 0.15.0 → 0.16.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-ATONRIEB.js → chunk-2U2X6E2X.js} +36 -13
  6. package/dist/chunk-2U2X6E2X.js.map +1 -0
  7. package/dist/{chunk-X4CNZFWC.js → chunk-4LDYR7QP.js} +7 -7
  8. package/dist/chunk-4LDYR7QP.js.map +1 -0
  9. package/dist/{chunk-VFAKME7Q.js → chunk-5XOJZQIP.js} +106 -29
  10. package/dist/chunk-5XOJZQIP.js.map +1 -0
  11. package/dist/{chunk-XCSTU6IJ.js → chunk-PVTGMCVR.js} +3 -3
  12. package/dist/{chunk-XCSTU6IJ.js.map → chunk-PVTGMCVR.js.map} +1 -1
  13. package/dist/{chunk-LU3YRNES.js → chunk-XGSQOMEY.js} +4 -4
  14. package/dist/{chunk-LU3YRNES.js.map → chunk-XGSQOMEY.js.map} +1 -1
  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 +68 -5
  27. package/dist/index.js +884 -471
  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 +11 -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,25 @@
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 { useAppShell, ControlSizeContext, Button, useDirtyGuard, OverlayCloseCtx, DiscardConfirm, Spinner, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-2U2X6E2X.js';
2
+ export { AppShell, Button, ButtonSizeContext, ControlSizeContext, Drawer, Sheet, SkipLink, Spinner, useAppShell, useUnsavedChanges } from './chunk-2U2X6E2X.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-5XOJZQIP.js';
6
+ import { Link } from './chunk-XGSQOMEY.js';
7
+ export { CodeBlock, Divider, Link } from './chunk-XGSQOMEY.js';
8
+ import { Badge } from './chunk-PVTGMCVR.js';
9
+ export { Badge } from './chunk-PVTGMCVR.js';
10
10
  import { inferColumnKinds, CardValue } from './chunk-OENJCGTJ.js';
11
11
  import { SplitPaneCtx, useShellPreferences, TableCardCtx, PREFERENCE_HOUSE_DEFAULTS, preferenceActionKey } from './chunk-TKVTZWBN.js';
12
12
  export { PreferencesContext, preferenceActionKey, usePreferences } from './chunk-TKVTZWBN.js';
13
- import { useBreakpoint } from './chunk-CRQ6KCRD.js';
13
+ import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
14
14
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
15
15
  import { useTextOverflow, Tooltip, usePosition } from './chunk-EMCSARQO.js';
16
16
  export { Tooltip, useTextOverflow } from './chunk-EMCSARQO.js';
17
17
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
18
18
  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';
19
+ import { Icon } from './chunk-4LDYR7QP.js';
20
+ 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';
21
+ import { createContext, forwardRef, useState, useRef, useCallback, useLayoutEffect, useMemo, useContext, useId, memo, useEffect, Children, isValidElement, cloneElement, Fragment as Fragment$1, useSyncExternalStore } from 'react';
22
+ import { createPortal, flushSync } from 'react-dom';
23
23
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
24
24
 
25
25
  function isDivider(entry) {
@@ -44,10 +44,10 @@ function DropdownMenu({ trigger, items, align = "end", className, container }) {
44
44
  useEffect(() => {
45
45
  if (!open) return;
46
46
  const onKeyDown = (e) => {
47
- if (e.key === "Escape" && isTopLayer()) {
48
- returnFocus();
49
- setOpen(false);
50
- }
47
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
48
+ e.preventDefault();
49
+ returnFocus();
50
+ setOpen(false);
51
51
  };
52
52
  const onClick = (e) => {
53
53
  const target = e.target;
@@ -523,7 +523,7 @@ function Dropdown({
523
523
  const els = optionButtons();
524
524
  const current = els.findIndex((el) => el === document.activeElement);
525
525
  if (e.key === "Escape") {
526
- if (!isTopLayer()) return;
526
+ if (e.defaultPrevented || !isTopLayer()) return;
527
527
  e.preventDefault();
528
528
  returnFocus();
529
529
  setOpen(false);
@@ -585,14 +585,14 @@ function Dropdown({
585
585
  };
586
586
  const chipsRef = useRef(null);
587
587
  const pendingChipFocus = useRef(null);
588
- const chipCount = selectedValues.length;
589
588
  useLayoutEffect(() => {
590
- const index = pendingChipFocus.current;
591
- if (index === null) return;
589
+ const pending = pendingChipFocus.current;
590
+ if (pending === null) return;
592
591
  pendingChipFocus.current = null;
592
+ if (selectedValues.includes(pending.value)) return;
593
593
  const chips = Array.from(chipsRef.current?.querySelectorAll("[data-chip-remove]") ?? []);
594
- focusAfterRemoval(chips, index, triggerRef.current);
595
- }, [chipCount]);
594
+ focusAfterRemoval(chips, pending.index, triggerRef.current);
595
+ });
596
596
  const displayLabel = multiple ? selectedValues.length > 0 ? `${selectedValues.length} selected` : placeholder : options.find((o) => o.id === selectedValues[0])?.label ?? placeholder;
597
597
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines: 1, content: displayLabel });
598
598
  return /* @__PURE__ */ jsxs("div", { ref, className: cn("relative", className), children: [
@@ -705,7 +705,7 @@ function Dropdown({
705
705
  {
706
706
  "data-chip-remove": "",
707
707
  onClick: () => {
708
- pendingChipFocus.current = i;
708
+ pendingChipFocus.current = { index: i, value: v };
709
709
  handleSelect(v);
710
710
  },
711
711
  className: "hover:text-[var(--ds-text-primary)] cursor-pointer",
@@ -845,9 +845,14 @@ function Combobox({ value, onChange, options, placeholder = "Search\u2026", disa
845
845
  } else if (e.key === "Enter" && open && filtered[highlightIdx]) {
846
846
  e.preventDefault();
847
847
  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") {
848
+ } else if (e.key === "Tab" && open) {
849
+ if (moved && filtered[highlightIdx]) onChange(filtered[highlightIdx].value);
850
+ flushSync(() => {
851
+ setOpen(false);
852
+ setQuery("");
853
+ });
854
+ } else if (e.key === "Escape" && open) {
855
+ e.preventDefault();
851
856
  setOpen(false);
852
857
  setQuery("");
853
858
  }
@@ -1362,8 +1367,9 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1362
1367
  const raf = requestAnimationFrame(() => focusActiveDay.current());
1363
1368
  return () => cancelAnimationFrame(raf);
1364
1369
  }, [open, focusActiveDay]);
1365
- const closeAndRefocus = () => {
1370
+ const choose = (d) => {
1366
1371
  returnFocus();
1372
+ onChange(d);
1367
1373
  setOpen(false);
1368
1374
  };
1369
1375
  const dayFirst = useMemo(() => localeDayFirst(), []);
@@ -1388,10 +1394,10 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1388
1394
  setOpen(false);
1389
1395
  };
1390
1396
  const keyHandler = (e) => {
1391
- if (e.key === "Escape" && isTopLayer()) {
1392
- returnFocus();
1393
- setOpen(false);
1394
- }
1397
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
1398
+ e.preventDefault();
1399
+ returnFocus();
1400
+ setOpen(false);
1395
1401
  };
1396
1402
  document.addEventListener("mousedown", handler);
1397
1403
  document.addEventListener("keydown", keyHandler);
@@ -1485,8 +1491,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1485
1491
  const selectDay = (day) => {
1486
1492
  const d = new Date(year, month, day);
1487
1493
  if (isDisabledDay(d)) return;
1488
- onChange(d);
1489
- closeAndRefocus();
1494
+ choose(d);
1490
1495
  };
1491
1496
  const rangeA = range?.start ?? null;
1492
1497
  const rangeB = range?.end ?? null;
@@ -1676,8 +1681,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1676
1681
  onClick: () => {
1677
1682
  const now = /* @__PURE__ */ new Date();
1678
1683
  setViewDate(now);
1679
- onChange(now);
1680
- closeAndRefocus();
1684
+ choose(now);
1681
1685
  },
1682
1686
  children: "Today"
1683
1687
  }
@@ -1687,10 +1691,7 @@ function DatePicker({ value, onChange, placeholder = "Pick a date", disabled, cl
1687
1691
  {
1688
1692
  variant: "ghost",
1689
1693
  size: "sm",
1690
- onClick: () => {
1691
- onChange(null);
1692
- closeAndRefocus();
1693
- },
1694
+ onClick: () => choose(null),
1694
1695
  children: "Clear"
1695
1696
  }
1696
1697
  )
@@ -1962,6 +1963,10 @@ function DateRangePicker({
1962
1963
  keyOf: dayKey,
1963
1964
  move: (from, e) => moveDay(from, e.key, e.shiftKey, weekStartsOn)
1964
1965
  });
1966
+ const pageMonths = (n) => {
1967
+ setViewDate(addMonths(viewDate, n));
1968
+ setFocusDate(addMonthsClamped(focusDate, n));
1969
+ };
1965
1970
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close: () => setOpen(false), tabExits: "edges" });
1966
1971
  const focusActiveDay = useLatest(grid.focusActive);
1967
1972
  useEffect(() => {
@@ -1977,10 +1982,10 @@ function DateRangePicker({
1977
1982
  setOpen(false);
1978
1983
  };
1979
1984
  const onKey = (e) => {
1980
- if (e.key === "Escape" && isTopLayer()) {
1981
- returnFocus();
1982
- setOpen(false);
1983
- }
1985
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
1986
+ e.preventDefault();
1987
+ returnFocus();
1988
+ setOpen(false);
1984
1989
  };
1985
1990
  document.addEventListener("mousedown", onMouse);
1986
1991
  document.addEventListener("keydown", onKey);
@@ -2071,7 +2076,7 @@ function DateRangePicker({
2071
2076
  }, children: p.label }, p.label)) }),
2072
2077
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
2073
2078
  /* @__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" }) }),
2079
+ /* @__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
2080
  /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-[var(--ds-text-primary)]", children: [
2076
2081
  MONTHS2[months[0].getMonth()],
2077
2082
  " ",
@@ -2081,7 +2086,7 @@ function DateRangePicker({
2081
2086
  " ",
2082
2087
  months[1].getFullYear()
2083
2088
  ] }),
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 }) })
2089
+ /* @__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
2090
  ] }),
2086
2091
  /* @__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
2092
  /* @__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 +2140,96 @@ function DateRangePicker({
2135
2140
  )
2136
2141
  ] });
2137
2142
  }
2143
+
2144
+ // ../../showcase/src/lib/file-accept.ts
2145
+ var MIME_NAMES = {
2146
+ "text/plain": "TXT",
2147
+ "audio/mpeg": "MP3",
2148
+ "video/quicktime": "MOV",
2149
+ "application/msword": "DOC",
2150
+ "application/vnd.ms-excel": "XLS",
2151
+ "application/vnd.ms-powerpoint": "PPT",
2152
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "DOCX",
2153
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "XLSX",
2154
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "PPTX"
2155
+ };
2156
+ var EXT_NAMES = { jpeg: "JPG", tif: "TIFF", htm: "HTML", yml: "YAML" };
2157
+ var FAMILIES = {
2158
+ image: { one: "an image", many: "images" },
2159
+ video: { one: "a video", many: "videos" },
2160
+ audio: { one: "an audio file", many: "audio files" },
2161
+ text: { one: "a text file", many: "text files" }
2162
+ };
2163
+ var READ_AS_WORD = /* @__PURE__ */ new Set(["FLAC", "HEIC", "HEIF", "LOG", "MIDI", "MOV", "RAR", "RAW"]);
2164
+ var extName = (ext) => EXT_NAMES[ext] ?? ext.toUpperCase();
2165
+ function mimeName(mime) {
2166
+ if (MIME_NAMES[mime]) return MIME_NAMES[mime];
2167
+ const subtype = mime.slice(mime.indexOf("/") + 1).replace(/\+.*$/, "").replace(/^(?:x-|vnd\.)/, "");
2168
+ return extName(subtype.slice(subtype.lastIndexOf(".") + 1));
2169
+ }
2170
+ function parseAccept(accept) {
2171
+ return (accept ?? "").split(",").map((t) => t.trim().toLowerCase()).filter(Boolean).map((t) => {
2172
+ if (t === "*" || t === "*/*") return { kind: "any" };
2173
+ if (t.startsWith(".")) return { kind: "format", name: extName(t.slice(1)), suffix: t };
2174
+ if (t.endsWith("/*")) return { kind: "family", family: t.slice(0, -2) };
2175
+ return { kind: "format", name: mimeName(t), mime: t };
2176
+ });
2177
+ }
2178
+ function fileFormat(file) {
2179
+ const dot = file.name.lastIndexOf(".");
2180
+ return dot > 0 ? extName(file.name.slice(dot + 1).toLowerCase()) : null;
2181
+ }
2182
+ function matches(file, tokens) {
2183
+ if (tokens.length === 0 || tokens.some((t) => t.kind === "any")) return true;
2184
+ const name = file.name.toLowerCase();
2185
+ const format = fileFormat(file);
2186
+ const type = file.type.toLowerCase();
2187
+ return tokens.some((t) => {
2188
+ if (t.kind === "family") return type === "" || type.startsWith(`${t.family}/`);
2189
+ if (t.kind !== "format") return false;
2190
+ return t.name === format || t.suffix != null && name.endsWith(t.suffix) || t.mime != null && t.mime === type;
2191
+ });
2192
+ }
2193
+ var joinOr = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
2194
+ var article = (name) => {
2195
+ const asWord = name.replace(/[^A-Z]/g, "").length >= 5 || READ_AS_WORD.has(name);
2196
+ return (asWord ? /^[AEIOU]/ : /^[AEFHILMNORSX]/).test(name) ? "an" : "a";
2197
+ };
2198
+ function typeWords(accept) {
2199
+ const tokens = parseAccept(accept);
2200
+ if (tokens.length === 0 || tokens.some((t) => t.kind === "any" || t.kind === "family" && !FAMILIES[t.family])) return null;
2201
+ const families = [...new Set(tokens.flatMap((t) => t.kind === "family" ? [t.family] : []))].map((f) => FAMILIES[f]);
2202
+ const formats = [...new Set(tokens.flatMap((t) => t.kind === "format" ? [t.name] : []))];
2203
+ const many = `${joinOr([...families.map((f) => f.many), ...formats])}${formats.length ? " files" : ""}`;
2204
+ const one = families.length === 0 ? `${article(formats[0])} ${joinOr(formats)}` : joinOr([...families.map((f) => f.one), ...formats.map((f) => `${article(f)} ${f}`)]);
2205
+ return { one, many };
2206
+ }
2207
+ function sizeWords(bytes) {
2208
+ const units = [["GB", 1024 ** 3], ["MB", 1024 ** 2], ["KB", 1024]];
2209
+ for (const [unit, size] of units) if (bytes >= size) return `${Math.round(bytes / size * 10) / 10} ${unit}`;
2210
+ return `${bytes} bytes`;
2211
+ }
2212
+ var capitalise = (s) => s.charAt(0).toUpperCase() + s.slice(1);
2213
+ function fileConstraints(accept, maxSize, multiple) {
2214
+ const types = typeWords(accept)?.many;
2215
+ const size = maxSize != null ? `up to ${sizeWords(maxSize)}${multiple ? " each" : ""}` : null;
2216
+ const line = [types, size].filter(Boolean).join(" ");
2217
+ return line ? capitalise(line) : null;
2218
+ }
2219
+ function checkFiles(files, { accept, maxSize, multiple }) {
2220
+ if (!multiple && files.length > 1) return { accepted: [], message: "You can only select one file" };
2221
+ const tokens = parseAccept(accept);
2222
+ const types = typeWords(accept);
2223
+ const accepted = [];
2224
+ const reasons = [];
2225
+ for (const file of files) {
2226
+ const subject = multiple ? file.name : "The selected file";
2227
+ if (!matches(file, tokens)) reasons.push(types ? `${subject} must be ${types.one}` : `${subject} is not a file type that can be added here`);
2228
+ else if (maxSize != null && file.size > maxSize) reasons.push(`${subject} must be smaller than ${sizeWords(maxSize)}`);
2229
+ else accepted.push(file);
2230
+ }
2231
+ return { accepted, message: reasons.length ? reasons.join("; ") : null };
2232
+ }
2138
2233
  var variantFills = {
2139
2234
  default: "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]",
2140
2235
  success: "bg-[var(--ds-status-success)]",
@@ -2182,6 +2277,66 @@ function Progress({ value, max = 100, variant = "default", size = "md", indeterm
2182
2277
  ] })
2183
2278
  ] });
2184
2279
  }
2280
+ function InlineFieldError({ message, id, className }) {
2281
+ return /* @__PURE__ */ jsxs(
2282
+ "p",
2283
+ {
2284
+ id,
2285
+ role: "alert",
2286
+ className: cn(
2287
+ "flex items-center gap-ds-1.5 text-xs text-[var(--ds-status-error)]",
2288
+ "mt-ds-1.5",
2289
+ className
2290
+ ),
2291
+ children: [
2292
+ /* @__PURE__ */ jsx(Icon, { name: "error", size: 14, className: "shrink-0" }),
2293
+ /* @__PURE__ */ jsx("span", { children: message })
2294
+ ]
2295
+ }
2296
+ );
2297
+ }
2298
+ var FieldContext = createContext(null);
2299
+ function useField() {
2300
+ return useContext(FieldContext);
2301
+ }
2302
+ function Field({ label, children, hint, error, required, labelAction, id: idProp, className }) {
2303
+ const generated = useId();
2304
+ const id = idProp ?? `field-${generated}`;
2305
+ const hintId = `${id}-hint`;
2306
+ const errorId = `${id}-error`;
2307
+ const hasError = typeof error === "string" && error.length > 0;
2308
+ const describedBy = [hint ? hintId : null, hasError ? errorId : null].filter(Boolean).join(" ") || void 0;
2309
+ const labelId = `${id}-label`;
2310
+ const fieldContext = useMemo(() => ({ controlId: id, labelId, describedBy }), [id, labelId, describedBy]);
2311
+ const controlProps = {
2312
+ id,
2313
+ "aria-describedby": describedBy,
2314
+ ...hasError ? { "aria-invalid": true, error: true } : {},
2315
+ ...required ? { "aria-required": true, required: true } : {}
2316
+ };
2317
+ let control;
2318
+ if (typeof children === "function") {
2319
+ control = children(controlProps);
2320
+ } else {
2321
+ const child = Children.only(children);
2322
+ if (!isValidElement(child)) throw new Error("Field expects a single form control as its child");
2323
+ const isNative = typeof child.type === "string";
2324
+ const { error: errorFlag, ...ariaOnly } = controlProps;
2325
+ control = cloneElement(child, isNative ? ariaOnly : { ...ariaOnly, ...errorFlag ? { error: true } : {} });
2326
+ }
2327
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-ds-1.5", className), children: [
2328
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-ds-2", children: [
2329
+ /* @__PURE__ */ jsxs(Label, { id: labelId, htmlFor: id, children: [
2330
+ label,
2331
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "ml-ds-0.5 text-[var(--ds-status-error)]", children: "*" })
2332
+ ] }),
2333
+ labelAction
2334
+ ] }),
2335
+ /* @__PURE__ */ jsx(FieldContext.Provider, { value: fieldContext, children: control }),
2336
+ hint && /* @__PURE__ */ jsx("p", { id: hintId, className: "text-xs text-[var(--ds-text-muted)]", children: hint }),
2337
+ hasError && /* @__PURE__ */ jsx(InlineFieldError, { id: errorId, message: error, className: "mt-0" })
2338
+ ] });
2339
+ }
2185
2340
  function TruncFileName({ children, className }) {
2186
2341
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
2187
2342
  const el = /* @__PURE__ */ jsx("p", { ref, className: cn("truncate", className), children });
@@ -2193,37 +2348,57 @@ function formatSize(bytes) {
2193
2348
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2194
2349
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2195
2350
  }
2196
- function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label }) {
2351
+ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, error = false, label, id }) {
2197
2352
  const [files, setFiles] = useState([]);
2198
2353
  const [dragOver, setDragOver] = useState(false);
2199
- const inputId = useId();
2354
+ const [rejected, setRejected] = useState(null);
2200
2355
  const inputRef = useRef(null);
2356
+ const nextEntryId = useRef(0);
2357
+ const timers = useRef(/* @__PURE__ */ new Map());
2358
+ useEffect(() => {
2359
+ const running = timers.current;
2360
+ return () => running.forEach(clearInterval);
2361
+ }, []);
2362
+ const field = useField();
2363
+ const ownId = useId();
2364
+ const inputId = id ?? `${ownId}-input`;
2365
+ const constraintsId = `${ownId}-constraints`;
2366
+ const rejectedId = `${ownId}-rejected`;
2367
+ const showLabel = label != null && !field;
2368
+ const constraints = fileConstraints(accept, maxSize, multiple);
2369
+ const invalid = error || rejected != null;
2370
+ const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null].filter(Boolean).join(" ") || void 0;
2371
+ const stopTimer = (id2) => {
2372
+ clearInterval(timers.current.get(id2));
2373
+ timers.current.delete(id2);
2374
+ };
2201
2375
  const addFiles = useCallback(
2202
2376
  (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) => {
2377
+ const { accepted, message } = checkFiles(Array.from(fileList), { accept, maxSize, multiple });
2378
+ setRejected(message);
2379
+ if (accepted.length === 0) return;
2380
+ const entries = accepted.map((file) => ({ id: nextEntryId.current++, file, progress: 0, status: "uploading" }));
2381
+ if (!multiple) {
2382
+ timers.current.forEach(clearInterval);
2383
+ timers.current.clear();
2384
+ }
2385
+ setFiles((prev) => multiple ? [...prev, ...entries] : entries);
2386
+ onFiles(accepted);
2387
+ for (const { id: id2 } of entries) {
2209
2388
  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
- );
2389
+ const timer = setInterval(() => {
2390
+ progress = Math.min(100, progress + Math.random() * 30 + 10);
2391
+ const done = progress >= 100;
2392
+ if (done) {
2393
+ clearInterval(timer);
2394
+ timers.current.delete(id2);
2222
2395
  }
2396
+ setFiles((prev) => prev.map((f) => f.id === id2 ? { ...f, progress, status: done ? "complete" : "uploading" } : f));
2223
2397
  }, 300);
2224
- });
2398
+ timers.current.set(id2, timer);
2399
+ }
2225
2400
  },
2226
- [maxSize, onFiles]
2401
+ [accept, maxSize, multiple, onFiles]
2227
2402
  );
2228
2403
  const handleDrop = useCallback(
2229
2404
  (e) => {
@@ -2233,63 +2408,66 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2233
2408
  },
2234
2409
  [addFiles]
2235
2410
  );
2236
- const removeFile = (index) => {
2237
- setFiles((prev) => prev.filter((_, i) => i !== index));
2411
+ const removeFile = (id2) => {
2412
+ stopTimer(id2);
2413
+ setFiles((prev) => prev.filter((f) => f.id !== id2));
2238
2414
  };
2239
2415
  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 = "";
2416
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-ds-1.5", children: [
2417
+ showLabel && /* @__PURE__ */ jsx(Label, { htmlFor: inputId, children: label }),
2418
+ /* @__PURE__ */ jsxs(
2419
+ "div",
2420
+ {
2421
+ onDragOver: (e) => {
2422
+ e.preventDefault();
2423
+ setDragOver(true);
2424
+ },
2425
+ onDragLeave: () => setDragOver(false),
2426
+ onDrop: handleDrop,
2427
+ onClick: () => inputRef.current?.click(),
2428
+ className: cn(
2429
+ "flex flex-col items-center justify-center gap-ds-2 cursor-pointer group",
2430
+ "border-2 border-dashed rounded-[var(--ds-radius-md)]",
2431
+ "transition-colors",
2432
+ // The real file input is visually hidden inside; Tab lands on it and
2433
+ // Enter/Space open the picker natively, so the zone shows its focus.
2434
+ invalid ? ERROR_HAS_FOCUS_RING : HAS_FOCUS_RING,
2435
+ 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)]"
2436
+ ),
2437
+ style: { padding: "var(--ds-space-8) var(--ds-space-4)" },
2438
+ children: [
2439
+ /* @__PURE__ */ jsx("div", { className: cn(
2440
+ "flex items-center justify-center w-10 h-10 rounded-[var(--ds-radius-full)] transition-colors",
2441
+ "bg-[var(--ds-bg-muted)] group-hover:bg-[var(--ds-accent-subtle)]"
2442
+ ), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 20, className: "text-[var(--ds-text-muted)] group-hover:text-[var(--ds-accent-primary)] transition-colors" }) }),
2443
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2444
+ /* @__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" }),
2445
+ constraints && /* @__PURE__ */ jsx("p", { id: constraintsId, className: "text-xs text-[var(--ds-text-muted)] mt-ds-1", children: constraints })
2446
+ ] }),
2447
+ /* @__PURE__ */ jsx(
2448
+ "input",
2449
+ {
2450
+ ref: inputRef,
2451
+ id: inputId,
2452
+ type: "file",
2453
+ accept,
2454
+ multiple,
2455
+ className: "sr-only",
2456
+ "aria-labelledby": field ? field.labelId : void 0,
2457
+ "aria-describedby": describedBy,
2458
+ "aria-invalid": invalid || void 0,
2459
+ onChange: (e) => {
2460
+ if (e.target.files?.length) addFiles(e.target.files);
2461
+ e.target.value = "";
2462
+ }
2286
2463
  }
2287
- }
2288
- )
2289
- ]
2290
- }
2291
- ),
2292
- files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-ds-2", children: files.map((entry, i) => /* @__PURE__ */ jsxs(
2464
+ )
2465
+ ]
2466
+ }
2467
+ ),
2468
+ rejected && /* @__PURE__ */ jsx(InlineFieldError, { id: rejectedId, message: rejected, className: "mt-0" })
2469
+ ] }),
2470
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-ds-2", children: files.map((entry) => /* @__PURE__ */ jsxs(
2293
2471
  "li",
2294
2472
  {
2295
2473
  className: cn(
@@ -2306,10 +2484,10 @@ function FileUpload({ onFiles, accept, multiple = true, maxSize, className, erro
2306
2484
  entry.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: entry.progress, size: "sm", className: "mt-ds-1" })
2307
2485
  ] }),
2308
2486
  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 }) })
2487
+ /* @__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
2488
  ]
2311
2489
  },
2312
- `${entry.file.name}-${i}`
2490
+ entry.id
2313
2491
  )) })
2314
2492
  ] });
2315
2493
  }
@@ -2324,9 +2502,17 @@ function SliderRange({
2324
2502
  className,
2325
2503
  ref,
2326
2504
  error = false,
2327
- label = "Value",
2505
+ label,
2506
+ "aria-label": ariaLabel,
2507
+ "aria-describedby": describedByProp,
2328
2508
  trackStyle
2329
2509
  }) {
2510
+ const field = useField();
2511
+ const ownId = useId();
2512
+ const showLabel = label != null && !field;
2513
+ const nameId = field ? field.labelId : showLabel ? `${ownId}-label` : void 0;
2514
+ const describedBy = [...new Set([describedByProp, field?.describedBy].join(" ").split(" ").filter(Boolean))].join(" ") || void 0;
2515
+ const thumbName = (part) => nameId ? { "aria-labelledby": part ? `${nameId} ${ownId}-${part}` : nameId } : { "aria-label": ariaLabel ? part ? `${ariaLabel} ${part}` : ariaLabel : void 0 };
2330
2516
  const isRange = Array.isArray(value);
2331
2517
  const low = isRange ? value[0] : value;
2332
2518
  const high = isRange ? value[1] : value;
@@ -2382,7 +2568,14 @@ function SliderRange({
2382
2568
  [disabled, min, max, handleChange]
2383
2569
  );
2384
2570
  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 }) }),
2571
+ (showLabel || showValue) && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-ds-2", showLabel ? "justify-between" : "justify-end"), children: [
2572
+ showLabel && /* @__PURE__ */ jsx("span", { id: `${ownId}-label`, className: "text-sm font-medium leading-none text-[var(--ds-text-secondary)]", children: label }),
2573
+ showValue && /* @__PURE__ */ jsx("span", { className: "text-xs font-mono tabular-nums text-[var(--ds-text-muted)]", children: isRange ? `${low} \u2013 ${high}` : low })
2574
+ ] }),
2575
+ isRange && nameId && /* @__PURE__ */ jsxs(Fragment, { children: [
2576
+ /* @__PURE__ */ jsx("span", { id: `${ownId}-minimum`, hidden: true, children: "minimum" }),
2577
+ /* @__PURE__ */ jsx("span", { id: `${ownId}-maximum`, hidden: true, children: "maximum" })
2578
+ ] }),
2386
2579
  /* @__PURE__ */ jsxs(
2387
2580
  "div",
2388
2581
  {
@@ -2405,7 +2598,8 @@ function SliderRange({
2405
2598
  {
2406
2599
  role: "slider",
2407
2600
  tabIndex: disabled ? -1 : 0,
2408
- "aria-label": `${label} minimum`,
2601
+ ...thumbName("minimum"),
2602
+ "aria-describedby": describedBy,
2409
2603
  "aria-valuemin": min,
2410
2604
  "aria-valuemax": high,
2411
2605
  "aria-valuenow": low,
@@ -2429,7 +2623,8 @@ function SliderRange({
2429
2623
  {
2430
2624
  role: "slider",
2431
2625
  tabIndex: disabled ? -1 : 0,
2432
- "aria-label": isRange ? `${label} maximum` : label,
2626
+ ...thumbName(isRange ? "maximum" : void 0),
2627
+ "aria-describedby": describedBy,
2433
2628
  "aria-valuemin": isRange ? low : min,
2434
2629
  "aria-valuemax": max,
2435
2630
  "aria-valuenow": high,
@@ -2605,10 +2800,10 @@ function TimePicker({
2605
2800
  setOpen(false);
2606
2801
  };
2607
2802
  const keyHandler = (e) => {
2608
- if (e.key === "Escape" && isTopLayer()) {
2609
- returnFocus();
2610
- setOpen(false);
2611
- }
2803
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
2804
+ e.preventDefault();
2805
+ returnFocus();
2806
+ setOpen(false);
2612
2807
  };
2613
2808
  document.addEventListener("mousedown", handler);
2614
2809
  document.addEventListener("keydown", keyHandler);
@@ -3246,14 +3441,14 @@ function TagInput({
3246
3441
  );
3247
3442
  const containerRef = useRef(null);
3248
3443
  const pendingFocus = useRef(null);
3249
- const tagCount = value.length;
3250
3444
  useLayoutEffect(() => {
3251
- const index = pendingFocus.current;
3252
- if (index === null) return;
3445
+ const pending = pendingFocus.current;
3446
+ if (pending === null) return;
3253
3447
  pendingFocus.current = null;
3448
+ if (value.includes(pending.tag)) return;
3254
3449
  const buttons = Array.from(containerRef.current?.querySelectorAll("[data-tag-remove]") ?? []);
3255
- focusAfterRemoval(buttons, index, inputRef.current);
3256
- }, [tagCount]);
3450
+ focusAfterRemoval(buttons, pending.index, inputRef.current);
3451
+ });
3257
3452
  const handleKeyDown = useCallback(
3258
3453
  (e) => {
3259
3454
  if ((e.key === "Enter" || e.key === ",") && input.trim()) {
@@ -3308,7 +3503,7 @@ function TagInput({
3308
3503
  Tag,
3309
3504
  {
3310
3505
  onRemove: disabled ? void 0 : () => {
3311
- pendingFocus.current = i;
3506
+ pendingFocus.current = { index: i, tag };
3312
3507
  removeTag(tag);
3313
3508
  },
3314
3509
  disabled,
@@ -3837,60 +4032,6 @@ function ColorPicker({
3837
4032
  }
3838
4033
  );
3839
4034
  }
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
4035
  var variantStyles2 = {
3895
4036
  info: "border-[var(--ds-accent-primary)] bg-[var(--ds-accent-subtle)]",
3896
4037
  success: "border-[var(--ds-status-success)] bg-[var(--ds-status-success-bg)]",
@@ -4566,7 +4707,6 @@ function MentionInput({
4566
4707
  const value = controlledValue ?? internalValue;
4567
4708
  const [query, setQuery] = useState("");
4568
4709
  const [showPopup, setShowPopup] = useState(false);
4569
- useOverlayLayer(showPopup);
4570
4710
  const [activeIndex, setActiveIndex] = useState(0);
4571
4711
  const [triggerStart, setTriggerStart] = useState(-1);
4572
4712
  const [popupPos, setPopupPos] = useState({ top: 0, left: 0 });
@@ -4576,6 +4716,7 @@ function MentionInput({
4576
4716
  const filtered = suggestions.filter(
4577
4717
  (s) => s.label.toLowerCase().includes(query.toLowerCase()) || s.description?.toLowerCase().includes(query.toLowerCase())
4578
4718
  );
4719
+ useOverlayLayer(showPopup && filtered.length > 0);
4579
4720
  const updateValue = useCallback(
4580
4721
  (v) => {
4581
4722
  if (controlledValue === void 0) setInternalValue(v);
@@ -4654,6 +4795,7 @@ function MentionInput({
4654
4795
  e.preventDefault();
4655
4796
  insertMention(filtered[activeIndex]);
4656
4797
  } else if (e.key === "Escape") {
4798
+ e.preventDefault();
4657
4799
  setShowPopup(false);
4658
4800
  }
4659
4801
  };
@@ -4757,6 +4899,8 @@ function MentionInput({
4757
4899
  ] });
4758
4900
  }
4759
4901
  var CardCtx = createContext(false);
4902
+ var CardActionCtx = createContext(null);
4903
+ var CARD_ACTION_ATTR = "data-card-action";
4760
4904
  var CardSurfaceCtx = createContext(false);
4761
4905
  var variantStyles3 = {
4762
4906
  default: `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
@@ -4876,11 +5020,33 @@ var Card = forwardRef(function Card2({
4876
5020
  const isSelectable = selectable || !!selectType;
4877
5021
  const isToggle = selectType === "toggle";
4878
5022
  const hasSelectType = !!selectType;
5023
+ const isAction = !isSelectable && variant === "interactive" && !!onClick;
5024
+ const rootRef = useRef(null);
5025
+ const setRoot = useCallback((node) => {
5026
+ rootRef.current = node;
5027
+ if (typeof ref === "function") ref(node);
5028
+ else if (ref) ref.current = node;
5029
+ }, [ref]);
5030
+ const [inner, setInner] = useState({ controls: false, titleId: null });
5031
+ useLayoutEffect(() => {
5032
+ const root = rootRef.current;
5033
+ if (!root || !isAction && !checkable) return;
5034
+ const controls = Array.from(root.querySelectorAll(FOCUSABLE)).some((el) => !el.hasAttribute(CARD_ACTION_ATTR));
5035
+ const titleId = root.querySelector("[data-card-title]")?.id || null;
5036
+ setInner((cur) => cur.controls === controls && cur.titleId === titleId ? cur : { controls, titleId });
5037
+ }, [isAction, checkable, loading, children]);
5038
+ const holdsControls = isAction && inner.controls;
5039
+ const action = useMemo(() => holdsControls ? { activate: () => rootRef.current?.click(), disabled } : null, [holdsControls, disabled]);
4879
5040
  const handleClick = isSelectable && !disabled ? (e) => {
4880
5041
  onSelectedChange?.(!selected);
4881
5042
  onClick?.(e);
4882
- } : onClick;
4883
- const isAction = !isSelectable && variant === "interactive" && !!onClick;
5043
+ } : isAction && disabled ? void 0 : onClick;
5044
+ const handleCardClick = (e) => {
5045
+ if (disabled || !(e.target instanceof Element) || !e.currentTarget.contains(e.target)) return;
5046
+ const control = e.target.closest(FOCUSABLE);
5047
+ if (control && control !== e.currentTarget && e.currentTarget.contains(control)) return;
5048
+ onClick?.(e);
5049
+ };
4884
5050
  const handleKeyDown = isSelectable && !disabled ? (e) => {
4885
5051
  if (e.key === "Enter" || e.key === " ") {
4886
5052
  e.preventDefault();
@@ -4903,7 +5069,8 @@ var Card = forwardRef(function Card2({
4903
5069
  e.stopPropagation();
4904
5070
  onSelectedChange?.(!selected);
4905
5071
  };
4906
- const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isSelectable || isAction ? "button" : void 0;
5072
+ const isButton = isSelectable || isAction && !holdsControls;
5073
+ const role = selectType === "radio" ? "radio" : selectType === "checkbox" ? "checkbox" : selectType === "toggle" ? "switch" : isButton ? "button" : void 0;
4907
5074
  const ariaAttr = selectType === "radio" || selectType === "checkbox" || selectType === "toggle" ? { "aria-checked": !!selected } : isSelectable ? { "aria-pressed": !!selected } : {};
4908
5075
  const showRing = isSelectable && !isToggle && !hasSelectType && selected;
4909
5076
  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 +5078,11 @@ var Card = forwardRef(function Card2({
4911
5078
  return /* @__PURE__ */ jsxs(
4912
5079
  "div",
4913
5080
  {
4914
- ref,
5081
+ ref: setRoot,
4915
5082
  "data-card": "",
4916
5083
  role,
4917
- tabIndex: isSelectable || isAction && !disabled ? 0 : void 0,
4918
- "aria-disabled": (isSelectable || isAction) && disabled ? true : void 0,
5084
+ tabIndex: isSelectable || isButton && !disabled ? 0 : void 0,
5085
+ "aria-disabled": isButton && disabled ? true : void 0,
4919
5086
  ...name && selectType === "radio" ? { "data-radio-group": name } : {},
4920
5087
  style: variant === "interactive" ? { ...INTERACTIVE_SCALE_CAP, ...style } : style,
4921
5088
  className: cn(
@@ -4923,15 +5090,15 @@ var Card = forwardRef(function Card2({
4923
5090
  variantStyles3[variant],
4924
5091
  isSelectable && !disabled && "cursor-pointer select-none transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
4925
5092
  isSelectable && !disabled && !selected && "hover:border-[var(--ds-accent-primary)]",
4926
- (isSelectable || isAction) && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
5093
+ isButton && "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
4927
5094
  (showRing || hasSelectType && selected) && ACTIVE_RING,
4928
5095
  hasSelectType && selected && "bg-[var(--ds-accent-subtle)]",
4929
5096
  disabled && "opacity-disabled cursor-not-allowed",
4930
5097
  className
4931
5098
  ),
4932
- onClick: handleClick,
4933
- onKeyDown: handleKeyDown,
4934
- onKeyUp: handleKeyUp,
5099
+ onClick: holdsControls ? handleCardClick : handleClick,
5100
+ onKeyDown: holdsControls ? void 0 : handleKeyDown,
5101
+ onKeyUp: holdsControls ? void 0 : handleKeyUp,
4935
5102
  ...ariaAttr,
4936
5103
  ...props,
4937
5104
  children: [
@@ -4944,6 +5111,7 @@ var Card = forwardRef(function Card2({
4944
5111
  onClick: handleCheckableClick,
4945
5112
  role: "checkbox",
4946
5113
  "aria-checked": !!selected,
5114
+ ...inner.titleId ? { "aria-labelledby": inner.titleId } : { "aria-label": "Select" },
4947
5115
  tabIndex: 0,
4948
5116
  onKeyDown: (e) => {
4949
5117
  if (e.key === "Enter" || e.key === " ") {
@@ -4955,7 +5123,7 @@ var Card = forwardRef(function Card2({
4955
5123
  children: /* @__PURE__ */ jsx(CheckboxIndicator, { checked: !!selected })
4956
5124
  }
4957
5125
  ),
4958
- /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
5126
+ /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardCtx, { value: checkable, children: loading ? (
4959
5127
  // Round 15 (the owner's 52.1): a placeholder stands where the content will stand — inside the card's
4960
5128
  // content inset (CardContent's padding), a title-height line then two body lines — never flush against the border.
4961
5129
  /* @__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 +5131,7 @@ var Card = forwardRef(function Card2({
4963
5131
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-full" }),
4964
5132
  /* @__PURE__ */ jsx(Skeleton, { variant: "custom", className: "h-3 w-2/3" })
4965
5133
  ] }) })
4966
- ) : children }) })
5134
+ ) : children }) }) })
4967
5135
  ]
4968
5136
  }
4969
5137
  );
@@ -4981,6 +5149,7 @@ var CardHeader = forwardRef(function CardHeader2({ className, children, actions,
4981
5149
  "button",
4982
5150
  {
4983
5151
  type: "button",
5152
+ "aria-label": "More actions",
4984
5153
  className: cn(
4985
5154
  "inline-flex items-center justify-center h-8 w-8 rounded-[var(--ds-radius-md)]",
4986
5155
  "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]",
@@ -5017,20 +5186,42 @@ var cardLineClampClasses = {
5017
5186
  5: "line-clamp-5",
5018
5187
  6: "line-clamp-6"
5019
5188
  };
5020
- var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, ...props }, forwardedRef) {
5189
+ var CardTitle = forwardRef(function CardTitle2({ className, children, lines = 2, id, ...props }, forwardedRef) {
5190
+ const action = useContext(CardActionCtx);
5191
+ const generatedId = useId();
5021
5192
  const { ref: overflowRef, isTruncated } = useTextOverflow({ lines, disabled: lines === 0 });
5022
5193
  const lineClampClass = lines > 0 ? cardLineClampClasses[lines] || `line-clamp-[${lines}]` : "";
5023
5194
  const heading = /* @__PURE__ */ jsx(
5024
5195
  "h3",
5025
5196
  {
5026
5197
  ref: (node) => {
5027
- overflowRef(lines > 0 ? node : null);
5198
+ if (!action) overflowRef(lines > 0 ? node : null);
5028
5199
  if (typeof forwardedRef === "function") forwardedRef(node);
5029
5200
  else if (forwardedRef) forwardedRef.current = node;
5030
5201
  },
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),
5202
+ id: id ?? generatedId,
5203
+ "data-card-title": "",
5204
+ 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
5205
  ...props,
5033
- children
5206
+ children: action ? (
5207
+ // The card's action when the card holds controls of its own (Sprint 25.7): a real button, which a click
5208
+ // elsewhere on the card also runs. Its ::after spans the card to draw the card's focus ring, taking no clicks.
5209
+ // The clamp moves inside, since a button lays out its own lines.
5210
+ /* @__PURE__ */ jsx(
5211
+ "button",
5212
+ {
5213
+ type: "button",
5214
+ ...{ [CARD_ACTION_ATTR]: "" },
5215
+ disabled: action.disabled,
5216
+ onClick: action.activate,
5217
+ className: cn(
5218
+ "block w-full text-left cursor-pointer [text-overflow:inherit] focus-visible:outline-none disabled:cursor-not-allowed",
5219
+ "after:absolute after:inset-0 after:rounded-[var(--ds-radius-md)] after:pointer-events-none focus-visible:after:shadow-[var(--ds-shadow-focus)]"
5220
+ ),
5221
+ children: /* @__PURE__ */ jsx("span", { ref: (node) => overflowRef(lines > 0 ? node : null), className: cn("block [text-overflow:inherit]", lines > 0 && lineClampClass), children })
5222
+ }
5223
+ )
5224
+ ) : children
5034
5225
  }
5035
5226
  );
5036
5227
  if (!isTruncated || lines === 0) return heading;
@@ -6047,10 +6238,10 @@ function OverflowPopover({
6047
6238
  if (!open) return;
6048
6239
  const raf = requestAnimationFrame(() => focusInitial(floatingRef.current));
6049
6240
  const onKey = (e) => {
6050
- if (e.key === "Escape" && isTop()) {
6051
- returnFocus();
6052
- setOpen(false);
6053
- }
6241
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
6242
+ e.preventDefault();
6243
+ returnFocus();
6244
+ setOpen(false);
6054
6245
  };
6055
6246
  const onDown = (e) => {
6056
6247
  if (!triggerRef.current?.contains(e.target)) setOpen(false);
@@ -6705,6 +6896,9 @@ function CompactDayCell({
6705
6896
  const today = isCurrentMonth && isToday(day);
6706
6897
  const selected = isCurrentMonth && selectedDate && isSameDay2(day, selectedDate);
6707
6898
  const hasEvents = dayEvents.length > 0;
6899
+ const shown = open && hasEvents;
6900
+ const isTop = useOverlayLayer(shown);
6901
+ useCloseOnFocusOut(shown, cellRef, floatingRef, () => setOpen(false), isTop);
6708
6902
  const eventColors = dayEvents.slice(0, 3).map((e) => categories[e.category ?? ""]?.color ?? "var(--ds-accent-primary)");
6709
6903
  const handleMouseEnter = () => {
6710
6904
  if (!hasEvents) return;
@@ -6723,7 +6917,9 @@ function CompactDayCell({
6723
6917
  useEffect(() => {
6724
6918
  if (!open) return;
6725
6919
  const onKey = (e) => {
6726
- if (e.key === "Escape") setOpen(false);
6920
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
6921
+ e.preventDefault();
6922
+ setOpen(false);
6727
6923
  };
6728
6924
  const onClick = (e) => {
6729
6925
  const t = e.target;
@@ -6736,7 +6932,7 @@ function CompactDayCell({
6736
6932
  document.removeEventListener("keydown", onKey);
6737
6933
  document.removeEventListener("mousedown", onClick);
6738
6934
  };
6739
- }, [open]);
6935
+ }, [open, isTop]);
6740
6936
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6741
6937
  /* @__PURE__ */ jsxs(
6742
6938
  "button",
@@ -6908,10 +7104,10 @@ function MonthDropdown({
6908
7104
  (panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
6909
7105
  });
6910
7106
  const onKey = (e) => {
6911
- if (e.key === "Escape" && isTop()) {
6912
- returnFocus();
6913
- setOpen(false);
6914
- }
7107
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
7108
+ e.preventDefault();
7109
+ returnFocus();
7110
+ setOpen(false);
6915
7111
  };
6916
7112
  const onClick = (e) => {
6917
7113
  const t = e.target;
@@ -7042,10 +7238,10 @@ function YearDropdown({
7042
7238
  (panel?.querySelector("[data-current]") ?? panel?.querySelector("button"))?.focus();
7043
7239
  });
7044
7240
  const onKey = (e) => {
7045
- if (e.key === "Escape" && isTop()) {
7046
- returnFocus();
7047
- setOpen(false);
7048
- }
7241
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
7242
+ e.preventDefault();
7243
+ returnFocus();
7244
+ setOpen(false);
7049
7245
  };
7050
7246
  const onClick = (e) => {
7051
7247
  const t = e.target;
@@ -9877,10 +10073,10 @@ var Breadcrumbs = forwardRef(function Breadcrumbs2({
9877
10073
  setDropdownOpen(false);
9878
10074
  };
9879
10075
  const onKeyDown = (e) => {
9880
- if (e.key === "Escape" && isTop()) {
9881
- returnFocus();
9882
- setDropdownOpen(false);
9883
- }
10076
+ if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
10077
+ e.preventDefault();
10078
+ returnFocus();
10079
+ setDropdownOpen(false);
9884
10080
  };
9885
10081
  document.addEventListener("mousedown", handler);
9886
10082
  document.addEventListener("keydown", onKeyDown);
@@ -10080,27 +10276,31 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
10080
10276
  setTriggerWidth(node?.offsetWidth);
10081
10277
  }, []);
10082
10278
  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" });
10279
+ const onOpenChangeRef = useLatest(onOpenChange);
10280
+ const { props: pressMarks, marked } = usePressMarks();
10281
+ const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: floatingRef, close: () => onOpenChangeRef.current(false), tabExits: "edges", marked });
10084
10282
  useEffect(() => {
10085
10283
  if (!open) return;
10086
10284
  const onKeyDown = (e) => {
10087
- if (e.key === "Escape" && isTopLayer()) {
10088
- returnFocus();
10089
- onOpenChange(false);
10090
- }
10091
- };
10092
- const onClick = (e) => {
10285
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
10286
+ const active = document.activeElement;
10287
+ if (active && active !== document.body && !triggerRef.current?.contains(active) && !floatingRef.current?.contains(active)) return;
10288
+ e.preventDefault();
10289
+ returnFocus();
10290
+ onOpenChangeRef.current(false);
10291
+ };
10292
+ const onPress = (e) => {
10093
10293
  const target = e.target;
10094
- if (triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
10095
- onOpenChange(false);
10294
+ if (marked(e) || triggerRef.current?.contains(target) || floatingRef.current?.contains(target)) return;
10295
+ onOpenChangeRef.current(false);
10096
10296
  };
10097
10297
  document.addEventListener("keydown", onKeyDown);
10098
- document.addEventListener("mousedown", onClick);
10298
+ document.addEventListener("mousedown", onPress);
10099
10299
  return () => {
10100
10300
  document.removeEventListener("keydown", onKeyDown);
10101
- document.removeEventListener("mousedown", onClick);
10301
+ document.removeEventListener("mousedown", onPress);
10102
10302
  };
10103
- }, [open, onOpenChange, isTopLayer, returnFocus]);
10303
+ }, [open, onOpenChangeRef, isTopLayer, returnFocus, marked]);
10104
10304
  useEffect(() => {
10105
10305
  if (open) focusInitial(floatingRef.current);
10106
10306
  }, [open]);
@@ -10122,6 +10322,7 @@ function Popover({ open, onOpenChange, trigger, children, side = "bottom", align
10122
10322
  {
10123
10323
  ref: floatingRef,
10124
10324
  "data-gs": "popover",
10325
+ ...pressMarks,
10125
10326
  onClick: (e) => e.stopPropagation(),
10126
10327
  className: cn(
10127
10328
  "fixed z-[var(--ds-z-popover)]",
@@ -10337,10 +10538,87 @@ function EllipsisJumper({ totalPages, onPageChange }) {
10337
10538
  }
10338
10539
  );
10339
10540
  }
10541
+ var HeaderTabRowContext = createContext(null);
10542
+ function useHeaderTabRow() {
10543
+ return useContext(HeaderTabRowContext);
10544
+ }
10545
+ function useNarrowRow(row) {
10546
+ const [narrow, setNarrow] = useState(false);
10547
+ useLayoutEffect(() => {
10548
+ if (!row) return;
10549
+ const limit = readBreakpointPx("sm");
10550
+ const measure = () => setNarrow(row.clientWidth < limit);
10551
+ measure();
10552
+ if (typeof ResizeObserver === "undefined") return;
10553
+ const ro = new ResizeObserver(measure);
10554
+ ro.observe(row);
10555
+ return () => ro.disconnect();
10556
+ }, [row]);
10557
+ return narrow;
10558
+ }
10340
10559
  function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal", className }) {
10341
10560
  const isHorizontal = orientation === "horizontal";
10561
+ const tabRow = useHeaderTabRow();
10562
+ const narrow = useNarrowRow(tabRow?.row ?? null);
10563
+ const inHeader = isHorizontal && tabRow != null;
10564
+ if (inHeader && narrow) {
10565
+ const current = steps[currentStep];
10566
+ const where = `Step ${Math.min(currentStep + 1, steps.length)} of ${steps.length}`;
10567
+ return /* @__PURE__ */ jsxs("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("mx-ds-1", className), children: [
10568
+ /* @__PURE__ */ jsxs("p", { className: cn("mb-ds-2 text-sm font-medium text-[var(--ds-text-secondary)]", tabRow.hasCaption && "sr-only"), children: [
10569
+ where,
10570
+ current ? /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
10571
+ ": ",
10572
+ current.label
10573
+ ] }) : null
10574
+ ] }),
10575
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "flex gap-ds-1", children: steps.map((step, i) => /* @__PURE__ */ jsx(
10576
+ "div",
10577
+ {
10578
+ className: cn(
10579
+ "h-1.5 flex-1 rounded-[var(--ds-radius-full)] transition-colors",
10580
+ step.status === "error" ? "bg-[var(--ds-status-error)]" : i <= currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
10581
+ )
10582
+ },
10583
+ i
10584
+ )) })
10585
+ ] });
10586
+ }
10587
+ if (inHeader) {
10588
+ return /* @__PURE__ */ jsx("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("flex items-start mx-ds-1", className), children: steps.map((step, i) => {
10589
+ const status = step.status ?? (i < currentStep ? "complete" : i === currentStep ? "current" : "upcoming");
10590
+ const isLast = i === steps.length - 1;
10591
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-start min-w-0", isLast ? "flex-none" : "flex-1"), children: [
10592
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
10593
+ /* @__PURE__ */ jsx(StepCircle, { status, index: i, onClick: onStepChange ? () => onStepChange(i) : void 0, clickable: !!onStepChange }),
10594
+ !isLast && /* @__PURE__ */ jsx(
10595
+ "div",
10596
+ {
10597
+ className: cn(
10598
+ "flex-1 h-0.5 mx-ds-2 transition-colors",
10599
+ i < currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
10600
+ )
10601
+ }
10602
+ )
10603
+ ] }),
10604
+ /* @__PURE__ */ jsxs("div", { className: "mt-ds-2 pr-ds-2", children: [
10605
+ /* @__PURE__ */ jsx(
10606
+ "p",
10607
+ {
10608
+ className: cn(
10609
+ "text-xs font-medium",
10610
+ status === "current" ? "text-[var(--ds-accent-primary)]" : "text-[var(--ds-text-primary)]"
10611
+ ),
10612
+ children: step.label
10613
+ }
10614
+ ),
10615
+ step.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--ds-text-muted)] mt-ds-0.5", children: step.description })
10616
+ ] })
10617
+ ] }, i);
10618
+ }) });
10619
+ }
10342
10620
  if (!isHorizontal) {
10343
- return /* @__PURE__ */ jsx("nav", { "aria-label": "Progress", className: cn("flex flex-col", className), children: steps.map((step, i) => {
10621
+ return /* @__PURE__ */ jsx("nav", { "data-gs": "stepper", "aria-label": "Progress", className: cn("flex flex-col", className), children: steps.map((step, i) => {
10344
10622
  const status = step.status ?? (i < currentStep ? "complete" : i === currentStep ? "current" : "upcoming");
10345
10623
  const isLast = i === steps.length - 1;
10346
10624
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-ds-3", children: [
@@ -10375,6 +10653,7 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
10375
10653
  return /* @__PURE__ */ jsx(
10376
10654
  "nav",
10377
10655
  {
10656
+ "data-gs": "stepper",
10378
10657
  "aria-label": "Progress",
10379
10658
  className: cn("flex items-start", className),
10380
10659
  children: steps.map((step, i) => {
@@ -10795,6 +11074,7 @@ function computeCompactFlyoutPos(anchorEl, itemsLength) {
10795
11074
  return { top: Math.max(8, top), left: rect.right - (FLYOUT_BRIDGE_PX - 4) };
10796
11075
  }
10797
11076
  function CompactFlyout({
11077
+ id,
10798
11078
  parentLabel,
10799
11079
  items,
10800
11080
  anchorEl,
@@ -10808,22 +11088,23 @@ function CompactFlyout({
10808
11088
  const pos = useMemo(() => computeCompactFlyoutPos(anchorEl, items.length), [anchorEl, items.length]);
10809
11089
  const anchorRef = useRef(anchorEl);
10810
11090
  const panelRef = useRef(null);
11091
+ const headingId = useId();
10811
11092
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(true, { trigger: anchorRef, panel: panelRef, close: onClose, tabExits: "edges" });
10812
11093
  useEffect(() => {
10813
11094
  if (!focusIn) return;
10814
- const raf = requestAnimationFrame(() => panelRef.current?.querySelector('[role="menuitem"]')?.focus());
11095
+ const raf = requestAnimationFrame(() => panelRef.current?.querySelector("a[href]")?.focus());
10815
11096
  return () => cancelAnimationFrame(raf);
10816
11097
  }, [focusIn]);
10817
11098
  useEffect(() => {
10818
11099
  function handler(e) {
10819
- if (!isTopLayer()) return;
11100
+ if (e.defaultPrevented || !isTopLayer()) return;
10820
11101
  const inside = !!panelRef.current?.contains(document.activeElement);
10821
11102
  if (e.key === "Escape" || e.key === "ArrowLeft" && inside) {
10822
11103
  e.preventDefault();
10823
11104
  if (inside) returnFocus();
10824
11105
  onClose();
10825
11106
  } else if (inside && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
10826
- const links = Array.from(panelRef.current?.querySelectorAll('[role="menuitem"]') ?? []);
11107
+ const links = Array.from(panelRef.current?.querySelectorAll("a[href]") ?? []);
10827
11108
  const at = links.indexOf(document.activeElement);
10828
11109
  const next = links[(at + (e.key === "ArrowDown" ? 1 : -1) + links.length) % links.length];
10829
11110
  if (next) {
@@ -10846,8 +11127,7 @@ function CompactFlyout({
10846
11127
  "div",
10847
11128
  {
10848
11129
  ref: panelRef,
10849
- role: "menu",
10850
- "aria-label": parentLabel,
11130
+ id,
10851
11131
  className: cn(
10852
11132
  "min-w-[160px] py-ds-1",
10853
11133
  "bg-[var(--ds-sidebar-bg)] rounded-[var(--ds-radius-md)]",
@@ -10855,12 +11135,11 @@ function CompactFlyout({
10855
11135
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]"
10856
11136
  ),
10857
11137
  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(
11138
+ /* @__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 }),
11139
+ /* @__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
11140
  "a",
10861
11141
  {
10862
11142
  href: child.href ?? "#",
10863
- role: "menuitem",
10864
11143
  onClick: (e) => {
10865
11144
  e.preventDefault();
10866
11145
  onItemClick?.(child, i);
@@ -10875,9 +11154,8 @@ function CompactFlyout({
10875
11154
  /* @__PURE__ */ jsx(TruncLabel3, { children: child.label }),
10876
11155
  child.badge && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: child.badge })
10877
11156
  ]
10878
- },
10879
- child.label
10880
- )) })
11157
+ }
11158
+ ) }, child.label)) })
10881
11159
  ]
10882
11160
  }
10883
11161
  )
@@ -11224,6 +11502,7 @@ function CompactNavItem({
11224
11502
  onToggleExpand
11225
11503
  }) {
11226
11504
  const [triggerEl, setTriggerEl] = useState(null);
11505
+ const flyoutId = useId();
11227
11506
  const [keyboardOpen, setKeyboardOpen] = useState(false);
11228
11507
  if (!isOpen && keyboardOpen) setKeyboardOpen(false);
11229
11508
  const handleMouseEnter = useCallback(() => {
@@ -11255,8 +11534,8 @@ function CompactNavItem({
11255
11534
  setKeyboardOpen(true);
11256
11535
  onOpen(flyoutKey);
11257
11536
  } : void 0,
11258
- "aria-haspopup": !mobile && hasChildren ? "menu" : void 0,
11259
11537
  "aria-expanded": hasChildren ? mobile ? expanded : isOpen : void 0,
11538
+ "aria-controls": !mobile && hasChildren && isOpen ? flyoutId : void 0,
11260
11539
  className: cn(
11261
11540
  "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
11541
  // Compact tiles have no edge to stroke: they read the fill and text
@@ -11291,6 +11570,7 @@ function CompactNavItem({
11291
11570
  !mobile && isOpen && hasChildren && item.children && triggerEl && /* @__PURE__ */ jsx(
11292
11571
  CompactFlyout,
11293
11572
  {
11573
+ id: flyoutId,
11294
11574
  parentLabel: item.label,
11295
11575
  items: item.children,
11296
11576
  anchorEl: triggerEl,
@@ -11766,6 +12046,35 @@ function BackButton({ onLight, onClick }) {
11766
12046
  }
11767
12047
  );
11768
12048
  }
12049
+ function BackLink({ onClick, onLight }) {
12050
+ return /* @__PURE__ */ jsxs(
12051
+ "button",
12052
+ {
12053
+ type: "button",
12054
+ onClick,
12055
+ className: cn(
12056
+ "inline-flex items-center gap-ds-1 mb-ds-3 text-sm underline underline-offset-4 cursor-pointer",
12057
+ "rounded-[var(--ds-radius-sm)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
12058
+ onLight ? "text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-primary)] hover:text-[var(--ds-accent-primary)]"
12059
+ ),
12060
+ children: [
12061
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }),
12062
+ "Back"
12063
+ ]
12064
+ }
12065
+ );
12066
+ }
12067
+ function Caption({ children, onLight, onTint }) {
12068
+ return /* @__PURE__ */ jsx("p", { className: cn(
12069
+ "mb-ds-1 text-sm font-medium",
12070
+ onLight ? "text-[var(--ds-text-on-primary)]/80" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]"
12071
+ ), children });
12072
+ }
12073
+ function TabRow({ children, hasCaption, className, onBand }) {
12074
+ const [row, setRow] = useState(null);
12075
+ const value = useMemo(() => ({ row, hasCaption }), [row, hasCaption]);
12076
+ 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 }) });
12077
+ }
11769
12078
  var FLOATING_HEADER = cn(
11770
12079
  "rounded-[var(--ds-radius-md)]",
11771
12080
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
@@ -11783,9 +12092,10 @@ function computeFloatingPadding(navbarFloating, sidebarFloating, overlapActive,
11783
12092
  var PageHeader = forwardRef(function PageHeader2({
11784
12093
  title,
11785
12094
  description,
12095
+ caption,
11786
12096
  badge,
11787
12097
  breadcrumbs,
11788
- onBack,
12098
+ onBack: onBackProp,
11789
12099
  actions,
11790
12100
  avatar,
11791
12101
  filters,
@@ -11844,12 +12154,16 @@ var PageHeader = forwardRef(function PageHeader2({
11844
12154
  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
12155
  const noBottomMargin = noBottomMarginProp ?? container?.noBottomMargin;
11846
12156
  const overlapActive = container?.overlap ?? false;
12157
+ const focusBack = shell?.focus ? onBackProp : void 0;
12158
+ const onBack = focusBack ? void 0 : onBackProp;
11847
12159
  if (behavior === "snapping") {
11848
12160
  return /* @__PURE__ */ jsx(
11849
12161
  SnappingHeader,
11850
12162
  {
11851
12163
  title,
11852
12164
  description,
12165
+ caption,
12166
+ focusBack,
11853
12167
  badge,
11854
12168
  breadcrumbs: inlineBreadcrumbs,
11855
12169
  onBack,
@@ -11881,13 +12195,15 @@ var PageHeader = forwardRef(function PageHeader2({
11881
12195
  }
11882
12196
  let header;
11883
12197
  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 });
12198
+ 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
12199
  } else if (variant === "tabs-integrated") {
11886
12200
  header = /* @__PURE__ */ jsx(
11887
12201
  TabsIntegratedHeader,
11888
12202
  {
11889
12203
  title,
11890
12204
  description,
12205
+ caption,
12206
+ focusBack,
11891
12207
  badge,
11892
12208
  breadcrumbs: inlineBreadcrumbs,
11893
12209
  onBack,
@@ -11910,6 +12226,9 @@ var PageHeader = forwardRef(function PageHeader2({
11910
12226
  gutter: !insidePaddedColumn,
11911
12227
  title,
11912
12228
  description,
12229
+ caption,
12230
+ focusBack,
12231
+ tabs,
11913
12232
  badge,
11914
12233
  breadcrumbs: inlineBreadcrumbs,
11915
12234
  onBack,
@@ -11935,6 +12254,9 @@ var PageHeader = forwardRef(function PageHeader2({
11935
12254
  {
11936
12255
  title,
11937
12256
  description,
12257
+ caption,
12258
+ focusBack,
12259
+ tabs,
11938
12260
  badge,
11939
12261
  breadcrumbs: inlineBreadcrumbs,
11940
12262
  onBack,
@@ -11975,6 +12297,9 @@ function floatingContentProps(isFloating, contentMaxWidth, extraClassName) {
11975
12297
  function DefaultHeader({
11976
12298
  title,
11977
12299
  description,
12300
+ caption,
12301
+ focusBack,
12302
+ tabs,
11978
12303
  badge,
11979
12304
  breadcrumbs,
11980
12305
  onBack,
@@ -11997,6 +12322,7 @@ function DefaultHeader({
11997
12322
  return /* @__PURE__ */ jsxs(
11998
12323
  "header",
11999
12324
  {
12325
+ "data-gs": "page-header",
12000
12326
  ref: headerRef,
12001
12327
  className: cn(
12002
12328
  !hasBanner && (hasBreadcrumbs ? "pt-ds-3 pb-ds-6" : "py-ds-6"),
@@ -12015,6 +12341,7 @@ function DefaultHeader({
12015
12341
  children: [
12016
12342
  hasBanner && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[var(--ds-bg-canvas)]/85" }),
12017
12343
  /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
12344
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12018
12345
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12019
12346
  /* @__PURE__ */ jsxs("div", { className: cn(
12020
12347
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12024,6 +12351,7 @@ function DefaultHeader({
12024
12351
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack }),
12025
12352
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12026
12353
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
12354
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12027
12355
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12028
12356
  /* @__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
12357
  badge
@@ -12033,7 +12361,8 @@ function DefaultHeader({
12033
12361
  ] }),
12034
12362
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12035
12363
  ] }),
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 }) })
12364
+ 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 }) }),
12365
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
12037
12366
  ] })
12038
12367
  ]
12039
12368
  }
@@ -12041,9 +12370,12 @@ function DefaultHeader({
12041
12370
  }
12042
12371
  function MinimalHeader({
12043
12372
  title,
12373
+ caption,
12374
+ focusBack,
12044
12375
  badge,
12045
12376
  breadcrumbs,
12046
12377
  actions,
12378
+ tabs,
12047
12379
  breadcrumbPill,
12048
12380
  breadcrumbSeparator,
12049
12381
  as: Heading = "h1",
@@ -12054,20 +12386,26 @@ function MinimalHeader({
12054
12386
  }) {
12055
12387
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
12056
12388
  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: [
12389
+ 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: [
12390
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12058
12391
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12392
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12059
12393
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && "mt-ds-0.5"), children: [
12060
12394
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
12061
12395
  /* @__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
12396
  badge
12063
12397
  ] }),
12064
12398
  actions && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) })
12065
- ] })
12399
+ ] }),
12400
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
12066
12401
  ] }) });
12067
12402
  }
12068
12403
  function ColourfulHeader({
12069
12404
  title,
12070
12405
  description,
12406
+ caption,
12407
+ focusBack,
12408
+ tabs,
12071
12409
  badge,
12072
12410
  breadcrumbs,
12073
12411
  onBack,
@@ -12092,6 +12430,7 @@ function ColourfulHeader({
12092
12430
  return /* @__PURE__ */ jsxs(
12093
12431
  "header",
12094
12432
  {
12433
+ "data-gs": "page-header",
12095
12434
  ref: headerRef,
12096
12435
  className: cn(
12097
12436
  "relative overflow-hidden w-full",
@@ -12135,6 +12474,7 @@ function ColourfulHeader({
12135
12474
  ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
12136
12475
  },
12137
12476
  children: [
12477
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight: isBold }),
12138
12478
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight: isBold, onTint: !isBold, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12139
12479
  /* @__PURE__ */ jsxs("div", { className: cn(
12140
12480
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12144,6 +12484,7 @@ function ColourfulHeader({
12144
12484
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack, onLight: isBold }),
12145
12485
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12146
12486
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
12487
+ caption && /* @__PURE__ */ jsx(Caption, { onLight: isBold, onTint: !isBold, children: caption }),
12147
12488
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12148
12489
  /* @__PURE__ */ jsx(TruncHeading, { as: Heading, className: cn(
12149
12490
  "font-[family-name:var(--ds-font-display)] text-3xl font-[var(--ds-weight-heading)]",
@@ -12159,7 +12500,8 @@ function ColourfulHeader({
12159
12500
  ] }),
12160
12501
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12161
12502
  ] }),
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 }) })
12503
+ 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 }) }),
12504
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBold, children: tabs })
12163
12505
  ]
12164
12506
  }
12165
12507
  )
@@ -12170,6 +12512,8 @@ function ColourfulHeader({
12170
12512
  function SnappingHeader({
12171
12513
  title,
12172
12514
  description,
12515
+ caption,
12516
+ focusBack,
12173
12517
  badge,
12174
12518
  breadcrumbs,
12175
12519
  onBack,
@@ -12293,6 +12637,7 @@ function SnappingHeader({
12293
12637
  const headerEl = /* @__PURE__ */ jsxs(
12294
12638
  "header",
12295
12639
  {
12640
+ "data-gs": "page-header",
12296
12641
  ref: !isFloating ? mergedRef : void 0,
12297
12642
  className: cn(
12298
12643
  "transition-all duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
@@ -12329,8 +12674,10 @@ function SnappingHeader({
12329
12674
  onTint && !backgroundImage && /* @__PURE__ */ jsx(TintWash, {}),
12330
12675
  (() => {
12331
12676
  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: [
12677
+ const innerContent = isMinimal && !collapsed ? /* @__PURE__ */ jsxs("div", { className: cn("py-ds-2", hasBreadcrumbs && !hideExpandedBreadcrumbs && "pt-ds-1", tabs && "pb-ds-4"), children: [
12678
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12333
12679
  hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12680
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12334
12681
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && !hideExpandedBreadcrumbs && "mt-ds-0.5"), children: [
12335
12682
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
12336
12683
  /* @__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 +12687,7 @@ function SnappingHeader({
12340
12687
  ] }),
12341
12688
  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
12689
  expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-3", children: expandedContent }),
12343
- tabs && /* @__PURE__ */ jsx("div", { className: "mt-ds-3 -mx-ds-1", children: tabs })
12690
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
12344
12691
  ] }) : /* @__PURE__ */ jsxs(
12345
12692
  "div",
12346
12693
  {
@@ -12354,6 +12701,7 @@ function SnappingHeader({
12354
12701
  ),
12355
12702
  style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : void 0,
12356
12703
  children: [
12704
+ !collapsed && focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight }),
12357
12705
  !collapsed && hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight, onTint, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12358
12706
  /* @__PURE__ */ jsxs("div", { className: cn(
12359
12707
  "flex justify-between gap-ds-4",
@@ -12365,6 +12713,7 @@ function SnappingHeader({
12365
12713
  onBack && !collapsed && /* @__PURE__ */ jsx(BackButton, { onClick: onBack, onLight }),
12366
12714
  avatar && !collapsed && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12367
12715
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
12716
+ !collapsed && caption && /* @__PURE__ */ jsx(Caption, { onLight, onTint, children: caption }),
12368
12717
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12369
12718
  collapsed && hasBreadcrumbs && (() => {
12370
12719
  const lastCrumb = breadcrumbs[breadcrumbs.length - 1];
@@ -12407,7 +12756,7 @@ function SnappingHeader({
12407
12756
  ] }),
12408
12757
  !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
12758
  !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 })
12759
+ !collapsed && tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBoldColourful, children: tabs })
12411
12760
  ]
12412
12761
  }
12413
12762
  );
@@ -12455,6 +12804,8 @@ function SnappingHeader({
12455
12804
  function TabsIntegratedHeader({
12456
12805
  title,
12457
12806
  description,
12807
+ caption,
12808
+ focusBack,
12458
12809
  badge,
12459
12810
  breadcrumbs,
12460
12811
  onBack,
@@ -12471,7 +12822,8 @@ function TabsIntegratedHeader({
12471
12822
  }) {
12472
12823
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
12473
12824
  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: [
12825
+ 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: [
12826
+ focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
12475
12827
  hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
12476
12828
  /* @__PURE__ */ jsxs("div", { className: cn(
12477
12829
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
@@ -12481,6 +12833,7 @@ function TabsIntegratedHeader({
12481
12833
  onBack && /* @__PURE__ */ jsx(BackButton, { onClick: onBack }),
12482
12834
  avatar && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: avatar }),
12483
12835
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
12836
+ caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
12484
12837
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
12485
12838
  /* @__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
12839
  badge
@@ -12490,7 +12843,7 @@ function TabsIntegratedHeader({
12490
12843
  ] }),
12491
12844
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
12492
12845
  ] }),
12493
- tabs && /* @__PURE__ */ jsx("div", { className: "mt-ds-4 -mx-ds-1", children: tabs })
12846
+ tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
12494
12847
  ] }) });
12495
12848
  }
12496
12849
  var DEFAULT_BASE = 48;
@@ -12655,6 +13008,7 @@ function NavMenu({
12655
13008
  const [open, setOpen] = useState(false);
12656
13009
  const triggerRef = useRef(null);
12657
13010
  const panelRef = useRef(null);
13011
+ const panelId = useId();
12658
13012
  const [pos, setPos] = useState({ top: 0, left: 0, minWidth: 0 });
12659
13013
  const close = useCallback(() => setOpen(false), []);
12660
13014
  const { isTop: isTopLayer, returnFocus } = usePopupFocus(open, { trigger: triggerRef, panel: panelRef, close, tabExits: "edges" });
@@ -12680,10 +13034,10 @@ function NavMenu({
12680
13034
  }
12681
13035
  }
12682
13036
  function handleKey(e) {
12683
- if (e.key === "Escape" && isTopLayer()) {
12684
- returnFocus();
12685
- close();
12686
- }
13037
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
13038
+ e.preventDefault();
13039
+ returnFocus();
13040
+ close();
12687
13041
  }
12688
13042
  document.addEventListener("mousedown", handleClick);
12689
13043
  document.addEventListener("keydown", handleKey);
@@ -12725,7 +13079,7 @@ function NavMenu({
12725
13079
  ref: triggerRef,
12726
13080
  onClick: () => setOpen(!open),
12727
13081
  "aria-expanded": open,
12728
- "aria-haspopup": "true",
13082
+ "aria-controls": open ? panelId : void 0,
12729
13083
  className: triggerClasses,
12730
13084
  children: [
12731
13085
  label,
@@ -12745,7 +13099,7 @@ function NavMenu({
12745
13099
  "div",
12746
13100
  {
12747
13101
  ref: panelRef,
12748
- role: "menu",
13102
+ id: panelId,
12749
13103
  "data-gs": "menu",
12750
13104
  className: cn(
12751
13105
  "fixed z-[var(--ds-z-popover)] py-ds-1.5",
@@ -12754,7 +13108,7 @@ function NavMenu({
12754
13108
  "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]"
12755
13109
  ),
12756
13110
  style: { top: pos.top, left: pos.left, minWidth: pos.minWidth },
12757
- children: items.map((item, i) => {
13111
+ children: /* @__PURE__ */ jsx("ul", { children: items.map((item, i) => {
12758
13112
  if (item.children?.length) {
12759
13113
  return /* @__PURE__ */ jsx(
12760
13114
  NavMenuGroup,
@@ -12780,7 +13134,7 @@ function NavMenu({
12780
13134
  },
12781
13135
  item.label
12782
13136
  );
12783
- })
13137
+ }) })
12784
13138
  }
12785
13139
  ),
12786
13140
  document.body
@@ -12791,11 +13145,10 @@ function NavMenuItemRow({
12791
13145
  item,
12792
13146
  onClick
12793
13147
  }) {
12794
- return /* @__PURE__ */ jsxs(
13148
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
12795
13149
  "a",
12796
13150
  {
12797
13151
  href: item.href ?? "#",
12798
- role: "menuitem",
12799
13152
  onClick: (e) => {
12800
13153
  e.preventDefault();
12801
13154
  onClick();
@@ -12821,14 +13174,15 @@ function NavMenuItemRow({
12821
13174
  ] })
12822
13175
  ]
12823
13176
  }
12824
- );
13177
+ ) });
12825
13178
  }
12826
13179
  function NavMenuGroup({
12827
13180
  group,
12828
13181
  isFirst,
12829
13182
  onItemClick
12830
13183
  }) {
12831
- return /* @__PURE__ */ jsxs("div", { children: [
13184
+ const headingId = useId();
13185
+ return /* @__PURE__ */ jsxs("li", { children: [
12832
13186
  !isFirst && /* @__PURE__ */ jsx(
12833
13187
  "div",
12834
13188
  {
@@ -12836,15 +13190,15 @@ function NavMenuGroup({
12836
13190
  style: { height: "var(--ds-divider-width, 1px)", marginTop: "var(--ds-space-1)", marginBottom: "var(--ds-space-1)" }
12837
13191
  }
12838
13192
  ),
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(
13193
+ /* @__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 }),
13194
+ /* @__PURE__ */ jsx("ul", { "aria-labelledby": headingId, children: group.children.map((child) => /* @__PURE__ */ jsx(
12841
13195
  NavMenuItemRow,
12842
13196
  {
12843
13197
  item: child,
12844
13198
  onClick: () => onItemClick(child)
12845
13199
  },
12846
13200
  child.label
12847
- ))
13201
+ )) })
12848
13202
  ] });
12849
13203
  }
12850
13204
  function resolveNavItemType(type) {
@@ -12908,6 +13262,174 @@ function getTypeClasses(type, active) {
12908
13262
  );
12909
13263
  }
12910
13264
  }
13265
+ var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
13266
+ var sizeClasses = {
13267
+ // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
13268
+ sm: "max-w-md",
13269
+ md: "max-w-lg",
13270
+ lg: "max-w-2xl",
13271
+ xl: "max-w-4xl",
13272
+ full: "max-w-[calc(100vw-2rem)]"
13273
+ };
13274
+ function Dialog({ open, onClose: onCloseProp, title, description, children, footer, variant = "default", size = "md", alert = false, container, onSubmit, dirty, onDiscard }) {
13275
+ const guard = useDirtyGuard(dirty, onCloseProp, onDiscard);
13276
+ const onClose = guard.requestClose;
13277
+ const titleId = useId();
13278
+ const descriptionId = useId();
13279
+ const isMobile = useBreakpoint("sm");
13280
+ const overlayRef = useRef(null);
13281
+ const dialogRef = useRef(null);
13282
+ const opener = useRef(null);
13283
+ const defaultButtonRef = useRef(null);
13284
+ const onCloseRef = useLatest(onClose);
13285
+ const onSubmitRef = useLatest(onSubmit);
13286
+ const isTopLayer = useOverlayLayer(open);
13287
+ useFocusTrap(dialogRef, open, isTopLayer);
13288
+ const before = useFocusBeforeOpen(open);
13289
+ useEffect(() => {
13290
+ if (!open) return;
13291
+ const active = before.current;
13292
+ opener.current = active instanceof HTMLElement && active !== document.body && active.isConnected ? rememberFocus(active) : null;
13293
+ }, [open, before]);
13294
+ useEffect(() => {
13295
+ if (!open) return;
13296
+ requestAnimationFrame(() => {
13297
+ focusInitial(dialogRef.current, { destructive: variant === "destructive" });
13298
+ });
13299
+ }, [open, variant]);
13300
+ useEffect(() => {
13301
+ if (!open) return;
13302
+ const handler = (e) => {
13303
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
13304
+ e.preventDefault();
13305
+ onCloseRef.current();
13306
+ };
13307
+ document.addEventListener("keydown", handler);
13308
+ return () => {
13309
+ document.removeEventListener("keydown", handler);
13310
+ };
13311
+ }, [open, onCloseRef, isTopLayer]);
13312
+ useEffect(() => {
13313
+ if (!open) return;
13314
+ lockBodyScroll();
13315
+ return () => {
13316
+ unlockBodyScroll();
13317
+ const r = opener.current;
13318
+ opener.current = null;
13319
+ const active = document.activeElement;
13320
+ if (!active || active === document.body || !active.isConnected) r?.restore();
13321
+ };
13322
+ }, [open]);
13323
+ if (!open) return null;
13324
+ return createPortal(
13325
+ /* @__PURE__ */ jsxs(
13326
+ "div",
13327
+ {
13328
+ ref: overlayRef,
13329
+ className: "fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center p-ds-4",
13330
+ children: [
13331
+ /* @__PURE__ */ jsx(
13332
+ "div",
13333
+ {
13334
+ 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))]",
13335
+ onClick: alert ? void 0 : onClose,
13336
+ "aria-hidden": "true"
13337
+ }
13338
+ ),
13339
+ /* @__PURE__ */ jsxs(
13340
+ "form",
13341
+ {
13342
+ onSubmit: (e) => {
13343
+ e.preventDefault();
13344
+ if (variant === "destructive") return;
13345
+ const submitter = e.nativeEvent.submitter;
13346
+ const implicit = !submitter || submitter === defaultButtonRef.current;
13347
+ if (!implicit && submitter.getAttribute("type") !== "submit") return;
13348
+ if (implicit) {
13349
+ const explicit = e.currentTarget.querySelector(`button[type="submit"]:not([${DEFAULT_BUTTON_ATTR}])`);
13350
+ if (explicit?.disabled) return;
13351
+ }
13352
+ onSubmitRef.current?.();
13353
+ },
13354
+ className: "contents",
13355
+ children: [
13356
+ /* @__PURE__ */ jsx(
13357
+ "button",
13358
+ {
13359
+ ref: defaultButtonRef,
13360
+ type: "submit",
13361
+ tabIndex: -1,
13362
+ "aria-hidden": "true",
13363
+ className: "sr-only",
13364
+ ...{ [DEFAULT_BUTTON_ATTR]: "" }
13365
+ }
13366
+ ),
13367
+ /* @__PURE__ */ jsxs(
13368
+ "div",
13369
+ {
13370
+ ref: dialogRef,
13371
+ role: alert ? "alertdialog" : "dialog",
13372
+ "aria-modal": "true",
13373
+ "data-gs": "dialog",
13374
+ "aria-labelledby": titleId,
13375
+ "aria-describedby": description ? descriptionId : void 0,
13376
+ style: { containerType: "inline-size" },
13377
+ className: cn(
13378
+ "relative z-10 w-full",
13379
+ isMobile ? "max-w-none h-full" : sizeClasses[size],
13380
+ "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
13381
+ GLASS_OVERLAY,
13382
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
13383
+ isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)]",
13384
+ "overflow-hidden",
13385
+ "animate-in fade-in-0 zoom-in-95"
13386
+ ),
13387
+ children: [
13388
+ /* @__PURE__ */ jsx(
13389
+ "button",
13390
+ {
13391
+ type: "button",
13392
+ onClick: onClose,
13393
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
13394
+ className: "absolute right-4 top-4 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
13395
+ "aria-label": "Close",
13396
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
13397
+ }
13398
+ ),
13399
+ /* @__PURE__ */ jsx("div", { className: "p-ds-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-ds-3", children: [
13400
+ 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)]" }) }),
13401
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
13402
+ /* @__PURE__ */ jsx(
13403
+ "h2",
13404
+ {
13405
+ id: titleId,
13406
+ className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] leading-none tracking-tight",
13407
+ children: title
13408
+ }
13409
+ ),
13410
+ description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
13411
+ children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) })
13412
+ ] })
13413
+ ] }) }),
13414
+ /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
13415
+ footer && /* @__PURE__ */ jsx("div", { className: cn(
13416
+ "flex justify-end gap-ds-2 p-ds-4",
13417
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
13418
+ "bg-[var(--ds-bg-muted)]",
13419
+ "rounded-b-[var(--ds-radius-xl)]"
13420
+ ), children: footer })
13421
+ ]
13422
+ }
13423
+ )
13424
+ ]
13425
+ }
13426
+ )
13427
+ ]
13428
+ }
13429
+ ),
13430
+ container ?? document.body
13431
+ );
13432
+ }
12911
13433
  function HamburgerButton({ onClick, open, className: cls }) {
12912
13434
  return /* @__PURE__ */ jsx(
12913
13435
  "button",
@@ -12963,9 +13485,11 @@ var Navbar = forwardRef(function Navbar2({
12963
13485
  const [visibleCount, setVisibleCount] = useState(null);
12964
13486
  const links = useMemo(() => flattenLinks(navLinks), [navLinks]);
12965
13487
  const mobileHidesLinks = !!appShell?.isMobile && (appShell.hasMobileMenu || appShell.hasMobileNav);
13488
+ const focus = appShell?.focus ?? null;
13489
+ const [confirmExit, setConfirmExit] = useState(false);
12966
13490
  useEffect(() => {
12967
13491
  const header = headerRef.current;
12968
- if (!header || mobileHidesLinks || links.length === 0 || typeof ResizeObserver === "undefined") return;
13492
+ if (!header || mobileHidesLinks || focus || links.length === 0 || typeof ResizeObserver === "undefined") return;
12969
13493
  const measure = () => {
12970
13494
  const nav = navRef.current;
12971
13495
  if (!nav) return;
@@ -13002,7 +13526,7 @@ var Navbar = forwardRef(function Navbar2({
13002
13526
  const ro = new ResizeObserver(measure);
13003
13527
  ro.observe(header);
13004
13528
  return () => ro.disconnect();
13005
- }, [mobileHidesLinks, links]);
13529
+ }, [mobileHidesLinks, focus, links]);
13006
13530
  const [topInset, setTopInset] = useState(null);
13007
13531
  const shellEl = appShell?.container ?? null;
13008
13532
  useLayoutEffect(() => {
@@ -13034,7 +13558,40 @@ var Navbar = forwardRef(function Navbar2({
13034
13558
  }, [visibleCount, links]);
13035
13559
  const showHamburger = appShell?.isMobile && appShell.hasMobileMenu && !(isSmallPhone && appShell.hasMobileNav) || !!onMenuClick;
13036
13560
  const handleHamburger = () => onMenuClick ? onMenuClick() : appShell?.setSidebarOpen(!appShell.sidebarOpen);
13037
- const hideNavLinks = mobileHidesLinks;
13561
+ const hideNavLinks = mobileHidesLinks || !!focus;
13562
+ const shownSearch = focus ? null : search;
13563
+ const requestExit = () => {
13564
+ if (!focus) return;
13565
+ if (focus.kind === "in-app" && appShell?.hasUnsavedChanges) {
13566
+ setConfirmExit(true);
13567
+ return;
13568
+ }
13569
+ focus.onExit?.();
13570
+ };
13571
+ 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;
13572
+ const shownUserMenu = focus?.kind === "before-app" ? null : userMenu;
13573
+ const asking = confirmExit && focus?.kind === "in-app" && !!appShell?.hasUnsavedChanges;
13574
+ if (confirmExit && !asking) setConfirmExit(false);
13575
+ const exitDialog = focus?.kind === "in-app" ? /* @__PURE__ */ jsx(
13576
+ Dialog,
13577
+ {
13578
+ open: asking,
13579
+ onClose: () => setConfirmExit(false),
13580
+ title: "You have unsaved changes",
13581
+ description: "Exit now and your changes are lost.",
13582
+ variant: "destructive",
13583
+ size: "sm",
13584
+ alert: true,
13585
+ container: shellEl ?? void 0,
13586
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
13587
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setConfirmExit(false), children: "Keep editing" }),
13588
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => {
13589
+ setConfirmExit(false);
13590
+ focus.onExit?.();
13591
+ }, children: "Discard changes" })
13592
+ ] })
13593
+ }
13594
+ ) : null;
13038
13595
  const type = resolveNavItemType(typeProp);
13039
13596
  const isMinimal = type === "minimal";
13040
13597
  const isFloating = position === "floating";
@@ -13100,9 +13657,10 @@ var Navbar = forwardRef(function Navbar2({
13100
13657
  ] }),
13101
13658
  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
13659
  /* @__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 }),
13660
+ shownSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: shownSearch }),
13104
13661
  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 })
13662
+ exitControl && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: exitControl }),
13663
+ shownUserMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: shownUserMenu })
13106
13664
  ] })
13107
13665
  ]
13108
13666
  }
@@ -13127,15 +13685,16 @@ var Navbar = forwardRef(function Navbar2({
13127
13685
  align === "right" && /* @__PURE__ */ jsx("div", { "data-navbar-spacer": "", className: "flex-1" }),
13128
13686
  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
13687
  (!navLinks || hideNavLinks) && /* @__PURE__ */ jsx("div", { "data-navbar-spacer": "", className: "flex-1" }),
13130
- search && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: search }),
13688
+ shownSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: shownSearch }),
13131
13689
  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 })
13690
+ exitControl && /* @__PURE__ */ jsx("div", { className: "flex items-center shrink-0", children: exitControl }),
13691
+ shownUserMenu && /* @__PURE__ */ jsx("div", { className: cn("flex items-center", resolvedSize === "sm" ? "min-w-0 max-w-[180px]" : "shrink-0"), children: shownUserMenu })
13133
13692
  ]
13134
13693
  }
13135
13694
  );
13136
13695
  }
13137
13696
  if (isFloating) {
13138
- return /* @__PURE__ */ jsx(
13697
+ return /* @__PURE__ */ jsxs(
13139
13698
  "div",
13140
13699
  {
13141
13700
  ref,
@@ -13145,11 +13704,17 @@ var Navbar = forwardRef(function Navbar2({
13145
13704
  className
13146
13705
  ),
13147
13706
  style: { padding: "var(--ds-space-3)" },
13148
- children: headerEl
13707
+ children: [
13708
+ headerEl,
13709
+ exitDialog
13710
+ ]
13149
13711
  }
13150
13712
  );
13151
13713
  }
13152
- return headerEl;
13714
+ return exitDialog ? /* @__PURE__ */ jsxs(Fragment, { children: [
13715
+ headerEl,
13716
+ exitDialog
13717
+ ] }) : headerEl;
13153
13718
  });
13154
13719
  function MobileHeaderMenu({ items, type = "tint", onItemClick, className }) {
13155
13720
  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 +13806,6 @@ function MobileMenuItem({
13241
13806
  }
13242
13807
  );
13243
13808
  }
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
13809
  function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a command\u2026", loading = false, container }) {
13411
13810
  const [query, setQuery] = useState("");
13412
13811
  const [selectedIdx, setSelectedIdx] = useState(0);
@@ -13443,14 +13842,17 @@ function CommandPalette({ open, onOpenChange, commands, placeholder = "Type a co
13443
13842
  const isTopLayer = useOverlayLayer(open);
13444
13843
  useFocusTrap(dialogRef, open, isTopLayer);
13445
13844
  const returnFocus = useReturnFocus(open, dialogRef);
13845
+ const onOpenChangeRef = useLatest(onOpenChange);
13446
13846
  useEffect(() => {
13447
13847
  if (!open) return;
13448
13848
  const handler = (e) => {
13449
- if (e.key === "Escape" && isTopLayer()) onOpenChange(false);
13849
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
13850
+ e.preventDefault();
13851
+ onOpenChangeRef.current(false);
13450
13852
  };
13451
13853
  document.addEventListener("keydown", handler);
13452
13854
  return () => document.removeEventListener("keydown", handler);
13453
- }, [open, onOpenChange, isTopLayer]);
13855
+ }, [open, onOpenChangeRef, isTopLayer]);
13454
13856
  const handleSelect = useCallback(
13455
13857
  (cmd) => {
13456
13858
  returnFocus();
@@ -13664,7 +14066,9 @@ function ContextMenu({ items, children, className, ref, container }) {
13664
14066
  };
13665
14067
  const keyHandler = (e) => {
13666
14068
  if (e.key === "Escape") {
13667
- if (isTopLayer()) setOpen(false);
14069
+ if (e.defaultPrevented || !isTopLayer()) return;
14070
+ e.preventDefault();
14071
+ setOpen(false);
13668
14072
  return;
13669
14073
  }
13670
14074
  const actionable = items.filter((it) => !it.separator && !it.disabled);
@@ -13801,10 +14205,11 @@ function NotificationCenter({
13801
14205
  const [internalFilter, setInternalFilter] = useState("all");
13802
14206
  const isControlled = controlledOpen !== void 0;
13803
14207
  const open = isControlled ? controlledOpen : internalOpen;
14208
+ const onOpenChangeRef = useLatest(onOpenChange);
13804
14209
  const setOpen = useCallback((v) => {
13805
- if (isControlled) onOpenChange?.(v);
14210
+ if (isControlled) onOpenChangeRef.current?.(v);
13806
14211
  else setInternalOpen(v);
13807
- }, [isControlled, onOpenChange]);
14212
+ }, [isControlled, onOpenChangeRef]);
13808
14213
  const triggerRef = useRef(null);
13809
14214
  const panelRef = useRef(null);
13810
14215
  const pos = usePosition(triggerRef, panelRef, open, { side: "bottom", align: "end", offset: 8, flip: true });
@@ -13821,10 +14226,10 @@ function NotificationCenter({
13821
14226
  useEffect(() => {
13822
14227
  if (!open) return;
13823
14228
  const onKey = (e) => {
13824
- if (e.key === "Escape" && isTopLayer()) {
13825
- returnFocus();
13826
- setOpen(false);
13827
- }
14229
+ if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
14230
+ e.preventDefault();
14231
+ returnFocus();
14232
+ setOpen(false);
13828
14233
  };
13829
14234
  const onClick = (e) => {
13830
14235
  const target = e.target;
@@ -13946,6 +14351,8 @@ function NotificationCenter({
13946
14351
  ref: triggerRef,
13947
14352
  onClick: () => setOpen(!open),
13948
14353
  "aria-label": `Notifications${unreadCount ? ` (${unreadCount} unread)` : ""}`,
14354
+ "aria-haspopup": "dialog",
14355
+ "aria-expanded": open,
13949
14356
  className: cn(
13950
14357
  "relative flex items-center justify-center w-9 h-9",
13951
14358
  "rounded-[var(--ds-radius-md)]",
@@ -13964,6 +14371,8 @@ function NotificationCenter({
13964
14371
  "div",
13965
14372
  {
13966
14373
  ref: panelRef,
14374
+ role: "dialog",
14375
+ "aria-label": "Notifications",
13967
14376
  className: cn(
13968
14377
  "fixed z-[var(--ds-z-popover)] w-[360px] max-h-[480px] flex flex-col",
13969
14378
  "bg-[var(--ds-bg-surface-elevated)]",
@@ -14119,17 +14528,21 @@ function Tour({ steps, open, onClose, onComplete, container }) {
14119
14528
  const raf = requestAnimationFrame(() => nextRef.current?.focus());
14120
14529
  return () => cancelAnimationFrame(raf);
14121
14530
  }, [open, current]);
14531
+ const onCloseRef = useLatest(onClose);
14532
+ const handleNextRef = useLatest(handleNext);
14533
+ const handlePrevRef = useLatest(handlePrev);
14122
14534
  useEffect(() => {
14123
14535
  if (!open) return;
14124
14536
  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();
14537
+ if (e.defaultPrevented || !isTopLayer()) return;
14538
+ const act = e.key === "Escape" ? onCloseRef.current : e.key === "ArrowRight" ? handleNextRef.current : e.key === "ArrowLeft" ? handlePrevRef.current : null;
14539
+ if (!act) return;
14540
+ e.preventDefault();
14541
+ act();
14129
14542
  };
14130
14543
  document.addEventListener("keydown", onKey);
14131
14544
  return () => document.removeEventListener("keydown", onKey);
14132
- }, [open, onClose, handleNext, handlePrev, isTopLayer]);
14545
+ }, [open, isTopLayer, onCloseRef, handleNextRef, handlePrevRef]);
14133
14546
  if (!open || !step) return null;
14134
14547
  const padding = 8;
14135
14548
  const spotlightRadius = 8;
@@ -14593,7 +15006,7 @@ function useDestructiveAction(options) {
14593
15006
  }
14594
15007
  setOpen(true);
14595
15008
  };
14596
- const matches = !typedRequired || typed.trim().toLowerCase() === typedText.trim().toLowerCase();
15009
+ const matches2 = !typedRequired || typed.trim().toLowerCase() === typedText.trim().toLowerCase();
14597
15010
  const element = open ? /* @__PURE__ */ jsx(
14598
15011
  Dialog,
14599
15012
  {
@@ -14604,7 +15017,7 @@ function useDestructiveAction(options) {
14604
15017
  variant: "destructive",
14605
15018
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
14606
15019
  /* @__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 })
15020
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", onClick: () => void confirm(), disabled: !matches2 || busy, loading: busy, children: label })
14608
15021
  ] }),
14609
15022
  children: typedRequired && /* @__PURE__ */ jsx(Field, { label: /* @__PURE__ */ jsxs(Fragment, { children: [
14610
15023
  "Type ",