gridsmith-ui 0.22.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 (43) hide show
  1. package/dist/Breadcrumbs-CGS8GKl-.d.ts +33 -0
  2. package/dist/chat.js +1 -1
  3. package/dist/chunk-2PWGJ4PK.js +45 -0
  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-3BRQRKDO.js +72 -0
  8. package/dist/chunk-3BRQRKDO.js.map +1 -0
  9. package/dist/{chunk-2RV23OBK.js → chunk-6XAU44U4.js} +25 -10
  10. package/dist/chunk-6XAU44U4.js.map +1 -0
  11. package/dist/{chunk-RT7RYGC3.js → chunk-JXANCXK6.js} +4 -39
  12. package/dist/chunk-JXANCXK6.js.map +1 -0
  13. package/dist/{chunk-AAYCQXZQ.js → chunk-MV7JXWXK.js} +630 -411
  14. package/dist/chunk-MV7JXWXK.js.map +1 -0
  15. package/dist/{chunk-EYMGLK4M.js → chunk-SVP2IG4S.js} +2 -2
  16. package/dist/{chunk-EYMGLK4M.js.map → chunk-SVP2IG4S.js.map} +1 -1
  17. package/dist/chunk-WOVYHFUD.js +50 -0
  18. package/dist/chunk-WOVYHFUD.js.map +1 -0
  19. package/dist/chunk-XVB55M3N.js +42 -0
  20. package/dist/chunk-XVB55M3N.js.map +1 -0
  21. package/dist/data-grid.js +5 -3
  22. package/dist/data-grid.js.map +1 -1
  23. package/dist/editor.js +11 -3
  24. package/dist/editor.js.map +1 -1
  25. package/dist/index.d.ts +227 -45
  26. package/dist/index.js +843 -586
  27. package/dist/index.js.map +1 -1
  28. package/dist/kanban.js +23 -4
  29. package/dist/kanban.js.map +1 -1
  30. package/dist/resizable.d.ts +17 -2
  31. package/dist/resizable.js +102 -24
  32. package/dist/resizable.js.map +1 -1
  33. package/dist/roots.css +19 -4
  34. package/dist/tokens.css +20 -3
  35. package/dist/virtualized.js +5 -2
  36. package/dist/virtualized.js.map +1 -1
  37. package/dist-static/tokens.css +20 -3
  38. package/package.json +1 -1
  39. package/dist/chunk-2RV23OBK.js.map +0 -1
  40. package/dist/chunk-AAYCQXZQ.js.map +0 -1
  41. package/dist/chunk-RG4KUCRS.js +0 -9
  42. package/dist/chunk-RG4KUCRS.js.map +0 -1
  43. package/dist/chunk-RT7RYGC3.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,21 +1,25 @@
1
- import { useAppShell, REACT_VERSION_PROBLEM, useOverlayMark, useDirtyGuard, OverlayMarkScope, OverlayCloseCtx, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, DrawerFooterCtx, useCompact, useElementWidth, Sheet, useCloseSettle, Drawer, useRememberedWidth, pxOf, ResizeEdge, defaultMaxWidth } from './chunk-AAYCQXZQ.js';
2
- export { AppShell, Drawer, Sheet, SkipLink, useAppShell, useUnsavedChanges } from './chunk-AAYCQXZQ.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
- import { Link } from './chunk-EYMGLK4M.js';
6
- export { CodeBlock, Divider, Link } from './chunk-EYMGLK4M.js';
5
+ import { useScrollEdges, EDGE_FADE } from './chunk-3BRQRKDO.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';
7
9
  import { Badge } from './chunk-SBTBMAIU.js';
8
10
  export { Badge } from './chunk-SBTBMAIU.js';
9
11
  import { inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, CardValue } from './chunk-IVCVNAXG.js';
10
- import { useWholeListCheck, pagerPart, Pagination, Dropdown, useFieldControl, useField, warnOnce, Label, InlineFieldError, AddAnotherRowCtx, Input, Field } from './chunk-2RV23OBK.js';
11
- export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-2RV23OBK.js';
12
- import { SplitPaneCtx, TableCardCtx } from './chunk-RG4KUCRS.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';
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';
19
23
  import { useTextOverflow, Tooltip, usePosition } from './chunk-CXMV7SXJ.js';
20
24
  export { Tooltip, useTextOverflow } from './chunk-CXMV7SXJ.js';
21
25
  import { mergeRefs, Slot } from './chunk-7Q46JXSK.js';
@@ -508,7 +512,7 @@ function Checkbox({ checked, onChange: onChangeProp, label, size = "md", disable
508
512
  "border-[2px]",
509
513
  "transition-all",
510
514
  "peer-focus-visible:shadow-[var(--ds-shadow-focus)]",
511
- filled ? "bg-[var(--ds-check-bg)] [background-image:var(--ds-gradient)] border-[var(--ds-check-border)]" : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]",
515
+ filled ? cn(CHECK_FILL, "border-[var(--ds-check-border)]") : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]",
512
516
  disabled && "pointer-events-none"
513
517
  ),
514
518
  children: /* @__PURE__ */ jsx(
@@ -629,7 +633,8 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
629
633
  selected && "border-[var(--ds-check-border)]",
630
634
  "group-focus-visible:shadow-[var(--ds-shadow-focus)]"
631
635
  ),
632
- children: selected && /* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]" })
636
+ children: selected && // Sprint 25.14 A20: the checked colour, as a checkbox's fill (a preset whose accent falls short of 3:1 sets it)
637
+ /* @__PURE__ */ jsx("span", { className: cn("h-2.5 w-2.5 rounded-[var(--ds-radius-full)]", CHECK_FILL) })
633
638
  }
634
639
  ),
635
640
  /* @__PURE__ */ jsx("span", { className: "text-sm text-[var(--ds-text-primary)]", children: option.label })
@@ -1880,9 +1885,9 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
1880
1885
  useEffect(() => {
1881
1886
  const owner = inputRef.current?.form;
1882
1887
  if (!owner) return;
1883
- const clear = () => setRejected(null);
1884
- owner.addEventListener("submit", clear);
1885
- return () => owner.removeEventListener("submit", clear);
1888
+ const clear2 = () => setRejected(null);
1889
+ owner.addEventListener("submit", clear2);
1890
+ return () => owner.removeEventListener("submit", clear2);
1886
1891
  }, []);
1887
1892
  const invalid = error || rejected != null || !!waiting;
1888
1893
  const describedBy = [constraints ? constraintsId : null, field?.describedBy, rejected ? rejectedId : null, waiting ? waitingId : null].filter(Boolean).join(" ") || void 0;
@@ -1991,7 +1996,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
1991
1996
  // The real file input is visually hidden inside; Tab lands on it and
1992
1997
  // Enter/Space open the picker natively, so the zone shows its focus.
1993
1998
  invalid ? ERROR_HAS_FOCUS_RING : HAS_FOCUS_RING,
1994
- dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
1999
+ dragOver ? `${ACTIVE_RING} bg-[var(--ds-accent-whisper)]` : invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-input-border)] hover:border-[var(--ds-accent-primary)] hover:bg-[var(--ds-accent-whisper)]"
1995
2000
  ),
1996
2001
  style: { padding: "var(--ds-space-8) var(--ds-space-4)" },
1997
2002
  children: [
@@ -2165,7 +2170,7 @@ function SliderRange({
2165
2170
  !trackStyle && /* @__PURE__ */ jsx(
2166
2171
  "div",
2167
2172
  {
2168
- className: "absolute h-1.5 rounded-[var(--ds-radius-full)] bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)]",
2173
+ className: cn("absolute h-1.5 rounded-[var(--ds-radius-full)]", CHECK_FILL),
2169
2174
  style: {
2170
2175
  left: `${isRange ? pct(low) : 0}%`,
2171
2176
  width: `${isRange ? pct(high) - pct(low) : pct(low)}%`
@@ -2189,7 +2194,7 @@ function SliderRange({
2189
2194
  className: cn(
2190
2195
  "absolute w-ds-4 h-ds-4 -translate-x-1/2 rounded-[var(--ds-radius-full)]",
2191
2196
  "bg-[var(--ds-bg-surface)] border-2",
2192
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-accent-primary)]",
2197
+ error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-check-border)]",
2193
2198
  "shadow-[var(--ds-shadow-sm)]",
2194
2199
  "transition-[shadow,transform] duration-200 ease-out",
2195
2200
  "focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -2215,7 +2220,7 @@ function SliderRange({
2215
2220
  className: cn(
2216
2221
  "absolute w-ds-4 h-ds-4 -translate-x-1/2 rounded-[var(--ds-radius-full)]",
2217
2222
  "bg-[var(--ds-bg-surface)] border-2",
2218
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-accent-primary)]",
2223
+ error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-check-border)]",
2219
2224
  "shadow-[var(--ds-shadow-sm)]",
2220
2225
  "transition-[shadow,transform] duration-200 ease-out",
2221
2226
  "focus-visible:shadow-[var(--ds-shadow-focus)]",
@@ -2988,7 +2993,7 @@ function PinInput({
2988
2993
  role: "group",
2989
2994
  "aria-label": "PIN input",
2990
2995
  "aria-invalid": error || void 0,
2991
- className: cn("inline-flex items-center gap-ds-2", className),
2996
+ className: cn("inline-flex w-fit items-center gap-ds-2", className),
2992
2997
  children: digits.map((digit, i) => /* @__PURE__ */ jsx(
2993
2998
  "input",
2994
2999
  {
@@ -3153,7 +3158,9 @@ function Rating({
3153
3158
  "aria-required": readOnly ? void 0 : control.required || void 0,
3154
3159
  onKeyDown: readOnly ? void 0 : handleKeyDown,
3155
3160
  className: cn(
3156
- "inline-flex items-center gap-ds-0.5",
3161
+ // Sprint 25.14 A21: its own width in a Field's column, not the field's; w-fit, not self-start, so a row or a
3162
+ // centred column still aligns it (the review of 30 September)
3163
+ "inline-flex w-fit items-center gap-ds-0.5",
3157
3164
  error && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)] rounded-[var(--ds-radius-sm)] p-ds-0.5",
3158
3165
  className
3159
3166
  ),
@@ -3583,7 +3590,7 @@ function focusTarget(el) {
3583
3590
  if (el.tabIndex >= 0 || /^(?:INPUT|SELECT|TEXTAREA|BUTTON)$/.test(el.tagName)) return el;
3584
3591
  return el.querySelector("[aria-checked='true'], [aria-selected='true']:not([tabindex='-1'])") ?? el.querySelector("input, select, textarea, button, [tabindex]:not([tabindex='-1'])") ?? el;
3585
3592
  }
3586
- 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 }) {
3587
3594
  const preferences = useShellPreferences();
3588
3595
  const validationMode = validationModeProp ?? preferences.validationTiming ?? "onSubmit";
3589
3596
  const actionsAlign = actionsAlignProp ?? preferences.formActions?.align ?? "end";
@@ -3750,7 +3757,7 @@ function FormWrapper({ onSubmit, validationMode: validationModeProp, children, a
3750
3757
  f.message
3751
3758
  ] }, f.id || f.label)) }) }) : null;
3752
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;
3753
- 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: [
3754
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 }),
3755
3762
  children,
3756
3763
  actionsNode
@@ -4373,7 +4380,6 @@ function holdsHeader(children) {
4373
4380
  }
4374
4381
  var CardActionCtx = createContext(null);
4375
4382
  var CARD_ACTION_ATTR = "data-card-action";
