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
@@ -4,8 +4,10 @@ import {
4
4
  Checkbox,
5
5
  Group,
6
6
  Pagination,
7
+ ScrollArea,
7
8
  Select,
8
9
  Skeleton,
10
+ Stack,
9
11
  Table,
10
12
  Text,
11
13
  TextInput
@@ -18,9 +20,11 @@ import {
18
20
  getSortedRowModel,
19
21
  useReactTable
20
22
  } from "@tanstack/react-table";
21
- import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
23
+ import { Fragment, useLayoutEffect, useMemo, useRef, useState } from "react";
22
24
  import { cx } from "../common/props.js";
23
25
  import { BASALT_PREFIX } from "../common/errors.js";
26
+ import { HIT_COARSE } from "../common/hit-floor.js";
27
+ import { useMeasuredWidths } from "../common/use-measured-widths.js";
24
28
  import { assertRequiredProps, useValidateProps } from "../common/validate.js";
25
29
  import { FilterSet } from "../controls/index.js";
26
30
  import { ActionGroup, BarActionSlot } from "../controls/actions.js";
@@ -30,6 +34,13 @@ import { CtlSlot } from "../theme/index.js";
30
34
  import { alpha, VX } from "../tokens/index.js";
31
35
  import { WidgetHeader } from "../dashboard/index.js";
32
36
  import { ErrorState } from "../dashboard/query-state.js";
37
+ import {
38
+ activationProps,
39
+ EMPTY_FOLD_SET,
40
+ useCardProjection,
41
+ useDataTableState,
42
+ useRowDisclosure
43
+ } from "./data-table-state.js";
33
44
  import { dataQueryBranch } from "./query-branch.js";
34
45
  import { isDev } from "../common/is-dev.js";
35
46
  import classes from "./data-table.module.css";
@@ -106,26 +117,31 @@ function selectionColumn() {
106
117
  return {
107
118
  id: SELECT_COLUMN_ID,
108
119
  enableSorting: false,
109
- header: ({ table }) => /* @__PURE__ */ jsx(
110
- Checkbox,
111
- {
112
- size: "xs",
113
- "aria-label": "Select all rows",
114
- checked: table.getIsAllPageRowsSelected(),
115
- indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
116
- onChange: table.getToggleAllPageRowsSelectedHandler()
117
- }
120
+ header: ({ table }) => (
121
+ // oxlint-disable-next-line jsx-a11y/label-has-associated-control -- wraps <Checkbox>, which oxlint cannot see is an input
122
+ /* @__PURE__ */ jsx("label", { className: classes.selectHit, children: /* @__PURE__ */ jsx(
123
+ Checkbox,
124
+ {
125
+ size: "xs",
126
+ "aria-label": "Select all rows",
127
+ checked: table.getIsAllPageRowsSelected(),
128
+ indeterminate: table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected(),
129
+ onChange: table.getToggleAllPageRowsSelectedHandler()
130
+ }
131
+ ) })
118
132
  ),
119
- cell: ({ row }) => /* @__PURE__ */ jsx(
120
- Checkbox,
121
- {
122
- size: "xs",
123
- "aria-label": `Select row ${row.index + 1}`,
124
- checked: row.getIsSelected(),
125
- disabled: !row.getCanSelect(),
126
- onChange: row.getToggleSelectedHandler(),
127
- onClick: (event) => event.stopPropagation()
128
- }
133
+ cell: ({ row }) => (
134
+ // oxlint-disable-next-line jsx-a11y/label-has-associated-control -- wraps <Checkbox>, which oxlint cannot see is an input
135
+ /* @__PURE__ */ jsx("label", { className: classes.selectHit, children: /* @__PURE__ */ jsx(
136
+ Checkbox,
137
+ {
138
+ size: "xs",
139
+ "aria-label": `Select row ${row.index + 1}`,
140
+ checked: row.getIsSelected(),
141
+ disabled: !row.getCanSelect(),
142
+ onChange: row.getToggleSelectedHandler()
143
+ }
144
+ ) })
129
145
  )
130
146
  };
131
147
  }
@@ -142,6 +158,7 @@ function SortIndicator({ direction }) {
142
158
  if (!direction) return null;
143
159
  return /* @__PURE__ */ jsx(Box, { component: "span", ml: 4, "aria-hidden": true, children: direction === "asc" ? "\u2191" : "\u2193" });
144
160
  }
161
+ const ARIA_SORT = { asc: "ascending", desc: "descending" };
145
162
  const NUMERIC_CELL_STYLE = {
146
163
  fontFamily: "var(--mantine-font-family-monospace)",
147
164
  fontSize: VX.text.sm,
@@ -201,6 +218,117 @@ function getPinnedCellStyle(table, column) {
201
218
  boxShadow: isEdge ? `-4px 0 6px -4px ${alpha(VX.ink, 0.18)}` : void 0
202
219
  };
203
220
  }
221
+ function columnDefId(column) {
222
+ if (column.id !== void 0) return column.id;
223
+ return "accessorKey" in column && typeof column.accessorKey === "string" ? column.accessorKey : void 0;
224
+ }
225
+ function foldOrder(columns) {
226
+ return columns.map((column, index) => ({
227
+ id: columnDefId(column),
228
+ priority: column.meta?.priority ?? index,
229
+ index
230
+ })).filter(
231
+ (candidate) => Boolean(candidate.id)
232
+ ).toSorted((a, b) => b.priority - a.priority || b.index - a.index).map((candidate) => candidate.id);
233
+ }
234
+ const FOLD_ID_ATTR = "data-basalt-fold-id";
235
+ function planColumnFold(input) {
236
+ const { room, toggleWidth, overhead, order, widths } = input;
237
+ const known = order.filter((id) => widths.has(id));
238
+ let unfolded = overhead + known.reduce((sum, id) => sum + (widths.get(id) ?? 0), 0);
239
+ const folded = /* @__PURE__ */ new Set();
240
+ for (const id of known) {
241
+ if (folded.size >= known.length - 1) break;
242
+ const withToggle = unfolded + (folded.size > 0 ? toggleWidth : 0);
243
+ if (withToggle <= room) break;
244
+ folded.add(id);
245
+ unfolded -= widths.get(id) ?? 0;
246
+ }
247
+ return folded;
248
+ }
249
+ function useColumnFold(input) {
250
+ const { active, order, fixedIds } = input;
251
+ const wrapperRef = useRef(null);
252
+ const [folded, setFolded] = useState(EMPTY_FOLD_SET);
253
+ const orderRef = useRef(order);
254
+ orderRef.current = order;
255
+ const fixedIdsRef = useRef(fixedIds);
256
+ fixedIdsRef.current = fixedIds;
257
+ useLayoutEffect(() => {
258
+ if (!active || order.length === 0) {
259
+ setFolded((current) => current.size === 0 ? current : EMPTY_FOLD_SET);
260
+ }
261
+ }, [active, order.length]);
262
+ useMeasuredWidths({
263
+ resolveRoot: () => wrapperRef.current,
264
+ signature: `${order.join("|")}::${fixedIds.join("|")}`,
265
+ enabled: active && order.length > 0,
266
+ // The table too, not only the wrapper: a re-measured column set moves its min-content width
267
+ // while the wrapper's own box never changes.
268
+ extra: (root) => [root.querySelector("table")],
269
+ onMeasure: (root, boxes) => {
270
+ for (const cell of root.querySelectorAll(`[${FOLD_ID_ATTR}]`)) {
271
+ const id = cell.getAttribute(FOLD_ID_ATTR);
272
+ if (id !== null && cell.offsetWidth > 0) {
273
+ boxes.current.set(id, { width: cell.offsetWidth, height: cell.offsetHeight });
274
+ }
275
+ }
276
+ const room = root.clientWidth;
277
+ if (room === 0) return;
278
+ const widths = new Map([...boxes.current].map(([id, box]) => [id, box.width]));
279
+ const overhead = fixedIdsRef.current.reduce((sum, id) => sum + (widths.get(id) ?? 0), 0);
280
+ const next = planColumnFold({
281
+ room,
282
+ // The disclosure toggle is a fixed icon-button cell matched to the coarse hit floor, so it
283
+ // reads as one more `--vx-hit` control rather than a narrower afterthought.
284
+ toggleWidth: HIT_COARSE,
285
+ overhead,
286
+ order: orderRef.current,
287
+ widths
288
+ });
289
+ setFolded(
290
+ (current) => current.size === next.size && [...current].every((id) => next.has(id)) ? current : next
291
+ );
292
+ }
293
+ });
294
+ return { wrapperRef, folded: active ? folded : EMPTY_FOLD_SET };
295
+ }
296
+ function columnLabel(column) {
297
+ const header = column.columnDef.header;
298
+ return typeof header === "string" ? header : column.id;
299
+ }
300
+ function RowDisclosure({
301
+ row,
302
+ folded
303
+ }) {
304
+ const cells = row.getAllCells().filter((cell) => folded.has(cell.column.id));
305
+ return /* @__PURE__ */ jsx("div", { className: classes.foldDisclosure, children: cells.map((cell) => /* @__PURE__ */ jsxs("div", { className: classes.foldDisclosureRow, children: [
306
+ /* @__PURE__ */ jsx(Text, { className: classes.foldDisclosureLabel, children: columnLabel(cell.column) }),
307
+ /* @__PURE__ */ jsx("div", { className: classes.foldDisclosureValue, children: flexRender(cell.column.columnDef.cell, cell.getContext()) })
308
+ ] }, cell.id)) });
309
+ }
310
+ function FoldToggle({
311
+ expanded,
312
+ controlsId,
313
+ onToggle
314
+ }) {
315
+ return /* @__PURE__ */ jsx(
316
+ "button",
317
+ {
318
+ type: "button",
319
+ className: classes.foldToggle,
320
+ "data-basalt-hit": true,
321
+ "aria-expanded": expanded,
322
+ "aria-controls": controlsId,
323
+ "aria-label": expanded ? "Hide row details" : "Show row details",
324
+ onClick: (event) => {
325
+ event.stopPropagation();
326
+ onToggle();
327
+ },
328
+ children: /* @__PURE__ */ jsx(Box, { component: "span", "aria-hidden": true, children: expanded ? "\u25BE" : "\u25B8" })
329
+ }
330
+ );
331
+ }
204
332
  const BREACH_REMEDY = {
205
333
  inert: "`enablePagination` is not set, so `manualPagination` never reaches the table and the server page renders as if it were the whole set, with no bar to say otherwise. Pass `enablePagination`, or drop `manualPagination`.",
206
334
  total: 'neither `rowCount` nor `pageCount` was given, so "of N" counts only the rows on this page and the pager collapses to one page nobody can leave. Pass `rowCount={total}`.',
@@ -233,35 +361,21 @@ function enforceManualPaginationContract(breaches) {
233
361
  function useMeasuredContainment(active) {
234
362
  const wrapperRef = useRef(null);
235
363
  const [contained, setContained] = useState(false);
236
- useLayoutEffect(() => {
237
- if (!active) return;
238
- const wrapper = wrapperRef.current;
239
- if (wrapper === null) return;
240
- const checkFit = () => {
241
- const table2 = wrapper.querySelector("table");
242
- if (table2 === null) return "unknown";
243
- if (wrapper.clientWidth === 0 || table2.offsetWidth === 0) return "unknown";
244
- return table2.offsetWidth <= wrapper.clientWidth ? "fits" : "contained";
245
- };
246
- const apply = () => {
247
- const result = checkFit();
248
- if (result === "unknown") return;
249
- const next = result === "contained";
364
+ useMeasuredWidths({
365
+ resolveRoot: () => wrapperRef.current,
366
+ signature: String(active),
367
+ enabled: active,
368
+ // The TABLE too, not only the wrapper: a column set that re-measures (a facet filter changing
369
+ // the widest cell, a font settling) moves the table's min-content width while the wrapper's
370
+ // box never changes at all.
371
+ extra: (root) => [root.querySelector("table")],
372
+ onMeasure: (root) => {
373
+ const table = root.querySelector("table");
374
+ if (table === null || root.clientWidth === 0 || table.offsetWidth === 0) return;
375
+ const next = table.offsetWidth > root.clientWidth;
250
376
  setContained((current) => current === next ? current : next);
251
- };
252
- if (typeof ResizeObserver === "undefined") {
253
- apply();
254
- return;
255
377
  }
256
- const observer = new ResizeObserver(apply);
257
- observer.observe(wrapper);
258
- const table = wrapper.querySelector("table");
259
- if (table !== null) observer.observe(table);
260
- apply();
261
- return () => {
262
- observer.disconnect();
263
- };
264
- }, [active]);
378
+ });
265
379
  return { wrapperRef, contained: active && contained };
266
380
  }
267
381
  function BasaltDataTable(props) {
@@ -280,32 +394,23 @@ function BasaltDataTable(props) {
280
394
  isLoading = false,
281
395
  query,
282
396
  skeletonRows = 5,
283
- initialSorting,
284
- onSortingChange,
285
397
  enableGlobalFilter = false,
286
398
  globalFilterPlaceholder = "Search\u2026",
287
399
  searchIcon,
288
- initialGlobalFilter,
289
- onGlobalFilterChange,
290
400
  facets,
291
- onColumnFiltersChange,
292
401
  manualFiltering = false,
293
402
  actions,
294
403
  onRowActivate,
404
+ renderCard,
295
405
  enableRowSelection = false,
296
- rowSelection,
297
- onRowSelectionChange,
298
406
  getRowId,
299
407
  bulkActions,
300
408
  enablePagination = false,
301
409
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
302
- initialPagination,
303
- onPaginationChange,
304
410
  manualPagination = false,
305
411
  rowCount,
306
412
  pageCount,
307
413
  enablePinning = false,
308
- initialColumnPinning,
309
414
  maxHeight,
310
415
  minWidth,
311
416
  stickyHeader,
@@ -318,15 +423,8 @@ function BasaltDataTable(props) {
318
423
  style,
319
424
  classNames
320
425
  } = props;
321
- const [sorting, setSorting] = useState(initialSorting ?? []);
322
- const [globalFilter, setGlobalFilter] = useState(initialGlobalFilter ?? "");
323
- const [columnFilters, setColumnFilters] = useState([]);
324
- const [pagination, setPagination] = useState(
325
- initialPagination ?? { pageIndex: 0, pageSize: pageSizeOptions[0] ?? 10 }
326
- );
327
- const [columnPinning, setColumnPinning] = useState(initialColumnPinning ?? {});
328
- const [internalRowSelection, setInternalRowSelection] = useState({});
329
- const rowSelectionState = rowSelection ?? internalRowSelection;
426
+ const tableState = useDataTableState(props, pageSizeOptions[0] ?? 10);
427
+ const { globalFilter, columnPinning, onGlobalFilterChange: handleGlobalFilterChange } = tableState;
330
428
  useValidateProps(
331
429
  "BasaltDataTable",
332
430
  () => [
@@ -343,65 +441,11 @@ function BasaltDataTable(props) {
343
441
  props.isLoading === void 0
344
442
  ]
345
443
  );
346
- const handleSortingChange = useCallback(
347
- (updater) => {
348
- setSorting((prev) => {
349
- const next = typeof updater === "function" ? updater(prev) : updater;
350
- onSortingChange?.(next);
351
- return next;
352
- });
353
- },
354
- [onSortingChange]
355
- );
356
- const handleGlobalFilterChange = useCallback(
357
- (updater) => {
358
- setGlobalFilter((prev) => {
359
- const next = typeof updater === "function" ? updater(prev) : updater;
360
- onGlobalFilterChange?.(next);
361
- return next;
362
- });
363
- },
364
- [onGlobalFilterChange]
365
- );
366
- const handleColumnFiltersChange = useCallback(
367
- (updater) => {
368
- setColumnFilters((prev) => {
369
- const next = typeof updater === "function" ? updater(prev) : updater;
370
- onColumnFiltersChange?.(next);
371
- return next;
372
- });
373
- },
374
- [onColumnFiltersChange]
375
- );
376
- const handlePaginationChange = useCallback(
377
- (updater) => {
378
- setPagination((prev) => {
379
- const next = typeof updater === "function" ? updater(prev) : updater;
380
- onPaginationChange?.(next);
381
- return next;
382
- });
383
- },
384
- [onPaginationChange]
385
- );
386
- const handleRowSelectionChange = useCallback(
387
- (updater) => {
388
- if (rowSelection !== void 0) {
389
- onRowSelectionChange?.(typeof updater === "function" ? updater(rowSelection) : updater);
390
- return;
391
- }
392
- setInternalRowSelection((prev) => {
393
- const next = typeof updater === "function" ? updater(prev) : updater;
394
- onRowSelectionChange?.(next);
395
- return next;
396
- });
397
- },
398
- [rowSelection, onRowSelectionChange]
399
- );
400
444
  const facetColumns = useMemo(() => {
401
445
  if (!facets || facets.length === 0) return columns;
402
446
  const facetById = new Map(facets.map((facet) => [facet.columnId, facet]));
403
447
  return columns.map((column) => {
404
- const id = column.id ?? column.accessorKey;
448
+ const id = columnDefId(column);
405
449
  const facet = id === void 0 ? void 0 : facetById.get(id);
406
450
  if (!facet) return column;
407
451
  const filterFn = facet.multiple ? (row, columnId, filterValue) => !Array.isArray(filterValue) || filterValue.length === 0 ? true : filterValue.includes(String(row.getValue(columnId))) : (row, columnId, filterValue) => filterValue === null || filterValue === void 0 || filterValue === "" ? true : String(row.getValue(columnId)) === filterValue;
@@ -430,25 +474,54 @@ function BasaltDataTable(props) {
430
474
  const sortingEnabled = enableSorting && !breaches.includes("sorting");
431
475
  const filteringEnabled = !breaches.includes("filtering");
432
476
  const totalIsAuthoritative = !breaches.includes("total");
477
+ const scrolls = maxHeight !== void 0 || minWidth !== void 0;
478
+ const foldEligible = minWidth === void 0;
479
+ const { columnFoldOrder, columnFoldFixedIds } = useMemo(() => {
480
+ const pinnedIds = /* @__PURE__ */ new Set([...columnPinning.left ?? [], ...columnPinning.right ?? []]);
481
+ const excluded = new Set(
482
+ columns.flatMap((column) => {
483
+ const id = columnDefId(column);
484
+ if (id === void 0) return [];
485
+ return column.enableHiding === false || pinnedIds.has(id) ? [id] : [];
486
+ })
487
+ );
488
+ return {
489
+ columnFoldOrder: foldOrder(columns).filter((id) => !excluded.has(id)),
490
+ columnFoldFixedIds: [...excluded]
491
+ };
492
+ }, [columns, columnPinning]);
493
+ const projection = useCardProjection(renderCard !== void 0);
494
+ const columnFold = useColumnFold({
495
+ active: foldEligible && !projection.cards,
496
+ order: columnFoldOrder,
497
+ fixedIds: columnFoldFixedIds
498
+ });
499
+ const columnVisibility = useMemo(
500
+ () => Object.fromEntries([...columnFold.folded].map((id) => [id, false])),
501
+ [columnFold.folded]
502
+ );
503
+ const hasFolded = columnFold.folded.size > 0;
504
+ const foldRows = useRowDisclosure({ data, getRowId });
433
505
  const table = useReactTable({
434
506
  data,
435
507
  columns: tableColumns,
436
508
  state: {
437
- sorting,
509
+ sorting: tableState.sorting,
438
510
  globalFilter,
439
- columnFilters,
511
+ columnFilters: tableState.columnFilters,
440
512
  columnPinning,
441
- ...enablePagination && { pagination },
442
- ...enableRowSelection && { rowSelection: rowSelectionState }
513
+ columnVisibility,
514
+ ...enablePagination && { pagination: tableState.pagination },
515
+ ...enableRowSelection && { rowSelection: tableState.rowSelection }
443
516
  },
444
- onSortingChange: handleSortingChange,
517
+ onSortingChange: tableState.onSortingChange,
445
518
  onGlobalFilterChange: handleGlobalFilterChange,
446
- onColumnFiltersChange: handleColumnFiltersChange,
447
- onColumnPinningChange: setColumnPinning,
448
- ...enablePagination && { onPaginationChange: handlePaginationChange },
519
+ onColumnFiltersChange: tableState.onColumnFiltersChange,
520
+ onColumnPinningChange: tableState.onColumnPinningChange,
521
+ ...enablePagination && { onPaginationChange: tableState.onPaginationChange },
449
522
  ...enableRowSelection && {
450
523
  enableRowSelection: true,
451
- onRowSelectionChange: handleRowSelectionChange
524
+ onRowSelectionChange: tableState.onRowSelectionChange
452
525
  },
453
526
  ...getRowId !== void 0 && { getRowId },
454
527
  getCoreRowModel: getCoreRowModel(),
@@ -476,7 +549,7 @@ function BasaltDataTable(props) {
476
549
  const branch = query === void 0 ? void 0 : dataQueryBranch("BasaltDataTable", query);
477
550
  const showSkeleton = branch === void 0 ? isLoading : branch === "pending";
478
551
  const queryError = branch === "error" && query !== void 0 ? query : void 0;
479
- const columnCount = columns.length + (enableRowSelection ? 1 : 0);
552
+ const columnCount = columns.length + (enableRowSelection ? 1 : 0) + (hasFolded ? 1 : 0) - columnFold.folded.size;
480
553
  const selectedModelRows = enableRowSelection ? table.getSelectedRowModel().rows : void 0;
481
554
  const selectedRows = useMemo(
482
555
  () => selectedModelRows?.map((row) => row.original) ?? [],
@@ -488,10 +561,20 @@ function BasaltDataTable(props) {
488
561
  );
489
562
  const headerGroups = enablePinning ? getOrderedHeaderGroups(table) : table.getHeaderGroups();
490
563
  const rows = table.getRowModel().rows;
491
- const scrolls = maxHeight !== void 0 || minWidth !== void 0;
492
564
  const pageStickyHeader = !scrolls && stickyHeader === true;
493
- const containment = useMeasuredContainment(pageStickyHeader);
565
+ const containment = useMeasuredContainment(pageStickyHeader && !projection.cards);
494
566
  const resolvedStickyHeaderOffset = scrolls ? void 0 : stickyHeaderOffset;
567
+ const errorNode = queryError !== void 0 && /* @__PURE__ */ jsx(
568
+ ErrorState,
569
+ {
570
+ error: queryError.error,
571
+ title: "Could not load",
572
+ tier: "section",
573
+ retrying: queryError.fetchStatus === "fetching",
574
+ onRetry: () => void queryError.refetch()
575
+ }
576
+ );
577
+ const emptyNode = emptyState ?? /* @__PURE__ */ jsx(Text, { c: "dimmed", ta: "center", size: "sm", py: "sm", children: "No data to display." });
495
578
  const tableNode = /* @__PURE__ */ jsxs(
496
579
  Table,
497
580
  {
@@ -507,228 +590,245 @@ function BasaltDataTable(props) {
507
590
  ...withRowBorders !== void 0 && { withRowBorders },
508
591
  withTableBorder,
509
592
  children: [
510
- /* @__PURE__ */ jsx(Table.Thead, { children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(Table.Tr, { children: headerGroup.headers.map((header) => {
511
- const canSort = sortingEnabled && header.column.getCanSort();
512
- const sorted = header.column.getIsSorted();
513
- const toggleSorting = header.column.getToggleSortingHandler();
514
- const pinnedStyle = enablePinning ? getPinnedCellStyle(table, header.column) : void 0;
515
- const align = resolveAlign(header.column);
516
- return /* @__PURE__ */ jsxs(
517
- Table.Th,
518
- {
519
- onClick: canSort ? toggleSorting : void 0,
520
- onKeyDown: canSort ? (event) => {
521
- if (event.key !== "Enter" && event.key !== " ") return;
522
- event.preventDefault();
523
- toggleSorting?.(event);
524
- } : void 0,
525
- tabIndex: canSort ? 0 : void 0,
526
- "aria-sort": canSort ? sorted === "asc" ? "ascending" : sorted === "desc" ? "descending" : "none" : void 0,
527
- style: {
528
- ...canSort ? { cursor: "pointer", userSelect: "none" } : void 0,
529
- ...align !== void 0 && { textAlign: align },
530
- ...pinnedStyle
593
+ /* @__PURE__ */ jsx(Table.Thead, { children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
594
+ hasFolded && /* @__PURE__ */ jsx(Table.Th, { className: classes.foldToggleCell, "aria-hidden": true }),
595
+ headerGroup.headers.map((header) => {
596
+ const canSort = sortingEnabled && header.column.getCanSort();
597
+ const sorted = header.column.getIsSorted();
598
+ const toggleSorting = header.column.getToggleSortingHandler();
599
+ const pinnedStyle = enablePinning ? getPinnedCellStyle(table, header.column) : void 0;
600
+ const align = resolveAlign(header.column);
601
+ const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
602
+ return /* @__PURE__ */ jsxs(
603
+ Table.Th,
604
+ {
605
+ ...isSelectColumn && { className: classes.selectCell },
606
+ ...!isSelectColumn && { [FOLD_ID_ATTR]: header.column.id },
607
+ onClick: canSort ? toggleSorting : void 0,
608
+ onKeyDown: canSort ? (event) => {
609
+ if (event.key !== "Enter" && event.key !== " ") return;
610
+ event.preventDefault();
611
+ toggleSorting?.(event);
612
+ } : void 0,
613
+ tabIndex: canSort ? 0 : void 0,
614
+ "aria-sort": canSort ? sorted ? ARIA_SORT[sorted] : "none" : void 0,
615
+ style: {
616
+ ...canSort ? { cursor: "pointer", userSelect: "none" } : void 0,
617
+ ...align !== void 0 && { textAlign: align },
618
+ ...pinnedStyle
619
+ },
620
+ children: [
621
+ header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()),
622
+ canSort && /* @__PURE__ */ jsx(SortIndicator, { direction: sorted })
623
+ ]
531
624
  },
532
- children: [
533
- header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()),
534
- canSort && /* @__PURE__ */ jsx(SortIndicator, { direction: sorted })
535
- ]
536
- },
537
- header.id
538
- );
539
- }) }, headerGroup.id)) }),
540
- /* @__PURE__ */ jsx(Table.Tbody, { children: queryError !== void 0 ? (
541
- // The branch the table did not have (components audit #3). It spans every rendered
542
- // column and carries the query's OWN `refetch` behind Retry, so the failure is reported
543
- // where the rows would have been — not as an empty table saying nothing failed.
544
- /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: /* @__PURE__ */ jsx(
545
- ErrorState,
546
- {
547
- error: queryError.error,
548
- title: "Could not load",
549
- tier: "section",
550
- retrying: queryError.fetchStatus === "fetching",
551
- onRetry: () => void queryError.refetch()
552
- }
553
- ) }) })
554
- ) : showSkeleton ? Array.from({ length: skeletonRows }, (_, rowIndex) => /* @__PURE__ */ jsx(Table.Tr, { children: Array.from({ length: columnCount }, (__, colIndex) => /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Skeleton, { height: 16, radius: "sm" }) }, `skeleton-${rowIndex}-${colIndex}`)) }, `skeleton-${rowIndex}`)) : rows.length === 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: emptyState ?? /* @__PURE__ */ jsx(Text, { c: "dimmed", ta: "center", size: "sm", py: "sm", children: "No data to display." }) }) }) : rows.map((row) => {
625
+ header.id
626
+ );
627
+ })
628
+ ] }, headerGroup.id)) }),
629
+ /* @__PURE__ */ jsx(Table.Tbody, { children: queryError !== void 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: errorNode }) }) : showSkeleton ? Array.from({ length: skeletonRows }, (_, rowIndex) => /* @__PURE__ */ jsx(Table.Tr, { children: Array.from({ length: columnCount }, (__, colIndex) => /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Skeleton, { height: 16, radius: "sm" }) }, `skeleton-${rowIndex}-${colIndex}`)) }, `skeleton-${rowIndex}`)) : rows.length === 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: emptyNode }) }) : rows.map((row) => {
555
630
  const cells = enablePinning ? [
556
631
  ...row.getLeftVisibleCells(),
557
632
  ...row.getCenterVisibleCells(),
558
633
  ...row.getRightVisibleCells()
559
634
  ] : row.getVisibleCells();
560
- return /* @__PURE__ */ jsx(
561
- Table.Tr,
562
- {
563
- ...enableRowSelection && row.getIsSelected() && { "data-selected": true },
564
- ...onRowActivate !== void 0 && {
565
- className: classes.activatable,
566
- "data-activatable": true,
567
- tabIndex: 0,
568
- onClick: () => onRowActivate(row.original),
569
- // Enter only. Space is the browser's own page-scroll on a focused non-button, and
570
- // stealing it from a keyboard reader moving down a long table costs more than the
571
- // second activation key buys.
572
- onKeyDown: (event) => {
573
- if (event.key !== "Enter") return;
574
- if (event.target !== event.currentTarget) return;
575
- event.preventDefault();
576
- onRowActivate(row.original);
577
- }
578
- },
579
- children: cells.map((cell) => {
580
- const pinnedStyle = enablePinning ? getPinnedCellStyle(table, cell.column) : void 0;
581
- const align = resolveAlign(cell.column);
582
- return /* @__PURE__ */ jsx(
583
- Table.Td,
584
- {
585
- style: {
586
- ...typeof cell.getValue() === "number" && cell.column.columnDef.meta?.numeral !== false ? NUMERIC_CELL_STYLE : void 0,
587
- ...align !== void 0 && { textAlign: align },
588
- ...pinnedStyle
589
- },
590
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
591
- },
592
- cell.id
593
- );
594
- })
595
- },
596
- row.id
597
- );
635
+ const rowExpanded = hasFolded && foldRows.expanded.has(row.id);
636
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
637
+ /* @__PURE__ */ jsxs(
638
+ Table.Tr,
639
+ {
640
+ ...enableRowSelection && row.getIsSelected() && { "data-selected": true },
641
+ ...activationProps(onRowActivate, row.original),
642
+ children: [
643
+ hasFolded && /* @__PURE__ */ jsx(Table.Td, { className: classes.foldToggleCell, children: /* @__PURE__ */ jsx(
644
+ FoldToggle,
645
+ {
646
+ expanded: rowExpanded,
647
+ controlsId: `${row.id}-fold`,
648
+ onToggle: () => foldRows.toggle(row.id)
649
+ }
650
+ ) }),
651
+ cells.map((cell) => {
652
+ const pinnedStyle = enablePinning ? getPinnedCellStyle(table, cell.column) : void 0;
653
+ const align = resolveAlign(cell.column);
654
+ return /* @__PURE__ */ jsx(
655
+ Table.Td,
656
+ {
657
+ ...cell.column.id === SELECT_COLUMN_ID && {
658
+ className: classes.selectCell,
659
+ // The label's ::after fills the cell, so a tap on it bubbles to the row —
660
+ // stop it only where a tick is possible; a disabled row's tap is a row tap.
661
+ ...cell.row.getCanSelect() && {
662
+ onClick: (event) => event.stopPropagation()
663
+ }
664
+ },
665
+ style: {
666
+ ...typeof cell.getValue() === "number" && cell.column.columnDef.meta?.numeral !== false ? NUMERIC_CELL_STYLE : void 0,
667
+ ...align !== void 0 && { textAlign: align },
668
+ ...pinnedStyle
669
+ },
670
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
671
+ },
672
+ cell.id
673
+ );
674
+ })
675
+ ]
676
+ }
677
+ ),
678
+ rowExpanded && /* @__PURE__ */ jsx(Table.Tr, { id: `${row.id}-fold`, className: classes.foldDisclosureRowTr, children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, className: classes.foldDisclosureCell, children: /* @__PURE__ */ jsx(RowDisclosure, { row, folded: columnFold.folded }) }) })
679
+ ] }, row.id);
598
680
  }) })
