basalt-ui 1.30.2 → 1.32.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 (357) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +54 -0
  3. package/MIGRATING.md +256 -12
  4. package/agent/rules/basalt-controls.md +19 -19
  5. package/agent/rules/basalt-mantine.md +5 -5
  6. package/configs/oxlint-plugin.js +371 -4
  7. package/configs/oxlint-plugin.test.ts +327 -2
  8. package/configs/oxlint.json +2 -1
  9. package/dist/agent-chat/composer.d.ts.map +1 -1
  10. package/dist/agent-chat/composer.js +41 -43
  11. package/dist/agent-chat/thread-workspace.d.ts +2 -2
  12. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  13. package/dist/agent-chat/thread-workspace.js +3 -4
  14. package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
  15. package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
  16. package/dist/agent-chat/use-keyboard-inset.js +34 -0
  17. package/dist/charts/cursor/touch-pin.d.ts +45 -0
  18. package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
  19. package/dist/charts/cursor/touch-pin.js +79 -0
  20. package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
  21. package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
  22. package/dist/charts/hooks/use-discrete-cursor.js +198 -0
  23. package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
  24. package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
  25. package/dist/charts/hooks/use-hysteresis.js +12 -0
  26. package/dist/charts/hooks/useBandPlot.d.ts +7 -0
  27. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  28. package/dist/charts/hooks/useBandPlot.js +62 -18
  29. package/dist/charts/hooks/useChartCursor.d.ts +40 -11
  30. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  31. package/dist/charts/hooks/useChartCursor.js +102 -11
  32. package/dist/charts/index.d.ts +0 -1
  33. package/dist/charts/index.d.ts.map +1 -1
  34. package/dist/charts/index.js +0 -10
  35. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  36. package/dist/charts/kinds/BandStrip.js +47 -82
  37. package/dist/charts/kinds/Donut.d.ts +9 -3
  38. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  39. package/dist/charts/kinds/Donut.js +110 -48
  40. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  41. package/dist/charts/kinds/DualPanel.js +138 -145
  42. package/dist/charts/kinds/Heatmap.d.ts +7 -0
  43. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  44. package/dist/charts/kinds/Heatmap.js +37 -16
  45. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  46. package/dist/charts/kinds/MirroredBars.js +68 -111
  47. package/dist/charts/kinds/band-chrome.d.ts +31 -0
  48. package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
  49. package/dist/charts/kinds/band-chrome.js +71 -0
  50. package/dist/charts/kinds/donut-layout.d.ts +47 -0
  51. package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
  52. package/dist/charts/kinds/donut-layout.js +52 -0
  53. package/dist/charts/layout/auto-margin.d.ts +7 -2
  54. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  55. package/dist/charts/layout/auto-margin.js +12 -3
  56. package/dist/charts/layout/end-labels.d.ts +64 -0
  57. package/dist/charts/layout/end-labels.d.ts.map +1 -0
  58. package/dist/charts/layout/end-labels.js +76 -0
  59. package/dist/charts/primitives/Axes.d.ts +32 -4
  60. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  61. package/dist/charts/primitives/Axes.js +62 -16
  62. package/dist/charts/primitives/CartesianChart.d.ts +7 -6
  63. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  64. package/dist/charts/primitives/CartesianChart.js +264 -145
  65. package/dist/charts/primitives/ChartCard.d.ts +0 -14
  66. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartCard.js +56 -21
  68. package/dist/charts/primitives/ChartFrame.d.ts +28 -22
  69. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  70. package/dist/charts/primitives/ChartFrame.js +116 -34
  71. package/dist/charts/primitives/ChartLegend.d.ts +19 -15
  72. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  73. package/dist/charts/primitives/ChartLegend.js +86 -73
  74. package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
  75. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  76. package/dist/charts/primitives/ChartTooltip.js +20 -4
  77. package/dist/charts/primitives/Crosshair.js +2 -2
  78. package/dist/charts/primitives/HoverOverlay.d.ts +29 -9
  79. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  80. package/dist/charts/primitives/HoverOverlay.js +54 -27
  81. package/dist/charts/primitives/HoverOverlay.module.css +13 -0
  82. package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  83. package/dist/charts/primitives/chart-card-context.d.ts +33 -0
  84. package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
  85. package/dist/charts/primitives/chart-card-context.js +18 -0
  86. package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
  87. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  88. package/dist/charts/primitives/chart-frame-layout.js +75 -34
  89. package/dist/charts/primitives/chart-layout.d.ts +112 -0
  90. package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
  91. package/dist/charts/primitives/chart-layout.js +175 -0
  92. package/dist/charts/primitives/chart-tier.d.ts +13 -11
  93. package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
  94. package/dist/charts/primitives/chart-tier.js +42 -13
  95. package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
  96. package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
  97. package/dist/charts/primitives/legend-disclosure.js +153 -0
  98. package/dist/charts/series.d.ts +4 -1
  99. package/dist/charts/series.d.ts.map +1 -1
  100. package/dist/charts/utils/ticks.d.ts +27 -8
  101. package/dist/charts/utils/ticks.d.ts.map +1 -1
  102. package/dist/charts/utils/ticks.js +35 -11
  103. package/dist/common/errors.d.ts +11 -1
  104. package/dist/common/errors.d.ts.map +1 -1
  105. package/dist/common/errors.js +16 -2
  106. package/dist/common/hit-floor.d.ts +10 -0
  107. package/dist/common/hit-floor.d.ts.map +1 -0
  108. package/dist/common/hit-floor.js +6 -0
  109. package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
  110. package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
  111. package/dist/common/use-measured-widths.d.ts +37 -0
  112. package/dist/common/use-measured-widths.d.ts.map +1 -0
  113. package/dist/common/use-measured-widths.js +51 -0
  114. package/dist/common/use-media-query.d.ts +6 -0
  115. package/dist/common/use-media-query.d.ts.map +1 -0
  116. package/dist/common/use-media-query.js +40 -0
  117. package/dist/common/validate.d.ts +9 -0
  118. package/dist/common/validate.d.ts.map +1 -1
  119. package/dist/common/validate.js +9 -4
  120. package/dist/content/article-layout.d.ts +4 -2
  121. package/dist/content/article-layout.d.ts.map +1 -1
  122. package/dist/content/article-layout.js +43 -3
  123. package/dist/content/article-layout.module.css +45 -4
  124. package/dist/content/article-layout.module.css.d.ts +3 -0
  125. package/dist/content/code-block.d.ts.map +1 -1
  126. package/dist/content/code-block.js +40 -2
  127. package/dist/content/code-block.module.css +32 -2
  128. package/dist/content/prose.module.css +13 -8
  129. package/dist/controls/actions.d.ts +29 -6
  130. package/dist/controls/actions.d.ts.map +1 -1
  131. package/dist/controls/actions.js +158 -27
  132. package/dist/controls/actions.module.css +14 -1
  133. package/dist/controls/actions.module.css.d.ts +1 -0
  134. package/dist/controls/controls.module.css +13 -31
  135. package/dist/controls/enum-filter.js +2 -2
  136. package/dist/controls/filter-context.d.ts +10 -0
  137. package/dist/controls/filter-context.d.ts.map +1 -1
  138. package/dist/controls/filter-context.js +2 -0
  139. package/dist/controls/filter-pill.d.ts.map +1 -1
  140. package/dist/controls/filter-pill.js +1 -0
  141. package/dist/controls/index.d.ts +1 -1
  142. package/dist/controls/index.d.ts.map +1 -1
  143. package/dist/controls/index.js +0 -2
  144. package/dist/controls/multi-select-filter.js +2 -2
  145. package/dist/controls/sync-button.module.css +2 -1
  146. package/dist/dashboard/section.d.ts.map +1 -1
  147. package/dist/dashboard/section.js +1 -0
  148. package/dist/dashboard/section.module.css +1 -26
  149. package/dist/dashboard/stat-card.d.ts +1 -1
  150. package/dist/dashboard/stat-card.d.ts.map +1 -1
  151. package/dist/dashboard/stat-card.js +4 -1
  152. package/dist/dashboard/stat-card.module.css +6 -9
  153. package/dist/dashboard/stat-group.module.css +33 -66
  154. package/dist/dashboard/widget-grid.d.ts.map +1 -1
  155. package/dist/dashboard/widget-grid.module.css +11 -38
  156. package/dist/dashboard/widget-header.d.ts +14 -4
  157. package/dist/dashboard/widget-header.d.ts.map +1 -1
  158. package/dist/dashboard/widget-header.js +23 -6
  159. package/dist/dashboard/widget-header.module.css +145 -133
  160. package/dist/dashboard/widget-header.module.css.d.ts +2 -0
  161. package/dist/data/data-table-state.d.ts +83 -0
  162. package/dist/data/data-table-state.d.ts.map +1 -0
  163. package/dist/data/data-table-state.js +139 -0
  164. package/dist/data/data-table.d.ts +56 -2
  165. package/dist/data/data-table.d.ts.map +1 -1
  166. package/dist/data/data-table.js +436 -336
  167. package/dist/data/data-table.module.css +94 -0
  168. package/dist/data/data-table.module.css.d.ts +11 -0
  169. package/dist/data/virtual-list.d.ts.map +1 -1
  170. package/dist/data/virtual-list.js +10 -0
  171. package/dist/forms/form-layout.d.ts.map +1 -1
  172. package/dist/forms/form-layout.js +3 -3
  173. package/dist/forms/form-layout.module.css +20 -5
  174. package/dist/forms/form-layout.module.css.d.ts +1 -0
  175. package/dist/guard/index.d.ts +8 -2
  176. package/dist/guard/index.d.ts.map +1 -1
  177. package/dist/guard/index.js +68 -2
  178. package/dist/guard/types.d.ts +3 -3
  179. package/dist/guard/types.d.ts.map +1 -1
  180. package/dist/index.d.ts +1 -1
  181. package/dist/index.d.ts.map +1 -1
  182. package/dist/index.js +3 -1
  183. package/dist/notifications/center.d.ts.map +1 -1
  184. package/dist/provider/index.d.ts +20 -1
  185. package/dist/provider/index.d.ts.map +1 -1
  186. package/dist/provider/index.js +30 -8
  187. package/dist/shell/app-brand.d.ts.map +1 -1
  188. package/dist/shell/app-brand.js +1 -0
  189. package/dist/shell/app-brand.module.css +2 -2
  190. package/dist/shell/app-header.module.css +18 -8
  191. package/dist/shell/app-main.module.css +54 -3
  192. package/dist/shell/app-main.module.css.d.ts +1 -0
  193. package/dist/shell/app-mobile-nav.module.css +16 -4
  194. package/dist/shell/app-sidebar.module.css +4 -6
  195. package/dist/shell/aside-docking.d.ts +31 -0
  196. package/dist/shell/aside-docking.d.ts.map +1 -0
  197. package/dist/shell/aside-docking.js +19 -0
  198. package/dist/shell/index.d.ts +1 -0
  199. package/dist/shell/index.d.ts.map +1 -1
  200. package/dist/shell/index.js +56 -16
  201. package/dist/shell/mobile-nav-model.d.ts +1 -1
  202. package/dist/shell/page-aside.d.ts +22 -3
  203. package/dist/shell/page-aside.d.ts.map +1 -1
  204. package/dist/shell/page-aside.js +27 -13
  205. package/dist/shell/page-bar.d.ts.map +1 -1
  206. package/dist/shell/page-bar.js +10 -5
  207. package/dist/shell/page-bar.module.css +10 -34
  208. package/dist/shell/use-breakpoint.d.ts +3 -4
  209. package/dist/shell/use-breakpoint.d.ts.map +1 -1
  210. package/dist/shell/use-breakpoint.js +3 -25
  211. package/dist/shell/use-size-class.d.ts +6 -0
  212. package/dist/shell/use-size-class.d.ts.map +1 -0
  213. package/dist/shell/use-size-class.js +19 -0
  214. package/dist/state/persisted-or-local.d.ts +4 -1
  215. package/dist/state/persisted-or-local.d.ts.map +1 -1
  216. package/dist/state/persisted-or-local.js +4 -3
  217. package/dist/state/persisted.d.ts +10 -0
  218. package/dist/state/persisted.d.ts.map +1 -1
  219. package/dist/state/persisted.js +44 -14
  220. package/dist/styles.css +41 -0
  221. package/dist/surfaces.d.ts +4 -4
  222. package/dist/surfaces.d.ts.map +1 -1
  223. package/dist/surfaces.js +11 -3
  224. package/dist/theme/controls.module.css +7 -0
  225. package/dist/theme/ctl-theme.d.ts +1 -0
  226. package/dist/theme/ctl-theme.d.ts.map +1 -1
  227. package/dist/theme/ctl-theme.js +12 -4
  228. package/dist/theme/floating.module.css +37 -0
  229. package/dist/theme/floating.module.css.d.ts +3 -0
  230. package/dist/theme/index.d.ts +2 -2
  231. package/dist/theme/index.d.ts.map +1 -1
  232. package/dist/theme/index.js +34 -5
  233. package/dist/theme/nav-link.module.css +7 -0
  234. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  235. package/dist/tokens/index.d.ts +7 -16
  236. package/dist/tokens/index.d.ts.map +1 -1
  237. package/dist/tokens/index.js +23 -28
  238. package/dist/tokens/palette.d.ts +5 -27
  239. package/dist/tokens/palette.d.ts.map +1 -1
  240. package/dist/tokens/palette.js +8 -30
  241. package/dist/tokens/size-classes.d.ts +40 -0
  242. package/dist/tokens/size-classes.d.ts.map +1 -0
  243. package/dist/tokens/size-classes.js +29 -0
  244. package/dist/tokens.css +8 -4
  245. package/llms.txt +3 -3
  246. package/package.json +18 -17
  247. package/src/agent-chat/composer.tsx +74 -47
  248. package/src/agent-chat/thread-workspace.tsx +8 -11
  249. package/src/agent-chat/use-keyboard-inset.ts +68 -0
  250. package/src/charts/cursor/touch-pin.ts +138 -0
  251. package/src/charts/hooks/use-discrete-cursor.ts +335 -0
  252. package/src/charts/hooks/use-hysteresis.ts +26 -0
  253. package/src/charts/hooks/useBandPlot.ts +90 -27
  254. package/src/charts/hooks/useChartCursor.ts +211 -26
  255. package/src/charts/index.ts +0 -8
  256. package/src/charts/kinds/BandStrip.tsx +25 -61
  257. package/src/charts/kinds/Donut.tsx +153 -62
  258. package/src/charts/kinds/DualPanel.tsx +21 -26
  259. package/src/charts/kinds/Heatmap.tsx +61 -22
  260. package/src/charts/kinds/MirroredBars.tsx +23 -65
  261. package/src/charts/kinds/band-chrome.tsx +91 -0
  262. package/src/charts/kinds/donut-layout.ts +82 -0
  263. package/src/charts/layout/auto-margin.ts +24 -5
  264. package/src/charts/layout/end-labels.ts +135 -0
  265. package/src/charts/primitives/Axes.tsx +112 -21
  266. package/src/charts/primitives/CartesianChart.tsx +282 -111
  267. package/src/charts/primitives/ChartCard.tsx +119 -48
  268. package/src/charts/primitives/ChartFrame.tsx +204 -60
  269. package/src/charts/primitives/ChartLegend.tsx +115 -78
  270. package/src/charts/primitives/ChartTooltip.tsx +24 -3
  271. package/src/charts/primitives/Crosshair.tsx +2 -2
  272. package/src/charts/primitives/HoverOverlay.module.css +13 -0
  273. package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  274. package/src/charts/primitives/HoverOverlay.tsx +62 -20
  275. package/src/charts/primitives/chart-card-context.ts +46 -0
  276. package/src/charts/primitives/chart-frame-layout.ts +207 -182
  277. package/src/charts/primitives/chart-layout.ts +318 -0
  278. package/src/charts/primitives/chart-tier.tsx +66 -21
  279. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  280. package/src/charts/series.ts +4 -1
  281. package/src/charts/utils/ticks.ts +95 -19
  282. package/src/common/errors.ts +25 -1
  283. package/src/common/hit-floor.ts +9 -0
  284. package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
  285. package/src/common/use-measured-widths.ts +88 -0
  286. package/src/common/use-media-query.ts +56 -0
  287. package/src/common/validate.ts +17 -4
  288. package/src/content/article-layout.module.css +45 -4
  289. package/src/content/article-layout.module.css.d.ts +3 -0
  290. package/src/content/article-layout.tsx +60 -5
  291. package/src/content/code-block.module.css +32 -2
  292. package/src/content/code-block.tsx +47 -3
  293. package/src/content/prose.module.css +13 -8
  294. package/src/controls/actions.module.css +14 -1
  295. package/src/controls/actions.module.css.d.ts +1 -0
  296. package/src/controls/actions.tsx +223 -75
  297. package/src/controls/controls.module.css +13 -31
  298. package/src/controls/enum-filter.tsx +2 -2
  299. package/src/controls/filter-context.tsx +11 -0
  300. package/src/controls/filter-pill.tsx +1 -0
  301. package/src/controls/index.ts +0 -1
  302. package/src/controls/multi-select-filter.tsx +2 -2
  303. package/src/controls/sync-button.module.css +2 -1
  304. package/src/dashboard/section.module.css +1 -26
  305. package/src/dashboard/section.tsx +1 -0
  306. package/src/dashboard/stat-card.module.css +6 -9
  307. package/src/dashboard/stat-card.tsx +6 -3
  308. package/src/dashboard/stat-group.module.css +33 -66
  309. package/src/dashboard/widget-grid.module.css +11 -38
  310. package/src/dashboard/widget-grid.tsx +7 -6
  311. package/src/dashboard/widget-header.module.css +145 -133
  312. package/src/dashboard/widget-header.module.css.d.ts +2 -0
  313. package/src/dashboard/widget-header.tsx +45 -11
  314. package/src/data/data-table-state.ts +218 -0
  315. package/src/data/data-table.module.css +94 -0
  316. package/src/data/data-table.module.css.d.ts +11 -0
  317. package/src/data/data-table.tsx +547 -299
  318. package/src/data/virtual-list.tsx +31 -0
  319. package/src/forms/form-layout.module.css +20 -5
  320. package/src/forms/form-layout.module.css.d.ts +1 -0
  321. package/src/forms/form-layout.tsx +12 -10
  322. package/src/guard/index.ts +188 -3
  323. package/src/guard/types.ts +3 -2
  324. package/src/index.ts +2 -0
  325. package/src/notifications/center.tsx +3 -0
  326. package/src/provider/index.tsx +60 -7
  327. package/src/shell/app-brand.module.css +2 -2
  328. package/src/shell/app-brand.tsx +1 -0
  329. package/src/shell/app-header.module.css +18 -8
  330. package/src/shell/app-main.module.css +54 -3
  331. package/src/shell/app-main.module.css.d.ts +1 -0
  332. package/src/shell/app-mobile-nav.module.css +16 -4
  333. package/src/shell/app-sidebar.module.css +4 -6
  334. package/src/shell/aside-docking.ts +54 -0
  335. package/src/shell/index.tsx +178 -129
  336. package/src/shell/mobile-nav-model.ts +1 -1
  337. package/src/shell/page-aside.tsx +70 -21
  338. package/src/shell/page-bar.module.css +10 -34
  339. package/src/shell/page-bar.tsx +10 -5
  340. package/src/shell/use-breakpoint.ts +9 -45
  341. package/src/shell/use-size-class.ts +31 -0
  342. package/src/state/persisted-or-local.ts +10 -4
  343. package/src/state/persisted.ts +76 -17
  344. package/src/styles.css +41 -0
  345. package/src/surfaces.ts +9 -1
  346. package/src/theme/controls.module.css +7 -0
  347. package/src/theme/ctl-theme.tsx +15 -3
  348. package/src/theme/floating.module.css +37 -0
  349. package/src/theme/floating.module.css.d.ts +3 -0
  350. package/src/theme/index.ts +46 -8
  351. package/src/theme/nav-link.module.css +7 -0
  352. package/src/theme-lab/derive-controls.tsx +2 -0
  353. package/src/tokens/index.ts +20 -30
  354. package/src/tokens/palette.ts +6 -28
  355. package/src/tokens/size-classes.ts +46 -0
  356. package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
  357. /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
