gridsmith-ui 0.25.0 → 0.27.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 (105) hide show
  1. package/CHANGELOG.md +969 -0
  2. package/README.md +21 -5
  3. package/dist/charts.d.ts +2 -2
  4. package/dist/charts.js +58 -43
  5. package/dist/charts.js.map +1 -1
  6. package/dist/chat.js +4 -3
  7. package/dist/chat.js.map +1 -1
  8. package/dist/{chunk-W7GCEJEH.js → chunk-2CKSSWYG.js} +10 -6
  9. package/dist/chunk-2CKSSWYG.js.map +1 -0
  10. package/dist/chunk-3BRQRKDO.js +1 -0
  11. package/dist/chunk-3BRQRKDO.js.map +1 -1
  12. package/dist/{chunk-RWKCAKN3.js → chunk-5TUVX4B2.js} +6 -5
  13. package/dist/chunk-5TUVX4B2.js.map +1 -0
  14. package/dist/{chunk-X2HEV7FB.js → chunk-64TXZ4L7.js} +23 -33
  15. package/dist/chunk-64TXZ4L7.js.map +1 -0
  16. package/dist/chunk-7Q46JXSK.js +1 -0
  17. package/dist/chunk-7Q46JXSK.js.map +1 -1
  18. package/dist/chunk-CRQ6KCRD.js +1 -0
  19. package/dist/chunk-CRQ6KCRD.js.map +1 -1
  20. package/dist/{chunk-HZIP4EJI.js → chunk-EKBWWA6P.js} +53 -6
  21. package/dist/chunk-EKBWWA6P.js.map +1 -0
  22. package/dist/chunk-HP7WUZPK.js +1 -0
  23. package/dist/chunk-HP7WUZPK.js.map +1 -1
  24. package/dist/{chunk-QGSCF7PN.js → chunk-IXR4UEF4.js} +5 -4
  25. package/dist/chunk-IXR4UEF4.js.map +1 -0
  26. package/dist/chunk-M2QRNBDQ.js +1 -0
  27. package/dist/chunk-M2QRNBDQ.js.map +1 -1
  28. package/dist/chunk-MD2EFMES.js +1 -0
  29. package/dist/chunk-MD2EFMES.js.map +1 -1
  30. package/dist/chunk-NRHIYS5W.js +1 -0
  31. package/dist/chunk-NRHIYS5W.js.map +1 -1
  32. package/dist/chunk-OTCEJN64.js +1 -0
  33. package/dist/chunk-OTCEJN64.js.map +1 -1
  34. package/dist/chunk-OYNUU346.js +78 -0
  35. package/dist/chunk-OYNUU346.js.map +1 -0
  36. package/dist/{chunk-FHMCAOAH.js → chunk-PO65I3MQ.js} +4 -3
  37. package/dist/chunk-PO65I3MQ.js.map +1 -0
  38. package/dist/chunk-QM4SJKWV.js +1 -0
  39. package/dist/chunk-QM4SJKWV.js.map +1 -1
  40. package/dist/{chunk-3CUKJ4VP.js → chunk-R5NFCEZ3.js} +11 -8
  41. package/dist/chunk-R5NFCEZ3.js.map +1 -0
  42. package/dist/{chunk-2YSEME33.js → chunk-SFY2KMHG.js} +64 -6
  43. package/dist/chunk-SFY2KMHG.js.map +1 -0
  44. package/dist/{chunk-HYMXAIXN.js → chunk-TJEV7YAY.js} +38 -10
  45. package/dist/chunk-TJEV7YAY.js.map +1 -0
  46. package/dist/chunk-WH2FUFAC.js +1 -0
  47. package/dist/chunk-WH2FUFAC.js.map +1 -1
  48. package/dist/chunk-XVB55M3N.js +1 -0
  49. package/dist/chunk-XVB55M3N.js.map +1 -1
  50. package/dist/{chunk-JXANCXK6.js → chunk-ZI3OQDET.js} +19 -4
  51. package/dist/chunk-ZI3OQDET.js.map +1 -0
  52. package/dist/data-grid.d.ts +1 -1
  53. package/dist/data-grid.js +6 -4
  54. package/dist/data-grid.js.map +1 -1
  55. package/dist/editor-mentions.js +3 -2
  56. package/dist/editor-mentions.js.map +1 -1
  57. package/dist/editor.js +4 -3
  58. package/dist/editor.js.map +1 -1
  59. package/dist/{feather-W26ERBQV.js → feather-FNBQ74RR.js} +12 -18
  60. package/dist/feather-FNBQ74RR.js.map +1 -0
  61. package/dist/{heroicons-MY43466C.js → heroicons-DODCSYWD.js} +11 -9
  62. package/dist/heroicons-DODCSYWD.js.map +1 -0
  63. package/dist/icons.d.ts +32 -5
  64. package/dist/icons.js +2 -1
  65. package/dist/icons.js.map +1 -1
  66. package/dist/index.d.ts +469 -67
  67. package/dist/index.js +3017 -1751
  68. package/dist/index.js.map +1 -1
  69. package/dist/kanban.js +5 -5
  70. package/dist/{lucide-CMPVM3PS.js → lucide-ZWE3EPGP.js} +11 -9
  71. package/dist/lucide-ZWE3EPGP.js.map +1 -0
  72. package/dist/{phosphor-RMHZOW2O.js → phosphor-DIHPA4AJ.js} +11 -9
  73. package/dist/phosphor-DIHPA4AJ.js.map +1 -0
  74. package/dist/resizable.js +8 -7
  75. package/dist/resizable.js.map +1 -1
  76. package/dist/roots.css +43 -7
  77. package/dist/{tabler-MJVEYHA5.js → tabler-D3VYTSGI.js} +11 -9
  78. package/dist/tabler-D3VYTSGI.js.map +1 -0
  79. package/dist/tokens.css +17 -8
  80. package/dist/{use-pagination-BnKOnBdz.d.ts → use-pagination-BxcRlNQG.d.ts} +6 -0
  81. package/dist/utils.d.ts +275 -1
  82. package/dist/utils.js +875 -1
  83. package/dist/utils.js.map +1 -1
  84. package/dist/virtualized.d.ts +1 -1
  85. package/dist/virtualized.js +6 -4
  86. package/dist/virtualized.js.map +1 -1
  87. package/dist-static/tokens.css +17 -8
  88. package/package.json +5 -3
  89. package/dist/chunk-2YSEME33.js.map +0 -1
  90. package/dist/chunk-3CUKJ4VP.js.map +0 -1
  91. package/dist/chunk-FHMCAOAH.js.map +0 -1
  92. package/dist/chunk-HYMXAIXN.js.map +0 -1
  93. package/dist/chunk-HZIP4EJI.js.map +0 -1
  94. package/dist/chunk-JXANCXK6.js.map +0 -1
  95. package/dist/chunk-QGSCF7PN.js.map +0 -1
  96. package/dist/chunk-RWKCAKN3.js.map +0 -1
  97. package/dist/chunk-W7GCEJEH.js.map +0 -1
  98. package/dist/chunk-WOVYHFUD.js +0 -50
  99. package/dist/chunk-WOVYHFUD.js.map +0 -1
  100. package/dist/chunk-X2HEV7FB.js.map +0 -1
  101. package/dist/feather-W26ERBQV.js.map +0 -1
  102. package/dist/heroicons-MY43466C.js.map +0 -1
  103. package/dist/lucide-CMPVM3PS.js.map +0 -1
  104. package/dist/phosphor-RMHZOW2O.js.map +0 -1
  105. package/dist/tabler-MJVEYHA5.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/Tooltip.tsx","../../../showcase/src/lib/use-text-overflow.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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","file":"chunk-MD2EFMES.js","sourcesContent":["import { useState, useRef, useCallback, useEffect, useId, type HTMLAttributes, type ReactNode, type Ref, createContext } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Slot, mergeRefs } from \"../lib/slot\";\nimport { usePosition, type Side } from \"../lib/use-position\";\n\ninterface TooltipProps extends Omit<HTMLAttributes<HTMLElement>, \"content\"> {\n content: ReactNode;\n children: ReactNode;\n side?: Side;\n delay?: number;\n className?: string;\n container?: Element | DocumentFragment;\n /**\n * Keep the Tooltip in the tree but never show it. Use this instead of rendering\n * the Tooltip only sometimes: adding or removing the wrapper remounts its child,\n * which drops focus from a field inside it (Sprint 25.7, the Combobox).\n */\n disabled?: boolean;\n /**\n * A Tooltip can itself be the child of a Slot — `<DropdownMenu trigger={<Tooltip …><Button/></Tooltip>}>` —\n * so the ref and handlers a parent merges onto it are passed through to its own trigger (14 Sep 2026).\n */\n ref?: Ref<HTMLElement>;\n}\n\n// 600ms: long enough that passing the pointer over a toolbar, or landing focus\n// on an icon button, does not flash a label; short enough to still feel live.\n/** True inside an explicit <Tooltip>: an icon-size Button then skips the tooltip it would otherwise make from its aria-label (round 15, 52.7 — two tooltips on one button). */\nexport const TooltipCtx = createContext(false);\n\nexport function Tooltip({ content, children, side = \"top\", delay = 600, className, container, disabled = false, ref: forwardedRef, ...slotProps }: TooltipProps) {\n const [open, setOpen] = useState(false);\n // Disabling while shown hides it (render-time state adjustment).\n if (disabled && open) setOpen(false);\n const triggerRef = useRef<HTMLElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n // Set when the trigger is activated: the label must not reappear (from the\n // focus the click leaves behind, or a still-pending timer) until the pointer\n // leaves and comes back.\n const suppressedRef = useRef(false);\n const id = useId();\n\n const pos = usePosition(triggerRef, floatingRef, open, { side, offset: 6, flip: true });\n\n const cancel = useCallback(() => {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }, []);\n\n // One pending timer at a time: hover and focus both call show(), and the\n // earlier timer used to be orphaned, firing after hide() had run and leaving\n // the tooltip up with nothing left to close it.\n const show = useCallback(() => {\n if (disabled || suppressedRef.current) return;\n cancel();\n timeoutRef.current = setTimeout(() => setOpen(true), delay);\n }, [delay, cancel, disabled]);\n\n const hide = useCallback(() => {\n cancel();\n setOpen(false);\n }, [cancel]);\n\n // Pointer focus (a click) does not warrant a label; keyboard focus does. The element focused is asked, not the\n // trigger: focus that reaches the trigger from a control inside it (a card title's button) is that control's, and\n // the trigger itself never matched, so the label never showed by keyboard (the review of 30 September).\n const showOnFocus = useCallback((e: React.FocusEvent<HTMLElement>) => {\n let keyboard = true;\n const focused = e.target instanceof Element ? e.target : e.currentTarget;\n try { keyboard = focused.matches(\":focus-visible\"); } catch { /* selector unsupported: assume keyboard */ }\n if (keyboard) show();\n }, [show]);\n\n // Activating the trigger dismisses the label and keeps it away until the\n // pointer has left — what opens next (a menu, a dialog) owns the screen.\n const dismissOnActivate = useCallback(() => {\n suppressedRef.current = true;\n hide();\n }, [hide]);\n\n // Suppression lifts when the pointer leaves or focus moves on — a keyboard\n // user never produces a mouseleave, so blur must reset it too.\n const leave = useCallback(() => {\n suppressedRef.current = false;\n hide();\n }, [hide]);\n\n // Escape dismisses an open tooltip (WCAG 1.4.13); it does not stop the key\n // reaching whatever overlay is also listening.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, hide]);\n\n useEffect(() => cancel, [cancel]);\n\n return (\n <TooltipCtx value={true}>\n <Slot\n {...slotProps}\n ref={mergeRefs(triggerRef, forwardedRef)}\n onMouseEnter={(e) => { slotProps.onMouseEnter?.(e); show(); }}\n onMouseLeave={(e) => { slotProps.onMouseLeave?.(e); leave(); }}\n onFocus={(e) => { slotProps.onFocus?.(e); showOnFocus(e); }}\n onBlur={(e) => { slotProps.onBlur?.(e); leave(); }}\n onPointerDown={(e) => { slotProps.onPointerDown?.(e); dismissOnActivate(); }}\n onKeyDown={(e) => { slotProps.onKeyDown?.(e); if (e.key === \"Enter\" || e.key === \" \") dismissOnActivate(); }}\n aria-describedby={open ? id : undefined}\n >\n {children}\n </Slot>\n {open && createPortal(\n <div\n ref={floatingRef}\n id={id}\n role=\"tooltip\"\n className={cn(\n \"fixed z-[var(--ds-z-tooltip)] pointer-events-none\",\n \"px-ds-2.5 py-ds-1.5 text-xs\",\n \"max-w-[280px] break-words\",\n \"bg-[var(--ds-bg-surface-elevated)] text-[var(--ds-text-primary)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n className\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {content}\n </div>,\n container ?? document.body\n )}\n </TooltipCtx>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { useLatest } from \"./use-latest\";\n\n// ─── Shared ResizeObserver (singleton per document) ────────────────────────────\n// All useTextOverflow instances share a single ResizeObserver. This avoids the\n// performance cost of 80+ independent observers on pages with many truncated elements.\n\ntype OverflowCallback = () => void;\n\nconst registry = new WeakMap<Element, Set<OverflowCallback>>();\nlet sharedObserver: ResizeObserver | null = null;\nlet rafId: number | null = null;\nconst pendingElements = new Set<Element>();\n\nfunction getSharedObserver(): ResizeObserver {\n if (sharedObserver) return sharedObserver;\n sharedObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n pendingElements.add(entry.target);\n }\n if (rafId === null) {\n rafId = requestAnimationFrame(flush);\n }\n });\n return sharedObserver;\n}\n\nfunction flush() {\n rafId = null;\n for (const el of pendingElements) {\n const callbacks = registry.get(el);\n if (callbacks) {\n for (const cb of callbacks) cb();\n }\n }\n pendingElements.clear();\n}\n\nfunction observe(el: Element, callback: OverflowCallback) {\n let set = registry.get(el);\n if (!set) {\n set = new Set();\n registry.set(el, set);\n }\n set.add(callback);\n if (set.size === 1) {\n getSharedObserver().observe(el);\n }\n}\n\nfunction unobserve(el: Element, callback: OverflowCallback) {\n const set = registry.get(el);\n if (!set) return;\n set.delete(callback);\n if (set.size === 0) {\n registry.delete(el);\n getSharedObserver().unobserve(el);\n }\n}\n\n// ─── Overflow detection ────────────────────────────────────────────────────────\n\nconst TOLERANCE = 1; // px — accounts for integer rounding in scrollWidth/clientWidth\n\nfunction checkOverflow(el: HTMLElement, lines: number): boolean {\n if (lines <= 1) {\n return el.scrollWidth - el.clientWidth > TOLERANCE;\n }\n return el.scrollHeight - el.clientHeight > TOLERANCE;\n}\n\n// ─── Hook ──────────────────────────────────────────────────────────────────────\n\nexport interface UseTextOverflowOptions {\n /** Number of visible lines before truncation. 1 = single-line ellipsis, >1 = line-clamp. */\n lines?: number;\n /** Disable overflow detection (e.g. when component is hidden) */\n disabled?: boolean;\n /** Content key — triggers re-check when this value changes (use for dynamic text content) */\n content?: unknown;\n}\n\nexport interface UseTextOverflowResult {\n /** Callback ref — attach to the element that has truncation CSS applied */\n ref: (node: HTMLElement | null) => void;\n /** True when the element's text is currently overflowing/truncated */\n isTruncated: boolean;\n}\n\n/**\n * Detects whether an element's text content is overflowing (truncated).\n * Uses a single shared ResizeObserver for all instances on the page.\n *\n * @example\n * const { ref, isTruncated } = useTextOverflow({ lines: 1 });\n * return (\n * <Tooltip content={fullText} disabled={!isTruncated}>\n * <span ref={ref} className=\"truncate\">{fullText}</span>\n * </Tooltip>\n * );\n */\nexport function useTextOverflow(options: UseTextOverflowOptions = {}): UseTextOverflowResult {\n const { lines = 1, disabled = false, content } = options;\n const [isTruncated, setIsTruncated] = useState(false);\n const elementRef = useRef<HTMLElement | null>(null);\n // The observer-registered wrapper delegates to the latest check logic via checkRef.\n const registeredRef = useRef<OverflowCallback | null>(null);\n\n const doCheck = useCallback(() => {\n if (!elementRef.current || disabled) {\n setIsTruncated(false);\n return;\n }\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }, [lines, disabled]);\n\n // Latest check logic, updated in a layout effect so the stable wrapper never runs a stale closure.\n const checkRef = useLatest<OverflowCallback>(doCheck);\n\n // Re-check when content changes (handles dynamic text like Dropdown selection)\n // or when disabled state changes\n // Measures the committed DOM, so it must stay a post-layout effect.\n /* eslint-disable react-hooks/set-state-in-effect -- overflow is a layout measurement */\n useEffect(() => {\n if (disabled) {\n setIsTruncated(false);\n } else if (elementRef.current) {\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }\n }, [content, lines, disabled]);\n /* eslint-enable react-hooks/set-state-in-effect */\n\n const ref = useCallback(\n (node: HTMLElement | null) => {\n const prev = elementRef.current;\n\n // Unregister previous element using the SAME function we registered with\n if (prev && registeredRef.current) {\n unobserve(prev, registeredRef.current);\n registeredRef.current = null;\n }\n\n elementRef.current = node;\n\n if (node && !disabled) {\n // Stable wrapper that delegates to the latest check logic\n const wrapper: OverflowCallback = () => checkRef.current?.();\n registeredRef.current = wrapper;\n observe(node, wrapper);\n // Initial check on mount\n const initial = checkOverflow(node, lines);\n setIsTruncated(initial);\n }\n },\n [lines, disabled, checkRef],\n );\n\n // Cleanup on unmount — uses the registered wrapper, not the check function\n useEffect(() => {\n return () => {\n if (elementRef.current && registeredRef.current) {\n unobserve(elementRef.current, registeredRef.current);\n }\n };\n }, []);\n\n return { ref, isTruncated };\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/Tooltip.tsx","../../../showcase/src/lib/use-text-overflow.ts"],"names":["useState","useRef","useCallback","useEffect"],"mappings":"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","file":"chunk-MD2EFMES.js","sourcesContent":["import { useState, useRef, useCallback, useEffect, useId, type HTMLAttributes, type ReactNode, type Ref, createContext } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn, GLASS_DROPDOWN } from \"../lib/utils\";\nimport { Slot, mergeRefs } from \"../lib/slot\";\nimport { usePosition, type Side } from \"../lib/use-position\";\n\ninterface TooltipProps extends Omit<HTMLAttributes<HTMLElement>, \"content\"> {\n content: ReactNode;\n children: ReactNode;\n side?: Side;\n delay?: number;\n className?: string;\n container?: Element | DocumentFragment;\n /**\n * Keep the Tooltip in the tree but never show it. Use this instead of rendering\n * the Tooltip only sometimes: adding or removing the wrapper remounts its child,\n * which drops focus from a field inside it (Sprint 25.7, the Combobox).\n */\n disabled?: boolean;\n /**\n * A Tooltip can itself be the child of a Slot — `<DropdownMenu trigger={<Tooltip …><Button/></Tooltip>}>` —\n * so the ref and handlers a parent merges onto it are passed through to its own trigger (14 Sep 2026).\n */\n ref?: Ref<HTMLElement>;\n}\n\n// 600ms: long enough that passing the pointer over a toolbar, or landing focus\n// on an icon button, does not flash a label; short enough to still feel live.\n/** True inside an explicit <Tooltip>: an icon-size Button then skips the tooltip it would otherwise make from its aria-label (round 15, 52.7 — two tooltips on one button). */\nexport const TooltipCtx = createContext(false);\n\nexport function Tooltip({ content, children, side = \"top\", delay = 600, className, container, disabled = false, ref: forwardedRef, ...slotProps }: TooltipProps) {\n const [open, setOpen] = useState(false);\n // Disabling while shown hides it (render-time state adjustment).\n if (disabled && open) setOpen(false);\n const triggerRef = useRef<HTMLElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n // Set when the trigger is activated: the label must not reappear (from the\n // focus the click leaves behind, or a still-pending timer) until the pointer\n // leaves and comes back.\n const suppressedRef = useRef(false);\n const id = useId();\n\n const pos = usePosition(triggerRef, floatingRef, open, { side, offset: 6, flip: true });\n\n const cancel = useCallback(() => {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }, []);\n\n // One pending timer at a time: hover and focus both call show(), and the\n // earlier timer used to be orphaned, firing after hide() had run and leaving\n // the tooltip up with nothing left to close it.\n const show = useCallback(() => {\n if (disabled || suppressedRef.current) return;\n cancel();\n timeoutRef.current = setTimeout(() => setOpen(true), delay);\n }, [delay, cancel, disabled]);\n\n const hide = useCallback(() => {\n cancel();\n setOpen(false);\n }, [cancel]);\n\n // Pointer focus (a click) does not warrant a label; keyboard focus does. The element focused is asked, not the\n // trigger: focus that reaches the trigger from a control inside it (a card title's button) is that control's, and\n // the trigger itself never matched, so the label never showed by keyboard (the review of 30 September).\n const showOnFocus = useCallback((e: React.FocusEvent<HTMLElement>) => {\n let keyboard = true;\n const focused = e.target instanceof Element ? e.target : e.currentTarget;\n try { keyboard = focused.matches(\":focus-visible\"); } catch { /* selector unsupported: assume keyboard */ }\n if (keyboard) show();\n }, [show]);\n\n // Activating the trigger dismisses the label and keeps it away until the\n // pointer has left — what opens next (a menu, a dialog) owns the screen.\n const dismissOnActivate = useCallback(() => {\n suppressedRef.current = true;\n hide();\n }, [hide]);\n\n // Suppression lifts when the pointer leaves or focus moves on — a keyboard\n // user never produces a mouseleave, so blur must reset it too.\n const leave = useCallback(() => {\n suppressedRef.current = false;\n hide();\n }, [hide]);\n\n // Escape dismisses an open tooltip (WCAG 1.4.13); it does not stop the key\n // reaching whatever overlay is also listening.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, hide]);\n\n useEffect(() => cancel, [cancel]);\n\n return (\n <TooltipCtx value={true}>\n <Slot\n {...slotProps}\n ref={mergeRefs(triggerRef, forwardedRef)}\n onMouseEnter={(e) => { slotProps.onMouseEnter?.(e); show(); }}\n onMouseLeave={(e) => { slotProps.onMouseLeave?.(e); leave(); }}\n onFocus={(e) => { slotProps.onFocus?.(e); showOnFocus(e); }}\n onBlur={(e) => { slotProps.onBlur?.(e); leave(); }}\n onPointerDown={(e) => { slotProps.onPointerDown?.(e); dismissOnActivate(); }}\n onKeyDown={(e) => { slotProps.onKeyDown?.(e); if (e.key === \"Enter\" || e.key === \" \") dismissOnActivate(); }}\n aria-describedby={open ? id : undefined}\n >\n {children}\n </Slot>\n {open && createPortal(\n <div\n ref={floatingRef}\n id={id}\n role=\"tooltip\"\n className={cn(\n \"fixed z-[var(--ds-z-tooltip)] pointer-events-none\",\n \"px-ds-2.5 py-ds-1.5 text-xs\",\n \"max-w-[280px] break-words\",\n \"bg-[var(--ds-bg-surface-elevated)] text-[var(--ds-text-primary)]\",\n GLASS_DROPDOWN,\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n \"rounded-[var(--ds-radius-sm)]\",\n className\n )}\n style={{ top: pos.top, left: pos.left }}\n >\n {content}\n </div>,\n container ?? document.body\n )}\n </TooltipCtx>\n );\n}\n","import { useState, useCallback, useRef, useEffect } from \"react\";\nimport { useLatest } from \"./use-latest\";\n\n// ─── Shared ResizeObserver (singleton per document) ────────────────────────────\n// All useTextOverflow instances share a single ResizeObserver. This avoids the\n// performance cost of 80+ independent observers on pages with many truncated elements.\n\ntype OverflowCallback = () => void;\n\nconst registry = new WeakMap<Element, Set<OverflowCallback>>();\nlet sharedObserver: ResizeObserver | null = null;\nlet rafId: number | null = null;\nconst pendingElements = new Set<Element>();\n\nfunction getSharedObserver(): ResizeObserver {\n if (sharedObserver) return sharedObserver;\n sharedObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n pendingElements.add(entry.target);\n }\n if (rafId === null) {\n rafId = requestAnimationFrame(flush);\n }\n });\n return sharedObserver;\n}\n\nfunction flush() {\n rafId = null;\n for (const el of pendingElements) {\n const callbacks = registry.get(el);\n if (callbacks) {\n for (const cb of callbacks) cb();\n }\n }\n pendingElements.clear();\n}\n\nfunction observe(el: Element, callback: OverflowCallback) {\n let set = registry.get(el);\n if (!set) {\n set = new Set();\n registry.set(el, set);\n }\n set.add(callback);\n if (set.size === 1) {\n getSharedObserver().observe(el);\n }\n}\n\nfunction unobserve(el: Element, callback: OverflowCallback) {\n const set = registry.get(el);\n if (!set) return;\n set.delete(callback);\n if (set.size === 0) {\n registry.delete(el);\n getSharedObserver().unobserve(el);\n }\n}\n\n// ─── Overflow detection ────────────────────────────────────────────────────────\n\nconst TOLERANCE = 1; // px — accounts for integer rounding in scrollWidth/clientWidth\n\nfunction checkOverflow(el: HTMLElement, lines: number): boolean {\n if (lines <= 1) {\n return el.scrollWidth - el.clientWidth > TOLERANCE;\n }\n return el.scrollHeight - el.clientHeight > TOLERANCE;\n}\n\n// ─── Hook ──────────────────────────────────────────────────────────────────────\n\nexport interface UseTextOverflowOptions {\n /** Number of visible lines before truncation. 1 = single-line ellipsis, >1 = line-clamp. */\n lines?: number;\n /** Disable overflow detection (e.g. when component is hidden) */\n disabled?: boolean;\n /** Content key — triggers re-check when this value changes (use for dynamic text content) */\n content?: unknown;\n}\n\nexport interface UseTextOverflowResult {\n /** Callback ref — attach to the element that has truncation CSS applied */\n ref: (node: HTMLElement | null) => void;\n /** True when the element's text is currently overflowing/truncated */\n isTruncated: boolean;\n}\n\n/**\n * Detects whether an element's text content is overflowing (truncated).\n * Uses a single shared ResizeObserver for all instances on the page.\n *\n * @example\n * const { ref, isTruncated } = useTextOverflow({ lines: 1 });\n * return (\n * <Tooltip content={fullText} disabled={!isTruncated}>\n * <span ref={ref} className=\"truncate\">{fullText}</span>\n * </Tooltip>\n * );\n */\nexport function useTextOverflow(options: UseTextOverflowOptions = {}): UseTextOverflowResult {\n const { lines = 1, disabled = false, content } = options;\n const [isTruncated, setIsTruncated] = useState(false);\n const elementRef = useRef<HTMLElement | null>(null);\n // The observer-registered wrapper delegates to the latest check logic via checkRef.\n const registeredRef = useRef<OverflowCallback | null>(null);\n\n const doCheck = useCallback(() => {\n if (!elementRef.current || disabled) {\n setIsTruncated(false);\n return;\n }\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }, [lines, disabled]);\n\n // Latest check logic, updated in a layout effect so the stable wrapper never runs a stale closure.\n const checkRef = useLatest<OverflowCallback>(doCheck);\n\n // Re-check when content changes (handles dynamic text like Dropdown selection)\n // or when disabled state changes\n // Measures the committed DOM, so it must stay a post-layout effect.\n /* eslint-disable react-hooks/set-state-in-effect -- overflow is a layout measurement */\n useEffect(() => {\n if (disabled) {\n setIsTruncated(false);\n } else if (elementRef.current) {\n const next = checkOverflow(elementRef.current, lines);\n setIsTruncated((prev) => (prev !== next ? next : prev));\n }\n }, [content, lines, disabled]);\n /* eslint-enable react-hooks/set-state-in-effect */\n\n const ref = useCallback(\n (node: HTMLElement | null) => {\n const prev = elementRef.current;\n\n // Unregister previous element using the SAME function we registered with\n if (prev && registeredRef.current) {\n unobserve(prev, registeredRef.current);\n registeredRef.current = null;\n }\n\n elementRef.current = node;\n\n if (node && !disabled) {\n // Stable wrapper that delegates to the latest check logic\n const wrapper: OverflowCallback = () => checkRef.current?.();\n registeredRef.current = wrapper;\n observe(node, wrapper);\n // Initial check on mount\n const initial = checkOverflow(node, lines);\n setIsTruncated(initial);\n }\n },\n [lines, disabled, checkRef],\n );\n\n // Cleanup on unmount — uses the registered wrapper, not the check function\n useEffect(() => {\n return () => {\n if (elementRef.current && registeredRef.current) {\n unobserve(elementRef.current, registeredRef.current);\n }\n };\n }, []);\n\n return { ref, isTruncated };\n}\n"]}
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  // ../../showcase/src/lib/icon-packs/utils.ts
2
3
  function pascalToKebab(s) {
3
4
  return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").replace(/([a-zA-Z])(\d)/g, "$1-$2").replace(/(\d)([a-zA-Z])/g, "$1-$2").toLowerCase();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/icon-packs/utils.ts"],"names":[],"mappings":";AAEA,SAAS,cAAc,CAAA,EAAmB;AACxC,EAAA,OAAO,EACJ,OAAA,CAAQ,oBAAA,EAAsB,OAAO,CAAA,CACrC,QAAQ,sBAAA,EAAwB,OAAO,CAAA,CACvC,OAAA,CAAQ,mBAAmB,OAAO,CAAA,CAClC,QAAQ,iBAAA,EAAmB,OAAO,EAClC,WAAA,EAAY;AACjB;AAEA,SAAS,sBAAsB,KAAA,EAAyB;AACtD,EAAA,IAAI,OAAO,KAAA,KAAU,UAAA,EAAY,OAAO,IAAA;AACxC,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,UAAA,IAAc,KAAA;AACtE;AAQO,SAAS,WAAA,CAAY,KAA8B,KAAA,EAAwC;AAChG,EAAA,MAAM,MAAe,EAAC;AACtB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,UAAU,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AACrD,IAAA,MAAM,KAAA,GAAQ,IAAI,UAAU,CAAA;AAC5B,IAAA,IAAI,qBAAA,CAAsB,KAAK,CAAA,EAAG,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,SACxC,OAAA,CAAQ,KAAK,UAAU,CAAA;AAAA,EAC9B;AACA,EAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG,OAAA,CAAQ,IAAA,CAAK,CAAA,cAAA,EAAiB,OAAA,CAAQ,MAAM,CAAA,yDAAA,EAA4D,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AACpJ,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,gBAAA,CACd,GAAA,EACA,OAAA,EACA,IAAA,EACS;AACT,EAAA,MAAM,MAAe,EAAC;AAEtB,EAAA,KAAA,MAAW,CAAC,UAAA,EAAY,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AACrD,IAAA,IAAI,CAAC,qBAAA,CAAsB,KAAK,CAAA,EAAG;AACnC,IAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,IAAK,UAAA,CAAW,CAAC,CAAA,KAAM,UAAA,CAAW,CAAC,CAAA,CAAE,WAAA,EAAY,EAAG;AAErE,IAAA,IAAI,IAAA,GAAO,UAAA;AACX,IAAA,IAAI,MAAM,WAAA,IAAe,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,WAAW,CAAA,EAAG;AAC1D,MAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA;AAAA,IAC3C;AACA,IAAA,IAAI,MAAM,WAAA,IAAe,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,WAAW,CAAA,EAAG;AACxD,MAAA,IAAA,GAAO,KAAK,KAAA,CAAM,CAAA,EAAG,CAAC,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IAC/C;AACA,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,GAAA,CAAI,aAAA,CAAc,IAAI,CAAC,CAAA,GAAI,KAAA;AAAA,EAC7B;AAEA,EAAA,MAAA,CAAO,MAAA,CAAO,KAAK,OAAO,CAAA;AAC1B,EAAA,OAAO,GAAA;AACT","file":"chunk-NRHIYS5W.js","sourcesContent":["import type { IconMap, IconComponent } from './types'\n\nfunction pascalToKebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/([A-Z])([A-Z][a-z])/g, \"$1-$2\")\n .replace(/([a-zA-Z])(\\d)/g, \"$1-$2\")\n .replace(/(\\d)([a-zA-Z])/g, \"$1-$2\")\n .toLowerCase()\n}\n\nfunction isRenderableComponent(value: unknown): boolean {\n if (typeof value === \"function\") return true\n return typeof value === \"object\" && value !== null && \"$$typeof\" in value\n}\n\n/**\n * The curated map from an installed pack's namespace: each curated name looks up\n * its export by string. A name the installed version lacks is skipped with one\n * warning rather than breaking every icon; icon-completeness.test.tsx enforces\n * completeness against the versions this repo installs.\n */\nexport function pickCurated(mod: Record<string, unknown>, names: Record<string, string>): IconMap {\n const map: IconMap = {}\n const missing: string[] = []\n for (const [key, exportName] of Object.entries(names)) {\n const value = mod[exportName]\n if (isRenderableComponent(value)) map[key] = value as IconComponent\n else missing.push(exportName)\n }\n if (missing.length > 0) console.warn(`gridsmith-ui: ${missing.length} curated icon export(s) not found in the installed pack: ${missing.join(', ')}`)\n return map\n}\n\nexport function buildExtendedMap(\n mod: Record<string, unknown>,\n aliases: IconMap,\n opts?: { stripPrefix?: string; stripSuffix?: string },\n): IconMap {\n const map: IconMap = {}\n\n for (const [exportName, value] of Object.entries(mod)) {\n if (!isRenderableComponent(value)) continue\n if (!exportName[0] || exportName[0] !== exportName[0].toUpperCase()) continue\n\n let name = exportName\n if (opts?.stripPrefix && name.startsWith(opts.stripPrefix)) {\n name = name.slice(opts.stripPrefix.length)\n }\n if (opts?.stripSuffix && name.endsWith(opts.stripSuffix)) {\n name = name.slice(0, -opts.stripSuffix.length)\n }\n if (!name) continue\n\n map[pascalToKebab(name)] = value as IconComponent\n }\n\n Object.assign(map, aliases)\n return map\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/lib/icon-packs/utils.ts"],"names":[],"mappings":";;AAEA,SAAS,cAAc,CAAA,EAAmB;AACxC,EAAA,OAAO,EACJ,OAAA,CAAQ,oBAAA,EAAsB,OAAO,CAAA,CACrC,QAAQ,sBAAA,EAAwB,OAAO,CAAA,CACvC,OAAA,CAAQ,mBAAmB,OAAO,CAAA,CAClC,QAAQ,iBAAA,EAAmB,OAAO,EAClC,WAAA,EAAY;AACjB;AAEA,SAAS,sBAAsB,KAAA,EAAyB;AACtD,EAAA,IAAI,OAAO,KAAA,KAAU,UAAA,EAAY,OAAO,IAAA;AACxC,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,UAAA,IAAc,KAAA;AACtE;AAQO,SAAS,WAAA,CAAY,KAA8B,KAAA,EAAwC;AAChG,EAAA,MAAM,MAAe,EAAC;AACtB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,UAAU,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AACrD,IAAA,MAAM,KAAA,GAAQ,IAAI,UAAU,CAAA;AAC5B,IAAA,IAAI,qBAAA,CAAsB,KAAK,CAAA,EAAG,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,SACxC,OAAA,CAAQ,KAAK,UAAU,CAAA;AAAA,EAC9B;AACA,EAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG,OAAA,CAAQ,IAAA,CAAK,CAAA,cAAA,EAAiB,OAAA,CAAQ,MAAM,CAAA,yDAAA,EAA4D,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AACpJ,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,gBAAA,CACd,GAAA,EACA,OAAA,EACA,IAAA,EACS;AACT,EAAA,MAAM,MAAe,EAAC;AAEtB,EAAA,KAAA,MAAW,CAAC,UAAA,EAAY,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AACrD,IAAA,IAAI,CAAC,qBAAA,CAAsB,KAAK,CAAA,EAAG;AACnC,IAAA,IAAI,CAAC,UAAA,CAAW,CAAC,CAAA,IAAK,UAAA,CAAW,CAAC,CAAA,KAAM,UAAA,CAAW,CAAC,CAAA,CAAE,WAAA,EAAY,EAAG;AAErE,IAAA,IAAI,IAAA,GAAO,UAAA;AACX,IAAA,IAAI,MAAM,WAAA,IAAe,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,WAAW,CAAA,EAAG;AAC1D,MAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA;AAAA,IAC3C;AACA,IAAA,IAAI,MAAM,WAAA,IAAe,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,WAAW,CAAA,EAAG;AACxD,MAAA,IAAA,GAAO,KAAK,KAAA,CAAM,CAAA,EAAG,CAAC,IAAA,CAAK,YAAY,MAAM,CAAA;AAAA,IAC/C;AACA,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,GAAA,CAAI,aAAA,CAAc,IAAI,CAAC,CAAA,GAAI,KAAA;AAAA,EAC7B;AAEA,EAAA,MAAA,CAAO,MAAA,CAAO,KAAK,OAAO,CAAA;AAC1B,EAAA,OAAO,GAAA;AACT","file":"chunk-NRHIYS5W.js","sourcesContent":["import type { IconMap, IconComponent } from './types'\n\nfunction pascalToKebab(s: string): string {\n return s\n .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n .replace(/([A-Z])([A-Z][a-z])/g, \"$1-$2\")\n .replace(/([a-zA-Z])(\\d)/g, \"$1-$2\")\n .replace(/(\\d)([a-zA-Z])/g, \"$1-$2\")\n .toLowerCase()\n}\n\nfunction isRenderableComponent(value: unknown): boolean {\n if (typeof value === \"function\") return true\n return typeof value === \"object\" && value !== null && \"$$typeof\" in value\n}\n\n/**\n * The curated map from an installed pack's namespace: each curated name looks up\n * its export by string. A name the installed version lacks is skipped with one\n * warning rather than breaking every icon; icon-completeness.test.tsx enforces\n * completeness against the versions this repo installs.\n */\nexport function pickCurated(mod: Record<string, unknown>, names: Record<string, string>): IconMap {\n const map: IconMap = {}\n const missing: string[] = []\n for (const [key, exportName] of Object.entries(names)) {\n const value = mod[exportName]\n if (isRenderableComponent(value)) map[key] = value as IconComponent\n else missing.push(exportName)\n }\n if (missing.length > 0) console.warn(`gridsmith-ui: ${missing.length} curated icon export(s) not found in the installed pack: ${missing.join(', ')}`)\n return map\n}\n\nexport function buildExtendedMap(\n mod: Record<string, unknown>,\n aliases: IconMap,\n opts?: { stripPrefix?: string; stripSuffix?: string },\n): IconMap {\n const map: IconMap = {}\n\n for (const [exportName, value] of Object.entries(mod)) {\n if (!isRenderableComponent(value)) continue\n if (!exportName[0] || exportName[0] !== exportName[0].toUpperCase()) continue\n\n let name = exportName\n if (opts?.stripPrefix && name.startsWith(opts.stripPrefix)) {\n name = name.slice(opts.stripPrefix.length)\n }\n if (opts?.stripSuffix && name.endsWith(opts.stripSuffix)) {\n name = name.slice(0, -opts.stripSuffix.length)\n }\n if (!name) continue\n\n map[pascalToKebab(name)] = value as IconComponent\n }\n\n Object.assign(map, aliases)\n return map\n}\n"]}
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { useTextOverflow, Tooltip } from './chunk-MD2EFMES.js';
2
3
  import { cn } from './chunk-M2QRNBDQ.js';
