gridsmith-ui 0.25.0 → 0.26.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 (55) hide show
  1. package/dist/charts.js.map +1 -1
  2. package/dist/chat.js +3 -3
  3. package/dist/{chunk-W7GCEJEH.js → chunk-CYN5O567.js} +5 -5
  4. package/dist/{chunk-W7GCEJEH.js.map → chunk-CYN5O567.js.map} +1 -1
  5. package/dist/{chunk-FHMCAOAH.js → chunk-EX64MN2U.js} +3 -3
  6. package/dist/{chunk-FHMCAOAH.js.map → chunk-EX64MN2U.js.map} +1 -1
  7. package/dist/{chunk-HYMXAIXN.js → chunk-HXVXJUGV.js} +7 -7
  8. package/dist/chunk-HXVXJUGV.js.map +1 -0
  9. package/dist/{chunk-3CUKJ4VP.js → chunk-KWLNEH53.js} +5 -5
  10. package/dist/{chunk-3CUKJ4VP.js.map → chunk-KWLNEH53.js.map} +1 -1
  11. package/dist/{chunk-QGSCF7PN.js → chunk-MYIJNVTK.js} +4 -4
  12. package/dist/{chunk-QGSCF7PN.js.map → chunk-MYIJNVTK.js.map} +1 -1
  13. package/dist/{chunk-2YSEME33.js → chunk-SFY2KMHG.js} +63 -6
  14. package/dist/chunk-SFY2KMHG.js.map +1 -0
  15. package/dist/{chunk-JXANCXK6.js → chunk-ZI3OQDET.js} +18 -4
  16. package/dist/chunk-ZI3OQDET.js.map +1 -0
  17. package/dist/{chunk-HZIP4EJI.js → chunk-ZYVXV4WC.js} +3 -3
  18. package/dist/{chunk-HZIP4EJI.js.map → chunk-ZYVXV4WC.js.map} +1 -1
  19. package/dist/data-grid.js +4 -4
  20. package/dist/editor-mentions.js +1 -1
  21. package/dist/editor.js +2 -2
  22. package/dist/{feather-W26ERBQV.js → feather-VFWLAILO.js} +4 -3
  23. package/dist/feather-VFWLAILO.js.map +1 -0
  24. package/dist/{heroicons-MY43466C.js → heroicons-H3Z7DFCF.js} +4 -3
  25. package/dist/heroicons-H3Z7DFCF.js.map +1 -0
  26. package/dist/icons.d.ts +1 -1
  27. package/dist/icons.js +1 -1
  28. package/dist/index.d.ts +53 -4
  29. package/dist/index.js +1192 -981
  30. package/dist/index.js.map +1 -1
  31. package/dist/kanban.js +4 -5
  32. package/dist/kanban.js.map +1 -1
  33. package/dist/{lucide-CMPVM3PS.js → lucide-MR32KM4G.js} +4 -3
  34. package/dist/lucide-MR32KM4G.js.map +1 -0
  35. package/dist/{phosphor-RMHZOW2O.js → phosphor-ABQQL5N5.js} +4 -3
  36. package/dist/phosphor-ABQQL5N5.js.map +1 -0
  37. package/dist/resizable.js +5 -6
  38. package/dist/resizable.js.map +1 -1
  39. package/dist/roots.css +18 -0
  40. package/dist/{tabler-MJVEYHA5.js → tabler-ANUSY4D2.js} +4 -3
  41. package/dist/tabler-ANUSY4D2.js.map +1 -0
  42. package/dist/tokens.css +6 -0
  43. package/dist/virtualized.js +4 -4
  44. package/dist-static/tokens.css +6 -0
  45. package/package.json +1 -1
  46. package/dist/chunk-2YSEME33.js.map +0 -1
  47. package/dist/chunk-HYMXAIXN.js.map +0 -1
  48. package/dist/chunk-JXANCXK6.js.map +0 -1
  49. package/dist/chunk-WOVYHFUD.js +0 -50
  50. package/dist/chunk-WOVYHFUD.js.map +0 -1
  51. package/dist/feather-W26ERBQV.js.map +0 -1
  52. package/dist/heroicons-MY43466C.js.map +0 -1
  53. package/dist/lucide-CMPVM3PS.js.map +0 -1
  54. package/dist/phosphor-RMHZOW2O.js.map +0 -1
  55. package/dist/tabler-MJVEYHA5.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { Badge } from './chunk-FHMCAOAH.js';
2
- import { slotIcon, Icon } from './chunk-HYMXAIXN.js';
1
+ import { Badge } from './chunk-EX64MN2U.js';
2
+ import { slotIcon, Icon } from './chunk-HXVXJUGV.js';
3
3
  import { cn } from './chunk-M2QRNBDQ.js';
4
4
  import { forwardRef, useState, useCallback } from 'react';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