@@ -9,8 +9,13 @@
9
9
  * by name with rem fallbacks at their level-0 px value (36/28/6), same idiom as ChartCard's
10
10
  * `--mantine-spacing-*` fallbacks.
11
11
  *
12
- * Layout: the title row holds title/icon/info/count/actions only. `value` + `delta` render on
13
- * their own hero-metric row directly beneath it — for every tier, never inline with the title.
12
+ * Layout keys on the enclosing card's OWN width (`container: basalt-card`, declared by ChartCard /
13
+ * StatCard / Section — docs/DESIGN-CORE.md § Layout, elevation, shapes), never the viewport: below 480px the subtitle folds
14
+ * into the info glyph; from 480px (wave 8) `value` + `delta` move onto the title row. Wrapping and
15
+ * shrinking are unconditional. The subtitle ALSO folds (regardless of width) whenever the card's own
16
+ * height is under 280px — see the `[data-basalt-card-short]` block below (wave 8's header-economy
17
+ * law). No enclosing card, no container: the standard layout. Container conditions are px literals
18
+ * of CONTAINER_CLASSES.
14
19
  */
15
20
 
16
21
  .root {
@@ -18,73 +23,15 @@
18
23
  flex-direction: column;
19
24
  }
20
25
 
26
+ /* The row WRAPS only when it does not fit (a lone kebab stays beside the title; a wide `actions` slot
27
+ * takes its own right-aligned line instead of squeezing the heading) — unconditional, so composers
28
+ * with no `basalt-card` container (`SettingsSection`, `BasaltDataTable`) keep the safety net. */
21
29
  .titleRow {
22
30
  display: flex;
31
+ flex-wrap: wrap;
23
32
  align-items: center;
24
33
  gap: var(--vx-space-control-gap, 0.375rem);
25
- }
26
-
27
- /*
28
- * Below `sm` the title row WRAPS, which is the safety net for an `actions` slot too wide to sit
29
- * beside the title. `flex-wrap` is the right tool precisely because it is conditional: a 30px kebab
30
- * fits and stays on the row, while a control whose hypothetical size does not fit takes its own line
31
- * instead of squeezing the heading to an ellipsis. No breakpoint arithmetic, no max-width fraction,
32
- * and no prop — the row measures the actual content.
33
- *
34
- * `row-gap` is the 8px rhythm step, so a wrapped slot reads as the line under the title rather than
35
- * as a second header. `.actions` keeps its `margin-inline-start: auto`, which on a wrapped line makes
36
- * it right-aligned — the same edge it holds when it is on the row.
37
- */
38
- @media (max-width: 47.99375em) {
39
- .titleRow {
40
- flex-wrap: wrap;
41
- row-gap: var(--vx-space-stack-sm);
42
- }
43
-
44
- /*
45
- * WRAP ONLY WHEN IT DOES NOT FIT (M4 follow-up) — `flex: 1 1 100%` forced EVERY actions slot onto
46
- * its own row, even a single 24px icon that fit beside the title fine (a KPI card's lone `···`
47
- * menu dropped under the title and pushed the value down with it). `flex: 0 1 auto` restores
48
- * ordinary flex sizing: no grow, so a short slot stays put beside the title on the shared row;
49
- * `min-width: 0` plus `flex-shrink: 1` (part of the same shorthand, overriding the base rule's
50
- * `flex-shrink: 0`) let it shrink and, once `.titleRow`'s own `flex-wrap: wrap` decides it truly
51
- * does not fit, drop to its own line. `max-width: 100%` is the cap once it IS alone on that line —
52
- * without it a `.actions` still carrying unshrinkable content (the `visibleFrom="sm"` control plus
53
- * a nowrap switch `Group`, see below) can overflow the card rather than shrink into it. The base
54
- * rule's `margin-inline-start: auto` still applies in both states — right-aligned beside the title
55
- * when it fits, right-aligned as the lone item on its own wrapped line when it does not.
56
- *
57
- * `.actions` keeps its OWN `flex-wrap: wrap` from the prior fix: `Section` can hand it a `tabs`
58
- * control (`CtlSlot`-wrapped) AND an `actions` control (another `CtlSlot`) in the same slot, and
59
- * without this a nowrap `Group` of switches beside a view-switch control gets squeezed into one
60
- * shared line and overflows below its own min-content width — the exact regression this rule
61
- * once fixed. It is orthogonal to the row-level "wrap only when it does not fit" law above: with
62
- * one child, wrapping has nothing to do, so a lone `···` menu still stays on the title line.
63
- *
64
- * The real flex items get `flex: 1 1 auto; min-width: 0` so they can shrink or (via the `> * > *`
65
- * twin) stack within whatever width `.actions` itself resolves to — a full-width phone `Select`
66
- * still reads as its own full-width line because the CONTROL sets that itself (`ViewTabs`'
67
- * `fullWidth`/`.phoneSelect`), not because `.actions` forces it.
68
- *
69
- * The `> * > *` twin exists because `CtlSlot` (`theme/ctl-theme.tsx`) wraps a home's actions in a
70
- * `display: contents` box — invisible to the flex box tree, so its OWN children (not it) are the
71
- * real flex items. `display: contents` elements still match `.actions > *` by DOM structure but
72
- * carry no box to apply `flex`/`min-width` to, hence styling one level deeper as well; a call site
73
- * that skips `CtlSlot` and hands `actions` a real element directly gets the same rule from the
74
- * first selector.
75
- */
76
- .actions {
77
- flex: 0 1 auto;
78
- flex-wrap: wrap;
79
- min-width: 0;
80
- max-width: 100%;
81
- }
82
-
83
- .actions > *,
84
- .actions > * > * {
85
- flex: 1 1 auto;
86
- min-width: 0;
87
- }
34
+ row-gap: var(--vx-space-stack-sm);
88
35
  }