599
681
  ]
600
682
  }
601
683
  );
684
+ const renderCardList = () => /* @__PURE__ */ jsx(Stack, { gap: "xs", className: cx(classNames?.table), children: queryError !== void 0 ? errorNode : showSkeleton ? Array.from({ length: skeletonRows }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { height: 48, radius: "md" }, `skeleton-${index}`)) : rows.length === 0 ? emptyNode : rows.map((row) => /* @__PURE__ */ jsx(Box, { ...activationProps(onRowActivate, row.original), children: renderCard?.(row.original) }, row.id)) });
602
685
  const paginationState = table.getState().pagination;
603
686
  const pageSizeRows = [.../* @__PURE__ */ new Set([...pageSizeOptions, paginationState.pageSize])].sort((a, b) => a - b).map((size) => ({ value: String(size), label: `${size} / page` }));
604
687
  const total = table.getRowCount();
605
688
  const rangeStart = total === 0 ? 0 : paginationState.pageIndex * paginationState.pageSize + 1;
606
689
  const rangeEnd = Math.min((paginationState.pageIndex + 1) * paginationState.pageSize, total);
607
- return /* @__PURE__ */ jsxs("div", { className: cx(classNames?.root, className), ...style !== void 0 && { style }, children: [
608
- (title !== void 0 || showToolbar) && /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", wrap: "wrap", gap: "xs", mb: "xs", children: [
609
- title !== void 0 ? /* @__PURE__ */ jsx(Box, { style: TABLE_HEADER_LEAD_STYLE, children: /* @__PURE__ */ jsx(
610
- WidgetHeader,
611
- {
612
- tier: "widget",
613
- title,
614
- ...icon !== void 0 && { icon },
615
- ...subtitle !== void 0 && { subtitle },
616
- count: table.getRowCount()
617
- }
618
- ) }) : null,
619
- showToolbar && /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsxs(
620
- Group,
621
- {
622
- className: cx(classNames?.toolbar),
623
- gap: "xs",
624
- wrap: "wrap",
625
- align: "center",
626
- style: TABLE_TOOLBAR_STYLE,
627
- children: [
628
- showSearch && /* @__PURE__ */ jsx(
629
- TextInput,
630
- {
631
- radius: "md",
632
- style: TABLE_SEARCH_STYLE,
633
- placeholder: globalFilterPlaceholder,
634
- leftSection: searchIcon,
635
- value: globalFilter,
636
- onChange: (event) => handleGlobalFilterChange(event.currentTarget.value)
637
- }
638
- ),
639
- showFacets && /* @__PURE__ */ jsx(FilterSet, { children: facets?.map((facet) => {
640
- const column = resolveFacetColumn(table, facet);
641
- if (!column) return null;
642
- return facet.multiple ? /* @__PURE__ */ jsx(
643
- MultiSelectFilter,
644
- {
645
- field: facetMultiHandle(column, facet),
646
- label: facet.label
647
- },
648
- facet.columnId
649
- ) : /* @__PURE__ */ jsx(
650
- EnumFilter,
651
- {
652
- field: facetEnumHandle(column, facet),
653
- label: facet.label
654
- },
655
- facet.columnId
656
- );
657
- }) }),
658
- actions !== void 0 && /* @__PURE__ */ jsx(BarActionSlot, { actions })
659
- ]
660
- }
661
- ) })
662
- ] }),
663
- bulkBarActions !== void 0 && /* @__PURE__ */ jsx(BulkActionBar, { count: selectedRows.length, actions: bulkBarActions }),
664
- pageStickyHeader ? (
665
- // Pinning takes this wrapper too — a pinned column's offsets are `position: sticky` on the
666
- // cells and need no overflow box of their own, so the `overflow-x: auto` Box that used to
667
- // sit here was the exact inert-sticky defect this shape exists to avoid.
668
- /* @__PURE__ */ jsx(
669
- "div",
670
- {
671
- ref: containment.wrapperRef,
672
- className: classes.containment,
673
- "data-contained": containment.contained ? "true" : "false",
674
- children: tableNode
675
- }
676
- )
677
- ) : /* @__PURE__ */ jsx(
678
- Table.ScrollContainer,
679
- {
680
- type: "native",
681
- minWidth: minWidth ?? 0,
682
- ...maxHeight !== void 0 && { maxHeight },
683
- children: tableNode
684
- }
685
- ),
686
- enablePagination && /* @__PURE__ */ jsxs(
687
- Group,
688
- {
689
- className: cx(classNames?.footer),
690
- justify: "space-between",
691
- mt: "xs",
692
- wrap: "wrap",
693
- gap: "xs",
694
- align: "center",
695
- children: [
696
- /* @__PURE__ */ jsxs(Text, { style: RANGE_LABEL_STYLE, children: [
697
- "Showing ",
698
- rangeStart,
699
- "\u2013",
700
- rangeEnd,
701
- totalIsAuthoritative ? ` of ${total}` : ""
702
- ] }),
703
- /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "center", children: [
704
- /* @__PURE__ */ jsx(
705
- Select,
706
- {
707
- size: "ctl",
708
- radius: "md",
709
- w: PAGE_SIZE_WIDTH,
710
- data: pageSizeRows,
711
- value: String(paginationState.pageSize),
712
- onChange: (value) => value && table.setPageSize(Number(value)),
713
- allowDeselect: false
714
- }
715
- ),
690
+ return /* @__PURE__ */ jsxs(
691
+ "div",
692
+ {
693
+ ref: projection.rootRef,
694
+ className: cx(classNames?.root, className),
695
+ ...style !== void 0 && { style },
696
+ children: [
697
+ (title !== void 0 || showToolbar) && /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", wrap: "wrap", gap: "xs", mb: "xs", children: [
698
+ title !== void 0 ? /* @__PURE__ */ jsx(Box, { style: TABLE_HEADER_LEAD_STYLE, children: /* @__PURE__ */ jsx(
699
+ WidgetHeader,
700
+ {
701
+ tier: "widget",
702
+ title,
703
+ ...icon !== void 0 && { icon },
704
+ ...subtitle !== void 0 && { subtitle },
705
+ count: table.getRowCount()
706
+ }
707
+ ) }) : null,
708
+ showToolbar && /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsxs(
709
+ Group,
710
+ {
711
+ className: cx(classNames?.toolbar),
712
+ gap: "xs",
713
+ wrap: "wrap",
714
+ align: "center",
715
+ style: TABLE_TOOLBAR_STYLE,
716
+ children: [
717
+ showSearch && /* @__PURE__ */ jsx(
718
+ TextInput,
719
+ {
720
+ radius: "md",
721
+ style: TABLE_SEARCH_STYLE,
722
+ placeholder: globalFilterPlaceholder,
723
+ leftSection: searchIcon,
724
+ value: globalFilter,
725
+ onChange: (event) => handleGlobalFilterChange(event.currentTarget.value)
726
+ }
727
+ ),
728
+ showFacets && /* @__PURE__ */ jsx(FilterSet, { children: facets?.map((facet) => {
729
+ const column = resolveFacetColumn(table, facet);
730
+ if (!column) return null;
731
+ return facet.multiple ? /* @__PURE__ */ jsx(
732
+ MultiSelectFilter,
733
+ {
734
+ field: facetMultiHandle(column, facet),
735
+ label: facet.label
736
+ },
737
+ facet.columnId
738
+ ) : /* @__PURE__ */ jsx(
739
+ EnumFilter,
740
+ {
741
+ field: facetEnumHandle(column, facet),
742
+ label: facet.label
743
+ },
744
+ facet.columnId
745
+ );
746
+ }) }),
747
+ actions !== void 0 && /* @__PURE__ */ jsx(BarActionSlot, { actions })
748
+ ]
749
+ }
750
+ ) })
751
+ ] }),
752
+ bulkBarActions !== void 0 && /* @__PURE__ */ jsx(BulkActionBar, { count: selectedRows.length, actions: bulkBarActions }),
753
+ projection.cards ? maxHeight === void 0 ? renderCardList() : /* @__PURE__ */ jsx(ScrollArea.Autosize, { mah: maxHeight, type: "hover", scrollbars: "y", children: renderCardList() }) : !foldEligible ? (
754
+ // An explicit `minWidth` is the one genuinely DECLARED horizontal floor — the column fold
755
+ // never engages here (see `foldEligible`, above), so this shape keeps its exact
756
+ // pre-existing DOM: no extra measuring wrapper for a table nothing folds.
757
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { type: "native", minWidth: minWidth ?? 0, maxHeight, children: tableNode })
758
+ ) : (
759
+ // The column fold's own measuring box (`useColumnFold`) — wraps every remaining shape,
760
+ // `maxHeight`-only included: a vertical cap is orthogonal to column width, so a
761
+ // `stickyHeader` + `maxHeight` table (the pairing this package's own docs recommend) still
762
+ // folds instead of silently regressing to horizontal scroll. A plain block box, so it
763
+ // constrains nothing on its own; `columnFold.wrapperRef` only reads it.
764
+ /* @__PURE__ */ jsx("div", { ref: columnFold.wrapperRef, className: classes.foldWrapper, children: pageStickyHeader ? (
765
+ // Pinning takes this wrapper too — a pinned column's offsets are `position: sticky` on
766
+ // the cells and need no overflow box of their own, so the `overflow-x: auto` Box that
767
+ // used to sit here was the exact inert-sticky defect this shape exists to avoid.
716
768
  /* @__PURE__ */ jsx(
717
- Pagination,
769
+ "div",
718
770
  {
719
- size: "sm",
720
- radius: "md",
721
- total: Math.max(table.getPageCount(), 1),
722
- value: paginationState.pageIndex + 1,
723
- onChange: (page) => table.setPageIndex(page - 1)
771
+ ref: containment.wrapperRef,
772
+ className: classes.containment,
773
+ "data-contained": containment.contained ? "true" : "false",
774
+ children: tableNode
724
775
  }
725
776
  )
726
- ] })
727
- ]
728
- }
729
- )
730
- ] });
777
+ ) : (
778
+ // `pageStickyHeader` is false whenever `maxHeight` is set (`scrolls` above), so a vertical
779
+ // cap lands here — its own `ScrollArea` scrollport, just with no `minWidth` floor.
780
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { type: "native", minWidth: 0, maxHeight, children: tableNode })
781
+ ) })
782
+ ),
783
+ enablePagination && /* @__PURE__ */ jsxs(
784
+ Group,
785
+ {
786
+ className: cx(classNames?.footer),
787
+ justify: "space-between",
788
+ mt: "xs",
789
+ wrap: "wrap",
790
+ gap: "xs",
791
+ align: "center",
792
+ children: [
793
+ /* @__PURE__ */ jsxs(Text, { style: RANGE_LABEL_STYLE, children: [
794
+ "Showing ",
795
+ rangeStart,
796
+ "\u2013",
797
+ rangeEnd,
798
+ totalIsAuthoritative ? ` of ${total}` : ""
799
+ ] }),
800
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "center", children: [
801
+ /* @__PURE__ */ jsx(
802
+ Select,
803
+ {
804
+ size: "ctl",
805
+ radius: "md",
806
+ w: PAGE_SIZE_WIDTH,
807
+ data: pageSizeRows,
808
+ value: String(paginationState.pageSize),
809
+ onChange: (value) => value && table.setPageSize(Number(value)),
810
+ allowDeselect: false
811
+ }
812
+ ),
813
+ /* @__PURE__ */ jsx(CtlSlot, { children: /* @__PURE__ */ jsx(
814
+ Pagination,
815
+ {
816
+ size: "sm",
817
+ radius: "md",
818
+ total: Math.max(table.getPageCount(), 1),
819
+ value: paginationState.pageIndex + 1,
820
+ onChange: (page) => table.setPageIndex(page - 1)
821
+ }
822
+ ) })
823
+ ] })
824
+ ]
825
+ }
826
+ )
827
+ ]
828
+ }
829
+ );
731
830
  }
732
831
  export {
733
- BasaltDataTable
832
+ BasaltDataTable,
833
+ planColumnFold
734
834
  };