@eifi1/ui-kit 0.7.1 → 0.8.1

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 (222) hide show
  1. package/README.md +32 -17
  2. package/dist/chart.d.ts +3 -2
  3. package/dist/components/alert-banner.d.ts +59 -10
  4. package/dist/components/alert-banner.js +114 -8
  5. package/dist/components/alert-banner.js.map +1 -1
  6. package/dist/components/amount-input.d.ts +3 -0
  7. package/dist/components/button-group.d.ts +26 -0
  8. package/dist/components/button-group.js +49 -0
  9. package/dist/components/button-group.js.map +1 -0
  10. package/dist/components/calculator.d.ts +3 -0
  11. package/dist/components/chart-zoom.d.ts +63 -14
  12. package/dist/components/chart-zoom.js +82 -16
  13. package/dist/components/chart-zoom.js.map +1 -1
  14. package/dist/components/chip.d.ts +67 -2
  15. package/dist/components/chip.js +73 -8
  16. package/dist/components/chip.js.map +1 -1
  17. package/dist/components/confirm-dialog.d.ts +120 -0
  18. package/dist/components/confirm-dialog.js +96 -0
  19. package/dist/components/confirm-dialog.js.map +1 -0
  20. package/dist/components/copy-button.d.ts +62 -0
  21. package/dist/components/copy-button.js +95 -0
  22. package/dist/components/copy-button.js.map +1 -0
  23. package/dist/components/data-table-filter-popover.d.ts +1 -1
  24. package/dist/components/data-table-filters.d.ts +1 -1
  25. package/dist/components/data-table-pagination.d.ts +5 -1
  26. package/dist/components/data-table-pagination.js +71 -58
  27. package/dist/components/data-table-pagination.js.map +1 -1
  28. package/dist/components/data-table-sort.d.ts +28 -5
  29. package/dist/components/data-table-sort.js +11 -7
  30. package/dist/components/data-table-sort.js.map +1 -1
  31. package/dist/components/data-table.d.ts +1 -1
  32. package/dist/components/data-table.js +544 -423
  33. package/dist/components/data-table.js.map +1 -1
  34. package/dist/components/date-picker.d.ts +51 -5
  35. package/dist/components/date-picker.js +152 -40
  36. package/dist/components/date-picker.js.map +1 -1
  37. package/dist/components/description-list.d.ts +60 -0
  38. package/dist/components/description-list.js +112 -0
  39. package/dist/components/description-list.js.map +1 -0
  40. package/dist/components/disclosure.d.ts +37 -2
  41. package/dist/components/disclosure.js +14 -5
  42. package/dist/components/disclosure.js.map +1 -1
  43. package/dist/components/facing-pair.d.ts +2 -0
  44. package/dist/components/file-button.d.ts +17 -1
  45. package/dist/components/file-button.js +8 -1
  46. package/dist/components/file-button.js.map +1 -1
  47. package/dist/components/file-dropzone.d.ts +54 -9
  48. package/dist/components/file-dropzone.js +100 -70
  49. package/dist/components/file-dropzone.js.map +1 -1
  50. package/dist/components/floating-panel.d.ts +107 -0
  51. package/dist/components/floating-panel.js +206 -0
  52. package/dist/components/floating-panel.js.map +1 -0
  53. package/dist/components/full-bleed-dialog.d.ts +12 -1
  54. package/dist/components/full-bleed-dialog.js +28 -5
  55. package/dist/components/full-bleed-dialog.js.map +1 -1
  56. package/dist/components/modal.d.ts +6 -2
  57. package/dist/components/modal.js +2 -1
  58. package/dist/components/modal.js.map +1 -1
  59. package/dist/components/number-field.d.ts +3 -0
  60. package/dist/components/number-input.d.ts +3 -0
  61. package/dist/components/numpad-sheet.d.ts +3 -0
  62. package/dist/components/progress-bar.d.ts +54 -0
  63. package/dist/components/progress-bar.js +94 -0
  64. package/dist/components/progress-bar.js.map +1 -0
  65. package/dist/components/scroll-area.d.ts +53 -0
  66. package/dist/components/scroll-area.js +86 -0
  67. package/dist/components/scroll-area.js.map +1 -0
  68. package/dist/components/separator.d.ts +23 -0
  69. package/dist/components/separator.js +24 -0
  70. package/dist/components/separator.js.map +1 -0
  71. package/dist/components/series-chart-ticks.d.ts +34 -1
  72. package/dist/components/series-chart-ticks.js +83 -1
  73. package/dist/components/series-chart-ticks.js.map +1 -1
  74. package/dist/components/series-chart.d.ts +314 -19
  75. package/dist/components/series-chart.js +510 -120
  76. package/dist/components/series-chart.js.map +1 -1
  77. package/dist/components/skeleton.d.ts +36 -0
  78. package/dist/components/skeleton.js +35 -0
  79. package/dist/components/skeleton.js.map +1 -0
  80. package/dist/components/stat-tile.d.ts +60 -6
  81. package/dist/components/stat-tile.js +60 -26
  82. package/dist/components/stat-tile.js.map +1 -1
  83. package/dist/components/table.d.ts +83 -0
  84. package/dist/components/table.js +166 -0
  85. package/dist/components/table.js.map +1 -0
  86. package/dist/components/toggle-legend.d.ts +3 -2
  87. package/dist/components/toggle-legend.js +1 -1
  88. package/dist/components/toggle-legend.js.map +1 -1
  89. package/dist/components/tree-view.d.ts +129 -0
  90. package/dist/components/tree-view.js +376 -0
  91. package/dist/components/tree-view.js.map +1 -0
  92. package/dist/components/treemap.d.ts +9 -2
  93. package/dist/components/treemap.js +6 -2
  94. package/dist/components/treemap.js.map +1 -1
  95. package/dist/components/ui.d.ts +94 -13
  96. package/dist/components/ui.js +111 -29
  97. package/dist/components/ui.js.map +1 -1
  98. package/dist/components/use-table-state.d.ts +1 -1
  99. package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
  100. package/dist/data-table.d.ts +2 -2
  101. package/dist/data-table.js.map +1 -1
  102. package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
  103. package/dist/hooks/use-copy-to-clipboard.js +81 -0
  104. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  105. package/dist/hooks/use-debounce.d.ts +43 -0
  106. package/dist/hooks/use-debounce.js +56 -0
  107. package/dist/hooks/use-debounce.js.map +1 -0
  108. package/dist/hooks/use-file-drop.d.ts +72 -0
  109. package/dist/hooks/use-file-drop.js +57 -0
  110. package/dist/hooks/use-file-drop.js.map +1 -0
  111. package/dist/i18n/defaults.d.ts +3 -0
  112. package/dist/i18n/defaults.js +7 -1
  113. package/dist/i18n/defaults.js.map +1 -1
  114. package/dist/i18n/kit-labels.d.ts +15 -0
  115. package/dist/i18n/kit-labels.js +6 -2
  116. package/dist/i18n/kit-labels.js.map +1 -1
  117. package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
  118. package/dist/i18n/locales/de-CH-informal.js +8 -0
  119. package/dist/i18n/locales/de-CH-informal.js.map +1 -0
  120. package/dist/i18n/locales/de-CH.d.ts +3 -0
  121. package/dist/i18n/locales/de-CH.js +1 -14
  122. package/dist/i18n/locales/de-CH.js.map +1 -1
  123. package/dist/i18n/locales/de-informal.d.ts +64 -0
  124. package/dist/i18n/locales/de-informal.js +37 -0
  125. package/dist/i18n/locales/de-informal.js.map +1 -0
  126. package/dist/i18n/locales/de.d.ts +3 -0
  127. package/dist/i18n/locales/de.js +25 -0
  128. package/dist/i18n/locales/de.js.map +1 -1
  129. package/dist/i18n/locales/es.d.ts +3 -0
  130. package/dist/i18n/locales/es.js +25 -0
  131. package/dist/i18n/locales/es.js.map +1 -1
  132. package/dist/i18n/locales/fr.d.ts +3 -0
  133. package/dist/i18n/locales/fr.js +25 -0
  134. package/dist/i18n/locales/fr.js.map +1 -1
  135. package/dist/i18n/locales/hu.d.ts +3 -0
  136. package/dist/i18n/locales/hu.js +25 -0
  137. package/dist/i18n/locales/hu.js.map +1 -1
  138. package/dist/i18n/locales/it.d.ts +3 -0
  139. package/dist/i18n/locales/it.js +25 -0
  140. package/dist/i18n/locales/it.js.map +1 -1
  141. package/dist/i18n/locales/zh.d.ts +3 -0
  142. package/dist/i18n/locales/zh.js +25 -0
  143. package/dist/i18n/locales/zh.js.map +1 -1
  144. package/dist/i18n/swiss.d.ts +13 -0
  145. package/dist/i18n/swiss.js +19 -0
  146. package/dist/i18n/swiss.js.map +1 -0
  147. package/dist/index.d.ts +25 -10
  148. package/dist/index.js +22 -0
  149. package/dist/index.js.map +1 -1
  150. package/dist/lib/dates.d.ts +37 -1
  151. package/dist/lib/dates.js +19 -0
  152. package/dist/lib/dates.js.map +1 -1
  153. package/dist/search/command-palette.d.ts +15 -1
  154. package/dist/search/command-palette.js +10 -2
  155. package/dist/search/command-palette.js.map +1 -1
  156. package/dist/wizard/stepper-nav.d.ts +35 -2
  157. package/dist/wizard/stepper-nav.js +54 -22
  158. package/dist/wizard/stepper-nav.js.map +1 -1
  159. package/dist/wizard/types.d.ts +107 -5
  160. package/dist/wizard/types.js +2 -1
  161. package/dist/wizard/types.js.map +1 -1
  162. package/dist/wizard/use-wizard.js +141 -21
  163. package/dist/wizard/use-wizard.js.map +1 -1
  164. package/dist/wizard/wizard-summary.d.ts +10 -2
  165. package/dist/wizard/wizard-summary.js +3 -1
  166. package/dist/wizard/wizard-summary.js.map +1 -1
  167. package/dist/wizard.d.ts +4 -1
  168. package/dist/wizard.js.map +1 -1
  169. package/package.json +1 -1
  170. package/src/components/alert-banner.tsx +211 -20
  171. package/src/components/button-group.tsx +75 -0
  172. package/src/components/chart-zoom.tsx +158 -20
  173. package/src/components/chip.tsx +160 -9
  174. package/src/components/confirm-dialog.tsx +242 -0
  175. package/src/components/copy-button.tsx +158 -0
  176. package/src/components/data-table-pagination.tsx +19 -6
  177. package/src/components/data-table-sort.ts +49 -10
  178. package/src/components/data-table.tsx +293 -24
  179. package/src/components/date-picker.tsx +262 -57
  180. package/src/components/description-list.tsx +174 -0
  181. package/src/components/disclosure.tsx +58 -5
  182. package/src/components/file-button.tsx +22 -2
  183. package/src/components/file-dropzone.tsx +198 -100
  184. package/src/components/floating-panel.tsx +341 -0
  185. package/src/components/full-bleed-dialog.tsx +53 -5
  186. package/src/components/modal.tsx +9 -2
  187. package/src/components/progress-bar.tsx +163 -0
  188. package/src/components/scroll-area.tsx +129 -0
  189. package/src/components/separator.tsx +37 -0
  190. package/src/components/series-chart-ticks.ts +135 -0
  191. package/src/components/series-chart.tsx +934 -67
  192. package/src/components/skeleton.tsx +65 -0
  193. package/src/components/stat-tile.tsx +160 -27
  194. package/src/components/table.tsx +263 -0
  195. package/src/components/toggle-legend.tsx +4 -3
  196. package/src/components/tree-view.tsx +589 -0
  197. package/src/components/treemap.tsx +13 -2
  198. package/src/components/ui.tsx +270 -39
  199. package/src/data-table.ts +2 -0
  200. package/src/hooks/use-copy-to-clipboard.ts +122 -0
  201. package/src/hooks/use-debounce.ts +105 -0
  202. package/src/hooks/use-file-drop.ts +123 -0
  203. package/src/i18n/defaults.ts +6 -0
  204. package/src/i18n/kit-labels.tsx +19 -0
  205. package/src/i18n/locales/de-CH-informal.ts +10 -0
  206. package/src/i18n/locales/de-CH.ts +1 -16
  207. package/src/i18n/locales/de-informal.ts +61 -0
  208. package/src/i18n/locales/de.ts +25 -0
  209. package/src/i18n/locales/es.ts +25 -0
  210. package/src/i18n/locales/fr.ts +25 -0
  211. package/src/i18n/locales/hu.ts +25 -0
  212. package/src/i18n/locales/it.ts +25 -0
  213. package/src/i18n/locales/zh.ts +25 -0
  214. package/src/i18n/swiss.ts +25 -0
  215. package/src/index.ts +29 -0
  216. package/src/lib/dates.ts +48 -0
  217. package/src/search/command-palette.tsx +29 -3
  218. package/src/wizard/stepper-nav.tsx +102 -28
  219. package/src/wizard/types.ts +106 -4
  220. package/src/wizard/use-wizard.ts +211 -31
  221. package/src/wizard/wizard-summary.tsx +24 -12
  222. package/src/wizard.ts +3 -2