4376
- var CardSurfaceCtx = createContext(false);
4377
4383
  var variantStyles3 = {
4378
4384
  default: `bg-[var(--ds-bg-surface)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] ${GLASS_SURFACE}`,
4379
4385
  featured: [
@@ -4409,7 +4415,8 @@ function RadioIndicator({ checked, disabled }) {
4409
4415
  {
4410
4416
  className: cn(
4411
4417
  "h-2.5 w-2.5 rounded-[var(--ds-radius-full)] transition-transform duration-[var(--ds-transition-speed)]",
4412
- checked ? "bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] scale-100" : "scale-0"
4418
+ // Sprint 25.14 A20: the radio's selected dot (lib/control-looks.ts)
4419
+ checked ? cn(CHECK_FILL, "scale-100") : "scale-0"
4413
4420
  )
4414
4421
  }
4415
4422
  )
@@ -4427,7 +4434,8 @@ function CheckboxIndicator({ checked, indeterminate, disabled }) {
4427
4434
  "rounded-[calc(var(--ds-radius-sm)*0.6)]",
4428
4435
  "border-[2px] transition-all duration-[var(--ds-transition-speed)]",
4429
4436
  disabled && "opacity-disabled",
4430
- filled ? "bg-[var(--ds-check-bg)] [background-image:var(--ds-gradient)] border-[var(--ds-check-border)]" : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]"
4437
+ // Sprint 25.14 A20: the checkbox's checked fill (lib/control-looks.ts)
4438
+ filled ? cn(CHECK_FILL, "border-[var(--ds-check-border)]") : "bg-[var(--ds-input-bg)] border-[var(--ds-input-border)]"
4431
4439
  ),
4432
4440
  children: /* @__PURE__ */ jsx(
4433
4441
  "svg",
@@ -4455,13 +4463,14 @@ function ToggleIndicator({ checked, disabled }) {
4455
4463
  "border-[length:var(--ds-border-width)] rounded-[var(--ds-radius-full)]",
4456
4464
  "transition-colors duration-[var(--ds-transition-speed)]",
4457
4465
  disabled && "opacity-disabled",
4458
- checked ? "bg-[var(--ds-accent-primary)] border-transparent" : "bg-[var(--ds-bg-muted)] border-[var(--ds-border-default)]"
4466
+ // Sprint 25.14 A20: the Toggle's track and knob, on and off (lib/control-looks.ts)
4467
+ checked ? SWITCH_TRACK_ON : SWITCH_TRACK_OFF
4459
4468
  ),
4460
4469
  style: { width: 44, height: 24 },
4461
4470
  children: /* @__PURE__ */ jsx(
4462
4471
  "span",
4463
4472
  {
4464
- className: "pointer-events-none inline-block shrink-0 bg-[var(--ds-bg-surface)] rounded-[var(--ds-radius-full)] transition-transform",
4473
+ className: cn("pointer-events-none inline-block shrink-0 rounded-[var(--ds-radius-full)]", checked ? SWITCH_KNOB_ON : SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION),
4465
4474
  style: {
4466
4475
  width: 16,
4467
4476
  height: 16,
@@ -4647,9 +4656,9 @@ var Card = forwardRef(function Card2({
4647
4656
  ...props,
4648
4657
  children: [
4649
4658
  !loading && !hasHeader && checkbox && /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-center gap-ds-2 px-ds-5 pt-ds-4", children: [
4650
- checkbox,
4651
4659
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow" }),
4652
- check && /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: "shrink-0 flex", children: check })
4660
+ check && /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: "shrink-0 flex", children: check }),
4661
+ checkbox
4653
4662
  ] }),
4654
4663
  !loading && !hasHeader && !checkbox && check && /* @__PURE__ */ jsx("div", { "data-card-selection": "", className: "absolute top-ds-3 right-ds-3 z-[1] flex", children: check }),
4655
4664
  /* @__PURE__ */ jsx(CardSurfaceCtx, { value: true, children: /* @__PURE__ */ jsx(CardActionCtx, { value: action, children: /* @__PURE__ */ jsx(CardRowCtx, { value: rowParts, children: loading ? (
@@ -4724,7 +4733,7 @@ var CardHeader = forwardRef(function CardHeader2(allProps, ref) {
4724
4733
  }
4725
4734
  ) : null;
4726
4735
  const band = (order) => cn("shrink-0 self-start flex items-center", TITLE_LINE, order);
4727
- const checkbox = owns && row.checkbox ? /* @__PURE__ */ jsx("span", { className: band("order-0"), children: row.checkbox }) : null;
4736
+ const checkbox = owns && row.checkbox ? /* @__PURE__ */ jsx("span", { className: band("order-5"), children: row.checkbox }) : null;
4728
4737
  const check = owns && row.check ? /* @__PURE__ */ jsx("span", { "data-card-selection": "", className: band("order-4"), children: row.check }) : null;
4729
4738
  const menuPart = menu ? /* @__PURE__ */ jsx("span", { className: band("order-6"), children: menu }) : null;
4730
4739
  const { lead, title, rest } = splitTitle(children);
@@ -4734,15 +4743,14 @@ var CardHeader = forwardRef(function CardHeader2(allProps, ref) {
4734
4743
  const actionsSlot = "actions" in allProps ? /* @__PURE__ */ jsx("div", { "data-header-actions": "", className: "contents", children: partsOf(actions).map((item, i) => /* @__PURE__ */ jsx("span", { className: cn("flex items-center shrink-0", TITLE_LINE), children: item }, isValidElement(item) && item.key !== null ? item.key : i)) }) : null;
4735
4744
  return /* @__PURE__ */ jsxs("div", { ref, "data-card-header": "", className: cn("flex flex-col px-ds-5 pt-ds-4 last:pb-ds-4 min-w-0", className), ...props, children: [
4736
4745
  topLine && /* @__PURE__ */ jsxs("div", { "data-card-icon-row": iconBlock ? "" : void 0, "data-card-lead-row": iconBlock ? void 0 : "", className: cn("flex items-start gap-ds-2", iconBlock ? "mb-ds-2.5" : "mb-ds-1.5"), children: [
4737
- checkbox,
4738
4746
  topLine,
4739
4747
  iconBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "grow -ml-ds-2" }),
4740
4748
  check,
4749
+ checkbox,
4741
4750
  menuPart
4742
4751
  ] }),
4743
4752
  iconBlock && leadBlock && /* @__PURE__ */ jsx("div", { className: "flex mb-ds-1.5", children: leadBlock }),
4744
4753
  /* @__PURE__ */ jsxs("div", { "data-card-header-row": "", className: "flex items-start gap-ds-2 min-w-0", children: [
4745
- !topLine && checkbox,
4746
4754
  /* @__PURE__ */ jsxs("div", { "data-card-title-line": "", className: TITLE_ROW, children: [
4747
4755
  title && /* @__PURE__ */ jsx(
4748
4756
  "div",
@@ -4756,6 +4764,7 @@ var CardHeader = forwardRef(function CardHeader2(allProps, ref) {
4756
4764
  !ownBlock && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "order-3 grow basis-0 -ml-ds-2" })
4757
4765
  ] }),
4758
4766
  !topLine && check,
4767
+ !topLine && checkbox,
4759
4768
  !topLine && menuPart
4760
4769
  ] }),
4761
4770
  rest.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-col space-y-ds-1.5 break-words mt-ds-1.5 min-w-0", children: rest })
@@ -4842,7 +4851,7 @@ var CardFooter = forwardRef(function CardFooter2({ className, children, ...props
4842
4851
  });
4843
4852
  function TruncLabel({ children }) {
4844
4853
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
4845
- const el = /* @__PURE__ */ jsx("span", { ref, className: "text-xs font-medium text-[var(--ds-text-muted)] uppercase tracking-wider truncate min-w-0", children });
4854
+ const el = /* @__PURE__ */ jsx("span", { ref, className: "block text-xs font-medium text-[var(--ds-text-muted)] uppercase tracking-wider truncate min-w-0", children });
4846
4855
  if (!isTruncated) return el;
4847
4856
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: el });
4848
4857
  }
@@ -4887,13 +4896,15 @@ function StatsCard({ value, label, trend, icon, layout = "card", interactive, on
4887
4896
  } : void 0,
4888
4897
  children: [
4889
4898
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-ds-2 min-w-0", children: [
4890
- /* @__PURE__ */ jsx(TruncLabel, { children: label }),
4899
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
4900
+ /* @__PURE__ */ jsx(TruncLabel, { children: label }),
4901
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-ds-2 mt-ds-2 flex-wrap", children: [
4902
+ /* @__PURE__ */ jsx("span", { className: "font-[family-name:var(--ds-font-display)] text-2xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] leading-none tabular-nums", children: value }),
4903
+ trend && /* @__PURE__ */ jsx(TrendBadge, { trend })
4904
+ ] })
4905
+ ] }),
4891
4906
  icon && /* @__PURE__ */ jsx("div", { className: "shrink-0 flex items-center justify-center w-8 h-8 rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-muted)]", children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "text-[var(--ds-text-muted)]" }) })
4892
4907
  ] }),
4893
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-ds-2 mt-ds-2 flex-wrap", children: [
4894
- /* @__PURE__ */ jsx("span", { className: "font-[family-name:var(--ds-font-display)] text-2xl font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] leading-none tabular-nums", children: value }),
4895
- trend && /* @__PURE__ */ jsx(TrendBadge, { trend })
4896
- ] }),
4897
4908
  children && /* @__PURE__ */ jsx("div", { className: "mt-ds-3 text-xs text-[var(--ds-text-muted)]", children })
4898
4909
  ]
4899
4910
  }
@@ -4978,6 +4989,7 @@ function countCards(children) {
4978
4989
  return n;
4979
4990
  }
4980
4991
  function CardGrid({ minWidth, maxColumns = 4, compare = false, gap = "var(--ds-space-4)", fill: fill2 = true, className, style, children, ref, ...props }) {
4992
+ useFramesItself();
4981
4993
  const most = Math.max(1, Math.floor(maxColumns));
4982
4994
  const drawn = "var(--gs-cards, var(--gs-cards-counted))";
4983
4995
  const min = "var(--gs-card-min)";
@@ -5286,71 +5298,6 @@ function Accordion({ items, type = "single", defaultOpen = [], open: controlledO
5286
5298
  ] }, i);
5287
5299
  }) });
5288
5300
  }
5289
- var EDGE_FADE = 24;
5290
- var NONE = { left: false, right: false, vertical: false, gutterRight: 0, gutterBottom: 0 };
5291
- var same = (a, b) => a.left === b.left && a.right === b.right && a.vertical === b.vertical && a.gutterRight === b.gutterRight && a.gutterBottom === b.gutterBottom;
5292
- function useScrollEdges(options = {}) {
5293
- const { enabled = true, fade = EDGE_FADE, noFade } = options;
5294
- const elRef = useRef(null);
5295
- const observer = useRef(null);
5296
- const borders = useRef({ x: 0, y: 0 });
5297
- const [state, setState] = useState(NONE);
5298
- const measure = useCallback(() => {
5299
- const el = elRef.current;
5300
- if (!el) return;
5301
- const next = {
5302
- left: el.scrollLeft > 1,
5303
- right: el.scrollLeft + el.clientWidth < el.scrollWidth - 1,
5304
- vertical: el.scrollHeight > el.clientHeight + 1,
5305
- gutterRight: Math.max(0, el.offsetWidth - el.clientWidth - borders.current.x),
5306
- gutterBottom: Math.max(0, el.offsetHeight - el.clientHeight - borders.current.y)
5307
- };
5308
- setState((prev) => same(prev, next) ? prev : next);
5309
- }, []);
5310
- const ref = useCallback((el) => {
5311
- observer.current?.disconnect();
5312
- observer.current = null;
5313
- elRef.current = el;
5314
- if (!el || !enabled) return;
5315
- const style = getComputedStyle(el);
5316
- const px = (v) => parseFloat(v) || 0;
5317
- borders.current = { x: px(style.borderLeftWidth) + px(style.borderRightWidth), y: px(style.borderTopWidth) + px(style.borderBottomWidth) };
5318
- measure();
5319
- if (typeof ResizeObserver === "undefined") return;
5320
- const o = new ResizeObserver(measure);
5321
- o.observe(el);
5322
- for (const child of Array.from(el.children)) o.observe(child);
5323
- observer.current = o;
5324
- }, [enabled, measure]);
5325
- const fadeLeft = state.left && !noFade?.left;
5326
- const fadeRight = state.right && !noFade?.right;
5327
- const shown = enabled && (fadeLeft || fadeRight);
5328
- const { gutterRight: gr, gutterBottom: gb } = state;
5329
- const fadeLayer = `linear-gradient(to right, ${fadeLeft ? `transparent 0, black ${fade}px` : "black 0"}, ${fadeRight ? `black calc(100% - ${gr + fade}px), transparent calc(100% - ${gr}px)` : "black 100%"})`;
5330
- const gutters = [gb > 0 && { size: `100% ${gb}px`, position: "0 100%" }, gr > 0 && { size: `${gr}px 100%`, position: "100% 0" }].filter(Boolean);
5331
- const mask = shown ? {
5332
- image: [fadeLayer, ...gutters.map(() => "linear-gradient(black, black)")].join(", "),
5333
- size: ["100% 100%", ...gutters.map((g) => g.size)].join(", "),
5334
- position: ["0 0", ...gutters.map((g) => g.position)].join(", ")
5335
- } : void 0;
5336
- return {
5337
- /** The scroller's ref (a callback). */
5338
- ref,
5339
- /** The scroller itself, for code that moves it. */
5340
- elRef,
5341
- measure,
5342
- /** Content hides past a side edge: the scroller moves sideways, and its edges fade (but those in `noFade`). */
5343
- overflowing: enabled && (state.left || state.right),
5344
- /** It scrolls either way, sideways or up and down: a keyboard needs to reach it. */
5345
- scrollable: enabled && (state.left || state.right || state.vertical),
5346
- edges: { left: state.left, right: state.right },
5347
- /** For the scroller: `data-fade` names the faded edges ("left", "right" or "left right"), the style masks them. */
5348
- fadeProps: {
5349
- "data-fade": shown ? [fadeLeft && "left", fadeRight && "right"].filter(Boolean).join(" ") : void 0,
5350
- style: mask ? { maskImage: mask.image, WebkitMaskImage: mask.image, maskSize: mask.size, WebkitMaskSize: mask.size, maskPosition: mask.position, WebkitMaskPosition: mask.position, maskRepeat: "no-repeat", WebkitMaskRepeat: "no-repeat" } : void 0
5351
- }
5352
- };
5353
- }
5354
5301
  var HEADER_LAYOUT = {
5355
5302
  row: "flex flex-wrap items-start justify-between gap-ds-4",
5356
5303
  block: "flex flex-col space-y-ds-1.5 grow basis-1/2 break-words max-w-full has-[[data-title-one-line]]:min-w-0",
@@ -5409,6 +5356,9 @@ var PINNED_LAYERS = cn(
5409
5356
  );
5410
5357
  var PIN_SHADOW = "oklch(0 0 0 / 0.18)";
5411
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
+ }
5412
5362
  function clickIsRows(e) {
5413
5363
  const target = e.target;
5414
5364
  if (!(target instanceof Element) || !e.currentTarget.contains(target)) return false;
@@ -5511,8 +5461,11 @@ function TableHeading({ title, description, actions, titleId, descriptionId, ins
5511
5461
  descriptionText(HEADER_LAYOUT.below)
5512
5462
  ] }) });
5513
5463
  }
5514
- var NO_PINS = { start: [], end: [], nameFits: false, frame: 0 };
5515
- var samePins = (a, b) => a.nameFits === b.nameFits && a.frame === b.frame && a.start.join() === b.start.join() && a.end.join() === b.end.join();
5464
+ var NO_PINS = { start: [], end: [], nameFits: false, frame: 0, panel: null };
5465
+ var samePins = (a, b) => a.nameFits === b.nameFits && a.frame === b.frame && a.start.join() === b.start.join() && a.end.join() === b.end.join() && a.panel?.join() === b.panel?.join();
5466
+ function PanelContent({ children }) {
5467
+ return /* @__PURE__ */ jsx(SurfaceBoundary, { children });
5468
+ }
5516
5469
  function Table({
5517
5470
  columns: columnsProp,
5518
5471
  data,
@@ -5545,6 +5498,7 @@ function Table({
5545
5498
  "aria-label": ariaLabel,
5546
5499
  ref
5547
5500
  }) {
5501
+ useFramesItself();
5548
5502
  const headingId = useId();
5549
5503
  const titleId = `${headingId}-title`;
5550
5504
  const descriptionId = `${headingId}-description`;
@@ -5686,6 +5640,7 @@ function Table({
5686
5640
  const cells = Array.from(table2.querySelectorAll(":scope > thead > tr > th"));
5687
5641
  if (!cells.length) return;
5688
5642
  const widths = cells.map((c) => c.getBoundingClientRect().width);
5643
+ if (!widths.some((w) => w > 0)) return;
5689
5644
  const start = [];
5690
5645
  let at = 0;
5691
5646
  for (let i = 0; i < lead; i++) {
@@ -5702,7 +5657,9 @@ function Table({
5702
5657
  const frame = scroller.clientWidth;
5703
5658
  const nameFits = nameIndex >= 0 && frame > 0 && at + nameWidth + fromRight <= frame / 2;
5704
5659
  if (nameFits) start.push(at);
5705
- const next = { start, end, nameFits, frame };
5660
+ const pad3 = (cell, side) => cell ? parseFloat(getComputedStyle(cell)[side]) || 0 : 0;
5661
+ const panel = cells.length > lead ? [at + pad3(cells[lead], "paddingLeft"), pad3(cells[cells.length - 1], "paddingRight")] : null;
5662
+ const next = { start, end, nameFits, frame, panel };
5706
5663
  setPins((prev) => samePins(prev, next) ? prev : next);
5707
5664
  };
5708
5665
  measure();
@@ -5742,9 +5699,16 @@ function Table({
5742
5699
  const expandedKey = expanded.map(String).join("");
5743
5700
  const shownBefore = useRef(/* @__PURE__ */ new Set());
5744
5701
  const pendingScroll = useRef(null);
5702
+ const scrollFrame = useRef(0);
5745
5703
  const scrollToRow = useCallback((key) => {
5746
- const row = rootRef.current?.querySelector(`[data-row-key="${CSS.escape(key)}"], [data-row-index="${CSS.escape(key)}"]`);
5747
- 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
+ });
5748
5712
  }, [rootRef]);
5749
5713
  useEffect(() => {
5750
5714
  const now = new Set(expanded.map(String));
@@ -5806,8 +5770,8 @@ function Table({
5806
5770
  const row = rootRef.current?.querySelector(was.row);
5807
5771
  if (!row) return;
5808
5772
  const controls = Array.from(row.querySelectorAll(FOCUSABLE));
5809
- const same2 = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
5810
- (same2 ?? (row.tabIndex >= 0 ? row : controls[0]))?.focus({ preventScroll: true });
5773
+ const same = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
5774
+ (same ?? (row.tabIndex >= 0 ? row : controls[0]))?.focus({ preventScroll: true });
5811
5775
  }, [isMobile, rootRef]);
5812
5776
  if (isMobile) {
5813
5777
  if (loading) {
@@ -5823,6 +5787,14 @@ function Table({
5823
5787
  drawn.map(({ row, originalIdx }) => {
5824
5788
  const isSelected = selectedSet.has(originalIdx);
5825
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
+ };
5826
5798
  return /* @__PURE__ */ jsxs(
5827
5799
  "div",
5828
5800
  {
@@ -5833,10 +5805,15 @@ function Table({
5833
5805
  "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
5834
5806
  "p-ds-4 space-y-ds-3",
5835
5807
  isSelected && ACTIVE_RING,
5836
- 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"
5837
5812
  ),
5838
5813
  onClick: onRowClick ? (e) => {
5839
- if (clickIsRows(e)) onRowClick(row, originalIdx);
5814
+ if (clickIsCard(e)) onRowClick(row, originalIdx);
5815
+ } : canOpenByClick ? (e) => {
5816
+ if (clickIsCard(e)) toggleCard(e.currentTarget);
5840
5817
  } : void 0,
5841
5818
  tabIndex: onRowClick ? 0 : void 0,
5842
5819
  onKeyDown: onRowClick ? (e) => {
@@ -5853,7 +5830,36 @@ function Table({
5853
5830
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
5854
5831
  /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
5855
5832
  ] }, col.key)),
5856
- 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: 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
+ ] })
5857
5863
  ]
5858
5864
  },
5859
5865
  String(id)
@@ -5953,7 +5959,8 @@ function Table({
5953
5959
  {
5954
5960
  ...marksOf(row, originalIdx),
5955
5961
  className: cn(
5956
- "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
5962
+ // an opened row's divider is under its panel (A6)
5963
+ !(expandable && isExpanded && renderExpanded) && "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
5957
5964
  // the row's background and its tint change together, so its pinned cells and the rest fade as one
5958
5965
  // (roots.css registers --gs-row-tint as a colour so it can)
5959
5966
  "transition-[background-color,--gs-row-tint]",
@@ -6038,12 +6045,21 @@ function Table({
6038
6045
  ]
6039
6046
  }
6040
6047
  ),
6041
- expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6048
+ expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: /* @__PURE__ */ jsx(
6042
6049
  "td",
6043
6050
  {
6044
6051
  colSpan: cellCount,
6045
- className: "bg-[var(--ds-bg-muted)] px-ds-6 py-ds-4 text-sm text-[var(--ds-text-primary)]",
6046
- children: /* @__PURE__ */ jsx("div", { "data-row-panel": "", className: "sticky left-ds-6", style: pins.frame ? { maxWidth: `calc(${pins.frame}px - 2 * var(--ds-space-6))` } : void 0, children: renderExpanded(row, originalIdx) })
6052
+ className: cn("py-ds-4 text-sm text-[var(--ds-text-primary)]", !pins.panel && (density === "compact" ? "px-ds-3" : "px-ds-4")),
6053
+ style: pins.panel ? { paddingLeft: pins.panel[0], paddingRight: pins.panel[1] } : void 0,
6054
+ children: /* @__PURE__ */ jsx(
6055
+ "div",
6056
+ {
6057
+ "data-row-panel": "",
6058
+ className: "sticky",
6059
+ style: pins.panel ? { left: pins.panel[0], maxWidth: pins.frame ? pins.frame - pins.panel[0] - pins.panel[1] : void 0 } : { left: 0 },
6060
+ children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) })
6061
+ }
6062
+ )
6047
6063
  }
6048
6064
  ) })
6049
6065
  ] }, String(id));
@@ -6109,6 +6125,9 @@ var indicatorInset = {
6109
6125
  md: "4px",
6110
6126
  lg: "4px"
6111
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
+ }
6112
6131
  function SegmentedControl({
6113
6132
  value,
6114
6133
  onChange: onChangeProp,
@@ -6244,22 +6263,32 @@ function SegmentedControl({
6244
6263
  onKeyDown: handleKeyDown,
6245
6264
  className: cn(
6246
6265
  "relative items-center",
6247
- fullWidth ? "flex w-full" : "inline-flex",
6266
+ // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,
6267
+ // so a row or a centred column still aligns it (the review of 30 September)
6268
+ fullWidth ? "flex w-full" : "inline-flex w-fit",
6248
6269
  containerPadding[size],
6249
6270
  containerHeight[size],
6250
6271
  "bg-[var(--ds-bg-muted)]",
6251
- "rounded-[var(--ds-radius-lg)]",
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)
6252
6277
  variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6253
6278
  control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
6254
6279
  className
6255
6280
  ),
6281
+ style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
6256
6282
  children: [
6257
6283
  /* @__PURE__ */ jsx(
6258
6284
  "div",
6259
6285
  {
6260
6286
  className: cn(
6261
- "absolute rounded-[var(--ds-radius-md)]",
6287
+ "absolute rounded-[var(--ds-segment-radius)]",
6262
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)]",
6263
6292
  variant === "default" && "shadow-[var(--ds-shadow-sm)]",
6264
6293
  "transition-all motion-reduce:transition-none"
6265
6294
  ),
@@ -6290,7 +6319,7 @@ function SegmentedControl({
6290
6319
  className: cn(
6291
6320
  "relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
6292
6321
  fullWidth && "flex-1",
6293
- "rounded-[var(--ds-radius-md)]",
6322
+ "rounded-[var(--ds-segment-radius)]",
6294
6323
  "transition-colors cursor-pointer",
6295
6324
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
6296
6325
  sizes2[size],
@@ -6736,6 +6765,7 @@ function WeekView({
6736
6765
  const hasAllDay = allDayEvents.some((d) => d.length > 0);
6737
6766
  const containerRef = useRef(null);
6738
6767
  const syncingScroll = useRef(false);
6768
+ const { ref: edgesRef, measure: measureEdges, fadeProps: edgeFade } = useScrollEdges();
6739
6769
  const handleScroll = useCallback((e) => {
6740
6770
  if (syncingScroll.current) return;
6741
6771
  syncingScroll.current = true;
@@ -6761,7 +6791,7 @@ function WeekView({
6761
6791
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "flex flex-col h-full overflow-hidden", children: [
6762
6792
  /* @__PURE__ */ jsxs("div", { className: "flex border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]", children: [
6763
6793
  /* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 border-r-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)]" }),
6764
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6794
+ /* @__PURE__ */ jsx("div", { ref: edgesRef, onScroll: measureEdges, ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6765
6795
  "div",
6766
6796
  {
6767
6797
  className: "grid",
@@ -6801,7 +6831,7 @@ function WeekView({
6801
6831
  ] }),
6802
6832
  hasAllDay && /* @__PURE__ */ jsxs("div", { className: "flex border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-muted)]/30", children: [
6803
6833
  /* @__PURE__ */ jsx("div", { className: "w-14 shrink-0 border-r-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-2xs text-[var(--ds-text-muted)] px-ds-1 py-ds-1 text-right", children: "all-day" }),
6804
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6834
+ /* @__PURE__ */ jsx("div", { ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6805
6835
  "div",
6806
6836
  {
6807
6837
  className: "grid",
@@ -6838,7 +6868,7 @@ function WeekView({
6838
6868
  },
6839
6869
  h
6840
6870
  )) }),
6841
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6871
+ /* @__PURE__ */ jsx("div", { ...edgeFade, className: "flex-1 overflow-x-auto gs-scrollbar-none", "data-week-scroll": true, children: /* @__PURE__ */ jsx(
6842
6872
  "div",
6843
6873
  {
6844
6874
  className: "relative",
@@ -7404,16 +7434,16 @@ function MonthDropdown({
7404
7434
  )
7405
7435
  ] }),
7406
7436
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-ds-1", children: MONTH_NAMES_SHORT.map((name, i) => {
7407
- const isActive = i === currentMonth && browseYear === currentYear;
7437
+ const isActive2 = i === currentMonth && browseYear === currentYear;
7408
7438
  return /* @__PURE__ */ jsx(
7409
7439
  "button",
7410
7440
  {
7411
7441
  className: cn(
7412
7442
  "px-ds-2 py-ds-1.5 text-xs font-medium rounded-[var(--ds-radius-sm)] cursor-pointer transition-colors",
7413
7443
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
7414
- 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)]"
7415
7445
  ),
7416
- "data-current": isActive || void 0,
7446
+ "data-current": isActive2 || void 0,
7417
7447
  onClick: () => {
7418
7448
  returnFocus();
7419
7449
  onSelect(new Date(browseYear, i, 1));
@@ -7882,7 +7912,6 @@ function AvatarGroup({ items, max = 4, size = "sm", ring = "canvas", onOverflowC
7882
7912
  alt: item.alt ?? item.name ?? initials,
7883
7913
  initials,
7884
7914
  size,
7885
- status: item.status,
7886
7915
  color: item.color,
7887
7916
  className: ringClass
7888
7917
  },
@@ -8002,7 +8031,7 @@ function TableToolbar({
8002
8031
  const iconSize = size === "sm" ? 14 : 16;
8003
8032
  const count = activeFilterCount ?? activeFilters?.length ?? 0;
8004
8033
  const hasChips = !!activeFilters?.length;
8005
- const inPane = useContext(SplitPaneCtx);
8034
+ const inPane = !!useContext(SplitPaneCtx);
8006
8035
  const searchFills = inPane || !primaryFilter && !filters && !actions;
8007
8036
  const hasControls = !!(search || primaryFilter || filters || actions);
8008
8037
  const noun = (n) => n === 1 ? resultNoun[0] : resultNoun[1];
@@ -8201,6 +8230,7 @@ function VerticalLayout({ items, className }) {
8201
8230
  ] }, i)) });
8202
8231
  }
8203
8232
  function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 200, className, ref, pager }) {
8233
+ useScrollsInside();
8204
8234
  useWholeListCheck("Timeline", allItems.length, pager);
8205
8235
  const items = pagerPart(allItems, pager);
8206
8236
  const measureRef = useRef(null);
@@ -8287,12 +8317,27 @@ var Collapsible = Object.assign(CollapsibleRoot, {
8287
8317
  Trigger: CollapsibleTrigger,
8288
8318
  Content: CollapsibleContent
8289
8319
  });
8320
+ var SYMBOL = /[⌘⇧⌥⌃⎋⏎↵⌫⌦⇥↑↓←→]/u;
8321
+ function keyParts(text) {
8322
+ const parts = [];
8323
+ let run = "";
8324
+ for (const ch of Array.from(text)) {
8325
+ if (SYMBOL.test(ch)) {
8326
+ if (run.trim()) parts.push(run.trim());
8327
+ run = "";
8328
+ parts.push(ch);
8329
+ } else run += ch;
8330
+ }
8331
+ if (run.trim()) parts.push(run.trim());
8332
+ return parts;
8333
+ }
8290
8334
  function Kbd({ children, className }) {
8335
+ const parts = typeof children === "string" ? keyParts(children) : null;
8291
8336
  return /* @__PURE__ */ jsx(
8292
8337
  "kbd",
8293
8338
  {
8294
8339
  className: cn(
8295
- "inline-flex items-center justify-center min-w-[2.5em] px-ds-1.5 py-ds-0.5",
8340
+ "inline-flex items-center justify-center gap-[0.25em] min-w-[2.5em] px-ds-1.5 py-ds-0.5",
8296
8341
  "text-xs leading-none font-[family-name:var(--ds-font-mono)]",
8297
8342
  "text-[var(--ds-text-secondary)]",
8298
8343
  "bg-[var(--ds-bg-surface)]",
@@ -8301,16 +8346,30 @@ function Kbd({ children, className }) {
8301
8346
  "rounded-[var(--ds-radius-sm)]",
8302
8347
  className
8303
8348
  ),
8304
- children
8349
+ children: parts && parts.length > 1 ? parts.map((p, i) => /* @__PURE__ */ jsx("span", { children: p }, i)) : children
8305
8350
  }
8306
8351
  );
8307
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
+ }
8308
8358
  function TruncLabel2({ children }) {
8309
8359
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
8310
8360
  const label = /* @__PURE__ */ jsx("span", { ref, className: "truncate", children });
8311
8361
  if (!isTruncated) return label;
8312
8362
  return /* @__PURE__ */ jsx(Tooltip, { content: children, children: label });
8313
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);
8314
8373
  function allDescendantIds(node) {
8315
8374
  const ids = [node.id];
8316
8375
  if (node.children) {
@@ -8325,13 +8384,26 @@ function TreeView({
8325
8384
  checkable,
8326
8385
  defaultExpanded = [],
8327
8386
  defaultSelected = [],
8387
+ selected: selectedProp,
8328
8388
  defaultChecked = [],
8329
8389
  onSelect,
8330
8390
  onCheck,
8331
8391
  className
8332
8392
  }) {
8333
- const [expanded, setExpanded] = useState(new Set(defaultExpanded));
8334
- 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]);
8335
8407
  const [checked, setChecked] = useState(new Set(defaultChecked));
8336
8408
  const baseId = useId();
8337
8409
  const toggleExpand = useCallback((id) => {
@@ -8345,20 +8417,18 @@ function TreeView({
8345
8417
  const handleSelect = useCallback(
8346
8418
  (id) => {
8347
8419
  if (!selectable) return;
8348
- setSelected((prev) => {
8349
- let next;
8350
- if (multiSelect) {
8351
- next = new Set(prev);
8352
- if (next.has(id)) next.delete(id);
8353
- else next.add(id);
8354
- } else {
8355
- next = prev.has(id) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([id]);
8356
- }
8357
- onSelect?.(Array.from(next));
8358
- return next;
8359
- });
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));
8360
8430
  },
8361
- [selectable, multiSelect, onSelect]
8431
+ [selectable, multiSelect, selected, selectedProp, onSelect]
8362
8432
  );
8363
8433
  const handleCheck = useCallback(
8364
8434
  (node) => {
@@ -8443,6 +8513,7 @@ function TreeView({
8443
8513
  baseId,
8444
8514
  expanded,
8445
8515
  selected,
8516
+ holding,
8446
8517
  checked,
8447
8518
  checkable,
8448
8519
  selectable,
@@ -8461,6 +8532,7 @@ function TreeNodeRow({
8461
8532
  baseId,
8462
8533
  expanded,
8463
8534
  selected,
8535
+ holding,
8464
8536
  checkable,
8465
8537
  selectable,
8466
8538
  toggleExpand,
@@ -8472,6 +8544,7 @@ function TreeNodeRow({
8472
8544
  const hasChildren = !!node.children?.length;
8473
8545
  const isExpanded = expanded.has(node.id);
8474
8546
  const isSelected = selected.has(node.id);
8547
+ const holdsSelection = !isSelected && holding.has(node.id);
8475
8548
  const checkState = checkable ? getCheckState(node) : "unchecked";
8476
8549
  return /* @__PURE__ */ jsxs("div", { role: "treeitem", "aria-expanded": hasChildren ? isExpanded : void 0, "aria-selected": selectable ? isSelected : void 0, children: [
8477
8550
  /* @__PURE__ */ jsxs(
@@ -8487,6 +8560,9 @@ function TreeNodeRow({
8487
8560
  // transparent placeholder so content never shifts on selection.
8488
8561
  "border-l-[length:var(--ds-tree-active-rail)]",
8489
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",
8490
8566
  node.disabled && "opacity-disabled pointer-events-none"
8491
8567
  ),
8492
8568
  style: { paddingLeft: `calc(var(--ds-space-3) * ${depth} + var(--ds-space-2))` },
@@ -8513,7 +8589,7 @@ function TreeNodeRow({
8513
8589
  {
8514
8590
  name: "chevron-right",
8515
8591
  size: 14,
8516
- 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")
8517
8593
  }
8518
8594
  )
8519
8595
  }
@@ -8528,7 +8604,7 @@ function TreeNodeRow({
8528
8604
  }
8529
8605
  }
8530
8606
  ),
8531
- 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)]") }),
8532
8608
  /* @__PURE__ */ jsx(TruncLabel2, { children: node.label })
8533
8609
  ]
8534
8610
  }
@@ -8541,6 +8617,7 @@ function TreeNodeRow({
8541
8617
  baseId,
8542
8618
  expanded,
8543
8619
  selected,
8620
+ holding,
8544
8621
  checked: /* @__PURE__ */ new Set(),
8545
8622
  checkable,
8546
8623
  selectable,
@@ -8898,7 +8975,8 @@ function ChatInputBar({ onSend, busy, onStop, onAttach, onVoice, placeholder })
8898
8975
  className: cn(
8899
8976
  "flex flex-col",
8900
8977
  "rounded-[var(--ds-radius-lg)]",
8901
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
8978
+ // Sprint 25.14 A20: the composer is a field, its edge at 3:1 like every other control's
8979
+ "border-[length:var(--ds-border-width)] border-[var(--ds-input-border)]",
8902
8980
  "bg-[var(--ds-input-bg)]",
8903
8981
  "shadow-[var(--ds-input-shadow)]",
8904
8982
  FOCUS_WITHIN_RING,
@@ -9191,6 +9269,28 @@ function useCornerInset(corner, inset, priority, target) {
9191
9269
  return claimCornerInset(corner, inset, priority, el);
9192
9270
  }, [corner, inset, priority, target]);
9193
9271
  }
9272
+ function Sheet({ open, onClose: onCloseProp, title, children, footer, dirty, onDiscard, snapPoints = [0.5, 0.92], container, flush, keepMounted = false, id, labelledBy }) {
9273
+ const { marked, mark } = useOverlayMark(open);
9274
+ const guard = useDirtyGuard(dirty ?? marked, onCloseProp, onDiscard);
9275
+ return /* @__PURE__ */ jsx(
9276
+ DrawerPanel,
9277
+ {
9278
+ form: "sheet",
9279
+ open,
9280
+ onClose: guard.requestClose,
9281
+ title,
9282
+ footer,
9283
+ container,
9284
+ flush,
9285
+ snapPoints,
9286
+ keepMounted,
9287
+ id,
9288
+ labelledBy,
9289
+ confirm: /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
9290
+ children: /* @__PURE__ */ jsx(DrawerFooterCtx, { value: null, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children }) }) })
9291
+ }
9292
+ );
9293
+ }
9194
9294
  var DEFAULT_OFFSET = "var(--ds-space-4)";
9195
9295
  var CORNER_PANEL_ANCHOR_INSET = "calc(var(--ds-space-12) + var(--ds-space-3))";
9196
9296
  function CornerPanel({
@@ -9475,9 +9575,10 @@ var StackedList = forwardRef(function StackedList2({
9475
9575
  hover,
9476
9576
  pager
9477
9577
  }, ref) {
9578
+ useScrollsInside();
9478
9579
  const isPhone = useBreakpoint("sm");
9479
9580
  const inCard = useContext(CardSurfaceCtx);
9480
- const inPane = useContext(SplitPaneCtx);
9581
+ const inPane = !!useContext(SplitPaneCtx);
9481
9582
  const variant = variantProp ?? (flush || hover === "title" ? "articles" : "list");
9482
9583
  const legacy = flush !== void 0 || hover !== void 0;
9483
9584
  useEffect(() => {
@@ -10144,6 +10245,8 @@ var TimeViewport = forwardRef(function TimeViewport2({
10144
10245
  className
10145
10246
  }, ref) {
10146
10247
  const scrollerRef = useRef(null);
10248
+ const rootRef = useRef(null);
10249
+ const fill2 = useFillHeight(rootRef);
10147
10250
  const headerRef = useRef(null);
10148
10251
  const rowsRef = useRef(null);
10149
10252
  const [frame, setFrame] = useState({ width: 0, header: 0 });
@@ -10366,23 +10469,26 @@ var TimeViewport = forwardRef(function TimeViewport2({
10366
10469
  };
10367
10470
  const headerNode = header === void 0 ? /* @__PURE__ */ jsx(TimeScaleHeader, {}) : header;
10368
10471
  const hasHeader = headerNode != null || labelHeader != null;
10369
- 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: [
10370
- onZoomChange && levels.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
10371
- /* @__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 }) }),
10372
- /* @__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] }),
10373
- /* @__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 }) })
10374
- ] }),
10375
- /* @__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 }) }),
10376
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: todayAtX === null, onClick: () => {
10377
- if (todayAtX !== null) setLeft(todayAtX - visibleWidth / 2);
10378
- }, children: "Today" }),
10379
- /* @__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 }) }),
10380
- toolbar
10381
- ] });
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
+ );
10382
10488
  return (
10383
10489
  // Inline-size containment: the rows' width never widens the frame (in a grid or flex parent it would, and the
10384
10490
  // wider frame would extend the scale again). The viewport takes the width it is given and scrolls inside it.
10385
- /* @__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: [
10386
10492
  bar,
10387
10493
  /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1", children: [
10388
10494
  /* @__PURE__ */ jsx(
@@ -10658,7 +10764,7 @@ function Cue({ name, size }) {
10658
10764
  function Legend({ statuses }) {
10659
10765
  const entries = Object.entries(statuses);
10660
10766
  if (!entries.length) return null;
10661
- 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]) => {
10662
10768
  const tone = TONES[s.tone] ?? TONES.neutral;
10663
10769
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
10664
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 }) }),
@@ -10706,6 +10812,7 @@ function Gantt({
10706
10812
  "aria-label": ariaLabel,
10707
10813
  className
10708
10814
  }) {
10815
+ useScrollsInside();
10709
10816
  const [zoomState, setZoomState] = useState(defaultZoom);
10710
10817
  const zoom = zoomProp ?? zoomState;
10711
10818
  const changeZoom = (next) => {
@@ -10999,6 +11106,8 @@ function Gantt({
10999
11106
  };
11000
11107
  const legend = /* @__PURE__ */ jsx(Legend, { statuses });
11001
11108
  const legendApart = toolbar === false && hasStatuses;
11109
+ const apartRef = useRef(null);
11110
+ const apartFill = useFillHeight(apartRef, legendApart);
11002
11111
  const chart = /* @__PURE__ */ jsx(
11003
11112
  TimeViewport,
11004
11113
  {
@@ -11008,6 +11117,7 @@ function Gantt({
11008
11117
  labelHeader,
11009
11118
  toolbar: toolbar === false ? false : /* @__PURE__ */ jsxs(Fragment, { children: [
11010
11119
  toolbar,
11120
+ hasStatuses && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "min-w-0 flex-1" }),
11011
11121
  legend
11012
11122
  ] }),
11013
11123
  tabStop: false,
@@ -11036,9 +11146,9 @@ function Gantt({
11036
11146
  }
11037
11147
  );
11038
11148
  if (!legendApart) return chart;
11039
- return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 w-full min-w-0 flex-col", className), children: [
11040
- /* @__PURE__ */ jsx("div", { className: "flex shrink-0 px-ds-2 py-ds-1.5", children: legend }),
11041
- 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 })
11042
11152
  ] });
11043
11153
  }
11044
11154
  var DEFAULT_OFFSET2 = "var(--ds-space-4)";
@@ -11464,14 +11574,14 @@ function TabButton({
11464
11574
  hasPanel
11465
11575
  }) {
11466
11576
  const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !truncate && orientation !== "horizontal" });
11467
- const isActive = activeTab === id;
11577
+ const isActive2 = activeTab === id;
11468
11578
  const tabId = `ds-tab-${id}`;
11469
11579
  const panelId = hasPanel === false ? void 0 : `ds-tabpanel-${id}`;
11470
11580
  const badgeBg = orientation === "vertical" ? "bg-[var(--ds-nav-v-badge-bg)]" : "bg-[var(--ds-nav-h-badge-bg)]";
11471
11581
  const countBadge = count !== void 0 ? /* @__PURE__ */ jsx("span", { className: cn(
11472
11582
  orientation === "vertical" ? "ml-ds-2" : "",
11473
11583
  "px-ds-1.5 py-ds-0.5 text-xs rounded-[var(--ds-radius-sm)]",
11474
- 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)]"
11475
11585
  ), children: count }) : null;
11476
11586
  if (orientation === "vertical") {
11477
11587
  const btn2 = /* @__PURE__ */ jsxs(
@@ -11480,10 +11590,10 @@ function TabButton({
11480
11590
  type: "button",
11481
11591
  id: tabId,
11482
11592
  role: "tab",
11483
- "aria-selected": isActive,
11593
+ "aria-selected": isActive2,
11484
11594
  "aria-controls": panelId,
11485
11595
  "aria-disabled": disabled || void 0,
11486
- tabIndex: isActive ? 0 : -1,
11596
+ tabIndex: isActive2 ? 0 : -1,
11487
11597
  onClick: disabled ? void 0 : () => onTabChange(id),
11488
11598
  onKeyDown,
11489
11599
  className: cn(
@@ -11495,7 +11605,7 @@ function TabButton({
11495
11605
  // Every item carries the rail width as a transparent placeholder so the
11496
11606
  // label does not shift when the rail appears on the active item.
11497
11607
  "border-l-[length:var(--ds-nav-v-active-rail)]",
11498
- 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)]",
11499
11609
  className
11500
11610
  ),
11501
11611
  children: [
@@ -11516,26 +11626,29 @@ function TabButton({
11516
11626
  type: "button",
11517
11627
  id: tabId,
11518
11628
  role: "tab",
11519
- "aria-selected": isActive,
11629
+ "aria-selected": isActive2,
11520
11630
  "aria-controls": panelId,
11521
11631
  "aria-disabled": disabled || void 0,
11522
- tabIndex: isActive ? 0 : -1,
11632
+ tabIndex: isActive2 ? 0 : -1,
11523
11633
  onClick: disabled ? void 0 : () => onTabChange(id),
11524
11634
  onKeyDown,
11525
11635
  className: cn(
11526
- "px-ds-4 py-ds-2.5 text-sm font-medium",
11636
+ // Sprint 25.14 A38: a flex box, so the label sits in the middle of the tab. On a text line an icon's baseline
11637
+ // pushed the line 3 px taller and the label to its top (R28-72); a tab with an icon is now as tall as one
11638
+ // without, as a truncating tab (flex already) always was.
11639
+ "flex items-center justify-center px-ds-4 py-ds-2.5 text-sm font-medium",
11527
11640
  // never wrap: the strip scrolls when the tabs do not fit (round 17, 54.4)
11528
11641
  !fullWidth && !truncate && "shrink-0 whitespace-nowrap",
11529
11642
  disabled ? "cursor-not-allowed opacity-disabled" : "cursor-pointer",
11530
11643
  "transition-colors duration-[var(--ds-transition-speed)]",
11531
11644
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
11532
11645
  fullWidth && "flex-1",
11533
- truncate && "flex-1 min-w-0 flex items-center",
11646
+ truncate && "flex-1 min-w-0 justify-start",
11534
11647
  "rounded-[var(--ds-nav-h-item-radius)]",
11535
- 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)]",
11536
11649
  className
11537
11650
  ),
11538
- 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,
11539
11652
  children: truncate ? /* @__PURE__ */ jsxs(Fragment, { children: [
11540
11653
  icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16, className: "shrink-0" }),
11541
11654
  /* @__PURE__ */ jsx("span", { ref: truncRef, className: "truncate min-w-0 flex-1", children: label }),
@@ -11764,241 +11877,6 @@ function SettingsLayout({
11764
11877
  /* @__PURE__ */ jsx("div", { "data-settings-panel": "", className: cn("flex-1", PANEL_CLASSES), style: PANEL_STYLE, children })
11765
11878
  ] });
11766
11879
  }
11767
- function SeparatorIcon({ type, size }) {
11768
- const iconSize = size === "sm" ? 10 : 12;
11769
- if (type === "chevron") return /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: iconSize });
11770
- if (type === "arrow") return /* @__PURE__ */ jsx(Icon, { name: "arrow-right", size: iconSize });
11771
- if (type === "dot") return /* @__PURE__ */ jsx("span", { children: "\xB7" });
11772
- return /* @__PURE__ */ jsx("span", { children: "/" });
11773
- }
11774
- var Breadcrumbs = forwardRef(function Breadcrumbs2({
11775
- items,
11776
- separator = "chevron",
11777
- variant = "inline",
11778
- itemVariant,
11779
- size = "md",
11780
- showIcons = false,
11781
- maxItems,
11782
- bleed = false,
11783
- contentMaxWidth,
11784
- className,
11785
- container
11786
- }, ref) {
11787
- const [dropdownOpen, setDropdownOpen] = useState(false);
11788
- const [activeIdx, setActiveIdx] = useState(0);
11789
- const ellipsisBtnRef = useRef(null);
11790
- const dropdownRef = useRef(null);
11791
- const closeDropdown = useCallback(() => setDropdownOpen(false), []);
11792
- const { isTop, returnFocus } = usePopupFocus(dropdownOpen, { trigger: ellipsisBtnRef, panel: dropdownRef, close: closeDropdown, tabExits: "any" });
11793
- const pos = usePosition(ellipsisBtnRef, dropdownRef, dropdownOpen, { side: "bottom", align: "start", offset: 4, flip: true });
11794
- const effectiveMaxItems = maxItems ? Math.max(maxItems, 2) : void 0;
11795
- const shouldCollapse = effectiveMaxItems && items.length > effectiveMaxItems;
11796
- const collapsedItems = shouldCollapse ? items.slice(1, -(effectiveMaxItems - 1)) : [];
11797
- const visibleItems = shouldCollapse ? [...items.slice(0, 1), null, ...items.slice(-(effectiveMaxItems - 1))] : items;
11798
- useEffect(() => {
11799
- if (!dropdownOpen) return;
11800
- const handler = (e) => {
11801
- const target = e.target;
11802
- if (ellipsisBtnRef.current?.contains(target) || dropdownRef.current?.contains(target)) return;
11803
- setDropdownOpen(false);
11804
- };
11805
- const onKeyDown = (e) => {
11806
- if (e.key !== "Escape" || e.defaultPrevented || !isTop()) return;
11807
- e.preventDefault();
11808
- returnFocus();
11809
- setDropdownOpen(false);
11810
- };
11811
- document.addEventListener("mousedown", handler);
11812
- document.addEventListener("keydown", onKeyDown);
11813
- return () => {
11814
- document.removeEventListener("mousedown", handler);
11815
- document.removeEventListener("keydown", onKeyDown);
11816
- };
11817
- }, [dropdownOpen, isTop, returnFocus]);
11818
- useEffect(() => {
11819
- if (!dropdownOpen) return;
11820
- const id = requestAnimationFrame(() => dropdownRef.current?.querySelector(`[data-index="${activeIdx}"]`)?.focus());
11821
- return () => cancelAnimationFrame(id);
11822
- }, [dropdownOpen, activeIdx]);
11823
- function onMenuKeyDown(e) {
11824
- const last = collapsedItems.length - 1;
11825
- 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;
11826
- if (next === null) return;
11827
- e.preventDefault();
11828
- setActiveIdx(next);
11829
- }
11830
- const textClass = size === "sm" ? "text-xs" : "text-sm";
11831
- const gapClass = size === "sm" ? "gap-ds-1" : "gap-ds-1.5";
11832
- const iconSize = size === "sm" ? 12 : 14;
11833
- const isContainedBleed = variant === "contained" && bleed;
11834
- const wrapperClasses = cn(
11835
- variant === "contained" && !bleed && cn(
11836
- "bg-[var(--ds-bg-muted)] rounded-[var(--ds-radius-md)]",
11837
- size === "sm" ? "py-ds-1 px-ds-2" : "py-ds-1.5 px-ds-3"
11838
- ),
11839
- !isContainedBleed && className
11840
- );
11841
- function renderItem(item, _index, isLast) {
11842
- const isPill = itemVariant === "pill" || variant === "pill" && !itemVariant;
11843
- const insideContained = variant === "contained";
11844
- const pillClasses = isPill ? cn(
11845
- "rounded-[var(--ds-radius-full)]",
11846
- insideContained ? "bg-[var(--ds-bg-surface)]" : "bg-[var(--ds-bg-muted)]",
11847
- size === "sm" ? "px-ds-2 py-[2px]" : "px-ds-2.5 py-ds-0.5"
11848
- ) : "";
11849
- const content = /* @__PURE__ */ jsxs(Fragment, { children: [
11850
- showIcons && item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon, size: iconSize, className: "shrink-0" }),
11851
- /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label })
11852
- ] });
11853
- if (isLast) {
11854
- return /* @__PURE__ */ jsx(
11855
- "span",
11856
- {
11857
- "aria-current": "page",
11858
- className: cn(
11859
- "font-medium text-[var(--ds-text-primary)] flex items-center",
11860
- gapClass,
11861
- pillClasses
11862
- ),
11863
- children: content
11864
- }
11865
- );
11866
- }
11867
- return /* @__PURE__ */ jsx(
11868
- "a",
11869
- {
11870
- href: item.href ?? "#",
11871
- onClick: (e) => {
11872
- e.preventDefault();
11873
- item.onClick?.();
11874
- },
11875
- className: cn(
11876
- "text-[var(--ds-text-secondary)] flex items-center",
11877
- "hover:text-[var(--ds-text-primary)]",
11878
- "transition-colors duration-[var(--ds-transition-speed)]",
11879
- gapClass,
11880
- pillClasses
11881
- ),
11882
- children: content
11883
- }
11884
- );
11885
- }
11886
- const listContent = /* @__PURE__ */ jsx("ol", { className: cn("flex items-center flex-wrap", gapClass, textClass), children: visibleItems.map((item, i) => {
11887
- if (item === null) {
11888
- return /* @__PURE__ */ jsxs("li", { className: cn("flex items-center", gapClass), children: [
11889
- 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 }) }),
11890
- /* @__PURE__ */ jsx(
11891
- "button",
11892
- {
11893
- type: "button",
11894
- ref: ellipsisBtnRef,
11895
- onClick: () => {
11896
- if (!dropdownOpen) setActiveIdx(0);
11897
- setDropdownOpen(!dropdownOpen);
11898
- },
11899
- "aria-label": "Show hidden breadcrumbs",
11900
- "aria-expanded": dropdownOpen,
11901
- "aria-haspopup": "menu",
11902
- className: cn(
11903
- "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]",
11904
- "transition-colors duration-[var(--ds-transition-speed)] cursor-pointer",
11905
- "flex items-center",
11906
- (itemVariant === "pill" || variant === "pill" && !itemVariant) && cn(
11907
- "rounded-[var(--ds-radius-full)]",
11908
- variant === "contained" ? "bg-[var(--ds-bg-surface)]" : "bg-[var(--ds-bg-muted)]",
11909
- size === "sm" ? "px-ds-2 py-[2px]" : "px-ds-2.5 py-ds-0.5"
11910
- )
11911
- ),
11912
- children: "\xB7\xB7\xB7"
11913
- }
11914
- )
11915
- ] }, "__ellipsis");
11916
- }
11917
- const isLast = i === visibleItems.length - 1;
11918
- return /* @__PURE__ */ jsxs("li", { className: cn("flex items-center", gapClass), children: [
11919
- 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 }) }),
11920
- renderItem(item, i, isLast)
11921
- ] }, i);
11922
- }) });
11923
- const dropdown = dropdownOpen && collapsedItems.length > 0 && createPortal(
11924
- /* @__PURE__ */ jsx(
11925
- "div",
11926
- {
11927
- ref: dropdownRef,
11928
- role: "menu",
11929
- "aria-label": "Hidden breadcrumbs",
11930
- onKeyDown: onMenuKeyDown,
11931
- className: cn(
11932
- "fixed z-[var(--ds-z-popover)] min-w-[160px] max-h-60 overflow-auto",
11933
- "bg-[var(--ds-bg-surface-elevated)]",
11934
- "[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]",
11935
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
11936
- "rounded-[var(--ds-radius-md)]",
11937
- "shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]",
11938
- "py-ds-1"
11939
- ),
11940
- style: { top: pos.top, left: pos.left },
11941
- children: collapsedItems.map((ci, idx) => /* @__PURE__ */ jsxs(
11942
- "button",
11943
- {
11944
- type: "button",
11945
- "data-index": idx,
11946
- role: "menuitem",
11947
- tabIndex: idx === activeIdx ? 0 : -1,
11948
- className: cn(
11949
- "flex items-center w-full text-left text-sm cursor-pointer",
11950
- "px-ds-3 py-ds-1.5 transition-colors",
11951
- "text-[var(--ds-text-secondary)]",
11952
- "hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]",
11953
- "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)]",
11954
- gapClass
11955
- ),
11956
- onClick: () => {
11957
- setDropdownOpen(false);
11958
- ci.onClick?.();
11959
- },
11960
- children: [
11961
- showIcons && ci.icon && /* @__PURE__ */ jsx(Icon, { name: ci.icon, size: iconSize, className: "shrink-0" }),
11962
- ci.label
11963
- ]
11964
- },
11965
- idx
11966
- ))
11967
- }
11968
- ),
11969
- container ?? document.body
11970
- );
11971
- if (isContainedBleed) {
11972
- return /* @__PURE__ */ jsxs(
11973
- "nav",
11974
- {
11975
- ref,
11976
- "aria-label": "Breadcrumb",
11977
- className: cn(
11978
- "w-full bg-[var(--ds-bg-muted)]",
11979
- // Hairline under the band: it sits within ~1.2:1 of both canvas and a tinted header, so the edge needs a line.
11980
- "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
11981
- className
11982
- ),
11983
- children: [
11984
- /* @__PURE__ */ jsx(
11985
- "div",
11986
- {
11987
- className: cn("w-full mx-auto px-ds-6", size === "sm" ? "py-ds-1" : "py-ds-1.5"),
11988
- style: { maxWidth: contentMaxWidth ?? "var(--ds-bp-xl)" },
11989
- children: listContent
11990
- }
11991
- ),
11992
- dropdown
11993
- ]
11994
- }
11995
- );
11996
- }
11997
- return /* @__PURE__ */ jsxs("nav", { ref, "aria-label": "Breadcrumb", className: wrapperClasses, children: [
11998
- listContent,
11999
- dropdown
12000
- ] });
12001
- });
12002
11880
  var HeaderTabRowContext = createContext(null);
12003
11881
  function useHeaderTabRow() {
12004
11882
  return useContext(HeaderTabRowContext);
@@ -12051,13 +11929,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12051
11929
  const isLast = i === steps.length - 1;
12052
11930
  return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-start min-w-0", isLast ? "flex-none" : "flex-1"), children: [
12053
11931
  /* @__PURE__ */ jsxs("div", { className: "flex items-center w-full", children: [
12054
- /* @__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 }),
12055
11933
  !isLast && /* @__PURE__ */ jsx(
12056
11934
  "div",
12057
11935
  {
12058
11936
  className: cn(
12059
11937
  "flex-1 h-0.5 mx-ds-2 transition-colors",
12060
- 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)]"
12061
11939
  )
12062
11940
  }
12063
11941
  )
@@ -12084,13 +11962,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12084
11962
  const isLast = i === steps.length - 1;
12085
11963
  return /* @__PURE__ */ jsxs("div", { className: "flex gap-ds-3", children: [
12086
11964
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center", children: [
12087
- /* @__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 }),
12088
11966
  !isLast && /* @__PURE__ */ jsx(
12089
11967
  "div",
12090
11968
  {
12091
11969
  className: cn(
12092
11970
  "w-0.5 flex-1 min-h-6 transition-colors",
12093
- 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)]"
12094
11972
  )
12095
11973
  }
12096
11974
  )
@@ -12131,13 +12009,13 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12131
12009
  )
12132
12010
  }
12133
12011
  ),
12134
- /* @__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 }),
12135
12013
  /* @__PURE__ */ jsx(
12136
12014
  "div",
12137
12015
  {
12138
12016
  className: cn(
12139
12017
  "flex-1 h-0.5 transition-colors",
12140
- 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)]"
12141
12019
  )
12142
12020
  }
12143
12021
  )
@@ -12160,13 +12038,14 @@ function Stepper({ steps, currentStep, onStepChange, orientation = "horizontal",
12160
12038
  }
12161
12039
  );
12162
12040
  }
12163
- function StepCircle({ status, index, onClick, clickable }) {
12041
+ function StepCircle({ status, index, label, onClick, clickable }) {
12164
12042
  return /* @__PURE__ */ jsx(
12165
12043
  "button",
12166
12044
  {
12167
12045
  type: "button",
12168
12046
  onClick,
12169
12047
  disabled: !clickable,
12048
+ "aria-label": `Step ${index + 1}: ${label}${status === "complete" ? ", completed" : ""}`,
12170
12049
  "aria-current": status === "current" ? "step" : void 0,
12171
12050
  className: cn(
12172
12051
  "flex items-center justify-center shrink-0 w-8 h-8 rounded-[var(--ds-radius-full)] text-xs font-bold transition-colors",
@@ -12177,7 +12056,9 @@ function StepCircle({ status, index, onClick, clickable }) {
12177
12056
  status === "current" && `border-2 ${ACTIVE_RING} text-[var(--ds-accent-primary)] bg-[var(--ds-bg-surface)]`,
12178
12057
  status === "error" && "bg-[var(--ds-status-error)] text-[var(--ds-text-on-error)]",
12179
12058
  status === "loading" && `border-2 ${ACTIVE_RING} text-[var(--ds-accent-primary)] bg-[var(--ds-bg-surface)] animate-pulse`,
12180
- 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)]"
12181
12062
  ),
12182
12063
  children: status === "complete" ? /* @__PURE__ */ jsx(Icon, { name: "check", size: 14 }) : status === "error" ? /* @__PURE__ */ jsx(Icon, { name: "warning", size: 14 }) : index + 1
12183
12064
  }
@@ -12509,17 +12390,13 @@ function TruncLabel3({ children, className, side = "right", container, lines = 1
12509
12390
  if (!isTruncated) return label;
12510
12391
  return /* @__PURE__ */ jsx(Tooltip, { content: children, side, delay: 200, container, children: label });
12511
12392
  }
12512
- function hasActiveDescendant(items) {
12513
- return items.some(
12514
- (it) => it.active || it.children && hasActiveDescendant(it.children)
12515
- );
12516
- }
12393
+ var isActive = (item) => !!item.active;
12517
12394
  function collectInitialExpanded(items, prefix = "") {
12518
12395
  const keys = [];
12519
12396
  for (const item of items) {
12520
12397
  if (!item.children?.length) continue;
12521
12398
  const key = prefix ? `${prefix}/${item.label}` : item.label;
12522
- if (item.defaultExpanded || hasActiveDescendant(item.children)) {
12399
+ if (item.defaultExpanded || holdsCurrent(item.children, isActive)) {
12523
12400
  keys.push(key);
12524
12401
  }
12525
12402
  keys.push(...collectInitialExpanded(item.children, key));
@@ -12741,7 +12618,7 @@ function SidebarNav({
12741
12618
  function renderCompactItem(item, index, parentKey) {
12742
12619
  const hasChildren = !!item.children?.length;
12743
12620
  const key = makeKey(item.label, parentKey);
12744
- const childActive = hasChildren && hasActiveDescendant(item.children);
12621
+ const childActive = hasChildren && holdsCurrent(item.children, isActive);
12745
12622
  const isOpen = openFlyout === key;
12746
12623
  return /* @__PURE__ */ jsx(
12747
12624
  CompactNavItem,
@@ -12775,7 +12652,7 @@ function SidebarNav({
12775
12652
  return renderLeaf(item, depth, index);
12776
12653
  }
12777
12654
  const isExpanded = expandedItems.has(key);
12778
- const childActive = hasActiveDescendant(item.children);
12655
+ const childActive = holdsCurrent(item.children, isActive);
12779
12656
  const isRoot = depth === 0;
12780
12657
  const rootPy = isSm ? "py-ds-1.5" : "py-ds-2";
12781
12658
  const childPy = isSm ? "py-ds-1" : "py-ds-1.5";
@@ -13345,7 +13222,7 @@ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", du
13345
13222
  const noteShown = useCallback((id, variant, action, fresh) => {
13346
13223
  if (runs.current.size) shownDuringRuns.current.push({ seq: ++showSeq.current, at: Date.now(), id, variant, action, fresh });
13347
13224
  }, []);
13348
- const update = useCallback((id, message, options = {}) => {
13225
+ const update2 = useCallback((id, message, options = {}) => {
13349
13226
  const list2 = entries.current;
13350
13227
  const at = list2.findIndex((t2) => t2.id === id);
13351
13228
  if (at === -1) return;
@@ -13395,21 +13272,21 @@ function ToastProvider({ children, maxVisible = 3, position = "bottom-right", du
13395
13272
  success: (m, o) => toast(m, { ...o, variant: "success" }),
13396
13273
  warning: (m, o) => toast(m, { ...o, variant: "warning" }),
13397
13274
  error: (m, o) => toast(m, { ...o, variant: "error" }),
13398
- update,
13275
+ update: update2,
13399
13276
  dismiss: remove,
13400
13277
  dismissAll,
13401
13278
  promise: async (promise, messages, options = {}) => {
13402
13279
  const id = toast(messages.loading, { ...options, variant: "default", duration: Infinity });
13403
13280
  try {
13404
13281
  const value = await promise;
13405
- 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 });
13406
13283
  return value;
13407
13284
  } catch (err) {
13408
- 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 });
13409
13286
  throw err;
13410
13287
  }
13411
13288
  }
13412
- }), [toast, update, remove, dismissAll, duration]);
13289
+ }), [toast, update2, remove, dismissAll, duration]);
13413
13290
  const ledger = useMemo(() => ({
13414
13291
  begin: () => {
13415
13292
  const run = ++runSeq.current;
@@ -13737,18 +13614,20 @@ function pageMaxWidth(resolved) {
13737
13614
  }
13738
13615
  var paddingClasses = {
13739
13616
  none: "",
13740
- default: "px-ds-6",
13617
+ // the page's gutter, which an overlay's header, body and footer share (Sprint 25.14 A36)
13618
+ default: "px-[var(--ds-page-gutter)]",
13741
13619
  wide: "px-ds-10"
13742
13620
  };
13621
+ var FILLED_BOTTOM = "has-[[data-fill]]:pb-[var(--ds-page-gutter)]";
13743
13622
  var verticalPaddingClasses = {
13744
13623
  none: "",
13745
- default: "pt-ds-8 pb-ds-12",
13746
- 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}`
13747
13626
  };
13748
13627
  var bottomPaddingClasses = {
13749
13628
  none: "",
13750
- default: "pb-ds-12",
13751
- spacious: "pb-ds-16"
13629
+ default: `pb-ds-12 ${FILLED_BOTTOM}`,
13630
+ spacious: `pb-ds-16 ${FILLED_BOTTOM}`
13752
13631
  };
13753
13632
  var bottomPaddingValues = {
13754
13633
  none: "0px",
@@ -13771,6 +13650,38 @@ function useContentWidth(width) {
13771
13650
  return () => request(null);
13772
13651
  }, [request, width]);
13773
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
+ }
13774
13685
  function usePageContainer() {
13775
13686
  return useContext(PageContainerContext);
13776
13687
  }
@@ -13847,6 +13758,7 @@ var PageContainer = forwardRef(function PageContainer2({
13847
13758
  headerCount.current = Math.max(0, headerCount.current + (present ? 1 : -1));
13848
13759
  setWrappedHeader(headerCount.current > 0);
13849
13760
  }, []);
13761
+ const pageActions = usePageActionsSlot();
13850
13762
  const launcherCorner = shell?.launcherCorner ?? null;
13851
13763
  const vPad = verticalPadding ?? "default";
13852
13764
  const launcherPadding = launcherCorner ? { paddingBottom: `max(${bottomPaddingValues[vPad]}, var(--ds-toast-inset-${launcherCorner}, 0px))` } : {};
@@ -13892,7 +13804,7 @@ var PageContainer = forwardRef(function PageContainer2({
13892
13804
  observedRef.current = null;
13893
13805
  }, []);
13894
13806
  if (headerIdx === -1 || !headerEl || !r) {
13895
- 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(
13896
13808
  "div",
13897
13809
  {
13898
13810
  ref: setRoot,
@@ -13908,7 +13820,7 @@ var PageContainer = forwardRef(function PageContainer2({
13908
13820
  style: { maxWidth: maxWidthValue, containerType: "inline-size", ...launcherPadding },
13909
13821
  children: capped(children)
13910
13822
  }
13911
- ) });
13823
+ ) }) });
13912
13824
  }
13913
13825
  const rest = kids.filter((_, i) => i !== headerIdx);
13914
13826
  const snapping = r.behavior === "snapping";
@@ -13929,7 +13841,7 @@ var PageContainer = forwardRef(function PageContainer2({
13929
13841
  ...overlapActive ? { marginTop: "calc(-1 * var(--ds-overlap, 0px))", position: "relative", zIndex: 1 } : {}
13930
13842
  } : void 0;
13931
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) }) });
13932
- 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: [
13933
13845
  placement === "block" ? /* @__PURE__ */ jsx("div", { style: floatingBlockPadding(r.navbarFloating, r.sidebarFloating, overlapActive), children: /* @__PURE__ */ jsxs("div", { className: FLOATING_BLOCK, children: [
13934
13846
  strip,
13935
13847
  colourful ? header : /* @__PURE__ */ jsx("div", { className: columnClass(padding), style: columnStyle, children: header })
@@ -13941,7 +13853,7 @@ var PageContainer = forwardRef(function PageContainer2({
13941
13853
  /* @__PURE__ */ jsx("div", { className: columnClass(padding), style: columnStyle, children: header })
13942
13854
  ] }),
13943
13855
  wrapperStyle ? /* @__PURE__ */ jsx("div", { style: wrapperStyle, children: column }) : column
13944
- ] });
13856
+ ] }) });
13945
13857
  });
13946
13858
  function TruncHeading({ as: Tag3 = "h1", className, children, lines: linesProp }) {
13947
13859
  const lines = linesProp ?? (Tag3 === "h1" ? 2 : 1);
@@ -13969,7 +13881,8 @@ function outerHeight(el) {
13969
13881
  const s = getComputedStyle(el);
13970
13882
  return el.getBoundingClientRect().height + (parseFloat(s.marginTop) || 0) + (parseFloat(s.marginBottom) || 0);
13971
13883
  }
13972
- function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
13884
+ function Breadcrumbs2({ items, onLight, onTint, pill, separator = "chevron" }) {
13885
+ const follow = useCrumbFollow();
13973
13886
  if (!items.length) return null;
13974
13887
  const mutedCls = onLight ? "text-[var(--ds-text-on-primary)]/40" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]";
13975
13888
  const sepEl = (key) => {
@@ -13984,17 +13897,15 @@ function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
13984
13897
  onLight ? "bg-[var(--ds-text-on-primary)]/10" : onTint ? "bg-[var(--ds-bg-surface)]/70" : "bg-[var(--ds-bg-muted)]"
13985
13898
  ) : "";
13986
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) => {
13987
- const isNavigable = !!(item.onClick || item.href);
13900
+ const isNavigable = crumbGoes(item);
13901
+ const isCurrent = !isNavigable && i === items.length - 1;
13988
13902
  return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-ds-1.5", children: [
13989
13903
  i > 0 && sepEl(i),
13990
13904
  isNavigable ? /* @__PURE__ */ jsx(
13991
13905
  "a",
13992
13906
  {
13993
13907
  href: item.href ?? "#",
13994
- onClick: (e) => {
13995
- e.preventDefault();
13996
- item.onClick?.();
13997
- },
13908
+ onClick: (e) => follow(e, item),
13998
13909
  className: cn(
13999
13910
  "transition-colors duration-[var(--ds-transition-speed)]",
14000
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)]"),
@@ -14002,9 +13913,8 @@ function Breadcrumbs3({ items, onLight, onTint, pill, separator = "chevron" }) {
14002
13913
  ),
14003
13914
  children: item.label
14004
13915
  }
14005
- ) : /* @__PURE__ */ jsx("span", { className: cn(
14006
- "font-medium",
14007
- 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,
14008
13918
  pillCls
14009
13919
  ), children: item.label })
14010
13920
  ] }, i);
@@ -14019,6 +13929,11 @@ function TintWash() {
14019
13929
  }
14020
13930
  );
14021
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
+ }
14022
13937
  function BackButton({ onLight, onClick }) {
14023
13938
  return /* @__PURE__ */ jsx(
14024
13939
  "button",
@@ -14065,10 +13980,18 @@ function Caption({ children, onLight, onTint }) {
14065
13980
  onLight ? "text-[var(--ds-text-on-primary)]/80" : onTint ? "text-[var(--ds-text-muted-on-tint)]" : "text-[var(--ds-text-secondary)]"
14066
13981
  ), children });
14067
13982
  }
13983
+ var ON_BAND_STRIP = cn(
13984
+ "rounded-[var(--ds-radius-md)] px-ds-3 py-ds-2 border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
13985
+ "bg-[var(--ds-bg-surface)] supports-[color:oklch(from_red_l_c_h)]:bg-[oklch(from_var(--ds-bg-surface)_l_c_h_/_max(alpha,min(1,alpha*1000)*0.6))]",
13986
+ GLASS_SURFACE
13987
+ );
14068
13988
  function TabRow({ children, hasCaption, className, onBand }) {
14069
13989
  const [row, setRow] = useState(null);
14070
13990
  const value = useMemo(() => ({ row, hasCaption }), [row, hasCaption]);
14071
- return /* @__PURE__ */ jsx("div", { ref: setRow, className: cn(className, onBand && "mx-0 rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)] px-ds-3 py-ds-2"), children: /* @__PURE__ */ jsx(HeaderTabRowContext.Provider, { value, children }) });
13991
+ return /* @__PURE__ */ jsx("div", { ref: setRow, className: cn(className, onBand && cn("mx-0", ON_BAND_STRIP)), children: /* @__PURE__ */ jsx(HeaderTabRowContext.Provider, { value, children }) });
13992
+ }
13993
+ function FilterRow({ children, className, onBand }) {
13994
+ return /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx(FilterRowContext.Provider, { value: true, children: /* @__PURE__ */ jsx("div", { "data-gs": "filter-row", className: cn("flex items-center flex-wrap gap-ds-3", className, onBand && cn("w-fit max-w-full", ON_BAND_STRIP)), children }) }) });
14072
13995
  }
14073
13996
  var FLOATING_HEADER = cn(
14074
13997
  "rounded-[var(--ds-radius-md)]",
@@ -14117,6 +14040,11 @@ var PageHeader = forwardRef(function PageHeader2({
14117
14040
  }, ref) {
14118
14041
  const shell = useAppShell();
14119
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;
14120
14048
  const reportHeader = container?.reportHeader;
14121
14049
  useLayoutEffect(() => {
14122
14050
  if (!reportHeader) return;
@@ -14162,7 +14090,7 @@ var PageHeader = forwardRef(function PageHeader2({
14162
14090
  badge,
14163
14091
  breadcrumbs: inlineBreadcrumbs,
14164
14092
  onBack,
14165
- actions,
14093
+ actions: shownActions,
14166
14094
  avatar,
14167
14095
  filters,
14168
14096
  expandedContent,
@@ -14190,7 +14118,7 @@ var PageHeader = forwardRef(function PageHeader2({
14190
14118
  }
14191
14119
  let header;
14192
14120
  if (variant === "minimal") {
14193
- 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 });
14194
14122
  } else if (variant === "tabs-integrated") {
14195
14123
  header = /* @__PURE__ */ jsx(
14196
14124
  TabsIntegratedHeader,
@@ -14202,7 +14130,7 @@ var PageHeader = forwardRef(function PageHeader2({
14202
14130
  badge,
14203
14131
  breadcrumbs: inlineBreadcrumbs,
14204
14132
  onBack,
14205
- actions,
14133
+ actions: shownActions,
14206
14134
  avatar,
14207
14135
  tabs,
14208
14136
  breadcrumbPill: bcPill,
@@ -14227,7 +14155,7 @@ var PageHeader = forwardRef(function PageHeader2({
14227
14155
  badge,
14228
14156
  breadcrumbs: inlineBreadcrumbs,
14229
14157
  onBack,
14230
- actions,
14158
+ actions: shownActions,
14231
14159
  avatar,
14232
14160
  filters,
14233
14161
  intensity,
@@ -14255,7 +14183,7 @@ var PageHeader = forwardRef(function PageHeader2({
14255
14183
  badge,
14256
14184
  breadcrumbs: inlineBreadcrumbs,
14257
14185
  onBack,
14258
- actions,
14186
+ actions: shownActions,
14259
14187
  avatar,
14260
14188
  filters,
14261
14189
  bannerImage,
@@ -14337,7 +14265,7 @@ function DefaultHeader({
14337
14265
  hasBanner && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-[var(--ds-bg-canvas)]/85" }),
14338
14266
  /* @__PURE__ */ jsxs("div", { className: inner.className, style: inner.style, children: [
14339
14267
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14340
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14268
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14341
14269
  /* @__PURE__ */ jsxs("div", { className: cn(
14342
14270
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14343
14271
  hasBreadcrumbs && "mt-ds-1"
@@ -14356,7 +14284,7 @@ function DefaultHeader({
14356
14284
  ] }),
14357
14285
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
14358
14286
  ] }),
14359
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14287
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", children: filters }),
14360
14288
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", children: tabs })
14361
14289
  ] })
14362
14290
  ]
@@ -14384,7 +14312,7 @@ function MinimalHeader({
14384
14312
  const inner = floatingContentProps(isFloating, contentMaxWidth);
14385
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: [
14386
14314
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14387
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14315
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14388
14316
  caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
14389
14317
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && "mt-ds-0.5"), children: [
14390
14318
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
@@ -14468,11 +14396,13 @@ function ColourfulHeader({
14468
14396
  style: {
14469
14397
  maxWidth: gutter ? contentMaxWidth ?? "var(--ds-bp-xl)" : void 0,
14470
14398
  containerType: "inline-size",
14471
- ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : {}
14399
+ // under the overlap, the gap from the header's last row to the cards is the page's own (space-6): the band's
14400
+ // bottom edge counts in it (the owner, 30 September: 26 px above the cards, 24 below)
14401
+ ...overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px) - ${isBold && !isFloating ? "2px" : "var(--ds-border-width)"})` } : {}
14472
14402
  },
14473
14403
  children: [
14474
14404
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight: isBold }),
14475
- 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 }),
14476
14406
  /* @__PURE__ */ jsxs("div", { className: cn(
14477
14407
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14478
14408
  hasBreadcrumbs && "mt-ds-1"
@@ -14497,7 +14427,7 @@ function ColourfulHeader({
14497
14427
  ] }),
14498
14428
  actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions })
14499
14429
  ] }),
14500
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14430
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", onBand: isBold, children: filters }),
14501
14431
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBold, children: tabs })
14502
14432
  ]
14503
14433
  }
@@ -14539,6 +14469,7 @@ function SnappingHeader({
14539
14469
  stickyTop = "top-[var(--ds-navbar-height)]"
14540
14470
  }) {
14541
14471
  const [collapsed, setCollapsed] = useState(false);
14472
+ const followCrumb = useCrumbFollow();
14542
14473
  const container = usePageContainer();
14543
14474
  const stickyRef = useRef(null);
14544
14475
  const setStickyRef = useCallback((node) => {
@@ -14673,7 +14604,7 @@ function SnappingHeader({
14673
14604
  const hasBreadcrumbs = !!(breadcrumbs && breadcrumbs.length > 0);
14674
14605
  const innerContent = isMinimal && !collapsed ? /* @__PURE__ */ jsxs("div", { className: cn("py-ds-2", hasBreadcrumbs && !hideExpandedBreadcrumbs && "pt-ds-1", tabs && "pb-ds-4"), children: [
14675
14606
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14676
- hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14607
+ hasBreadcrumbs && !hideExpandedBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14677
14608
  caption && /* @__PURE__ */ jsx(Caption, { children: caption }),
14678
14609
  /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between gap-ds-4", hasBreadcrumbs && !hideExpandedBreadcrumbs && "mt-ds-0.5"), children: [
14679
14610
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2 min-w-0", children: [
@@ -14683,7 +14614,7 @@ function SnappingHeader({
14683
14614
  actions && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) })
14684
14615
  ] }),
14685
14616
  description && /* @__PURE__ */ jsx(ClampedText, { className: "mt-ds-1 text-sm text-[var(--ds-text-secondary)]", children: description }),
14686
- filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-3", children: filters }) }),
14617
+ filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-3", children: filters }),
14687
14618
  expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-3", children: expandedContent }),
14688
14619
  tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-3 -mx-ds-1", children: tabs })
14689
14620
  ] }) : /* @__PURE__ */ jsxs(
@@ -14697,10 +14628,10 @@ function SnappingHeader({
14697
14628
  hasBreadcrumbs && !hideExpandedBreadcrumbs ? "pt-ds-3" : "pt-ds-6"
14698
14629
  ]
14699
14630
  ),
14700
- style: !collapsed && isColourful && overlapActive ? { paddingBottom: `calc(var(--ds-space-6) + var(--ds-overlap, 0px))` } : void 0,
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,
14701
14632
  children: [
14702
14633
  !collapsed && focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack, onLight }),
14703
- !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 }),
14704
14635
  /* @__PURE__ */ jsxs("div", { className: cn(
14705
14636
  "flex justify-between gap-ds-4",
14706
14637
  collapsed ? "items-center" : "flex-col @sm:flex-row @sm:items-center",
@@ -14715,17 +14646,14 @@ function SnappingHeader({
14715
14646
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
14716
14647
  collapsed && hasBreadcrumbs && (() => {
14717
14648
  const lastCrumb = breadcrumbs[breadcrumbs.length - 1];
14718
- const isClickable = !!(lastCrumb?.onClick || lastCrumb?.href);
14649
+ const isClickable = !!lastCrumb && crumbGoes(lastCrumb);
14719
14650
  const Tag3 = isClickable ? "a" : "span";
14720
14651
  return /* @__PURE__ */ jsxs(
14721
14652
  Tag3,
14722
14653
  {
14723
14654
  ...isClickable ? {
14724
14655
  href: lastCrumb.href ?? "#",
14725
- onClick: (e) => {
14726
- e.preventDefault();
14727
- lastCrumb.onClick?.();
14728
- }
14656
+ onClick: (e) => followCrumb(e, lastCrumb)
14729
14657
  } : {},
14730
14658
  className: cn(
14731
14659
  "text-sm hidden @sm:inline-flex items-center gap-ds-1",
@@ -14752,7 +14680,7 @@ function SnappingHeader({
14752
14680
  ] }),
14753
14681
  actions && (collapsed ? /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }) }) : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-2 shrink-0", children: actions }))
14754
14682
  ] }),
14755
- !collapsed && filters && /* @__PURE__ */ jsx(ControlSizeContext.Provider, { value: "sm", children: /* @__PURE__ */ jsx("div", { className: "flex items-center flex-wrap gap-ds-3 mt-ds-4", children: filters }) }),
14683
+ !collapsed && filters && /* @__PURE__ */ jsx(FilterRow, { className: "mt-ds-4", onBand: isBoldColourful, children: filters }),
14756
14684
  !collapsed && expandedContent && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: expandedContent }),
14757
14685
  !collapsed && tabs && /* @__PURE__ */ jsx(TabRow, { hasCaption: !!caption, className: "mt-ds-4 -mx-ds-1", onBand: isBoldColourful, children: tabs })
14758
14686
  ]
@@ -14822,7 +14750,7 @@ function TabsIntegratedHeader({
14822
14750
  const inner = floatingContentProps(isFloating, contentMaxWidth);
14823
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: [
14824
14752
  focusBack && /* @__PURE__ */ jsx(BackLink, { onClick: focusBack }),
14825
- hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs3, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14753
+ hasBreadcrumbs && /* @__PURE__ */ jsx(Breadcrumbs2, { items: breadcrumbs, pill: breadcrumbPill, separator: breadcrumbSeparator }),
14826
14754
  /* @__PURE__ */ jsxs("div", { className: cn(
14827
14755
  "flex flex-col @sm:flex-row @sm:items-center justify-between gap-ds-4",
14828
14756
  hasBreadcrumbs && "mt-ds-1"
@@ -14989,6 +14917,73 @@ function Dock({
14989
14917
  }
14990
14918
  );
14991
14919
  }
14920
+ function resolveNavItemType(type) {
14921
+ return type === "default" ? "tint" : type;
14922
+ }
14923
+ var NavbarTypeContext = createContext("tint");
14924
+ var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href, onClick, type: typeProp, className, children }, ref) {
14925
+ const contextType = useContext(NavbarTypeContext);
14926
+ const type = resolveNavItemType(typeProp ?? contextType);
14927
+ const baseClasses = cn(
14928
+ "inline-flex items-center gap-ds-2 cursor-pointer select-none whitespace-nowrap",
14929
+ // one line: the navbar measures each link (round 12)
14930
+ "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
14931
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
14932
+ "text-[length:var(--ds-font-size-sm)]"
14933
+ );
14934
+ const typeClasses = getTypeClasses(type, active);
14935
+ return /* @__PURE__ */ jsxs(
14936
+ "a",
14937
+ {
14938
+ ref,
14939
+ href: href ?? "#",
14940
+ onClick,
14941
+ "aria-current": active ? "page" : void 0,
14942
+ className: cn(baseClasses, typeClasses, className),
14943
+ children: [
14944
+ icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16 }),
14945
+ children ?? label,
14946
+ type === "underline" && active && /* @__PURE__ */ jsx(NavUnderline, {})
14947
+ ]
14948
+ }
14949
+ );
14950
+ });
14951
+ function getTypeClasses(type, active) {
14952
+ switch (type) {
14953
+ case "pill":
14954
+ return cn(
14955
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
14956
+ active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
14957
+ );
14958
+ case "underline":
14959
+ return cn(
14960
+ "px-ds-2 py-ds-2 relative",
14961
+ active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
14962
+ );
14963
+ case "minimal":
14964
+ return cn(
14965
+ "px-ds-2 py-ds-1",
14966
+ active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
14967
+ );
14968
+ case "tint":
14969
+ return cn(
14970
+ "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
14971
+ active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
14972
+ );
14973
+ }
14974
+ }
14975
+ function NavUnderline() {
14976
+ return /* @__PURE__ */ jsx(
14977
+ "span",
14978
+ {
14979
+ className: "absolute bottom-0 left-ds-1 right-ds-1 rounded-t-full bg-[var(--ds-accent-primary)]",
14980
+ style: { height: "2px" }
14981
+ }
14982
+ );
14983
+ }
14984
+ function navItemClasses(type, active) {
14985
+ return cn("text-[length:var(--ds-font-size-sm)]", getTypeClasses(type, active));
14986
+ }
14992
14987
  function TruncSpan({ children, className }) {
14993
14988
  const { ref, isTruncated } = useTextOverflow({ lines: 1 });
14994
14989
  const el = /* @__PURE__ */ jsx("span", { ref, className: cn("truncate", className), children });
@@ -14999,7 +14994,7 @@ function NavMenu({
14999
14994
  label,
15000
14995
  items,
15001
14996
  active = false,
15002
- type = "tint",
14997
+ type: typeProp,
15003
14998
  onItemClick,
15004
14999
  className
15005
15000
  }) {
@@ -15047,27 +15042,14 @@ function NavMenu({
15047
15042
  const hasActiveChild = active || items.some(
15048
15043
  (it) => it.active || it.children?.some((c) => c.active)
15049
15044
  );
15045
+ const contextType = useContext(NavbarTypeContext);
15046
+ const type = resolveNavItemType(typeProp ?? contextType);
15050
15047
  const isUnderline = type === "underline";
15051
- const isMinimal = type === "minimal";
15052
- const isPill = type === "pill";
15053
15048
  const triggerClasses = cn(
15054
- "inline-flex items-center gap-ds-1 cursor-pointer transition-colors whitespace-nowrap",
15049
+ "inline-flex items-center gap-ds-1 cursor-pointer select-none whitespace-nowrap",
15050
+ "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
15055
15051
  "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
15056
- isUnderline ? cn(
15057
- "px-ds-3 py-ds-2 text-sm border-b-2 -mb-[2px]",
15058
- hasActiveChild ? "border-[var(--ds-accent-primary)] text-[var(--ds-text-primary)] font-medium" : "border-transparent text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:border-[var(--ds-border-default)]"
15059
- ) : isMinimal ? cn(
15060
- "px-ds-2 py-ds-1 text-sm",
15061
- hasActiveChild ? "text-[var(--ds-text-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]"
15062
- ) : isPill ? cn(
15063
- "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
15064
- hasActiveChild ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
15065
- ) : cn(
15066
- // tint (and the deprecated `default` alias) — the same soft accent fill
15067
- // NavItem uses, so a menu trigger and a link read as one row (decision 30).
15068
- "px-ds-3 py-ds-1.5 text-sm rounded-[var(--ds-radius-md)]",
15069
- hasActiveChild ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-muted)] hover:text-[var(--ds-text-primary)]"
15070
- ),
15052
+ navItemClasses(type, hasActiveChild),
15071
15053
  className
15072
15054
  );
15073
15055
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -15089,7 +15071,8 @@ function NavMenu({
15089
15071
  size: 12,
15090
15072
  className: cn("transition-transform", open && "rotate-180")
15091
15073
  }
15092
- )
15074
+ ),
15075
+ isUnderline && hasActiveChild && /* @__PURE__ */ jsx(NavUnderline, {})
15093
15076
  ]
15094
15077
  }
15095
15078
  ),
@@ -15200,67 +15183,6 @@ function NavMenuGroup({
15200
15183
  )) })
15201
15184
  ] });
15202
15185
  }
15203
- function resolveNavItemType(type) {
15204
- return type === "default" ? "tint" : type;
15205
- }
15206
- var NavbarTypeContext = createContext("tint");
15207
- var NavItem = forwardRef(function NavItem2({ label, icon, active = false, href, onClick, type: typeProp, className, children }, ref) {
15208
- const contextType = useContext(NavbarTypeContext);
15209
- const type = resolveNavItemType(typeProp ?? contextType);
15210
- const baseClasses = cn(
15211
- "inline-flex items-center gap-ds-2 cursor-pointer select-none whitespace-nowrap",
15212
- // one line: the navbar measures each link (round 12)
15213
- "transition-colors duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
15214
- "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
15215
- "text-[length:var(--ds-font-size-sm)]"
15216
- );
15217
- const typeClasses = getTypeClasses(type, active);
15218
- return /* @__PURE__ */ jsxs(
15219
- "a",
15220
- {
15221
- ref,
15222
- href: href ?? "#",
15223
- onClick,
15224
- "aria-current": active ? "page" : void 0,
15225
- className: cn(baseClasses, typeClasses, className),
15226
- children: [
15227
- icon && /* @__PURE__ */ jsx(Icon, { name: icon, size: 16 }),
15228
- children ?? label,
15229
- type === "underline" && active && /* @__PURE__ */ jsx(
15230
- "span",
15231
- {
15232
- className: "absolute bottom-0 left-ds-1 right-ds-1 rounded-t-full bg-[var(--ds-accent-primary)]",
15233
- style: { height: "2px" }
15234
- }
15235
- )
15236
- ]
15237
- }
15238
- );
15239
- });
15240
- function getTypeClasses(type, active) {
15241
- switch (type) {
15242
- case "pill":
15243
- return cn(
15244
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
15245
- active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
15246
- );
15247
- case "underline":
15248
- return cn(
15249
- "px-ds-2 py-ds-2 relative",
15250
- active ? "text-[var(--ds-text-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]"
15251
- );
15252
- case "minimal":
15253
- return cn(
15254
- "px-ds-2 py-ds-1",
15255
- active ? "text-[var(--ds-accent-primary)] font-medium" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]"
15256
- );
15257
- case "tint":
15258
- return cn(
15259
- "px-ds-2 py-ds-1 rounded-[var(--ds-radius-sm)]",
15260
- active ? "bg-[var(--ds-accent-subtle)] text-[var(--ds-accent-primary)] font-[var(--ds-weight-heading)]" : "text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)] hover:bg-[var(--ds-bg-muted)]"
15261
- );
15262
- }
15263
- }
15264
15186
  var DEFAULT_BUTTON_ATTR = "data-dialog-default-button";
15265
15187
  var sizeClasses = {
15266
15188
  // round 16 (the owner's item 5): the small dialog is 448 px, not 384 — a delete confirmation with a typed field was cramped
@@ -15281,6 +15203,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15281
15203
  const dialogRef = useRef(null);
15282
15204
  const opener = useRef(null);
15283
15205
  const defaultButtonRef = useRef(null);
15206
+ const bodyRef = useScrollbarInset();
15284
15207
  const onCloseRef = useLatest(onClose);
15285
15208
  const onSubmitRef = useLatest(onSubmit);
15286
15209
  const isTopLayer = useOverlayLayer(open);
@@ -15326,7 +15249,7 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15326
15249
  "div",
15327
15250
  {
15328
15251
  ref: overlayRef,
15329
- className: "fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center p-ds-4",
15252
+ className: cn("fixed inset-0 z-[var(--ds-z-modal)] flex items-center justify-center", !isMobile && "p-ds-4"),
15330
15253
  children: [
15331
15254
  /* @__PURE__ */ jsx(
15332
15255
  "div",
@@ -15376,31 +15299,22 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15376
15299
  "data-gs": "dialog",
15377
15300
  "aria-labelledby": titleId,
15378
15301
  "aria-describedby": description ? descriptionId : void 0,
15379
- style: { containerType: "inline-size" },
15302
+ style: { containerType: "inline-size", "--ds-form-max": "none" },
15380
15303
  className: cn(
15381
- "relative z-10 w-full",
15304
+ // a column: the title row, a body that scrolls when it is taller than the room, and the footer (Sprint 25.14,
15305
+ // with A36's full-screen phone dialog: its content ran past the screen with the footer cut off)
15306
+ "relative z-10 w-full flex flex-col max-h-full",
15382
15307
  isMobile ? "max-w-none h-full" : sizeClasses[size],
15383
15308
  OVERLAY_SURFACE,
15384
15309
  "text-[var(--ds-text-primary)]",
15385
15310
  GLASS_OVERLAY,
15386
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
15387
- isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)]",
15311
+ // full screen on a phone: no edge to draw, so the content sits at the gutter from the screen's edge
15312
+ isMobile ? "rounded-none" : "rounded-[var(--ds-radius-xl)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
15388
15313
  "overflow-hidden",
15389
15314
  "animate-in fade-in-0 zoom-in-95"
15390
15315
  ),
15391
15316
  children: [
15392
- /* @__PURE__ */ jsx(
15393
- "button",
15394
- {
15395
- type: "button",
15396
- onClick: onClose,
15397
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
15398
- className: "absolute right-4 top-4 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
15399
- "aria-label": "Close",
15400
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
15401
- }
15402
- ),
15403
- /* @__PURE__ */ jsx("div", { className: "p-ds-6", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-ds-3", children: [
15317
+ /* @__PURE__ */ jsxs("div", { className: cn("shrink-0 flex items-start gap-ds-3 pt-ds-6 px-[var(--ds-page-gutter)]", children ? "pb-ds-4" : "pb-ds-6"), children: [
15404
15318
  variant === "destructive" && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center shrink-0 w-10 h-10 rounded-[var(--ds-radius-full)] bg-[var(--ds-status-error-bg)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 20, className: "text-[var(--ds-status-error)]" }) }),
15405
15319
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
15406
15320
  /* @__PURE__ */ jsx(
@@ -15411,16 +15325,39 @@ function Dialog({ open, onClose: onCloseProp, title, description, children, foot
15411
15325
  children: title
15412
15326
  }
15413
15327
  ),
15414
- description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description }),
15415
- children && /* @__PURE__ */ jsx("div", { className: "mt-ds-4", children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) })
15416
- ] })
15417
- ] }) }),
15328
+ description && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-ds-2 text-sm text-[var(--ds-text-muted)]", children: description })
15329
+ ] }),
15330
+ /* @__PURE__ */ jsx(
15331
+ "button",
15332
+ {
15333
+ type: "button",
15334
+ onClick: onClose,
15335
+ ...{ [OVERLAY_CLOSE_ATTR]: "" },
15336
+ className: "shrink-0 text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
15337
+ "aria-label": "Close",
15338
+ children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
15339
+ }
15340
+ )
15341
+ ] }),
15342
+ children && /* @__PURE__ */ jsx(
15343
+ "div",
15344
+ {
15345
+ ref: bodyRef,
15346
+ className: "min-h-0 flex-[0_1_auto] overflow-y-auto pb-ds-6",
15347
+ style: {
15348
+ paddingLeft: variant === "destructive" ? "calc(var(--ds-page-gutter) + 2.5rem + var(--ds-space-3))" : "var(--ds-page-gutter)",
15349
+ paddingRight: insetLessScrollbar("var(--ds-page-gutter)")
15350
+ },
15351
+ children: /* @__PURE__ */ jsx(SurfaceBoundary, { children: /* @__PURE__ */ jsx(DrawerFooterCtx, { value: null, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: onClose, children }) }) }) })
15352
+ }
15353
+ ),
15354
+ isMobile && /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "flex-1 min-h-0" }),
15418
15355
  /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