@@ -157,5 +157,5 @@ function Divider({
157
157
  }
158
158
 
159
159
  export { CodeBlock, Divider, Link };
160
- //# sourceMappingURL=chunk-QGSCF7PN.js.map
161
- //# sourceMappingURL=chunk-QGSCF7PN.js.map
160
+ //# sourceMappingURL=chunk-MYIJNVTK.js.map
161
+ //# sourceMappingURL=chunk-MYIJNVTK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/CodeBlock.tsx","../../../showcase/src/components/Link.tsx","../../../showcase/src/components/Divider.tsx"],"names":["Link","jsxs","jsx"],"mappings":"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","file":"chunk-QGSCF7PN.js","sourcesContent":["import { useState, useCallback } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../lib/icons\";\nimport { Badge } from \"./Badge\";\n\ninterface CodeBlockProps {\n code: string;\n language?: string;\n showLineNumbers?: boolean;\n /** Wrap long lines instead of scrolling sideways. Default: on for a single-line snippet (a tracking tag, a command), off for multi-line code. */\n wrap?: boolean;\n className?: string;\n}\n\nexport function CodeBlock({\n code,\n language,\n showLineNumbers = false,\n wrap,\n className,\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n /* clipboard not available */\n }\n }, [code]);\n\n const lines = code.split(\"\\n\");\n const wraps = wrap ?? lines.length === 1;\n const gutterWidth = showLineNumbers ? `${String(lines.length).length + 1}ch` : undefined;\n\n return (\n <div\n className={cn(\n \"relative group\",\n \"bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"shadow-[var(--ds-shadow-sm)]\",\n \"overflow-hidden\",\n className,\n )}\n >\n <div className=\"flex items-center justify-between px-ds-4 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-muted)]\">\n <div className=\"flex items-center gap-ds-2\">\n {language && <Badge variant=\"outline\" size=\"sm\">{language}</Badge>}\n </div>\n <button\n type=\"button\"\n onClick={handleCopy}\n className={cn(\n \"inline-flex items-center gap-ds-1.5 px-ds-2 py-ds-1 text-xs font-medium cursor-pointer\",\n \"text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n \"hover:bg-[var(--ds-bg-surface)]\",\n \"transition-colors\",\n \"focus-visible:shadow-[var(--ds-shadow-focus)] outline-none\",\n )}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"folder\"} size={14} />\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n\n <div className={wraps ? undefined : \"overflow-x-auto\"} data-wrap={wraps ? \"true\" : undefined}>\n <pre className={cn(\"p-ds-4 text-sm leading-relaxed font-[family-name:var(--ds-font-mono)]\", wraps && \"whitespace-pre-wrap break-words\")}>\n <code>\n {lines.map((line, i) => (\n // with line numbers each line is a two-column row, so a wrapped line continues under its text, not under the gutter\n <span key={i} className={showLineNumbers ? \"grid grid-cols-[max-content_1fr]\" : \"block\"}>\n {showLineNumbers && (\n <span\n className=\"text-right text-[var(--ds-text-muted)] select-none mr-ds-4 opacity-50\"\n style={{ width: gutterWidth }}\n >\n {i + 1}\n </span>\n )}\n <span className=\"min-w-0\">{line || \" \"}</span>\n </span>\n ))}\n </code>\n </pre>\n </div>\n </div>\n );\n}\n","import { forwardRef, type AnchorHTMLAttributes, type ElementType, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { slotIcon, type IconSlot } from \"../lib/icons\";\n\ntype LinkUnderline = \"hover\" | \"always\" | \"none\";\ntype LinkTone = \"accent\" | \"muted\" | \"inherit\";\n\nexport interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n href: string;\n children: ReactNode;\n /** When to show the underline. Default \"hover\". */\n underline?: LinkUnderline;\n /** Colour: accent (default), muted for secondary links, inherit to match surrounding text. */\n tone?: LinkTone;\n /** Opens in a new tab with rel=\"noopener noreferrer\". Default false. */\n external?: boolean;\n /**\n * Render through a router link component instead of a plain anchor\n * (e.g. `component={RouterLink}` from react-router or next/link). The\n * component receives the same props; `href` is also passed as `to` for\n * react-router compatibility. Any element type: a router's Link requires\n * its own props (react-router's `to`), which a narrower type refused under\n * strict TypeScript (Sprint 25.16 A19).\n */\n component?: ElementType;\n /** An icon name (\"info\"), drawn as the theme's icon at 14 px, or an element. Not text: use children. */\n iconLeft?: IconSlot;\n /** As iconLeft, after the label. */\n iconRight?: IconSlot;\n}\n\nconst toneStyles: Record<LinkTone, string> = {\n accent: \"text-[var(--ds-accent-primary)] hover:text-[var(--ds-accent-primary-hover)] active:text-[var(--ds-accent-primary-active)]\",\n muted: \"text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]\",\n inherit: \"text-inherit hover:text-[var(--ds-accent-primary)]\",\n};\n\nconst underlineStyles: Record<LinkUnderline, string> = {\n hover: \"no-underline hover:underline\",\n always: \"underline underline-offset-2\",\n none: \"no-underline\",\n};\n\n/**\n * Navigation link. Renders a real <a> (or a router link via `component`) so\n * middle-click, open-in-new-tab, right-click \"copy link\" and screen readers\n * all behave correctly. Visually identical to Button variant=\"link\".\n *\n * Use Link for NAVIGATION (to a page or URL). Use Button variant=\"link\" for an\n * ACTION that should look like a link (show more, resend code). Never use a\n * ghost Button for inline text — its padding breaks the line.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n { href, children, underline = \"hover\", tone = \"accent\", external = false, component: Component, iconLeft, iconRight, className, target, rel, ...props },\n ref,\n) {\n const classes = cn(\n \"inline-flex items-center gap-ds-1 font-medium cursor-pointer\",\n \"transition-colors duration-[var(--ds-transition-speed)]\",\n \"focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] focus-visible:outline-offset-2 rounded-[var(--ds-radius-sm)]\",\n toneStyles[tone],\n underlineStyles[underline],\n className,\n );\n const resolvedTarget = external ? \"_blank\" : target;\n const resolvedRel = external ? cn(\"noopener noreferrer\", rel) : rel;\n const content = (\n <>\n {slotIcon(iconLeft, 14, \"Link's iconLeft\")}\n {children}\n {slotIcon(iconRight, 14, \"Link's iconRight\")}\n </>\n );\n\n if (Component) {\n return (\n <Component ref={ref} href={href} to={href} className={classes} target={resolvedTarget} rel={resolvedRel} {...props}>\n {content}\n </Component>\n );\n }\n\n return (\n <a ref={ref} href={href} className={classes} target={resolvedTarget} rel={resolvedRel} {...props}>\n {content}\n </a>\n );\n});\n","import { cn } from \"../lib/utils\";\nimport type { ReactNode } from \"react\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\n\ninterface DividerProps {\n orientation?: Orientation;\n label?: ReactNode;\n className?: string;\n}\n\nexport function Divider({\n orientation = \"horizontal\",\n label,\n className,\n}: DividerProps) {\n if (orientation === \"vertical\") {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className={cn(\n \"inline-block self-stretch min-h-[1em]\",\n \"w-[var(--ds-divider-width)]\",\n \"bg-[var(--ds-border-default)]\",\n className,\n )}\n />\n );\n }\n\n if (label) {\n return (\n <div\n role=\"separator\"\n className={cn(\"flex items-center gap-ds-3\", className)}\n >\n <span className=\"flex-1 h-[var(--ds-divider-width)] bg-[var(--ds-border-default)]\" />\n <span className=\"text-xs font-medium text-[var(--ds-text-muted)] shrink-0\">\n {label}\n </span>\n <span className=\"flex-1 h-[var(--ds-divider-width)] bg-[var(--ds-border-default)]\" />\n </div>\n );\n }\n\n return (\n <hr\n role=\"separator\"\n className={cn(\n \"border-none w-full\",\n \"h-[var(--ds-divider-width)]\",\n \"bg-[var(--ds-border-default)]\",\n className,\n )}\n />\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/CodeBlock.tsx","../../../showcase/src/components/Link.tsx","../../../showcase/src/components/Divider.tsx"],"names":["Link","jsxs","jsx"],"mappings":"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","file":"chunk-MYIJNVTK.js","sourcesContent":["import { useState, useCallback } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../lib/icons\";\nimport { Badge } from \"./Badge\";\n\ninterface CodeBlockProps {\n code: string;\n language?: string;\n showLineNumbers?: boolean;\n /** Wrap long lines instead of scrolling sideways. Default: on for a single-line snippet (a tracking tag, a command), off for multi-line code. */\n wrap?: boolean;\n className?: string;\n}\n\nexport function CodeBlock({\n code,\n language,\n showLineNumbers = false,\n wrap,\n className,\n}: CodeBlockProps) {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(code);\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch {\n /* clipboard not available */\n }\n }, [code]);\n\n const lines = code.split(\"\\n\");\n const wraps = wrap ?? lines.length === 1;\n const gutterWidth = showLineNumbers ? `${String(lines.length).length + 1}ch` : undefined;\n\n return (\n <div\n className={cn(\n \"relative group\",\n \"bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"shadow-[var(--ds-shadow-sm)]\",\n \"overflow-hidden\",\n className,\n )}\n >\n <div className=\"flex items-center justify-between px-ds-4 py-ds-2 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-muted)]\">\n <div className=\"flex items-center gap-ds-2\">\n {language && <Badge variant=\"outline\" size=\"sm\">{language}</Badge>}\n </div>\n <button\n type=\"button\"\n onClick={handleCopy}\n className={cn(\n \"inline-flex items-center gap-ds-1.5 px-ds-2 py-ds-1 text-xs font-medium cursor-pointer\",\n \"text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n \"hover:bg-[var(--ds-bg-surface)]\",\n \"transition-colors\",\n \"focus-visible:shadow-[var(--ds-shadow-focus)] outline-none\",\n )}\n aria-label={copied ? \"Copied\" : \"Copy code\"}\n >\n <Icon name={copied ? \"check\" : \"folder\"} size={14} />\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </div>\n\n <div className={wraps ? undefined : \"overflow-x-auto\"} data-wrap={wraps ? \"true\" : undefined}>\n <pre className={cn(\"p-ds-4 text-sm leading-relaxed font-[family-name:var(--ds-font-mono)]\", wraps && \"whitespace-pre-wrap break-words\")}>\n <code>\n {lines.map((line, i) => (\n // with line numbers each line is a two-column row, so a wrapped line continues under its text, not under the gutter\n <span key={i} className={showLineNumbers ? \"grid grid-cols-[max-content_1fr]\" : \"block\"}>\n {showLineNumbers && (\n <span\n className=\"text-right text-[var(--ds-text-muted)] select-none mr-ds-4 opacity-50\"\n style={{ width: gutterWidth }}\n >\n {i + 1}\n </span>\n )}\n <span className=\"min-w-0\">{line || \" \"}</span>\n </span>\n ))}\n </code>\n </pre>\n </div>\n </div>\n );\n}\n","import { forwardRef, type AnchorHTMLAttributes, type ElementType, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { slotIcon, type IconSlot } from \"../lib/icons\";\n\ntype LinkUnderline = \"hover\" | \"always\" | \"none\";\ntype LinkTone = \"accent\" | \"muted\" | \"inherit\";\n\nexport interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n href: string;\n children: ReactNode;\n /** When to show the underline. Default \"hover\". */\n underline?: LinkUnderline;\n /** Colour: accent (default), muted for secondary links, inherit to match surrounding text. */\n tone?: LinkTone;\n /** Opens in a new tab with rel=\"noopener noreferrer\". Default false. */\n external?: boolean;\n /**\n * Render through a router link component instead of a plain anchor\n * (e.g. `component={RouterLink}` from react-router or next/link). The\n * component receives the same props; `href` is also passed as `to` for\n * react-router compatibility. Any element type: a router's Link requires\n * its own props (react-router's `to`), which a narrower type refused under\n * strict TypeScript (Sprint 25.16 A19).\n */\n component?: ElementType;\n /** An icon name (\"info\"), drawn as the theme's icon at 14 px, or an element. Not text: use children. */\n iconLeft?: IconSlot;\n /** As iconLeft, after the label. */\n iconRight?: IconSlot;\n}\n\nconst toneStyles: Record<LinkTone, string> = {\n accent: \"text-[var(--ds-accent-primary)] hover:text-[var(--ds-accent-primary-hover)] active:text-[var(--ds-accent-primary-active)]\",\n muted: \"text-[var(--ds-text-secondary)] hover:text-[var(--ds-text-primary)]\",\n inherit: \"text-inherit hover:text-[var(--ds-accent-primary)]\",\n};\n\nconst underlineStyles: Record<LinkUnderline, string> = {\n hover: \"no-underline hover:underline\",\n always: \"underline underline-offset-2\",\n none: \"no-underline\",\n};\n\n/**\n * Navigation link. Renders a real <a> (or a router link via `component`) so\n * middle-click, open-in-new-tab, right-click \"copy link\" and screen readers\n * all behave correctly. Visually identical to Button variant=\"link\".\n *\n * Use Link for NAVIGATION (to a page or URL). Use Button variant=\"link\" for an\n * ACTION that should look like a link (show more, resend code). Never use a\n * ghost Button for inline text — its padding breaks the line.\n */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(\n { href, children, underline = \"hover\", tone = \"accent\", external = false, component: Component, iconLeft, iconRight, className, target, rel, ...props },\n ref,\n) {\n const classes = cn(\n \"inline-flex items-center gap-ds-1 font-medium cursor-pointer\",\n \"transition-colors duration-[var(--ds-transition-speed)]\",\n \"focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] focus-visible:outline-offset-2 rounded-[var(--ds-radius-sm)]\",\n toneStyles[tone],\n underlineStyles[underline],\n className,\n );\n const resolvedTarget = external ? \"_blank\" : target;\n const resolvedRel = external ? cn(\"noopener noreferrer\", rel) : rel;\n const content = (\n <>\n {slotIcon(iconLeft, 14, \"Link's iconLeft\")}\n {children}\n {slotIcon(iconRight, 14, \"Link's iconRight\")}\n </>\n );\n\n if (Component) {\n return (\n <Component ref={ref} href={href} to={href} className={classes} target={resolvedTarget} rel={resolvedRel} {...props}>\n {content}\n </Component>\n );\n }\n\n return (\n <a ref={ref} href={href} className={classes} target={resolvedTarget} rel={resolvedRel} {...props}>\n {content}\n </a>\n );\n});\n","import { cn } from \"../lib/utils\";\nimport type { ReactNode } from \"react\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\n\ninterface DividerProps {\n orientation?: Orientation;\n label?: ReactNode;\n className?: string;\n}\n\nexport function Divider({\n orientation = \"horizontal\",\n label,\n className,\n}: DividerProps) {\n if (orientation === \"vertical\") {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className={cn(\n \"inline-block self-stretch min-h-[1em]\",\n \"w-[var(--ds-divider-width)]\",\n \"bg-[var(--ds-border-default)]\",\n className,\n )}\n />\n );\n }\n\n if (label) {\n return (\n <div\n role=\"separator\"\n className={cn(\"flex items-center gap-ds-3\", className)}\n >\n <span className=\"flex-1 h-[var(--ds-divider-width)] bg-[var(--ds-border-default)]\" />\n <span className=\"text-xs font-medium text-[var(--ds-text-muted)] shrink-0\">\n {label}\n </span>\n <span className=\"flex-1 h-[var(--ds-divider-width)] bg-[var(--ds-border-default)]\" />\n </div>\n );\n }\n\n return (\n <hr\n role=\"separator\"\n className={cn(\n \"border-none w-full\",\n \"h-[var(--ds-divider-width)]\",\n \"bg-[var(--ds-border-default)]\",\n className,\n )}\n />\n );\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { createContext, useContext, useState, useLayoutEffect } from 'react';
1
+ import { tabStops } from './chunk-ZI3OQDET.js';
2
+ import { createContext, useContext, useState, useLayoutEffect, useEffect } from 'react';
2
3
 
3
- // ../../showcase/src/lib/use-fill-height.ts
4
4
  function useFillHeight(ref, enabled = true) {
5
5
  const insideFill = useContext(FillContext);
6
6
  const active = enabled && !insideFill;
@@ -50,12 +50,26 @@ var MIN_FILL = 240;
50
50
  var ROW_MAX = 56;
51
51
  function fillHeight(el, main) {
52
52
  const page = el.closest("[data-shell-content]") ?? main;
53
- if (!leadsPage(el, page) || !isLastContent(el, page) || hasOwnHeight(el) || !followsContent(el, main)) return null;
53
+ if (!leadsPage(el, page) || !isLastContent(el, page) || keepScroll(el, () => hasOwnHeight(el) || !followsContent(el, main))) return null;
54
54
  const mainBox = main.getBoundingClientRect();
55
55
  const top = el.getBoundingClientRect().top - mainBox.top - main.clientTop + main.scrollTop;
56
56
  const height = Math.floor(main.clientHeight - top - spaceBelow(el, main));
57
57
  return height >= MIN_FILL ? height : null;
58
58
  }
59
+ function keepScroll(el, probe) {
60
+ const scrolled = [];
61
+ for (const node of [el, ...Array.from(el.querySelectorAll("*"))]) {
62
+ if (node.scrollTop || node.scrollLeft) scrolled.push([node, node.scrollTop, node.scrollLeft]);
63
+ }
64
+ try {
65
+ return probe();
66
+ } finally {
67
+ for (const [node, top, left] of scrolled) {
68
+ node.scrollTop = top;
69
+ node.scrollLeft = left;
70
+ }
71
+ }
72
+ }
59
73
  function takesRoom(box) {
60
74
  const style = getComputedStyle(box);
61
75
  if (style.display === "none" || style.position === "absolute" || style.position === "fixed") return false;
@@ -112,7 +126,50 @@ function spaceBelow(el, main) {
112
126
  }
113
127
  return space;
114
128
  }
129
+ function useKeyboardScroll(el, label, enabled = true, nameFromHeading = false) {
130
+ useEffect(() => {
131
+ if (!enabled || !el || typeof ResizeObserver === "undefined") return;
132
+ const name = () => {
133
+ const heading = nameFromHeading ? el.querySelector("h1, h2, h3, h4, h5, h6")?.textContent?.trim() : "";
134
+ return heading || label;
135
+ };
136
+ const set = (on) => {
137
+ if (on === el.hasAttribute("data-scroll-stop")) {
138
+ if (on && el.getAttribute("aria-label") !== name()) el.setAttribute("aria-label", name());
139
+ return;
140
+ }
141
+ if (on) {
142
+ el.setAttribute("data-scroll-stop", "");
143
+ el.tabIndex = 0;
144
+ el.setAttribute("role", "region");
145
+ el.setAttribute("aria-label", name());
146
+ } else {
147
+ el.removeAttribute("data-scroll-stop");
148
+ el.removeAttribute("tabindex");
149
+ el.removeAttribute("role");
150
+ el.removeAttribute("aria-label");
151
+ }
152
+ };
153
+ const update = () => {
154
+ const scrolls = el.scrollHeight > el.clientHeight + 1 && tabStops(el).length === 0;
155
+ set(scrolls || el.hasAttribute("data-scroll-stop") && document.activeElement === el);
156
+ };
157
+ update();
158
+ const ro = new ResizeObserver(update);
159
+ ro.observe(el);
160
+ const mo = typeof MutationObserver === "undefined" ? null : new MutationObserver(update);
161
+ mo?.observe(el, { childList: true, subtree: true, characterData: true });
162
+ el.addEventListener("focusout", update);
163
+ return () => {
164
+ ro.disconnect();
165
+ mo?.disconnect();
166
+ el.removeEventListener("focusout", update);
167
+ set(false);
168
+ };
169
+ }, [el, label, enabled, nameFromHeading]);
170
+ }
171
+ var FOCUS_INSET = "outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]";
115
172
 
116
- export { FillContext, useFillHeight };
117
- //# sourceMappingURL=chunk-2YSEME33.js.map
118
- //# sourceMappingURL=chunk-2YSEME33.js.map
173
+ export { FOCUS_INSET, FillContext, useFillHeight, useKeyboardScroll };
174
+ //# sourceMappingURL=chunk-SFY2KMHG.js.map
175
+ //# sourceMappingURL=chunk-SFY2KMHG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/lib/use-fill-height.ts","../../../showcase/src/lib/use-keyboard-scroll.ts"],"names":[],"mappings":"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","file":"chunk-SFY2KMHG.js","sourcesContent":["import { createContext, useContext, useLayoutEffect, useState, type RefObject } from \"react\";\n\n/**\n * Sprint 25.16 A42 (P31): a page led by a chart, a board or a split view fills the height under its header, and its\n * content scrolls inside it; the page doesn't. SplitView (frame=\"page\"), Gantt and KanbanBoard use this one mechanism.\n *\n * The element takes the rest of the shell's <main> height (main's height less what sits above it and the padding below\n * it) when all of these hold:\n * - it leads the page: above it there is only the page's header, its chrome (notices, tabs, a toolbar, a search or\n * filters row, however tall) and short rows, so a board in a dashboard's last card keeps its own height;\n * - it is the page's last content: nothing after it, up to <main>, takes room, so nothing below it is cut off (the\n * shell's footer is not the page's: it sits below the fold, as it does under a SplitView frame=\"fill\");\n * - its height follows its content: neither its own class nor a box around it gives it one (a fixed height, a flex\n * column that already gives it the rest), and given more, the page grows;\n * - the rest is tall enough to use (a phone on its side keeps the natural height, and the page scrolls).\n * Outside the shell's <main> (AppShell marks it), or inside a view that fills (a Gantt in a SplitView's pane), it keeps\n * its own height. Returns the height in pixels, or null for the natural height.\n */\nexport function useFillHeight(ref: RefObject<HTMLElement | null>, enabled = true): number | null {\n const insideFill = useContext(FillContext);\n const active = enabled && !insideFill;\n const [height, setHeight] = useState<number | null>(null);\n useLayoutEffect(() => {\n const main = ref.current?.closest<HTMLElement>(\"main[data-shell-main]\");\n if (!active || !main || typeof ResizeObserver === \"undefined\") {\n setHeight(null);\n return;\n }\n let frame = 0;\n // the element is read on each measure: a view that changes its layout (stacked or side by side) changes its root\n const measure = () => {\n frame = 0;\n const el = ref.current;\n setHeight(el && main.contains(el) ? fillHeight(el, main) : null);\n };\n const schedule = () => {\n if (!frame) frame = requestAnimationFrame(measure);\n };\n measure();\n // main's height (the window, a phone's toolbar), and what sits above or after the element at the page's top level (a\n // header's actions wrapping, a banner or a note arriving): the boxes there now, and any that arrive\n const ro = new ResizeObserver(schedule);\n const watch = (box: Element) => {\n ro.observe(box);\n for (const child of Array.from(box.children)) ro.observe(child);\n };\n const page = main.querySelector<HTMLElement>(\"[data-shell-content]\");\n watch(main);\n if (page) watch(page);\n const mo = typeof MutationObserver === \"undefined\" ? null : new MutationObserver((records) => {\n for (const record of records) record.addedNodes.forEach((node) => { if (node instanceof Element) ro.observe(node); });\n schedule();\n });\n mo?.observe(main, { childList: true });\n if (page) mo?.observe(page, { childList: true });\n return () => {\n ro.disconnect();\n mo?.disconnect();\n if (frame) cancelAnimationFrame(frame);\n };\n }, [ref, active]);\n return active ? height : null;\n}\n\n/** Content inside a view that fills: it takes the view's height (h-full), never the page's. */\nexport const FillContext = createContext(false);\n\n/** Below this the rest of the screen is too short to scroll inside (about six rows of a chart). */\nconst MIN_FILL = 240;\n/** A row that may sit between the header and the view: tabs, a toolbar, a filters row (PageContainer's ROW_MAX). */\nconst ROW_MAX = 56;\n\nfunction fillHeight(el: HTMLElement, main: HTMLElement): number | null {\n // the page's own content: the shell's wrapper above its footer, else main\n const page = el.closest<HTMLElement>(\"[data-shell-content]\") ?? main;\n if (!leadsPage(el, page) || !isLastContent(el, page) || keepScroll(el, () => hasOwnHeight(el) || !followsContent(el, main))) return null;\n const mainBox = main.getBoundingClientRect();\n const top = el.getBoundingClientRect().top - mainBox.top - main.clientTop + main.scrollTop;\n const height = Math.floor(main.clientHeight - top - spaceBelow(el, main));\n return height >= MIN_FILL ? height : null;\n}\n\n/**\n * Runs a probe that changes the element's height for a moment, and puts back where everything inside it was scrolled:\n * at its natural height a region that scrolls inside it stops scrolling, and the browser drops its position (Sprint\n * 25.18: the week's hours opened at the working day, then the next measure sent them back to midnight).\n */\nfunction keepScroll<T>(el: HTMLElement, probe: () => T): T {\n const scrolled: Array<[HTMLElement, number, number]> = [];\n for (const node of [el, ...Array.from(el.querySelectorAll<HTMLElement>(\"*\"))]) {\n if (node.scrollTop || node.scrollLeft) scrolled.push([node, node.scrollTop, node.scrollLeft]);\n }\n try {\n return probe();\n } finally {\n for (const [node, top, left] of scrolled) {\n node.scrollTop = top;\n node.scrollLeft = left;\n }\n }\n}\n\n/** Whether a box takes room on the page: an overlay, a portal's anchor, a hidden or an empty node doesn't. */\nfunction takesRoom(box: Element): boolean {\n const style = getComputedStyle(box);\n if (style.display === \"none\" || style.position === \"absolute\" || style.position === \"fixed\") return false;\n const rect = box.getBoundingClientRect();\n return rect.height > 0 && rect.width > 0;\n}\n\n/**\n * The page's chrome, at any height: a notice (an Alert, a banner that wraps to two lines on a phone), tabs, a toolbar, a\n * search or filters row (the review of 1 October: a two-line banner turned the fill off). Content of the page's own (a\n * row of stats, cards, text) is not chrome.\n */\nconst CHROME = '[role=\"alert\"], [role=\"status\"], [role=\"tablist\"], [role=\"toolbar\"], [role=\"search\"]';\nconst isChrome = (box: Element) =>\n box.matches(CHROME) || (box.children.length > 0 && Array.from(box.children).every((child) => !takesRoom(child) || child.matches(CHROME)));\n\n/** Above the element, up to the page's root: only the page's header (its title), its chrome, and rows no taller than ROW_MAX. */\nfunction leadsPage(el: HTMLElement, page: HTMLElement): boolean {\n for (let node: HTMLElement | null = el; node && node !== page; node = node.parentElement) {\n for (let prev = node.previousElementSibling; prev; prev = prev.previousElementSibling) {\n if (!takesRoom(prev)) continue;\n if (prev.matches('[data-gs=\"page-header\"]') || prev.querySelector('[data-gs=\"page-header\"], h1')) continue;\n if (isChrome(prev)) continue;\n if (prev.getBoundingClientRect().height > ROW_MAX) return false;\n }\n }\n return true;\n}\n\n/** Nothing after the element, up to the page's root, takes room. */\nfunction isLastContent(el: HTMLElement, page: HTMLElement): boolean {\n for (let node: HTMLElement | null = el; node && node !== page; node = node.parentElement) {\n for (let next = node.nextElementSibling; next; next = next.nextElementSibling) {\n if (takesRoom(next)) return false;\n }\n }\n return true;\n}\n\n/**\n * The element's own CSS gives it a height that isn't its content's: a fixed height of its own (`h-[420px]`), or `h-full`\n * that resolves against a box which already has one. It keeps it. Read with the fill's own height set aside.\n */\nfunction hasOwnHeight(el: HTMLElement): boolean {\n const inline = el.style.height;\n el.style.height = \"\";\n const own = el.offsetHeight;\n el.style.height = \"auto\";\n const natural = el.offsetHeight;\n el.style.height = inline;\n return Math.abs(own - natural) > 1;\n}\n\n/**\n * Given more height, does the page grow? Probed once: the element is made a screen taller and main's top-level box\n * measured. A box of fixed height around it, or a flex column that already gives it its height, absorbs it.\n */\nfunction followsContent(el: HTMLElement, main: HTMLElement): boolean {\n let top: HTMLElement = el;\n while (top.parentElement && top.parentElement !== main) top = top.parentElement;\n const before = top.offsetHeight;\n const inline = el.style.height;\n el.style.height = `${el.offsetHeight + main.clientHeight}px`;\n const grows = top.offsetHeight > before;\n el.style.height = inline;\n return grows;\n}\n\n/** The padding, borders and margins under the element, up to main's own padding: they stay below it. */\nfunction spaceBelow(el: HTMLElement, main: HTMLElement): number {\n const px = (value: string) => parseFloat(value) || 0;\n let space = px(getComputedStyle(main).paddingBottom);\n for (let node: HTMLElement | null = el; node && node !== main; node = node.parentElement) {\n const style = getComputedStyle(node);\n space += px(style.marginBottom);\n if (node !== el) space += px(style.paddingBottom) + px(style.borderBottomWidth);\n }\n return space;\n}\n","import { useEffect } from \"react\";\nimport { tabStops } from \"./focus\";\n\n/**\n * WCAG 2.1.1: a region that scrolls, with nothing inside it to Tab to, is a Tab stop of its own, named, so a keyboard\n * can scroll it (Chrome makes such a scroller focusable by itself; Safari doesn't). Sprint 25.16 A42: what fills the\n * page scrolls inside itself, so a board's column or a split view's pane can be such a region. It stops being one when\n * it no longer scrolls or something inside it takes focus — but while it has focus it stays one, until focus leaves it,\n * so a resize that makes it fit never drops the focus to the page (as Table's scroll region does; the review of\n * 1 October). Give the element a visible focus style (FOCUS_INSET). Pass the element itself (a callback ref's), so a\n * pane drawn later (a detail that opens) is read when it appears. With `nameFromHeading`, the region takes its first\n * heading's words as its name (an item's view: its title), and `label` only when it has none.\n */\nexport function useKeyboardScroll(el: HTMLElement | null, label: string, enabled = true, nameFromHeading = false): void {\n useEffect(() => {\n if (!enabled || !el || typeof ResizeObserver === \"undefined\") return;\n const name = () => {\n const heading = nameFromHeading ? el.querySelector(\"h1, h2, h3, h4, h5, h6\")?.textContent?.trim() : \"\";\n return heading || label;\n };\n const set = (on: boolean) => {\n if (on === el.hasAttribute(\"data-scroll-stop\")) {\n if (on && el.getAttribute(\"aria-label\") !== name()) el.setAttribute(\"aria-label\", name());\n return;\n }\n if (on) {\n el.setAttribute(\"data-scroll-stop\", \"\");\n el.tabIndex = 0;\n el.setAttribute(\"role\", \"region\");\n el.setAttribute(\"aria-label\", name());\n } else {\n el.removeAttribute(\"data-scroll-stop\");\n el.removeAttribute(\"tabindex\");\n el.removeAttribute(\"role\");\n el.removeAttribute(\"aria-label\");\n }\n };\n const update = () => {\n const scrolls = el.scrollHeight > el.clientHeight + 1 && tabStops(el).length === 0;\n set(scrolls || (el.hasAttribute(\"data-scroll-stop\") && document.activeElement === el));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(el);\n const mo = typeof MutationObserver === \"undefined\" ? null : new MutationObserver(update);\n mo?.observe(el, { childList: true, subtree: true, characterData: true });\n // focus leaving a stop that no longer scrolls ends it\n el.addEventListener(\"focusout\", update);\n return () => {\n ro.disconnect();\n mo?.disconnect();\n el.removeEventListener(\"focusout\", update);\n set(false);\n };\n }, [el, label, enabled, nameFromHeading]);\n}\n\n/** The focus style for such a region: the theme's ring, drawn inside its edge so a neighbour can't hide it. */\nexport const FOCUS_INSET = \"outline-none focus-visible:shadow-[inset_0_0_0_2px_var(--ds-border-focus)]\";\n"]}
@@ -31,7 +31,7 @@ function isTabStop(el) {
31
31
  return isRendered(el);
32
32
  }
33
33
  function tabStops(root) {
34
- return Array.from(root.querySelectorAll(FOCUSABLE)).filter(isTabStop);
34
+ return Array.from(root.querySelectorAll(FOCUSABLE)).filter(isTabStop).sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
35
35
  }
36
36
  function focusInitial(root, opts = {}) {
37
37
  if (!root) return false;
@@ -57,6 +57,10 @@ function useFocusBeforeOpen(open) {
57
57
  }, [open]);
58
58
  return before;
59
59
  }
60
+ function pathAbove(to, from) {
61
+ const up = to.replace(/\/+$/, "");
62
+ return from.startsWith(`${up}/`) && from.replace(/\/+$/, "") !== up;
63
+ }
60
64
  var ROW = 'tr, li, [role="row"], [data-card], [data-row]';
61
65
  function deletedRow(before, now) {
62
66
  const refilled = now.length === before.length;
@@ -80,7 +84,11 @@ function rememberFocus(el, { ignoreRoute = false } = {}) {
80
84
  const texts = rows.map((r) => r.textContent ?? "");
81
85
  const keyOf = (r) => r?.getAttribute("data-row-key") ?? null;
82
86
  const key = keyOf(row);
83
- const pageChanged = () => !ignoreRoute && (location.pathname !== pathname || location.href !== href && !el.isConnected);
87
+ const pageChanged = () => {
88
+ if (ignoreRoute) return false;
89
+ if (location.pathname !== pathname) return !pathAbove(location.pathname, pathname);
90
+ return location.href !== href && !el.isConnected;
91
+ };
84
92
  const land = (target) => {
85
93
  if (!target || !target.isConnected) return false;
86
94
  target.focus();
@@ -164,6 +172,12 @@ function useReturnFocus(open, root) {
164
172
  const lost = !active || active === document.body || !active.isConnected;
165
173
  if (!root || lost || active instanceof Node && !!root.current?.contains(active)) r.restore();
166
174
  }, [open, root, before]);
175
+ useEffect(() => () => {
176
+ const r = remembered.current;
177
+ remembered.current = null;
178
+ const now = document.activeElement;
179
+ if (r && (!now || now === document.body || !now.isConnected)) r.restore();
180
+ }, []);
167
181
  return useCallback(() => {
168
182
  remembered.current?.restore();
169
183
  }, []);
@@ -464,5 +478,5 @@ function useNonModalDialog(root, active, onClose, isTop = ALWAYS_TOP) {
464
478
  }
465
479
 
466
480
  export { FIELD, FOCUSABLE, OVERLAY_CLOSE_ATTR, ZOOM_KEYS, focusAfterRemoval, focusInitial, isTabStop, rememberFocus, rovingTabStop, tabStops, useCloseOnFocusOut, useFocusBeforeOpen, useFocusTrap, useGridKeyboard, useNonModalDialog, useOverlayLayer, usePopupFocus, usePressMarks, useReturnFocus };
467
- //# sourceMappingURL=chunk-JXANCXK6.js.map
468
- //# sourceMappingURL=chunk-JXANCXK6.js.map
481
+ //# sourceMappingURL=chunk-ZI3OQDET.js.map
482
+ //# sourceMappingURL=chunk-ZI3OQDET.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/lib/focus.ts"],"names":["active"],"mappings":"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","file":"chunk-ZI3OQDET.js","sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent, type RefObject } from \"react\";\nimport { flushSync } from \"react-dom\";\nimport { useLatest } from \"./use-latest\";\n\n/**\n * Everything a keyboard user might land on: the candidates. Whether one is a\n * Tab stop right now is `isTabStop`'s call (a roving item with tabindex=\"-1\", a\n * hidden input or a disabled field matches this selector but is not a stop).\n */\nexport const FOCUSABLE =\n 'a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex=\"-1\"]), [contenteditable]:not([contenteditable=\"false\"])';\n\n/** A field that takes typing: keys pressed in one are its own (a grid's arrows, a time view's zoom keys leave them). */\nexport const FIELD = \"input, select, textarea, [contenteditable='true']\";\nconst SAFE_BUTTON = 'button:not([data-variant=\"destructive\"])';\n\n/** Mark an overlay's own close/dismiss button so initial focus skips it. */\nexport const OVERLAY_CLOSE_ATTR = \"data-overlay-close\";\n\n// ─── Tab stops ───────────────────────────────────────────────────────────────\n\n/** Rendered and not hidden. Real browsers answer with checkVisibility; with no layout (jsdom) the tree is walked. */\nfunction isRendered(el: HTMLElement): boolean {\n if (typeof el.checkVisibility === \"function\") return el.checkVisibility({ checkVisibilityCSS: true });\n for (let n: Element | null = el; n; n = n.parentElement) {\n if (n instanceof HTMLElement && n.hidden) return false;\n const style = getComputedStyle(n);\n if (style.display === \"none\") return false;\n if (n === el && style.visibility === \"hidden\") return false;\n }\n return true;\n}\n\n/** A native radio is a Tab stop when it is its group's checked radio, or the first when none is checked. */\nfunction isRadioStop(el: HTMLInputElement): boolean {\n if (!el.name) return true;\n const scope: ParentNode = el.form ?? el.ownerDocument;\n const group = Array.from(scope.querySelectorAll<HTMLInputElement>('input[type=\"radio\"]'))\n .filter((r) => r.name === el.name && !r.disabled && r.form === el.form);\n const checked = group.find((r) => r.checked);\n return (checked ?? group[0]) === el;\n}\n\n/**\n * Could the browser's own Tab land here? A focusable element with a\n * non-negative tabIndex, not disabled, not a hidden input, not inside an inert\n * subtree, rendered, and for a native radio the group's one stop (Sprint 25.7).\n */\nexport function isTabStop(el: Element): el is HTMLElement {\n if (!(el instanceof HTMLElement) || el.tabIndex < 0) return false;\n if ((el instanceof HTMLButtonElement || el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) && el.disabled) return false;\n if (el instanceof HTMLInputElement && (el.type === \"hidden\" || (el.type === \"radio\" && !isRadioStop(el)))) return false;\n if (el.closest(\"[inert]\")) return false;\n return isRendered(el);\n}\n\n/** The Tab stops inside `root`, in document order. */\nexport function tabStops(root: ParentNode): HTMLElement[] {\n // sorted: a selector list can come back grouped by selector (jsdom's does), which put a trap's first or last stop, or a\n // card's first action, on the wrong element in a test (the review of 2 October)\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE))\n .filter(isTabStop)\n .sort((a, b) => (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1));\n}\n\n/**\n * Move initial focus into an overlay the way users expect: the first visible\n * form field if there is one, otherwise the first focusable element that is\n * not the overlay's own close button.\n *\n * For destructive confirmations (`destructive: true`) the corpus rule applies\n * instead: focus the non-destructive action so Enter can't confirm by\n * accident. That is the first button not rendered with the destructive\n * variant, regardless of footer order; a lone destructive button falls back to\n * the general rule.\n *\n * React's `autoFocus` prop is not reflected as a DOM attribute, so a\n * field-first rule is the reliable general behaviour.\n */\nexport function focusInitial(root: HTMLElement | null | undefined, opts: { destructive?: boolean } = {}): boolean {\n if (!root) return false;\n const all = tabStops(root);\n if (!all.length) return false;\n const candidates = all.filter((el) => !el.hasAttribute(OVERLAY_CLOSE_ATTR));\n\n // Round 18 (the owner, 55.5; round 17 F-9): a destructive dialog that holds a field is a type-to-confirm dialog — its\n // confirm stays disabled until the text matches, so the field is the safe target. Cancel is the target only when the\n // dialog is buttons-only.\n const field = candidates.find((el) => el.matches(FIELD));\n if (opts.destructive && !field) {\n const safe = candidates.find((el) => el.matches(SAFE_BUTTON));\n if (safe) {\n safe.focus();\n return true;\n }\n }\n\n const target = candidates.find((el) => el.matches(FIELD)) ?? candidates[0] ?? all[0];\n target.focus();\n return true;\n}\n\n// ─── Remembering where focus was ─────────────────────────────────────────────\n\n/**\n * The element that had focus when `open` turned true, read before this commit's children can move it. An `autoFocus`\n * field inside a dialog takes focus while React commits (the layout phase), so an effect that reads\n * document.activeElement afterwards sees that field, not the opener (run 57's type-to-confirm dialog, Sprint 25.7).\n * Insertion effects run earlier in the commit, before any of that.\n */\nexport function useFocusBeforeOpen(open: boolean): RefObject<Element | null> {\n const before = useRef<Element | null>(null);\n useInsertionEffect(() => {\n if (open) before.current = document.activeElement;\n }, [open]);\n return before;\n}\n\n/** `to` is a path above `from`: /calendar above /calendar/events/7; the site's root above /events/7 (a home page's own\n * overlay, the second review). */\nfunction pathAbove(to: string, from: string): boolean {\n const up = to.replace(/\\/+$/, \"\");\n return from.startsWith(`${up}/`) && from.replace(/\\/+$/, \"\") !== up;\n}\n\n/** What counts as \"the row\" an opener sits in, for a row that is deleted while its menu or dialog is open. */\nconst ROW = 'tr, li, [role=\"row\"], [data-card], [data-row]';\n\n/**\n * The index of the one row a deletion took out, when the rows now read as the old rows with that one removed: every\n * row after it moved up by one. A list keyed by position (the library Table, `key={i}`) keeps a deleted row's\n * elements for the next item, and a paged list keeps its length by bringing a new item into its last row. Else -1:\n * an edit (even one that makes a row read like another) or a re-sort.\n */\nfunction deletedRow(before: readonly string[], now: readonly string[]): number {\n const refilled = now.length === before.length;\n if (!refilled && now.length !== before.length - 1) return -1;\n let k = 0;\n while (k < now.length && now[k] === before[k]) k++;\n const last = now.length - 1;\n // a refilled last row shows an item the list did not show\n if (refilled && (k >= last || before.includes(now[last]))) return -1;\n for (let j = k; j < (refilled ? last : now.length); j++) if (now[j] !== before[j + 1]) return -1;\n return k;\n}\n\nexport interface RememberFocusOptions {\n /** The element's page outlives a change of path (a list beside a pane whose selection is in the URL): give focus back even then. */\n ignoreRoute?: boolean;\n}\n\nexport interface RememberedFocus {\n readonly el: HTMLElement;\n /**\n * Put focus back (Sprint 25.7). Nothing happens once the page has changed — a new path, or a new URL that took the\n * element with it — since the new page owns focus (D14(d)); a new query or hash alone (a filter, a page number) is\n * no page change, nor is a path above (an overlay's own address closing back to its page, Sprint 25.18). Otherwise the element itself; if its row was deleted, the same control in the row before, else\n * the row after, else the first Tab stop of the list (D14(c), MOJ \"Add another\" order); else `fallback`. True when\n * focus landed.\n *\n * Rows carrying `data-row-key` are told apart by it. Without one, a list keyed by position (the library Table,\n * `key={i}`) keeps a deleted row's elements for the next item, so the rows' text decides: focus follows this item\n * to the one row that now shows it, and when the rows read as the old ones with one taken out, this row's item was\n * deleted or moved up. An edit keeps focus, even one that makes the row read like another.\n *\n * When focus lands back on the element in a row, the list is watched for 10 s: a row deleted a moment later (a\n * server round trip) that takes focus with it sends focus to its neighbour the same way.\n */\n restore(fallback?: HTMLElement | null): boolean;\n}\n\nexport function rememberFocus(el: HTMLElement, { ignoreRoute = false }: RememberFocusOptions = {}): RememberedFocus {\n const { href, pathname } = location;\n const row = el.closest<HTMLElement>(ROW);\n const before = row?.previousElementSibling ?? null;\n const after = row?.nextElementSibling ?? null;\n const list = row?.parentElement ?? null;\n const index = row ? tabStops(row).indexOf(el) : -1;\n const rows = list ? Array.from(list.children) : [];\n const at = row ? rows.indexOf(row) : -1;\n const texts = rows.map((r) => r.textContent ?? \"\");\n const keyOf = (r: Element | null) => r?.getAttribute(\"data-row-key\") ?? null;\n const key = keyOf(row);\n const pageChanged = () => {\n if (ignoreRoute) return false;\n // back up to a path above: an overlay or a pane with an address of its own closing (/calendar/events/7 back to\n // /calendar, navigation#29), the same page (Sprint 25.18). A page that replaced it keeps nothing focus could land on.\n if (location.pathname !== pathname) return !pathAbove(location.pathname, pathname);\n return location.href !== href && !el.isConnected;\n };\n const land = (target: HTMLElement | null | undefined) => {\n if (!target || !target.isConnected) return false;\n target.focus();\n return document.activeElement === target;\n };\n const inRow = (r: Element | null | undefined) => {\n if (!r || !r.isConnected) return false;\n const stops = tabStops(r);\n return land(stops[Math.max(0, Math.min(index, stops.length - 1))]);\n };\n const byKey = (k: string | null) => (k !== null && list?.isConnected ? list.querySelector(`:scope > [data-row-key=\"${CSS.escape(k)}\"]`) : null);\n /** The row this item is in now, when the list moved under it; undefined when it did not. */\n const moved = (): Element | undefined => {\n if (!list?.isConnected || at < 0) return undefined;\n if (key !== null) {\n const same = byKey(key);\n return same === row ? undefined : same ?? byKey(keyOf(before)) ?? byKey(keyOf(after)) ?? undefined;\n }\n const now = Array.from(list.children, (r) => r.textContent ?? \"\");\n const text = texts[at];\n if (texts.indexOf(text) === texts.lastIndexOf(text)) {\n const found = now.flatMap((t, i) => (t === text ? [i] : []));\n if (found.length === 1) return list.children[found[0]] === row ? undefined : list.children[found[0]];\n }\n const k = deletedRow(texts, now);\n return k >= 0 && k <= at ? list.children[Math.max(0, at - 1)] ?? undefined : undefined;\n };\n const neighbour = () => inRow(byKey(keyOf(before)) ?? before) || inRow(byKey(keyOf(after)) ?? after) || (!!list?.isConnected && land(tabStops(list)[0]));\n let unwatch: (() => void) | null = null;\n const watch = () => {\n unwatch?.();\n if (!list || typeof MutationObserver === \"undefined\") return;\n const observer = new MutationObserver(() => {\n if (el.isConnected) return;\n const active = document.activeElement;\n const dropped = !active || active === document.body || !active.isConnected;\n stop();\n if (dropped && !pageChanged()) neighbour();\n });\n const onFocusIn = (e: FocusEvent) => { if (e.target !== el) stop(); };\n const timer = setTimeout(() => stop(), 10_000);\n function stop() {\n observer.disconnect();\n clearTimeout(timer);\n document.removeEventListener(\"focusin\", onFocusIn, true);\n unwatch = null;\n }\n observer.observe(list, { childList: true });\n if (list.parentNode) observer.observe(list.parentNode, { childList: true });\n document.addEventListener(\"focusin\", onFocusIn, true);\n unwatch = stop;\n };\n return {\n el,\n restore(fallback) {\n if (pageChanged()) return false;\n if (inRow(moved())) return true;\n if (land(el)) {\n if (row) watch();\n return true;\n }\n if (row && !row.isConnected && neighbour()) return true;\n return land(fallback);\n },\n };\n}\n\n/**\n * Remember what had focus when a modal overlay opened and give it back on\n * close (corpus: \"After an overlay closes, return focus to the element that\n * triggered it\"). With `root`, focus is given back only when it was lost or is\n * still inside the closing overlay, so a focus move the consumer made on close\n * wins. Returns `returnFocus()` for a command that must hand focus back before\n * it runs (CommandPalette: a command that opens a Dialog).\n */\nexport function useReturnFocus(open: boolean, root?: RefObject<HTMLElement | null>) {\n const remembered = useRef<RememberedFocus | null>(null);\n const before = useFocusBeforeOpen(open);\n useEffect(() => {\n if (open) {\n const active = before.current;\n remembered.current = active instanceof HTMLElement && active !== document.body && active.isConnected ? rememberFocus(active) : null;\n return;\n }\n const r = remembered.current;\n remembered.current = null;\n if (!r) return;\n const active = document.activeElement;\n const lost = !active || active === document.body || !active.isConnected;\n if (!root || lost || (active instanceof Node && !!root.current?.contains(active))) r.restore();\n }, [open, root, before]);\n // Unmounted while open, as Dialog gives focus back (Sprint 25.18: run 72 keys its form by the event's address, so\n // closing it unmounted an open Drawer, which gave none back; a page that renders an overlay only while open does the\n // same). Its panel is gone, so focus is lost, unless the page moved it.\n useEffect(() => () => {\n const r = remembered.current;\n remembered.current = null;\n const now = document.activeElement;\n if (r && (!now || now === document.body || !now.isConnected)) r.restore();\n }, []);\n return useCallback(() => { remembered.current?.restore(); }, []);\n}\n\n/**\n * Index of the single Tab stop in a radio-style group (APG radio pattern):\n * the checked item, else the first enabled item so a group with no selection\n * is still reachable. −1 when every item is disabled.\n */\nexport function rovingTabStop<T>(items: T[], isChecked: (item: T) => boolean, isDisabled: (item: T) => boolean = () => false): number {\n const checked = items.findIndex((item) => isChecked(item) && !isDisabled(item));\n return checked >= 0 ? checked : items.findIndex((item) => !isDisabled(item));\n}\n\n/**\n * Rule 4 (D14(c), MOJ \"Add another\" order): after the item at `index` was\n * removed, focus the item before it, else the one now in its place, else\n * `fallback`. `items` is the list as it is after the removal.\n */\nexport function focusAfterRemoval(items: ArrayLike<HTMLElement>, index: number, fallback?: HTMLElement | null) {\n (items[index - 1] ?? items[index] ?? fallback)?.focus();\n}\n\n// ─── Overlay layers ──────────────────────────────────────────────────────────\n// Every overlay that closes on Escape listens on `document`, so a Dropdown open\n// inside a Drawer used to close both with one key press. Open overlays register\n// here in the order they opened; only the topmost one acts on a global key.\n// (Two overlays opening in the same commit register child-first; in practice\n// overlays open one user action at a time.)\n\nconst layers: symbol[] = [];\n\n/**\n * Register this overlay as a layer while `open`. Returns a stable predicate\n * that is true when this overlay is the topmost open one — check it before\n * acting on Escape or trapping Tab.\n */\nexport function useOverlayLayer(open: boolean): () => boolean {\n const [id] = useState(() => Symbol(\"overlay-layer\"));\n useEffect(() => {\n if (!open) return;\n layers.push(id);\n return () => {\n const i = layers.indexOf(id);\n if (i >= 0) layers.splice(i, 1);\n };\n }, [open, id]);\n return useCallback(() => layers[layers.length - 1] === id, [id]);\n}\n\n/** Number of registered overlay layers (test seam). */\nexport function getOverlayLayerCount(): number {\n return layers.length;\n}\n\n// ─── Close on focus-out ──────────────────────────────────────────────────────\n\n/**\n * Close a non-modal popover (menu, picker, panel) when keyboard focus leaves\n * both its trigger and its panel — Tab and Shift+Tab out, or focus leaving the\n * document. Nested overlays (a Dropdown inside a Popover) register a layer on\n * top, so focus moving into them does not count as leaving. A pointer-down on\n * a non-focusable spot inside the panel briefly sends focus to body; that is\n * ignored so clicking panel chrome never closes it.\n */\nexport function useCloseOnFocusOut(\n open: boolean,\n triggerRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n onClose: () => void,\n isTop: () => boolean = ALWAYS_TOP,\n) {\n // Latest-value ref written in a layout effect, not during render (React Compiler `refs` rule).\n const onCloseRef = useLatest(onClose);\n useEffect(() => {\n if (!open) return;\n let pointerInside = false;\n const inside = (n: EventTarget | Node | null) =>\n n instanceof Node && (!!triggerRef.current?.contains(n) || !!panelRef.current?.contains(n));\n const onPointerDown = (e: PointerEvent) => { pointerInside = inside(e.target); };\n const onFocusOut = (e: FocusEvent) => {\n if (!inside(e.target)) return;\n if (e.relatedTarget) {\n if (!inside(e.relatedTarget) && isTop()) onCloseRef.current();\n return;\n }\n setTimeout(() => {\n if (pointerInside) { pointerInside = false; return; }\n if (!inside(document.activeElement) && isTop()) onCloseRef.current();\n }, 0);\n };\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n document.addEventListener(\"focusout\", onFocusOut, true);\n return () => {\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n document.removeEventListener(\"focusout\", onFocusOut, true);\n };\n }, [open, triggerRef, panelRef, isTop, onCloseRef]);\n}\n\n// ─── Close on a press outside ───────────────────────────────────────────────\n\n/**\n * Marks the presses that pass through a component's React tree, so a document\n * listener can tell a press inside it from one outside (Sprint 25.7; Radix's\n * dismissable layer). A popup that content inside it renders elsewhere in the\n * page — a Dropdown's list, a DatePicker's calendar, a Dialog — is outside its\n * element tree, but React carries the press through its own tree first. Spread\n * `props` on the tree's outer element; `marked(e)` is true for a pointer or\n * mouse press that went through it.\n */\nexport function usePressMarks() {\n const [marks] = useState(() => new WeakSet<Event>());\n return useMemo(() => {\n const mark = (e: { nativeEvent: Event }) => { marks.add(e.nativeEvent); };\n return { props: { onPointerDownCapture: mark, onMouseDownCapture: mark }, marked: (e: Event) => marks.has(e) };\n }, [marks]);\n}\n\n/**\n * Call `onOutside` for a pointer press outside `ref`'s element and its React\n * tree (Sprint 25.7): spread the returned handler on the element. For a\n * container that is not an overlay layer (the builder's floating toolbar).\n */\nexport function usePressOutside(enabled: boolean, ref: RefObject<HTMLElement | null>, onOutside: (e: PointerEvent) => void) {\n const onOutsideRef = useLatest(onOutside);\n const { props, marked } = usePressMarks();\n useEffect(() => {\n if (!enabled) return;\n const onPointerDown = (e: PointerEvent) => {\n if (marked(e) || (e.target instanceof Node && ref.current?.contains(e.target))) return;\n onOutsideRef.current(e);\n };\n document.addEventListener(\"pointerdown\", onPointerDown);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown);\n }, [enabled, ref, onOutsideRef, marked]);\n return { onPointerDownCapture: props.onPointerDownCapture };\n}\n\n// ─── Popup focus (Sprint 25.7) ───────────────────────────────────────────────\n// Every non-modal popup — a list, a menu, a picker, a panel — renders in a portal\n// at the end of <body>. Without help, a choice removes the focused option and\n// focus falls to the page, and Tab out of the panel moves on from the end of the\n// document. usePopupFocus hands focus back and makes Tab carry on from the\n// control that opened the popup, in the browser's own order.\n\nexport type PopupTabExits = \"any\" | \"edges\" | \"none\";\n\ninterface OpenPopup {\n panel: RefObject<HTMLElement | null>;\n tabExits: PopupTabExits;\n leave: (e: KeyboardEvent) => void;\n}\n\n/** Open popups, so a popup inside another popup's panel can hand a Tab exit on to it. */\nconst openPopups = new Map<symbol, OpenPopup>();\n\nexport interface PopupFocusOptions {\n trigger: RefObject<HTMLElement | null>;\n panel: RefObject<HTMLElement | null>;\n /** Closes the popup (sets its open state false). */\n close: () => void;\n /**\n * How Tab leaves the panel. \"any\": every Tab leaves (single-stop lists and\n * menus). \"edges\": Tab leaves from the panel's last Tab stop and Shift+Tab\n * from its first (panels with several stops). \"none\": focus never enters the\n * panel (a combobox keeps it in its input), so the browser's Tab is left alone.\n */\n tabExits: PopupTabExits;\n /** Runs first when Tab leaves, before the popup closes — a list commits the option the user moved to (D14(a)). */\n onTabOut?: (e: KeyboardEvent) => void;\n /**\n * True for a press that went through the popup's React tree (`usePressMarks`), so a press in a list its content\n * opened elsewhere in the page is not a press outside it.\n */\n marked?: (e: Event) => boolean;\n}\n\n/**\n * Focus for a non-modal popup (Sprint 25.7, P10):\n * - records the opener when the popup opens (the trigger when focus was on the page);\n * - closes on focus-out, and registers the overlay layer (`isTop`);\n * - Tab out of the panel closes it and carries on from the opener: Shift+Tab lands\n * on the opener; Tab puts focus on the opener and lets the browser's own Tab run,\n * except that inside a modal the opener's being its last stop wraps to its first,\n * and inside another popup's panel that popup's own exit rule then applies;\n * - on close, when focus was lost (the focused option went with the panel), puts it\n * back — unless the close followed a press outside the popup and its React tree\n * (`marked`), or the page changed;\n * - returns `returnFocus()`, which a select handler calls before the consumer's\n * callback so a dialog that callback opens records the opener, and any focus\n * move the consumer makes wins.\n */\nexport function usePopupFocus(open: boolean, { trigger, panel, close, tabExits, onTabOut, marked }: PopupFocusOptions) {\n const isTop = useOverlayLayer(open);\n const [id] = useState(() => Symbol(\"popup\"));\n const closeRef = useLatest(close);\n const onTabOutRef = useLatest(onTabOut);\n const markedRef = useLatest(marked);\n const remembered = useRef<RememberedFocus | null>(null);\n // The last press while open, and whether it was in the trigger or panel; a key press since clears it.\n const lastPress = useRef<{ e: Event; inside: boolean } | null>(null);\n const wasOpen = useRef(false);\n\n useCloseOnFocusOut(open, trigger, panel, close, isTop);\n\n // The opener, as it was before this commit (content with autoFocus moves focus in during it).\n const before = useFocusBeforeOpen(open);\n useLayoutEffect(() => {\n if (!open) return;\n const active = before.current;\n const inPanel = active instanceof Node && !!panel.current?.contains(active);\n const opener = active instanceof HTMLElement && active !== document.body && !inPanel ? active : trigger.current;\n remembered.current = opener ? rememberFocus(opener) : null;\n lastPress.current = null;\n }, [open, trigger, panel, before]);\n\n useEffect(() => {\n if (!open) return;\n const onPointerDown = (e: PointerEvent) => {\n const t = e.target;\n lastPress.current = { e, inside: t instanceof Node && (!!trigger.current?.contains(t) || !!panel.current?.contains(t)) };\n };\n const onKeyPress = () => { lastPress.current = null; };\n const leave = (e: KeyboardEvent) => {\n onTabOutRef.current?.(e);\n const r = remembered.current;\n const opener = r && r.el.isConnected ? r.el : trigger.current;\n // Focus the opener, then close now, so the panel is gone before the browser moves focus on.\n opener?.focus();\n flushSync(() => closeRef.current());\n if (!opener || !opener.isConnected) return;\n if (e.shiftKey) {\n e.preventDefault();\n return;\n }\n const modal = opener.closest<HTMLElement>('[aria-modal=\"true\"]');\n if (modal) {\n const stops = tabStops(modal);\n if (stops.length && stops[stops.length - 1] === opener) {\n e.preventDefault();\n stops[0].focus();\n return;\n }\n }\n for (const [otherId, other] of openPopups) {\n const outer = other.panel.current;\n if (otherId === id || !outer?.contains(opener)) continue;\n const stops = tabStops(outer);\n if (other.tabExits === \"any\" || stops[stops.length - 1] === opener) {\n other.leave(e);\n return;\n }\n break;\n }\n // The browser's own Tab carries on from the opener.\n };\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || e.defaultPrevented || tabExits === \"none\" || !isTop()) return;\n const p = panel.current;\n const active = document.activeElement;\n if (!p || !(active instanceof Node) || !p.contains(active)) return;\n if (tabExits === \"edges\") {\n const stops = tabStops(p);\n if (stops.length && active !== (e.shiftKey ? stops[0] : stops[stops.length - 1])) return;\n }\n leave(e);\n };\n openPopups.set(id, { panel, tabExits, leave });\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n document.addEventListener(\"keydown\", onKeyPress, true);\n document.addEventListener(\"keydown\", onKeyDown);\n return () => {\n openPopups.delete(id);\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n document.removeEventListener(\"keydown\", onKeyPress, true);\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [open, tabExits, isTop, id, trigger, panel, closeRef, onTabOutRef]);\n\n // The safety net: a close the component could not see (content that closes it,\n // a choice that removed the focused option) puts focus back when it was lost.\n useLayoutEffect(() => {\n if (open) {\n wasOpen.current = true;\n return;\n }\n if (!wasOpen.current) return;\n wasOpen.current = false;\n const r = remembered.current;\n remembered.current = null;\n // A close that followed a press outside the popup (and outside its React tree) leaves focus to that press.\n const press = lastPress.current;\n if (!r || (press && !press.inside && !markedRef.current?.(press.e))) return;\n // Focus that is somewhere real stays there: the user or the consumer put it there. Focus on the opener is\n // checked again, because the choice may have deleted the opener's row in this commit (see rememberFocus).\n const active = document.activeElement;\n if (active !== r.el && active && active !== document.body && active.isConnected) return;\n r.restore(trigger.current);\n }, [open, trigger, markedRef]);\n\n // Removed while open with focus in it (a row menu whose Delete removed its own row): give focus back once the DOM\n // has settled. The panel is still in the page while this runs. A popup focus never went into (a flyout opened by\n // hover) leaves focus where it is.\n useLayoutEffect(() => () => {\n if (!wasOpen.current) return;\n const active = document.activeElement;\n if (!(active instanceof Node) || !(panel.current?.contains(active) || trigger.current?.contains(active))) return;\n const r = remembered.current;\n queueMicrotask(() => {\n const now = document.activeElement;\n if (now && now !== document.body && now.isConnected) return;\n r?.restore();\n });\n }, [panel, trigger]);\n\n const returnFocus = useCallback(() => {\n remembered.current?.restore(trigger.current);\n }, [trigger]);\n\n return { isTop, returnFocus };\n}\n\n// ─── Grid keyboard (Sprint 25.7, P11) ────────────────────────────────────────\n\nexport interface GridKeyboardOptions<K> {\n /** Holds the items, which carry `data-grid-key={keyOf(item)}`. */\n container: RefObject<HTMLElement | null>;\n /** The item that is the Tab stop. Keep it in step with focus (setActive from the item's onFocus). */\n active: K;\n setActive: (item: K) => void;\n keyOf: (item: K) => string;\n /** Where a key moves focus from `from` (arrows, Home, End: the caller's geometry), or null for a key it leaves alone. */\n move: (from: K, e: ReactKeyboardEvent) => K | null;\n /** Enter and Space on an item. */\n onActivate?: (item: K, e: ReactKeyboardEvent) => void;\n /**\n * Printable keys go to the next item whose label starts with what was typed in the last 500 ms (the same letter\n * again cycles). Never the zoom keys (+, = and −), never with Ctrl, ⌘ or Alt.\n */\n typeahead?: { items: readonly K[]; labelOf: (item: K) => string };\n /** Runs after focus moves to an item, in place of the browser's own scrolling (a time view keeps the item clear of its pinned column). */\n scrollIntoView?: (key: string, el: HTMLElement) => void;\n /**\n * Keys held with Ctrl, ⌘ or Alt go to `move` too: an APG grid's Ctrl+Home and Ctrl+End. Typeahead and activation\n * still ignore them. Off by default, so the browser keeps its own (Alt+Left is Back).\n */\n modifierKeys?: boolean;\n}\n\nexport interface GridKeyboard<K> {\n /** On the container. */\n onKeyDown: (e: ReactKeyboardEvent) => void;\n tabIndexOf: (item: K) => 0 | -1;\n /** Focus the active item (after the next render when it is not in the DOM yet). */\n focusActive: () => void;\n /** Focus an item, as a key would (after the next render when it is not in the DOM yet); keep `active` in step. */\n focus: (item: K) => void;\n}\n\nconst TYPEAHEAD_MS = 500;\n/** The keys a time view zooms with (TimeViewport), in and out; typeahead leaves them to it. U+2212 is the minus sign. */\nexport const ZOOM_KEYS: { readonly in: readonly string[]; readonly out: readonly string[] } = { in: [\"+\", \"=\"], out: [\"-\", \"\\u2212\"] };\nconst isZoomKey = (key: string) => ZOOM_KEYS.in.includes(key) || ZOOM_KEYS.out.includes(key);\n\nfunction focusGridKey(root: HTMLElement | null, key: string, scrollIntoView?: (key: string, el: HTMLElement) => void): boolean {\n const el = root?.querySelector<HTMLElement>(`[data-grid-key=\"${CSS.escape(key)}\"]`);\n if (!el) return false;\n el.focus(scrollIntoView ? { preventScroll: true } : undefined);\n scrollIntoView?.(key, el);\n return true;\n}\n\n/** The next item whose label starts with `text`: a new search (one letter, or one letter repeated) starts after `from`. */\nfunction typeaheadMatch<K>(items: readonly K[], labelOf: (item: K) => string, keyOf: (item: K) => string, fromKey: string, text: string): K | null {\n const chars = [...text];\n const query = chars.every((c) => c === chars[0]) ? chars[0] : text;\n const at = items.findIndex((item) => keyOf(item) === fromKey);\n const first = at < 0 ? 0 : at + (query.length === 1 ? 1 : 0);\n for (let k = 0; k < items.length; k++) {\n const item = items[(first + k) % items.length];\n if (labelOf(item).trim().toLocaleLowerCase().startsWith(query)) return item;\n }\n return null;\n}\n\n/**\n * One Tab stop over a grid or rows of items (APG grid and tree patterns): the caller says where each key moves\n * (`move`), and the hook keeps the roving tabindex and puts focus on the new item — after the next render when the\n * item is not in the DOM yet (a date grid moving into the next month). Enter and Space activate, typeahead jumps by\n * label, and `scrollIntoView` places the item. It works by key, not DOM position, so a virtualised list can use it.\n * Keys typed in a field inside the container are left alone. Built for the date pickers, extended in Part D for\n * time-axis rows (it is Sprint 2X.3's `useGridNavigation`).\n */\nexport function useGridKeyboard<K>({ container, active, setActive, keyOf, move, onActivate, typeahead, scrollIntoView, modifierKeys = false }: GridKeyboardOptions<K>): GridKeyboard<K> {\n const pending = useRef<string | null>(null);\n const typed = useRef({ text: \"\", at: 0 });\n // One try, on the render the move caused: a key that never renders is dropped, so a later render cannot steal focus.\n useLayoutEffect(() => {\n const key = pending.current;\n if (key === null) return;\n pending.current = null;\n focusGridKey(container.current, key, scrollIntoView);\n });\n const activeKey = keyOf(active);\n const go = (next: K) => {\n const key = keyOf(next);\n setActive(next);\n if (!focusGridKey(container.current, key, scrollIntoView)) pending.current = key;\n };\n const onKeyDown = (e: ReactKeyboardEvent) => {\n const modified = e.altKey || e.ctrlKey || e.metaKey;\n if (e.defaultPrevented || (modified && !modifierKeys)) return;\n // React passes on keys from a portal opened inside an item (a dialog, a menu): they are that popup's own\n if (!(e.currentTarget as Node).contains(e.target as Node)) return;\n const target = e.target instanceof HTMLElement ? e.target : null;\n if (target?.closest(FIELD)) return;\n const next = move(active, e);\n if (next !== null) {\n e.preventDefault();\n go(next);\n return;\n }\n // Activation and typeahead act on the item itself, not a control inside it, and never with a modifier held.\n if (modified || target?.getAttribute(\"data-grid-key\") !== activeKey) return;\n const buffered = typed.current.text !== \"\" && Date.now() - typed.current.at <= TYPEAHEAD_MS;\n if (onActivate && (e.key === \"Enter\" || (e.key === \" \" && !buffered))) {\n e.preventDefault();\n onActivate(active, e);\n return;\n }\n if (!typeahead || [...e.key].length !== 1 || isZoomKey(e.key) || (e.key === \" \" && !buffered)) return;\n const text = (buffered ? typed.current.text : \"\") + e.key.toLocaleLowerCase();\n typed.current = { text, at: Date.now() };\n const match = typeaheadMatch(typeahead.items, typeahead.labelOf, keyOf, activeKey, text);\n if (match === null) return;\n e.preventDefault();\n if (keyOf(match) !== activeKey) go(match);\n };\n const focus = (item: K) => {\n const key = keyOf(item);\n if (!focusGridKey(container.current, key, scrollIntoView)) pending.current = key;\n };\n const focusActive = () => focus(active);\n const tabIndexOf = (item: K): 0 | -1 => (keyOf(item) === activeKey ? 0 : -1);\n return { onKeyDown, tabIndexOf, focusActive, focus };\n}\n\n// ─── Focus trap ──────────────────────────────────────────────────────────────\n\nconst ALWAYS_TOP = () => true;\n\n/**\n * Keep Tab and Shift+Tab inside `root` while `active` (corpus: a modal\n * overlay owns keyboard focus until it closes). Focus that has escaped the\n * root — a portalled menu that just closed, or the body — is pulled back in on\n * the next Tab. Pass the predicate from `useOverlayLayer` so a nested overlay\n * on top of this one keeps its own keyboard handling.\n */\nexport function useFocusTrap(root: RefObject<HTMLElement | null>, active: boolean, isTop: () => boolean = ALWAYS_TOP) {\n useEffect(() => {\n if (!active) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || e.defaultPrevented || !root.current || !isTop()) return;\n const focusable = tabStops(root.current);\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n const current = document.activeElement;\n const inside = current instanceof Node && root.current.contains(current);\n if (e.shiftKey) {\n if (!inside || current === first) {\n e.preventDefault();\n last.focus();\n }\n } else if (!inside || current === last) {\n e.preventDefault();\n first.focus();\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [root, active, isTop]);\n}\n\n// ─── Non-modal dialog ────────────────────────────────────────────────────────\n\n/**\n * The lifecycle of a persistent, non-modal dialog — an assistant panel the\n * user works alongside (Drawer with modal={false}, CornerPanel). Unlike a\n * popover it does not close when focus leaves; unlike a modal it never traps.\n *\n * While `active`: remembers the opener and gives focus back on close when focus\n * was lost or inside the panel, never from where the user moved it; moves focus\n * into the panel on open (first field, else first non-close control); closes on\n * Escape only while focus is inside the panel, it is the topmost layer and\n * nothing inside handled the key, so Escape elsewhere is untouched. Outside\n * clicks do nothing. Tab out of the panel is allowed — the panel is portalled to the end\n * of the document, so the last Tab reaches the browser chrome; a Launcher\n * shortcut is the way back in.\n */\nexport function useNonModalDialog(\n root: RefObject<HTMLElement | null>,\n active: boolean,\n onClose: () => void,\n isTop: () => boolean = ALWAYS_TOP,\n) {\n const onCloseRef = useLatest(onClose);\n useReturnFocus(active, root);\n useEffect(() => {\n if (!active) return;\n const raf = requestAnimationFrame(() => focusInitial(root.current));\n return () => cancelAnimationFrame(raf);\n }, [active, root]);\n useEffect(() => {\n if (!active) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\" || e.defaultPrevented || !isTop()) return;\n const el = root.current;\n if (!el || !el.contains(document.activeElement)) return;\n e.preventDefault();\n onCloseRef.current();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [active, root, isTop, onCloseRef]);\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { TooltipCtx, Tooltip } from './chunk-MD2EFMES.js';
2
- import { slotDraws, slotIcon } from './chunk-HYMXAIXN.js';
2
+ import { slotDraws, slotIcon } from './chunk-HXVXJUGV.js';
3
3
  import { cn } from './chunk-M2QRNBDQ.js';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
  import { createContext, forwardRef, useContext, isValidElement, useMemo } from 'react';
@@ -198,5 +198,5 @@ function preferenceActionKey(label) {
198
198
  }
199
199
 
200
200
  export { Button, ButtonSizeContext, ControlSizeContext, PREFERENCE_HOUSE_DEFAULTS, PreferencesContext, Spinner, preferenceActionKey, triggerType, usePreferences, useShellPreferences };
201
- //# sourceMappingURL=chunk-HZIP4EJI.js.map
202
- //# sourceMappingURL=chunk-HZIP4EJI.js.map
201
+ //# sourceMappingURL=chunk-ZYVXV4WC.js.map
202
+ //# sourceMappingURL=chunk-ZYVXV4WC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/Spinner.tsx","../../../showcase/src/components/Button.tsx","../../../showcase/src/lib/preferences.ts"],"names":["Button","jsx","createContext","useContext"],"mappings":"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","file":"chunk-HZIP4EJI.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\nconst sizes = {\n sm: \"h-4 w-4 border-[1.5px]\",\n md: \"h-6 w-6 border-2\",\n lg: \"h-8 w-8 border-[2.5px]\",\n} as const;\n\ninterface SpinnerProps {\n size?: keyof typeof sizes;\n color?: \"default\" | \"inherit\";\n label?: string;\n className?: string;\n}\n\nexport function Spinner({ size = \"md\", color = \"default\", label = \"Loading\", className }: SpinnerProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n className={cn(\n \"inline-block rounded-[var(--ds-radius-full)] animate-spin\",\n color === \"inherit\"\n ? \"border-current/30 border-t-current\"\n : \"border-[var(--ds-bg-emphasis)] border-t-[var(--ds-accent-primary)]\",\n sizes[size],\n className\n )}\n />\n );\n}\n","import { cn } from \"../lib/utils\";\nimport { createContext, useContext, forwardRef, isValidElement, type ButtonHTMLAttributes, type CSSProperties, type ReactNode } from \"react\";\nimport { Tooltip, TooltipCtx } from \"./Tooltip\";\nimport { Spinner } from \"./Spinner\";\nimport { slotIcon, type IconSlot, slotDraws } from \"../lib/icons\";\n\ntype ButtonVariant = \"default\" | \"secondary\" | \"tertiary\" | \"outline\" | \"ghost\" | \"destructive\" | \"success\" | \"warning\" | \"link\";\ntype ButtonSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"icon-xs\" | \"icon-sm\" | \"icon\" | \"icon-lg\";\n\nexport type ControlSize = \"sm\" | \"md\" | \"lg\";\nexport const ControlSizeContext = createContext<ControlSize | null>(null);\nexport const ButtonSizeContext = ControlSizeContext;\n\ninterface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** An icon name (\"clock\"), drawn as the theme's icon at the button's icon size, or an element. Not text: use children. */\n iconLeft?: IconSlot;\n /** As iconLeft, after the label. */\n iconRight?: IconSlot;\n loading?: boolean;\n children?: ReactNode;\n}\n\nconst variantStyles: Record<ButtonVariant, string> = {\n default: \"bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] [background-origin:border-box] text-[var(--ds-text-on-primary)] border-transparent hover:bg-[var(--ds-accent-primary-hover)] hover:brightness-110\",\n secondary: \"bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)]\",\n tertiary: \"bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)] border-transparent hover:brightness-95\",\n outline: \"bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)]\",\n ghost: \"bg-transparent text-[var(--ds-text-primary)] border-transparent hover:bg-[var(--ds-bg-muted)] shadow-none\",\n destructive: \"bg-[var(--ds-status-error)] text-[var(--ds-text-on-error)] border-transparent hover:opacity-90\",\n success: \"bg-[var(--ds-status-success)] text-[var(--ds-text-on-success)] border-transparent hover:opacity-90\",\n warning: \"bg-[var(--ds-status-warning)] text-[var(--ds-text-on-warning)] border-transparent hover:opacity-90\",\n link: \"bg-transparent text-[var(--ds-accent-primary)] hover:text-[var(--ds-accent-primary-hover)] hover:underline\",\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n // xs / icon-xs: dense tool chrome (builder toolbars, inline row actions).\n xs: \"h-ds-6 px-ds-3 py-0 text-xs\",\n sm: \"h-[var(--ds-control-height-sm)] px-ds-4 py-ds-1 text-xs\",\n md: \"h-[var(--ds-control-height-md)] px-ds-6 py-ds-2 text-sm\",\n lg: \"h-[var(--ds-control-height-lg)] px-ds-8 py-ds-3 text-base\",\n \"icon-xs\": \"h-ds-6 w-ds-6 p-0\",\n \"icon-sm\": \"h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] p-0\",\n icon: \"h-[var(--ds-control-height-md)] w-[var(--ds-control-height-md)] p-0\",\n \"icon-lg\": \"h-[var(--ds-control-height-lg)] w-[var(--ds-control-height-lg)] p-0\",\n};\n\n// Link buttons have no box, so only the text size follows `size`.\nconst linkSizeStyles: Record<ButtonSize, string> = {\n xs: \"text-xs\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n \"icon-xs\": \"text-xs\",\n \"icon-sm\": \"text-xs\",\n icon: \"text-sm\",\n \"icon-lg\": \"text-base\",\n};\n\nconst iconPaddingReduce: Record<Exclude<ButtonSize, \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\">, number> = {\n xs: 2,\n sm: 2,\n md: 4,\n lg: 6,\n};\n\n/** The icon size each button size draws a named icon at (Sprint 25.17 A26): what the library's own buttons pass by hand. */\nconst SLOT_ICON_PX: Record<ButtonSize, number> = { xs: 12, sm: 14, md: 16, lg: 18, \"icon-xs\": 12, \"icon-sm\": 14, icon: 16, \"icon-lg\": 18 };\n\nconst HOVER_SCALE_CAP = { '--_btn-scale': 'min(var(--ds-hover-scale), 1.02)' } as CSSProperties;\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button({ variant = \"default\", size: sizeProp, iconLeft, iconRight, loading, className, children, disabled, style, onClick, ...props }, ref) {\n const contextSize = useContext(ControlSizeContext);\n const size = sizeProp ?? contextSize ?? \"md\";\n const isLink = variant === \"link\";\n const spinnerSize = size === \"lg\" || size === \"icon-lg\" ? \"md\" : \"sm\";\n let paddingStyle: CSSProperties | undefined;\n const isIconSize = size === \"icon\" || size === \"icon-xs\" || size === \"icon-sm\" || size === \"icon-lg\";\n // a slot that draws nothing (text, which no icon pack names) keeps the button's even padding\n const drawsLeft = slotDraws(iconLeft);\n const drawsRight = slotDraws(iconRight);\n if (!isIconSize && !isLink && (drawsLeft || drawsRight)) {\n const reduce = iconPaddingReduce[size];\n paddingStyle = {};\n if (drawsLeft) paddingStyle.paddingLeft = `calc(${getCssPadding(size)} - ${reduce}px)`;\n if (drawsRight) paddingStyle.paddingRight = `calc(${getCssPadding(size)} - ${reduce}px)`;\n }\n\n const isLoading = loading && !disabled;\n const isDisabled = !!disabled;\n\n const handleClick = isLoading\n ? (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); }\n : onClick;\n\n const ariaLabel = props[\"aria-label\"];\n // Round 15 (52.7): inside an explicit <Tooltip> the caller owns the label; the automatic one from aria-label would be a second tooltip.\n const inTooltip = useContext(TooltipCtx);\n\n const btn = (\n <button\n ref={ref}\n data-variant={variant}\n className={cn(\n \"relative inline-flex items-center justify-center gap-ds-2 whitespace-nowrap font-medium\",\n isLink\n ? cn(\n \"px-0 py-ds-0.5 h-auto\",\n linkSizeStyles[size],\n \"transition-colors duration-[var(--ds-transition-speed)]\",\n \"focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] focus-visible:outline-offset-2\",\n \"cursor-pointer\",\n \"active:text-[var(--ds-accent-primary-active)]\",\n )\n : cn(\n \"border-[length:var(--ds-border-width)]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"shadow-[var(--ds-shadow-button)]\",\n \"hover:scale-[var(--_btn-scale)] hover:-translate-y-[var(--ds-hover-lift)]\",\n \"active:shadow-[var(--ds-shadow-button-pressed)] active:translate-x-[var(--ds-press-depth)] active:translate-y-[var(--ds-press-depth)]\",\n \"[transition:var(--ds-transition-interactive)]\",\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"cursor-pointer disabled:cursor-not-allowed disabled:opacity-disabled\",\n sizeStyles[size],\n ),\n variantStyles[variant],\n isLoading && !isLink && \"cursor-wait pointer-events-none\",\n className\n )}\n disabled={isDisabled}\n aria-busy={isLoading || undefined}\n aria-disabled={isLoading && !disabled ? true : undefined}\n onClick={handleClick}\n style={isLink ? style : { ...HOVER_SCALE_CAP, ...paddingStyle, ...style }}\n {...props}\n >\n {loading && !isLink && (\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size={spinnerSize} color=\"inherit\" />\n </span>\n )}\n <span className={cn(\"inline-flex items-center gap-ds-2 min-w-0\", loading && !isLink && \"invisible\")}>\n {slotIcon(iconLeft, SLOT_ICON_PX[size], \"Button's iconLeft\")}\n {children}\n {slotIcon(iconRight, SLOT_ICON_PX[size], \"Button's iconRight\")}\n </span>\n </button>\n );\n\n if (isIconSize && ariaLabel && !inTooltip) {\n return <Tooltip content={ariaLabel}>{btn}</Tooltip>;\n }\n\n return btn;\n});\n\nfunction getCssPadding(size: Exclude<ButtonSize, \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\">): string {\n switch (size) {\n case \"xs\": return \"var(--ds-space-3)\";\n case \"sm\": return \"var(--ds-space-4)\";\n case \"md\": return \"var(--ds-space-6)\";\n case \"lg\": return \"var(--ds-space-8)\";\n }\n}\n\n/**\n * A trigger the library wires up (a Collapsible's, a menu's, a popover's) never submits the form it sits in (0.21.0): a\n * button element or Button given no type gets type=\"button\". A page's own Button keeps HTML's default, so its submits\n * still work.\n */\nexport function triggerType(node: unknown): { type?: \"button\" } {\n if (!isValidElement(node)) return {};\n return (node.type === \"button\" || node.type === Button) && (node.props as { type?: unknown }).type === undefined ? { type: \"button\" } : {};\n}\n","/**\n * The customer's UX preferences, carried once by AppShell the way the theme is (Sprint 25.6 B9).\n *\n * The generated shell passes the object the template emits from the profile's rules; the builder preview passes the\n * same values from the theme state. Components default from it when no prop is passed — FormWrapper's validation mode\n * and actions alignment, Table and DataGrid's mobile breakpoint, Pagination's page size, EmptyState's style, the\n * destructive-action helper's mode, lists and undo window, and RelativeTime's threshold (Sprint 25.17 A29) — so a page\n * gets the customer's value by doing nothing. A\n * prop always wins; a component outside any shell keeps the default it always had (components read\n * `useShellPreferences`). App code reads `usePreferences`, which fills every field (round 21); a paged list reads its\n * page size through `usePagination`.\n */\nimport { createContext, useContext, useMemo } from \"react\";\n\nexport type PreferenceValidationTiming = \"onBlur\" | \"onSubmit\" | \"onChange\";\nexport type PreferenceBreakpoint = \"sm\" | \"md\" | \"lg\";\nexport type PreferenceEmptyStyle = \"illustration-cta\" | \"text-cta\" | \"redirect\";\nexport type PreferenceLoadingStyle = \"skeleton\" | \"spinner\" | \"progress-bar\";\nexport type PreferenceDestructiveMode = \"dialog\" | \"undo\";\n/** dataDisplay.paginationStyle — how a long list shows more: page numbers, a Load more button, or loading as the user scrolls. */\nexport type PreferencePaginationStyle = \"pagination\" | \"load-more\" | \"infinite-scroll\";\n\nexport interface AppShellPreferences {\n /** forms.validationTiming — when a field first shows its error. */\n validationTiming?: PreferenceValidationTiming;\n /** forms.actions — where a form's buttons sit and how they align. */\n formActions?: { align?: \"start\" | \"end\"; placement?: \"auto\" | \"inline\" };\n /** dataDisplay.itemsPerPage — the page size of a paged or load-more list. */\n itemsPerPage?: number;\n /** dataDisplay.paginationStyle — page numbers, a Load more button, or loading on scroll; usePagination and Pagination follow it (round 22). */\n paginationStyle?: PreferencePaginationStyle;\n /** responsive.tableToCardsBreakpoint — below this width a Table becomes stacked cards. */\n tableToCardsBreakpoint?: PreferenceBreakpoint;\n /** emptyStates.emptyStyle — illustration with an action, text with an action, or redirect to creation. */\n emptyStyle?: PreferenceEmptyStyle;\n /** emptyStates.loadingStyle — what a page shows while data loads. */\n loadingStyle?: PreferenceLoadingStyle;\n /** destructiveActions.mode — confirm in a dialog, or act now with an Undo toast. */\n destructiveMode?: PreferenceDestructiveMode;\n /** destructiveActions.highSeverityActions — always confirm in a dialog, even in undo mode. */\n highSeverityActions?: string[];\n /** destructiveActions.typeToConfirmActions — the user types the name before the confirm enables. */\n typeToConfirmActions?: string[];\n /** destructiveActions.undoWindowSeconds — how long the Undo toast stays. */\n undoWindowSeconds?: number;\n /** dateTime.relativeThresholdHours — within this many hours a time reads relative (\"2 hours ago\"), older the date (RelativeTime). */\n relativeThresholdHours?: number;\n}\n\nexport const PreferencesContext = createContext<AppShellPreferences | null>(null);\n\n/** Every preference in force, the shell's value or the house default — what app code reads. */\nexport interface ResolvedPreferences {\n validationTiming: PreferenceValidationTiming;\n formActions: { align: \"start\" | \"end\"; placement: \"auto\" | \"inline\" };\n itemsPerPage: number;\n paginationStyle: PreferencePaginationStyle;\n tableToCardsBreakpoint: PreferenceBreakpoint;\n emptyStyle: PreferenceEmptyStyle;\n loadingStyle: PreferenceLoadingStyle;\n destructiveMode: PreferenceDestructiveMode;\n highSeverityActions: string[];\n typeToConfirmActions: string[];\n undoWindowSeconds: number;\n relativeThresholdHours: number;\n}\n\n/**\n * The house defaults for the fields the shell carries: the platform's preference-defaults.ts, mirrored in\n * decisions-schema.ts (DEFAULT_PATTERN_DECISIONS); a test keeps the three equal.\n */\nconst HOUSE_DEFAULTS: ResolvedPreferences = {\n validationTiming: \"onBlur\",\n formActions: { align: \"end\", placement: \"auto\" },\n itemsPerPage: 25,\n paginationStyle: \"pagination\",\n tableToCardsBreakpoint: \"md\",\n emptyStyle: \"illustration-cta\",\n loadingStyle: \"skeleton\",\n destructiveMode: \"dialog\",\n highSeverityActions: [\"delete_account\", \"remove_team_member\"],\n typeToConfirmActions: [\"delete_account\"],\n undoWindowSeconds: 5,\n relativeThresholdHours: 24,\n};\nexport const PREFERENCE_HOUSE_DEFAULTS: Readonly<ResolvedPreferences> = Object.freeze(HOUSE_DEFAULTS);\n\n/**\n * For app code: every preference in force — the shell's value, else the house default — so a page never writes a\n * fallback of its own. Round 21 (run 58): with every field optional, TypeScript made the builder write\n * `const { itemsPerPage = 20 } = usePreferences()`, a second copy of the preference it then edited by hand when the\n * rule changed. Paged lists use `usePagination`, which reads the page size itself.\n */\nexport function usePreferences(): ResolvedPreferences {\n const raw = useShellPreferences();\n return useMemo(() => resolvePreferences(raw), [raw]);\n}\n\n/** The shell's values over the house defaults; a field the shell leaves undefined keeps its default. */\nexport function resolvePreferences(raw: AppShellPreferences): ResolvedPreferences {\n const d = PREFERENCE_HOUSE_DEFAULTS;\n const out: ResolvedPreferences = { ...d, formActions: { ...d.formActions }, highSeverityActions: [...d.highSeverityActions], typeToConfirmActions: [...d.typeToConfirmActions] };\n for (const [key, value] of Object.entries(raw) as Array<[keyof AppShellPreferences, unknown]>) {\n if (value === undefined) continue;\n if (key === \"formActions\") {\n const actions = value as AppShellPreferences[\"formActions\"];\n if (actions?.align !== undefined) out.formActions.align = actions.align;\n if (actions?.placement !== undefined) out.formActions.placement = actions.placement;\n continue;\n }\n (out as unknown as Record<string, unknown>)[key] = value;\n }\n return out;\n}\n\n/**\n * For components: only what the shell passed, or an empty object outside any shell — a component with no prop takes\n * the shell's value, and a bare component keeps the default it always had.\n */\nexport function useShellPreferences(): AppShellPreferences {\n return useContext(PreferencesContext) ?? EMPTY;\n}\n\nconst EMPTY: AppShellPreferences = Object.freeze({});\n\n/**\n * \"Remove range\" → \"remove_range\": the key the high-severity and type-to-confirm lists use. The engine's `actionKey`\n * (platform/src/lib/destructive-mode.ts) is the same rule for compliance; a platform test keeps the two identical.\n */\nexport function preferenceActionKey(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_|_$/g, \"\");\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/Spinner.tsx","../../../showcase/src/components/Button.tsx","../../../showcase/src/lib/preferences.ts"],"names":["Button","jsx","createContext","useContext"],"mappings":"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","file":"chunk-ZYVXV4WC.js","sourcesContent":["import { cn } from \"../lib/utils\";\n\nconst sizes = {\n sm: \"h-4 w-4 border-[1.5px]\",\n md: \"h-6 w-6 border-2\",\n lg: \"h-8 w-8 border-[2.5px]\",\n} as const;\n\ninterface SpinnerProps {\n size?: keyof typeof sizes;\n color?: \"default\" | \"inherit\";\n label?: string;\n className?: string;\n}\n\nexport function Spinner({ size = \"md\", color = \"default\", label = \"Loading\", className }: SpinnerProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n className={cn(\n \"inline-block rounded-[var(--ds-radius-full)] animate-spin\",\n color === \"inherit\"\n ? \"border-current/30 border-t-current\"\n : \"border-[var(--ds-bg-emphasis)] border-t-[var(--ds-accent-primary)]\",\n sizes[size],\n className\n )}\n />\n );\n}\n","import { cn } from \"../lib/utils\";\nimport { createContext, useContext, forwardRef, isValidElement, type ButtonHTMLAttributes, type CSSProperties, type ReactNode } from \"react\";\nimport { Tooltip, TooltipCtx } from \"./Tooltip\";\nimport { Spinner } from \"./Spinner\";\nimport { slotIcon, type IconSlot, slotDraws } from \"../lib/icons\";\n\ntype ButtonVariant = \"default\" | \"secondary\" | \"tertiary\" | \"outline\" | \"ghost\" | \"destructive\" | \"success\" | \"warning\" | \"link\";\ntype ButtonSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"icon-xs\" | \"icon-sm\" | \"icon\" | \"icon-lg\";\n\nexport type ControlSize = \"sm\" | \"md\" | \"lg\";\nexport const ControlSizeContext = createContext<ControlSize | null>(null);\nexport const ButtonSizeContext = ControlSizeContext;\n\ninterface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** An icon name (\"clock\"), drawn as the theme's icon at the button's icon size, or an element. Not text: use children. */\n iconLeft?: IconSlot;\n /** As iconLeft, after the label. */\n iconRight?: IconSlot;\n loading?: boolean;\n children?: ReactNode;\n}\n\nconst variantStyles: Record<ButtonVariant, string> = {\n default: \"bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] [background-origin:border-box] text-[var(--ds-text-on-primary)] border-transparent hover:bg-[var(--ds-accent-primary-hover)] hover:brightness-110\",\n secondary: \"bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)]\",\n tertiary: \"bg-[var(--ds-bg-muted)] text-[var(--ds-text-primary)] border-transparent hover:brightness-95\",\n outline: \"bg-[var(--ds-input-bg)] text-[var(--ds-text-primary)] border-[var(--ds-border-default)] hover:bg-[var(--ds-bg-muted)]\",\n ghost: \"bg-transparent text-[var(--ds-text-primary)] border-transparent hover:bg-[var(--ds-bg-muted)] shadow-none\",\n destructive: \"bg-[var(--ds-status-error)] text-[var(--ds-text-on-error)] border-transparent hover:opacity-90\",\n success: \"bg-[var(--ds-status-success)] text-[var(--ds-text-on-success)] border-transparent hover:opacity-90\",\n warning: \"bg-[var(--ds-status-warning)] text-[var(--ds-text-on-warning)] border-transparent hover:opacity-90\",\n link: \"bg-transparent text-[var(--ds-accent-primary)] hover:text-[var(--ds-accent-primary-hover)] hover:underline\",\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n // xs / icon-xs: dense tool chrome (builder toolbars, inline row actions).\n xs: \"h-ds-6 px-ds-3 py-0 text-xs\",\n sm: \"h-[var(--ds-control-height-sm)] px-ds-4 py-ds-1 text-xs\",\n md: \"h-[var(--ds-control-height-md)] px-ds-6 py-ds-2 text-sm\",\n lg: \"h-[var(--ds-control-height-lg)] px-ds-8 py-ds-3 text-base\",\n \"icon-xs\": \"h-ds-6 w-ds-6 p-0\",\n \"icon-sm\": \"h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] p-0\",\n icon: \"h-[var(--ds-control-height-md)] w-[var(--ds-control-height-md)] p-0\",\n \"icon-lg\": \"h-[var(--ds-control-height-lg)] w-[var(--ds-control-height-lg)] p-0\",\n};\n\n// Link buttons have no box, so only the text size follows `size`.\nconst linkSizeStyles: Record<ButtonSize, string> = {\n xs: \"text-xs\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n \"icon-xs\": \"text-xs\",\n \"icon-sm\": \"text-xs\",\n icon: \"text-sm\",\n \"icon-lg\": \"text-base\",\n};\n\nconst iconPaddingReduce: Record<Exclude<ButtonSize, \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\">, number> = {\n xs: 2,\n sm: 2,\n md: 4,\n lg: 6,\n};\n\n/** The icon size each button size draws a named icon at (Sprint 25.17 A26): what the library's own buttons pass by hand. */\nconst SLOT_ICON_PX: Record<ButtonSize, number> = { xs: 12, sm: 14, md: 16, lg: 18, \"icon-xs\": 12, \"icon-sm\": 14, icon: 16, \"icon-lg\": 18 };\n\nconst HOVER_SCALE_CAP = { '--_btn-scale': 'min(var(--ds-hover-scale), 1.02)' } as CSSProperties;\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button({ variant = \"default\", size: sizeProp, iconLeft, iconRight, loading, className, children, disabled, style, onClick, ...props }, ref) {\n const contextSize = useContext(ControlSizeContext);\n const size = sizeProp ?? contextSize ?? \"md\";\n const isLink = variant === \"link\";\n const spinnerSize = size === \"lg\" || size === \"icon-lg\" ? \"md\" : \"sm\";\n let paddingStyle: CSSProperties | undefined;\n const isIconSize = size === \"icon\" || size === \"icon-xs\" || size === \"icon-sm\" || size === \"icon-lg\";\n // a slot that draws nothing (text, which no icon pack names) keeps the button's even padding\n const drawsLeft = slotDraws(iconLeft);\n const drawsRight = slotDraws(iconRight);\n if (!isIconSize && !isLink && (drawsLeft || drawsRight)) {\n const reduce = iconPaddingReduce[size];\n paddingStyle = {};\n if (drawsLeft) paddingStyle.paddingLeft = `calc(${getCssPadding(size)} - ${reduce}px)`;\n if (drawsRight) paddingStyle.paddingRight = `calc(${getCssPadding(size)} - ${reduce}px)`;\n }\n\n const isLoading = loading && !disabled;\n const isDisabled = !!disabled;\n\n const handleClick = isLoading\n ? (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); }\n : onClick;\n\n const ariaLabel = props[\"aria-label\"];\n // Round 15 (52.7): inside an explicit <Tooltip> the caller owns the label; the automatic one from aria-label would be a second tooltip.\n const inTooltip = useContext(TooltipCtx);\n\n const btn = (\n <button\n ref={ref}\n data-variant={variant}\n className={cn(\n \"relative inline-flex items-center justify-center gap-ds-2 whitespace-nowrap font-medium\",\n isLink\n ? cn(\n \"px-0 py-ds-0.5 h-auto\",\n linkSizeStyles[size],\n \"transition-colors duration-[var(--ds-transition-speed)]\",\n \"focus-visible:outline-2 focus-visible:outline-[var(--ds-accent-primary)] focus-visible:outline-offset-2\",\n \"cursor-pointer\",\n \"active:text-[var(--ds-accent-primary-active)]\",\n )\n : cn(\n \"border-[length:var(--ds-border-width)]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"shadow-[var(--ds-shadow-button)]\",\n \"hover:scale-[var(--_btn-scale)] hover:-translate-y-[var(--ds-hover-lift)]\",\n \"active:shadow-[var(--ds-shadow-button-pressed)] active:translate-x-[var(--ds-press-depth)] active:translate-y-[var(--ds-press-depth)]\",\n \"[transition:var(--ds-transition-interactive)]\",\n \"focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]\",\n \"cursor-pointer disabled:cursor-not-allowed disabled:opacity-disabled\",\n sizeStyles[size],\n ),\n variantStyles[variant],\n isLoading && !isLink && \"cursor-wait pointer-events-none\",\n className\n )}\n disabled={isDisabled}\n aria-busy={isLoading || undefined}\n aria-disabled={isLoading && !disabled ? true : undefined}\n onClick={handleClick}\n style={isLink ? style : { ...HOVER_SCALE_CAP, ...paddingStyle, ...style }}\n {...props}\n >\n {loading && !isLink && (\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <Spinner size={spinnerSize} color=\"inherit\" />\n </span>\n )}\n <span className={cn(\"inline-flex items-center gap-ds-2 min-w-0\", loading && !isLink && \"invisible\")}>\n {slotIcon(iconLeft, SLOT_ICON_PX[size], \"Button's iconLeft\")}\n {children}\n {slotIcon(iconRight, SLOT_ICON_PX[size], \"Button's iconRight\")}\n </span>\n </button>\n );\n\n if (isIconSize && ariaLabel && !inTooltip) {\n return <Tooltip content={ariaLabel}>{btn}</Tooltip>;\n }\n\n return btn;\n});\n\nfunction getCssPadding(size: Exclude<ButtonSize, \"icon\" | \"icon-xs\" | \"icon-sm\" | \"icon-lg\">): string {\n switch (size) {\n case \"xs\": return \"var(--ds-space-3)\";\n case \"sm\": return \"var(--ds-space-4)\";\n case \"md\": return \"var(--ds-space-6)\";\n case \"lg\": return \"var(--ds-space-8)\";\n }\n}\n\n/**\n * A trigger the library wires up (a Collapsible's, a menu's, a popover's) never submits the form it sits in (0.21.0): a\n * button element or Button given no type gets type=\"button\". A page's own Button keeps HTML's default, so its submits\n * still work.\n */\nexport function triggerType(node: unknown): { type?: \"button\" } {\n if (!isValidElement(node)) return {};\n return (node.type === \"button\" || node.type === Button) && (node.props as { type?: unknown }).type === undefined ? { type: \"button\" } : {};\n}\n","/**\n * The customer's UX preferences, carried once by AppShell the way the theme is (Sprint 25.6 B9).\n *\n * The generated shell passes the object the template emits from the profile's rules; the builder preview passes the\n * same values from the theme state. Components default from it when no prop is passed — FormWrapper's validation mode\n * and actions alignment, Table and DataGrid's mobile breakpoint, Pagination's page size, EmptyState's style, the\n * destructive-action helper's mode, lists and undo window, and RelativeTime's threshold (Sprint 25.17 A29) — so a page\n * gets the customer's value by doing nothing. A\n * prop always wins; a component outside any shell keeps the default it always had (components read\n * `useShellPreferences`). App code reads `usePreferences`, which fills every field (round 21); a paged list reads its\n * page size through `usePagination`.\n */\nimport { createContext, useContext, useMemo } from \"react\";\n\nexport type PreferenceValidationTiming = \"onBlur\" | \"onSubmit\" | \"onChange\";\nexport type PreferenceBreakpoint = \"sm\" | \"md\" | \"lg\";\nexport type PreferenceEmptyStyle = \"illustration-cta\" | \"text-cta\" | \"redirect\";\nexport type PreferenceLoadingStyle = \"skeleton\" | \"spinner\" | \"progress-bar\";\nexport type PreferenceDestructiveMode = \"dialog\" | \"undo\";\n/** dataDisplay.paginationStyle — how a long list shows more: page numbers, a Load more button, or loading as the user scrolls. */\nexport type PreferencePaginationStyle = \"pagination\" | \"load-more\" | \"infinite-scroll\";\n\nexport interface AppShellPreferences {\n /** forms.validationTiming — when a field first shows its error. */\n validationTiming?: PreferenceValidationTiming;\n /** forms.actions — where a form's buttons sit and how they align. */\n formActions?: { align?: \"start\" | \"end\"; placement?: \"auto\" | \"inline\" };\n /** dataDisplay.itemsPerPage — the page size of a paged or load-more list. */\n itemsPerPage?: number;\n /** dataDisplay.paginationStyle — page numbers, a Load more button, or loading on scroll; usePagination and Pagination follow it (round 22). */\n paginationStyle?: PreferencePaginationStyle;\n /** responsive.tableToCardsBreakpoint — below this width a Table becomes stacked cards. */\n tableToCardsBreakpoint?: PreferenceBreakpoint;\n /** emptyStates.emptyStyle — illustration with an action, text with an action, or redirect to creation. */\n emptyStyle?: PreferenceEmptyStyle;\n /** emptyStates.loadingStyle — what a page shows while data loads. */\n loadingStyle?: PreferenceLoadingStyle;\n /** destructiveActions.mode — confirm in a dialog, or act now with an Undo toast. */\n destructiveMode?: PreferenceDestructiveMode;\n /** destructiveActions.highSeverityActions — always confirm in a dialog, even in undo mode. */\n highSeverityActions?: string[];\n /** destructiveActions.typeToConfirmActions — the user types the name before the confirm enables. */\n typeToConfirmActions?: string[];\n /** destructiveActions.undoWindowSeconds — how long the Undo toast stays. */\n undoWindowSeconds?: number;\n /** dateTime.relativeThresholdHours — within this many hours a time reads relative (\"2 hours ago\"), older the date (RelativeTime). */\n relativeThresholdHours?: number;\n}\n\nexport const PreferencesContext = createContext<AppShellPreferences | null>(null);\n\n/** Every preference in force, the shell's value or the house default — what app code reads. */\nexport interface ResolvedPreferences {\n validationTiming: PreferenceValidationTiming;\n formActions: { align: \"start\" | \"end\"; placement: \"auto\" | \"inline\" };\n itemsPerPage: number;\n paginationStyle: PreferencePaginationStyle;\n tableToCardsBreakpoint: PreferenceBreakpoint;\n emptyStyle: PreferenceEmptyStyle;\n loadingStyle: PreferenceLoadingStyle;\n destructiveMode: PreferenceDestructiveMode;\n highSeverityActions: string[];\n typeToConfirmActions: string[];\n undoWindowSeconds: number;\n relativeThresholdHours: number;\n}\n\n/**\n * The house defaults for the fields the shell carries: the platform's preference-defaults.ts, mirrored in\n * decisions-schema.ts (DEFAULT_PATTERN_DECISIONS); a test keeps the three equal.\n */\nconst HOUSE_DEFAULTS: ResolvedPreferences = {\n validationTiming: \"onBlur\",\n formActions: { align: \"end\", placement: \"auto\" },\n itemsPerPage: 25,\n paginationStyle: \"pagination\",\n tableToCardsBreakpoint: \"md\",\n emptyStyle: \"illustration-cta\",\n loadingStyle: \"skeleton\",\n destructiveMode: \"dialog\",\n highSeverityActions: [\"delete_account\", \"remove_team_member\"],\n typeToConfirmActions: [\"delete_account\"],\n undoWindowSeconds: 5,\n relativeThresholdHours: 24,\n};\nexport const PREFERENCE_HOUSE_DEFAULTS: Readonly<ResolvedPreferences> = Object.freeze(HOUSE_DEFAULTS);\n\n/**\n * For app code: every preference in force — the shell's value, else the house default — so a page never writes a\n * fallback of its own. Round 21 (run 58): with every field optional, TypeScript made the builder write\n * `const { itemsPerPage = 20 } = usePreferences()`, a second copy of the preference it then edited by hand when the\n * rule changed. Paged lists use `usePagination`, which reads the page size itself.\n */\nexport function usePreferences(): ResolvedPreferences {\n const raw = useShellPreferences();\n return useMemo(() => resolvePreferences(raw), [raw]);\n}\n\n/** The shell's values over the house defaults; a field the shell leaves undefined keeps its default. */\nexport function resolvePreferences(raw: AppShellPreferences): ResolvedPreferences {\n const d = PREFERENCE_HOUSE_DEFAULTS;\n const out: ResolvedPreferences = { ...d, formActions: { ...d.formActions }, highSeverityActions: [...d.highSeverityActions], typeToConfirmActions: [...d.typeToConfirmActions] };\n for (const [key, value] of Object.entries(raw) as Array<[keyof AppShellPreferences, unknown]>) {\n if (value === undefined) continue;\n if (key === \"formActions\") {\n const actions = value as AppShellPreferences[\"formActions\"];\n if (actions?.align !== undefined) out.formActions.align = actions.align;\n if (actions?.placement !== undefined) out.formActions.placement = actions.placement;\n continue;\n }\n (out as unknown as Record<string, unknown>)[key] = value;\n }\n return out;\n}\n\n/**\n * For components: only what the shell passed, or an empty object outside any shell — a component with no prop takes\n * the shell's value, and a bare component keeps the default it always had.\n */\nexport function useShellPreferences(): AppShellPreferences {\n return useContext(PreferencesContext) ?? EMPTY;\n}\n\nconst EMPTY: AppShellPreferences = Object.freeze({});\n\n/**\n * \"Remove range\" → \"remove_range\": the key the high-severity and type-to-confirm lists use. The engine's `actionKey`\n * (platform/src/lib/destructive-mode.ts) is the same rule for compliance; a platform test keeps the two identical.\n */\nexport function preferenceActionKey(label: string): string {\n return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_|_$/g, \"\");\n}\n"]}
package/dist/data-grid.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { useCardsSwitch, CARDS_BELOW, inferColumnKinds, CardValue } from './chunk-OTCEJN64.js';
2
- import { usePagination, useWholeListCheck, pagerPart, Pagination, Input } from './chunk-W7GCEJEH.js';
3
- import { useShellPreferences } from './chunk-HZIP4EJI.js';
2
+ import { usePagination, useWholeListCheck, pagerPart, Pagination, Input } from './chunk-CYN5O567.js';
3
+ import { useShellPreferences } from './chunk-ZYVXV4WC.js';
4
4
  import { useBreakpoint } from './chunk-CRQ6KCRD.js';
5
5
  import { FormChangeCtx } from './chunk-XVB55M3N.js';
6
6
  import { useFramesItself, TableCardCtx, RowsCtx } from './chunk-QM4SJKWV.js';
7
- import { useGridKeyboard } from './chunk-JXANCXK6.js';
7
+ import { useGridKeyboard } from './chunk-ZI3OQDET.js';
8
8
  import { useTextOverflow, Tooltip } from './chunk-MD2EFMES.js';
9
9
  import './chunk-7Q46JXSK.js';
10
- import { Icon } from './chunk-HYMXAIXN.js';
10
+ import { Icon } from './chunk-HXVXJUGV.js';
11
11
  import './chunk-HP7WUZPK.js';
12
12
  import './chunk-WH2FUFAC.js';
13
13
  import { cn } from './chunk-M2QRNBDQ.js';
@@ -1,5 +1,5 @@
1
1
  import { matchMentions, shownMentions, mentionOptionId, MentionList } from './chunk-RWKCAKN3.js';
2
- import { useOverlayLayer } from './chunk-JXANCXK6.js';
2
+ import { useOverlayLayer } from './chunk-ZI3OQDET.js';
3
3
  import './chunk-HP7WUZPK.js';
4
4
  import { useLatest } from './chunk-WH2FUFAC.js';
5
5
  import './chunk-M2QRNBDQ.js';