@@ -61,6 +61,25 @@ function cleanAttrs(attrs) {
61
61
  function columnLabel(col) {
62
62
  return typeof col.header === "string" ? col.header : col.key;
63
63
  }
64
+ const OWN_CONTROL = 'a[href],button,input,select,textarea,label,summary,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="menuitem"],[role="option"],[contenteditable=""],[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
65
+ function fromOwnControl(target, row) {
66
+ if (!(target instanceof Element)) return false;
67
+ const hit = target.closest(OWN_CONTROL);
68
+ return !!hit && hit !== row && row.contains(hit) && !hit.hasAttribute("data-row-link");
69
+ }
70
+ function moveRowFocus(from, key) {
71
+ if (!(key in ROW_NAV_KEYS)) return false;
72
+ const body = from.parentElement;
73
+ if (!body) return false;
74
+ const rows = Array.from(body.children).filter(
75
+ (el) => el instanceof HTMLElement && el.hasAttribute("data-row-nav")
76
+ );
77
+ const i = rows.indexOf(from);
78
+ const target = key === "ArrowDown" ? rows[i + 1] : key === "ArrowUp" ? rows[i - 1] : key === "Home" ? rows[0] : key === "End" ? rows[rows.length - 1] : void 0;
79
+ target?.focus();
80
+ return true;
81
+ }
82
+ const ROW_NAV_KEYS = { ArrowDown: 1, ArrowUp: 1, Home: 1, End: 1 };
64
83
  const isPlainLeftClick = (e) => e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;
65
84
  function RowLink({
66
85
  href,
@@ -123,8 +142,21 @@ function DataTable({
123
142
  mobileGroupLabel,
124
143
  mobileCard,
125
144
  mobileSwipeActions,
126
- leadingRow
145
+ leadingRow,
146
+ density = "comfortable",
147
+ sortCycle = "tri",
148
+ chrome = "full",
149
+ columnSettings: columnSettingsProp,
150
+ resizable: resizableProp,
151
+ multiSort: multiSortProp,
152
+ frame = true,
153
+ className
127
154
  }) {
155
+ const compact = density === "compact";
156
+ const minimal = chrome === "minimal";
157
+ const columnSettings = columnSettingsProp ?? !minimal;
158
+ const resizable = resizableProp ?? !minimal;
159
+ const multiSort = multiSortProp ?? !minimal;
128
160
  const isServer = !!serverPagination;
129
161
  const isLoading = !!serverPagination?.isLoading;
130
162
  const labelOverrides = useKitLabelOverrides("dataTable");
@@ -149,9 +181,9 @@ function DataTable({
149
181
  setPage,
150
182
  pageSize,
151
183
  setPageSize,
152
- widths,
184
+ widths: storedWidths,
153
185
  setWidths,
154
- hiddenCols,
186
+ hiddenCols: storedHiddenCols,
155
187
  setHiddenCols,
156
188
  showSettings,
157
189
  setShowSettings,
@@ -165,6 +197,11 @@ function DataTable({
165
197
  sortsProp,
166
198
  filtersProp
167
199
  });
200
+ const widths = useMemo(() => resizable ? storedWidths : {}, [resizable, storedWidths]);
201
+ const hiddenCols = useMemo(
202
+ () => columnSettings ? storedHiddenCols : /* @__PURE__ */ new Set(),
203
+ [columnSettings, storedHiddenCols]
204
+ );
168
205
  const columnSig = columns.map((c) => c.key).join("\0");
169
206
  const selectOptionsSig = columns.map((c) => {
170
207
  const f = resolveFilter(c);
@@ -228,11 +265,14 @@ function DataTable({
228
265
  const totalPages = isServer ? Math.max(1, Math.ceil(serverPagination.total / Math.max(1, serverPagination.pageSize))) : Math.max(1, Math.ceil(sorted.length / effectivePageSize));
229
266
  const safePage = isServer ? Math.min(Math.max(0, serverPagination.page), totalPages - 1) : Math.min(page, totalPages - 1);
230
267
  const slice = isServer || unpaged || pageSize === Infinity ? sorted : sorted.slice(safePage * effectivePageSize, safePage * effectivePageSize + effectivePageSize);
231
- const toggleSort = (key, additive) => {
232
- const next = nextSorts(sorts, key, additive);
268
+ const toggleSort = (key, shiftKey) => {
269
+ const col = columns.find((c) => c.key === key);
270
+ const next = nextSorts(sorts, key, multiSort && shiftKey, {
271
+ firstDir: col?.firstSort,
272
+ cycle: sortCycle
273
+ });
233
274
  if (onSortsChange) onSortsChange(next);
234
275
  else setInternalSorts(next);
235
- const col = columns.find((c) => c.key === key);
236
276
  const name = col ? columnLabel(col) : key;
237
277
  const entry = next.find((s) => s.key === key);
238
278
  announce(
@@ -406,6 +446,9 @@ function DataTable({
406
446
  defaultPageSize
407
447
  });
408
448
  const selectionAnchor = useRef(null);
449
+ const rowsFocusable = !!onRowClick;
450
+ const [focusRowKey, setFocusRowKey] = useState(null);
451
+ const tabRowKey = focusRowKey !== null && slice.some((r) => rowKey(r) === focusRowKey) ? focusRowKey : slice.length ? rowKey(slice[0]) : null;
409
452
  const rangeClick = useRef(null);
410
453
  const canSelect = (row) => !!selection && (selection.isSelectable?.(row) ?? true);
411
454
  const selectRange = (toIndex) => {
@@ -439,7 +482,8 @@ function DataTable({
439
482
  const swipeEnabled = !!swipe && !expanded;
440
483
  const href = mobileCardLinkable ? rowHref?.(row) : void 0;
441
484
  const cardClass = cn(
442
- "w-full px-4 py-3 text-start flex items-center gap-3",
485
+ "w-full text-start flex items-center",
486
+ compact ? "px-3 py-2 gap-2 text-sm" : "px-4 py-3 gap-3",
443
487
  // Live #320's other half: the card acknowledges the touch before the sheet
444
488
  // arrives. On a cold route the data can take a beat, and an unacknowledged tap
445
489
  // reads as "did that register?" — which is most of what "abrupt" means here.
@@ -450,12 +494,21 @@ function DataTable({
450
494
  interactive && "cursor-pointer transition-transform duration-100 active:scale-[0.995] active:bg-[var(--bg-active)] motion-reduce:transition-none motion-reduce:active:scale-100"
451
495
  );
452
496
  const cardInner = /* @__PURE__ */ jsxs(Fragment, { children: [
453
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: mobileCard ? mobileCard(row) : /* @__PURE__ */ jsxs(Fragment, { children: [
497
+ /* @__PURE__ */ jsx("div", { className: cn("flex min-w-0 flex-1 flex-col", compact ? "gap-1" : "gap-2"), children: mobileCard ? mobileCard(row) : /* @__PURE__ */ jsxs(Fragment, { children: [
454
498
  mobilePrimaryCol && /* @__PURE__ */ jsx("div", { className: "font-medium", children: mobilePrimaryCol.cell(row) }),
455
- mobileSecondaryColumns.length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-sm", children: mobileSecondaryColumns.map((col) => /* @__PURE__ */ jsxs(Fragment2, { children: [
456
- /* @__PURE__ */ jsx("dt", { className: "text-xs uppercase tracking-wide text-[var(--text-muted)] self-center", children: col.header }),
457
- /* @__PURE__ */ jsx("dd", { className: "min-w-0 text-[var(--text-secondary)] self-center", children: col.cell(row) })
458
- ] }, col.key)) })
499
+ mobileSecondaryColumns.length > 0 && /* @__PURE__ */ jsx(
500
+ "dl",
501
+ {
502
+ className: cn(
503
+ "grid grid-cols-[auto_1fr] gap-x-3",
504
+ compact ? "gap-y-0.5 text-xs" : "gap-y-1 text-sm"
505
+ ),
506
+ children: mobileSecondaryColumns.map((col) => /* @__PURE__ */ jsxs(Fragment2, { children: [
507
+ /* @__PURE__ */ jsx("dt", { className: "text-xs uppercase tracking-wide text-[var(--text-muted)] self-center", children: col.header }),
508
+ /* @__PURE__ */ jsx("dd", { className: "min-w-0 text-[var(--text-secondary)] self-center", children: col.cell(row) })
509
+ ] }, col.key))
510
+ }
511
+ )
459
512
  ] }) }),
460
513
  opensDetail && // `aria-hidden`: the row already announces itself as a button with
461
514
  // `aria-expanded`, so the icon would only add a second, wordless stop.
@@ -505,6 +558,7 @@ function DataTable({
505
558
  tabIndex: interactive ? 0 : void 0,
506
559
  onClick: interactive ? () => onRowClick(row) : void 0,
507
560
  onKeyDown: interactive ? (e) => {
561
+ if (e.target !== e.currentTarget) return;
508
562
  if (e.key === "Enter" || e.key === " ") {
509
563
  e.preventDefault();
510
564
  onRowClick(row);
@@ -517,7 +571,16 @@ function DataTable({
517
571
  );
518
572
  return /* @__PURE__ */ jsxs("li", { className: cn(tint), ...cleanAttrs(rowAttributes?.(row)), children: [
519
573
  swipeEnabled ? /* @__PURE__ */ jsx(SwipeableRow, { enabled: true, left: swipe.left, right: swipe.right, children: body }) : body,
520
- expansion && !mobileExpandAsDialog && /* @__PURE__ */ jsx("div", { className: "px-4 py-3 bg-[var(--bg-surface-2)] border-t border-[var(--border)]", children: expansion })
574
+ expansion && !mobileExpandAsDialog && /* @__PURE__ */ jsx(
575
+ "div",
576
+ {
577
+ className: cn(
578
+ "bg-[var(--bg-surface-2)] border-t border-[var(--border)]",
579
+ compact ? "px-3 py-2" : "px-4 py-3"
580
+ ),
581
+ children: expansion
582
+ }
583
+ )
521
584
  ] }, rowKey(row));
522
585
  };
523
586
  const mobileGroups = mobileGroupBy ? mobileSlice.reduce((acc, row) => {
@@ -527,445 +590,503 @@ function DataTable({
527
590
  else acc.push({ key, rows: [row] });
528
591
  return acc;
529
592
  }, []) : null;
530
- return /* @__PURE__ */ jsxs(Card, { flush: true, className: cn("overflow-clip", fillHeight && "md:flex md:flex-1 md:flex-col md:min-h-0"), children: [
531
- /* @__PURE__ */ jsx("span", { ...regionProps }),
532
- !isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, children: [
533
- (!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
534
- MobileFilters,
535
- {
536
- columns,
537
- filters,
538
- selectOptionsByKey,
539
- isServer,
540
- hasFilterCallback: !!onFiltersChange,
541
- onSetFilter: setFilterValue,
542
- onClearFilter: clearFilter,
543
- onClearAll: clearAllFilters,
544
- labels,
545
- locale
546
- }
593
+ const Root = frame ? FramedRoot : PlainRoot;
594
+ return /* @__PURE__ */ jsxs(
595
+ Root,
596
+ {
597
+ "data-density": density,
598
+ "data-frame": frame ? void 0 : "none",
599
+ className: cn(
600
+ "group/table",
601
+ frame ? "overflow-clip" : "min-w-0",
602
+ fillHeight && "md:flex md:flex-1 md:flex-col md:min-h-0",
603
+ className
547
604
  ),
548
- /* @__PURE__ */ jsxs(
549
- "ul",
550
- {
551
- className: cn(
552
- "divide-y divide-[var(--border)]",
553
- // Same busy treatment as the desktop body; see `isLoading` below.
554
- isLoading && mobileSlice.length > 0 && "opacity-60 transition-opacity"
605
+ children: [
606
+ /* @__PURE__ */ jsx("span", { ...regionProps }),
607
+ !isMdUp && /* @__PURE__ */ jsxs("div", { ref: mobileRootRef, children: [
608
+ (!isServer || onFiltersChange) && /* @__PURE__ */ jsx(
609
+ MobileFilters,
610
+ {
611
+ columns,
612
+ filters,
613
+ selectOptionsByKey,
614
+ isServer,
615
+ hasFilterCallback: !!onFiltersChange,
616
+ onSetFilter: setFilterValue,
617
+ onClearFilter: clearFilter,
618
+ onClearAll: clearAllFilters,
619
+ labels,
620
+ locale
621
+ }
555
622
  ),
556
- "aria-busy": isLoading || void 0,
557
- children: [
558
- mobileGroups ? mobileGroups.map((g) => /* @__PURE__ */ jsxs(Fragment2, { children: [
559
- /* @__PURE__ */ jsx("li", { className: "sticky top-0 z-10 bg-[var(--bg-surface-2)] px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)] backdrop-blur", children: mobileGroupLabel ? mobileGroupLabel(g.key) : g.key }),
560
- g.rows.map(renderMobileRow)
561
- ] }, `hb-group:${g.key}`)) : mobileSlice.map(renderMobileRow),
562
- mobileSlice.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-4 py-6 text-center text-sm text-[var(--text-muted)]", children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? "\u2014" }),
563
- canRevealMoreMobile && /* @__PURE__ */ jsx(
564
- "li",
565
- {
566
- ref: loadMoreRef,
567
- className: "px-4 py-4 text-center text-xs text-[var(--text-placeholder)]",
568
- children: labels.loading
569
- }
570
- )
571
- ]
572
- }
573
- ),
574
- isServer && /* @__PURE__ */ jsx(
575
- Pagination,
576
- {
577
- page: safePage,
578
- totalPages,
579
- pageSize: paginationPageSize,
580
- total: paginationTotal,
581
- onPage: goToPage,
582
- onPageSize: serverPagination.onPageSizeChange,
583
- labels,
584
- locale
585
- }
586
- ),
587
- /* @__PURE__ */ jsx(
588
- FullBleedDialog,
589
- {
590
- open: dialogOpen,
591
- onClose: closeDialog,
592
- closeLabel: labels.close,
593
- header: mobileDialogRow ? mobilePrimaryCol?.cell(mobileDialogRow) : null,
594
- children: mobileDialogContent
595
- }
596
- )
597
- ] }),
598
- isMdUp && /* @__PURE__ */ jsxs("div", { className: cn("flex", fillHeight && "min-h-0 flex-1"), children: [
599
- /* @__PURE__ */ jsxs("div", { className: cn("relative min-w-0 flex-1", fillHeight && "flex flex-col min-h-0"), children: [
600
- /* @__PURE__ */ jsx(
601
- "div",
602
- {
603
- className: cn("overflow-auto [scrollbar-gutter:stable]", fillHeight && "flex-1 min-h-0"),
604
- style: fillHeight ? void 0 : { maxHeight: maxBodyHeight },
605
- children: /* @__PURE__ */ jsxs(
606
- "table",
623
+ /* @__PURE__ */ jsxs(
624
+ "ul",
625
+ {
626
+ className: cn(
627
+ "divide-y divide-[var(--border)]",
628
+ // Same busy treatment as the desktop body; see `isLoading` below.
629
+ isLoading && mobileSlice.length > 0 && "opacity-60 transition-opacity"
630
+ ),
631
+ "aria-busy": isLoading || void 0,
632
+ children: [
633
+ mobileGroups ? mobileGroups.map((g) => /* @__PURE__ */ jsxs(Fragment2, { children: [
634
+ /* @__PURE__ */ jsx("li", { className: "sticky top-0 z-10 bg-[var(--bg-surface-2)] px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-[var(--text-muted)] backdrop-blur", children: mobileGroupLabel ? mobileGroupLabel(g.key) : g.key }),
635
+ g.rows.map(renderMobileRow)
636
+ ] }, `hb-group:${g.key}`)) : mobileSlice.map(renderMobileRow),
637
+ mobileSlice.length === 0 && /* @__PURE__ */ jsx(
638
+ "li",
639
+ {
640
+ className: cn(
641
+ "text-center text-[var(--text-muted)]",
642
+ compact ? "px-3 py-3 text-xs" : "px-4 py-6 text-sm"
643
+ ),
644
+ children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? "\u2014"
645
+ }
646
+ ),
647
+ canRevealMoreMobile && /* @__PURE__ */ jsx(
648
+ "li",
649
+ {
650
+ ref: loadMoreRef,
651
+ className: "px-4 py-4 text-center text-xs text-[var(--text-placeholder)]",
652
+ children: labels.loading
653
+ }
654
+ )
655
+ ]
656
+ }
657
+ ),
658
+ isServer && /* @__PURE__ */ jsx(
659
+ Pagination,
660
+ {
661
+ page: safePage,
662
+ totalPages,
663
+ pageSize: paginationPageSize,
664
+ total: paginationTotal,
665
+ onPage: goToPage,
666
+ onPageSize: serverPagination.onPageSizeChange,
667
+ labels,
668
+ locale,
669
+ density
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsx(
673
+ FullBleedDialog,
674
+ {
675
+ open: dialogOpen,
676
+ onClose: closeDialog,
677
+ closeLabel: labels.close,
678
+ header: mobileDialogRow ? mobilePrimaryCol?.cell(mobileDialogRow) : null,
679
+ children: mobileDialogContent
680
+ }
681
+ )
682
+ ] }),
683
+ isMdUp && /* @__PURE__ */ jsxs("div", { className: cn("flex", fillHeight && "min-h-0 flex-1"), children: [
684
+ /* @__PURE__ */ jsxs("div", { className: cn("relative min-w-0 flex-1", fillHeight && "flex flex-col min-h-0"), children: [
685
+ /* @__PURE__ */ jsx(
686
+ "div",
607
687
  {
608
- className: "w-full text-sm",
609
- "aria-label": labels.table,
610
- "aria-busy": isLoading || void 0,
611
- children: [
612
- /* @__PURE__ */ jsx("thead", { className: "bg-[var(--bg-surface-2)] text-[var(--text-secondary)]", children: /* @__PURE__ */ jsxs("tr", { className: "text-start", children: [
613
- selection && /* @__PURE__ */ jsx(
614
- "th",
615
- {
616
- scope: "col",
617
- className: "sticky top-0 z-10 w-10 bg-[var(--bg-surface-2)] px-3 py-2 align-middle backdrop-blur-sm",
618
- children: /* @__PURE__ */ jsx(
619
- "input",
688
+ className: cn("overflow-auto [scrollbar-gutter:stable]", fillHeight && "flex-1 min-h-0"),
689
+ style: fillHeight ? void 0 : { maxHeight: maxBodyHeight },
690
+ children: /* @__PURE__ */ jsxs(
691
+ "table",
692
+ {
693
+ className: cn("w-full", compact ? "text-xs" : "text-sm"),
694
+ "aria-label": labels.table,
695
+ "aria-busy": isLoading || void 0,
696
+ children: [
697
+ /* @__PURE__ */ jsx("thead", { className: "bg-[var(--bg-surface-2)] text-[var(--text-secondary)]", children: /* @__PURE__ */ jsxs("tr", { className: "text-start", children: [
698
+ selection && /* @__PURE__ */ jsx(
699
+ "th",
620
700
  {
621
- type: "checkbox",
622
- className: "size-4 align-middle accent-indigo-600",
623
- checked: selection.allSelected,
624
- ref: (el) => {
625
- if (el) el.indeterminate = selection.someSelected && !selection.allSelected;
626
- },
627
- onChange: (e) => selection.onToggleAll(e.target.checked),
628
- "aria-label": labels.selectAllRows
629
- }
630
- )
631
- }
632
- ),
633
- visibleColumns.map((col) => {
634
- const sortable = (!isServer || !!onSortsChange) && !!col.sortBy;
635
- const filter = !isServer || onFiltersChange ? resolveFilter(col) : null;
636
- const filterState = filters[col.key] ?? (filter ? defaultFilterState(filter) : void 0);
637
- const sortIdx = sorts.findIndex((s) => s.key === col.key);
638
- const active = sortIdx !== -1;
639
- const Icon = active ? sorts[sortIdx].dir === "asc" ? ArrowUp : ArrowDown : ArrowUpDown;
640
- const filterActive = filterState && isFilterActive(filterState);
641
- const headClass = logicalAlign(col.headClassName);
642
- const isEndAligned = !!headClass && END_ALIGN.test(headClass);
643
- const width = widths[col.key];
644
- return /* @__PURE__ */ jsxs(
645
- "th",
646
- {
647
- ref: (el) => {
648
- if (el) headRefs.set(col.key, el);
649
- else headRefs.delete(col.key);
650
- },
651
- scope: "col",
652
- "aria-sort": sortable ? sortIdx === 0 ? sorts[0].dir === "asc" ? "ascending" : "descending" : "none" : void 0,
653
- style: width ? { width, minWidth: width, maxWidth: width } : void 0,
654
- className: cn(
655
- "relative px-3 py-2 font-medium align-middle whitespace-nowrap",
656
- // Keep the header visible while the user scrolls the page.
657
- // Each <th> carries its own bg so the row doesn't render
658
- // transparent over the data rows underneath.
659
- "sticky top-0 z-10 bg-[var(--bg-surface-2)] backdrop-blur-sm",
660
- headClass
661
- ),
662
- children: [
663
- /* @__PURE__ */ jsxs(
664
- "div",
701
+ scope: "col",
702
+ className: cn(
703
+ "sticky top-0 z-10 bg-[var(--bg-surface-2)] align-middle backdrop-blur-sm",
704
+ compact ? "w-8 px-2 py-1" : "w-10 px-3 py-2"
705
+ ),
706
+ children: /* @__PURE__ */ jsx(
707
+ "input",
665
708
  {
666
- className: cn(
667
- "flex items-center gap-1",
668
- isEndAligned && "flex-row-reverse"
669
- ),
670
- children: [
671
- /* @__PURE__ */ jsx(Tooltip, { label: sortable ? labels.sortHint : void 0, portal: true, children: /* @__PURE__ */ jsxs(
672
- "button",
673
- {
674
- type: "button",
675
- onClick: (e) => sortable && toggleSort(col.key, e.shiftKey),
676
- disabled: !sortable,
677
- className: cn(
678
- "flex items-center gap-1 text-start",
679
- sortable && "hover:text-[var(--text-primary)]"
680
- ),
681
- children: [
682
- /* @__PURE__ */ jsx("span", { children: col.header }),
683
- sortable && /* @__PURE__ */ jsx(Icon, { className: cn("size-3", active ? "opacity-100" : "opacity-40") }),
684
- active && sorts.length > 1 && /* @__PURE__ */ jsx("span", { className: "text-[9px] font-semibold leading-none text-brand", children: sortIdx + 1 })
685
- ]
686
- }
687
- ) }),
688
- filter && /* @__PURE__ */ jsx(
689
- Popover,
690
- {
691
- width: filter.type === "date" ? 420 : void 0,
692
- labels: { panel: labels.filter },
693
- trigger: ({ toggle, ref }) => /* @__PURE__ */ jsx(
709
+ type: "checkbox",
710
+ className: cn("align-middle accent-indigo-600", compact ? "size-3.5" : "size-4"),
711
+ checked: selection.allSelected,
712
+ ref: (el) => {
713
+ if (el) el.indeterminate = selection.someSelected && !selection.allSelected;
714
+ },
715
+ onChange: (e) => selection.onToggleAll(e.target.checked),
716
+ "aria-label": labels.selectAllRows
717
+ }
718
+ )
719
+ }
720
+ ),
721
+ visibleColumns.map((col) => {
722
+ const sortable = (!isServer || !!onSortsChange) && !!col.sortBy;
723
+ const filter = !isServer || onFiltersChange ? resolveFilter(col) : null;
724
+ const filterState = filters[col.key] ?? (filter ? defaultFilterState(filter) : void 0);
725
+ const sortIdx = sorts.findIndex((s) => s.key === col.key);
726
+ const active = sortIdx !== -1;
727
+ const Icon = active ? sorts[sortIdx].dir === "asc" ? ArrowUp : ArrowDown : ArrowUpDown;
728
+ const filterActive = filterState && isFilterActive(filterState);
729
+ const headClass = logicalAlign(col.headClassName);
730
+ const isEndAligned = !!headClass && END_ALIGN.test(headClass);
731
+ const width = widths[col.key];
732
+ return /* @__PURE__ */ jsxs(
733
+ "th",
734
+ {
735
+ ref: (el) => {
736
+ if (el) headRefs.set(col.key, el);
737
+ else headRefs.delete(col.key);
738
+ },
739
+ scope: "col",
740
+ "aria-sort": sortable ? sortIdx === 0 ? sorts[0].dir === "asc" ? "ascending" : "descending" : "none" : void 0,
741
+ style: width ? { width, minWidth: width, maxWidth: width } : void 0,
742
+ className: cn(
743
+ "relative font-medium align-middle whitespace-nowrap",
744
+ compact ? "px-2 py-1" : "px-3 py-2",
745
+ // Keep the header visible while the user scrolls the page.
746
+ // Each <th> carries its own bg so the row doesn't render
747
+ // transparent over the data rows underneath.
748
+ "sticky top-0 z-10 bg-[var(--bg-surface-2)] backdrop-blur-sm",
749
+ headClass
750
+ ),
751
+ children: [
752
+ /* @__PURE__ */ jsxs(
753
+ "div",
754
+ {
755
+ className: cn(
756
+ "flex items-center gap-1",
757
+ isEndAligned && "flex-row-reverse"
758
+ ),
759
+ children: [
760
+ /* @__PURE__ */ jsx(Tooltip, { label: sortable && multiSort ? labels.sortHint : void 0, portal: true, children: /* @__PURE__ */ jsxs(
694
761
  "button",
695
762
  {
696
763
  type: "button",
697
- ref,
698
- onClick: toggle,
699
- "aria-label": labels.filter,
764
+ onClick: (e) => sortable && toggleSort(col.key, e.shiftKey),
765
+ disabled: !sortable,
700
766
  className: cn(
701
- "rounded p-1 transition-colors",
702
- filterActive ? "bg-[var(--brand-bg)] text-[var(--brand-muted)]" : "text-[var(--text-placeholder)] hover:text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
767
+ "flex items-center gap-1 text-start",
768
+ sortable && "hover:text-[var(--text-primary)]"
703
769
  ),
704
- children: /* @__PURE__ */ jsx(Filter, { className: "size-3" })
770
+ children: [
771
+ /* @__PURE__ */ jsx("span", { children: col.header }),
772
+ sortable && /* @__PURE__ */ jsx(Icon, { className: cn("size-3", active ? "opacity-100" : "opacity-40") }),
773
+ active && sorts.length > 1 && /* @__PURE__ */ jsx("span", { className: "text-[9px] font-semibold leading-none text-brand", children: sortIdx + 1 })
774
+ ]
705
775
  }
706
- ),
707
- children: () => /* @__PURE__ */ jsx(
708
- FilterPopover,
776
+ ) }),
777
+ filter && /* @__PURE__ */ jsx(
778
+ Popover,
709
779
  {
710
- column: col,
711
- state: filterState ?? defaultFilterState(filter),
712
- onChange: (next) => setFilterValue(col.key, next),
713
- onClear: () => clearFilter(col.key),
714
- selectOptions: selectOptionsByKey[col.key] ?? [],
715
- labels,
716
- locale
780
+ width: filter.type === "date" ? 420 : void 0,
781
+ labels: { panel: labels.filter },
782
+ trigger: ({ toggle, ref }) => /* @__PURE__ */ jsx(
783
+ "button",
784
+ {
785
+ type: "button",
786
+ ref,
787
+ onClick: toggle,
788
+ "aria-label": labels.filter,
789
+ className: cn(
790
+ "rounded p-1 transition-colors",
791
+ filterActive ? "bg-[var(--brand-bg)] text-[var(--brand-muted)]" : "text-[var(--text-placeholder)] hover:text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]"
792
+ ),
793
+ children: /* @__PURE__ */ jsx(Filter, { className: "size-3" })
794
+ }
795
+ ),
796
+ children: () => /* @__PURE__ */ jsx(
797
+ FilterPopover,
798
+ {
799
+ column: col,
800
+ state: filterState ?? defaultFilterState(filter),
801
+ onChange: (next) => setFilterValue(col.key, next),
802
+ onClear: () => clearFilter(col.key),
803
+ selectOptions: selectOptionsByKey[col.key] ?? [],
804
+ labels,
805
+ locale
806
+ }
807
+ )
717
808
  }
718
809
  )
719
- }
720
- )
721
- ]
722
- }
723
- ),
724
- /* @__PURE__ */ jsx(
725
- "span",
726
- {
727
- role: "separator",
728
- "aria-orientation": "vertical",
729
- onPointerDown: (e) => startResize(e, col.key),
730
- onPointerMove: moveResize,
731
- onPointerUp: (e) => finishResize(e, true),
732
- onPointerCancel: (e) => finishResize(e, false),
733
- onDoubleClick: (e) => {
734
- e.preventDefault();
735
- setWidths((w) => {
736
- const next = { ...w };
737
- delete next[col.key];
738
- return next;
739
- });
740
- },
741
- className: "absolute end-0 top-0 z-10 h-full w-1.5 -translate-x-1/2 rtl:translate-x-1/2 cursor-col-resize select-none touch-none hover:bg-[var(--brand-bg-hover)]"
742
- }
743
- )
744
- ]
745
- },
746
- col.key
747
- );
748
- })
749
- ] }) }),
750
- /* @__PURE__ */ jsxs(
751
- "tbody",
752
- {
753
- className: cn(isLoading && slice.length > 0 && "opacity-60 transition-opacity"),
754
- children: [
755
- leadingRow && /* @__PURE__ */ jsx("tr", { className: "border-t border-[var(--border)]", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: leadingRow }) }),
756
- slice.map((row, rowIndex) => {
757
- const expanded = isExpanded?.(row) ?? false;
758
- const expansion = expanded ? expandedRow?.(row) : null;
759
- const rowInteractive = !!onRowClick || !!selection;
760
- const href = rowHref?.(row);
761
- return /* @__PURE__ */ jsxs(Fragment2, { children: [
762
- /* @__PURE__ */ jsxs(
763
- "tr",
764
- {
765
- ...cleanAttrs(rowAttributes?.(row)),
766
- className: cn(
767
- "border-t border-[var(--border)]",
768
- rowInteractive && "cursor-pointer hover:bg-[var(--bg-hover)]",
769
- rowClassName?.(row)
810
+ ]
811
+ }
770
812
  ),
771
- onClick: rowInteractive ? (e) => {
772
- if (selection && canSelect(row)) {
773
- if (e.shiftKey) {
774
- window.getSelection?.()?.removeAllRanges();
775
- if (selectRange(rowIndex)) return;
776
- selectionAnchor.current = rowKey(row);
777
- selection.onToggle(row, !selection.isSelected(row));
778
- return;
779
- }
780
- if (e.metaKey || e.ctrlKey) {
781
- selectionAnchor.current = rowKey(row);
782
- selection.onToggle(row, !selection.isSelected(row));
783
- return;
784
- }
813
+ resizable && /* @__PURE__ */ jsx(
814
+ "span",
815
+ {
816
+ role: "separator",
817
+ "aria-orientation": "vertical",
818
+ onPointerDown: (e) => startResize(e, col.key),
819
+ onPointerMove: moveResize,
820
+ onPointerUp: (e) => finishResize(e, true),
821
+ onPointerCancel: (e) => finishResize(e, false),
822
+ onDoubleClick: (e) => {
823
+ e.preventDefault();
824
+ setWidths((w) => {
825
+ const next = { ...w };
826
+ delete next[col.key];
827
+ return next;
828
+ });
829
+ },
830
+ className: "absolute end-0 top-0 z-10 h-full w-1.5 -translate-x-1/2 rtl:translate-x-1/2 cursor-col-resize select-none touch-none hover:bg-[var(--brand-bg-hover)]"
785
831
  }
786
- onRowClick?.(row);
787
- } : void 0,
788
- children: [
789
- selection && /* @__PURE__ */ jsx(
790
- "td",
791
- {
792
- className: "w-10 px-3 py-2 align-top",
793
- onClick: (e) => e.stopPropagation(),
794
- children: canSelect(row) && /* @__PURE__ */ jsx(
795
- "input",
832
+ )
833
+ ]
834
+ },
835
+ col.key
836
+ );
837
+ })
838
+ ] }) }),
839
+ /* @__PURE__ */ jsxs(
840
+ "tbody",
841
+ {
842
+ className: cn(isLoading && slice.length > 0 && "opacity-60 transition-opacity"),
843
+ children: [
844
+ leadingRow && /* @__PURE__ */ jsx("tr", { className: "border-t border-[var(--border)]", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: leadingRow }) }),
845
+ slice.map((row, rowIndex) => {
846
+ const expanded = isExpanded?.(row) ?? false;
847
+ const expansion = expanded ? expandedRow?.(row) : null;
848
+ const rowInteractive = !!onRowClick || !!selection;
849
+ const href = rowHref?.(row);
850
+ const key = rowKey(row);
851
+ return /* @__PURE__ */ jsxs(Fragment2, { children: [
852
+ /* @__PURE__ */ jsxs(
853
+ "tr",
854
+ {
855
+ ...cleanAttrs(rowAttributes?.(row)),
856
+ "data-row-nav": rowsFocusable ? "" : void 0,
857
+ tabIndex: rowsFocusable ? key === tabRowKey ? 0 : -1 : void 0,
858
+ "aria-expanded": rowsFocusable && expandedRow ? expanded : void 0,
859
+ onFocus: rowsFocusable ? (e) => {
860
+ if (e.target === e.currentTarget) setFocusRowKey(key);
861
+ } : void 0,
862
+ onKeyDown: rowsFocusable ? (e) => {
863
+ if (e.target !== e.currentTarget) return;
864
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
865
+ if (e.key === "Enter" || e.key === " ") {
866
+ e.preventDefault();
867
+ onRowClick(row);
868
+ return;
869
+ }
870
+ if (e.shiftKey) return;
871
+ if (moveRowFocus(e.currentTarget, e.key)) e.preventDefault();
872
+ } : void 0,
873
+ className: cn(
874
+ "border-t border-[var(--border)]",
875
+ rowInteractive && "cursor-pointer hover:bg-[var(--bg-hover)]",
876
+ rowsFocusable && "focus-visible:bg-[var(--bg-hover)] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--brand)]",
877
+ rowClassName?.(row)
878
+ ),
879
+ onClick: rowInteractive ? (e) => {
880
+ if (fromOwnControl(e.target, e.currentTarget)) return;
881
+ if (selection && canSelect(row)) {
882
+ if (e.shiftKey) {
883
+ window.getSelection?.()?.removeAllRanges();
884
+ if (selectRange(rowIndex)) return;
885
+ selectionAnchor.current = rowKey(row);
886
+ selection.onToggle(row, !selection.isSelected(row));
887
+ return;
888
+ }
889
+ if (e.metaKey || e.ctrlKey) {
890
+ selectionAnchor.current = rowKey(row);
891
+ selection.onToggle(row, !selection.isSelected(row));
892
+ return;
893
+ }
894
+ }
895
+ onRowClick?.(row);
896
+ } : void 0,
897
+ children: [
898
+ selection && /* @__PURE__ */ jsx(
899
+ "td",
796
900
  {
797
- type: "checkbox",
798
- className: "size-4 accent-indigo-600",
799
- checked: selection.isSelected(row),
800
- onClick: (e) => {
801
- if (e.shiftKey && selectRange(rowIndex)) {
802
- e.preventDefault();
803
- rangeClick.current = e.nativeEvent;
901
+ className: cn("align-top", compact ? "w-8 px-2 py-1" : "w-10 px-3 py-2"),
902
+ onClick: (e) => e.stopPropagation(),
903
+ children: canSelect(row) && /* @__PURE__ */ jsx(
904
+ "input",
905
+ {
906
+ type: "checkbox",
907
+ className: cn("accent-indigo-600", compact ? "size-3.5" : "size-4"),
908
+ checked: selection.isSelected(row),
909
+ onClick: (e) => {
910
+ if (e.shiftKey && selectRange(rowIndex)) {
911
+ e.preventDefault();
912
+ rangeClick.current = e.nativeEvent;
913
+ }
914
+ },
915
+ onChange: (e) => {
916
+ if (rangeClick.current && e.nativeEvent === rangeClick.current) {
917
+ rangeClick.current = null;
918
+ return;
919
+ }
920
+ selectionAnchor.current = rowKey(row);
921
+ selection.onToggle(row, e.target.checked);
922
+ },
923
+ "aria-label": labels.selectRow
804
924
  }
805
- },
806
- onChange: (e) => {
807
- if (rangeClick.current && e.nativeEvent === rangeClick.current) {
808
- rangeClick.current = null;
809
- return;
810
- }
811
- selectionAnchor.current = rowKey(row);
812
- selection.onToggle(row, e.target.checked);
813
- },
814
- "aria-label": labels.selectRow
925
+ )
815
926
  }
816
- )
817
- }
818
- ),
819
- visibleColumns.map((col) => {
820
- const width = widths[col.key];
821
- return /* @__PURE__ */ jsx(
822
- "td",
823
- {
824
- "data-col": col.key,
825
- style: width ? { width, minWidth: width, maxWidth: width } : void 0,
826
- className: cn(
827
- "px-3 py-2 align-top",
828
- width && "overflow-hidden text-ellipsis",
829
- logicalAlign(col.className)
830
- ),
831
- children: col === linkColumn && href ? (
832
- // No `onActivate`: the click is left to bubble to the
833
- // <tr> handler above, which already owns expand-vs-select.
834
- // Calling it here as well would toggle the row twice.
835
- /* @__PURE__ */ jsx(RowLink, { href, className: "block", children: col.cell(row) })
836
- ) : col.cell(row)
837
- },
838
- col.key
839
- );
840
- })
841
- ]
927
+ ),
928
+ visibleColumns.map((col) => {
929
+ const width = widths[col.key];
930
+ return /* @__PURE__ */ jsx(
931
+ "td",
932
+ {
933
+ "data-col": col.key,
934
+ style: width ? { width, minWidth: width, maxWidth: width } : void 0,
935
+ className: cn(
936
+ "align-top",
937
+ compact ? "px-2 py-1" : "px-3 py-2",
938
+ width && "overflow-hidden text-ellipsis",
939
+ logicalAlign(col.className)
940
+ ),
941
+ children: col === linkColumn && href ? (
942
+ // No `onActivate`: the click is left to bubble to the
943
+ // <tr> handler above, which already owns expand-vs-select.
944
+ // Calling it here as well would toggle the row twice.
945
+ /* @__PURE__ */ jsx(RowLink, { href, className: "block", "data-row-link": "", children: col.cell(row) })
946
+ ) : col.cell(row)
947
+ },
948
+ col.key
949
+ );
950
+ })
951
+ ]
952
+ }
953
+ ),
954
+ expansion && /* @__PURE__ */ jsx("tr", { className: "bg-[var(--bg-surface-2)] border-t border-[var(--border)]", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: /* @__PURE__ */ jsx("div", { className: cn("w-0 min-w-full", compact ? "px-2 py-2" : "px-3 py-3"), children: expansion }) }) })
955
+ ] }, key);
956
+ }),
957
+ slice.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
958
+ "td",
959
+ {
960
+ colSpan: totalColSpan,
961
+ className: cn("text-center text-[var(--text-muted)]", compact ? "px-2 py-2" : "px-3 py-4"),
962
+ children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? "\u2014"
842
963
  }
843
- ),
844
- expansion && /* @__PURE__ */ jsx("tr", { className: "bg-[var(--bg-surface-2)] border-t border-[var(--border)]", children: /* @__PURE__ */ jsx("td", { colSpan: totalColSpan, className: "p-0", children: /* @__PURE__ */ jsx("div", { className: "w-0 min-w-full px-3 py-3", children: expansion }) }) })
845
- ] }, rowKey(row));
846
- }),
847
- slice.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
848
- "td",
849
- {
850
- colSpan: totalColSpan,
851
- className: "px-3 py-4 text-center text-[var(--text-muted)]",
852
- children: isLoading ? /* @__PURE__ */ jsx(LoadingText, { label: labels.loading }) : empty ?? "\u2014"
853
- }
854
- ) })
855
- ]
856
- }
857
- )
858
- ]
964
+ ) })
965
+ ]
966
+ }
967
+ )
968
+ ]
969
+ }
970
+ )
971
+ }
972
+ ),
973
+ !unpaged && /* @__PURE__ */ jsx(
974
+ Pagination,
975
+ {
976
+ page: safePage,
977
+ totalPages,
978
+ pageSize: paginationPageSize,
979
+ total: paginationTotal,
980
+ onPage: goToPage,
981
+ onPageSize: isServer ? serverPagination.onPageSizeChange : (n) => {
982
+ setPageSize(n);
983
+ setPage(0);
984
+ },
985
+ labels,
986
+ locale,
987
+ density
859
988
  }
860
989
  )
861
- }
862
- ),
863
- !unpaged && /* @__PURE__ */ jsx(
864
- Pagination,
865
- {
866
- page: safePage,
867
- totalPages,
868
- pageSize: paginationPageSize,
869
- total: paginationTotal,
870
- onPage: goToPage,
871
- onPageSize: isServer ? serverPagination.onPageSizeChange : (n) => {
872
- setPageSize(n);
873
- setPage(0);
874
- },
875
- labels,
876
- locale
877
- }
878
- )
879
- ] }),
880
- /* @__PURE__ */ jsx(Tooltip, { label: columnsCountLabel, portal: true, children: /* @__PURE__ */ jsx(
881
- "button",
882
- {
883
- type: "button",
884
- onClick: () => setShowSettings(true),
885
- "aria-label": columnsCountLabel,
886
- "aria-hidden": showSettings,
887
- tabIndex: showSettings ? -1 : 0,
888
- className: cn(
889
- "group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out",
890
- "border-s border-[var(--border)]",
891
- "hover:bg-[var(--bg-hover)]",
892
- showSettings ? "w-0 border-s-0" : "w-8 cursor-pointer"
893
- ),
894
- children: /* @__PURE__ */ jsx(
895
- "span",
896
- {
897
- className: "whitespace-nowrap text-xs font-medium tracking-wide text-[var(--text-muted)] group-hover:text-[var(--text-secondary)]",
898
- style: { writingMode: "vertical-rl", transform: "rotate(180deg)" },
899
- children: columnsCountLabel
900
- }
901
- )
902
- }
903
- ) }),
904
- /* @__PURE__ */ jsx(
905
- "div",
906
- {
907
- className: cn(
908
- "shrink-0 overflow-hidden transition-[width] duration-200 ease-out",
909
- showSettings ? "w-56" : "w-0"
910
- ),
911
- children: /* @__PURE__ */ jsxs("div", { className: "flex w-56 flex-col border-s border-[var(--border)] p-2", children: [
912
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-1", children: [
913
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]", children: columnsCountLabel }),
914
- /* @__PURE__ */ jsx(Tooltip, { label: labels.close, portal: true, children: /* @__PURE__ */ jsx(
915
- "button",
916
- {
917
- type: "button",
918
- onClick: () => setShowSettings(false),
919
- "aria-label": labels.close,
920
- className: "rounded p-0.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
921
- children: /* @__PURE__ */ jsx(X, { className: "size-4" })
922
- }
923
- ) })
924
- ] }),
925
- /* @__PURE__ */ jsx(
990
+ ] }),
991
+ columnSettings && /* @__PURE__ */ jsxs(Fragment, { children: [
992
+ /* @__PURE__ */ jsx(Tooltip, { label: columnsCountLabel, portal: true, children: /* @__PURE__ */ jsx(
926
993
  "button",
927
994
  {
928
995
  type: "button",
929
- onClick: autoSizeAll,
930
- className: "mb-2 rounded border border-[var(--border)] px-2 py-1 text-xs font-medium text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
931
- children: labels.autoSize
996
+ onClick: () => setShowSettings(true),
997
+ "aria-label": columnsCountLabel,
998
+ "aria-hidden": showSettings,
999
+ tabIndex: showSettings ? -1 : 0,
1000
+ className: cn(
1001
+ "group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out",
1002
+ "border-s border-[var(--border)]",
1003
+ "hover:bg-[var(--bg-hover)]",
1004
+ showSettings ? "w-0 border-s-0" : "w-8 cursor-pointer"
1005
+ ),
1006
+ children: /* @__PURE__ */ jsx(
1007
+ "span",
1008
+ {
1009
+ className: "whitespace-nowrap text-xs font-medium tracking-wide text-[var(--text-muted)] group-hover:text-[var(--text-secondary)]",
1010
+ style: { writingMode: "vertical-rl", transform: "rotate(180deg)" },
1011
+ children: columnsCountLabel
1012
+ }
1013
+ )
932
1014
  }
933
- ),
934
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: columns.map((col) => {
935
- const checked = !hiddenCols.has(col.key);
936
- const headerLabel = columnLabel(col);
937
- return /* @__PURE__ */ jsxs(
938
- "label",
939
- {
940
- className: "inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-[var(--bg-hover)]",
941
- children: [
942
- /* @__PURE__ */ jsx(
943
- "input",
1015
+ ) }),
1016
+ /* @__PURE__ */ jsx(
1017
+ "div",
1018
+ {
1019
+ className: cn(
1020
+ "shrink-0 overflow-hidden transition-[width] duration-200 ease-out",
1021
+ showSettings ? "w-56" : "w-0"
1022
+ ),
1023
+ children: /* @__PURE__ */ jsxs("div", { className: "flex w-56 flex-col border-s border-[var(--border)] p-2", children: [
1024
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-1", children: [
1025
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]", children: columnsCountLabel }),
1026
+ /* @__PURE__ */ jsx(Tooltip, { label: labels.close, portal: true, children: /* @__PURE__ */ jsx(
1027
+ "button",
944
1028
  {
945
- type: "checkbox",
946
- className: "size-3.5",
947
- checked,
948
- onChange: (e) => {
949
- setHiddenCols((s) => {
950
- const next = new Set(s);
951
- if (e.target.checked) next.delete(col.key);
952
- else next.add(col.key);
953
- return next;
954
- });
955
- }
1029
+ type: "button",
1030
+ onClick: () => setShowSettings(false),
1031
+ "aria-label": labels.close,
1032
+ className: "rounded p-0.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]",
1033
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
956
1034
  }
957
- ),
958
- /* @__PURE__ */ jsx("span", { className: "truncate", children: headerLabel })
959
- ]
960
- },
961
- col.key
962
- );
963
- }) })
1035
+ ) })
1036
+ ] }),
1037
+ /* @__PURE__ */ jsx(
1038
+ "button",
1039
+ {
1040
+ type: "button",
1041
+ onClick: autoSizeAll,
1042
+ className: "mb-2 rounded border border-[var(--border)] px-2 py-1 text-xs font-medium text-[var(--text-secondary)] hover:bg-[var(--bg-hover)]",
1043
+ children: labels.autoSize
1044
+ }
1045
+ ),
1046
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: columns.map((col) => {
1047
+ const checked = !hiddenCols.has(col.key);
1048
+ const headerLabel = columnLabel(col);
1049
+ return /* @__PURE__ */ jsxs(
1050
+ "label",
1051
+ {
1052
+ className: "inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-[var(--bg-hover)]",
1053
+ children: [
1054
+ /* @__PURE__ */ jsx(
1055
+ "input",
1056
+ {
1057
+ type: "checkbox",
1058
+ className: "size-3.5",
1059
+ checked,
1060
+ onChange: (e) => {
1061
+ setHiddenCols((s) => {
1062
+ const next = new Set(s);
1063
+ if (e.target.checked) next.delete(col.key);
1064
+ else next.add(col.key);
1065
+ return next;
1066
+ });
1067
+ }
1068
+ }
1069
+ ),
1070
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: headerLabel })
1071
+ ]
1072
+ },
1073
+ col.key
1074
+ );
1075
+ }) })
1076
+ ] })
1077
+ }
1078
+ )
964
1079
  ] })
965
- }
966
- )
967
- ] })
968
- ] });
1080
+ ] })
1081
+ ]
1082
+ }
1083
+ );
1084
+ }
1085
+ function FramedRoot({ children, ...rest }) {
1086
+ return /* @__PURE__ */ jsx(Card, { flush: true, ...rest, children });
1087
+ }
1088
+ function PlainRoot(props) {
1089
+ return /* @__PURE__ */ jsx("div", { ...props });
969
1090
  }
970
1091
  function LoadingText({ label }) {
971
1092
  return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [