gridsmith-ui 0.29.0 → 0.30.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 (103) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +5 -1
  3. package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
  4. package/dist/charts.d.ts +5 -0
  5. package/dist/charts.js +5 -3
  6. package/dist/charts.js.map +1 -1
  7. package/dist/chat.js +5 -5
  8. package/dist/{chunk-EIWXVICU.js → chunk-6A5RHW2W.js} +3 -3
  9. package/dist/{chunk-EIWXVICU.js.map → chunk-6A5RHW2W.js.map} +1 -1
  10. package/dist/{chunk-XVB55M3N.js → chunk-72SDAX54.js} +2 -2
  11. package/dist/chunk-72SDAX54.js.map +1 -0
  12. package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
  13. package/dist/chunk-7QLXYRPD.js.map +1 -0
  14. package/dist/chunk-F2BNJSON.js +1348 -0
  15. package/dist/chunk-F2BNJSON.js.map +1 -0
  16. package/dist/{chunk-GXMTS3YD.js → chunk-GQVBZ4GW.js} +54 -16
  17. package/dist/chunk-GQVBZ4GW.js.map +1 -0
  18. package/dist/{chunk-2ROVDVLZ.js → chunk-INGF2CPW.js} +4 -23
  19. package/dist/chunk-INGF2CPW.js.map +1 -0
  20. package/dist/{chunk-66JJCQU3.js → chunk-JTU7OBYJ.js} +5 -3
  21. package/dist/chunk-JTU7OBYJ.js.map +1 -0
  22. package/dist/{chunk-DQCHCLEJ.js → chunk-KTPGMJUK.js} +6 -6
  23. package/dist/{chunk-DQCHCLEJ.js.map → chunk-KTPGMJUK.js.map} +1 -1
  24. package/dist/{chunk-TJEV7YAY.js → chunk-L6HWACK7.js} +7 -7
  25. package/dist/chunk-L6HWACK7.js.map +1 -0
  26. package/dist/chunk-LJYSVZMI.js +25 -0
  27. package/dist/chunk-LJYSVZMI.js.map +1 -0
  28. package/dist/{chunk-YUTF2X6B.js → chunk-NI5542ZM.js} +42 -38
  29. package/dist/chunk-NI5542ZM.js.map +1 -0
  30. package/dist/chunk-NKUCS67T.js +13 -0
  31. package/dist/chunk-NKUCS67T.js.map +1 -0
  32. package/dist/{chunk-TRDXWHD2.js → chunk-NLXQ323J.js} +6 -6
  33. package/dist/chunk-NLXQ323J.js.map +1 -0
  34. package/dist/{chunk-ZI3OQDET.js → chunk-OIOII5L6.js} +93 -4
  35. package/dist/chunk-OIOII5L6.js.map +1 -0
  36. package/dist/{chunk-R4GXKH4U.js → chunk-Q36JVJX4.js} +52 -93
  37. package/dist/chunk-Q36JVJX4.js.map +1 -0
  38. package/dist/{chunk-FD4YED7O.js → chunk-RHWTMTU5.js} +3 -3
  39. package/dist/{chunk-FD4YED7O.js.map → chunk-RHWTMTU5.js.map} +1 -1
  40. package/dist/chunk-TYNDXMBV.js +105 -0
  41. package/dist/chunk-TYNDXMBV.js.map +1 -0
  42. package/dist/{chunk-OPFJEL3Z.js → chunk-UNS4TGGK.js} +110 -20
  43. package/dist/chunk-UNS4TGGK.js.map +1 -0
  44. package/dist/chunk-Z4MCD73B.js +316 -0
  45. package/dist/chunk-Z4MCD73B.js.map +1 -0
  46. package/dist/{chunk-CRQ6KCRD.js → chunk-ZHUVLBCC.js} +7 -4
  47. package/dist/chunk-ZHUVLBCC.js.map +1 -0
  48. package/dist/data-grid.d.ts +26 -7
  49. package/dist/data-grid.js +179 -57
  50. package/dist/data-grid.js.map +1 -1
  51. package/dist/editor-mentions.js +3 -3
  52. package/dist/editor.js +5 -5
  53. package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
  54. package/dist/feather-XFFEFLTV.js.map +1 -0
  55. package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
  56. package/dist/heroicons-L6DOHBEA.js.map +1 -0
  57. package/dist/icons.d.ts +10 -1
  58. package/dist/icons.js +1 -1
  59. package/dist/index.d.ts +174 -50
  60. package/dist/index.js +810 -1731
  61. package/dist/index.js.map +1 -1
  62. package/dist/kanban.d.ts +26 -8
  63. package/dist/kanban.js +203 -79
  64. package/dist/kanban.js.map +1 -1
  65. package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
  66. package/dist/lucide-JOORDTO6.js.map +1 -0
  67. package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
  68. package/dist/phosphor-TKGDYNGR.js.map +1 -0
  69. package/dist/resizable.js +12 -11
  70. package/dist/resizable.js.map +1 -1
  71. package/dist/roots.css +14 -8
  72. package/dist/{table-utils-BkjNEWts.d.ts → table-utils-BXkP-2Hx.d.ts} +8 -5
  73. package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
  74. package/dist/tabler-YGETLYRU.js.map +1 -0
  75. package/dist/tokens.css +14 -0
  76. package/dist/{use-pagination-BxcRlNQG.d.ts → use-pagination-QykGSREv.d.ts} +7 -0
  77. package/dist/utils.js.map +1 -1
  78. package/dist/virtualized.d.ts +1 -1
  79. package/dist/virtualized.js +10 -9
  80. package/dist/virtualized.js.map +1 -1
  81. package/dist-static/tokens.css +14 -0
  82. package/package.json +1 -1
  83. package/dist/chunk-2ROVDVLZ.js.map +0 -1
  84. package/dist/chunk-66JJCQU3.js.map +0 -1
  85. package/dist/chunk-BRAXJEEP.js +0 -117
  86. package/dist/chunk-BRAXJEEP.js.map +0 -1
  87. package/dist/chunk-CRQ6KCRD.js.map +0 -1
  88. package/dist/chunk-GXMTS3YD.js.map +0 -1
  89. package/dist/chunk-OPFJEL3Z.js.map +0 -1
  90. package/dist/chunk-OYNUU346.js.map +0 -1
  91. package/dist/chunk-QM4SJKWV.js +0 -51
  92. package/dist/chunk-QM4SJKWV.js.map +0 -1
  93. package/dist/chunk-R4GXKH4U.js.map +0 -1
  94. package/dist/chunk-TJEV7YAY.js.map +0 -1
  95. package/dist/chunk-TRDXWHD2.js.map +0 -1
  96. package/dist/chunk-XVB55M3N.js.map +0 -1
  97. package/dist/chunk-YUTF2X6B.js.map +0 -1
  98. package/dist/chunk-ZI3OQDET.js.map +0 -1
  99. package/dist/feather-FNBQ74RR.js.map +0 -1
  100. package/dist/heroicons-DODCSYWD.js.map +0 -1
  101. package/dist/lucide-ZWE3EPGP.js.map +0 -1
  102. package/dist/phosphor-DIHPA4AJ.js.map +0 -1
  103. package/dist/tabler-D3VYTSGI.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, Ref } from 'react';
3
3
  import { VirtualItem } from '@tanstack/react-virtual';
4
- import { P as Pager } from './use-pagination-BxcRlNQG.js';
4
+ import { P as Pager } from './use-pagination-QykGSREv.js';
5
5
 
6
6
  interface VirtualizedListProps<T> {
7
7
  items: T[];
@@ -1,18 +1,19 @@
1
1
  "use client";
2
- import { useWholeListCheck, pagerPart, Pagination } from './chunk-YUTF2X6B.js';
3
- import './chunk-R4GXKH4U.js';
4
- import './chunk-OYNUU346.js';
2
+ import { useWholeListCheck, pagerPart, Pagination } from './chunk-NI5542ZM.js';
3
+ import './chunk-TYNDXMBV.js';
4
+ import './chunk-7QLXYRPD.js';
5
5
  import './chunk-7RKC5C7G.js';
6
- import './chunk-XVB55M3N.js';
7
- import { useScrollsInside } from './chunk-QM4SJKWV.js';
6
+ import './chunk-72SDAX54.js';
7
+ import { useScrollsInside } from './chunk-Q36JVJX4.js';
8
+ import './chunk-NKUCS67T.js';
8
9
  import { useRevealFocus } from './chunk-HTXP5BMM.js';
9
- import './chunk-ZI3OQDET.js';
10
- import './chunk-GXMTS3YD.js';
10
+ import './chunk-OIOII5L6.js';
11
+ import './chunk-GQVBZ4GW.js';
11
12
  import './chunk-7Q46JXSK.js';
12
- import './chunk-TJEV7YAY.js';
13
+ import './chunk-L6HWACK7.js';
13
14
  import './chunk-HP7WUZPK.js';
14
15
  import { useLatest } from './chunk-WH2FUFAC.js';
15
- import { cn } from './chunk-66JJCQU3.js';
16
+ import { cn } from './chunk-JTU7OBYJ.js';
16
17
  import { useRef, useState, useCallback, useEffect } from 'react';
17
18
  import { useVirtualizer } from '@tanstack/react-virtual';
18
19
  import { jsxs, jsx } from 'react/jsx-runtime';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../showcase/src/components/VirtualizedList.tsx"],"names":[],"mappings":"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","file":"virtualized.js","sourcesContent":["import { useCallback, useEffect, useRef, useState, type ReactNode, type Ref } from \"react\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\nimport { useLatest } from \"../lib/use-latest\";\nimport { Pagination } from \"./Pagination\";\nimport { pagerPart, useWholeListCheck, type Pager } from \"../lib/use-pagination\";\nimport { useScrollsInside } from \"../lib/surface-context\";\nimport { useRevealFocus } from \"../lib/reveal-focus\";\n\nexport interface VirtualizedListProps<T> {\n items: T[];\n estimateSize: (index: number) => number;\n /** An item, with its index in `items` (the whole list, when the list is paged). */\n renderItem: (item: T, index: number, virtualItem: VirtualItem) => ReactNode;\n overscan?: number;\n height?: number | string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n /**\n * The list's pager (Sprint 25.13 A49): paging and drawing only the rows on screen combine. Pass every item and\n * `usePagination(items)`'s pager: the list mounts only the rows on screen of the pager's part, and beneath it draws the\n * control in the project's style. A new page starts at its top; with infinite scroll, scrolling inside the list loads\n * more. For a long page (logs, feeds) the user asked to page differently, pass the size to the hook\n * (`usePagination(rows, { pageSize: 100 })`) with its override note.\n */\n pager?: Pager<unknown>;\n}\n\nexport function VirtualizedList<T>({\n items: allItems,\n estimateSize,\n renderItem,\n overscan = 5,\n height = 400,\n className,\n ref,\n pager,\n}: VirtualizedListProps<T>) {\n // Sprint 25.14 A44: a list with no frame of its own: a SplitView's list pane keeps its hairline around it\n useScrollsInside();\n const parentRef = useRef<HTMLDivElement>(null);\n const endRef = useRef<HTMLDivElement>(null);\n // what the keyboard reaches in a row comes into view whole, with its outline (Sprint 30, P3)\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n useRevealFocus(scroller);\n const setList = useCallback((node: HTMLDivElement | null) => {\n (parentRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n setScroller(node);\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }, [ref]);\n useWholeListCheck(\"VirtualizedList\", allItems.length, pager);\n const items = pagerPart(allItems, pager);\n const offset = pager ? Math.max(0, pager.range[0] - 1) : 0;\n\n // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual is not compiler-compatible (informational; the project does not use the React Compiler)\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => parentRef.current,\n estimateSize: (index) => estimateSize(offset + index),\n // a row is known by its place in the whole list, so page 2's third row isn't page 1's (the review of 30 September:\n // an entry opened on page 1 showed opened on page 2, and page 1's heights carried over)\n getItemKey: (index) => offset + index,\n overscan,\n });\n\n // A49: a new page starts at its top, and so does a growing list that starts again at its first page (a new order, a\n // search): left scrolled to its end, it loaded the next page at once (the review of 30 September)\n const page = pager ? pager.page : null;\n const pageBefore = useRef(page);\n const numbered = pager?.style === \"pagination\";\n useEffect(() => {\n const before = pageBefore.current;\n pageBefore.current = page;\n if (page === null || !parentRef.current) return;\n if (numbered ? page !== before : before !== null && page < before) parentRef.current.scrollTop = 0;\n }, [page, numbered]);\n\n // A49: with infinite scroll, the trigger sits at the end of the list's own content and watches the list's scroller\n // (it watched the viewport, so scrolling inside the list never loaded more); it loads once per growth of the list\n const loadMore = useLatest(pager?.loadMore);\n const growing = pager?.style === \"infinite-scroll\" && pager.hasMore;\n const loadedAt = useRef(-1);\n useEffect(() => {\n const root = parentRef.current;\n const end = endRef.current;\n // the list's length changed since the last load (it grew, or started again at page 1): it may load again (the\n // review of 30 September: after a reset to the first page it never loaded again)\n if (loadedAt.current !== items.length) loadedAt.current = -1;\n if (!growing || !root || !end || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((e) => e.isIntersecting) || loadedAt.current === items.length) return;\n loadedAt.current = items.length;\n loadMore.current?.();\n }, { root, rootMargin: \"200px 0px\" });\n observer.observe(end);\n return () => observer.disconnect();\n }, [growing, items.length, loadMore]);\n\n const list = (\n <div\n ref={setList}\n data-ds-list={pager ? \"\" : undefined}\n className={cn(\n \"overflow-auto scroll-p-[4px]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-bg-surface)]\",\n !pager && className,\n )}\n style={{ height }}\n >\n <div className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualItem) => (\n <div\n key={virtualItem.key}\n data-index={virtualItem.index}\n ref={virtualizer.measureElement}\n className=\"absolute top-0 left-0 w-full\"\n style={{ transform: `translateY(${virtualItem.start}px)` }}\n >\n {renderItem(items[virtualItem.index], offset + virtualItem.index, virtualItem)}\n </div>\n ))}\n </div>\n {growing && <div ref={endRef} data-load-more-trigger=\"\" aria-hidden=\"true\" className=\"h-px w-full\" />}\n </div>\n );\n // A3: the pager's control is the list's next sibling, as <Pagination {...pager.props} /> would be. With infinite\n // scroll it is the Load more button alone, for keyboard users: the list's own trigger loads as it scrolls (A49). The\n // two are one element, which takes the className, so in a grid or a flex row they stay one item, and the list keeps\n // its element while its items come and go (the review of 30 September).\n if (!pager) return list;\n return (\n <div data-gs-paged=\"\" className={cn(\"flex flex-col min-w-0\", className)}>\n {list}\n {allItems.length > 0 && <Pagination {...pager.props} mode={pager.style === \"infinite-scroll\" ? \"load-more\" : pager.props.mode} />}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../showcase/src/components/VirtualizedList.tsx"],"names":[],"mappings":"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","file":"virtualized.js","sourcesContent":["import { useCallback, useEffect, useRef, useState, type ReactNode, type Ref } from \"react\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\nimport { cn } from \"../lib/utils\";\nimport { useLatest } from \"../lib/use-latest\";\nimport { Pagination } from \"./Pagination\";\nimport { pagerPart, useWholeListCheck, type Pager } from \"../lib/use-pagination\";\nimport { useScrollsInside } from \"../lib/surface-context\";\nimport { useRevealFocus } from \"../lib/reveal-focus\";\n\nexport interface VirtualizedListProps<T> {\n items: T[];\n estimateSize: (index: number) => number;\n /** An item, with its index in `items` (the whole list, when the list is paged). */\n renderItem: (item: T, index: number, virtualItem: VirtualItem) => ReactNode;\n overscan?: number;\n height?: number | string;\n className?: string;\n ref?: Ref<HTMLDivElement>;\n /**\n * The list's pager (Sprint 25.13 A49): paging and drawing only the rows on screen combine. Pass every item and\n * `usePagination(items)`'s pager: the list mounts only the rows on screen of the pager's part, and beneath it draws the\n * control in the project's style. A new page starts at its top; with infinite scroll, scrolling inside the list loads\n * more. For a long page (logs, feeds) the user asked to page differently, pass the size to the hook\n * (`usePagination(rows, { pageSize: 100 })`) with its override note.\n */\n pager?: Pager<unknown>;\n}\n\nexport function VirtualizedList<T>({\n items: allItems,\n estimateSize,\n renderItem,\n overscan = 5,\n height = 400,\n className,\n ref,\n pager,\n}: VirtualizedListProps<T>) {\n // Sprint 25.14 A44: a list with no frame of its own: a SplitView's list pane keeps its hairline around it\n useScrollsInside();\n const parentRef = useRef<HTMLDivElement>(null);\n const endRef = useRef<HTMLDivElement>(null);\n // what the keyboard reaches in a row comes into view whole, with its outline (Sprint 30, P3)\n const [scroller, setScroller] = useState<HTMLDivElement | null>(null);\n useRevealFocus(scroller);\n const setList = useCallback((node: HTMLDivElement | null) => {\n (parentRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n setScroller(node);\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;\n }, [ref]);\n useWholeListCheck(\"VirtualizedList\", allItems.length, pager);\n const items = pagerPart(allItems, pager);\n const offset = pager ? Math.max(0, pager.range[0] - 1) : 0;\n\n // eslint-disable-next-line react-hooks/incompatible-library -- TanStack Virtual is not compiler-compatible (informational; the project does not use the React Compiler)\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => parentRef.current,\n estimateSize: (index) => estimateSize(offset + index),\n // a row is known by its place in the whole list, so page 2's third row isn't page 1's (the review of 30 September:\n // an entry opened on page 1 showed opened on page 2, and page 1's heights carried over)\n getItemKey: (index) => offset + index,\n overscan,\n });\n\n // A49: a new page starts at its top, and so does a growing list that starts again at its first page (a new order, a\n // search): left scrolled to its end, it loaded the next page at once (the review of 30 September)\n const page = pager ? pager.page : null;\n const pageBefore = useRef(page);\n const numbered = pager?.style === \"pagination\";\n useEffect(() => {\n const before = pageBefore.current;\n pageBefore.current = page;\n if (page === null || !parentRef.current) return;\n if (numbered ? page !== before : before !== null && page < before) parentRef.current.scrollTop = 0;\n }, [page, numbered]);\n\n // A49: with infinite scroll, the trigger sits at the end of the list's own content and watches the list's scroller\n // (it watched the viewport, so scrolling inside the list never loaded more); it loads once per growth of the list\n const loadMore = useLatest(pager?.loadMore);\n const growing = pager?.style === \"infinite-scroll\" && pager.hasMore;\n const loadedAt = useRef(-1);\n useEffect(() => {\n const root = parentRef.current;\n const end = endRef.current;\n // the list's length changed since the last load (it grew, or started again at page 1): it may load again (the\n // review of 30 September: after a reset to the first page it never loaded again)\n if (loadedAt.current !== items.length) loadedAt.current = -1;\n if (!growing || !root || !end || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver((entries) => {\n if (!entries.some((e) => e.isIntersecting) || loadedAt.current === items.length) return;\n loadedAt.current = items.length;\n loadMore.current?.();\n }, { root, rootMargin: \"200px 0px\" });\n observer.observe(end);\n return () => observer.disconnect();\n }, [growing, items.length, loadMore]);\n\n const list = (\n <div\n ref={setList}\n data-ds-list={pager ? \"\" : undefined}\n className={cn(\n \"overflow-auto scroll-p-[4px]\",\n \"rounded-[var(--ds-radius-md)]\",\n \"border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]\",\n \"bg-[var(--ds-bg-surface)]\",\n !pager && className,\n )}\n style={{ height }}\n >\n <div className=\"relative w-full\" style={{ height: virtualizer.getTotalSize() }}>\n {virtualizer.getVirtualItems().map((virtualItem) => (\n <div\n key={virtualItem.key}\n data-index={virtualItem.index}\n ref={virtualizer.measureElement}\n className=\"absolute top-0 left-0 w-full\"\n style={{ transform: `translateY(${virtualItem.start}px)` }}\n >\n {renderItem(items[virtualItem.index], offset + virtualItem.index, virtualItem)}\n </div>\n ))}\n </div>\n {growing && <div ref={endRef} data-load-more-trigger=\"\" aria-hidden=\"true\" className=\"h-px w-full\" />}\n </div>\n );\n // A3: the pager's control is the list's next sibling, as <Pagination {...pager.props} /> would be. With infinite\n // scroll it is the Load more button alone, for keyboard users: the list's own trigger loads as it scrolls (A49). The\n // two are one element, which takes the className, so in a grid or a flex row they stay one item, and the list keeps\n // its element while its items come and go (the review of 30 September).\n if (!pager) return list;\n return (\n <div data-gs-paged=\"\" className={cn(\"flex flex-col min-w-0\", className)}>\n {list}\n {allItems.length > 0 && <Pagination {...pager.props} mode={pager.style === \"infinite-scroll\" ? \"load-more\" : pager.props.mode} />}\n </div>\n );\n}\n"]}