15419
15356
  footer && /* @__PURE__ */ jsx("div", { className: cn(
15420
- "flex justify-end gap-ds-2 p-ds-4",
15357
+ "shrink-0 flex justify-end gap-ds-2 py-ds-4 px-[var(--ds-page-gutter)]",
15421
15358
  "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
15422
15359
  "bg-[var(--ds-bg-muted)]",
15423
- "rounded-b-[var(--ds-radius-xl)]"
15360
+ !isMobile && "rounded-b-[var(--ds-radius-xl)]"
15424
15361
  ), children: footer })
15425
15362
  ]
15426
15363
  }
@@ -15573,7 +15510,7 @@ var Navbar = forwardRef(function Navbar2({
15573
15510
  }
15574
15511
  focus.onExit?.();
15575
15512
  };
15576
- 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;
15577
15514
  const shownUserMenu = focus?.kind === "before-app" ? null : userMenu;
15578
15515
  const asking = confirmExit && focus?.kind === "in-app" && !!appShell?.hasUnsavedChanges;
15579
15516
  if (confirmExit && !asking) setConfirmExit(false);
@@ -16707,6 +16644,310 @@ function getPopoverPosition(rect, placement, padding) {
16707
16644
  top = Math.max(8, Math.min(top, window.innerHeight - popH - 8));
16708
16645
  return { top, left };
16709
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
+ }
16710
16951
  function useDestructiveAction(options) {
16711
16952
  const { action, title, description, confirmLabel, typeToConfirm, onConfirm, onUndo, doneMessage, undoMessage, restoredMessage, severity } = options;
16712
16953
  const preferences = useShellPreferences();
@@ -16872,6 +17113,8 @@ function pastTenseOf(label) {
16872
17113
  }
16873
17114
  var SectionHeading = forwardRef(function SectionHeading2({
16874
17115
  title,
17116
+ onTitleChange,
17117
+ titleLabel = "Title",
16875
17118
  description,
16876
17119
  actions,
16877
17120
  badge,
@@ -16883,6 +17126,8 @@ var SectionHeading = forwardRef(function SectionHeading2({
16883
17126
  }, ref) {
16884
17127
  const isPhone = useBreakpoint("sm");
16885
17128
  const pane = variant === "pane";
17129
+ const inDetail = useContext(SplitPaneCtx) === "detail";
17130
+ const editable = !!onTitleChange;
16886
17131
  return /* @__PURE__ */ jsxs(
16887
17132
  "div",
16888
17133
  {
@@ -16890,7 +17135,7 @@ var SectionHeading = forwardRef(function SectionHeading2({
16890
17135
  "data-gs": "section-heading",
16891
17136
  "data-variant": variant,
16892
17137
  className: cn(
16893
- 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)]",
16894
17139
  !pane && bordered && "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] pb-ds-4",
16895
17140
  className
16896
17141
  ),
@@ -16899,11 +17144,23 @@ var SectionHeading = forwardRef(function SectionHeading2({
16899
17144
  "flex gap-ds-4",
16900
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
16901
17146
  // pane's actions slot squeezed "Inbox" to "I."); below sm they always stack
16902
- 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")
16903
17148
  ), children: [
16904
- /* @__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: [
16905
17150
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
16906
- /* @__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 }),
16907
17164
  badge,
16908
17165
  count !== void 0 && /* @__PURE__ */ jsx(Badge, { variant: "neutral", size: "sm", children: count })
16909
17166
  ] }),
@@ -17010,6 +17267,6 @@ var TextOverflow = forwardRef(function TextOverflow2({
17010
17267
  return /* @__PURE__ */ jsx(Tooltip, { content: resolvedTooltipContent, children: content });
17011
17268
  });
17012
17269
 
17013
- 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, 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 };
17014
17271
  //# sourceMappingURL=index.js.map
17015
17272
  //# sourceMappingURL=index.js.map