gridsmith-ui 0.23.0 → 0.24.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 (38) hide show
  1. package/dist/Breadcrumbs-CGS8GKl-.d.ts +33 -0
  2. package/dist/chat.js +1 -1
  3. package/dist/{chunk-CKWITUSH.js → chunk-2PWGJ4PK.js} +10 -5
  4. package/dist/chunk-2PWGJ4PK.js.map +1 -0
  5. package/dist/chunk-2YSEME33.js +118 -0
  6. package/dist/chunk-2YSEME33.js.map +1 -0
  7. package/dist/{chunk-ICNWPNMM.js → chunk-6XAU44U4.js} +4 -3
  8. package/dist/chunk-6XAU44U4.js.map +1 -0
  9. package/dist/{chunk-RT7RYGC3.js → chunk-JXANCXK6.js} +4 -39
  10. package/dist/chunk-JXANCXK6.js.map +1 -0
  11. package/dist/{chunk-6PL7FXYH.js → chunk-MV7JXWXK.js} +318 -9
  12. package/dist/chunk-MV7JXWXK.js.map +1 -0
  13. package/dist/{chunk-EYMGLK4M.js → chunk-SVP2IG4S.js} +2 -2
  14. package/dist/{chunk-EYMGLK4M.js.map → chunk-SVP2IG4S.js.map} +1 -1
  15. package/dist/chunk-WOVYHFUD.js +50 -0
  16. package/dist/chunk-WOVYHFUD.js.map +1 -0
  17. package/dist/chunk-XVB55M3N.js +42 -0
  18. package/dist/chunk-XVB55M3N.js.map +1 -0
  19. package/dist/data-grid.js +4 -3
  20. package/dist/data-grid.js.map +1 -1
  21. package/dist/editor.js +2 -1
  22. package/dist/editor.js.map +1 -1
  23. package/dist/index.d.ts +199 -38
  24. package/dist/index.js +598 -369
  25. package/dist/index.js.map +1 -1
  26. package/dist/kanban.js +22 -5
  27. package/dist/kanban.js.map +1 -1
  28. package/dist/resizable.d.ts +16 -1
  29. package/dist/resizable.js +87 -20
  30. package/dist/resizable.js.map +1 -1
  31. package/dist/roots.css +5 -0
  32. package/dist/virtualized.js +4 -3
  33. package/dist/virtualized.js.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/chunk-6PL7FXYH.js.map +0 -1
  36. package/dist/chunk-CKWITUSH.js.map +0 -1
  37. package/dist/chunk-ICNWPNMM.js.map +0 -1
  38. package/dist/chunk-RT7RYGC3.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,22 +1,25 @@
1
- import { useAppShell, REACT_VERSION_PROBLEM, useOverlayMark, useDirtyGuard, useScrollbarInset, DrawerFooterCtx, OverlayMarkScope, OverlayCloseCtx, insetLessScrollbar, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, useCompact, useElementWidth, DrawerPanel, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-6PL7FXYH.js';
2
- export { AppShell, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-6PL7FXYH.js';
1
+ import { useAppShell, REACT_VERSION_PROBLEM, Breadcrumbs, useCrumbFollow, crumbGoes, useOverlayMark, useDirtyGuard, useScrollbarInset, DrawerFooterCtx, OverlayMarkScope, OverlayCloseCtx, insetLessScrollbar, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, useCompact, useElementWidth, DrawerPanel, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-MV7JXWXK.js';
2
+ export { AppShell, Breadcrumbs, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-MV7JXWXK.js';
3
3
  import { Skeleton, TITLE_TEXT } from './chunk-NCIZQYXQ.js';
4
4
  export { Skeleton } from './chunk-NCIZQYXQ.js';
5
5
  import { useScrollEdges, EDGE_FADE } from './chunk-3BRQRKDO.js';
6
- import { Link } from './chunk-EYMGLK4M.js';
7
- export { CodeBlock, Divider, Link } from './chunk-EYMGLK4M.js';
6
+ import { Link } from './chunk-SVP2IG4S.js';
7
+ export { CodeBlock, Divider, Link } from './chunk-SVP2IG4S.js';
8
+ import { useFillHeight, FillContext } from './chunk-2YSEME33.js';
8
9
  import { Badge } from './chunk-SBTBMAIU.js';
9
10
  export { Badge } from './chunk-SBTBMAIU.js';
10
11
  import { inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, CardValue } from './chunk-IVCVNAXG.js';
11
- import { useWholeListCheck, pagerPart, Pagination, CHECK_FILL, Dropdown, FilterRowContext, SWITCH_KNOB_ON, SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION, SWITCH_TRACK_ON, SWITCH_TRACK_OFF, useFieldControl, useField, warnOnce, Label, InlineFieldError, AddAnotherRowCtx, Input, Field } from './chunk-ICNWPNMM.js';
12
- export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-ICNWPNMM.js';
12
+ import { useWholeListCheck, pagerPart, Pagination, Input, CHECK_FILL, Dropdown, FilterRowContext, SWITCH_KNOB_ON, SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION, SWITCH_TRACK_ON, SWITCH_TRACK_OFF, useFieldControl, useField, warnOnce, Label, InlineFieldError, AddAnotherRowCtx, Field } from './chunk-6XAU44U4.js';
13
+ export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-6XAU44U4.js';
13
14
  import { Button, ControlSizeContext, triggerType, Spinner, useShellPreferences, preferenceActionKey } from './chunk-7TOJ4G7S.js';
14
15
  export { Button, ButtonSizeContext, ControlSizeContext, PreferencesContext, Spinner, preferenceActionKey, usePreferences } from './chunk-7TOJ4G7S.js';
15
16
  import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
16
17
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-CRQ6KCRD.js';
17
- import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, FormChangeCtx, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, useReportedChange, rovingTabStop, useGridKeyboard, useOptionalFormContext, focusAfterRemoval, FormCtx, useNonModalDialog, useReturnFocus, useCloseOnFocusOut } from './chunk-RT7RYGC3.js';
18
- export { useFormContext, useGridKeyboard } from './chunk-RT7RYGC3.js';
19
- import { CardSurfaceCtx, useScrollsInside, SplitPaneCtx, SurfaceBoundary, useFramesItself, TableCardCtx } from './chunk-CKWITUSH.js';
18
+ import { FormChangeCtx, useReportedChange, useOptionalFormContext, FormCtx } from './chunk-XVB55M3N.js';
19
+ export { useFormContext } from './chunk-XVB55M3N.js';
20
+ import { CardSurfaceCtx, useScrollsInside, SplitPaneCtx, PageActionsCtx, SurfaceBoundary, useFramesItself, TableCardCtx } from './chunk-2PWGJ4PK.js';
21
+ import { FOCUSABLE, usePopupFocus, FIELD, ZOOM_KEYS, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberFocus, focusInitial, OVERLAY_CLOSE_ATTR, rovingTabStop, useGridKeyboard, focusAfterRemoval, useNonModalDialog, useReturnFocus, useCloseOnFocusOut } from './chunk-JXANCXK6.js';
22
+ export { useGridKeyboard } from './chunk-JXANCXK6.js';
20
23
  import { useTextOverflow, Tooltip, usePosition } from './chunk-CXMV7SXJ.js';
21
24
  export { Tooltip, useTextOverflow } from './chunk-CXMV7SXJ.js';
22
25
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
@@ -1882,9 +1885,9 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
1882
1885
  useEffect(() => {
1883
1886
  const owner = inputRef.current?.form;
1884
1887
  if (!owner) return;
1885
- const clear = () => setRejected(null);
1886
- owner.addEventListener("submit", clear);
1887
- return () => owner.removeEventListener("submit", clear);
1888
+ const clear2 = () => setRejected(null);
1889
+ owner.addEventListener("submit", clear2);
1890
+ return () => owner.removeEventListener("submit", clear2);
1888
1891
  }, []);
1889
1892
  const invalid = error || rejected != null || !!waiting;
1890
1893
  const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null, waiting ? waitingId : null].filter(Boolean).join(" ") || void 0;
@@ -3587,7 +3590,7 @@ function focusTarget(el) {
3587
3590
  if (el.tabIndex >= 0 || /^(?:INPUT|SELECT|TEXTAREA|BUTTON)$/.test(el.tagName)) return el;
3588
3591
  return el.querySelector("[aria-checked='true'], [aria-selected='true']:not([tabindex='-1'])") ?? el.querySelector("input, select, textarea, button, [tabindex]:not([tabindex='-1'])") ?? el;
3589
3592
  }
3590
- function FormWrapper({ onSubmit, validationMode: validationModeProp, children, actions: actionsProp, actionsAlign: actionsAlignProp, onCancel, cancelLabel = "Cancel", focusFirstError = true, errorSummary = true, className, ref }) {
3593
+ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, actions: actionsProp, actionsAlign: actionsAlignProp, onCancel, cancelLabel = "Cancel", focusFirstError = true, errorSummary = true, fullWidth = false, className, ref }) {
3591
3594
  const preferences = useShellPreferences();
3592
3595
  const validationMode = validationModeProp ?? preferences.validationTiming ?? "onSubmit";
3593
3596
  const actionsAlign = actionsAlignProp ?? preferences.formActions?.align ?? "end";
@@ -3754,7 +3757,7 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
3754
3757
  f.message
3755
3758
  ] }, f.id || f.label)) }) }) : null;
3756
3759
  const actionsNode = actions && !drawerFooter ? /* @__PURE__ */ jsx("div", { "data-form-actions": "", className: cn("flex flex-wrap items-center gap-ds-2 pt-ds-2", actionsAlign === "end" ? "justify-end" : "justify-start"), children: actions }) : null;