89
36
 
90
37
  .root[data-tier='section'] .titleRow {
@@ -203,59 +150,28 @@
203
150
  }
204
151
 
205
152
  /*
206
- * The hero-metric row — value + delta, directly under the title row, for every tier. The delta rides
207
- * 4px off the value, not 8: it is a suffix ON the number (`$168,920 +47%`), and at 8px it read as a
208
- * second, unrelated chip in the row. `baseline` alignment is what keeps the mono numeral and the
209
- * badge's own smaller mono text sitting on one line.
210
- */
211
- /*
212
- * The hero-metric row: the value, then its `DeltaBadge`.
213
- *
214
- * The gap is the 8px vertical-rhythm step (`--vx-space-stack-sm`), not the 4px it shipped as. 4px is
215
- * the rhythm's SMALLEST step — the distance between a label and the thing it labels — and at that
216
- * distance the badge read as a superscript hanging off the numeral rather than as a second fact
217
- * beside it. It is a token and not a literal so it tracks the density knob with every other spacing
218
- * in the header (`deriveSpacing`, `tokens/palette.ts`); `theme/layout-rhythm-css.test.ts`'s sibling
219
- * assertion in `widget-header.test.tsx` pins it against exactly the literal it used to be.
220
- *
221
- * `align-items: center`, not `baseline`. The badge is a BOX with its own padding and a mono 11.5px
222
- * label, so aligning its text baseline to a 24px KPI numeral's baseline put the box itself low —
223
- * its top edge sat below the numeral's x-height and its bottom edge hung under the numeral's
224
- * baseline. A chip beside a numeral is centred on the numeral, the same law the `count` tag in the
225
- * title row above already follows.
153
+ * The hero-metric row: the value, then its `DeltaBadge`, 8px apart (`--vx-space-stack-sm`, tracks
154
+ * density). It always WRAPS — the badge drops under the value when they do not fit, the value never
155
+ * ellipsizes. `center`, not `baseline`: the badge is a padded box, so a chip beside a numeral is
156
+ * centred on it (same law as the `count` tag above).
226
157
  */