3
4
  import { useRef, useState, useLayoutEffect, Fragment } from 'react';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/table-utils.ts","../../../showcase/src/lib/card-cell.tsx","../../../showcase/src/lib/use-cards-switch.ts"],"names":[],"mappings":"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","file":"chunk-OTCEJN64.js","sourcesContent":["/** Shared column-kind inference for Table and DataGrid. */\n\n/**\n * What a column holds, which decides how its cells lay out: numbers and dates shrink to their content and stay whole,\n * an identifier (a long run of digits: a barcode, an ID) may wrap anywhere, an email truncates, text wraps at spaces.\n */\nexport type ColumnKind = \"date\" | \"number\" | \"email\" | \"label\" | \"text\" | \"identifier\";\n\nconst DATE_ISO = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_US = /^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/;\n// a month's name or its short form, with or without a full stop: \"Jan\", \"Sept\", \"Sep.\", \"September\" (Sprint 25.13 A35)\nconst MONTH = \"[A-Z][a-z]{2,8}\\\\.?\";\nconst DATE_TEXT_MDY = new RegExp(`^${MONTH}\\\\s+\\\\d{1,2},?\\\\s+\\\\d{4}$`); // \"Jan 12, 2024\" or \"Sept 12 2024\"\nconst DATE_TEXT_DMY = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH}\\\\s+\\\\d{4}$`); // \"12 Jan 2024\", \"24 Sept 2026\"\n// a date this year, with no year: \"Sep 16\", \"16 Sept\" (the review of 30 September). Only a month's own name, so \"Room 12\"\n// isn't a date.\nconst MONTH_NAME = \"(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\\\.?\";\nconst DATE_TEXT_MD = new RegExp(`^${MONTH_NAME}\\\\s+\\\\d{1,2}$`);\nconst DATE_TEXT_DM = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH_NAME}$`);\n// a number, with a currency before or a unit after it: \"$1,200\", \"12%\", \"18 KB\", \"1.2 MB\", \"250 ms\" (Sprint 25.13 A35)\nconst NUMBER_RE = /^[$€£¥]?\\s?[\\d,]+\\.?\\d*\\s?(?:%|[KMGTP]i?B|B|bytes?|ms|s|sec|min|h|hrs?|kg|g|km|m|°[CF])?$/i;\n// a long run of digits is an identifier, never a quantity: a barcode or an account number (Sprint 25.13 A35)\nconst IDENTIFIER_RE = /^\\d{13,}$/;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isDate(v: string): boolean {\n return DATE_ISO.test(v) || DATE_US.test(v) || DATE_TEXT_MDY.test(v) || DATE_TEXT_DMY.test(v) || DATE_TEXT_MD.test(v) || DATE_TEXT_DM.test(v);\n}\n\nfunction isJsx(v: unknown): boolean {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction detectKind(values: unknown[]): ColumnKind | undefined {\n // Filter out null/undefined\n const present = values.filter((v) => v != null);\n if (present.length === 0) return undefined;\n\n // Any JSX value means no kind\n if (present.some(isJsx)) return undefined;\n\n // Check date (strings only)\n if (present.every((v) => typeof v === \"string\" && isDate(v))) return \"date\";\n\n // A long run of digits, as text or as a number too large to be one: an identifier\n if (present.every((v) => (typeof v === \"string\" && IDENTIFIER_RE.test(v)) || (typeof v === \"number\" && Math.abs(v) >= 1e13 && Number.isInteger(v)))) return \"identifier\";\n\n // Check number: all typeof number, or all strings matching the number pattern\n if (present.every((v) => typeof v === \"number\")) return \"number\";\n if (present.every((v) => typeof v === \"string\" && NUMBER_RE.test(v))) return \"number\";\n\n // Check email\n if (present.every((v) => typeof v === \"string\" && EMAIL_RE.test(v))) return \"email\";\n\n // Check label: strings <= 20 chars, no line breaks\n if (present.every((v) => typeof v === \"string\" && v.length <= 20 && !/[\\n\\r]/.test(v))) return \"label\";\n\n // Fallback\n return \"text\";\n}\n\n/**\n * The kind of a column from the words its cells show (Sprint 25.13 A35): a column of elements has no kind from its\n * values, so the Table reads what they render. Only a date or a number counts from rendered text, to keep it whole.\n */\nexport function kindOfRenderedText(texts: string[]): ColumnKind | undefined {\n const kind = detectKind(texts.map((t) => t.trim()).filter(Boolean));\n return kind === \"date\" || kind === \"number\" ? kind : undefined;\n}\n\n/**\n * Sample data rows and infer a ColumnKind for each key.\n * Returns a Map of key -> kind for columns that matched a pattern.\n * Columns with mixed types or JSX values get no entry.\n */\nexport function inferColumnKinds(\n data: Record<string, unknown>[],\n columnKeys: string[],\n maxSample = 50,\n): Map<string, ColumnKind> {\n const sample = data.length > maxSample ? data.slice(0, maxSample) : data;\n const result = new Map<string, ColumnKind>();\n\n for (const key of columnKeys) {\n const values = sample.map((row) => row[key]);\n const kind = detectKind(values);\n if (kind) result.set(key, kind);\n }\n\n return result;\n}\n","/**\n * The value cell of a Table or DataGrid in card mode (Sprint 25.4 C-2, C-4).\n *\n * A card row is `label | value`, the value right-aligned. Three things a value must do on a phone:\n * - wrap a long single string at sensible points first (after @, ., /, -) and mid-word only as a last resort\n * (`overflow-wrap: break-word` breaks inside a word only when the word alone cannot fit a line);\n * - clamp after two lines with the full text in a tooltip;\n * - reach the builder's own markup: a nested flex row (icon + name) keeps `min-width: auto` and overflows unless\n * every descendant is told `min-width: 0` and given the same break rule.\n *\n * A cell that holds an Avatar stacks: the avatar above, the primary text, then the secondary, aligned to the value\n * column. The rule is CSS (`:has([data-avatar])`) on the value cell's direct child, so a hand-rolled\n * `<div class=\"flex\"><Avatar/><div>…</div></div>` stacks the same way EntityCell does.\n */\nimport { Fragment, type ReactNode } from \"react\";\nimport { cn } from \"./utils\";\nimport { Tooltip } from \"../components/Tooltip\";\nimport { useTextOverflow } from \"./use-text-overflow\";\n\nexport const CARD_VALUE_CLASS = cn(\n \"min-w-0 text-right text-[var(--ds-text-primary)]\",\n \"[overflow-wrap:break-word] [&_*]:min-w-0 [&_*]:[overflow-wrap:break-word]\",\n // A hand-rolled avatar cell (div > Avatar + text) keeps its row and sits against the value column's right edge,\n // like EntityCell (round 7: the stacked cell was replaced).\n \"[&:has([data-avatar])>*]:flex [&:has([data-avatar])>*]:items-center [&:has([data-avatar])>*]:justify-end [&:has([data-avatar])>*]:text-right\",\n);\n\nconst BREAK_AFTER = /([@./-])/g;\n\n/** The string with a soft break opportunity after each @, ., / and -, so a long email or path wraps there first. */\nexport function breakable(text: string): ReactNode {\n const parts = text.split(BREAK_AFTER);\n if (parts.length === 1) return text;\n return parts.map((part, i) => (\n <Fragment key={i}>{part}{i % 2 === 1 && <wbr />}</Fragment>\n ));\n}\n\n/** A string value: sensible breaks, two lines, the full text as a tooltip once it clamps. */\nexport function CardStringValue({ text }: { text: string }) {\n const { ref, isTruncated } = useTextOverflow({ lines: 2, content: text });\n const el = <span ref={ref} className=\"block line-clamp-2\">{breakable(text)}</span>;\n return isTruncated ? <Tooltip content={text}>{el}</Tooltip> : el;\n}\n\nexport function CardValue({ value, id }: { value: ReactNode; id?: string }) {\n return (\n <span id={id} className={CARD_VALUE_CLASS}>\n {typeof value === \"string\" ? <CardStringValue text={value} /> : value}\n </span>\n );\n}\n","import { useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Sprint 25.13 A35: below this width of its own, about a phone's, a table that doesn't fit shows cards; it goes back to\n * a table once it has the room the table needed, or this much more, so a width near the line can't flip it back and\n * forth.\n */\nexport const CARDS_BELOW = 440;\nexport const CARDS_HYSTERESIS = 40;\n\n/**\n * Whether a table (Table, DataGrid) shows cards (Sprint 25.13 A35): on a narrow screen, as before, and anywhere else\n * only when its own width is under `cardsBelow` and the table doesn't fit it — measured on the element `rootRef` holds,\n * never against the screen's breakpoint, so a table in a pane, a drawer or a dialog stays a table and scrolls sideways,\n * and a table that fits stays a table however narrow its box. 0 never switches by width. The root is whichever element\n * renders, so the measure follows the mode and `remeasure` (a titled table's root differs). A width of 0 (not laid out\n * yet, or no layout at all) decides nothing.\n *\n * The review of 30 September: the switch measured the root in both modes, and in a box that sizes to its content (a\n * flex item, an inline-block, a grid item placed at its start) the table's width said cards and the cards' width said\n * table, so it flipped on every commit until React stopped the page. It now decides each way from what the other mode\n * can't change: a table goes to cards when it overflows its frame (a box sized to its content never makes it overflow),\n * and cards go back once the root has the width the table needed then, which the cards can't make it overflow again.\n */\nexport function useCardsSwitch(narrowScreen: boolean, cardsBelow: number = CARDS_BELOW, remeasure?: unknown) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [narrowBox, setNarrowBox] = useState(false);\n // the root's width at which the table fits its frame, read when it went to cards\n const needed = useRef(0);\n const cards = narrowScreen || narrowBox;\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || cardsBelow <= 0) { setNarrowBox(false); return; }\n const measure = () => {\n const width = root.getBoundingClientRect().width;\n if (width === 0) return;\n if (narrowBox) {\n if (width >= Math.min(needed.current, cardsBelow + CARDS_HYSTERESIS)) setNarrowBox(false);\n return;\n }\n // cards shown for a narrow screen measure nothing: the table isn't drawn to read\n if (cards) return;\n const frame = root.querySelector<HTMLElement>(\"[data-table-scroll]\") ?? root;\n const over = frame.scrollWidth - frame.clientWidth;\n if (width < cardsBelow && over > 1) {\n needed.current = width + over;\n setNarrowBox(true);\n }\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(root);\n // the table's own width too: rows arriving can make it overflow a frame that keeps its size\n const table = root.querySelector(\"[data-table-scroll] > *\");\n if (table) observer.observe(table);\n return () => observer.disconnect();\n }, [cardsBelow, cards, narrowBox, remeasure]);\n return { cards, rootRef };\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/lib/table-utils.ts","../../../showcase/src/lib/card-cell.tsx","../../../showcase/src/lib/use-cards-switch.ts"],"names":[],"mappings":"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","file":"chunk-OTCEJN64.js","sourcesContent":["/** Shared column-kind inference for Table and DataGrid. */\n\n/**\n * What a column holds, which decides how its cells lay out: numbers and dates shrink to their content and stay whole,\n * an identifier (a long run of digits: a barcode, an ID) may wrap anywhere, an email truncates, text wraps at spaces.\n */\nexport type ColumnKind = \"date\" | \"number\" | \"email\" | \"label\" | \"text\" | \"identifier\";\n\nconst DATE_ISO = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst DATE_US = /^\\d{1,2}\\/\\d{1,2}\\/\\d{4}$/;\n// a month's name or its short form, with or without a full stop: \"Jan\", \"Sept\", \"Sep.\", \"September\" (Sprint 25.13 A35)\nconst MONTH = \"[A-Z][a-z]{2,8}\\\\.?\";\nconst DATE_TEXT_MDY = new RegExp(`^${MONTH}\\\\s+\\\\d{1,2},?\\\\s+\\\\d{4}$`); // \"Jan 12, 2024\" or \"Sept 12 2024\"\nconst DATE_TEXT_DMY = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH}\\\\s+\\\\d{4}$`); // \"12 Jan 2024\", \"24 Sept 2026\"\n// a date this year, with no year: \"Sep 16\", \"16 Sept\" (the review of 30 September). Only a month's own name, so \"Room 12\"\n// isn't a date.\nconst MONTH_NAME = \"(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\\\.?\";\nconst DATE_TEXT_MD = new RegExp(`^${MONTH_NAME}\\\\s+\\\\d{1,2}$`);\nconst DATE_TEXT_DM = new RegExp(`^\\\\d{1,2}\\\\s+${MONTH_NAME}$`);\n// a number, with a currency before or a unit after it: \"$1,200\", \"12%\", \"18 KB\", \"1.2 MB\", \"250 ms\" (Sprint 25.13 A35)\nconst NUMBER_RE = /^[$€£¥]?\\s?[\\d,]+\\.?\\d*\\s?(?:%|[KMGTP]i?B|B|bytes?|ms|s|sec|min|h|hrs?|kg|g|km|m|°[CF])?$/i;\n// a long run of digits is an identifier, never a quantity: a barcode or an account number (Sprint 25.13 A35)\nconst IDENTIFIER_RE = /^\\d{13,}$/;\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nfunction isDate(v: string): boolean {\n return DATE_ISO.test(v) || DATE_US.test(v) || DATE_TEXT_MDY.test(v) || DATE_TEXT_DMY.test(v) || DATE_TEXT_MD.test(v) || DATE_TEXT_DM.test(v);\n}\n\nfunction isJsx(v: unknown): boolean {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction detectKind(values: unknown[]): ColumnKind | undefined {\n // Filter out null/undefined\n const present = values.filter((v) => v != null);\n if (present.length === 0) return undefined;\n\n // Any JSX value means no kind\n if (present.some(isJsx)) return undefined;\n\n // Check date (strings only)\n if (present.every((v) => typeof v === \"string\" && isDate(v))) return \"date\";\n\n // A long run of digits, as text or as a number too large to be one: an identifier\n if (present.every((v) => (typeof v === \"string\" && IDENTIFIER_RE.test(v)) || (typeof v === \"number\" && Math.abs(v) >= 1e13 && Number.isInteger(v)))) return \"identifier\";\n\n // Check number: all typeof number, or all strings matching the number pattern\n if (present.every((v) => typeof v === \"number\")) return \"number\";\n if (present.every((v) => typeof v === \"string\" && NUMBER_RE.test(v))) return \"number\";\n\n // Check email\n if (present.every((v) => typeof v === \"string\" && EMAIL_RE.test(v))) return \"email\";\n\n // Check label: strings <= 20 chars, no line breaks\n if (present.every((v) => typeof v === \"string\" && v.length <= 20 && !/[\\n\\r]/.test(v))) return \"label\";\n\n // Fallback\n return \"text\";\n}\n\n/**\n * The kind of a column from the words its cells show (Sprint 25.13 A35): a column of elements has no kind from its\n * values, so the Table reads what they render. Only a date or a number counts from rendered text, to keep it whole.\n */\nexport function kindOfRenderedText(texts: string[]): ColumnKind | undefined {\n const kind = detectKind(texts.map((t) => t.trim()).filter(Boolean));\n return kind === \"date\" || kind === \"number\" ? kind : undefined;\n}\n\n/**\n * Sample data rows and infer a ColumnKind for each key.\n * Returns a Map of key -> kind for columns that matched a pattern.\n * Columns with mixed types or JSX values get no entry.\n */\nexport function inferColumnKinds(\n data: Record<string, unknown>[],\n columnKeys: string[],\n maxSample = 50,\n): Map<string, ColumnKind> {\n const sample = data.length > maxSample ? data.slice(0, maxSample) : data;\n const result = new Map<string, ColumnKind>();\n\n for (const key of columnKeys) {\n const values = sample.map((row) => row[key]);\n const kind = detectKind(values);\n if (kind) result.set(key, kind);\n }\n\n return result;\n}\n","/**\n * The value cell of a Table or DataGrid in card mode (Sprint 25.4 C-2, C-4).\n *\n * A card row is `label | value`, the value right-aligned. Three things a value must do on a phone:\n * - wrap a long single string at sensible points first (after @, ., /, -) and mid-word only as a last resort\n * (`overflow-wrap: break-word` breaks inside a word only when the word alone cannot fit a line);\n * - clamp after two lines with the full text in a tooltip;\n * - reach the builder's own markup: a nested flex row (icon + name) keeps `min-width: auto` and overflows unless\n * every descendant is told `min-width: 0` and given the same break rule.\n *\n * A cell that holds an Avatar stacks: the avatar above, the primary text, then the secondary, aligned to the value\n * column. The rule is CSS (`:has([data-avatar])`) on the value cell's direct child, so a hand-rolled\n * `<div class=\"flex\"><Avatar/><div>…</div></div>` stacks the same way EntityCell does.\n */\nimport { Fragment, type ReactNode } from \"react\";\nimport { cn } from \"./utils\";\nimport { Tooltip } from \"../components/Tooltip\";\nimport { useTextOverflow } from \"./use-text-overflow\";\n\nexport const CARD_VALUE_CLASS = cn(\n \"min-w-0 text-right text-[var(--ds-text-primary)]\",\n \"[overflow-wrap:break-word] [&_*]:min-w-0 [&_*]:[overflow-wrap:break-word]\",\n // A hand-rolled avatar cell (div > Avatar + text) keeps its row and sits against the value column's right edge,\n // like EntityCell (round 7: the stacked cell was replaced).\n \"[&:has([data-avatar])>*]:flex [&:has([data-avatar])>*]:items-center [&:has([data-avatar])>*]:justify-end [&:has([data-avatar])>*]:text-right\",\n);\n\nconst BREAK_AFTER = /([@./-])/g;\n\n/** The string with a soft break opportunity after each @, ., / and -, so a long email or path wraps there first. */\nexport function breakable(text: string): ReactNode {\n const parts = text.split(BREAK_AFTER);\n if (parts.length === 1) return text;\n return parts.map((part, i) => (\n <Fragment key={i}>{part}{i % 2 === 1 && <wbr />}</Fragment>\n ));\n}\n\n/** A string value: sensible breaks, two lines, the full text as a tooltip once it clamps. */\nexport function CardStringValue({ text }: { text: string }) {\n const { ref, isTruncated } = useTextOverflow({ lines: 2, content: text });\n const el = <span ref={ref} className=\"block line-clamp-2\">{breakable(text)}</span>;\n return isTruncated ? <Tooltip content={text}>{el}</Tooltip> : el;\n}\n\nexport function CardValue({ value, id }: { value: ReactNode; id?: string }) {\n return (\n <span id={id} className={CARD_VALUE_CLASS}>\n {typeof value === \"string\" ? <CardStringValue text={value} /> : value}\n </span>\n );\n}\n","import { useLayoutEffect, useRef, useState } from \"react\";\n\n/**\n * Sprint 25.13 A35: below this width of its own, about a phone's, a table that doesn't fit shows cards; it goes back to\n * a table once it has the room the table needed, or this much more, so a width near the line can't flip it back and\n * forth.\n */\nexport const CARDS_BELOW = 440;\nexport const CARDS_HYSTERESIS = 40;\n\n/**\n * Whether a table (Table, DataGrid) shows cards (Sprint 25.13 A35): on a narrow screen, as before, and anywhere else\n * only when its own width is under `cardsBelow` and the table doesn't fit it — measured on the element `rootRef` holds,\n * never against the screen's breakpoint, so a table in a pane, a drawer or a dialog stays a table and scrolls sideways,\n * and a table that fits stays a table however narrow its box. 0 never switches by width. The root is whichever element\n * renders, so the measure follows the mode and `remeasure` (a titled table's root differs). A width of 0 (not laid out\n * yet, or no layout at all) decides nothing.\n *\n * The review of 30 September: the switch measured the root in both modes, and in a box that sizes to its content (a\n * flex item, an inline-block, a grid item placed at its start) the table's width said cards and the cards' width said\n * table, so it flipped on every commit until React stopped the page. It now decides each way from what the other mode\n * can't change: a table goes to cards when it overflows its frame (a box sized to its content never makes it overflow),\n * and cards go back once the root has the width the table needed then, which the cards can't make it overflow again.\n */\nexport function useCardsSwitch(narrowScreen: boolean, cardsBelow: number = CARDS_BELOW, remeasure?: unknown) {\n const rootRef = useRef<HTMLElement | null>(null);\n const [narrowBox, setNarrowBox] = useState(false);\n // the root's width at which the table fits its frame, read when it went to cards\n const needed = useRef(0);\n const cards = narrowScreen || narrowBox;\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || cardsBelow <= 0) { setNarrowBox(false); return; }\n const measure = () => {\n const width = root.getBoundingClientRect().width;\n if (width === 0) return;\n if (narrowBox) {\n if (width >= Math.min(needed.current, cardsBelow + CARDS_HYSTERESIS)) setNarrowBox(false);\n return;\n }\n // cards shown for a narrow screen measure nothing: the table isn't drawn to read\n if (cards) return;\n const frame = root.querySelector<HTMLElement>(\"[data-table-scroll]\") ?? root;\n const over = frame.scrollWidth - frame.clientWidth;\n if (width < cardsBelow && over > 1) {\n needed.current = width + over;\n setNarrowBox(true);\n }\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(root);\n // the table's own width too: rows arriving can make it overflow a frame that keeps its size\n const table = root.querySelector(\"[data-table-scroll] > *\");\n if (table) observer.observe(table);\n return () => observer.disconnect();\n }, [cardsBelow, cards, narrowBox, remeasure]);\n return { cards, rootRef };\n}\n"]}
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ // ../../showcase/src/lib/element-type.ts
3
+ var LAZY = /* @__PURE__ */ Symbol.for("react.lazy");
4
+ var isLazy = (type) => type !== null && typeof type === "object" && type.$$typeof === LAZY && typeof type._init === "function";
5
+ function elementType(type) {
6
+ let resolved = type;
7
+ for (let step = 0; step < 8 && isLazy(resolved); step++) resolved = resolved._init(resolved._payload);
8
+ return resolved;
9
+ }
10
+
11
+ // ../../showcase/src/lib/format-locale.ts
12
+ var last = null;
13
+ var FIXED_FORM = "en";
14
+ function formatLocale() {
15
+ if (typeof document === "undefined") return FIXED_FORM;
16
+ const page = document.documentElement.lang?.trim();
17
+ if (!page) return void 0;
18
+ return localeFor(page, browserLanguages());
19
+ }
20
+ function browserLanguages() {
21
+ if (typeof navigator === "undefined") return [];
22
+ return navigator.languages?.length ? navigator.languages : navigator.language ? [navigator.language] : [];
23
+ }
24
+ function localeFor(page, spoken) {
25
+ const key = `${page}|${spoken.join(",")}`;
26
+ if (last?.key !== key) last = { key, locale: choose(page, spoken) };
27
+ return last.locale;
28
+ }
29
+ function localeChoice(lang, acceptLanguage) {
30
+ const page = lang?.trim() || FIXED_FORM;
31
+ const spoken = acceptedLanguages(acceptLanguage);
32
+ return { lang: page, spoken, hasHeader: !!acceptLanguage, server: localeFor(page, spoken) ?? FIXED_FORM };
33
+ }
34
+ var MAX_LANGUAGES = 20;
35
+ function acceptedLanguages(header) {
36
+ if (!header) return [];
37
+ const read = [];
38
+ header.split(",").slice(0, MAX_LANGUAGES).forEach((part, at) => {
39
+ const [tag, ...params] = part.trim().split(";");
40
+ const name = tag?.trim();
41
+ if (!name || name === "*") return;
42
+ const qParam = params.map((p) => p.trim()).find((p) => /^q=/i.test(p));
43
+ const q = qParam ? Number(qParam.slice(2)) : 1;
44
+ if (!(q > 0)) return;
45
+ read.push({ tag: name, q, at });
46
+ });
47
+ return read.sort((a, b) => b.q - a.q || a.at - b.at).map((r) => r.tag);
48
+ }
49
+ function writtenIn(tag) {
50
+ try {
51
+ const locale = new Intl.Locale(tag).maximize();
52
+ return `${locale.language}-${locale.script ?? ""}`;
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ function plainTag(tag) {
58
+ try {
59
+ const locale = new Intl.Locale(tag);
60
+ return [locale.language, locale.script, locale.region].filter(Boolean).join("-");
61
+ } catch {
62
+ return void 0;
63
+ }
64
+ }
65
+ function choose(page, spoken) {
66
+ const language = writtenIn(page);
67
+ if (language === null) return void 0;
68
+ const own = spoken.find((tag) => typeof tag === "string" && writtenIn(tag) === language);
69
+ try {
70
+ return Intl.getCanonicalLocales(own && plainTag(own) || page)[0];
71
+ } catch {
72
+ return void 0;
73
+ }
74
+ }
75
+
76
+ export { FIXED_FORM, browserLanguages, elementType, formatLocale, localeChoice, localeFor };
77
+ //# sourceMappingURL=chunk-OYNUU346.js.map
78
+ //# sourceMappingURL=chunk-OYNUU346.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/lib/element-type.ts","../../../showcase/src/lib/format-locale.ts"],"names":[],"mappings":"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","file":"chunk-OYNUU346.js","sourcesContent":["/**\n * An element's own component, through a lazy reference (Sprint 25.22's fresh-eyes review). A Next.js server component\n * hands each client component among its children on as a React.lazy reference, not as the component, so a check of\n * `element.type` against a component (PageContainer's header, Card's title, EmptyState's icon) missed every one, and a\n * server page drew without its layout: no colourful band, no contained breadcrumbs, a card's badge out of place. The\n * reference is resolved as React resolves one when it renders it: its component once loaded; while it loads, its promise\n * is thrown, so the component reading it suspends until it has it. Anything else is returned as it is.\n */\nconst LAZY = Symbol.for(\"react.lazy\");\n\ninterface LazyReference {\n $$typeof: symbol;\n _payload: unknown;\n _init: (payload: unknown) => unknown;\n}\n\nconst isLazy = (type: unknown): type is LazyReference =>\n type !== null && typeof type === \"object\" && (type as LazyReference).$$typeof === LAZY && typeof (type as LazyReference)._init === \"function\";\n\nexport function elementType(type: unknown): unknown {\n let resolved = type;\n // a reference to a reference resolves in turn; a handful of steps covers any real chain\n for (let step = 0; step < 8 && isLazy(resolved); step++) resolved = resolved._init(resolved._payload);\n return resolved;\n}\n","/** The last page language and visitor's languages read, and the locale they gave: formatting runs often, they change rarely. */\nlet last: { key: string; locale: string | undefined } | null = null;\n\n/**\n * The form a server-rendered page is written in without LocaleProvider, and what the library writes on a server where no\n * locale is given: an explicit locale, the same on every machine and on both sides of hydration (Sprint 25.20 D2).\n */\nexport const FIXED_FORM = \"en\";\n\n/**\n * The locale the library writes dates, times and numbers in: the app's language, the page's `lang`, in the visitor's own\n * formats where they speak it (Sprint 25.17, the owner's decision of 2 October). An English page shows \"5 minutes ago\"\n * and \"250K\" to a visitor whose browser is Japanese, where it showed \"5 分前\" and \"25万\" in a page of English (WCAG 3.1.2:\n * text in the page's language needs no `lang` of its own), and a British visitor to it keeps \"1 Oct 2026\". The visitor's\n * form counts only in the page's script: a page in traditional Chinese stays traditional for a visitor who reads\n * simplified. A page with no `lang` is written in the browser's language, as before. On a server, where there is no page,\n * English (\"en\"), never the machine's own language (Sprint 25.22's review): server code passes requestLocale's choice.\n */\nexport function formatLocale(): string | undefined {\n if (typeof document === \"undefined\") return FIXED_FORM;\n const page = document.documentElement.lang?.trim();\n if (!page) return undefined;\n return localeFor(page, browserLanguages());\n}\n\n/** The browser's languages, in its order (`navigator.languages`), or none where there is no browser. */\nexport function browserLanguages(): readonly string[] {\n if (typeof navigator === \"undefined\") return [];\n return navigator.languages?.length ? navigator.languages : navigator.language ? [navigator.language] : [];\n}\n\n/**\n * The locale for a page language and the languages a visitor speaks, in their order: the visitor's form of the page's\n * language where they speak it, else the page's own (Sprint 25.20: the same choice on a server, from the request's\n * languages, and in the browser). Undefined for a page language Intl can't read.\n */\nexport function localeFor(page: string, spoken: readonly string[]): string | undefined {\n const key = `${page}|${spoken.join(\",\")}`;\n if (last?.key !== key) last = { key, locale: choose(page, spoken) };\n return last.locale;\n}\n\n/** What a server-rendered app hands the library at its top: the page's language, and the visitor's from the request. */\nexport interface LocaleChoice {\n /** The page's language, as `<html lang>` says, or \"en\". */\n lang: string;\n /** The request's languages, in a browser's order; empty without the header. */\n spoken: string[];\n /** Whether the request's header was given. */\n hasHeader: boolean;\n /** The locale the server writes in, and the browser while it hydrates. */\n server: string;\n}\n\n/** What LocaleProvider hands down for a page language and a request's Accept-Language header. */\nexport function localeChoice(lang: string | undefined, acceptLanguage: string | null | undefined): LocaleChoice {\n const page = lang?.trim() || FIXED_FORM;\n const spoken = acceptedLanguages(acceptLanguage);\n return { lang: page, spoken, hasHeader: !!acceptLanguage, server: localeFor(page, spoken) ?? FIXED_FORM };\n}\n\n/**\n * The locale LocaleProvider writes in on the server for a page language and a request's Accept-Language header (Sprint\n * 25.22's review), for server code's own dates and numbers: a server page passes it to the helpers from\n * `gridsmith-ui/utils`, so its words read as the components' beside them. `requestLocale(LANG, (await\n * headers()).get('accept-language'))`.\n */\nexport function requestLocale(lang: string | undefined, acceptLanguage?: string | null): string {\n return localeChoice(lang, acceptLanguage).server;\n}\n\n/** The most of a request's languages read: browsers send a handful. */\nconst MAX_LANGUAGES = 20;\n\n/**\n * A request's `Accept-Language` header as the languages a browser lists (Sprint 25.20 L1): highest quality first, ties in\n * the header's order, \"*\" and anything with q=0 left out. \"en-GB,en;q=0.9,fr;q=0.8\" is [\"en-GB\", \"en\", \"fr\"].\n */\nexport function acceptedLanguages(header: string | null | undefined): string[] {\n if (!header) return [];\n const read: Array<{ tag: string; q: number; at: number }> = [];\n // a browser lists a handful; a header made up to be long reads no further than the first twenty (the review)\n header.split(\",\").slice(0, MAX_LANGUAGES).forEach((part, at) => {\n const [tag, ...params] = part.trim().split(\";\");\n const name = tag?.trim();\n if (!name || name === \"*\") return;\n const qParam = params.map((p) => p.trim()).find((p) => /^q=/i.test(p));\n const q = qParam ? Number(qParam.slice(2)) : 1;\n if (!(q > 0)) return;\n read.push({ tag: name, q, at });\n });\n return read.sort((a, b) => b.q - a.q || a.at - b.at).map((r) => r.tag);\n}\n\n/** The language and script a tag is written in: \"en\" and \"en-GB\" are both \"en-Latn\"; \"zh-TW\" is \"zh-Hant\", \"zh-CN\" \"zh-Hans\". */\nfunction writtenIn(tag: string): string | null {\n try {\n const locale = new Intl.Locale(tag).maximize();\n return `${locale.language}-${locale.script ?? \"\"}`;\n } catch {\n return null;\n }\n}\n\n/**\n * A visitor's tag as its language, script and region (Sprint 25.20's review): a request's header can carry private-use,\n * extension and variant parts (\"en-x-…\", \"en-GB-u-nu-thai\") that would change the digits a server writes and make every\n * cache keyed by locale grow with each new header. Read by syntax alone, so a server and a browser reduce a tag alike.\n */\nfunction plainTag(tag: string): string | undefined {\n try {\n const locale = new Intl.Locale(tag);\n return [locale.language, locale.script, locale.region].filter(Boolean).join(\"-\");\n } catch {\n return undefined;\n }\n}\n\nfunction choose(page: string, spoken: readonly string[]): string | undefined {\n const language = writtenIn(page);\n // a tag Intl can't read: the browser's language\n if (language === null) return undefined;\n // the visitor's own form of the page's language (\"en-GB\" for an \"en\" page), wherever it is in their list; else the page's\n const own = spoken.find((tag) => typeof tag === \"string\" && writtenIn(tag) === language);\n try {\n return Intl.getCanonicalLocales((own && plainTag(own)) || page)[0];\n } catch {\n return undefined;\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
+ "use client";
1
2
  import { useTextOverflow, Tooltip } from './chunk-MD2EFMES.js';
2
- import { Icon } from './chunk-HYMXAIXN.js';
3
+ import { Icon } from './chunk-TJEV7YAY.js';
3
4
  import { cn } from './chunk-M2QRNBDQ.js';
4
5
  import { jsxs, jsx } from 'react/jsx-runtime';
5
6
 
@@ -98,5 +99,5 @@ function Badge({ variant = "default", severity, size = "md", color, iconLeft, ic
98
99
  }
99
100
 
100
101
  export { Badge };
101
- //# sourceMappingURL=chunk-FHMCAOAH.js.map
102
- //# sourceMappingURL=chunk-FHMCAOAH.js.map
102
+ //# sourceMappingURL=chunk-PO65I3MQ.js.map
103
+ //# sourceMappingURL=chunk-PO65I3MQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../showcase/src/components/Badge.tsx"],"names":[],"mappings":";;;;;;AAsBA,IAAM,iBAAA,GAAyD;AAAA,EAC7D,IAAA,EAAM,SAAA;AAAA,EACN,GAAA,EAAK,cAAA;AAAA,EACL,MAAA,EAAQ,cAAA;AAAA,EACR,IAAA,EAAM,YAAA;AAAA,EACN,QAAA,EAAU;AACZ,CAAA;AAEA,IAAM,aAAA,GAA8C;AAAA,EAClD,OAAA,EAAS,sGAAA;AAAA,EACT,OAAA,EAAS,kEAAA;AAAA,EACT,MAAA,EAAQ,yDAAA;AAAA,EACR,OAAA,EAAS,gEAAA;AAAA,EACT,cAAA,EAAgB,iJAAA;AAAA,EAChB,OAAA,EAAS,gEAAA;AAAA,EACT,cAAA,EAAgB,sJAAA;AAAA,EAChB,KAAA,EAAO,4DAAA;AAAA,EACP,YAAA,EAAc,2IAAA;AAAA,EACd,OAAA,EAAS;AACX,CAAA;AAEA,IAAM,UAAA,GAAwC;AAAA,EAC5C,EAAA,EAAI,iDAAA;AAAA,EACJ,EAAA,EAAI,iEAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEA,IAAM,SAAA,GAAuC;AAAA,EAC3C,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI,EAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAIA,IAAM,gBAAA,GAAmD;AAAA,EACvD,OAAA,EAAS,CAAC,CAAA,KAAM,cAAA,CAAe,CAAC,CAAA;AAAA,EAChC,OAAA,EAAS,CAAC,CAAA,KAAM,aAAA,CAAc,CAAC,CAAA;AAAA,EAC/B,MAAA,EAAQ,CAAC,CAAA,KAAM,aAAA,CAAc,CAAC,CAAA;AAAA,EAC9B,OAAA,EAAS,CAAC,CAAA,KAAM,cAAA,CAAe,CAAC;AAClC,CAAA;AAEA,IAAM,cAAA,GAAoD;AAAA,EACxD,CAAA,EAAG,kEAAA;AAAA,EACH,CAAA,EAAG,kEAAA;AAAA,EACH,CAAA,EAAG,kEAAA;AAAA,EACH,CAAA,EAAG,kEAAA;AAAA,EACH,CAAA,EAAG;AACL,CAAA;AAEA,IAAM,aAAA,GAAmD;AAAA,EACvD,CAAA,EAAG,6DAAA;AAAA,EACH,CAAA,EAAG,6DAAA;AAAA,EACH,CAAA,EAAG,6DAAA;AAAA,EACH,CAAA,EAAG,6DAAA;AAAA,EACH,CAAA,EAAG;AACL,CAAA;AAEA,IAAM,cAAA,GAAoD;AAAA,EACxD,CAAA,EAAG,yHAAA;AAAA,EACH,CAAA,EAAG,yHAAA;AAAA,EACH,CAAA,EAAG,yHAAA;AAAA,EACH,CAAA,EAAG,yHAAA;AAAA,EACH,CAAA,EAAG;AACL,CAAA;AAEA,SAAS,iBAAA,CAAkB,OAA0B,OAAA,EAA+B;AAClF,EAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,OAAO,CAAA,IAAK,gBAAA,CAAiB,OAAA;AAC9D,EAAA,OAAO,QAAQ,KAAK,CAAA;AACtB;AAmBO,SAAS,KAAA,CAAM,EAAE,OAAA,GAAU,SAAA,EAAW,QAAA,EAAU,IAAA,GAAO,IAAA,EAAM,KAAA,EAAO,QAAA,EAAU,SAAA,EAAW,QAAA,EAAU,SAAA,EAAW,UAAS,EAAe;AAE3I,EAAA,MAAM,eAAA,GAAkB,QAAA,GAAW,iBAAA,CAAkB,QAAQ,CAAA,GAAI,OAAA;AACjE,EAAA,MAAM,EAAE,GAAA,EAAK,QAAA,EAAU,WAAA,EAAY,GAAI,eAAA,CAAgB,EAAE,KAAA,EAAO,CAAA,EAAG,QAAA,EAAU,CAAC,QAAA,EAAU,CAAA;AACxF,EAAA,MAAM,QAAA,GAAW,UAAU,IAAI,CAAA;AAC/B,EAAA,MAAM,UAAA,GAAA,CAAc,QAAA,IAAY,SAAA,KAAc,CAAC,QAAA;AAE/C,EAAA,MAAM,eAAe,KAAA,GACjB,iBAAA,CAAkB,OAAO,eAAe,CAAA,GACxC,cAAc,eAAe,CAAA;AAEjC,EAAA,MAAM,KAAA,mBACJ,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MAGC,eAAA,EAAc,EAAA;AAAA,MACd,SAAA,EAAW,EAAA;AAAA,QACT,4EAAA;AAAA,QACA,iCAAA;AAAA,QACA,WAAW,IAAI,CAAA;AAAA,QACf,UAAA,IAAc,SAAS,IAAA,IAAQ,UAAA;AAAA,QAC/B,UAAA,IAAc,SAAS,IAAA,IAAQ,SAAA;AAAA,QAC/B,UAAA,IAAc,SAAS,IAAA,IAAQ,WAAA;AAAA,QAC/B,YAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,KAAA,EAAO,QAAA,GAAW,EAAE,QAAA,EAAS,GAAI,MAAA;AAAA,MAEhC,QAAA,EAAA;AAAA,QAAA,QAAA,oBAAY,GAAA,CAAC,QAAK,IAAA,EAAM,QAAA,EAAU,MAAM,QAAA,EAAU,SAAA,EAAU,UAAA,EAAW,IAAA,EAAK,kBAAA,EAAmB,CAAA;AAAA,QAC/F,QAAA,uBACE,MAAA,EAAA,EAAK,GAAA,EAAK,UAAU,SAAA,EAAU,UAAA,EAAY,UAAS,CAAA,GAEpD,QAAA;AAAA,QAED,SAAA,oBAAa,GAAA,CAAC,IAAA,EAAA,EAAK,IAAA,EAAM,SAAA,EAAW,MAAM,QAAA,EAAU,SAAA,EAAU,UAAA,EAAW,IAAA,EAAK,mBAAA,EAAoB;AAAA;AAAA;AAAA,GACrG;AAGF,EAAA,IAAI,WAAA,EAAa;AACf,IAAA,uBAAO,GAAA,CAAC,OAAA,EAAA,EAAQ,OAAA,EAAS,QAAA,EAAW,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,EAC5C;AACA,EAAA,OAAO,KAAA;AACT","file":"chunk-PO65I3MQ.js","sourcesContent":["import { cn } from \"../lib/utils\";\nimport { Icon, type IconName } from \"../lib/icons\";\nimport { Tooltip } from \"./Tooltip\";\nimport { useTextOverflow } from \"../lib/use-text-overflow\";\nimport type { ReactNode } from \"react\";\n\nexport type BadgeVariant =\n | \"default\"\n | \"neutral\"\n | \"subtle\"\n | \"success\"\n | \"success-soft\"\n | \"warning\"\n | \"warning-soft\"\n | \"error\"\n | \"error-soft\"\n | \"outline\";\n\nexport type BadgeSize = \"xs\" | \"sm\" | \"md\";\nexport type BadgePaletteColor = 1 | 2 | 3 | 4 | 5;\nexport type BadgeSeverity = \"info\" | \"low\" | \"medium\" | \"high\" | \"critical\";\n\nconst severityToVariant: Record<BadgeSeverity, BadgeVariant> = {\n info: \"neutral\",\n low: \"success-soft\",\n medium: \"warning-soft\",\n high: \"error-soft\",\n critical: \"error\",\n};\n\nconst variantStyles: Record<BadgeVariant, string> = {\n default: \"bg-[var(--ds-accent-primary)] [background-image:var(--ds-gradient)] text-[var(--ds-text-on-primary)]\",\n neutral: \"bg-[var(--ds-text-muted)]/[0.12] text-[var(--ds-text-secondary)]\",\n subtle: \"bg-[var(--ds-bg-muted)] text-[var(--ds-text-secondary)]\",\n success: \"bg-[var(--ds-status-success)] text-[var(--ds-text-on-success)]\",\n \"success-soft\": \"bg-[var(--ds-status-success-bg)] text-[var(--ds-status-success)] border-[length:var(--ds-border-width)] border-[var(--ds-status-success)]/[0.2]\",\n warning: \"bg-[var(--ds-status-warning)] text-[var(--ds-text-on-warning)]\",\n \"warning-soft\": \"bg-[var(--ds-status-warning-bg)] text-[var(--ds-status-warning-text)] border-[length:var(--ds-border-width)] border-[var(--ds-status-warning)]/[0.2]\",\n error: \"bg-[var(--ds-status-error)] text-[var(--ds-text-on-error)]\",\n \"error-soft\": \"bg-[var(--ds-status-error-bg)] text-[var(--ds-status-error)] border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]/[0.2]\",\n outline: \"bg-transparent text-[var(--ds-text-primary)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]\",\n};\n\nconst sizeStyles: Record<BadgeSize, string> = {\n xs: \"px-ds-1.5 py-[1px] text-[length:10px] gap-[3px]\",\n sm: \"px-ds-2 py-ds-0.5 text-[length:var(--ds-font-size-xs)] gap-ds-1\",\n md: \"px-ds-2.5 py-ds-0.5 text-[length:var(--ds-font-size-xs)] gap-ds-1\",\n};\n\nconst iconSizes: Record<BadgeSize, number> = {\n xs: 10,\n sm: 12,\n md: 12,\n};\n\ntype PaletteStyleFn = (n: BadgePaletteColor) => string;\n\nconst paletteByVariant: Record<string, PaletteStyleFn> = {\n default: (n) => paletteDefault[n],\n neutral: (n) => paletteSubtle[n],\n subtle: (n) => paletteSubtle[n],\n outline: (n) => paletteOutline[n],\n};\n\nconst paletteDefault: Record<BadgePaletteColor, string> = {\n 1: \"bg-[var(--ds-palette-1-bold)] text-[var(--ds-palette-1-on-bold)]\",\n 2: \"bg-[var(--ds-palette-2-bold)] text-[var(--ds-palette-2-on-bold)]\",\n 3: \"bg-[var(--ds-palette-3-bold)] text-[var(--ds-palette-3-on-bold)]\",\n 4: \"bg-[var(--ds-palette-4-bold)] text-[var(--ds-palette-4-on-bold)]\",\n 5: \"bg-[var(--ds-palette-5-bold)] text-[var(--ds-palette-5-on-bold)]\",\n};\n\nconst paletteSubtle: Record<BadgePaletteColor, string> = {\n 1: \"bg-[var(--ds-palette-1-bg)] text-[var(--ds-palette-1-text)]\",\n 2: \"bg-[var(--ds-palette-2-bg)] text-[var(--ds-palette-2-text)]\",\n 3: \"bg-[var(--ds-palette-3-bg)] text-[var(--ds-palette-3-text)]\",\n 4: \"bg-[var(--ds-palette-4-bg)] text-[var(--ds-palette-4-text)]\",\n 5: \"bg-[var(--ds-palette-5-bg)] text-[var(--ds-palette-5-text)]\",\n};\n\nconst paletteOutline: Record<BadgePaletteColor, string> = {\n 1: \"bg-transparent text-[var(--ds-palette-1-bold)] border-[length:var(--ds-border-width)] border-[var(--ds-palette-1-bold)]\",\n 2: \"bg-transparent text-[var(--ds-palette-2-bold)] border-[length:var(--ds-border-width)] border-[var(--ds-palette-2-bold)]\",\n 3: \"bg-transparent text-[var(--ds-palette-3-bold)] border-[length:var(--ds-border-width)] border-[var(--ds-palette-3-bold)]\",\n 4: \"bg-transparent text-[var(--ds-palette-4-bold)] border-[length:var(--ds-border-width)] border-[var(--ds-palette-4-bold)]\",\n 5: \"bg-transparent text-[var(--ds-palette-5-bold)] border-[length:var(--ds-border-width)] border-[var(--ds-palette-5-bold)]\",\n};\n\nfunction getPaletteClasses(color: BadgePaletteColor, variant: BadgeVariant): string {\n const styleFn = paletteByVariant[variant] ?? paletteByVariant.default;\n return styleFn(color);\n}\n\ninterface BadgeProps {\n variant?: BadgeVariant;\n /** Severity shorthand — maps to a variant. When set, overrides `variant`. */\n severity?: BadgeSeverity;\n size?: BadgeSize;\n /** Palette colour (1-5). Overrides variant colour with decorative palette tokens. */\n color?: BadgePaletteColor;\n /** Icon to show before the label */\n iconLeft?: IconName;\n /** Icon to show after the label */\n iconRight?: IconName;\n /** Max width before truncation (e.g. \"120px\", \"16ch\"). Shows tooltip when truncated. */\n maxWidth?: string;\n className?: string;\n children?: ReactNode;\n}\n\nexport function Badge({ variant = \"default\", severity, size = \"md\", color, iconLeft, iconRight, maxWidth, className, children }: BadgeProps) {\n // severity sets the variant and variant is ignored when both are written\n const resolvedVariant = severity ? severityToVariant[severity] : variant;\n const { ref: truncRef, isTruncated } = useTextOverflow({ lines: 1, disabled: !maxWidth });\n const iconSize = iconSizes[size];\n const isIconOnly = (iconLeft || iconRight) && !children;\n\n const colorClasses = color\n ? getPaletteClasses(color, resolvedVariant)\n : variantStyles[resolvedVariant];\n\n const badge = (\n <span\n // Sprint 25.13 A1: a card header reads this to put a badge in its actions beside the title (D1a), and a card grid\n // to size from what its headers hold (A2)\n data-gs-badge=\"\"\n className={cn(\n \"inline-flex items-center font-medium max-w-full whitespace-nowrap shrink-0\",\n \"rounded-[var(--ds-radius-full)]\",\n sizeStyles[size],\n isIconOnly && size === \"xs\" && \"px-[3px]\",\n isIconOnly && size === \"sm\" && \"px-ds-1\",\n isIconOnly && size === \"md\" && \"px-ds-1.5\",\n colorClasses,\n className\n )}\n style={maxWidth ? { maxWidth } : undefined}\n >\n {iconLeft && <Icon name={iconLeft} size={iconSize} className=\"shrink-0\" from=\"Badge's iconLeft\" />}\n {maxWidth ? (\n <span ref={truncRef} className=\"truncate\">{children}</span>\n ) : (\n children\n )}\n {iconRight && <Icon name={iconRight} size={iconSize} className=\"shrink-0\" from=\"Badge's iconRight\" />}\n </span>\n );\n\n if (isTruncated) {\n return <Tooltip content={children}>{badge}</Tooltip>;\n }\n return badge;\n}\n"]}
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { createContext, createElement, useContext, useLayoutEffect } from 'react';
2
3
 
3
4
  // ../../showcase/src/lib/page-actions.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/page-actions.ts","../../../showcase/src/lib/surface-context.ts"],"names":["createContext"],"mappings":";;;AAwBO,IAAM,cAAA,GAAiB,cAAsC,IAAI;AClBjE,IAAM,cAAA,GAAiBA,cAAc,KAAK;AAS1C,IAAM,YAAA,GAAeA,cAA6B,KAAK;AAOvD,IAAM,YAAA,GAAeA,cAAc,KAAK;AAUxC,IAAM,OAAA,GAAUA,cAAc,KAAK;AAWnC,IAAM,kBAAA,GAAqBA,cAAsC,IAAI,CAAA;AAE5E,SAAS,gBAAgB,IAAA,EAA4B;AACnD,EAAA,MAAM,MAAA,GAAS,WAAW,kBAAkB,CAAA;AAC5C,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAA,CAAO,MAAM,IAAI,CAAA;AACjB,IAAA,OAAO,MAAM,MAAA,CAAO,IAAA,EAAM,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAI,CAAC,CAAA;AACnB;AAGO,SAAS,eAAA,GAAkB;AAChC,EAAA,eAAA,CAAgB,QAAQ,CAAA;AAC1B;AAGO,SAAS,gBAAA,GAAmB;AACjC,EAAA,eAAA,CAAgB,SAAS,CAAA;AAC3B;AASO,SAAS,gBAAgB,EAAE,QAAA,EAAU,OAAO,KAAA,EAAO,MAAA,GAAS,MAAK,EAAmF;AAGzJ,EAAA,OAAO,aAAA;AAAA,IAAc,cAAA;AAAA,IAAgB,EAAE,OAAO,KAAA,EAAM;AAAA,IAClD,aAAA;AAAA,MAAc,YAAA;AAAA,MAAc,EAAE,OAAO,IAAA,EAAK;AAAA,MACxC,aAAA;AAAA,QAAc,YAAA;AAAA,QAAc,EAAE,OAAO,KAAA,EAAM;AAAA,QACzC,aAAA;AAAA,UAAc,OAAA;AAAA,UAAS,EAAE,OAAO,KAAA,EAAM;AAAA,UACpC,aAAA;AAAA,YAAc,cAAA;AAAA,YAAgB,EAAE,OAAO,IAAA,EAAK;AAAA,YAC1C,cAAc,kBAAA,EAAoB,EAAE,KAAA,EAAO,MAAA,IAAU,QAAQ;AAAA;AAAC;AAAC;AAAC;AAAC,GAAC;AAC9E","file":"chunk-QM4SJKWV.js","sourcesContent":["import { createContext } from \"react\";\nimport type { ControlSize } from \"../components/Button\";\n\n/**\n * Sprint 25.16 A4 (R28-7): the slot in a page's own PageHeader for its actions (PageActions). PageContainer provides it,\n * its header draws it while a page asks, and the page's PageActions render into it. The review of 1 October:\n * - content that starts afresh (an overlay's body, a SplitView's pane: SurfaceBoundary) has no slot, so a header there\n * (a message's own header, a drawer's) never takes the page's actions;\n * - the page's header is the first slot in the page, so a second header that comes and goes hands nothing on;\n * - a container with no header of its own (a page that wraps itself in one under an area layout) passes its page's\n * actions to the container around it;\n * - the actions take the header's control size (a minimal or snapping header draws its own actions small).\n */\nexport interface PageActionsSlot {\n /** A page below asks for the slot: a PageActions is drawn. */\n wanted: boolean;\n /** Where the actions go: the slot of the page's header, at the size its controls are drawn. */\n target: { el: HTMLElement; size: ControlSize | null } | null;\n /** A header's slot, while drawn, with its controls' size; returns its release. */\n register: (el: HTMLElement, size: ControlSize | null) => () => void;\n /** A PageActions asks for the slot while drawn; returns its release. */\n add: () => () => void;\n}\n\nexport const PageActionsCtx = createContext<PageActionsSlot | null>(null);\n","import { createContext, createElement, useContext, useLayoutEffect, type ReactNode } from \"react\";\nimport { PageActionsCtx } from \"./page-actions\";\n\n/**\n * True inside a Card's surface. A StackedList reads it to inset its rows to the card's header (px-ds-5).\n */\nexport const CardSurfaceCtx = createContext(false);\n\n/**\n * The SplitView pane content sits in (\"list\", \"detail\", \"aside\"), false outside one. A StackedList reads it to take its\n * inset from the pane (Sprint 25.4 C-5): rows keep their padding so the selection rail sits at the pane's edge and never\n * touches content. A pane header (SectionHeading variant=\"pane\") in the detail insets at the page's gutter, as the item's\n * path above it does, and in the list at its rows' inset (the owner's review of 1 October).\n */\nexport type SplitPaneKind = false | \"list\" | \"detail\" | \"aside\";\nexport const SplitPaneCtx = createContext<SplitPaneKind>(false);\n\n/**\n * True inside a Table or DataGrid card (the phone layout below mobileBreakpoint). An EntityCell reads it to keep\n * the avatar beside the text and align to the value column (Sprint 25.4, round 7: the stacked cell made the card\n * three lines taller than it needed to be).\n */\nexport const TableCardCtx = createContext(false);\n\n/**\n * True among a collection's rows: a Table's or a DataGrid's rows and their phone cards, a StackedList's rows, a Timeline's\n * items. Something\n * with a Tab stop of its own adds a stop to every row there, which makes the collection hard to cross: a RelativeTime\n * drops its own, its full timestamp in its name and on hover (Sprint 25.17 A29). A DataList is one record's fields, not\n * rows, so it doesn't set it (the second review). Inside a control (a card or a row that is a button, a link) the time\n * finds that for itself, from the page. Overlays reset it: SurfaceBoundary, a Popover, a HoverCard.\n */\nexport const RowsCtx = createContext(false);\n\n/**\n * Sprint 25.14 A44: what a SplitView's list pane holds, as its content reports it while mounted. \"framed\": content that\n * draws its own frame or cards (a KanbanBoard, a CardGrid, a Table, a DataGrid). \"scrolls\": content with no frame of its\n * own that scrolls inside the pane (a StackedList, a Gantt, a VirtualizedList), which the view's hairline frames. The\n * view drops its frame and surface when the pane holds framed content and nothing that scrolls (P32: no frame around\n * what frames itself; a hairline around what scrolls inside). Here, not in SplitView, so a board or a card grid never\n * imports the resizable panels. Null outside a list pane: overlays and the view's other panes reset it (SurfaceBoundary).\n */\nexport type SplitListReport = (kind: \"framed\" | \"scrolls\", present: boolean) => void;\nexport const SplitListFramerCtx = createContext<SplitListReport | null>(null);\n\nfunction useReportToList(kind: \"framed\" | \"scrolls\") {\n const report = useContext(SplitListFramerCtx);\n useLayoutEffect(() => {\n if (!report) return;\n report(kind, true);\n return () => report(kind, false);\n }, [report, kind]);\n}\n\n/** Tell a SplitView whose list pane this sits in that it draws its own frame or cards (A44). */\nexport function useFramesItself() {\n useReportToList(\"framed\");\n}\n\n/** Tell a SplitView whose list pane this sits in that it scrolls inside the pane with no frame of its own (A44). */\nexport function useScrollsInside() {\n useReportToList(\"scrolls\");\n}\n\n/**\n * Where content starts afresh: an overlay's body, an opened table row's panel, a SplitView's pane. What a Card, a pane\n * or a table card told the content around it stops here (the review of 30 September: a StackedList in a Drawer opened\n * from a Card took the card's inset, and one in a Dialog declared in a list pane the pane's, so neither lined up with\n * the overlay's gutter), and so does the page's actions slot. A pane passes `pane` (which pane its content is in) and\n * the list pane its `framer`.\n */\nexport function SurfaceBoundary({ children, pane = false, framer = null }: { children: ReactNode; pane?: SplitPaneKind; framer?: SplitListReport | null }) {\n // the page's actions belong to the page's own header (Sprint 25.16's review): a header in a pane or an overlay never\n // takes them, and PageActions there renders nothing\n return createElement(CardSurfaceCtx, { value: false },\n createElement(SplitPaneCtx, { value: pane },\n createElement(TableCardCtx, { value: false },\n createElement(RowsCtx, { value: false },\n createElement(PageActionsCtx, { value: null },\n createElement(SplitListFramerCtx, { value: framer }, children))))));\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/lib/page-actions.ts","../../../showcase/src/lib/surface-context.ts"],"names":["createContext"],"mappings":";;;;AAwBO,IAAM,cAAA,GAAiB,cAAsC,IAAI;AClBjE,IAAM,cAAA,GAAiBA,cAAc,KAAK;AAS1C,IAAM,YAAA,GAAeA,cAA6B,KAAK;AAOvD,IAAM,YAAA,GAAeA,cAAc,KAAK;AAUxC,IAAM,OAAA,GAAUA,cAAc,KAAK;AAWnC,IAAM,kBAAA,GAAqBA,cAAsC,IAAI,CAAA;AAE5E,SAAS,gBAAgB,IAAA,EAA4B;AACnD,EAAA,MAAM,MAAA,GAAS,WAAW,kBAAkB,CAAA;AAC5C,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAA,CAAO,MAAM,IAAI,CAAA;AACjB,IAAA,OAAO,MAAM,MAAA,CAAO,IAAA,EAAM,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAI,CAAC,CAAA;AACnB;AAGO,SAAS,eAAA,GAAkB;AAChC,EAAA,eAAA,CAAgB,QAAQ,CAAA;AAC1B;AAGO,SAAS,gBAAA,GAAmB;AACjC,EAAA,eAAA,CAAgB,SAAS,CAAA;AAC3B;AASO,SAAS,gBAAgB,EAAE,QAAA,EAAU,OAAO,KAAA,EAAO,MAAA,GAAS,MAAK,EAAmF;AAGzJ,EAAA,OAAO,aAAA;AAAA,IAAc,cAAA;AAAA,IAAgB,EAAE,OAAO,KAAA,EAAM;AAAA,IAClD,aAAA;AAAA,MAAc,YAAA;AAAA,MAAc,EAAE,OAAO,IAAA,EAAK;AAAA,MACxC,aAAA;AAAA,QAAc,YAAA;AAAA,QAAc,EAAE,OAAO,KAAA,EAAM;AAAA,QACzC,aAAA;AAAA,UAAc,OAAA;AAAA,UAAS,EAAE,OAAO,KAAA,EAAM;AAAA,UACpC,aAAA;AAAA,YAAc,cAAA;AAAA,YAAgB,EAAE,OAAO,IAAA,EAAK;AAAA,YAC1C,cAAc,kBAAA,EAAoB,EAAE,KAAA,EAAO,MAAA,IAAU,QAAQ;AAAA;AAAC;AAAC;AAAC;AAAC,GAAC;AAC9E","file":"chunk-QM4SJKWV.js","sourcesContent":["import { createContext } from \"react\";\nimport type { ControlSize } from \"../components/Button\";\n\n/**\n * Sprint 25.16 A4 (R28-7): the slot in a page's own PageHeader for its actions (PageActions). PageContainer provides it,\n * its header draws it while a page asks, and the page's PageActions render into it. The review of 1 October:\n * - content that starts afresh (an overlay's body, a SplitView's pane: SurfaceBoundary) has no slot, so a header there\n * (a message's own header, a drawer's) never takes the page's actions;\n * - the page's header is the first slot in the page, so a second header that comes and goes hands nothing on;\n * - a container with no header of its own (a page that wraps itself in one under an area layout) passes its page's\n * actions to the container around it;\n * - the actions take the header's control size (a minimal or snapping header draws its own actions small).\n */\nexport interface PageActionsSlot {\n /** A page below asks for the slot: a PageActions is drawn. */\n wanted: boolean;\n /** Where the actions go: the slot of the page's header, at the size its controls are drawn. */\n target: { el: HTMLElement; size: ControlSize | null } | null;\n /** A header's slot, while drawn, with its controls' size; returns its release. */\n register: (el: HTMLElement, size: ControlSize | null) => () => void;\n /** A PageActions asks for the slot while drawn; returns its release. */\n add: () => () => void;\n}\n\nexport const PageActionsCtx = createContext<PageActionsSlot | null>(null);\n","import { createContext, createElement, useContext, useLayoutEffect, type ReactNode } from \"react\";\nimport { PageActionsCtx } from \"./page-actions\";\n\n/**\n * True inside a Card's surface. A StackedList reads it to inset its rows to the card's header (px-ds-5).\n */\nexport const CardSurfaceCtx = createContext(false);\n\n/**\n * The SplitView pane content sits in (\"list\", \"detail\", \"aside\"), false outside one. A StackedList reads it to take its\n * inset from the pane (Sprint 25.4 C-5): rows keep their padding so the selection rail sits at the pane's edge and never\n * touches content. A pane header (SectionHeading variant=\"pane\") in the detail insets at the page's gutter, as the item's\n * path above it does, and in the list at its rows' inset (the owner's review of 1 October).\n */\nexport type SplitPaneKind = false | \"list\" | \"detail\" | \"aside\";\nexport const SplitPaneCtx = createContext<SplitPaneKind>(false);\n\n/**\n * True inside a Table or DataGrid card (the phone layout below mobileBreakpoint). An EntityCell reads it to keep\n * the avatar beside the text and align to the value column (Sprint 25.4, round 7: the stacked cell made the card\n * three lines taller than it needed to be).\n */\nexport const TableCardCtx = createContext(false);\n\n/**\n * True among a collection's rows: a Table's or a DataGrid's rows and their phone cards, a StackedList's rows, a Timeline's\n * items. Something\n * with a Tab stop of its own adds a stop to every row there, which makes the collection hard to cross: a RelativeTime\n * drops its own, its full timestamp in its name and on hover (Sprint 25.17 A29). A DataList is one record's fields, not\n * rows, so it doesn't set it (the second review). Inside a control (a card or a row that is a button, a link) the time\n * finds that for itself, from the page. Overlays reset it: SurfaceBoundary, a Popover, a HoverCard.\n */\nexport const RowsCtx = createContext(false);\n\n/**\n * Sprint 25.14 A44: what a SplitView's list pane holds, as its content reports it while mounted. \"framed\": content that\n * draws its own frame or cards (a KanbanBoard, a CardGrid, a Table, a DataGrid). \"scrolls\": content with no frame of its\n * own that scrolls inside the pane (a StackedList, a Gantt, a VirtualizedList), which the view's hairline frames. The\n * view drops its frame and surface when the pane holds framed content and nothing that scrolls (P32: no frame around\n * what frames itself; a hairline around what scrolls inside). Here, not in SplitView, so a board or a card grid never\n * imports the resizable panels. Null outside a list pane: overlays and the view's other panes reset it (SurfaceBoundary).\n */\nexport type SplitListReport = (kind: \"framed\" | \"scrolls\", present: boolean) => void;\nexport const SplitListFramerCtx = createContext<SplitListReport | null>(null);\n\nfunction useReportToList(kind: \"framed\" | \"scrolls\") {\n const report = useContext(SplitListFramerCtx);\n useLayoutEffect(() => {\n if (!report) return;\n report(kind, true);\n return () => report(kind, false);\n }, [report, kind]);\n}\n\n/** Tell a SplitView whose list pane this sits in that it draws its own frame or cards (A44). */\nexport function useFramesItself() {\n useReportToList(\"framed\");\n}\n\n/** Tell a SplitView whose list pane this sits in that it scrolls inside the pane with no frame of its own (A44). */\nexport function useScrollsInside() {\n useReportToList(\"scrolls\");\n}\n\n/**\n * Where content starts afresh: an overlay's body, an opened table row's panel, a SplitView's pane. What a Card, a pane\n * or a table card told the content around it stops here (the review of 30 September: a StackedList in a Drawer opened\n * from a Card took the card's inset, and one in a Dialog declared in a list pane the pane's, so neither lined up with\n * the overlay's gutter), and so does the page's actions slot. A pane passes `pane` (which pane its content is in) and\n * the list pane its `framer`.\n */\nexport function SurfaceBoundary({ children, pane = false, framer = null }: { children: ReactNode; pane?: SplitPaneKind; framer?: SplitListReport | null }) {\n // the page's actions belong to the page's own header (Sprint 25.16's review): a header in a pane or an overlay never\n // takes them, and PageActions there renders nothing\n return createElement(CardSurfaceCtx, { value: false },\n createElement(SplitPaneCtx, { value: pane },\n createElement(TableCardCtx, { value: false },\n createElement(RowsCtx, { value: false },\n createElement(PageActionsCtx, { value: null },\n createElement(SplitListFramerCtx, { value: framer }, children))))));\n}\n"]}
@@ -1,10 +1,11 @@
1
- import { PreferencesContext, Button } from './chunk-HZIP4EJI.js';
1
+ "use client";
2
+ import { PreferencesContext, Button } from './chunk-EKBWWA6P.js';
2
3
  import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
3
4
  import { FormChangeCtx } from './chunk-XVB55M3N.js';
4
5
  import { SurfaceBoundary } from './chunk-QM4SJKWV.js';
5
- import { usePopupFocus, useOverlayLayer, useReturnFocus, useFocusTrap, focusInitial, useNonModalDialog, OVERLAY_CLOSE_ATTR } from './chunk-JXANCXK6.js';
6
+ import { usePopupFocus, useOverlayLayer, useReturnFocus, useFocusTrap, focusInitial, useNonModalDialog, OVERLAY_CLOSE_ATTR } from './chunk-ZI3OQDET.js';
6
7
  import { useTextOverflow, Tooltip } from './chunk-MD2EFMES.js';
7
- import { Icon } from './chunk-HYMXAIXN.js';
8
+ import { Icon } from './chunk-TJEV7YAY.js';
8
9
  import { usePosition } from './chunk-HP7WUZPK.js';
9
10
  import { useLatest } from './chunk-WH2FUFAC.js';
10
11
  import { cn, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY } from './chunk-M2QRNBDQ.js';
@@ -812,9 +813,11 @@ var AppShell = forwardRef(function AppShell2({
812
813
  if (typeof ref === "function") ref(el);
813
814
  else if (ref) ref.current = el;
814
815
  }, className: cn(
815
- // Sprint 25.16 A42: the dynamic viewport where the browser has it, so nothing hides under a phone browser's
816
- // toolbar (100vh is the height with the toolbar hidden)
817
- "relative h-screen supports-[height:100dvh]:h-dvh flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden",
816
+ // Sprint 25.16 A42: the dynamic viewport, so nothing hides under a phone browser's toolbar (100vh is the height
817
+ // with the toolbar hidden). Sprint 25.21 S1: h-dvh alone, which an app's own height class replaces through cn();
818
+ // the supports-[…] variant that stood beside it survived that merge and beat the app's class (the builder's
819
+ // preview ran under its footer). Every browser that draws the themes' oklch() colours has dvh.
820
+ "relative h-dvh flex flex-col bg-[var(--ds-bg-canvas)] overflow-hidden",
818
821
  className
819
822
  ), children: [
820
823
  skipLink && /* @__PURE__ */ jsx(SkipLink, { href: `#${mainId}` }),
@@ -1185,5 +1188,5 @@ function useElementWidth(ref) {
1185
1188
  }
1186
1189
 
1187
1190
  export { AppShell, Breadcrumbs, DiscardConfirm, Drawer, DrawerFooterCtx, DrawerPanel, OVERLAY_ROOT_ATTR, OverlayCloseCtx, OverlayMarkCtx, OverlayMarkScope, REACT_VERSION_PROBLEM, ResizeEdge, SkipLink, crumbGoes, defaultMaxWidth, insetLessScrollbar, pxOf, useAppShell, useBelow, useCloseSettle, useCompact, useCrumbFollow, useDirtyGuard, useElementWidth, useOverlayMark, useRememberedWidth, useScrollbarInset, useUnsavedChanges };
1188
- //# sourceMappingURL=chunk-3CUKJ4VP.js.map
1189
- //# sourceMappingURL=chunk-3CUKJ4VP.js.map
1191
+ //# sourceMappingURL=chunk-R5NFCEZ3.js.map
1192
+ //# sourceMappingURL=chunk-R5NFCEZ3.js.map