@@ -497,3 +497,17 @@
497
497
  --ds-status-info-text: oklch(0.92 0.0300 200);
498
498
  --ds-text-on-primary-muted: var(--ds-text-on-primary);
499
499
  }
500
+
501
+ /* Reduced motion — the visitor's preference outranks the theme's motion */
502
+ @media (prefers-reduced-motion: reduce) {
503
+ :root {
504
+ --ds-transition-speed: 0ms !important;
505
+ --ds-hover-scale: 1 !important;
506
+ --ds-hover-lift: 0px !important;
507
+ --ds-press-depth: 0px !important;
508
+ --ds-interactive-hover-speed: 0ms !important;
509
+ --ds-ambient-hover-scale: 1 !important;
510
+ --ds-ambient-hover-lift: 0px !important;
511
+ --ds-ambient-hover-speed: 0ms !important;
512
+ }
513
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridsmith-ui",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "Gridsmith component library — React components and charts with design tokens and a CSS custom property theming engine",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/hydrated.ts","../../../showcase/src/lib/locale.ts","../../../showcase/src/components/Skeleton.tsx","../../../showcase/src/lib/title-text.ts"],"names":[],"mappings":"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","file":"chunk-2ROVDVLZ.js","sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nconst noSubscription = () => () => {};\nconst inBrowser = () => true;\nconst onServer = () => false;\n\n/**\n * Whether the browser has taken the page over (Sprint 25.20 D3): false on a server, and in the render that hydrates the\n * server's HTML (it must draw what the server drew), true straight after. A render with no server HTML (a Vite app, a\n * component mounted later) is true from its first render. What depends on the visitor's clock or time zone, which no\n * server knows, waits for it.\n */\nexport function useHydrated(): boolean {\n return useSyncExternalStore(noSubscription, inBrowser, onServer);\n}\n\n/**\n * Today on the visitor's device, once the browser has the page (null on a server and while hydrating): the day a date\n * grid marks and starts its keyboard on. Read on each render, so a grid drawn again after midnight marks the new day.\n */\nexport function useToday(): Date | null {\n return useHydrated() ? new Date() : null;\n}\n","import { createContext, useContext } from \"react\";\nimport { browserLanguages, formatLocale, localeFor, FIXED_FORM, type LocaleChoice } from \"./format-locale\";\nimport { useHydrated } from \"./hydrated\";\n\n// the choice itself needs no React, so server code can make it too (Sprint 25.22's review: requestLocale, on utils)\nexport { localeChoice, FIXED_FORM, type LocaleChoice } from \"./format-locale\";\n\nexport const LocaleContext = createContext<LocaleChoice | null>(null);\n\n/**\n * The locale a component writes its dates and numbers in (Sprint 25.20 L1). On a server, and while the browser hydrates\n * the server's HTML, the one the server chose: LocaleProvider's (the visitor's form of the page's language, from the\n * request), else \"en\", never a machine's own default, so both sides write the same. Once the page has loaded, the\n * browser's own reading (the visitor's form of the page's `lang`, as formatLocale() reads it), usually the same. A render\n * with no server HTML reads the browser's from its first render. Undefined is the browser's own language.\n */\nexport function useFormatLocale(): string | undefined {\n const choice = useContext(LocaleContext);\n const hydrated = useHydrated();\n if (!hydrated) return choice ? choice.server : FIXED_FORM;\n return choice ? localeFor(choice.lang, browserLanguages()) ?? choice.server : formatLocale();\n}\n","import { cn } from \"../lib/utils\";\n\ninterface SkeletonProps {\n variant?: \"text\" | \"avatar\" | \"card\" | \"table-row\" | \"custom\";\n lines?: number;\n width?: string;\n height?: string;\n className?: string;\n}\n\nfunction Bone({ className, style, \"data-gs\": marker }: { className?: string; style?: React.CSSProperties; \"data-gs\"?: string }) {\n return (\n <div\n data-gs={marker}\n className={cn(\"rounded-[var(--ds-radius-sm)] bg-[var(--ds-bg-emphasis)] animate-pulse\", className)}\n style={style}\n />\n );\n}\n\nexport function Skeleton({ variant = \"text\", lines = 3, width, height, className }: SkeletonProps) {\n if (variant === \"custom\") {\n return <Bone data-gs=\"skeleton\" className={className} style={{ width, height }} />;\n }\n\n if (variant === \"avatar\") {\n return <Bone data-gs=\"skeleton\" className={cn(\"h-10 w-10 rounded-[var(--ds-radius-full)]\", className)} />;\n }\n\n if (variant === \"card\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n <Bone className=\"h-32 w-full rounded-[var(--ds-radius-md)]\" />\n <Bone className=\"h-4 w-3/4\" />\n <Bone className=\"h-4 w-1/2\" />\n </div>\n );\n }\n\n if (variant === \"table-row\") {\n return (\n <div data-gs=\"skeleton\" className={cn(\"flex gap-ds-4\", className)}>\n <Bone className=\"h-4 flex-[3]\" />\n <Bone className=\"h-4 flex-[2]\" />\n <Bone className=\"h-4 flex-1\" />\n <Bone className=\"h-4 flex-1\" />\n </div>\n );\n }\n\n // text variant\n const widths = [\"w-3/4\", \"w-1/2\", \"w-5/6\", \"w-2/3\", \"w-4/5\"];\n return (\n <div data-gs=\"skeleton\" className={cn(\"space-y-ds-3\", className)}>\n {Array.from({ length: lines }, (_, i) => (\n <Bone key={i} className={cn(\"h-4\", widths[i % widths.length])} />\n ))}\n </div>\n );\n}\n","/**\n * A title's text classes: CardTitle's, which a titled Table's band and a ChartContainer's title share (Sprint 25.9, A1),\n * one definition so they cannot drift. A word or a name longer than its line breaks at the line's edge (round 27,\n * R27-16: run 70's \"primary-logo-navy.svg\" ran out of its card); break-word, not anywhere, so a title's words still\n * set its minimum and the actions beside it wrap under it before a word is broken (HEADER_LAYOUT.floor).\n */\nexport const TITLE_TEXT = \"font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] break-words\";\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/utils.ts"],"names":[],"mappings":";;;;;AAGO,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;AAEO,IAAM,cAAA,GAAiB;AAEvB,IAAM,aAAA,GAAgB,GAAG,cAAc,CAAA,qDAAA;AAEvC,IAAM,aAAA,GAAgB,GAAG,cAAc,CAAA,sDAAA;AAavC,IAAM,eAAA,GAAkB;AAExB,IAAM,cAAA,GAAiB,GAAG,cAAc,CAAA,yDAAA;AAWxC,IAAM,UAAA,GAAa;AAgBnB,IAAM,eAAA,GAAkB;AAExB,IAAM,gBAAA,GAAmB;AAUzB,IAAM,WAAA,GAAc;AAgBpB,IAAM,gBAAA,GAAmB;AAEzB,IAAM,uBAAA,GAA0B;AAEhC,IAAM,oBAAA,GAAuB;AAG7B,IAAM,gBAAA,GAAmB;AAEzB,IAAM,uBAAA,GAA0B;AAEhC,IAAM,oBAAA,GAAuB;AAK7B,IAAM,WAAA,GAAc;AAEpB,IAAM,iBAAA,GAAoB;AAMjC,IAAI,eAAA,GAAkB,CAAA;AACtB,IAAI,0BAAA,GAA6B,EAAA;AAE1B,SAAS,cAAA,GAAiB;AAC/B,EAAA,IAAI,oBAAoB,CAAA,EAAG;AACzB,IAAA,0BAAA,GAA6B,QAAA,CAAS,KAAK,KAAA,CAAM,QAAA;AACjD,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,EACjC;AACA,EAAA,eAAA,EAAA;AACF;AAEO,SAAS,gBAAA,GAAmB;AACjC,EAAA,IAAI,oBAAoB,CAAA,EAAG;AAC3B,EAAA,eAAA,EAAA;AACA,EAAA,IAAI,oBAAoB,CAAA,EAAG;AACzB,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,0BAAA;AAC/B,IAAA,0BAAA,GAA6B,EAAA;AAAA,EAC/B;AACF","file":"chunk-66JJCQU3.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n\nexport const GLASS_BACKDROP = \"[backdrop-filter:var(--ds-glass-filter)] [-webkit-backdrop-filter:var(--ds-glass-filter)]\";\n\nexport const GLASS_SURFACE = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-card),var(--ds-glass-inset)]`;\n\nexport const GLASS_OVERLAY = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-modal),var(--ds-glass-inset)]`;\n\n/**\n * A modal surface (Dialog, Drawer, Sheet, CommandPalette, GlobalSearch): the theme's surface, never less than 85% opaque.\n * A glass theme's surface is see-through by design, which suits a card on the canvas; over an overlay's scrim the same\n * glass reads as grey mud, and the page shows through (round 27, R27-17, the owner picked 85% from a mock-up). An opaque\n * theme keeps its surface as it is: the alpha only rises, through CSS relative colour, in every mode and scope.\n *\n * The theme's surface comes first, for a browser without relative colour (Chrome before 119, Firefox before 128,\n * Safari before 16.4): a value holding var() is only found invalid once computed, which drops the background to\n * transparent rather than to an earlier declaration, so the raised one applies only where @supports says it can (the\n * round 27 review). A surface with no colour at all (fully transparent) stays so: raising it would paint it black.\n */\nexport const OVERLAY_SURFACE = \"bg-[var(--ds-bg-surface)] supports-[color:oklch(from_red_l_c_h)]:bg-[oklch(from_var(--ds-bg-surface)_l_c_h_/_max(alpha,min(1,alpha*1000)*0.85))]\";\n\nexport const GLASS_DROPDOWN = `${GLASS_BACKDROP} shadow-[var(--ds-shadow-dropdown),var(--ds-glass-inset)]`;\n\n// Keyboard focus (Sprint 30, the owner's D1 and D6): where Tab has landed. A solid outline in the theme's focus colour\n// (--ds-focus, derived to 3:1 on every ground), 2 px, with a 2 px gap between it and the element. The gap shows what the\n// element sits on, so one colour reads beside any fill, and while the outline shows, the element's own shadow steps\n// aside (D7, P12): shadow-none drops its --tw-shadow and keeps any ring. Every part is prefixed: an unprefixed\n// outline-solid draws an outline at rest. And outline-solid is required: in Tailwind 4, outline-none (which many\n// components set at rest) sets --tw-outline-style: none, which outline-2 reads, so without it no outline draws. These\n// draw no border (a button that uses one, the Launcher, gets the outline alone); a field takes FIELD_FOCUS_RING. `cn()`\n// keeps only the last of outline-none and outline-solid in one variant, so a component's own focus-visible:outline-none\n// after a utility would remove the outline: none may follow one.\nexport const FOCUS_RING = \"focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-focus)] focus-visible:shadow-none\";\n\n/**\n * On a wrapper that shows focus for a control inside it: HAS_FOCUS_RING, from the keyboard only (`:has(:focus-visible)`).\n * FOCUS_WITHIN_RING shows it on any focus inside, a click's too, since `:focus-within` has no keyboard-only form: kept\n * for code that uses it, and a new wrapper takes HAS_FOCUS_RING (D6: keyboard focus shows only from the keyboard).\n */\nexport const FOCUS_WITHIN_RING = \"focus-within:outline-2 focus-within:outline-solid focus-within:outline-offset-2 focus-within:outline-[var(--ds-focus)] focus-within:shadow-none\";\n\nexport const HAS_FOCUS_RING = \"has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-solid has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-[var(--ds-focus)] has-[:focus-visible]:shadow-none\";\n\n/**\n * The outline on a drawn child, where the element that takes focus is hidden or wraps it: a checkbox's box beside its\n * input (`peer`), a radio's circle inside its button, a separator's grip, a Gantt milestone's diamond (`group`). The\n * same outline, gap and shadow rule.\n */\nexport const PEER_FOCUS_RING = \"peer-focus-visible:outline-2 peer-focus-visible:outline-solid peer-focus-visible:outline-offset-2 peer-focus-visible:outline-[var(--ds-focus)] peer-focus-visible:shadow-none\";\n\nexport const GROUP_FOCUS_RING = \"group-focus-visible:outline-2 group-focus-visible:outline-solid group-focus-visible:outline-offset-2 group-focus-visible:outline-[var(--ds-focus)] group-focus-visible:shadow-none\";\n\n/**\n * The same outline drawn inside the element's edge, for edge-to-edge rows, cells and scroll regions only (the plan's\n * P3): elements that are never filled solid, since inside the edge there is no gap to keep one colour readable on a fill.\n * It sits `--ds-focus-inset` in: 5 px, deeper where the theme's corners are rounder, so it clears a table's rounded\n * corners (D8, the owner's decision A); what such an element holds keeps 1 px more clear of its edge. Anywhere else the\n * outline stays outside, with room made for it. Moved here from use-keyboard-scroll.ts (Sprint 30) so the server-safe\n * `gridsmith-ui/utils` can export it.\n */\nexport const FOCUS_INSET = \"focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-[var(--ds-focus-inset)] focus-visible:outline-[var(--ds-focus)]\";\n\n/**\n * A field (D6, P4, P13). Reached with Tab: the keyboard's outline, and its border in the focus colour, as GOV.UK turns a\n * focused input's border: it is where you type. Clicked into: the active look instead, the accent border and its soft\n * halo (`ring-2` in --ds-ring-active, laid over the field's own shadow), and no outline. A browser matches :focus-visible\n * on a text field either way, so lib/input-modality.ts notes on <html> whether a pointer moved focus last; with no note\n * yet, the outline. The field keeps its own (inset) shadow in both: no shadow-none here.\n *\n * The wrapper forms (_WITHIN_ and _HAS_, for a box round the part you type in) show it while that part has focus, and\n * not while a control of the field's own does: a button, a select, a link, a checkbox, a radio or a switch inside it (a\n * tag's remove, a picker's chevron, a composer's Send, an editor's toolbar or a task's checkbox), or anything in a popup\n * inside the box (a dialog of its own; a field that is itself in a dialog still shows it). That control shows its own outline, so there is one focus point, never\n * two (P11). _WITHIN_ reads :focus, as a click on a date's segment isn't :focus-visible; _HAS_ reads :focus-visible.\n * Either outranks the field's hover border, so hovering keeps the look.\n */\nexport const FIELD_FOCUS_RING = \"focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-focus)] focus-visible:border-[var(--ds-focus)] [[data-input-modality=pointer]_&]:focus-visible:outline-none [[data-input-modality=pointer]_&]:focus-visible:border-[var(--ds-accent-ink)] [[data-input-modality=pointer]_&]:focus-visible:ring-2 [[data-input-modality=pointer]_&]:focus-visible:ring-[var(--ds-ring-active)]\";\n\nexport const FIELD_FOCUS_WITHIN_RING = \"has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-2 has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-solid has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-offset-2 has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-[var(--ds-focus)] has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:border-[var(--ds-focus)] [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-none [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:border-[var(--ds-accent-ink)] [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:ring-2 [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:ring-[var(--ds-ring-active)]\";\n\nexport const FIELD_HAS_FOCUS_RING = \"has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-2 has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-solid has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-offset-2 has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-[var(--ds-focus)] has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:border-[var(--ds-focus)] [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-none [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:border-[var(--ds-accent-ink)] [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:ring-2 [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:ring-[var(--ds-ring-active)]\";\n\n// A field with an error: the same, its red border kept, and the red halo when clicked into.\nexport const ERROR_FOCUS_RING = \"focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-focus)] focus-visible:border-[var(--ds-status-error)] [[data-input-modality=pointer]_&]:focus-visible:outline-none [[data-input-modality=pointer]_&]:focus-visible:ring-2 [[data-input-modality=pointer]_&]:focus-visible:ring-[var(--ds-ring-error)]\";\n\nexport const ERROR_FOCUS_WITHIN_RING = \"has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-2 has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-solid has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-offset-2 has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-[var(--ds-focus)] has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:border-[var(--ds-status-error)] [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:outline-none [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:ring-2 [[data-input-modality=pointer]_&]:has-[:focus:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus))]:ring-[var(--ds-ring-error)]\";\n\nexport const ERROR_HAS_FOCUS_RING = \"has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-2 has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-solid has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-offset-2 has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-[var(--ds-focus)] has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:border-[var(--ds-status-error)] [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:outline-none [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:ring-2 [[data-input-modality=pointer]_&]:has-[:focus-visible:not(button,select,a,[role=button],input[type=checkbox],input[type=radio],[role=checkbox],[role=radio],[role=switch])]:[&:not(:has([role=dialog]_:focus-visible))]:ring-[var(--ds-ring-error)]\";\n\n// Active (D6): a field clicked into, or a dropdown or picker that's open, is its accent border (--ds-accent-ink, P8) with\n// the soft halo beyond it; on a field with an error, red with the red halo. Never an outline: that is keyboard focus's.\n// Choices (a selected card, row or swatch, the Stepper's current step) draw their own selected look, not this (D2).\nexport const ACTIVE_RING = \"border-[var(--ds-accent-ink)] ring-2 ring-[var(--ds-ring-active)]\";\n\nexport const ERROR_ACTIVE_RING = \"border-[var(--ds-status-error)] ring-2 ring-[var(--ds-ring-error)]\";\n\n// Body scroll lock is reference-counted so stacked overlays (a Dialog opened\n// from a Drawer, for example) only restore scrolling when the LAST one\n// closes. The original overflow value is captured on the first lock and put\n// back on the last unlock; an unbalanced unlock is clamped at zero.\nlet scrollLockCount = 0;\nlet scrollLockPreviousOverflow = \"\";\n\nexport function lockBodyScroll() {\n if (scrollLockCount === 0) {\n scrollLockPreviousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n scrollLockCount++;\n}\n\nexport function unlockBodyScroll() {\n if (scrollLockCount === 0) return;\n scrollLockCount--;\n if (scrollLockCount === 0) {\n document.body.style.overflow = scrollLockPreviousOverflow;\n scrollLockPreviousOverflow = \"\";\n }\n}\n\n/** Test-only: current number of active locks. */\nexport function getBodyScrollLockCount() {\n return scrollLockCount;\n}\n"]}
@@ -1,117 +0,0 @@
1
- "use client";
2
- import { useTextOverflow, Tooltip } from './chunk-GXMTS3YD.js';
3
- import { cn } from './chunk-66JJCQU3.js';
4
- import { useRef, useState, useLayoutEffect, Fragment } from 'react';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
6
-
7
- // ../../showcase/src/lib/table-utils.ts
8
- var DATE_ISO = /^\d{4}-\d{2}-\d{2}$/;
9
- var DATE_US = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
10
- var MONTH = "[A-Z][a-z]{2,8}\\.?";
11
- var DATE_TEXT_MDY = new RegExp(`^${MONTH}\\s+\\d{1,2},?\\s+\\d{4}$`);
12
- var DATE_TEXT_DMY = new RegExp(`^\\d{1,2}\\s+${MONTH}\\s+\\d{4}$`);
13
- var MONTH_NAME = "(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\\.?";
14
- var DATE_TEXT_MD = new RegExp(`^${MONTH_NAME}\\s+\\d{1,2}$`);
15
- var DATE_TEXT_DM = new RegExp(`^\\d{1,2}\\s+${MONTH_NAME}$`);
16
- var NUMBER_RE = /^[$€£¥]?\s?[\d,]+\.?\d*\s?(?:%|[KMGTP]i?B|B|bytes?|ms|s|sec|min|h|hrs?|kg|g|km|m|°[CF])?$/i;
17
- var IDENTIFIER_RE = /^\d{13,}$/;
18
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
- function isDate(v) {
20
- 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);
21
- }
22
- function isJsx(v) {
23
- return typeof v === "object" && v !== null;
24
- }
25
- function detectKind(values) {
26
- const present = values.filter((v) => v != null);
27
- if (present.length === 0) return void 0;
28
- if (present.some(isJsx)) return void 0;
29
- if (present.every((v) => typeof v === "string" && isDate(v))) return "date";
30
- if (present.every((v) => typeof v === "string" && IDENTIFIER_RE.test(v) || typeof v === "number" && Math.abs(v) >= 1e13 && Number.isInteger(v))) return "identifier";
31
- if (present.every((v) => typeof v === "number")) return "number";
32
- if (present.every((v) => typeof v === "string" && NUMBER_RE.test(v))) return "number";
33
- if (present.every((v) => typeof v === "string" && EMAIL_RE.test(v))) return "email";
34
- if (present.every((v) => typeof v === "string" && v.length <= 20 && !/[\n\r]/.test(v))) return "label";
35
- return "text";
36
- }
37
- function kindOfRenderedText(texts) {
38
- const kind = detectKind(texts.map((t) => t.trim()).filter(Boolean));
39
- return kind === "date" || kind === "number" ? kind : void 0;
40
- }
41
- function inferColumnKinds(data, columnKeys, maxSample = 50) {
42
- const sample = data.length > maxSample ? data.slice(0, maxSample) : data;
43
- const result = /* @__PURE__ */ new Map();
44
- for (const key of columnKeys) {
45
- const values = sample.map((row) => row[key]);
46
- const kind = detectKind(values);
47
- if (kind) result.set(key, kind);
48
- }
49
- return result;
50
- }
51
- var CARD_VALUE_CLASS = cn(
52
- "min-w-0 text-right text-[var(--ds-text-primary)]",
53
- "[overflow-wrap:break-word] [&_*]:min-w-0 [&_*]:[overflow-wrap:break-word]",
54
- // A hand-rolled avatar cell (div > Avatar + text) keeps its row and sits against the value column's right edge,
55
- // like EntityCell (round 7: the stacked cell was replaced).
56
- "[&:has([data-avatar])>*]:flex [&:has([data-avatar])>*]:items-center [&:has([data-avatar])>*]:justify-end [&:has([data-avatar])>*]:text-right"
57
- );
58
- var BREAK_AFTER = /([@./-])/g;
59
- function breakable(text) {
60
- const parts = text.split(BREAK_AFTER);
61
- if (parts.length === 1) return text;
62
- return parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [
63
- part,
64
- i % 2 === 1 && /* @__PURE__ */ jsx("wbr", {})
65
- ] }, i));
66
- }
67
- function CardStringValue({ text }) {
68
- const { ref, isTruncated } = useTextOverflow({ lines: 2, content: text });
69
- const el = /* @__PURE__ */ jsx("span", { ref, className: "block line-clamp-2", children: breakable(text) });
70
- return isTruncated ? /* @__PURE__ */ jsx(Tooltip, { content: text, children: el }) : el;
71
- }
72
- function CardValue({ value, id }) {
73
- return /* @__PURE__ */ jsx("span", { id, className: CARD_VALUE_CLASS, children: typeof value === "string" ? /* @__PURE__ */ jsx(CardStringValue, { text: value }) : value });
74
- }
75
- var CARDS_BELOW = 440;
76
- var CARDS_HYSTERESIS = 40;
77
- function useCardsSwitch(narrowScreen, cardsBelow = CARDS_BELOW, remeasure) {
78
- const rootRef = useRef(null);
79
- const [narrowBox, setNarrowBox] = useState(false);
80
- const needed = useRef(0);
81
- const cards = narrowScreen || narrowBox;
82
- useLayoutEffect(() => {
83
- const root = rootRef.current;
84
- if (!root || cardsBelow <= 0) {
85
- setNarrowBox(false);
86
- return;
87
- }
88
- const measure = () => {
89
- const style = getComputedStyle(root);
90
- const width = root.getBoundingClientRect().width + (parseFloat(style.marginLeft) || 0) + (parseFloat(style.marginRight) || 0);
91
- if (width <= 0) return;
92
- if (narrowBox) {
93
- if (width >= Math.min(needed.current, cardsBelow + CARDS_HYSTERESIS)) setNarrowBox(false);
94
- return;
95
- }
96
- if (cards) return;
97
- const frame = root.querySelector("[data-table-scroll]") ?? root;
98
- const over = frame.scrollWidth - frame.clientWidth;
99
- if (width < cardsBelow && over > 1) {
100
- needed.current = width + over;
101
- setNarrowBox(true);
102
- }
103
- };
104
- measure();
105
- if (typeof ResizeObserver === "undefined") return;
106
- const observer = new ResizeObserver(measure);
107
- observer.observe(root);
108
- const table = root.querySelector("[data-table-scroll] > *");
109
- if (table) observer.observe(table);
110
- return () => observer.disconnect();
111
- }, [cardsBelow, cards, narrowBox, remeasure]);
112
- return { cards, rootRef };
113
- }
114
-
115
- export { CARDS_BELOW, CardValue, inferColumnKinds, kindOfRenderedText, useCardsSwitch };
116
- //# sourceMappingURL=chunk-BRAXJEEP.js.map
117
- //# sourceMappingURL=chunk-BRAXJEEP.js.map
@@ -1 +0,0 @@
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-BRAXJEEP.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 // the width of the root's place: the cards' list reaches past its box by the room it keeps for a card's keyboard\n // outline, its margins taken back (Sprint 30, P3), which mustn't read as more width than the table had there\n const style = getComputedStyle(root);\n const width = root.getBoundingClientRect().width + (parseFloat(style.marginLeft) || 0) + (parseFloat(style.marginRight) || 0);\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 +0,0 @@
1
- {"version":3,"sources":["../../../showcase/src/lib/use-breakpoint.ts"],"names":[],"mappings":";;;;AAEA,IAAM,mBAAA,GAAsB,EAAE,EAAA,EAAI,GAAA,EAAK,IAAI,GAAA,EAAK,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,IAAA,EAAK;AAInE,IAAM,kBAAA,GAAqB,cAA6B,IAAI,CAAA;AAOrD,SAAS,uBAAA,CAAwB,EAAE,KAAA,EAAO,QAAA,EAAS,EAAkD;AAC1G,EAAA,OAAO,cAAc,kBAAA,EAAoB,EAAE,KAAA,EAAO,KAAA,IAAS,QAAQ,CAAA;AACrE;AAGO,SAAS,iBAAiB,GAAA,EAA4B;AAC3D,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,oBAAoB,GAAG,CAAA;AACjE,EAAA,MAAM,GAAA,GAAM,gBAAA,CAAiB,QAAA,CAAS,eAAe,CAAA,CAClD,iBAAiB,CAAA,QAAA,EAAW,GAAG,CAAA,CAAE,CAAA,CACjC,IAAA,EAAK;AACR,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,GAAA,EAAK,EAAE,CAAA;AAC3B,EAAA,OAAO,KAAA,CAAM,EAAE,CAAA,GAAI,mBAAA,CAAoB,GAAG,CAAA,GAAI,EAAA;AAChD;AAEA,IAAM,aAAA,GAAgB,MAAM,MAAM;AAAC,CAAA;AACnC,IAAM,iBAAiB,MAAM,KAAA;AAatB,SAAS,cAAc,GAAA,EAA6B;AACzD,EAAA,MAAM,aAAA,GAAgB,WAAW,kBAAkB,CAAA;AACnD,EAAA,MAAM,EAAA,GAAK,QAAQ,MAAM,gBAAA,CAAiB,GAAG,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AACrD,EAAA,MAAM,UAAA,GAAa,eAAe,EAAE,CAAA,GAAA,CAAA;AAEpC,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,CAAC,QAAA,KAAyB;AACtD,IAAA,IAAI,kBAAkB,IAAA,IAAQ,OAAO,MAAA,KAAW,WAAA,SAAoB,aAAA,EAAc;AAClF,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,UAAU,CAAA;AACxC,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACvC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,UAAA,EAAY,aAAa,CAAC,CAAA;AAE9B,EAAA,MAAM,OAAA,GAAU,oBAAA;AAAA,IACd,SAAA;AAAA,IACA,MAAO,OAAO,MAAA,KAAW,WAAA,GAAc,OAAO,UAAA,CAAW,UAAU,EAAE,OAAA,GAAU,KAAA;AAAA,IAC/E;AAAA,GACF;AAEA,EAAA,IAAI,aAAA,KAAkB,IAAA,EAAM,OAAO,aAAA,IAAiB,EAAA;AACpD,EAAA,OAAO,OAAA;AACT","file":"chunk-CRQ6KCRD.js","sourcesContent":["import { useCallback, useMemo, useSyncExternalStore, createContext, useContext, createElement, type ReactNode } from \"react\";\n\nconst DEFAULT_BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 } as const;\n\nexport type BreakpointKey = keyof typeof DEFAULT_BREAKPOINTS;\n\nconst BreakpointWidthCtx = createContext<number | null>(null);\n\n/**\n * Provides an override container width for all `useBreakpoint` calls within\n * this subtree. Used by the builder's viewport preview to make components\n * respond to the constrained canvas width instead of the browser viewport.\n */\nexport function BreakpointWidthProvider({ width, children }: { width: number | null; children: ReactNode }) {\n return createElement(BreakpointWidthCtx, { value: width }, children);\n}\n\n/** The `--ds-bp-*` breakpoint in px (the token, else the default). */\nexport function readBreakpointPx(key: BreakpointKey): number {\n if (typeof window === \"undefined\") return DEFAULT_BREAKPOINTS[key];\n const raw = getComputedStyle(document.documentElement)\n .getPropertyValue(`--ds-bp-${key}`)\n .trim();\n const px = parseInt(raw, 10);\n return isNaN(px) ? DEFAULT_BREAKPOINTS[key] : px;\n}\n\nconst noopSubscribe = () => () => {};\nconst serverSnapshot = () => false;\n\n/**\n * Reactive breakpoint check that reads `--ds-bp-*` CSS variables.\n * Returns true when the viewport (or override container) is at or below the\n * named breakpoint.\n *\n * Implemented with useSyncExternalStore so the first render already reflects\n * the media query and a change between render and subscribe cannot be missed.\n *\n * @example\n * const isMobile = useBreakpoint(\"md\"); // true when ≤768px\n */\nexport function useBreakpoint(key: BreakpointKey): boolean {\n const overrideWidth = useContext(BreakpointWidthCtx);\n const px = useMemo(() => readBreakpointPx(key), [key]);\n const mediaQuery = `(max-width: ${px}px)`;\n\n const subscribe = useCallback((onChange: () => void) => {\n if (overrideWidth !== null || typeof window === \"undefined\") return noopSubscribe();\n const mql = window.matchMedia(mediaQuery);\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, [mediaQuery, overrideWidth]);\n\n const matches = useSyncExternalStore(\n subscribe,\n () => (typeof window !== \"undefined\" ? window.matchMedia(mediaQuery).matches : false),\n serverSnapshot,\n );\n\n if (overrideWidth !== null) return overrideWidth <= px;\n return matches;\n}\n"]}
@@ -1 +0,0 @@
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-GXMTS3YD.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"]}