227
158
  .metrics {
228
159
  display: flex;
229
- flex-wrap: nowrap;
160
+ flex-wrap: wrap;
230
161
  align-items: center;
231
162
  gap: var(--vx-space-stack-sm);
163
+ /* Tight when the badge wraps under the value. */
164
+ row-gap: 2px;
232
165
  margin-top: 2px;
233
166
  min-width: 0;
234
167
  }
235
168
 
236
- /*
237
- * Below `sm` the metric row WRAPS, so the badge drops under the value instead of ellipsizing it.
238
- * `nowrap` plus the `.value` clamp below is right on desktop — a `+18.9%` cannot ellipsize and read,
239
- * a numeral can — but a KPI's numeral IS the content, and in a 2-up card grid at 390px
240
- * `$1294.9k ▲18.9%` rendered as `$129… ▲18.9%`. Truncating the number to protect the delta about the
241
- * number is the wrong way round. Conditional like the title row above it: a short value keeps the
242
- * badge beside it, and only a value that would otherwise clip gives up the line.
243
- */
244
- @media (max-width: 47.99375em) {
245
- .metrics {
246
- flex-wrap: wrap;
247
- row-gap: 2px;
248
- }
249
- }
250
-
251
- /* The value is the row's shrinkable half and the delta badge is not: a pre-formatted numeral can
252
- * ellipsize and still read, a `+18.9%` cannot. Without the clamp the badge grew past the header box
253
- * and painted over `StatCard`'s 72px sparkline slot beside it. */
169
+ /* NEVER truncated: the numeral IS the content. It shrinks with the card's own width instead —
170
+ * `cqi` against `basalt-card` (the clamp, at the end of this file), from the `kpi` step down to the
171
+ * `lg` step (16px), and `overflow-wrap` breaks a pathological string rather than clipping it. */
254
172
  .value {
255
173
  min-width: 0;
256
- overflow: hidden;
257
- text-overflow: ellipsis;
258
- white-space: nowrap;
174
+ overflow-wrap: anywhere;
259
175
  font-family: var(--basalt-font-mono);
260
176
  font-size: var(--vx-text-kpi);
261
177
  font-weight: 600;
@@ -266,8 +182,8 @@
266
182
  /*
267
183
  * The unit beside the hero value. Mono like the number it qualifies (so `412 TSS` sits on one
268
184
  * baseline grid), `--vx-text-sm` and `--vx-muted` so it never competes with the numeral — the whole
269
- * point is that a unit is the SMALL half of the pair. `flex-shrink: 0`: the value is the row's
270
- * shrinkable half (see `.value`), and a unit that ellipsized to `TS…` would state nothing.
185
+ * point is that a unit is the SMALL half of the pair. `flex-shrink: 0`: a unit clipped to `TS…`
186
+ * would state nothing.
271
187
  */
272
188
  .unit {
273
189
  flex-shrink: 0;
@@ -278,14 +194,28 @@
278
194
  color: var(--vx-muted);
279
195
  }
280
196
 
281
- /* The C1/C5 home for header actions — always pinned right, regardless of what else is present. */
197
+ /* The C1/C5 home for header actions — always pinned right, regardless of what else is present.
198
+ * Law C15: adopts the shared hit overlay (`data-basalt-hit-scope`, `styles.css`); `--vx-hit-gap`
199
+ * caps the expansion at the control gap so neighbouring 24px icons never overlap. */
282
200
  .actions {
201
+ --vx-hit-gap: var(--vx-space-control-gap);
283
202
  display: inline-flex;
284
- flex-shrink: 0;
203
+ /* No grow, but it may shrink and wrap once `.titleRow` decides it does not fit, capped at 100% once
204
+ * alone on its line. Its flex items shrink too; the `> * > *` twin reaches through `CtlSlot`'s
205
+ * `display: contents` wrapper, whose own children are the real items. */
206
+ flex: 0 1 auto;
207
+ flex-wrap: wrap;
285
208
  align-items: center;
286
209
  gap: var(--vx-space-control-gap, 0.375rem);
287
210
  margin-inline-start: auto;
288
211
  min-width: 0;
212
+ max-width: 100%;
213
+ }
214
+
215
+ .actions > *,
216
+ .actions > * > * {
217
+ flex: 1 1 auto;
218
+ min-width: 0;
289
219
  }
290
220
 
291
221
  /* Directly under the title row, 4px off it — 1px put it inside the title's own line box, so a
@@ -306,6 +236,7 @@
306
236
  /* ── info glyph ─────────────────────────────────────────────────────────────────────────────── */
307
237
 
308
238
  .info {
239
+ --vx-hit-gap: var(--vx-space-control-gap);
309
240
  position: relative;
310
241
  display: inline-flex;
311
242
  flex-shrink: 0;
@@ -330,6 +261,12 @@
330
261
  color: var(--vx-muted);
331
262
  }
332
263
 
264
+ /* Folded-subtitle pieces: hidden until a `basalt-card` narrower than 480px shows them (below). */
265
+ .foldGlyph,
266
+ .foldNote {
267
+ display: none;
268
+ }
269
+
333
270
  /*
334
271
  * The tooltip bubble reads like every other floating surface — panel bg + `shadow-card` + card
335
272
  * radius (docs/DESIGN-SPEC.md §5). It opens DOWNWARD by convention, into whatever sits below the
@@ -383,33 +320,108 @@
383
320
  opacity: 0.6;
384
321
  }
385
322
 
323
+ /* Card narrower than 480px: the subtitle folds into the info glyph (`InfoGlyph`'s `note`, or
324
+ * `.foldGlyph` when there is no `info`) — exactly one of the two shows. */
325
+ @container basalt-card (max-width: 479.9px) {
326
+ .subtitle {
327
+ display: none;
328
+ }
329
+
330
+ .foldGlyph {
331
+ display: inline-flex;
332
+ }
333
+
334
+ .foldNote {
335
+ display: block;
336
+ }
337
+ }
338
+
386
339
  /*
387
- * Law C15 — the `actions` slot is the tier-2/tier-3 home, and its controls are 30px (`ctl`) or 24px
388
- * (`icon`). Below `sm` an `::after` overlay lifts the HIT area to
389
- * `--vx-space-touch-control-height` (36px at level 0) without moving the visible box, the same
390
- * overlay `shell/page-bar.module.css` draws for the bar. `overflow: visible` is load-bearing:
391
- * Mantine's Button/ActionIcon roots are `overflow: hidden`, which clips an overlay that by
392
- * definition extends past the box — the clipping only exists for their loading overlay, painted
393
- * under `[data-loading]`, which is excluded here.
340
+ * The same fold, triggered by the CARD'S OWN HEIGHT instead of its width (wave 8's header-economy
341
+ * law) — a short-but-wide chart card (a KPI-style plot with little vertical room) still needs the
342
+ * subtitle line back. Not a `@container basalt-card` SIZE query: `container-type: size` on an
343
+ * auto-height card collapses it to 0 (see `ChartCard`'s `CARD_SHORT_HEIGHT` doc), so `ChartCard`
344
+ * measures itself with a ResizeObserver instead and sets `data-basalt-card-short` under 280px.
394
345
  */
395
- @media (max-width: 47.99375em) {
396
- .actions :is(button, a) {
397
- position: relative;
346
+ [data-basalt-card-short] .subtitle {
347
+ display: none;
348
+ }
349
+
350
+ [data-basalt-card-short] .foldGlyph {
351
+ display: inline-flex;
352
+ }
353
+
354
+ [data-basalt-card-short] .foldNote {
355
+ display: block;
356
+ }
357
+
358
+ /* A short card's KPI numeral is the header's own tallest line (the merged title row has no other
359
+ * source of height once the subtitle folds) — the same floor the width `cqi` clamp already uses,
360
+ * so a short card never introduces a THIRD value size. */
361
+ [data-basalt-card-short] .value {
362
+ font-size: var(--vx-text-lg);
363
+ }
364
+
365
+ /* The KPI numeral shrinks with its card. `(width >= 0px)` is always true but only inside a
366
+ * `basalt-card` container, so `cqi` never resolves against an unrelated one (or the viewport) —
367
+ * with no card the numeral stays at the plain `kpi` step above. 8cqi reaches the 24px `kpi` step at
368
+ * a 300px card. */
369
+ @container basalt-card (width >= 0px) {
370
+ .value {
371
+ font-size: clamp(var(--vx-text-lg), 8cqi, var(--vx-text-kpi));
372
+ }
373
+ }
374
+
375
+ /*
376
+ * From 480px (`CONTAINER_CLASSES.regular`, wave 8's header-economy law) `value` + `delta` ride the
377
+ * title row: the title row dissolves (`display: contents`) so heading, info, count, metrics and
378
+ * actions share the root's one flex line, and `order` puts the metrics before the actions. The
379
+ * metrics take the free space (`margin-inline-start: auto`), which pushes metrics AND actions
380
+ * right; with no metrics, `.actions` keeps its own auto margin. The heading keeps the tier's row
381
+ * height (`min-height` was on `.titleRow`), so a title-less header does not collapse.
382
+ *
383
+ * This threshold moved from `wide` (800px) in wave 5/7 — most cards sit in the 480-799px `regular`
384
+ * band, which never reached the merged row before, keeping the taller stacked layout on the
385
+ * majority of dashboard cards (wave 8, `docs/waves/PLAN.md`).
386
+ */
387
+ @container basalt-card (min-width: 480px) {
388
+ .root {
389
+ flex-direction: row;
390
+ flex-wrap: wrap;
391
+ align-items: center;
392
+ column-gap: var(--vx-space-control-gap, 0.375rem);
393
+ }
394
+
395
+ .titleRow {
396
+ display: contents;
397
+ }
398
+
399
+ .root[data-tier='section'] .heading {
400
+ min-height: var(--vx-space-section-header-height, 2.25rem);
401
+ }
402
+
403
+ .root[data-tier='widget'] .heading,
404
+ .root[data-tier='group'] .heading {
405
+ min-height: var(--vx-space-widget-header-height, 1.75rem);
406
+ }
407
+
408
+ .metrics {
409
+ order: 1;
410
+ margin-block: 0;
411
+ margin-inline-start: auto;
412
+ }
413
+
414
+ .actions {
415
+ order: 2;
398
416
  }
399
417
 
400
- .actions :is(button, a):not([data-loading]) {
401
- overflow: visible;
418
+ .root:has(.metrics) .actions {
419
+ margin-inline-start: 0;
402
420
  }
403
421
 
404
- .actions :is(button, a)::after {
405
- content: '';
406
- position: absolute;
407
- top: 50%;
408
- left: 50%;
409
- translate: -50% -50%;
410
- width: 100%;
411
- height: 100%;
412
- min-width: var(--vx-space-touch-control-height, 2.25rem);
413
- min-height: var(--vx-space-touch-control-height, 2.25rem);
422
+ .subtitle,
423
+ .sparkline {
424
+ order: 3;
425
+ flex: 0 0 100%;
414
426
  }
415
427
  }
@@ -17,6 +17,8 @@ declare const classes: {
17
17
  readonly sparkline: string
18
18
  readonly info: string
19
19
  readonly infoTrigger: string
20
+ readonly foldGlyph: string
21
+ readonly foldNote: string
20
22
  readonly infoBubble: string
21
23
  readonly deltaBadge: string
22
24
  readonly deltaGlyph: string
@@ -0,0 +1,83 @@
1
+ /**
2
+ * `BasaltDataTable`'s state hooks — the table's own interactive state, the fold row disclosure, the
3
+ * card projection and the row/card activation props. Internal to `./data/table`; nothing here is on a
4
+ * published subpath.
5
+ */
6
+ import type { ColumnFiltersState, ColumnPinningState, PaginationState, RowSelectionState, SortingState, Updater } from '@tanstack/react-table';
7
+ import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
8
+ export declare const EMPTY_FOLD_SET: ReadonlySet<string>;
9
+ /**
10
+ * Which rows have their fold disclosure open. `row.id` defaults to the row's index in `data` (no
11
+ * `getRowId`) — stable across a client-side sort/filter/pagination, which reorder or subset the SAME
12
+ * core rows. PRUNED rather than cleared on every `data` change: a non-memoized `data` prop (an inline
13
+ * `.map()`, an unstable query result) hands a brand-new array of the SAME rows on every parent
14
+ * re-render, and clearing on that reference alone collapsed every open disclosure the instant
15
+ * anything upstream re-rendered.
16
+ *
17
+ * Pruning by ID ALONE is only safe with `getRowId` — a caller-declared identity that genuinely
18
+ * survives a page turn or a refetch, so an id no longer present (a manual-pagination page turn, a
19
+ * delete) still has to go, or a differently-shaped record seated at a reused id would silently read
20
+ * as pre-expanded. Without `getRowId` the id IS the array index, and an index survives a refetch
21
+ * trivially — it is still "0", "1", … even when every object behind it is a different record.
22
+ * Keeping an expanded index across THAT would silently attach the old disclosure to a stranger's
23
+ * row. So the index-id lane keeps a `prevData` snapshot and asks the narrower, correct question: is
24
+ * the object AT this index still the same reference as last time? A non-memoized array of the SAME
25
+ * rows answers yes at every index (the case above); a page turn or a refetch answers no.
26
+ */
27
+ export declare function useRowDisclosure<T>({ data, getRowId, }: {
28
+ data: readonly T[];
29
+ getRowId: ((row: T, index: number) => string) | undefined;
30
+ }): {
31
+ expanded: ReadonlySet<string>;
32
+ toggle: (rowId: string) => void;
33
+ };
34
+ /** The `BasaltDataTableProps` fields `useDataTableState` reads — declared here rather than picked
35
+ * off the props type, so the hook owns its contract and this module never imports the component. */
36
+ export type DataTableStateProps = {
37
+ initialSorting?: SortingState | undefined;
38
+ onSortingChange?: ((sorting: SortingState) => void) | undefined;
39
+ initialGlobalFilter?: string | undefined;
40
+ onGlobalFilterChange?: ((value: string) => void) | undefined;
41
+ onColumnFiltersChange?: ((filters: ColumnFiltersState) => void) | undefined;
42
+ initialPagination?: PaginationState | undefined;
43
+ onPaginationChange?: ((pagination: PaginationState) => void) | undefined;
44
+ initialColumnPinning?: ColumnPinningState | undefined;
45
+ rowSelection?: RowSelectionState | undefined;
46
+ onRowSelectionChange?: ((selection: RowSelectionState) => void) | undefined;
47
+ };
48
+ /** The table's own interactive state and the TanStack `on*Change` handlers that write it — every
49
+ * piece uncontrolled with an optional change callback, except row selection, which is controlled
50
+ * whenever `rowSelection` is passed. */
51
+ export declare function useDataTableState(props: DataTableStateProps, defaultPageSize: number): {
52
+ sorting: SortingState;
53
+ onSortingChange: (updater: Updater<SortingState>) => void;
54
+ globalFilter: string;
55
+ onGlobalFilterChange: (updater: Updater<string>) => void;
56
+ columnFilters: ColumnFiltersState;
57
+ onColumnFiltersChange: (updater: Updater<ColumnFiltersState>) => void;
58
+ pagination: PaginationState;
59
+ onPaginationChange: (updater: Updater<PaginationState>) => void;
60
+ columnPinning: ColumnPinningState;
61
+ onColumnPinningChange: import("react").Dispatch<import("react").SetStateAction<ColumnPinningState>>;
62
+ rowSelection: RowSelectionState;
63
+ onRowSelectionChange: (updater: Updater<RowSelectionState>) => void;
64
+ };
65
+ /** Whether the table renders as `renderCard` projections: its own root measured below the `regular`
66
+ * container class. A zero width is an un-laid-out ancestor, not a narrow table, so it moves nothing
67
+ * — the default (and the SSR answer) is the table. The root's width never depends on which body it
68
+ * holds, so the swap cannot oscillate. */
69
+ export declare function useCardProjection(enabled: boolean): {
70
+ rootRef: RefObject<HTMLDivElement | null>;
71
+ cards: boolean;
72
+ };
73
+ /** Click + Enter activation, shared by a row and its card. Enter only: Space is the browser's own
74
+ * page-scroll on a focused non-button, and stealing it from a keyboard reader moving down a long
75
+ * table costs more than the second activation key buys. */
76
+ export declare function activationProps<T>(onRowActivate: ((row: T) => void) | undefined, row: T): {
77
+ className: string;
78
+ 'data-activatable': boolean;
79
+ tabIndex: number;
80
+ onClick: () => void;
81
+ onKeyDown: (event: ReactKeyboardEvent<HTMLElement>) => void;
82
+ } | undefined;
83
+ //# sourceMappingURL=data-table-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-table-state.d.ts","sourceRoot":"","sources":["../../src/data/data-table-state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACR,MAAM,uBAAuB,CAAA;AAE9B,OAAO,KAAK,EAAE,aAAa,IAAI,kBAAkB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAM3E,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,MAAM,CAAa,CAAA;AAE5D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,SAAS,CAAC,EAAE,CAAA;IAClB,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS,CAAA;CAC1D,GAAG;IAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CA2BrE;AAuBD;oGACoG;AACpG,MAAM,MAAM,mBAAmB,GAAG;IAChC,cAAc,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;IACzC,eAAe,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC/D,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACxC,oBAAoB,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC5D,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IAC3E,iBAAiB,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;IAC/C,kBAAkB,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxE,oBAAoB,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACrD,YAAY,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAA;IAC5C,oBAAoB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CAC5E,CAAA;AAED;;wCAEwC;AACxC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM;;yDAjCrD,IAAI;;wDAAJ,IAAI;;qEAAJ,IAAI;;+DAAJ,IAAI;;;;oCA2DtB,OAAO,CAAC,iBAAiB,CAAC;EAgCvC;AAED;;;0CAG0C;AAC1C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IACnD,OAAO,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IACzC,KAAK,EAAE,OAAO,CAAA;CACf,CAcA;AAED;;2DAE2D;AAC3D,wBAAgB,eAAe,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,CAAC;;;;;uBAOjE,kBAAkB,CAAC,WAAW,CAAC;cAUrD"}
@@ -0,0 +1,139 @@
1
+ import { functionalUpdate } from "@tanstack/react-table";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { useMeasuredWidths } from "../common/use-measured-widths.js";
4
+ import { CONTAINER_CLASSES } from "../tokens/size-classes.js";
5
+ import classes from "./data-table.module.css";
6
+ const EMPTY_FOLD_SET = /* @__PURE__ */ new Set();
7
+ function useRowDisclosure({
8
+ data,
9
+ getRowId
10
+ }) {
11
+ const [expanded, setExpanded] = useState(EMPTY_FOLD_SET);
12
+ const toggle = useCallback((rowId) => {
13
+ setExpanded((current) => {
14
+ const next = new Set(current);
15
+ if (next.has(rowId)) next.delete(rowId);
16
+ else next.add(rowId);
17
+ return next;
18
+ });
19
+ }, []);
20
+ const prevDataRef = useRef(data);
21
+ useEffect(() => {
22
+ const prevData = prevDataRef.current;
23
+ prevDataRef.current = data;
24
+ setExpanded((current) => {
25
+ if (current.size === 0) return current;
26
+ const liveIds = getRowId && new Set(data.map((row, index) => getRowId(row, index)));
27
+ const isLive = (id) => {
28
+ if (liveIds) return liveIds.has(id);
29
+ const index = Number(id);
30
+ return data[index] !== void 0 && data[index] === prevData[index];
31
+ };
32
+ const next = new Set([...current].filter(isLive));
33
+ return next.size === current.size ? current : next;
34
+ });
35
+ }, [data, getRowId]);
36
+ return { expanded, toggle };
37
+ }
38
+ function useReportedState(initial, onChange) {
39
+ const [value, setValue] = useState(initial);
40
+ const update = useCallback(
41
+ (updater) => {
42
+ setValue((prev) => {
43
+ const next = functionalUpdate(updater, prev);
44
+ onChange?.(next);
45
+ return next;
46
+ });
47
+ },
48
+ [onChange]
49
+ );
50
+ return [value, update];
51
+ }
52
+ function useDataTableState(props, defaultPageSize) {
53
+ const { rowSelection, onRowSelectionChange } = props;
54
+ const [sorting, onSortingChange] = useReportedState(
55
+ props.initialSorting ?? [],
56
+ props.onSortingChange
57
+ );
58
+ const [globalFilter, onGlobalFilterChange] = useReportedState(
59
+ props.initialGlobalFilter ?? "",
60
+ props.onGlobalFilterChange
61
+ );
62
+ const [columnFilters, onColumnFiltersChange] = useReportedState(
63
+ [],
64
+ props.onColumnFiltersChange
65
+ );
66
+ const [pagination, onPaginationChange] = useReportedState(
67
+ props.initialPagination ?? { pageIndex: 0, pageSize: defaultPageSize },
68
+ props.onPaginationChange
69
+ );
70
+ const [columnPinning, setColumnPinning] = useState(
71
+ props.initialColumnPinning ?? {}
72
+ );
73
+ const [internalRowSelection, setInternalRowSelection] = useState({});
74
+ const handleRowSelectionChange = useCallback(
75
+ (updater) => {
76
+ if (rowSelection !== void 0) {
77
+ onRowSelectionChange?.(functionalUpdate(updater, rowSelection));
78
+ return;
79
+ }
80
+ setInternalRowSelection((prev) => {
81
+ const next = functionalUpdate(updater, prev);
82
+ onRowSelectionChange?.(next);
83
+ return next;
84
+ });
85
+ },
86
+ [rowSelection, onRowSelectionChange]
87
+ );
88
+ return {
89
+ sorting,
90
+ onSortingChange,
91
+ globalFilter,
92
+ onGlobalFilterChange,
93
+ columnFilters,
94
+ onColumnFiltersChange,
95
+ pagination,
96
+ onPaginationChange,
97
+ columnPinning,
98
+ onColumnPinningChange: setColumnPinning,
99
+ rowSelection: rowSelection ?? internalRowSelection,
100
+ onRowSelectionChange: handleRowSelectionChange
101
+ };
102
+ }
103
+ function useCardProjection(enabled) {
104
+ const rootRef = useRef(null);
105
+ const [narrow, setNarrow] = useState(false);
106
+ useMeasuredWidths({
107
+ resolveRoot: () => rootRef.current,
108
+ signature: "",
109
+ enabled,
110
+ onMeasure: (root) => {
111
+ if (root.clientWidth === 0) return;
112
+ const next = root.clientWidth < CONTAINER_CLASSES.regular;
113
+ setNarrow((current) => current === next ? current : next);
114
+ }
115
+ });
116
+ return { rootRef, cards: enabled && narrow };
117
+ }
118
+ function activationProps(onRowActivate, row) {
119
+ if (onRowActivate === void 0) return void 0;
120
+ return {
121
+ className: classes.activatable,
122
+ "data-activatable": true,
123
+ tabIndex: 0,
124
+ onClick: () => onRowActivate(row),
125
+ onKeyDown: (event) => {
126
+ if (event.key !== "Enter") return;
127
+ if (event.target !== event.currentTarget) return;
128
+ event.preventDefault();
129
+ onRowActivate(row);
130
+ }
131
+ };
132
+ }
133
+ export {
134
+ EMPTY_FOLD_SET,
135
+ activationProps,
136
+ useCardProjection,
137
+ useDataTableState,
138
+ useRowDisclosure
139
+ };