3757
- return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted, register }, children: /* @__PURE__ */ jsx(FormChangeCtx.Provider, { value: reportChange, children: /* @__PURE__ */ jsxs("form", { ref: setRefs, id: drawerFooter ? formId : void 0, onSubmit: handleSubmit, onFocusCapture: userActed, onInputCapture: userActed, className: cn("flex flex-col gap-ds-5 max-w-[var(--ds-form-max,none)]", className), noValidate: true, children: [
3760
+ return /* @__PURE__ */ jsx(FormCtx.Provider, { value: { errors, setError, clearErrors, validationMode, submitted, register }, children: /* @__PURE__ */ jsx(FormChangeCtx.Provider, { value: reportChange, children: /* @__PURE__ */ jsxs("form", { ref: setRefs, id: drawerFooter ? formId : void 0, onSubmit: handleSubmit, onFocusCapture: userActed, onInputCapture: userActed, className: cn("flex flex-col gap-ds-5 max-w-[var(--ds-form-max,calc(var(--ds-bp-md)_-_2*var(--ds-space-6)))]", className), style: fullWidth ? { "--ds-form-max": "none" } : void 0, noValidate: true, children: [
3758
3761
  summaryNode && /* @__PURE__ */ jsx("div", { ref: summaryRef, "data-form-error-summary": "", tabIndex: -1, className: "rounded-[var(--ds-radius-md)] outline-none focus-visible:shadow-[var(--ds-shadow-focus)]", children: summaryNode }),
3759
3762
  children,
3760
3763
  actionsNode
@@ -5353,6 +5356,9 @@ var PINNED_LAYERS = cn(
5353
5356
  );
5354
5357
  var PIN_SHADOW = "oklch(0 0 0 / 0.18)";
5355
5358
  var ROW_CONTROL = `${FOCUSABLE}, label, [role="button"], [role="checkbox"], [role="switch"], [role="menuitem"], [role="link"]`;
5359
+ function clickIsCard(e) {
5360
+ return clickIsRows(e) && !(e.target instanceof Element && e.target.closest("[data-card-detail]"));
5361
+ }
5356
5362
  function clickIsRows(e) {
5357
5363
  const target = e.target;
5358
5364
  if (!(target instanceof Element) || !e.currentTarget.contains(target)) return false;
@@ -5693,9 +5699,16 @@ function Table({
5693
5699
  const expandedKey = expanded.map(String).join("");
5694
5700
  const shownBefore = useRef(/* @__PURE__ */ new Set());
5695
5701
  const pendingScroll = useRef(null);
5702
+ const scrollFrame = useRef(0);
5696
5703
  const scrollToRow = useCallback((key) => {
5697
- const row = rootRef.current?.querySelector(`[data-row-key="${CSS.escape(key)}"], [data-row-index="${CSS.escape(key)}"]`);
5698
- if (row && typeof row.scrollIntoView === "function") row.scrollIntoView({ block: "nearest" });
5704
+ cancelAnimationFrame(scrollFrame.current);
5705
+ scrollFrame.current = requestAnimationFrame(() => {
5706
+ const row = rootRef.current?.querySelector(`[data-row-key="${CSS.escape(key)}"], [data-row-index="${CSS.escape(key)}"]`);
5707
+ if (!row || typeof row.scrollIntoView !== "function") return;
5708
+ const panel = row.nextElementSibling?.querySelector("[data-row-panel]") ? row.nextElementSibling : null;
5709
+ panel?.scrollIntoView({ block: "nearest" });
5710
+ row.scrollIntoView({ block: "nearest" });
5711
+ });
5699
5712
  }, [rootRef]);
5700
5713
  useEffect(() => {
5701
5714
  const now = new Set(expanded.map(String));
@@ -5774,6 +5787,14 @@ function Table({
5774
5787
  drawn.map(({ row, originalIdx }) => {
5775
5788
  const isSelected = selectedSet.has(originalIdx);
5776
5789
  const id = idOf(row, originalIdx);
5790
+ const opens = !!expandable && !!renderExpanded;
5791
+ const isExpanded = opens && expandedSet.has(String(id));
5792
+ const toggleCard = (card) => {
5793
+ const closing = isExpanded;
5794
+ const inDetail = closing && card.querySelector("[data-card-detail]")?.contains(document.activeElement);
5795
+ toggleExpand(id);
5796
+ if (inDetail) card.querySelector("[data-card-toggle]")?.focus();
5797
+ };
5777
5798
  return /* @__PURE__ */ jsxs(
5778
5799
  "div",
5779
5800
  {
@@ -5784,10 +5805,15 @@ function Table({
5784
5805
  "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
5785
5806
  "p-ds-4 space-y-ds-3",
5786
5807
  isSelected && ACTIVE_RING,
5787
- onRowClick && "cursor-pointer hover:bg-[var(--ds-bg-muted)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5808
+ onRowClick && "cursor-pointer hover:bg-[var(--ds-bg-muted)] focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
5809
+ // with no onRowClick a tap on the card opens and closes it, as a click on the row does; the toggle is the
5810
+ // keyboard's way
5811
+ !onRowClick && canOpenByClick && "cursor-pointer"
5788
5812
  ),
5789
5813
  onClick: onRowClick ? (e) => {
5790
- if (clickIsRows(e)) onRowClick(row, originalIdx);
5814
+ if (clickIsCard(e)) onRowClick(row, originalIdx);
5815
+ } : canOpenByClick ? (e) => {
5816
+ if (clickIsCard(e)) toggleCard(e.currentTarget);
5791
5817
  } : void 0,
5792
5818
  tabIndex: onRowClick ? 0 : void 0,
5793
5819
  onKeyDown: onRowClick ? (e) => {
@@ -5804,7 +5830,36 @@ function Table({
5804
5830
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
5805
5831
  /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
5806
5832
  ] }, col.key)),
5807
- expandable && renderExpanded && /* @__PURE__ */ jsx("div", { className: "pt-ds-2 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) })
5833
+ opens && // Alert's disclosure: a button that says whether it is open, at the card's foot, the detail below it, so
5834
+ // the control the user pressed stays where it was. Its name carries the row's (T15).
5835
+ /* @__PURE__ */ jsxs("div", { className: "pt-ds-1", children: [
5836
+ /* @__PURE__ */ jsxs(
5837
+ "button",
5838
+ {
5839
+ type: "button",
5840
+ "data-card-toggle": "",
5841
+ "aria-expanded": isExpanded,
5842
+ ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-card-toggle-${idPart(id)} ${headingId}-card-name-${idPart(id)}` } : {},
5843
+ onClick: (e) => {
5844
+ e.stopPropagation();
5845
+ toggleCard(e.currentTarget.closest("[data-row]"));
5846
+ },
5847
+ className: cn(
5848
+ // at least 24 px tall: the card around it is a target too, so its own box must meet the size (WCAG 2.5.8)
5849
+ "inline-flex min-h-[24px] items-center gap-ds-1 text-xs font-medium cursor-pointer",
5850
+ "transition-colors duration-[var(--ds-transition-speed)]",
5851
+ "text-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-muted)]",
5852
+ "rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5 -mx-ds-1.5",
5853
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
5854
+ ),
5855
+ children: [
5856
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 12, className: cn("transition-transform duration-[var(--ds-transition-speed)]", isExpanded && "rotate-90") }),
5857
+ /* @__PURE__ */ jsx("span", { id: `${headingId}-card-toggle-${idPart(id)}`, children: isExpanded ? "Hide details" : "Show details" })
5858
+ ]
5859
+ }
5860
+ ),
5861
+ isExpanded && /* @__PURE__ */ jsx("div", { "data-card-detail": "", className: "mt-ds-3 pt-ds-3 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) })
5862
+ ] })
5808
5863
  ]
5809
5864
  },
5810
5865
  String(id)
@@ -6070,6 +6125,9 @@ var indicatorInset = {
6070
6125
  md: "4px",
6071
6126
  lg: "4px"
6072
6127
  };
6128
+ function segmentRadius(inset, bordered) {
6129
+ return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? " - var(--ds-border-width)" : ""}))`;
6130
+ }
6073
6131
  function SegmentedControl({
6074
6132
  value,
6075
6133
  onChange: onChangeProp,
@@ -6211,19 +6269,26 @@ function SegmentedControl({
6211
6269
  containerPadding[size],
6212
6270
  containerHeight[size],
6213
6271
  "bg-[var(--ds-bg-muted)]",
6214
- "rounded-[var(--ds-radius-lg)]",
6215
- // Sprint 25.14 A20: the track's edge is a control's, at 3:1
6216
- variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-input-border)]",
6272
+ // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the
6273
+ // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)
6274
+ "rounded-[var(--ds-radius-md)]",
6275
+ // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the
6276
+ // control, and WCAG 1.4.11 asks no 3:1 of it. The selected segment carries the 3:1 edge instead (below)
6277
+ variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6217
6278
  control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
6218
6279
  className
6219
6280
  ),
6281
+ style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
6220
6282
  children: [
6221
6283
  /* @__PURE__ */ jsx(
6222
6284
  "div",
6223
6285
  {
6224
6286
  className: cn(
6225
- "absolute rounded-[var(--ds-radius-md)]",
6287
+ "absolute rounded-[var(--ds-segment-radius)]",
6226
6288
  "bg-[var(--ds-bg-surface)]",
6289
+ // The selected state is what the control shows, so its edge is the one at 3:1 (WCAG 1.4.11), against the
6290
+ // frame's fill and its own: the surface on the muted fill alone measures 1.05–1.46:1 across the presets
6291
+ "border-[length:var(--ds-border-width)] border-[var(--ds-input-border)]",
6227
6292
  variant === "default" && "shadow-[var(--ds-shadow-sm)]",
6228
6293
  "transition-all motion-reduce:transition-none"
6229
6294
  ),
@@ -6254,7 +6319,7 @@ function SegmentedControl({
6254
6319
  className: cn(
6255
6320
  "relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
6256
6321
  fullWidth && "flex-1",
6257
- "rounded-[var(--ds-radius-md)]",
6322
+ "rounded-[var(--ds-segment-radius)]",
6258
6323
  "transition-colors cursor-pointer",
6259
6324
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
6260
6325
  sizes2[size],
@@ -7369,16 +7434,16 @@ function MonthDropdown({
7369
7434
  )
7370
7435
  ] }),
7371
7436
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-ds-1", children: MONTH_NAMES_SHORT.map((name, i) => {
7372
- const isActive = i === currentMonth && browseYear === currentYear;
7437
+ const isActive2 = i === currentMonth && browseYear === currentYear;
7373
7438
  return /* @__PURE__ */ jsx(
7374
7439
  "button",
7375
7440
  {
7376
7441
  className: cn(
7377
7442
  "px-ds-2 py-ds-1.5 text-xs font-medium rounded-[var(--ds-radius-sm)] cursor-pointer transition-colors",
7378
7443
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
7379
- isActive ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)] hover:text-[var(--ds-text-primary)]"
7444
+ isActive2 ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-emphasis)] hover:text-[var(--ds-text-primary)]"
7380
7445
  ),
7381
- "data-current": isActive || void 0,
7446
+ "data-current": isActive2 || void 0,
7382
7447
  onClick: () => {
7383
7448
  returnFocus();
7384
7449
  onSelect(new Date(browseYear, i, 1));
@@ -7966,7 +8031,7 @@ function TableToolbar({
7966
8031
  const iconSize = size === "sm" ? 14 : 16;
7967
8032
  const count = activeFilterCount ?? activeFilters?.length ?? 0;
7968
8033
  const hasChips = !!activeFilters?.length;
7969
- const inPane = useContext(SplitPaneCtx);
8034
+ const inPane = !!useContext(SplitPaneCtx);
7970
8035
  const searchFills = inPane || !primaryFilter && !filters && !actions;
7971
8036
  const hasControls = !!(search || primaryFilter || filters || actions);
7972
8037
  const noun = (n) => n === 1 ? resultNoun[0] : resultNoun[1];
@@ -8285,12 +8350,26 @@ function Kbd({ children, className }) {
8285
8350
  }
8286
8351
  );
8287
8352
  }
8353
+
8354
+ // ../../showcase/src/lib/nested-items.ts
8355
+ function holdsCurrent(items, isCurrent) {
8356
+ return !!items?.some((item) => isCurrent(item) || holdsCurrent(item.children, isCurrent));
8357
+ }
8288
8358
  function TruncLabel2({ children }) {
8289
8359
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
8290
8360
  const label = /* @__PURE__ */ jsx("span", { ref, className: "truncate", children });
8291
8361
  if (!isTruncated) return label;
8292
8362
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: label });
8293
8363
  }
8364
+ function foldersHolding(nodes, selected) {
8365
+ const ids = [];
8366
+ for (const node of nodes) {
8367
+ if (!holdsCurrent(node.children, (n) => selected.has(n.id))) continue;
8368
+ ids.push(node.id, ...foldersHolding(node.children, selected));
8369
+ }
8370
+ return ids;
8371
+ }
8372
+ var inTree = (nodes, id) => holdsCurrent(nodes, (n) => n.id === id);
8294
8373
  function allDescendantIds(node) {
8295
8374
  const ids = [node.id];
8296
8375
  if (node.children) {
@@ -8305,13 +8384,26 @@ function TreeView({
8305
8384
  checkable,
8306
8385
  defaultExpanded = [],
8307
8386
  defaultSelected = [],
8387
+ selected: selectedProp,
8308
8388
  defaultChecked = [],
8309
8389
  onSelect,
8310
8390
  onCheck,
8311
8391
  className
8312
8392
  }) {
8313
- const [expanded, setExpanded] = useState(new Set(defaultExpanded));
8314
- const [selected, setSelected] = useState(new Set(defaultSelected));
8393
+ const [ownSelected, setOwnSelected] = useState(() => new Set(defaultSelected));
8394
+ const selected = useMemo(() => selectedProp ? new Set(selectedProp) : ownSelected, [selectedProp, ownSelected]);
8395
+ const [expanded, setExpanded] = useState(() => /* @__PURE__ */ new Set([...defaultExpanded, ...foldersHolding(nodes, selected)]));
8396
+ const revealed = useRef(null);
8397
+ if (revealed.current === null) revealed.current = new Set([...selected].filter((id) => inTree(nodes, id)));
8398
+ useEffect(() => {
8399
+ const seen = revealed.current;
8400
+ const fresh = [...selected].filter((id) => !seen.has(id) && inTree(nodes, id));
8401
+ if (!fresh.length) return;
8402
+ for (const id of fresh) seen.add(id);
8403
+ const folders = foldersHolding(nodes, new Set(fresh));
8404
+ if (folders.length) setExpanded((prev) => folders.every((id) => prev.has(id)) ? prev : /* @__PURE__ */ new Set([...prev, ...folders]));
8405
+ }, [nodes, selected]);
8406
+ const holding = useMemo(() => new Set(foldersHolding(nodes, selected)), [nodes, selected]);
8315
8407
  const [checked, setChecked] = useState(new Set(defaultChecked));
8316
8408
  const baseId = useId();
8317
8409
  const toggleExpand = useCallback((id) => {
@@ -8325,20 +8417,18 @@ function TreeView({
8325
8417
  const handleSelect = useCallback(
8326
8418
  (id) => {
8327
8419
  if (!selectable) return;
8328
- setSelected((prev) => {
8329
- let next;
8330
- if (multiSelect) {
8331
- next = new Set(prev);
8332
- if (next.has(id)) next.delete(id);
8333
- else next.add(id);
8334
- } else {
8335
- next = prev.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]);
8336
- }
8337
- onSelect?.(Array.from(next));
8338
- return next;
8339
- });
8420
+ let next;
8421
+ if (multiSelect) {
8422
+ next = new Set(selected);
8423
+ if (next.has(id)) next.delete(id);
8424
+ else next.add(id);
8425
+ } else {
8426
+ next = selected.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]);
8427
+ }
8428
+ if (!selectedProp) setOwnSelected(next);
8429
+ onSelect?.(Array.from(next));
8340
8430
  },
8341
- [selectable, multiSelect, onSelect]
8431
+ [selectable, multiSelect, selected, selectedProp, onSelect]
8342
8432
  );
8343
8433
  const handleCheck = useCallback(
8344
8434
  (node) => {
@@ -8423,6 +8513,7 @@ function TreeView({
8423
8513
  baseId,
8424
8514
  expanded,
8425
8515
  selected,
8516
+ holding,
8426
8517
  checked,
8427
8518
  checkable,
8428
8519
  selectable,
@@ -8441,6 +8532,7 @@ function TreeNodeRow({
8441
8532
  baseId,
8442
8533
  expanded,
8443
8534
  selected,
8535
+ holding,
8444
8536
  checkable,
8445
8537
  selectable,
8446
8538
  toggleExpand,
@@ -8452,6 +8544,7 @@ function TreeNodeRow({
8452
8544
  const hasChildren = !!node.children?.length;
8453
8545
  const isExpanded = expanded.has(node.id);
8454
8546
  const isSelected = selected.has(node.id);
8547
+ const holdsSelection = !isSelected && holding.has(node.id);
8455
8548
  const checkState = checkable ? getCheckState(node) : "unchecked";
8456
8549
  return /* @__PURE__ */ jsxs("div", { role: "treeitem", "aria-expanded": hasChildren ? isExpanded : void 0, "aria-selected": selectable ? isSelected : void 0, children: [
8457
8550
  /* @__PURE__ */ jsxs(
@@ -8467,6 +8560,9 @@ function TreeNodeRow({
8467
8560
  // transparent placeholder so content never shifts on selection.
8468
8561
  "border-l-[length:var(--ds-tree-active-rail)]",
8469
8562
  isSelected ? "bg-[var(--ds-tree-active-bg)] text-[var(--ds-tree-active-text)] font-medium border-[var(--ds-accent-primary)]" : "border-transparent text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)] hover:text-[var(--ds-text-primary)]",
8563
+ // A folder holding the selection, as SidebarNav draws a parent of the current page (Sprint 25.16 A18): no
8564
+ // fill, so it reads the plain tokens, never the active set (pill's on-primary text would vanish here).
8565
+ holdsSelection && "font-medium",
8470
8566
  node.disabled && "opacity-disabled pointer-events-none"
8471
8567
  ),
8472
8568
  style: { paddingLeft: `calc(var(--ds-space-3) * ${depth} + var(--ds-space-2))` },
@@ -8493,7 +8589,7 @@ function TreeNodeRow({
8493
8589
  {
8494
8590
  name: "chevron-right",
8495
8591
  size: 14,
8496
- className: cn(isSelected ? "text-[var(--ds-tree-active-muted)]" : "text-[var(--ds-text-muted)]", "transition-transform", isExpanded && "rotate-90")
8592
+ className: cn(isSelected ? "text-[var(--ds-tree-active-muted)]" : holdsSelection ? "text-[var(--ds-accent-primary)]" : "text-[var(--ds-text-muted)]", "transition-transform", isExpanded && "rotate-90")
8497
8593
  }
8498
8594
  )
8499
8595
  }
@@ -8508,7 +8604,7 @@ function TreeNodeRow({
8508
8604
  }
8509
8605
  }
8510
8606
  ),
8511
- node.icon && /* @__PURE__ */ jsx(Icon, { name: node.icon, size: 16, className: cn("shrink-0", isSelected ? "text-[var(--ds-tree-active-text)]" : "text-[var(--ds-text-muted)]") }),
8607
+ node.icon && /* @__PURE__ */ jsx(Icon, { name: node.icon, size: 16, className: cn("shrink-0", isSelected ? "text-[var(--ds-tree-active-text)]" : holdsSelection ? "text-[var(--ds-accent-primary)]" : "text-[var(--ds-text-muted)]") }),
8512
8608
  /* @__PURE__ */ jsx(TruncLabel2, { children: node.label })
8513
8609
  ]
8514
8610
  }
@@ -8521,6 +8617,7 @@ function TreeNodeRow({
8521
8617
  baseId,
8522
8618
  expanded,
8523
8619
  selected,
8620
+ holding,
8524
8621
  checked: /* @__PURE__ */ new Set(),
8525
8622
  checkable,
8526
8623
  selectable,
@@ -9481,7 +9578,7 @@ var StackedList = forwardRef(function StackedList2({
9481
9578
  useScrollsInside();
9482
9579
  const isPhone = useBreakpoint("sm");
9483
9580
  const inCard = useContext(CardSurfaceCtx);
9484
- const inPane = useContext(SplitPaneCtx);
9581
+ const inPane = !!useContext(SplitPaneCtx);
9485
9582
  const variant = variantProp ?? (flush || hover === "title" ? "articles" : "list");
9486
9583
  const legacy = flush !== void 0 || hover !== void 0;
9487
9584
  useEffect(() => {
@@ -10148,6 +10245,8 @@ var TimeViewport = forwardRef(function TimeViewport2({
10148
10245
  className
10149
10246
  }, ref) {
10150
10247
  const scrollerRef = useRef(null);
10248
+ const rootRef = useRef(null);
10249
+ const fill2 = useFillHeight(rootRef);
10151
10250
  const headerRef = useRef(null);
10152
10251
  const rowsRef = useRef(null);
10153
10252
  const [frame, setFrame] = useState({ width: 0, header: 0 });
@@ -10370,23 +10469,26 @@ var TimeViewport = forwardRef(function TimeViewport2({
10370
10469
  };
10371
10470
  const headerNode = header === void 0 ? /* @__PURE__ */ jsx(TimeScaleHeader, {}) : header;
10372
10471
  const hasHeader = headerNode != null || labelHeader != null;
10373
- const bar = toolbar === false ? null : /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 flex-wrap items-center gap-ds-1 px-ds-2 py-ds-1.5 border-b-[length:var(--ds-border-width)]", border), children: [
10374
- onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10375
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10376
- /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "min-w-[4.5rem] text-center text-xs text-[var(--ds-text-secondary)]", children: ZOOM_NAMES[scale.zoom] }),
10377
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10378
- ] }),
10379
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10380
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10381
- if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10382
- }, children: "Today" }),
10383
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10384
- toolbar
10385
- ] });
10472
+ const bar = toolbar === false ? null : (
10473
+ // a wrapped line (a chart's key, or controls on a phone) stands a row gap apart (Sprint 25.16 A43)
10474
+ /* @__PURE__ */ jsxs("div", { className: cn("flex shrink-0 flex-wrap items-center gap-x-ds-1 gap-y-ds-2 px-ds-2 py-ds-1.5 border-b-[length:var(--ds-border-width)]", border), children: [
10475
+ onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10476
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom out", "aria-disabled": !canZoomOut || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(1), children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 14 }) }),
10477
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "min-w-[4.5rem] text-center text-xs text-[var(--ds-text-secondary)]", children: ZOOM_NAMES[scale.zoom] }),
10478
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Zoom in", "aria-disabled": !canZoomIn || void 0, className: "aria-disabled:opacity-disabled aria-disabled:cursor-not-allowed", onClick: () => zoom(-1), children: /* @__PURE__ */ jsx(Icon, { name: "plus", size: 14 }) })
10479
+ ] }),
10480
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Previous period", onClick: () => api.page(-1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-left", size: 14 }) }),
10481
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10482
+ if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10483
+ }, children: "Today" }),
10484
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", "aria-label": "Next period", onClick: () => api.page(1), children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14 }) }),
10485
+ toolbar
10486
+ ] })
10487
+ );
10386
10488
  return (
10387
10489
  // Inline-size containment: the rows' width never widens the frame (in a grid or flex parent it would, and the
10388
10490
  // wider frame would extend the scale again). The viewport takes the width it is given and scrolls inside it.
10389
- /* @__PURE__ */ jsx("div", { "data-time-viewport": "", "data-compact": phone || void 0, className: cn("flex h-full min-h-0 w-full min-w-0 flex-col [contain:inline-size] bg-[var(--ds-bg-surface)]", className), onKeyDown, children: /* @__PURE__ */ jsx(TimeViewportCtx, { value: layout, children: /* @__PURE__ */ jsxs(TimeViewportScrollCtx, { value: store, children: [
10491
+ /* @__PURE__ */ jsx("div", { ref: rootRef, "data-time-viewport": "", "data-compact": phone || void 0, "data-fill": fill2 != null ? "" : void 0, className: cn("flex h-full min-h-0 w-full min-w-0 flex-col [contain:inline-size] bg-[var(--ds-bg-surface)]", className), style: fill2 != null ? { height: fill2 } : void 0, onKeyDown, children: /* @__PURE__ */ jsx(TimeViewportCtx, { value: layout, children: /* @__PURE__ */ jsxs(TimeViewportScrollCtx, { value: store, children: [
10390
10492
  bar,
10391
10493
  /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1", children: [
10392
10494
  /* @__PURE__ */ jsx(
@@ -10662,7 +10764,7 @@ function Cue({ name, size }) {
10662
10764
  function Legend({ statuses }) {
10663
10765
  const entries = Object.entries(statuses);
10664
10766
  if (!entries.length) return null;
10665
- return /* @__PURE__ */ jsx("ul", { "aria-label": "Status key", className: "ms-auto flex flex-wrap items-center gap-x-ds-3 gap-y-ds-1 ps-ds-2 text-xs text-[var(--ds-text-secondary)]", children: entries.map(([key, s]) => {
10767
+ return /* @__PURE__ */ jsx("ul", { "aria-label": "Status key", className: "flex flex-wrap items-center gap-x-ds-3 gap-y-ds-1 text-xs text-[var(--ds-text-secondary)]", children: entries.map(([key, s]) => {
10666
10768
  const tone = TONES[s.tone] ?? TONES.neutral;
10667
10769
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
10668
10770
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("grid size-4 shrink-0 place-items-center rounded-[var(--ds-radius-sm)]", EDGE, tone.fill, tone.on), children: /* @__PURE__ */ jsx(Cue, { name: s.icon ?? tone.icon, size: 10 }) }),
@@ -11004,6 +11106,8 @@ function Gantt({
11004
11106
  };
11005
11107
  const legend = /* @__PURE__ */ jsx(Legend, { statuses });
11006
11108
  const legendApart = toolbar === false && hasStatuses;
11109
+ const apartRef = useRef(null);
11110
+ const apartFill = useFillHeight(apartRef, legendApart);
11007
11111
  const chart = /* @__PURE__ */ jsx(
11008
11112
  TimeViewport,
11009
11113
  {
@@ -11013,6 +11117,7 @@ function Gantt({
11013
11117
  labelHeader,
11014
11118
  toolbar: toolbar === false ? false : /* @__PURE__ */ jsxs(Fragment, { children: [
11015
11119
  toolbar,
11120
+ hasStatuses && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "min-w-0 flex-1" }),
11016
11121
  legend
11017
11122
  ] }),
11018
11123
  tabStop: false,
@@ -11041,9 +11146,9 @@ function Gantt({
11041
11146
  }
11042
11147
  );
11043
11148
  if (!legendApart) return chart;
11044
- return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 w-full min-w-0 flex-col", className), children: [
11045
- /* @__PURE__ */ jsx("div", { className: "flex shrink-0 px-ds-2 py-ds-1.5", children: legend }),
11046
- chart
11149
+ return /* @__PURE__ */ jsxs("div", { ref: apartRef, "data-fill": apartFill != null ? "" : void 0, className: cn("flex h-full min-h-0 w-full min-w-0 flex-col", className), style: apartFill != null ? { height: apartFill } : void 0, children: [
11150
+ /* @__PURE__ */ jsx("div", { className: "flex shrink-0 justify-end px-ds-2 py-ds-1.5", children: legend }),
11151
+ /* @__PURE__ */ jsx(FillContext, { value: true, children: chart })
11047
11152
  ] });
11048
11153
  }
11049
11154
  var DEFAULT_OFFSET2 = "var(--ds-space-4)";
@@ -11469,14 +11574,14 @@ function TabButton({
11469
11574
  hasPanel
11470
11575
  }) {
11471
11576
  const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate && orientation !== "horizontal" });
11472
- const isActive = activeTab === id;
11577
+ const isActive2 = activeTab === id;
11473
11578
  const tabId = `ds-tab-${id}`;
11474
11579
  const panelId = hasPanel === false ? void 0 : `ds-tabpanel-${id}`;
11475
11580
  const badgeBg = orientation === "vertical" ? "bg-[var(--ds-nav-v-badge-bg)]" : "bg-[var(--ds-nav-h-badge-bg)]";
11476
11581
  const countBadge = count !== void 0 ? /* @__PURE__ */ jsx("span", { className: cn(
11477
11582
  orientation === "vertical" ? "ml-ds-2" : "",
11478
11583
  "px-ds-1.5 py-ds-0.5 text-xs rounded-[var(--ds-radius-sm)]",
11479
- isActive ? cn(badgeBg, "text-[var(--ds-text-on-primary)]") : "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-muted)]"
11584
+ isActive2 ? cn(badgeBg, "text-[var(--ds-text-on-primary)]") : "bg-[var(--ds-bg-emphasis)] text-[var(--ds-text-muted)]"
11480
11585
  ), children: count }) : null;
11481
11586
  if (orientation === "vertical") {
11482
11587
  const btn2 = /* @__PURE__ */ jsxs(
@@ -11485,10 +11590,10 @@ function TabButton({
11485
11590
  type: "button",
11486
11591
  id: tabId,
11487
11592
  role: "tab",
11488
- "aria-selected": isActive,
11593
+ "aria-selected": isActive2,
11489
11594
  "aria-controls": panelId,
11490
11595
  "aria-disabled": disabled || void 0,
11491
- tabIndex: isActive ? 0 : -1,
11596
+ tabIndex: isActive2 ? 0 : -1,
11492
11597
  onClick: disabled ? void 0 : () => onTabChange(id),
11493
11598
  onKeyDown,
11494
11599
  className: cn(
@@ -11500,7 +11605,7 @@ function TabButton({
11500
11605
  // Every item carries the rail width as a transparent placeholder so the
11501
11606
  // label does not shift when the rail appears on the active item.
11502
11607
  "border-l-[length:var(--ds-nav-v-active-rail)]",
11503
- isActive ? "bg-[var(--ds-nav-v-active-bg)] text-[var(--ds-nav-v-active-text)] border-[var(--ds-accent-primary)]" : "border-transparent text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]",
11608
+ isActive2 ? "bg-[var(--ds-nav-v-active-bg)] text-[var(--ds-nav-v-active-text)] border-[var(--ds-accent-primary)]" : "border-transparent text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-tab-hover-bg)]",
11504
11609
  className
11505
11610
  ),
11506
11611
  children: [
@@ -11521,10 +11626,10 @@ function TabButton({
11521
11626
  type: "button",
11522
11627
  id: tabId,
11523
11628
  role: "tab",
11524
- "aria-selected": isActive,
11629
+ "aria-selected": isActive2,
11525
11630
  "aria-controls": panelId,
11526
11631
  "aria-disabled": disabled || void 0,
11527
- tabIndex: isActive ? 0 : -1,
11632
+ tabIndex: isActive2 ? 0 : -1,
11528
11633
  onClick: disabled ? void 0 : () => onTabChange(id),
11529
11634
  onKeyDown,
11530
11635
  className: cn(
@@ -11540,10 +11645,10 @@ function TabButton({
11540
11645
  fullWidth && "flex-1",
11541
11646
  truncate && "flex-1 min-w-0 justify-start",
11542
11647
  "rounded-[var(--ds-nav-h-item-radius)]",
11543
- isActive ? "bg-[var(--ds-nav-h-active-bg)] text-[var(--ds-nav-h-active-text)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-nav-h-inactive-text)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-nav-h-hover-bg)]",
11648
+ isActive2 ? "bg-[var(--ds-nav-h-active-bg)] text-[var(--ds-nav-h-active-text)] border-[var(--ds-accent-primary)]" : "text-[var(--ds-nav-h-inactive-text)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-nav-h-hover-bg)]",
11544
11649
  className
11545
11650
  ),
11546
- style: isActive ? { borderBottomWidth: "var(--ds-nav-h-indicator)", marginBottom: "calc(var(--ds-nav-h-track-border) * -1)" } : void 0,
11651
+ style: isActive2 ? { borderBottomWidth: "var(--ds-nav-h-indicator)", marginBottom: "calc(var(--ds-nav-h-track-border) * -1)" } : void 0,
11547
11652
  children: truncate ? /* @__PURE__ */ jsxs(Fragment, { children: [
11548
11653
  icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "shrink-0" }),
11549
11654
  /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate min-w-0 flex-1", children: label }),
@@ -11772,241 +11877,6 @@ function SettingsLayout({
11772
11877
  /* @__PURE__ */ jsx("div", { "data-settings-panel": "", className: cn("flex-1", PANEL_CLASSES), style: PANEL_STYLE, children })
11773
11878
  ] });
11774
11879
  }
11775
- function SeparatorIcon({ type, size }) {
11776
- const iconSize = size === "sm" ? 10 : 12;
11777
- if (type === "chevron") return /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: iconSize });
11778
- if (type === "arrow") return /* @__PURE__ */ jsx(Icon, { name: "arrow-right", size: iconSize });
11779
- if (type === "dot") return /* @__PURE__ */ jsx("span", { children: "\xB7" });
11780
- return /* @__PURE__ */ jsx("span", { children: "/" });
11781
- }
11782
- var Breadcrumbs = forwardRef(function Breadcrumbs2({
11783
- items,
11784
- separator = "chevron",
11785
- variant = "inline",
11786
- itemVariant,
11787
- size = "md",
11788
- showIcons = false,
11789
- maxItems,
11790
- bleed = false,
11791
- contentMaxWidth,
11792
- className,
11793
- container
11794
- }, ref) {
11795
- const [dropdownOpen, setDropdownOpen] = useState(false);
11796
- const [activeIdx, setActiveIdx] = useState(0);
11797
- const ellipsisBtnRef = useRef(null);
11798
- const dropdownRef = useRef(null);
11799
- const closeDropdown = useCallback(() => setDropdownOpen(false), []);
11800
- const { isTop, returnFocus } = usePopupFocus(dropdownOpen, { trigger: ellipsisBtnRef, panel: dropdownRef, close: closeDropdown, tabExits: "any" });
11801
- const pos = usePosition(ellipsisBtnRef, dropdownRef, dropdownOpen, { side: "bottom", align: "start", offset: 4, flip: true });
11802
- const effectiveMaxItems = maxItems ? Math.max(maxItems, 2) : void 0;
11803
- const shouldCollapse = effectiveMaxItems && items.length > effectiveMaxItems;
11804
- const collapsedItems = shouldCollapse ? items.slice(1, -(effectiveMaxItems - 1)) : [];
11805
- const visibleItems = shouldCollapse ? [...items.slice(0, 1), null, ...items.slice(-(effectiveMaxItems - 1))] : items;
11806
- useEffect(() => {
11807
- if (!dropdownOpen) return;
11808
- const handler = (e) => {
11809
- const target = e.target;
11810
- if (ellipsisBtnRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
11811
- setDropdownOpen(false);
11812
- };
11813
- const onKeyDown = (e) => {
11814
- if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
11815
- e.preventDefault();
11816
- returnFocus();
11817
- setDropdownOpen(false);
11818
- };
11819
- document.addEventListener("mousedown", handler);
11820
- document.addEventListener("keydown", onKeyDown);
11821
- return () => {
11822
- document.removeEventListener("mousedown", handler);
11823
- document.removeEventListener("keydown", onKeyDown);
11824
- };
11825
- }, [dropdownOpen, isTop, returnFocus]);
11826
- useEffect(() => {
11827
- if (!dropdownOpen) return;
11828
- const id = requestAnimationFrame(() => dropdownRef.current?.querySelector(`[data-index="${activeIdx}"]`)?.focus());
11829
- return () => cancelAnimationFrame(id);
11830
- }, [dropdownOpen, activeIdx]);
11831
- function onMenuKeyDown(e) {
11832
- const last = collapsedItems.length - 1;
11833
- const next = e.key === "ArrowDown" ? activeIdx >= last ? 0 : activeIdx + 1 : e.key === "ArrowUp" ? activeIdx <= 0 ? last : activeIdx - 1 : e.key === "Home" ? 0 : e.key === "End" ? last : null;
11834
- if (next === null) return;
11835
- e.preventDefault();
11836
- setActiveIdx(next);
11837
- }
11838
- const textClass = size === "sm" ? "text-xs" : "text-sm";
11839
- const gapClass = size === "sm" ? "gap-ds-1" : "gap-ds-1.5";
11840
- const iconSize = size === "sm" ? 12 : 14;
11841
- const isContainedBleed = variant === "contained" && bleed;
11842
- const wrapperClasses = cn(
11843
- variant === "contained" && !bleed && cn(
11844
- "bg-[var(--ds-bg-muted)] rounded-[var(--ds-radius-md)]",
11845
- size === "sm" ? "py-ds-1 px-ds-2" : "py-ds-1.5 px-ds-3"
11846
- ),
11847
- !isContainedBleed && className
11848
- );
11849
- function renderItem(item, _index, isLast) {
11850
- const isPill = itemVariant === "pill" || variant === "pill" && !itemVariant;
11851
- const insideContained = variant === "contained";
11852
- const pillClasses = isPill ? cn(
11853
- "rounded-[var(--ds-radius-full)]",
11854
- insideContained ? "bg-[var(--ds-bg-surface)]" : "bg-[var(--ds-bg-muted)]",
11855
- size === "sm" ? "px-ds-2 py-[2px]" : "px-ds-2.5 py-ds-0.5"
11856
- ) : "";
11857
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [
11858
- showIcons && item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: iconSize, className: "shrink-0" }),
11859
- /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label })
11860
- ] });
11861
- if (isLast) {
11862
- return /* @__PURE__ */ jsx(
11863
- "span",
11864
- {
11865
- "aria-current": "page",
11866
- className: cn(
11867
- "font-medium text-[var(--ds-text-primary)] flex items-center",
11868
- gapClass,
11869
- pillClasses
11870
- ),
11871
- children: content
11872
- }
11873
- );
11874
- }
11875
- return /* @__PURE__ */ jsx(
11876
- "a",
11877
- {
11878
- href: item.href ?? "#",
11879
- onClick: (e) => {
11880
- e.preventDefault();
11881
- item.onClick?.();
11882
- },
11883
- className: cn(
11884
- "text-[var(--ds-text-secondary)] flex items-center",
11885
- "hover:text-[var(--ds-text-primary)]",
11886
- "transition-colors duration-[var(--ds-transition-speed)]",
11887
- gapClass,
11888
- pillClasses
11889
- ),
11890
- children: content
11891
- }
11892
- );
11893
- }
11894
- const listContent = /* @__PURE__ */ jsx("ol", { className: cn("flex items-center flex-wrap", gapClass, textClass), children: visibleItems.map((item, i) => {
11895
- if (item === null) {
11896
- return /* @__PURE__ */ jsxs("li", { className: cn("flex items-center", gapClass), children: [
11897
- i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[var(--ds-text-secondary)] select-none flex items-center", children: /* @__PURE__ */ jsx(SeparatorIcon, { type: separator, size }) }),
11898
- /* @__PURE__ */ jsx(
11899
- "button",
11900
- {
11901
- type: "button",
11902
- ref: ellipsisBtnRef,
11903
- onClick: () => {
11904
- if (!dropdownOpen) setActiveIdx(0);
11905
- setDropdownOpen(!dropdownOpen);
11906
- },
11907
- "aria-label": "Show hidden breadcrumbs",
11908
- "aria-expanded": dropdownOpen,
11909
- "aria-haspopup": "menu",
11910
- className: cn(
11911
- "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]",
11912
- "transition-colors duration-[var(--ds-transition-speed)] cursor-pointer",
11913
- "flex items-center",
11914
- (itemVariant === "pill" || variant === "pill" && !itemVariant) && cn(
11915
- "rounded-[var(--ds-radius-full)]",
11916
- variant === "contained" ? "bg-[var(--ds-bg-surface)]" : "bg-[var(--ds-bg-muted)]",
11917
- size === "sm" ? "px-ds-2 py-[2px]" : "px-ds-2.5 py-ds-0.5"
11918
- )
11919
- ),
11920
- children: "\xB7\xB7\xB7"
11921
- }
11922
- )
11923
- ] }, "__ellipsis");
11924
- }
11925
- const isLast = i === visibleItems.length - 1;
11926
- return /* @__PURE__ */ jsxs("li", { className: cn("flex items-center", gapClass), children: [
11927
- i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[var(--ds-text-secondary)] select-none flex items-center", children: /* @__PURE__ */ jsx(SeparatorIcon, { type: separator, size }) }),
11928
- renderItem(item, i, isLast)
11929
- ] }, i);
11930
- }) });
11931
- const dropdown = dropdownOpen && collapsedItems.length > 0 && createPortal(
11932
- /* @__PURE__ */ jsx(
11933
- "div",
11934
- {
11935
- ref: dropdownRef,
11936
- role: "menu",
11937
- "aria-label": "Hidden breadcrumbs",
11938
- onKeyDown: onMenuKeyDown,
11939
- className: cn(
11940
- "fixed z-[var(--ds-z-popover)] min-w-[160px] max-h-60 overflow-auto",
11941
- "bg-[var(--ds-bg-surface-elevated)]",
11942
- "[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]",
11943
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
11944
- "rounded-[var(--ds-radius-md)]",
11945
- "shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]",
11946
- "py-ds-1"
11947
- ),
11948
- style: { top: pos.top, left: pos.left },
11949
- children: collapsedItems.map((ci, idx) => /* @__PURE__ */ jsxs(
11950
- "button",
11951
- {
11952
- type: "button",
11953
- "data-index": idx,
11954
- role: "menuitem",
11955
- tabIndex: idx === activeIdx ? 0 : -1,
11956
- className: cn(
11957
- "flex items-center w-full text-left text-sm cursor-pointer",
11958
- "px-ds-3 py-ds-1.5 transition-colors",
11959
- "text-[var(--ds-text-secondary)]",
11960
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
11961
- "focus-visible:outline-none focus-visible:bg-[var(--ds-bg-muted)] focus-visible:text-[var(--ds-text-primary)] focus-visible:shadow-[inset_var(--ds-shadow-focus)]",
11962
- gapClass
11963
- ),
11964
- onClick: () => {
11965
- setDropdownOpen(false);
11966
- ci.onClick?.();
11967
- },
11968
- children: [
11969
- showIcons && ci.icon && /* @__PURE__ */ jsx(Icon, { name: ci.icon, size: iconSize, className: "shrink-0" }),
11970
- ci.label
11971
- ]
11972
- },
11973
- idx
11974
- ))
11975
- }
11976
- ),
11977
- container ?? document.body
11978
- );
11979
- if (isContainedBleed) {
11980
- return /* @__PURE__ */ jsxs(
11981
- "nav",
11982
- {
11983
- ref,
11984
- "aria-label": "Breadcrumb",
11985
- className: cn(
11986
- "w-full bg-[var(--ds-bg-muted)]",
11987
- // Hairline under the band: it sits within ~1.2:1 of both canvas and a tinted header, so the edge needs a line.
11988
- "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11989
- className
11990
- ),
11991
- children: [
11992
- /* @__PURE__ */ jsx(
11993
- "div",
11994
- {
11995
- className: cn("w-full mx-auto px-ds-6", size === "sm" ? "py-ds-1" : "py-ds-1.5"),
11996
- style: { maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)" },
11997
- children: listContent
11998
- }
11999
- ),
12000
- dropdown
12001
- ]
12002
- }
12003
- );
12004
- }
12005
- return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": "Breadcrumb", className: wrapperClasses, children: [
12006
- listContent,
12007
- dropdown
12008
- ] });
12009
- });
12010
11880
  var HeaderTabRowContext = createContext(null);
12011
11881
  function useHeaderTabRow() {
12012
11882
  return useContext(HeaderTabRowContext);
@@ -12059,13 +11929,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12059
11929
  const isLast = i === steps.length - 1;
12060
11930
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-start min-w-0", isLast ? "flex-none" : "flex-1"), children: [
12061
11931
  /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
12062
- /* @__PURE__ */ jsx(StepCircle, { status, index: i, onClick: onStepChange ? () => onStepChange(i) : void 0, clickable: !!onStepChange }),
11932
+ /* @__PURE__ */ jsx(StepCircle, { status, index: i, label: step.label, onClick: onStepChange && !step.disabled ? () => onStepChange(i) : void 0, clickable: !!onStepChange && !step.disabled }),
12063
11933
  !isLast && /* @__PURE__ */ jsx(
12064
11934
  "div",
12065
11935
  {
12066
11936
  className: cn(
12067
11937
  "flex-1 h-0.5 mx-ds-2 transition-colors",
12068
- i < currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
11938
+ i < currentStep || steps[i + 1]?.status === "complete" ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
12069
11939
  )
12070
11940
  }
12071
11941
  )
@@ -12092,13 +11962,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12092
11962
  const isLast = i === steps.length - 1;
12093
11963
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-ds-3", children: [
12094
11964
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
12095
- /* @__PURE__ */ jsx(StepCircle, { status, index: i, onClick: onStepChange ? () => onStepChange(i) : void 0, clickable: !!onStepChange }),
11965
+ /* @__PURE__ */ jsx(StepCircle, { status, index: i, label: step.label, onClick: onStepChange && !step.disabled ? () => onStepChange(i) : void 0, clickable: !!onStepChange && !step.disabled }),
12096
11966
  !isLast && /* @__PURE__ */ jsx(
12097
11967
  "div",
12098
11968
  {
12099
11969
  className: cn(
12100
11970
  "w-0.5 flex-1 min-h-6 transition-colors",
12101
- i < currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
11971
+ i < currentStep || steps[i + 1]?.status === "complete" ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
12102
11972
  )
12103
11973
  }
12104
11974
  )
@@ -12139,13 +12009,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12139
12009
  )
12140
12010
  }
12141
12011
  ),
12142
- /* @__PURE__ */ jsx(StepCircle, { status, index: i, onClick: onStepChange ? () => onStepChange(i) : void 0, clickable: !!onStepChange }),
12012
+ /* @__PURE__ */ jsx(StepCircle, { status, index: i, label: step.label, onClick: onStepChange && !step.disabled ? () => onStepChange(i) : void 0, clickable: !!onStepChange && !step.disabled }),
12143
12013
  /* @__PURE__ */ jsx(
12144
12014
  "div",
12145
12015
  {
12146
12016
  className: cn(
12147
12017
  "flex-1 h-0.5 transition-colors",
12148
- isLast ? "bg-transparent" : i < currentStep ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
12018
+ isLast ? "bg-transparent" : i < currentStep || steps[i + 1]?.status === "complete" ? "bg-[var(--ds-accent-primary)]" : "bg-[var(--ds-border-subtle)]"
12149
12019
  )
12150
12020
  }
12151
12021
  )
@@ -12168,13 +12038,14 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12168
12038
  }
12169
12039
  );
12170
12040
  }
12171
- function StepCircle({ status, index, onClick, clickable }) {
12041
+ function StepCircle({ status, index, label, onClick, clickable }) {
12172
12042
  return /* @__PURE__ */ jsx(
12173
12043
  "button",
12174
12044
  {
12175
12045
  type: "button",
12176
12046
  onClick,
12177
12047
  disabled: !clickable,
12048
+ "aria-label": `Step ${index + 1}: ${label}${status === "complete" ? ", completed" : ""}`,
12178
12049
  "aria-current": status === "current" ? "step" : void 0,
12179
12050
  className: cn(
12180
12051
  "flex items-center justify-center shrink-0 w-8 h-8 rounded-[var(--ds-radius-full)] text-xs font-bold transition-colors",
@@ -12185,7 +12056,9 @@ function StepCircle({ status, index, onClick, clickable }) {
12185
12056
  status === "current" && `border-2 ${ACTIVE_RING} text-[var(--ds-accent-primary)] bg-[var(--ds-bg-surface)]`,
12186
12057
  status === "error" && "bg-[var(--ds-status-error)] text-[var(--ds-text-on-error)]",
12187
12058
  status === "loading" && `border-2 ${ACTIVE_RING} text-[var(--ds-accent-primary)] bg-[var(--ds-bg-surface)] animate-pulse`,
12188
- status === "upcoming" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)] text-[var(--ds-text-muted)] bg-[var(--ds-bg-surface)]"
12059
+ status === "upcoming" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)] text-[var(--ds-text-muted)] bg-[var(--ds-bg-surface)]",
12060
+ // a step reached but not done (the user went back) can be gone to: it answers the pointer as the done ones do
12061
+ status === "upcoming" && clickable && "hover:border-[var(--ds-accent-primary)] hover:text-[var(--ds-accent-primary)]"
12189
12062
  ),
12190
12063
  children: status === "complete" ? /* @__PURE__ */ jsx(Icon, { name: "check", size: 14 }) : status === "error" ? /* @__PURE__ */ jsx(Icon, { name: "warning", size: 14 }) : index + 1
12191
12064
  }
@@ -12517,17 +12390,13 @@ function TruncLabel3({ children, className, side = "right", container, lines = 1
12517
12390
  if (!isTruncated) return label;
12518
12391
  return /* @__PURE__ */ jsx(Tooltip, { content: children, side, delay: 200, container, children: label });
12519
12392
  }
12520
- function hasActiveDescendant(items) {
12521
- return items.some(
12522
- (it) => it.active || it.children && hasActiveDescendant(it.children)
12523
- );
12524
- }
12393
+ var isActive = (item) => !!item.active;
12525
12394
  function collectInitialExpanded(items, prefix = "") {
12526
12395
  const keys = [];
12527
12396
  for (const item of items) {
12528
12397
  if (!item.children?.length) continue;
12529
12398
  const key = prefix ? `${prefix}/${item.label}` : item.label;
12530
- if (item.defaultExpanded || hasActiveDescendant(item.children)) {
12399
+ if (item.defaultExpanded || holdsCurrent(item.children, isActive)) {
12531
12400
  keys.push(key);
12532
12401
  }
12533
12402
  keys.push(...collectInitialExpanded(item.children, key));
@@ -12749,7 +12618,7 @@ function SidebarNav({
12749
12618
  function renderCompactItem(item, index, parentKey) {
12750
12619
  const hasChildren = !!item.children?.length;
12751
12620
  const key = makeKey(item.label, parentKey);
12752
- const childActive = hasChildren && hasActiveDescendant(item.children);
12621
+ const childActive = hasChildren && holdsCurrent(item.children, isActive);
12753
12622
  const isOpen = openFlyout === key;
12754
12623
  return /* @__PURE__ */ jsx(
12755
12624
  CompactNavItem,
@@ -12783,7 +12652,7 @@ function SidebarNav({
12783
12652
  return renderLeaf(item, depth, index);
12784
12653
  }
12785
12654
  const isExpanded = expandedItems.has(key);
12786
- const childActive = hasActiveDescendant(item.children);
12655
+ const childActive = holdsCurrent(item.children, isActive);
12787
12656
  const isRoot = depth === 0;
12788
12657
  const rootPy = isSm ? "py-ds-1.5" : "py-ds-2";
12789
12658
  const childPy = isSm ? "py-ds-1" : "py-ds-1.5";
@@ -13353,7 +13222,7 @@ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", du
13353
13222
  const noteShown = useCallback((id, variant, action, fresh) => {
13354
13223
  if (runs.current.size) shownDuringRuns.current.push({ seq: ++showSeq.current, at: Date.now(), id, variant, action, fresh });
13355
13224
  }, []);
13356
- const update = useCallback((id, message, options = {}) => {
13225
+ const update2 = useCallback((id, message, options = {}) => {
13357
13226
  const list2 = entries.current;
13358
13227
  const at = list2.findIndex((t2) => t2.id === id);
13359
13228
  if (at === -1) return;
@@ -13403,21 +13272,21 @@ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", du
13403
13272
  success: (m, o) => toast(m, { ...o, variant: "success" }),
13404
13273
  warning: (m, o) => toast(m, { ...o, variant: "warning" }),
13405
13274
  error: (m, o) => toast(m, { ...o, variant: "error" }),
13406
- update,
13275
+ update: update2,
13407
13276
  dismiss: remove,
13408
13277
  dismissAll,
13409
13278
  promise: async (promise, messages, options = {}) => {
13410
13279
  const id = toast(messages.loading, { ...options, variant: "default", duration: Infinity });
13411
13280
  try {
13412
13281
  const value = await promise;
13413
- update(id, typeof messages.success === "function" ? messages.success(value) : messages.success, { variant: "success", duration });
13282
+ update2(id, typeof messages.success === "function" ? messages.success(value) : messages.success, { variant: "success", duration });
13414
13283
  return value;
13415
13284
  } catch (err) {
13416
- update(id, typeof messages.error === "function" ? messages.error(err) : messages.error, { variant: "error", duration });
13285
+ update2(id, typeof messages.error === "function" ? messages.error(err) : messages.error, { variant: "error", duration });
13417
13286
  throw err;
13418
13287
  }
13419
13288
  }
13420
- }), [toast, update, remove, dismissAll, duration]);
13289
+ }), [toast, update2, remove, dismissAll, duration]);
13421
13290
  const ledger = useMemo(() => ({
13422
13291
  begin: () => {
13423
13292
  const run = ++runSeq.current;
@@ -13749,15 +13618,16 @@ var paddingClasses = {
13749
13618
  default: "px-[var(--ds-page-gutter)]",
13750
13619
  wide: "px-ds-10"
13751
13620
  };
13621
+ var FILLED_BOTTOM = "has-[[data-fill]]:pb-[var(--ds-page-gutter)]";
13752
13622
  var verticalPaddingClasses = {
13753
13623
  none: "",
13754
- default: "pt-ds-8 pb-ds-12",
13755
- spacious: "pt-ds-8 pb-ds-16"
13624
+ default: `pt-ds-8 pb-ds-12 ${FILLED_BOTTOM}`,
13625
+ spacious: `pt-ds-8 pb-ds-16 ${FILLED_BOTTOM}`
13756
13626
  };
13757
13627
  var bottomPaddingClasses = {
13758
13628
  none: "",
13759
- default: "pb-ds-12",
13760
- spacious: "pb-ds-16"
13629
+ default: `pb-ds-12 ${FILLED_BOTTOM}`,
13630
+ spacious: `pb-ds-16 ${FILLED_BOTTOM}`
13761
13631
  };
13762
13632
  var bottomPaddingValues = {
13763
13633
  none: "0px",
@@ -13780,6 +13650,38 @@ function useContentWidth(width) {
13780
13650
  return () => request(null);
13781
13651
  }, [request, width]);
13782
13652
  }
13653
+ function PageActions({ children }) {
13654
+ const slot = useContext(PageActionsCtx);
13655
+ const add = slot?.add;
13656
+ useEffect(() => add?.(), [add]);
13657
+ const target = slot?.target;
13658
+ return target ? createPortal(/* @__PURE__ */ jsx(ControlSizeContext, { value: target.size, children }), target.el) : null;
13659
+ }
13660
+ var pageOrder = (a, b) => a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
13661
+ function usePageActionsSlot() {
13662
+ const outer = useContext(PageActionsCtx);
13663
+ const [asking, setAsking] = useState(0);
13664
+ const [slots, setSlots] = useState([]);
13665
+ const register = useCallback((el, size) => {
13666
+ setSlots((list) => [...list.filter((s) => s.el !== el), { el, size }]);
13667
+ return () => setSlots((list) => list.filter((s) => s.el !== el));
13668
+ }, []);
13669
+ const outerAdd = outer?.add;
13670
+ const add = useCallback(() => {
13671
+ setAsking((n) => n + 1);
13672
+ const releaseOuter = outerAdd?.();
13673
+ let released = false;
13674
+ return () => {
13675
+ if (released) return;
13676
+ released = true;
13677
+ setAsking((n) => n - 1);
13678
+ releaseOuter?.();
13679
+ };
13680
+ }, [outerAdd]);
13681
+ const own = useMemo(() => [...slots].sort(pageOrder)[0] ?? null, [slots]);
13682
+ const target = own ?? outer?.target ?? null;
13683
+ return useMemo(() => ({ wanted: asking > 0, target, register, add }), [asking, target, register, add]);
13684
+ }
13783
13685
  function usePageContainer() {
13784
13686
  return useContext(PageContainerContext);
13785
13687
  }
@@ -13856,6 +13758,7 @@ var PageContainer = forwardRef(function PageContainer2({
13856
13758
  headerCount.current = Math.max(0, headerCount.current + (present ? 1 : -1));
13857
13759
  setWrappedHeader(headerCount.current > 0);
13858
13760
  }, []);
13761
+ const pageActions = usePageActionsSlot();
13859
13762
  const launcherCorner = shell?.launcherCorner ?? null;
13860
13763
  const vPad = verticalPadding ?? "default";
13861
13764
  const launcherPadding = launcherCorner ? { paddingBottom: `max(${bottomPaddingValues[vPad]}, var(--ds-toast-inset-${launcherCorner}, 0px))` } : {};
@@ -13901,7 +13804,7 @@ var PageContainer = forwardRef(function PageContainer2({
13901
13804
  observedRef.current = null;
13902
13805
  }, []);
13903
13806
  if (headerIdx === -1 || !headerEl || !r) {
13904
- return /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement: "inside", containedBreadcrumbs: false, breadcrumbsInsideHeader: false, noBottomMargin: false, overlap: false, setPinnedHeight, reportHeader, requestContentWidth, insidePaddedColumn: true }, children: /* @__PURE__ */ jsx(
13807
+ return /* @__PURE__ */ jsx(PageActionsCtx, { value: pageActions, children: /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement: "inside", containedBreadcrumbs: false, breadcrumbsInsideHeader: false, noBottomMargin: false, overlap: false, setPinnedHeight, reportHeader, requestContentWidth, insidePaddedColumn: true }, children: /* @__PURE__ */ jsx(
13905
13808
  "div",
13906
13809
  {
13907
13810
  ref: setRoot,
@@ -13917,7 +13820,7 @@ var PageContainer = forwardRef(function PageContainer2({
13917
13820
  style: { maxWidth: maxWidthValue, containerType: "inline-size", ...launcherPadding },
13918
13821
  children: capped(children)
13919
13822
  }
13920
- ) });
13823
+ ) }) });
13921
13824
  }
13922
13825
  const rest = kids.filter((_, i) => i !== headerIdx);
13923
13826
  const snapping = r.behavior === "snapping";
@@ -13938,7 +13841,7 @@ var PageContainer = forwardRef(function PageContainer2({
13938
13841
  ...overlapActive ? { marginTop: "calc(-1 * var(--ds-overlap, 0px))", position: "relative", zIndex: 1 } : {}
13939
13842
  } : void 0;
13940
13843
  const column = /* @__PURE__ */ jsx(PageContainerContext, { value: { contentMaxWidth: maxWidthValue, placement: "inside", containedBreadcrumbs: false, breadcrumbsInsideHeader: false, noBottomMargin: false, overlap: false, setPinnedHeight, reportHeader, requestContentWidth, insidePaddedColumn: true }, children: /* @__PURE__ */ jsx("div", { ref: columnRef, className: cn(columnClass(contentPadding), bottomPaddingClasses[vPad], className), style: { ...columnStyle, ...launcherPadding }, children: capped(rest) }) });
13941
- return /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-page-container": "", "data-width": dataWidth, className: "w-full", children: [
13844
+ return /* @__PURE__ */ jsx(PageActionsCtx, { value: pageActions, children: /* @__PURE__ */ jsxs("div", { ref: setRoot, "data-page-container": "", "data-width": dataWidth, className: "w-full", children: [
13942
13845
  placement === "block" ? /* @__PURE__ */ jsx("div", { style: floatingBlockPadding(r.navbarFloating, r.sidebarFloating, overlapActive), children: /* @__PURE__ */ jsxs("div", { className: FLOATING_BLOCK, children: [
13943
13846
  strip,
13944
13847
  colourful ? header : /* @__PURE__ */ jsx("div", { className: columnClass(padding), style: columnStyle, children: header })
@@ -13950,7 +13853,7 @@ var PageContainer = forwardRef(function PageContainer2({
13950
13853
  /* @__PURE__ */ jsx("div", { className: columnClass(padding), style: columnStyle, children: header })
13951
13854
  ] }),
13952
13855
  wrapperStyle ? /* @__PURE__ */ jsx("div", { style: wrapperStyle, children: column }) : column
13953
- ] });
13856
+ ] }) });
13954
13857
  });
13955
13858
  function TruncHeading({ as: Tag3 = "h1", className, children, lines: linesProp }) {
13956
13859
  const lines = linesProp ?? (Tag3 === "h1" ? 2 : 1);
@@ -13978,7 +13881,8 @@ function outerHeight(el) {
13978
13881
  const s = getComputedStyle(el);
13979
13882
  return el.getBoundingClientRect().height + (parseFloat(s.marginTop) || 0) + (parseFloat(s.marginBottom) || 0);
13980
13883
  }
13981
- function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
13884
+ function Breadcrumbs2({ items, onLight, onTint, pill, separator = "chevron" }) {
13885
+ const follow = useCrumbFollow();
13982
13886
  if (!items.length) return null;
13983
13887
  const mutedCls = onLight ? "text-[var(--ds-text-on-primary)]/40" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]";
13984
13888
  const sepEl = (key) => {
@@ -13993,17 +13897,15 @@ function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
13993
13897
  onLight ? "bg-[var(--ds-text-on-primary)]/10" : onTint ? "bg-[var(--ds-bg-surface)]/70" : "bg-[var(--ds-bg-muted)]"
13994
13898
  ) : "";
13995
13899
  return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-ds-1.5 text-sm", children: items.map((item, i) => {
13996
- const isNavigable = !!(item.onClick || item.href);
13900
+ const isNavigable = crumbGoes(item);
13901
+ const isCurrent = !isNavigable && i === items.length - 1;
13997
13902
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
13998
13903
  i > 0 && sepEl(i),
13999
13904
  isNavigable ? /* @__PURE__ */ jsx(
14000
13905
  "a",
14001
13906
  {
14002
13907
  href: item.href ?? "#",
14003
- onClick: (e) => {
14004
- e.preventDefault();
14005
- item.onClick?.();
14006
- },
13908
+ onClick: (e) => follow(e, item),
14007
13909
  className: cn(
14008
13910
  "transition-colors duration-[var(--ds-transition-speed)]",
14009
13911
  onLight ? "text-[var(--ds-text-on-primary)]/60 hover:text-[var(--ds-text-on-primary)]/90" : cn(mutedCls, "hover:text-[var(--ds-text-primary)]"),
@@ -14011,9 +13913,8 @@ function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
14011
13913
  ),
14012
13914
  children: item.label
14013
13915
  }
14014
- ) : /* @__PURE__ */ jsx("span", { className: cn(
14015
- "font-medium",
14016
- onLight ? "text-[var(--ds-text-on-primary)]/80" : "text-[var(--ds-text-primary)]",
13916
+ ) : /* @__PURE__ */ jsx("span", { "aria-current": isCurrent ? "page" : void 0, className: cn(
13917
+ isCurrent ? cn("font-medium", onLight ? "text-[var(--ds-text-on-primary)]/80" : "text-[var(--ds-text-primary)]") : onLight ? "text-[var(--ds-text-on-primary)]/60" : mutedCls,
14017
13918
  pillCls
14018
13919
  ), children: item.label })
14019
13920
  ] }, i);
@@ -14028,6 +13929,11 @@ function TintWash() {
14028
13929
  }
14029
13930
  );
14030
13931
  }
13932
+ function PageActionsSlotMark({ register }) {
13933
+ const size = useContext(ControlSizeContext);
13934
+ const ref = useCallback((el) => el ? register(el, size) : void 0, [register, size]);
13935
+ return /* @__PURE__ */ jsx("span", { ref, "data-page-actions": "", className: "contents" });
13936
+ }
14031
13937
  function BackButton({ onLight, onClick }) {
14032
13938
  return /* @__PURE__ */ jsx(
14033
13939
  "button",
@@ -14134,6 +14040,11 @@ var PageHeader = forwardRef(function PageHeader2({
14134
14040
  }, ref) {
14135
14041
  const shell = useAppShell();
14136
14042
  const container = usePageContainer();
14043
+ const pageActions = useContext(PageActionsCtx);
14044
+ const shownActions = pageActions?.wanted ? /* @__PURE__ */ jsxs(Fragment, { children: [
14045
+ actions,
14046
+ /* @__PURE__ */ jsx(PageActionsSlotMark, { register: pageActions.register })
14047
+ ] }) : actions;
14137
14048
  const reportHeader = container?.reportHeader;
14138
14049
  useLayoutEffect(() => {
14139
14050
  if (!reportHeader) return;
@@ -14179,7 +14090,7 @@ var PageHeader = forwardRef(function PageHeader2({
14179
14090
  badge,
14180
14091
  breadcrumbs: inlineBreadcrumbs,
14181
14092
  onBack,
14182
- actions,
14093
+ actions: shownActions,
14183
14094
  avatar,
14184
14095
  filters,
14185
14096
  expandedContent,
@@ -14207,7 +14118,7 @@ var PageHeader = forwardRef(function PageHeader2({
14207
14118
  }
14208
14119
  let header;
14209
14120
  if (variant === "minimal") {
14210
- header = /* @__PURE__ */ jsx(MinimalHeader, { title, description, caption, focusBack, badge, breadcrumbs: inlineBreadcrumbs, actions, tabs, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: floating ? void 0 : ref, isFloating: floating, contentMaxWidth: gutterWidth });
14121
+ header = /* @__PURE__ */ jsx(MinimalHeader, { title, description, caption, focusBack, badge, breadcrumbs: inlineBreadcrumbs, actions: shownActions, tabs, breadcrumbPill: bcPill, breadcrumbSeparator: bcSep, as: Heading, className, headerRef: floating ? void 0 : ref, isFloating: floating, contentMaxWidth: gutterWidth });
14211
14122
  } else if (variant === "tabs-integrated") {
14212
14123
  header = /* @__PURE__ */ jsx(
14213
14124
  TabsIntegratedHeader,
@@ -14219,7 +14130,7 @@ var PageHeader = forwardRef(function PageHeader2({
14219
14130
  badge,
14220
14131
  breadcrumbs: inlineBreadcrumbs,
14221
14132
  onBack,
14222
- actions,
14133
+ actions: shownActions,
14223
14134
  avatar,
14224
14135
  tabs,
14225
14136
  breadcrumbPill: bcPill,
@@ -14244,7 +14155,7 @@ var PageHeader = forwardRef(function PageHeader2({
14244
14155
  badge,
14245
14156
  breadcrumbs: inlineBreadcrumbs,
14246
14157
  onBack,
14247
- actions,
14158
+ actions: shownActions,
14248
14159
  avatar,
14249
14160
  filters,
14250
14161
  intensity,
@@ -14272,7 +14183,7 @@ var PageHeader = forwardRef(function PageHeader2({
14272
14183
  badge,
14273
14184
  breadcrumbs: inlineBreadcrumbs,
14274
14185
  onBack,
14275
- actions,
14186
+ actions: shownActions,
14276
14187
  avatar,
14277
14188
  filters,
14278
14189
  bannerImage,
@@ -14354,7 +14265,7 @@ function DefaultHeader({
14354
14265
  hasBanner && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[var(--ds-bg-canvas)]/85" }),
14355
14266
  /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
14356
14267
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14357
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14268
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14358
14269
  /* @__PURE__ */ jsxs("div", { className: cn(
14359
14270
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14360
14271
  hasBreadcrumbs && "mt-ds-1"
@@ -14401,7 +14312,7 @@ function MinimalHeader({
14401
14312
  const inner = floatingContentProps(isFloating, contentMaxWidth);
14402
14313
  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: [
14403
14314
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14404
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14315
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14405
14316
  caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
14406
14317
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && "mt-ds-0.5"), children: [
14407
14318
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
@@ -14491,7 +14402,7 @@ function ColourfulHeader({
14491
14402
  },
14492
14403
  children: [
14493
14404
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight: isBold }),
14494
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight: isBold, onTint: !isBold, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14405
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, onLight: isBold, onTint: !isBold, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14495
14406
  /* @__PURE__ */ jsxs("div", { className: cn(
14496
14407
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14497
14408
  hasBreadcrumbs && "mt-ds-1"
@@ -14558,6 +14469,7 @@ function SnappingHeader({
14558
14469
  stickyTop = "top-[var(--ds-navbar-height)]"
14559
14470
  }) {
14560
14471
  const [collapsed, setCollapsed] = useState(false);
14472
+ const followCrumb = useCrumbFollow();
14561
14473
  const container = usePageContainer();
14562
14474
  const stickyRef = useRef(null);
14563
14475
  const setStickyRef = useCallback((node) => {
@@ -14692,7 +14604,7 @@ function SnappingHeader({
14692
14604
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
14693
14605
  const innerContent = isMinimal && !collapsed ? /* @__PURE__ */ jsxs("div", { className: cn("py-ds-2", hasBreadcrumbs && !hideExpandedBreadcrumbs && "pt-ds-1", tabs && "pb-ds-4"), children: [
14694
14606
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14695
- hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14607
+ hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14696
14608
  caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
14697
14609
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && !hideExpandedBreadcrumbs && "mt-ds-0.5"), children: [
14698
14610
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
@@ -14719,7 +14631,7 @@ function SnappingHeader({
14719
14631
  style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px) - ${isBoldColourful && !(isFloating && !wrapperHasCard) ? "2px" : "var(--ds-border-width)"})` } : void 0,
14720
14632
  children: [
14721
14633
  !collapsed && focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight }),
14722
- !collapsed && hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, onLight, onTint, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14634
+ !collapsed && hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, onLight, onTint, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14723
14635
  /* @__PURE__ */ jsxs("div", { className: cn(
14724
14636
  "flex justify-between gap-ds-4",
14725
14637
  collapsed ? "items-center" : "flex-col @sm:flex-row @sm:items-center",
@@ -14734,17 +14646,14 @@ function SnappingHeader({
14734
14646
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
14735
14647
  collapsed && hasBreadcrumbs && (() => {
14736
14648
  const lastCrumb = breadcrumbs[breadcrumbs.length - 1];
14737
- const isClickable = !!(lastCrumb?.onClick || lastCrumb?.href);
14649
+ const isClickable = !!lastCrumb && crumbGoes(lastCrumb);
14738
14650
  const Tag3 = isClickable ? "a" : "span";
14739
14651
  return /* @__PURE__ */ jsxs(
14740
14652
  Tag3,
14741
14653
  {
14742
14654
  ...isClickable ? {
14743
14655
  href: lastCrumb.href ?? "#",
14744
- onClick: (e) => {
14745
- e.preventDefault();
14746
- lastCrumb.onClick?.();
14747
- }
14656
+ onClick: (e) => followCrumb(e, lastCrumb)
14748
14657
  } : {},
14749
14658
  className: cn(
14750
14659
  "text-sm hidden @sm:inline-flex items-center gap-ds-1",
@@ -14841,7 +14750,7 @@ function TabsIntegratedHeader({
14841
14750
  const inner = floatingContentProps(isFloating, contentMaxWidth);
14842
14751
  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: [
14843
14752
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14844
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14753
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14845
14754
  /* @__PURE__ */ jsxs("div", { className: cn(
14846
14755
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14847
14756
  hasBreadcrumbs && "mt-ds-1"
@@ -15390,7 +15299,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15390
15299
  "data-gs": "dialog",
15391
15300
  "aria-labelledby": titleId,
15392
15301
  "aria-describedby": description ? descriptionId : void 0,
15393
- style: { containerType: "inline-size" },
15302
+ style: { containerType: "inline-size", "--ds-form-max": "none" },
15394
15303
  className: cn(
15395
15304
  // a column: the title row, a body that scrolls when it is taller than the room, and the footer (Sprint 25.14,
15396
15305
  // with A36's full-screen phone dialog: its content ran past the screen with the footer cut off)
@@ -15601,7 +15510,7 @@ var Navbar = forwardRef(function Navbar2({
15601
15510
  }
15602
15511
  focus.onExit?.();
15603
15512
  };
15604
- const exitControl = focus ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: requestExit, children: focus.exitLabel ?? (focus.kind === "before-app" ? "Save and log out" : "Exit") }) : null;
15513
+ const exitControl = focus && !appShell?.flowLeft ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: requestExit, children: focus.exitLabel ?? (focus.kind === "before-app" ? "Save and log out" : "Exit") }) : null;
15605
15514
  const shownUserMenu = focus?.kind === "before-app" ? null : userMenu;
15606
15515
  const asking = confirmExit && focus?.kind === "in-app" && !!appShell?.hasUnsavedChanges;
15607
15516
  if (confirmExit && !asking) setConfirmExit(false);
@@ -16735,6 +16644,310 @@ function getPopoverPosition(rect, placement, padding) {
16735
16644
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
16736
16645
  return { top, left };
16737
16646
  }
16647
+ var FLOW_STORAGE_PREFIX = "gridsmith-flow:";
16648
+ var stores = /* @__PURE__ */ new Map();
16649
+ var storeFor = (key) => {
16650
+ let store = stores.get(key);
16651
+ if (!store) {
16652
+ store = { value: null, raw: void 0, stale: false, listeners: /* @__PURE__ */ new Set(), finished: false, focusStep: null, left: null };
16653
+ stores.set(key, store);
16654
+ }
16655
+ return store;
16656
+ };
16657
+ var notify = (store) => store.listeners.forEach((listener) => listener());
16658
+ function subscribeTo(key, listener) {
16659
+ const store = storeFor(key);
16660
+ store.listeners.add(listener);
16661
+ if (!store.onStorage && typeof window !== "undefined") {
16662
+ store.onStorage = (e) => {
16663
+ if (e.key !== null && e.key !== FLOW_STORAGE_PREFIX + key) return;
16664
+ store.stale = true;
16665
+ notify(store);
16666
+ };
16667
+ window.addEventListener("storage", store.onStorage);
16668
+ }
16669
+ return () => {
16670
+ store.listeners.delete(listener);
16671
+ if (store.listeners.size > 0) return;
16672
+ if (store.onStorage) window.removeEventListener("storage", store.onStorage);
16673
+ store.onStorage = void 0;
16674
+ store.finished = false;
16675
+ };
16676
+ }
16677
+ var readRaw = (key) => {
16678
+ try {
16679
+ return window.localStorage.getItem(FLOW_STORAGE_PREFIX + key);
16680
+ } catch {
16681
+ return null;
16682
+ }
16683
+ };
16684
+ function readRecord(key) {
16685
+ return parseRecord(readRaw(key));
16686
+ }
16687
+ function parseRecord(raw) {
16688
+ try {
16689
+ if (!raw) return null;
16690
+ const stored = JSON.parse(raw);
16691
+ const furthest = Math.max(1, Math.floor(Number(stored.furthest)) || 1);
16692
+ const current = Math.floor(Number(stored.current)) || void 0;
16693
+ return { furthest, current, answers: stored.answers ?? {} };
16694
+ } catch {
16695
+ return null;
16696
+ }
16697
+ }
16698
+ function writeRecord(key, value) {
16699
+ for (const text of [() => JSON.stringify(value), () => JSON.stringify({ furthest: value.furthest, current: value.current })]) {
16700
+ try {
16701
+ const raw = text();
16702
+ window.localStorage.setItem(FLOW_STORAGE_PREFIX + key, raw);
16703
+ return raw;
16704
+ } catch {
16705
+ }
16706
+ }
16707
+ return null;
16708
+ }
16709
+ function snapshot(key) {
16710
+ const store = storeFor(key);
16711
+ if (store.raw === void 0 || store.stale || store.listeners.size === 0) {
16712
+ store.stale = false;
16713
+ const raw = readRaw(key);
16714
+ if (raw !== store.raw || store.value === null) {
16715
+ store.raw = raw;
16716
+ store.value = parseRecord(raw) ?? { furthest: 1, answers: {} };
16717
+ }
16718
+ }
16719
+ return store.value;
16720
+ }
16721
+ function update(key, change) {
16722
+ const store = storeFor(key);
16723
+ const raw = readRaw(key);
16724
+ if (raw !== null && raw !== store.raw) {
16725
+ store.raw = raw;
16726
+ store.value = parseRecord(raw) ?? store.value;
16727
+ }
16728
+ store.value = change(store.value ?? { furthest: 1, answers: {} });
16729
+ store.raw = writeRecord(key, store.value) ?? store.raw;
16730
+ store.stale = false;
16731
+ notify(store);
16732
+ }
16733
+ function clear(key) {
16734
+ const store = storeFor(key);
16735
+ try {
16736
+ window.localStorage.removeItem(FLOW_STORAGE_PREFIX + key);
16737
+ } catch {
16738
+ }
16739
+ store.value = null;
16740
+ store.raw = void 0;
16741
+ notify(store);
16742
+ }
16743
+ function markFlowMove(storageKey, step) {
16744
+ storeFor(storageKey).focusStep = step;
16745
+ }
16746
+ function stepJustLeft(storageKey) {
16747
+ const left = storeFor(storageKey).left;
16748
+ return left && Date.now() - left.time < 3e3 ? left.step : null;
16749
+ }
16750
+ function readFlowProgress(storageKey, initialAnswers) {
16751
+ const record = typeof window === "undefined" ? null : readRecord(storageKey);
16752
+ return record ? { ...record, answers: { ...initialAnswers, ...record.answers } } : null;
16753
+ }
16754
+ function useFlowNavigate(navigate) {
16755
+ const shellNavigate = useAppShell()?.navigate;
16756
+ return useCallback((href, options) => {
16757
+ const go = navigate ?? shellNavigate;
16758
+ if (go) go(href, options);
16759
+ else if (options?.replace) window.location.replace(href);
16760
+ else window.location.assign(href);
16761
+ }, [navigate, shellNavigate]);
16762
+ }
16763
+ function focusHeading() {
16764
+ const heading = document.querySelector("main h1") ?? document.querySelector("main");
16765
+ if (!heading) return;
16766
+ if (!heading.hasAttribute("tabindex")) heading.setAttribute("tabindex", "-1");
16767
+ heading.focus({ preventScroll: false });
16768
+ }
16769
+ function focusAfterLeaving() {
16770
+ if (typeof window === "undefined" || typeof requestAnimationFrame === "undefined") return () => {
16771
+ };
16772
+ const from = window.location.href;
16773
+ const old = document.querySelector("main h1");
16774
+ const oldText = old?.textContent ?? null;
16775
+ return () => {
16776
+ let frames = 0;
16777
+ const tick = () => {
16778
+ const heading = document.querySelector("main h1");
16779
+ const drawn = window.location.href !== from && !!heading && (heading !== old || heading.textContent !== oldText);
16780
+ if (!drawn) {
16781
+ if (++frames < 60) requestAnimationFrame(tick);
16782
+ return;
16783
+ }
16784
+ const active = document.activeElement;
16785
+ if (!active || active === document.body || !active.isConnected) focusHeading();
16786
+ };
16787
+ requestAnimationFrame(tick);
16788
+ };
16789
+ }
16790
+ var titleBase = null;
16791
+ var titleUsers = 0;
16792
+ function usePageTitle(title) {
16793
+ useEffect(() => {
16794
+ if (!title || typeof document === "undefined") return;
16795
+ if (titleUsers++ === 0) titleBase = document.title;
16796
+ document.title = titleBase ? `${title} \u2013 ${titleBase}` : title;
16797
+ return () => {
16798
+ if (--titleUsers > 0) return;
16799
+ if (titleBase !== null) document.title = titleBase;
16800
+ titleBase = null;
16801
+ };
16802
+ }, [title]);
16803
+ }
16804
+ var shownSteps = (steps, answers) => steps.filter((s) => !s.when || s.when(answers));
16805
+ var STEP = /^[1-9]\d*$/;
16806
+ function useFlowSteps(options) {
16807
+ const { steps, at, href, initialAnswers, exitTo, finishTo } = options;
16808
+ const key = options.storageKey || null;
16809
+ const go = useFlowNavigate(options.navigate);
16810
+ const subscribe = useCallback((listener) => key ? subscribeTo(key, listener) : () => {
16811
+ }, [key]);
16812
+ const stored = useSyncExternalStore(subscribe, () => key ? snapshot(key) : null, () => null);
16813
+ const answers = useMemo(() => ({ ...initialAnswers, ...stored?.answers ?? {} }), [initialAnswers, stored]);
16814
+ const shown = useMemo(() => shownSteps(steps, answers), [steps, answers]);
16815
+ const total = Math.max(shown.length, 1);
16816
+ const furthest = Math.min(stored?.furthest ?? 1, total);
16817
+ const asked = at !== void 0 && STEP.test(at) ? Number(at) : NaN;
16818
+ const valid = asked >= 1 && asked <= furthest;
16819
+ const step = valid ? asked : furthest;
16820
+ const finished = !!key && storeFor(key).finished;
16821
+ const ready = !!key && stored != null && valid && !finished;
16822
+ const redirected = useRef(null);
16823
+ useEffect(() => {
16824
+ if (!key || storeFor(key).finished) return;
16825
+ if (!stored || valid) {
16826
+ redirected.current = null;
16827
+ return;
16828
+ }
16829
+ const target = href(furthest);
16830
+ if (redirected.current === `${at} ${target}`) return;
16831
+ redirected.current = `${at} ${target}`;
16832
+ go(target, { replace: true });
16833
+ }, [key, stored, valid, furthest, href, go, at]);
16834
+ useEffect(() => {
16835
+ if (key && ready && stored?.current !== step) update(key, (v) => ({ ...v, current: step }));
16836
+ }, [key, ready, stored?.current, step]);
16837
+ useEffect(() => {
16838
+ if (!key || !ready) return;
16839
+ const store = storeFor(key);
16840
+ if (store.focusStep !== step) return;
16841
+ store.focusStep = null;
16842
+ focusHeading();
16843
+ }, [key, ready, step]);
16844
+ useLayoutEffect(() => {
16845
+ if (!key || !ready) return;
16846
+ const store = storeFor(key);
16847
+ return () => {
16848
+ store.left = { step, time: Date.now() };
16849
+ };
16850
+ }, [key, ready, step]);
16851
+ const current = shown[step - 1] ?? steps[0];
16852
+ usePageTitle(ready ? current.title : null);
16853
+ const setAnswers = useCallback((patch) => {
16854
+ if (!key) return;
16855
+ update(key, (before) => {
16856
+ const prev = { ...initialAnswers, ...before.answers };
16857
+ const next2 = { ...prev, ...patch };
16858
+ const here = Math.max(step, before.current ?? step);
16859
+ const ahead = (list) => list.slice(here).map((s) => s.label).join("\0");
16860
+ const changes = ahead(shownSteps(steps, prev)) !== ahead(shownSteps(steps, next2));
16861
+ return { ...before, furthest: changes ? Math.min(before.furthest, here) : before.furthest, answers: next2 };
16862
+ });
16863
+ }, [key, initialAnswers, step, steps]);
16864
+ const moveTo = useCallback((target) => {
16865
+ if (key) storeFor(key).focusStep = target;
16866
+ go(href(target));
16867
+ }, [key, go, href]);
16868
+ const goTo = useCallback((target) => {
16869
+ if (!Number.isInteger(target) || target < 1 || target > furthest || target === step) return;
16870
+ moveTo(target);
16871
+ }, [furthest, step, moveTo]);
16872
+ const next = useCallback((patch) => {
16873
+ if (!key) return;
16874
+ if (patch) setAnswers(patch);
16875
+ const shownNow = patch ? shownSteps(steps, { ...answers, ...patch }) : shown;
16876
+ if (step >= shownNow.length) return;
16877
+ update(key, (v) => ({ ...v, furthest: Math.max(v.furthest, step + 1), current: step + 1 }));
16878
+ moveTo(step + 1);
16879
+ }, [key, setAnswers, steps, answers, shown, step, moveTo]);
16880
+ const back = step > 1 ? () => moveTo(step - 1) : void 0;
16881
+ const saveAndExit = useCallback(() => {
16882
+ if (key && stored) update(key, (v) => ({ ...v, current: step }));
16883
+ const left = focusAfterLeaving();
16884
+ go(exitTo);
16885
+ left();
16886
+ }, [key, stored, step, go, exitTo]);
16887
+ const finish = useCallback(() => {
16888
+ if (key) storeFor(key).finished = true;
16889
+ const left = focusAfterLeaving();
16890
+ go(finishTo);
16891
+ if (key) clear(key);
16892
+ left();
16893
+ }, [key, go, finishTo]);
16894
+ return {
16895
+ ready,
16896
+ step,
16897
+ total,
16898
+ current,
16899
+ caption: `Step ${step} of ${total}`,
16900
+ title: current.title,
16901
+ answers,
16902
+ setAnswers,
16903
+ furthest,
16904
+ isFirst: step === 1,
16905
+ isLast: step === total,
16906
+ next,
16907
+ back,
16908
+ goTo,
16909
+ stepper: {
16910
+ steps: shown.map((s, i) => ({
16911
+ label: s.label,
16912
+ status: i + 1 < furthest && i + 1 !== step ? "complete" : void 0,
16913
+ disabled: i + 1 > furthest || void 0
16914
+ })),
16915
+ currentStep: step - 1,
16916
+ onStepChange: (index) => goTo(index + 1)
16917
+ },
16918
+ saveAndExit,
16919
+ finish
16920
+ };
16921
+ }
16922
+ function FlowSaved({ storageKey, href, title = "Your progress has been saved", children, navigate }) {
16923
+ const go = useFlowNavigate(navigate);
16924
+ const titleId = useId();
16925
+ const markFlowLeft = useAppShell()?.markFlowLeft;
16926
+ useLayoutEffect(() => markFlowLeft?.(), [markFlowLeft]);
16927
+ const [saved, setSaved] = useState(void 0);
16928
+ useLayoutEffect(() => {
16929
+ const progress = readFlowProgress(storageKey, {});
16930
+ const step = progress ? progress.current ?? progress.furthest : null;
16931
+ setSaved(step === null ? null : { step, behind: stepJustLeft(storageKey) === step });
16932
+ }, [storageKey]);
16933
+ const resume = () => {
16934
+ const step = saved?.step ?? 1;
16935
+ markFlowMove(storageKey, step);
16936
+ if (saved?.behind) window.history.back();
16937
+ else go(href(step), { replace: true });
16938
+ };
16939
+ useLayoutEffect(() => {
16940
+ if (saved !== void 0) focusHeading();
16941
+ }, [saved]);
16942
+ const heading = saved === null ? "There's no saved progress" : title;
16943
+ usePageTitle(saved === void 0 ? null : heading);
16944
+ return /* @__PURE__ */ jsx(PageContainer, { width: "narrow", children: /* @__PURE__ */ jsxs("section", { "aria-labelledby": titleId, className: "flex min-h-[60vh] flex-col items-center justify-center py-ds-12 text-center", children: [
16945
+ saved !== null && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grid size-14 place-items-center rounded-[var(--ds-radius-full)] bg-[var(--ds-status-success-bg)] text-[var(--ds-status-success)]", children: /* @__PURE__ */ jsx(Icon, { name: "check", size: 28 }) }),
16946
+ /* @__PURE__ */ jsx("h1", { id: titleId, className: "mt-ds-6 font-[family-name:var(--ds-font-display)] text-3xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: heading }),
16947
+ /* @__PURE__ */ jsx("div", { className: "mt-ds-3 max-w-md text-[var(--ds-text-secondary)]", children: saved === null ? /* @__PURE__ */ jsx("p", { children: "Nothing has been saved in this browser yet." }) : children ?? /* @__PURE__ */ jsx("p", { children: "Your answers are kept in this browser. Come back to it to carry on where you left off." }) }),
16948
+ /* @__PURE__ */ jsx("div", { className: "mt-ds-8 flex flex-wrap justify-center gap-ds-3", children: /* @__PURE__ */ jsx(Button, { type: "button", onClick: resume, children: saved === null ? "Start" : "Continue" }) })
16949
+ ] }) });
16950
+ }
16738
16951
  function useDestructiveAction(options) {
16739
16952
  const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
16740
16953
  const preferences = useShellPreferences();
@@ -16900,6 +17113,8 @@ function pastTenseOf(label) {
16900
17113
  }
16901
17114
  var SectionHeading = forwardRef(function SectionHeading2({
16902
17115
  title,
17116
+ onTitleChange,
17117
+ titleLabel = "Title",
16903
17118
  description,
16904
17119
  actions,
16905
17120
  badge,
@@ -16911,6 +17126,8 @@ var SectionHeading = forwardRef(function SectionHeading2({
16911
17126
  }, ref) {
16912
17127
  const isPhone = useBreakpoint("sm");
16913
17128
  const pane = variant === "pane";
17129
+ const inDetail = useContext(SplitPaneCtx) === "detail";
17130
+ const editable = !!onTitleChange;
16914
17131
  return /* @__PURE__ */ jsxs(
16915
17132
  "div",
16916
17133
  {
@@ -16918,7 +17135,7 @@ var SectionHeading = forwardRef(function SectionHeading2({
16918
17135
  "data-gs": "section-heading",
16919
17136
  "data-variant": variant,
16920
17137
  className: cn(
16921
- pane ? "shrink-0 px-ds-4 pt-ds-4 pb-ds-3 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]" : "mb-[var(--ds-heading-gap)]",
17138
+ pane ? cn("shrink-0 pt-ds-4 pb-ds-3 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]", inDetail ? "px-[var(--ds-page-gutter)]" : "px-ds-4") : "mb-[var(--ds-heading-gap)]",
16922
17139
  !pane && bordered && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] pb-ds-4",
16923
17140
  className
16924
17141
  ),
@@ -16927,11 +17144,23 @@ var SectionHeading = forwardRef(function SectionHeading2({
16927
17144
  "flex gap-ds-4",
16928
17145
  // the actions wrap under the title when the two do not fit on one line (round 11: a search Input in a 284 px
16929
17146
  // pane's actions slot squeezed "Inbox" to "I."); below sm they always stack
16930
- isPhone && actions ? "flex-col" : "flex-wrap items-start justify-between"
17147
+ isPhone && actions ? "flex-col" : cn("flex-wrap justify-between", editable && !description ? "items-center" : "items-start")
16931
17148
  ), children: [
16932
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
17149
+ /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", editable && !(isPhone && actions) && "flex-[1_1_20ch] text-lg"), children: [
16933
17150
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
16934
- /* @__PURE__ */ jsx("h2", { className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", pane ? "truncate" : "min-w-0 break-words"), children: title }),
17151
+ editable ? (
17152
+ // the field is the title: the heading holds it, so the page's headings still name the view by its title
17153
+ /* @__PURE__ */ jsx("h2", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
17154
+ Input,
17155
+ {
17156
+ size: "md",
17157
+ "aria-label": titleLabel,
17158
+ value: title,
17159
+ onChange: (e) => onTitleChange(e.target.value),
17160
+ className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]"
17161
+ }
17162
+ ) })
17163
+ ) : /* @__PURE__ */ jsx("h2", { className: cn("font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", pane ? "truncate" : "min-w-0 break-words"), children: title }),
16935
17164
  badge,
16936
17165
  count !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "neutral", size: "sm", children: count })
16937
17166
  ] }),
@@ -17038,6 +17267,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17038
17267
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17039
17268
  });
17040
17269
 
17041
- export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, Breadcrumbs, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, DropdownMenu, EmptyState, EntityCell, FileUpload, FormWrapper, Gantt, HoverCard, Kbd, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, PageContainer, PageHeader, PinInput, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sheet, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17270
+ export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, Avatar, AvatarGroup, BottomNav, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Checkbox, CheckboxGroup, CheckboxGroupItem, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DataList, DatePicker, DateRangePicker, DescriptionList, Dialog, Dock, DropdownMenu, EmptyState, EntityCell, FileUpload, FlowSaved, FormWrapper, Gantt, HoverCard, Kbd, Launcher, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, PageActions, PageContainer, PageHeader, PinInput, Progress, RadioGroup, Rating, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sheet, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, Tag, TagInput, TextOverflow, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, Toast, ToastProvider, Tour, TransferList, TreeView, dateOfDay, dayOf, endDayOf, fitLabel, fitLabels, formatDateRange, readFlowProgress, timeExtent, useContentWidth, useDestructiveAction, useFittedLabels, useFlowSteps, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17042
17271
  //# sourceMappingURL=index.js.map
17043
17272
  //# sourceMappingURL=index.js.map