@flanksource/clicky-ui 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/components/Combobox.cjs +4 -1
  2. package/dist/components/Combobox.cjs.map +1 -1
  3. package/dist/components/Combobox.d.ts +6 -0
  4. package/dist/components/Combobox.d.ts.map +1 -1
  5. package/dist/components/Combobox.js +4 -1
  6. package/dist/components/Combobox.js.map +1 -1
  7. package/dist/components/JsonSchemaForm.cjs +24 -5
  8. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  9. package/dist/components/JsonSchemaForm.d.ts +1 -1
  10. package/dist/components/JsonSchemaForm.d.ts.map +1 -1
  11. package/dist/components/JsonSchemaForm.js +24 -5
  12. package/dist/components/JsonSchemaForm.js.map +1 -1
  13. package/dist/components/json-schema-form-array.cjs +117 -37
  14. package/dist/components/json-schema-form-array.cjs.map +1 -1
  15. package/dist/components/json-schema-form-array.d.ts.map +1 -1
  16. package/dist/components/json-schema-form-array.js +118 -38
  17. package/dist/components/json-schema-form-array.js.map +1 -1
  18. package/dist/components/json-schema-form-fields.cjs +121 -51
  19. package/dist/components/json-schema-form-fields.cjs.map +1 -1
  20. package/dist/components/json-schema-form-fields.d.ts +9 -6
  21. package/dist/components/json-schema-form-fields.d.ts.map +1 -1
  22. package/dist/components/json-schema-form-fields.js +121 -51
  23. package/dist/components/json-schema-form-fields.js.map +1 -1
  24. package/dist/components/json-schema-form-render.cjs +15 -8
  25. package/dist/components/json-schema-form-render.cjs.map +1 -1
  26. package/dist/components/json-schema-form-render.d.ts +5 -2
  27. package/dist/components/json-schema-form-render.d.ts.map +1 -1
  28. package/dist/components/json-schema-form-render.js +15 -8
  29. package/dist/components/json-schema-form-render.js.map +1 -1
  30. package/dist/components/json-schema-form-resolve.cjs +41 -6
  31. package/dist/components/json-schema-form-resolve.cjs.map +1 -1
  32. package/dist/components/json-schema-form-resolve.d.ts.map +1 -1
  33. package/dist/components/json-schema-form-resolve.js +41 -6
  34. package/dist/components/json-schema-form-resolve.js.map +1 -1
  35. package/dist/components/json-schema-form-size.cjs +61 -0
  36. package/dist/components/json-schema-form-size.cjs.map +1 -0
  37. package/dist/components/json-schema-form-size.d.ts +11 -0
  38. package/dist/components/json-schema-form-size.d.ts.map +1 -0
  39. package/dist/components/json-schema-form-size.js +61 -0
  40. package/dist/components/json-schema-form-size.js.map +1 -0
  41. package/dist/components/json-schema-form-types.d.ts +36 -1
  42. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  43. package/dist/components.cjs +2 -0
  44. package/dist/components.cjs.map +1 -1
  45. package/dist/components.d.ts +3 -1
  46. package/dist/components.d.ts.map +1 -1
  47. package/dist/components.js +2 -0
  48. package/dist/components.js.map +1 -1
  49. package/dist/data/Clicky.cjs +412 -90
  50. package/dist/data/Clicky.cjs.map +1 -1
  51. package/dist/data/Clicky.d.ts +4 -2
  52. package/dist/data/Clicky.d.ts.map +1 -1
  53. package/dist/data/Clicky.js +413 -91
  54. package/dist/data/Clicky.js.map +1 -1
  55. package/dist/data/DataTable.cjs +534 -293
  56. package/dist/data/DataTable.cjs.map +1 -1
  57. package/dist/data/DataTable.d.ts +12 -0
  58. package/dist/data/DataTable.d.ts.map +1 -1
  59. package/dist/data/DataTable.js +534 -293
  60. package/dist/data/DataTable.js.map +1 -1
  61. package/dist/data.d.ts +1 -1
  62. package/dist/data.d.ts.map +1 -1
  63. package/dist/index.cjs +2 -0
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.js +2 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/rpc/CommandOutput.cjs +101 -20
  68. package/dist/rpc/CommandOutput.cjs.map +1 -1
  69. package/dist/rpc/CommandOutput.d.ts +12 -4
  70. package/dist/rpc/CommandOutput.d.ts.map +1 -1
  71. package/dist/rpc/CommandOutput.js +102 -21
  72. package/dist/rpc/CommandOutput.js.map +1 -1
  73. package/dist/rpc/ExecutionResult.cjs +4 -76
  74. package/dist/rpc/ExecutionResult.cjs.map +1 -1
  75. package/dist/rpc/ExecutionResult.d.ts +3 -22
  76. package/dist/rpc/ExecutionResult.d.ts.map +1 -1
  77. package/dist/rpc/ExecutionResult.js +4 -76
  78. package/dist/rpc/ExecutionResult.js.map +1 -1
  79. package/dist/rpc/OperationCatalog.cjs +22 -13
  80. package/dist/rpc/OperationCatalog.cjs.map +1 -1
  81. package/dist/rpc/OperationCatalog.d.ts.map +1 -1
  82. package/dist/rpc/OperationCatalog.js +24 -15
  83. package/dist/rpc/OperationCatalog.js.map +1 -1
  84. package/dist/rpc/OperationCommandPage.cjs +10 -20
  85. package/dist/rpc/OperationCommandPage.cjs.map +1 -1
  86. package/dist/rpc/OperationCommandPage.d.ts.map +1 -1
  87. package/dist/rpc/OperationCommandPage.js +11 -21
  88. package/dist/rpc/OperationCommandPage.js.map +1 -1
  89. package/dist/rpc/apiClient.cjs +18 -0
  90. package/dist/rpc/apiClient.cjs.map +1 -1
  91. package/dist/rpc/apiClient.d.ts.map +1 -1
  92. package/dist/rpc/apiClient.js +18 -0
  93. package/dist/rpc/apiClient.js.map +1 -1
  94. package/dist/rpc/formMetadata.cjs +31 -0
  95. package/dist/rpc/formMetadata.cjs.map +1 -1
  96. package/dist/rpc/formMetadata.d.ts +3 -1
  97. package/dist/rpc/formMetadata.d.ts.map +1 -1
  98. package/dist/rpc/formMetadata.js +31 -0
  99. package/dist/rpc/formMetadata.js.map +1 -1
  100. package/dist/rpc/types.cjs.map +1 -1
  101. package/dist/rpc/types.d.ts +7 -0
  102. package/dist/rpc/types.d.ts.map +1 -1
  103. package/dist/rpc/types.js.map +1 -1
  104. package/dist/styles.css +1 -1
  105. package/package.json +1 -1
@@ -16,6 +16,8 @@ const StatusDot = require("./cells/StatusDot.cjs");
16
16
  const statusMapping = require("./cells/status-mapping.cjs");
17
17
  const UiEllipsis = require("../icons/components/UiEllipsis.cjs");
18
18
  const UiFilter = require("../icons/components/UiFilter.cjs");
19
+ const UiArrowLeft = require("../icons/components/UiArrowLeft.cjs");
20
+ const UiArrowRight = require("../icons/components/UiArrowRight.cjs");
19
21
  const UiEyeClosed = require("../icons/components/UiEyeClosed.cjs");
20
22
  const UiClose = require("../icons/components/UiClose.cjs");
21
23
  const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
@@ -92,7 +94,8 @@ function DataTableInner({
92
94
  themeMenuValue,
93
95
  onThemeChange,
94
96
  showThemeControl = false,
95
- showHeaderFilters = true
97
+ showHeaderFilters = true,
98
+ menuActions
96
99
  }) {
97
100
  const columns = react.useMemo(
98
101
  () => columnsInput.map(
@@ -147,7 +150,11 @@ function DataTableInner({
147
150
  const next = pruneColumnWidths({ ...stored, ...current }, columns);
148
151
  return sameColumnWidths(current, next) ? current : next;
149
152
  });
150
- }, [columnKeysSignature, persistColumnWidths, resolvedColumnResizeStorageKey]);
153
+ }, [
154
+ columnKeysSignature,
155
+ persistColumnWidths,
156
+ resolvedColumnResizeStorageKey
157
+ ]);
151
158
  react.useEffect(() => {
152
159
  if (!persistColumnWidths) return;
153
160
  writeStoredColumnWidths(resolvedColumnResizeStorageKey, columnWidths);
@@ -158,21 +165,39 @@ function DataTableInner({
158
165
  const next = pruneHiddenColumns({ ...stored, ...current }, columns);
159
166
  return sameHiddenColumns(current, next) ? current : next;
160
167
  });
161
- }, [columnKeysSignature, persistColumnVisibility, resolvedColumnVisibilityStorageKey]);
168
+ }, [
169
+ columnKeysSignature,
170
+ persistColumnVisibility,
171
+ resolvedColumnVisibilityStorageKey
172
+ ]);
162
173
  react.useEffect(() => {
163
174
  if (!persistColumnVisibility) return;
164
175
  writeStoredHiddenColumns(resolvedColumnVisibilityStorageKey, hiddenColumns);
165
- }, [hiddenColumns, persistColumnVisibility, resolvedColumnVisibilityStorageKey]);
176
+ }, [
177
+ hiddenColumns,
178
+ persistColumnVisibility,
179
+ resolvedColumnVisibilityStorageKey
180
+ ]);
166
181
  react.useEffect(() => {
167
182
  if (densityControlled) return;
168
183
  setLocalDensityOverride(
169
184
  persistDensity ? readStoredDensityOverride(resolvedDensityStorageKey) ?? defaultDensity : defaultDensity
170
185
  );
171
- }, [defaultDensity, densityControlled, persistDensity, resolvedDensityStorageKey]);
186
+ }, [
187
+ defaultDensity,
188
+ densityControlled,
189
+ persistDensity,
190
+ resolvedDensityStorageKey
191
+ ]);
172
192
  react.useEffect(() => {
173
193
  if (densityControlled || !persistDensity) return;
174
194
  writeStoredDensityOverride(resolvedDensityStorageKey, localDensityOverride);
175
- }, [densityControlled, localDensityOverride, persistDensity, resolvedDensityStorageKey]);
195
+ }, [
196
+ densityControlled,
197
+ localDensityOverride,
198
+ persistDensity,
199
+ resolvedDensityStorageKey
200
+ ]);
176
201
  react.useEffect(() => {
177
202
  if (!columnMenu && !headerFilterMenu) return;
178
203
  const close = () => {
@@ -208,17 +233,24 @@ function DataTableInner({
208
233
  if (parsed) dates.push(parsed);
209
234
  }
210
235
  const dataFormat = Timestamp.chooseTimestampFormat(dates);
211
- out[column.key] = Timestamp.modeToFormat(((_a = column.timestamp) == null ? void 0 : _a.mode) ?? "auto", dataFormat);
236
+ out[column.key] = Timestamp.modeToFormat(
237
+ ((_a = column.timestamp) == null ? void 0 : _a.mode) ?? "auto",
238
+ dataFormat
239
+ );
212
240
  }
213
241
  return out;
214
242
  }, [columns, data]);
215
243
  const effectiveColumns = react.useMemo(
216
- () => columns.map((column) => applyKindDefaults(column, timestampFormats[column.key])),
244
+ () => columns.map(
245
+ (column) => applyKindDefaults(column, timestampFormats[column.key])
246
+ ),
217
247
  [columns, timestampFormats]
218
248
  );
219
249
  const visibleColumns = react.useMemo(() => {
220
250
  if (!hideableColumns) return effectiveColumns;
221
- const next = effectiveColumns.filter((column) => !hiddenColumns[column.key]);
251
+ const next = effectiveColumns.filter(
252
+ (column) => !hiddenColumns[column.key]
253
+ );
222
254
  return next.length > 0 ? next : effectiveColumns;
223
255
  }, [effectiveColumns, hiddenColumns, hideableColumns]);
224
256
  const hideableColumnCount = react.useMemo(
@@ -231,7 +263,8 @@ function DataTableInner({
231
263
  );
232
264
  const showColumnVisibilityControl = hideableColumns && hideableColumnCount > 1;
233
265
  const resolvedShowDensityControl = showDensityControl ?? showColumnVisibilityControl;
234
- const showTablePreferencesControl = showColumnVisibilityControl || resolvedShowDensityControl || showThemeControl;
266
+ const hasMenuActions = Boolean(menuActions && menuActions.length > 0);
267
+ const showTablePreferencesControl = showColumnVisibilityControl || resolvedShowDensityControl || showThemeControl || hasMenuActions;
235
268
  const densityOverride = densityControlled ? density : localDensityOverride;
236
269
  const timeRangeColumn = react.useMemo(() => {
237
270
  if (filterBarProps == null ? void 0 : filterBarProps.timeRange) return null;
@@ -269,7 +302,9 @@ function DataTableInner({
269
302
  if (token) values.add(token);
270
303
  }
271
304
  }
272
- const options = Array.from(values).sort((left, right) => left.localeCompare(right, void 0, { numeric: true })).map((value) => ({ value, label: value }));
305
+ const options = Array.from(values).sort(
306
+ (left, right) => left.localeCompare(right, void 0, { numeric: true })
307
+ ).map((value) => ({ value, label: value }));
273
308
  const multiCap = column.kind === "tags" ? 50 : 20;
274
309
  const fitsMulti = options.length >= 2 && options.length <= multiCap;
275
310
  if (column.kind === "tags" && fitsMulti) {
@@ -303,9 +338,15 @@ function DataTableInner({
303
338
  return out;
304
339
  }, [generatedFilters, multiFilters]);
305
340
  react.useEffect(() => {
306
- setTextFilters((current) => pruneTextFilterState(current, generatedFilters));
307
- setMultiFilters((current) => pruneMultiFilterState(current, generatedFilters));
308
- setNumberFilters((current) => pruneNumberFilterState(current, generatedFilters));
341
+ setTextFilters(
342
+ (current) => pruneTextFilterState(current, generatedFilters)
343
+ );
344
+ setMultiFilters(
345
+ (current) => pruneMultiFilterState(current, generatedFilters)
346
+ );
347
+ setNumberFilters(
348
+ (current) => pruneNumberFilterState(current, generatedFilters)
349
+ );
309
350
  }, [generatedFilters]);
310
351
  const globalFilterControlled = globalFilter !== void 0;
311
352
  const effectiveGlobalFilter = globalFilterControlled ? globalFilter : localGlobalFilter;
@@ -321,7 +362,9 @@ function DataTableInner({
321
362
  kind: "multi",
322
363
  label: labelText(filter.column),
323
364
  value: multiFilters[columnKey] ?? {},
324
- onChange: (next) => setMultiFilters((current) => updateFilterRecord(current, columnKey, next)),
365
+ onChange: (next) => setMultiFilters(
366
+ (current) => updateFilterRecord(current, columnKey, next)
367
+ ),
325
368
  options: filter.options
326
369
  };
327
370
  }
@@ -331,7 +374,9 @@ function DataTableInner({
331
374
  kind: "nested-multi",
332
375
  label: labelText(filter.column),
333
376
  value: multiFilters[columnKey] ?? {},
334
- onChange: (next) => setMultiFilters((current) => updateFilterRecord(current, columnKey, next)),
377
+ onChange: (next) => setMultiFilters(
378
+ (current) => updateFilterRecord(current, columnKey, next)
379
+ ),
335
380
  groups: filter.groups ?? []
336
381
  };
337
382
  }
@@ -341,7 +386,9 @@ function DataTableInner({
341
386
  kind: "number",
342
387
  label: labelText(filter.column),
343
388
  value: numberFilters[columnKey] ?? {},
344
- onChange: (next) => setNumberFilters((current) => updateNumberFilterValue(current, columnKey, next))
389
+ onChange: (next) => setNumberFilters(
390
+ (current) => updateNumberFilterValue(current, columnKey, next)
391
+ )
345
392
  };
346
393
  if (((_a = filter.numberBounds) == null ? void 0 : _a.min) !== void 0) {
347
394
  numberFilter.domainMin = filter.numberBounds.min;
@@ -359,7 +406,9 @@ function DataTableInner({
359
406
  kind: "text",
360
407
  label: labelText(filter.column),
361
408
  value: textFilters[columnKey] ?? "",
362
- onChange: (next) => setTextFilters((current) => updateFilterValue(current, columnKey, next))
409
+ onChange: (next) => setTextFilters(
410
+ (current) => updateFilterValue(current, columnKey, next)
411
+ )
363
412
  };
364
413
  }),
365
414
  [generatedFilters, multiFilters, numberFilters, textFilters]
@@ -401,7 +450,12 @@ function DataTableInner({
401
450
  };
402
451
  const filterBarTrailing = showTablePreferencesControl ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
403
452
  filterBarProps == null ? void 0 : filterBarProps.trailing,
404
- /* @__PURE__ */ jsxRuntime.jsx(ColumnVisibilityTrigger, { onOpen: (event) => setColumnMenu(menuStateFromTrigger(event)) })
453
+ /* @__PURE__ */ jsxRuntime.jsx(
454
+ ColumnVisibilityTrigger,
455
+ {
456
+ onOpen: (event) => setColumnMenu(menuStateFromTrigger(event))
457
+ }
458
+ )
405
459
  ] }) : filterBarProps == null ? void 0 : filterBarProps.trailing;
406
460
  const showHeaderFilterControls = autoFilter && showHeaderFilters;
407
461
  const filteredRows = react.useMemo(() => {
@@ -415,7 +469,8 @@ function DataTableInner({
415
469
  const raw = resolvePath(row, rangeColumnKey);
416
470
  const ts = Timestamp.parseTimestamp(raw);
417
471
  if (!ts) return false;
418
- if (rangeFromDate && ts.getTime() < rangeFromDate.getTime()) return false;
472
+ if (rangeFromDate && ts.getTime() < rangeFromDate.getTime())
473
+ return false;
419
474
  if (rangeToDate && ts.getTime() > rangeToDate.getTime()) return false;
420
475
  }
421
476
  if (globalNeedle) {
@@ -494,7 +549,10 @@ function DataTableInner({
494
549
  const measuredWidth = (header == null ? void 0 : header.getBoundingClientRect().width) ?? 0;
495
550
  const startWidth = measuredWidth || columnWidths[column.key] || defaultColumnWidth(column);
496
551
  const onMove = (moveEvent) => {
497
- const nextWidth = clampColumnWidth(column, startWidth + moveEvent.clientX - startX);
552
+ const nextWidth = clampColumnWidth(
553
+ column,
554
+ startWidth + moveEvent.clientX - startX
555
+ );
498
556
  setColumnWidths((current) => ({
499
557
  ...current,
500
558
  [column.key]: nextWidth
@@ -518,7 +576,9 @@ function DataTableInner({
518
576
  const header = event.currentTarget.closest("th");
519
577
  const table = event.currentTarget.closest("table");
520
578
  if (!header || !table) return;
521
- const columnIndex = Array.from(((_a = header.parentElement) == null ? void 0 : _a.children) ?? []).indexOf(header);
579
+ const columnIndex = Array.from(
580
+ ((_a = header.parentElement) == null ? void 0 : _a.children) ?? []
581
+ ).indexOf(header);
522
582
  if (columnIndex < 0) return;
523
583
  const width = measureColumnContentWidth(table, columnIndex, column);
524
584
  setColumnWidths((current) => ({
@@ -551,274 +611,325 @@ function DataTableInner({
551
611
  event.stopPropagation();
552
612
  setHeaderFilterMenu(menuStateFromTrigger(event, columnKey));
553
613
  };
554
- return /* @__PURE__ */ jsxRuntime.jsx(useDensity.DensityValueProvider, { density: densityOverride ?? "comfortable", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex min-h-0 flex-col gap-3", className), "data-density": densityOverride, children: [
555
- showFilterBar && /* @__PURE__ */ jsxRuntime.jsx(
556
- FilterBar.FilterBar,
557
- {
558
- ...filterBarProps,
559
- ...externalSearch ? { search: externalSearch } : autoFilter && showGlobalFilter ? {
560
- search: {
561
- value: effectiveGlobalFilter,
562
- onChange: setEffectiveGlobalFilter,
563
- placeholder: globalFilterPlaceholder
614
+ return /* @__PURE__ */ jsxRuntime.jsx(useDensity.DensityValueProvider, { density: densityOverride ?? "comfortable", children: /* @__PURE__ */ jsxRuntime.jsxs(
615
+ "div",
616
+ {
617
+ className: utils.cn("flex min-h-0 flex-col gap-3", className),
618
+ "data-density": densityOverride,
619
+ children: [
620
+ showFilterBar && /* @__PURE__ */ jsxRuntime.jsx(
621
+ FilterBar.FilterBar,
622
+ {
623
+ ...filterBarProps,
624
+ ...externalSearch ? { search: externalSearch } : autoFilter && showGlobalFilter ? {
625
+ search: {
626
+ value: effectiveGlobalFilter,
627
+ onChange: setEffectiveGlobalFilter,
628
+ placeholder: globalFilterPlaceholder
629
+ }
630
+ } : {},
631
+ ...externalTimeRange ? { timeRange: externalTimeRange } : autoTimeRange ? { timeRange: autoTimeRange } : {},
632
+ trailing: filterBarTrailing,
633
+ filters: externalFilters && externalFilters.length > 0 ? [...externalFilters, ...nativeFilters] : nativeFilters
564
634
  }
565
- } : {},
566
- ...externalTimeRange ? { timeRange: externalTimeRange } : autoTimeRange ? { timeRange: autoTimeRange } : {},
567
- trailing: filterBarTrailing,
568
- filters: externalFilters && externalFilters.length > 0 ? [...externalFilters, ...nativeFilters] : nativeFilters
569
- }
570
- ),
571
- /* @__PURE__ */ jsxRuntime.jsx(
572
- "div",
573
- {
574
- className: "min-h-0 flex-1 overflow-auto rounded-md border border-border",
575
- "aria-busy": loading || void 0,
576
- children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-max table-auto text-left text-sm", children: [
577
- /* @__PURE__ */ jsxRuntime.jsx("colgroup", { children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(
578
- "col",
579
- {
580
- style: columnStyle(column, columnWidths),
581
- className: column.shrink && !column.grow ? "w-px" : void 0
582
- },
583
- column.key
584
- )) }),
585
- /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 z-10 bg-muted shadow-[0_1px_0_0_var(--tw-shadow-color)] shadow-border", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border text-xs text-muted-foreground", children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsxs(
586
- "th",
587
- {
588
- className: utils.cn(
589
- "group/header relative whitespace-nowrap font-medium",
590
- DATA_TABLE_HEADER_DENSITY_CLASS,
591
- alignmentClass(column.align),
592
- resizableColumns && column.resizable !== false && "select-none",
593
- column.headerClassName
594
- ),
595
- onContextMenu: (event) => openHeaderColumnMenu(event, column),
596
- children: [
597
- /* @__PURE__ */ jsxRuntime.jsxs(
598
- "div",
599
- {
600
- className: utils.cn(
601
- "flex min-w-0 items-center gap-1",
602
- headerAlignmentClass(column.align),
603
- resizableColumns && column.resizable !== false && "pr-2"
604
- ),
605
- children: [
606
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0", children: column.sortable === false ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: column.label }) : /* @__PURE__ */ jsxRuntime.jsx(
607
- SortableHeader.SortableHeader,
608
- {
609
- active: (sort == null ? void 0 : sort.key) === column.key,
610
- ...(sort == null ? void 0 : sort.key) === column.key ? { dir: sort.dir } : {},
611
- ...column.align ? { align: column.align } : {},
612
- onClick: () => toggle(column.key),
613
- children: column.label
614
- }
615
- ) }),
616
- showHeaderFilterControls && (nativeFilterByColumn.has(column.key) || autoTimeRange && (timeRangeColumn == null ? void 0 : timeRangeColumn.key) === column.key) && /* @__PURE__ */ jsxRuntime.jsx(
617
- HeaderFilterButton,
618
- {
619
- column,
620
- active: nativeFilterByColumn.has(column.key) ? FilterBar.isFilterBarFilterActive(nativeFilterByColumn.get(column.key)) : Boolean(timeRangeFilter.from || timeRangeFilter.to),
621
- onOpen: (event) => openHeaderFilterMenu(event, column.key)
622
- }
623
- )
624
- ]
625
- }
626
- ),
627
- resizableColumns && column.resizable !== false && /* @__PURE__ */ jsxRuntime.jsx(
628
- "span",
629
- {
630
- role: "separator",
631
- "aria-label": `Resize ${labelText(column)} column`,
632
- "aria-orientation": "vertical",
633
- className: "absolute right-0 top-0 flex h-full w-3 cursor-col-resize touch-none items-center justify-center border-r border-border/70 bg-gradient-to-l from-border/30 to-transparent transition-colors hover:border-primary hover:from-primary/20",
634
- onClick: (event) => {
635
- event.preventDefault();
636
- event.stopPropagation();
637
- },
638
- onDoubleClick: (event) => autoFitColumn(event, column),
639
- onMouseDown: (event) => startColumnResize(event, column),
640
- children: /* @__PURE__ */ jsxRuntime.jsx(
641
- "span",
642
- {
643
- "aria-hidden": true,
644
- className: "h-4 w-0.5 rounded-full bg-border transition-colors group-hover/header:bg-primary/70"
645
- }
646
- )
647
- }
648
- )
649
- ]
650
- },
651
- column.key
652
- )) }) }),
653
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(
654
- DataTableLoadingRows,
655
- {
656
- columns: visibleColumns,
657
- rowCount: loadingRowCount,
658
- message: loadingMessage
659
- }
660
- ) : sorted.map((record) => {
661
- const href = getRowHref == null ? void 0 : getRowHref(record.row);
662
- const expanded = expandedRows[record.id] ?? false;
663
- const expandedContent = (renderExpandedRow == null ? void 0 : renderExpandedRow(record.row, {
664
- columns: effectiveColumns,
665
- visibleColumns,
666
- filterActionsByColumn
667
- })) ?? null;
668
- const expandable = expandedContent !== null;
669
- const expandsInline = expandable && detailStyle === "row";
670
- const opensDialog = expandable && detailStyle === "dialog";
671
- const rowClickEnabled = (isRowClickable == null ? void 0 : isRowClickable(record.row)) ?? !!onRowClick;
672
- const clickable = !!href || rowClickEnabled || expandable;
673
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
674
- /* @__PURE__ */ jsxRuntime.jsx(
675
- "tr",
635
+ ),
636
+ /* @__PURE__ */ jsxRuntime.jsx(
637
+ "div",
638
+ {
639
+ className: "min-h-0 flex-1 overflow-auto rounded-md border border-border",
640
+ "aria-busy": loading || void 0,
641
+ children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-max table-auto text-left text-sm", children: [
642
+ /* @__PURE__ */ jsxRuntime.jsx("colgroup", { children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(
643
+ "col",
644
+ {
645
+ style: columnStyle(column, columnWidths),
646
+ className: column.shrink && !column.grow ? "w-px" : void 0
647
+ },
648
+ column.key
649
+ )) }),
650
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 z-10 bg-muted shadow-[0_1px_0_0_var(--tw-shadow-color)] shadow-border", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border text-xs text-muted-foreground", children: visibleColumns.map((column) => /* @__PURE__ */ jsxRuntime.jsxs(
651
+ "th",
676
652
  {
677
653
  className: utils.cn(
678
- "border-b border-border/60 align-top",
679
- clickable && "cursor-pointer hover:bg-accent/40"
654
+ "group/header relative whitespace-nowrap font-medium",
655
+ DATA_TABLE_HEADER_DENSITY_CLASS,
656
+ alignmentClass(column.align),
657
+ resizableColumns && column.resizable !== false && "select-none",
658
+ column.headerClassName
680
659
  ),
681
- onClick: () => {
682
- if (expandsInline) {
683
- setExpandedRows((current) => ({
684
- ...current,
685
- [record.id]: !current[record.id]
686
- }));
687
- }
688
- if (opensDialog) {
689
- setDetailRow(record);
690
- }
691
- if (rowClickEnabled) {
692
- onRowClick == null ? void 0 : onRowClick(record.row);
693
- }
694
- },
695
- children: visibleColumns.map((column, index) => {
696
- const rawValue = resolvePath(record.row, column.key);
697
- let content = column.render ? column.render(rawValue, record.row) : formatCell(rawValue);
698
- if (column.kind === "tags" && filterActionsByColumn[column.key]) {
699
- content = /* @__PURE__ */ jsxRuntime.jsx(TagList.TagActionsProvider, { value: filterActionsByColumn[column.key], children: content });
700
- }
701
- return /* @__PURE__ */ jsxRuntime.jsx(
702
- "td",
660
+ onContextMenu: (event) => openHeaderColumnMenu(event, column),
661
+ children: [
662
+ /* @__PURE__ */ jsxRuntime.jsxs(
663
+ "div",
703
664
  {
704
665
  className: utils.cn(
705
- DATA_TABLE_CELL_DENSITY_CLASS,
706
- alignmentClass(column.align),
707
- column.cellClassName
666
+ "flex min-w-0 items-center gap-1",
667
+ headerAlignmentClass(column.align),
668
+ resizableColumns && column.resizable !== false && "pr-2"
708
669
  ),
670
+ children: [
671
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0", children: column.sortable === false ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: column.label }) : /* @__PURE__ */ jsxRuntime.jsx(
672
+ SortableHeader.SortableHeader,
673
+ {
674
+ active: (sort == null ? void 0 : sort.key) === column.key,
675
+ ...(sort == null ? void 0 : sort.key) === column.key ? { dir: sort.dir } : {},
676
+ ...column.align ? { align: column.align } : {},
677
+ onClick: () => toggle(column.key),
678
+ children: column.label
679
+ }
680
+ ) }),
681
+ showHeaderFilterControls && (nativeFilterByColumn.has(column.key) || autoTimeRange && (timeRangeColumn == null ? void 0 : timeRangeColumn.key) === column.key) && /* @__PURE__ */ jsxRuntime.jsx(
682
+ HeaderFilterButton,
683
+ {
684
+ column,
685
+ active: nativeFilterByColumn.has(column.key) ? FilterBar.isFilterBarFilterActive(
686
+ nativeFilterByColumn.get(column.key)
687
+ ) : Boolean(
688
+ timeRangeFilter.from || timeRangeFilter.to
689
+ ),
690
+ onOpen: (event) => openHeaderFilterMenu(event, column.key)
691
+ }
692
+ )
693
+ ]
694
+ }
695
+ ),
696
+ resizableColumns && column.resizable !== false && /* @__PURE__ */ jsxRuntime.jsx(
697
+ "span",
698
+ {
699
+ role: "separator",
700
+ "aria-label": `Resize ${labelText(column)} column`,
701
+ "aria-orientation": "vertical",
702
+ className: "absolute right-0 top-0 flex h-full w-3 cursor-col-resize touch-none items-center justify-center border-r border-border/70 bg-gradient-to-l from-border/30 to-transparent transition-colors hover:border-primary hover:from-primary/20",
703
+ onClick: (event) => {
704
+ event.preventDefault();
705
+ event.stopPropagation();
706
+ },
707
+ onDoubleClick: (event) => autoFitColumn(event, column),
708
+ onMouseDown: (event) => startColumnResize(event, column),
709
709
  children: /* @__PURE__ */ jsxRuntime.jsx(
710
- CellContent,
710
+ "span",
711
711
  {
712
- column,
713
- ...columnWidths[column.key] !== void 0 ? { width: columnWidths[column.key] } : {},
714
- children: href && index === 0 ? /* @__PURE__ */ jsxRuntime.jsx("a", { href, className: "hover:underline", children: content }) : content
712
+ "aria-hidden": true,
713
+ className: "h-4 w-0.5 rounded-full bg-border transition-colors group-hover/header:bg-primary/70"
715
714
  }
716
715
  )
717
- },
718
- column.key
719
- );
720
- })
716
+ }
717
+ )
718
+ ]
719
+ },
720
+ column.key
721
+ )) }) }),
722
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: loading ? /* @__PURE__ */ jsxRuntime.jsx(
723
+ DataTableLoadingRows,
724
+ {
725
+ columns: visibleColumns,
726
+ rowCount: loadingRowCount,
727
+ message: loadingMessage
721
728
  }
722
- ),
723
- expandsInline && expanded && expandedContent && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: visibleColumns.length, className: "bg-muted/40 p-density-3", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-border bg-background p-density-3", children: expandedContent }) }) })
724
- ] }, record.id);
725
- }) })
726
- ] })
727
- }
728
- ),
729
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 text-xs text-muted-foreground", children: loading ? loadingMessage : `${sorted.length} of ${data.length} row${data.length === 1 ? "" : "s"}` }),
730
- pagination && /* @__PURE__ */ jsxRuntime.jsx(DataTablePaginationFooter, { pagination }),
731
- detailStyle === "dialog" && renderExpandedRow && /* @__PURE__ */ jsxRuntime.jsx(
732
- Modal.Modal,
733
- {
734
- open: detailRow !== null,
735
- onClose: () => setDetailRow(null),
736
- size: detailDialogSize,
737
- ...detailRow ? {
738
- title: typeof detailDialogTitle === "function" ? detailDialogTitle(detailRow.row) : detailDialogTitle
739
- } : {},
740
- children: detailRow ? renderExpandedRow(detailRow.row, {
741
- columns: effectiveColumns,
742
- visibleColumns,
743
- filterActionsByColumn
744
- }) : null
745
- }
746
- ),
747
- columnMenu && showTablePreferencesControl && /* @__PURE__ */ jsxRuntime.jsx(
748
- ColumnVisibilityMenu,
749
- {
750
- columns: effectiveColumns,
751
- hiddenColumns,
752
- anchor: columnMenu,
753
- showColumnVisibilityControl,
754
- showDensityControl: resolvedShowDensityControl,
755
- showThemeControl,
756
- themeMenuValue,
757
- densityOverride,
758
- visibleHideableColumnCount,
759
- onToggle: toggleColumnVisibility,
760
- onShowAll: showAllColumns,
761
- onDensityChange: setDensityOverride,
762
- ...onThemeChange ? { onThemeChange } : {},
763
- onClose: () => setColumnMenu(null)
764
- }
765
- ),
766
- headerFilterMenu && /* @__PURE__ */ jsxRuntime.jsx(
767
- HeaderFilterMenu,
768
- {
769
- filter: nativeFilterByColumn.get(headerFilterMenu.columnKey ?? ""),
770
- ...autoTimeRange && (timeRangeColumn == null ? void 0 : timeRangeColumn.key) === headerFilterMenu.columnKey ? { timeRange: autoTimeRange } : {},
771
- anchor: headerFilterMenu,
772
- onClose: () => setHeaderFilterMenu(null)
773
- }
774
- )
775
- ] }) });
729
+ ) : sorted.map((record) => {
730
+ const href = getRowHref == null ? void 0 : getRowHref(record.row);
731
+ const expanded = expandedRows[record.id] ?? false;
732
+ const expandedContent = (renderExpandedRow == null ? void 0 : renderExpandedRow(record.row, {
733
+ columns: effectiveColumns,
734
+ visibleColumns,
735
+ filterActionsByColumn
736
+ })) ?? null;
737
+ const expandable = expandedContent !== null;
738
+ const expandsInline = expandable && detailStyle === "row";
739
+ const opensDialog = expandable && detailStyle === "dialog";
740
+ const rowClickEnabled = (isRowClickable == null ? void 0 : isRowClickable(record.row)) ?? !!onRowClick;
741
+ const clickable = !!href || rowClickEnabled || expandable;
742
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
743
+ /* @__PURE__ */ jsxRuntime.jsx(
744
+ "tr",
745
+ {
746
+ className: utils.cn(
747
+ "border-b border-border/60 align-top",
748
+ clickable && "cursor-pointer hover:bg-accent/40"
749
+ ),
750
+ onClick: () => {
751
+ if (expandsInline) {
752
+ setExpandedRows((current) => ({
753
+ ...current,
754
+ [record.id]: !current[record.id]
755
+ }));
756
+ }
757
+ if (opensDialog) {
758
+ setDetailRow(record);
759
+ }
760
+ if (rowClickEnabled) {
761
+ onRowClick == null ? void 0 : onRowClick(record.row);
762
+ }
763
+ },
764
+ children: visibleColumns.map((column, index) => {
765
+ const rawValue = resolvePath(record.row, column.key);
766
+ let content = column.render ? column.render(rawValue, record.row) : formatCell(rawValue);
767
+ if (column.kind === "tags" && filterActionsByColumn[column.key]) {
768
+ content = /* @__PURE__ */ jsxRuntime.jsx(
769
+ TagList.TagActionsProvider,
770
+ {
771
+ value: filterActionsByColumn[column.key],
772
+ children: content
773
+ }
774
+ );
775
+ }
776
+ return /* @__PURE__ */ jsxRuntime.jsx(
777
+ "td",
778
+ {
779
+ className: utils.cn(
780
+ DATA_TABLE_CELL_DENSITY_CLASS,
781
+ alignmentClass(column.align),
782
+ column.cellClassName
783
+ ),
784
+ children: /* @__PURE__ */ jsxRuntime.jsx(
785
+ CellContent,
786
+ {
787
+ column,
788
+ ...columnWidths[column.key] !== void 0 ? { width: columnWidths[column.key] } : {},
789
+ children: href && index === 0 ? /* @__PURE__ */ jsxRuntime.jsx("a", { href, className: "hover:underline", children: content }) : content
790
+ }
791
+ )
792
+ },
793
+ column.key
794
+ );
795
+ })
796
+ }
797
+ ),
798
+ expandsInline && expanded && expandedContent && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
799
+ "td",
800
+ {
801
+ colSpan: visibleColumns.length,
802
+ className: "bg-muted/40 p-density-3",
803
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-border bg-background p-density-3", children: expandedContent })
804
+ }
805
+ ) })
806
+ ] }, record.id);
807
+ }) })
808
+ ] })
809
+ }
810
+ ),
811
+ pagination ? /* @__PURE__ */ jsxRuntime.jsx(
812
+ DataTablePaginationFooter,
813
+ {
814
+ pagination,
815
+ visibleRowCount: sorted.length,
816
+ loading,
817
+ loadingMessage
818
+ }
819
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 text-xs text-muted-foreground", children: loading ? loadingMessage : `${sorted.length} of ${data.length} row${data.length === 1 ? "" : "s"}` }),
820
+ detailStyle === "dialog" && renderExpandedRow && /* @__PURE__ */ jsxRuntime.jsx(
821
+ Modal.Modal,
822
+ {
823
+ open: detailRow !== null,
824
+ onClose: () => setDetailRow(null),
825
+ size: detailDialogSize,
826
+ ...detailRow ? {
827
+ title: typeof detailDialogTitle === "function" ? detailDialogTitle(detailRow.row) : detailDialogTitle
828
+ } : {},
829
+ children: detailRow ? renderExpandedRow(detailRow.row, {
830
+ columns: effectiveColumns,
831
+ visibleColumns,
832
+ filterActionsByColumn
833
+ }) : null
834
+ }
835
+ ),
836
+ columnMenu && showTablePreferencesControl && /* @__PURE__ */ jsxRuntime.jsx(
837
+ ColumnVisibilityMenu,
838
+ {
839
+ columns: effectiveColumns,
840
+ hiddenColumns,
841
+ anchor: columnMenu,
842
+ actions: menuActions ?? [],
843
+ showColumnVisibilityControl,
844
+ showDensityControl: resolvedShowDensityControl,
845
+ showThemeControl,
846
+ themeMenuValue,
847
+ densityOverride,
848
+ visibleHideableColumnCount,
849
+ onToggle: toggleColumnVisibility,
850
+ onShowAll: showAllColumns,
851
+ onDensityChange: setDensityOverride,
852
+ ...onThemeChange ? { onThemeChange } : {},
853
+ onClose: () => setColumnMenu(null)
854
+ }
855
+ ),
856
+ headerFilterMenu && /* @__PURE__ */ jsxRuntime.jsx(
857
+ HeaderFilterMenu,
858
+ {
859
+ filter: nativeFilterByColumn.get(headerFilterMenu.columnKey ?? ""),
860
+ ...autoTimeRange && (timeRangeColumn == null ? void 0 : timeRangeColumn.key) === headerFilterMenu.columnKey ? { timeRange: autoTimeRange } : {},
861
+ anchor: headerFilterMenu,
862
+ onClose: () => setHeaderFilterMenu(null)
863
+ }
864
+ )
865
+ ]
866
+ }
867
+ ) });
776
868
  }
777
- const DEFAULT_PAGE_SIZE_OPTIONS = [25, 50, 100, 200];
778
- function DataTablePaginationFooter({ pagination }) {
869
+ const DEFAULT_PAGE_SIZE_OPTIONS = [5, 10, 25, 50, 100, 200];
870
+ function DataTablePaginationFooter({
871
+ pagination,
872
+ visibleRowCount,
873
+ loading,
874
+ loadingMessage
875
+ }) {
779
876
  const { page, pageSize, total, onPageChange, onPageSizeChange } = pagination;
780
- const options = pagination.pageSizeOptions ?? DEFAULT_PAGE_SIZE_OPTIONS;
877
+ const options = Array.from(
878
+ /* @__PURE__ */ new Set([
879
+ ...pagination.pageSizeOptions ?? DEFAULT_PAGE_SIZE_OPTIONS,
880
+ pageSize
881
+ ])
882
+ ).sort((a, b) => a - b);
781
883
  const safePage = Math.max(0, page);
782
884
  const totalPages = total != null && pageSize > 0 ? Math.max(1, Math.ceil(total / pageSize)) : void 0;
783
885
  const atFirst = safePage === 0;
784
- const atLast = totalPages != null ? safePage >= totalPages - 1 : false;
785
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 px-1 text-xs text-muted-foreground", children: [
786
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-1", children: [
787
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Rows per page" }),
788
- /* @__PURE__ */ jsxRuntime.jsx(
789
- "select",
790
- {
791
- className: "rounded border border-border bg-background px-1 py-0.5 text-foreground",
792
- value: pageSize,
793
- onChange: (event) => onPageSizeChange(Number(event.target.value)),
794
- children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option, children: option }, option))
795
- }
796
- )
797
- ] }),
798
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
886
+ const atLast = totalPages != null ? safePage >= totalPages - 1 : visibleRowCount < pageSize;
887
+ const hasVisibleRows = visibleRowCount > 0;
888
+ const rangeStart = total === 0 || !hasVisibleRows ? 0 : safePage * pageSize + 1;
889
+ const rangeEnd = total != null && hasVisibleRows ? Math.min(total, rangeStart + Math.max(visibleRowCount - 1, 0)) : visibleRowCount;
890
+ const rangeLabel = total != null ? `${rangeStart}-${rangeEnd} of ${total}` : `${visibleRowCount} row${visibleRowCount === 1 ? "" : "s"}`;
891
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-9 flex-wrap items-center justify-between gap-3 border-t border-border/70 px-1 pt-2 text-xs text-muted-foreground", children: [
892
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-live": "polite", children: loading ? loadingMessage : rangeLabel }),
893
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
894
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-1.5", children: [
895
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Rows per page" }),
896
+ /* @__PURE__ */ jsxRuntime.jsx(
897
+ "select",
898
+ {
899
+ className: "h-7 rounded-md border border-border bg-background px-2 text-xs text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
900
+ value: pageSize,
901
+ onChange: (event) => onPageSizeChange(Number(event.target.value)),
902
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option, children: option }, option))
903
+ }
904
+ )
905
+ ] }),
906
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-20 text-center", children: [
907
+ "Page ",
908
+ safePage + 1,
909
+ totalPages != null ? ` of ${totalPages}` : ""
910
+ ] }),
799
911
  /* @__PURE__ */ jsxRuntime.jsx(
800
912
  "button",
801
913
  {
802
914
  type: "button",
803
- className: "rounded border border-border px-2 py-0.5 disabled:opacity-50",
915
+ className: "inline-flex h-7 w-7 items-center justify-center rounded-md border border-border bg-background text-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
916
+ "aria-label": "Previous page",
917
+ title: "Previous page",
804
918
  disabled: atFirst,
805
919
  onClick: () => onPageChange(Math.max(0, safePage - 1)),
806
- children: "Prev"
920
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowLeft.UiArrowLeft })
807
921
  }
808
922
  ),
809
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
810
- "Page ",
811
- safePage + 1,
812
- totalPages != null ? ` of ${totalPages}` : ""
813
- ] }),
814
923
  /* @__PURE__ */ jsxRuntime.jsx(
815
924
  "button",
816
925
  {
817
926
  type: "button",
818
- className: "rounded border border-border px-2 py-0.5 disabled:opacity-50",
927
+ className: "inline-flex h-7 w-7 items-center justify-center rounded-md border border-border bg-background text-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
928
+ "aria-label": "Next page",
929
+ title: "Next page",
819
930
  disabled: atLast,
820
931
  onClick: () => onPageChange(safePage + 1),
821
- children: "Next"
932
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiArrowRight.UiArrowRight })
822
933
  }
823
934
  )
824
935
  ] })
@@ -832,19 +943,26 @@ function DataTableLoadingRows({
832
943
  const safeRowCount = Math.max(1, rowCount);
833
944
  const safeColumnCount = Math.max(1, columns.length);
834
945
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
835
- /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border/60 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsxs("td", { colSpan: safeColumnCount, className: utils.cn(DATA_TABLE_CELL_DENSITY_CLASS, "p-0"), children: [
836
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-0.5 w-full overflow-hidden bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-1/2 animate-pulse rounded-full bg-primary/70" }) }),
837
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground", children: [
838
- /* @__PURE__ */ jsxRuntime.jsx(
839
- "span",
840
- {
841
- "aria-hidden": true,
842
- className: "h-3 w-3 animate-spin rounded-full border-2 border-primary/30 border-t-primary"
843
- }
844
- ),
845
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
846
- ] })
847
- ] }) }),
946
+ /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border/60 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsxs(
947
+ "td",
948
+ {
949
+ colSpan: safeColumnCount,
950
+ className: utils.cn(DATA_TABLE_CELL_DENSITY_CLASS, "p-0"),
951
+ children: [
952
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-0.5 w-full overflow-hidden bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-1/2 animate-pulse rounded-full bg-primary/70" }) }),
953
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground", children: [
954
+ /* @__PURE__ */ jsxRuntime.jsx(
955
+ "span",
956
+ {
957
+ "aria-hidden": true,
958
+ className: "h-3 w-3 animate-spin rounded-full border-2 border-primary/30 border-t-primary"
959
+ }
960
+ ),
961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
962
+ ] })
963
+ ]
964
+ }
965
+ ) }),
848
966
  Array.from({ length: safeRowCount }).map((_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border/60", children: columns.map((column, columnIndex) => /* @__PURE__ */ jsxRuntime.jsx(
849
967
  "td",
850
968
  {
@@ -885,7 +1003,14 @@ function HeaderFilterButton({
885
1003
  active && "bg-accent text-foreground"
886
1004
  ),
887
1005
  onClick: onOpen,
888
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "data-filter-icon-state": active ? "filled" : "outline", className: "inline-flex", children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: active ? UiFilter.UiFilterFilled : UiFilter.UiFilter, className: "text-xs" }) })
1006
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1007
+ "span",
1008
+ {
1009
+ "data-filter-icon-state": active ? "filled" : "outline",
1010
+ className: "inline-flex",
1011
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: active ? UiFilter.UiFilterFilled : UiFilter.UiFilter, className: "text-xs" })
1012
+ }
1013
+ )
889
1014
  }
890
1015
  );
891
1016
  }
@@ -974,6 +1099,7 @@ function ColumnVisibilityMenu({
974
1099
  columns,
975
1100
  hiddenColumns,
976
1101
  anchor,
1102
+ actions,
977
1103
  showColumnVisibilityControl,
978
1104
  showDensityControl,
979
1105
  showThemeControl,
@@ -988,6 +1114,7 @@ function ColumnVisibilityMenu({
988
1114
  }) {
989
1115
  const activeColumn = anchor.columnKey ? columns.find((column) => column.key === anchor.columnKey) : void 0;
990
1116
  const canHideActiveColumn = activeColumn && isColumnHideable(activeColumn) && visibleHideableColumnCount > 1;
1117
+ const hasActions = actions.length > 0;
991
1118
  return /* @__PURE__ */ jsxRuntime.jsxs(
992
1119
  "div",
993
1120
  {
@@ -1028,7 +1155,13 @@ function ColumnVisibilityMenu({
1028
1155
  onClose();
1029
1156
  },
1030
1157
  children: [
1031
- /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiEyeClosed.UiEyeClosed, className: "text-sm text-muted-foreground" }),
1158
+ /* @__PURE__ */ jsxRuntime.jsx(
1159
+ Icon.Icon,
1160
+ {
1161
+ icon: UiEyeClosed.UiEyeClosed,
1162
+ className: "text-sm text-muted-foreground"
1163
+ }
1164
+ ),
1032
1165
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1033
1166
  "Hide ",
1034
1167
  labelText(activeColumn)
@@ -1075,11 +1208,19 @@ function ColumnVisibilityMenu({
1075
1208
  onDensityChange
1076
1209
  }
1077
1210
  ),
1211
+ hasActions && /* @__PURE__ */ jsxRuntime.jsx(
1212
+ MenuActionSection,
1213
+ {
1214
+ actions,
1215
+ separated: showColumnVisibilityControl || showDensityControl,
1216
+ onClose
1217
+ }
1218
+ ),
1078
1219
  showThemeControl && onThemeChange && /* @__PURE__ */ jsxRuntime.jsx(
1079
1220
  ThemeMenuSection,
1080
1221
  {
1081
1222
  value: themeMenuValue ?? "system",
1082
- separated: showColumnVisibilityControl || showDensityControl,
1223
+ separated: showColumnVisibilityControl || showDensityControl || hasActions,
1083
1224
  onChange: onThemeChange
1084
1225
  }
1085
1226
  )
@@ -1087,6 +1228,54 @@ function ColumnVisibilityMenu({
1087
1228
  }
1088
1229
  );
1089
1230
  }
1231
+ function MenuActionSection({
1232
+ actions,
1233
+ separated,
1234
+ onClose
1235
+ }) {
1236
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn(separated && "mt-1 border-t border-border pt-1"), children: [
1237
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1.5 text-xs font-medium text-muted-foreground", children: "Download" }),
1238
+ actions.map((action) => {
1239
+ const hasDescription = Boolean(action.description);
1240
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1241
+ "button",
1242
+ {
1243
+ type: "button",
1244
+ role: "menuitem",
1245
+ disabled: action.disabled,
1246
+ className: utils.cn(
1247
+ "flex w-full gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:outline-none",
1248
+ hasDescription ? "items-start" : "items-center",
1249
+ action.disabled && "cursor-not-allowed opacity-50"
1250
+ ),
1251
+ onClick: () => {
1252
+ if (action.disabled) return;
1253
+ action.onSelect();
1254
+ onClose();
1255
+ },
1256
+ children: [
1257
+ action.icon && /* @__PURE__ */ jsxRuntime.jsx(
1258
+ Icon.Icon,
1259
+ {
1260
+ icon: action.icon,
1261
+ className: utils.cn(
1262
+ "shrink-0 text-sm",
1263
+ hasDescription && "mt-0.5",
1264
+ action.iconClassName ?? "text-muted-foreground"
1265
+ )
1266
+ }
1267
+ ),
1268
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1", children: [
1269
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: utils.cn(hasDescription && "font-medium"), children: action.label }),
1270
+ action.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: action.description })
1271
+ ] })
1272
+ ]
1273
+ },
1274
+ action.id
1275
+ );
1276
+ })
1277
+ ] });
1278
+ }
1090
1279
  function DensityMenuSection({
1091
1280
  densityOverride,
1092
1281
  separated,
@@ -1104,7 +1293,13 @@ function DensityMenuSection({
1104
1293
  className: densityMenuItemClassName(current === "inherit"),
1105
1294
  onClick: () => onDensityChange(void 0),
1106
1295
  children: [
1107
- /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiResizeVertical.UiResizeVertical, className: "text-sm text-muted-foreground" }),
1296
+ /* @__PURE__ */ jsxRuntime.jsx(
1297
+ Icon.Icon,
1298
+ {
1299
+ icon: UiResizeVertical.UiResizeVertical,
1300
+ className: "text-sm text-muted-foreground"
1301
+ }
1302
+ ),
1108
1303
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: "Use page density" }),
1109
1304
  current === "inherit" ? /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiCheck.UiCheck, className: "text-sm text-foreground" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block h-4 w-4", "aria-hidden": true })
1110
1305
  ]
@@ -1121,7 +1316,13 @@ function DensityMenuSection({
1121
1316
  className: densityMenuItemClassName(active),
1122
1317
  onClick: () => onDensityChange(option.value),
1123
1318
  children: [
1124
- /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: option.icon, className: "text-sm text-muted-foreground" }),
1319
+ /* @__PURE__ */ jsxRuntime.jsx(
1320
+ Icon.Icon,
1321
+ {
1322
+ icon: option.icon,
1323
+ className: "text-sm text-muted-foreground"
1324
+ }
1325
+ ),
1125
1326
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
1126
1327
  active ? /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiCheck.UiCheck, className: "text-sm text-foreground" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block h-4 w-4", "aria-hidden": true })
1127
1328
  ]
@@ -1160,7 +1361,13 @@ function ThemeMenuSection({
1160
1361
  className: densityMenuItemClassName(active),
1161
1362
  onClick: () => onChange(option.value),
1162
1363
  children: [
1163
- /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: option.icon, className: "text-sm text-muted-foreground" }),
1364
+ /* @__PURE__ */ jsxRuntime.jsx(
1365
+ Icon.Icon,
1366
+ {
1367
+ icon: option.icon,
1368
+ className: "text-sm text-muted-foreground"
1369
+ }
1370
+ ),
1164
1371
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label }),
1165
1372
  active ? /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiCheck.UiCheck, className: "text-sm text-foreground" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block h-4 w-4", "aria-hidden": true })
1166
1373
  ]
@@ -1178,7 +1385,10 @@ function CellContent({
1178
1385
  return /* @__PURE__ */ jsxRuntime.jsx(
1179
1386
  "div",
1180
1387
  {
1181
- className: utils.cn(cellContentClassName(column, width), alignmentClass(column.align)),
1388
+ className: utils.cn(
1389
+ cellContentClassName(column, width),
1390
+ alignmentClass(column.align)
1391
+ ),
1182
1392
  style: cellContentStyle(width),
1183
1393
  children
1184
1394
  }
@@ -1244,11 +1454,18 @@ function updateNumberFilterValue(state, key, nextValue) {
1244
1454
  return next;
1245
1455
  }
1246
1456
  function pruneTextFilterState(state, filters) {
1247
- return pruneFilterState(state, filters, "text", (value) => !String(value ?? "").trim());
1457
+ return pruneFilterState(
1458
+ state,
1459
+ filters,
1460
+ "text",
1461
+ (value) => !String(value ?? "").trim()
1462
+ );
1248
1463
  }
1249
1464
  function pruneMultiFilterState(state, filters) {
1250
1465
  const allowed = new Set(
1251
- filters.filter((filter) => filter.kind === "multi" || filter.kind === "nested-multi").map((filter) => filter.column.key)
1466
+ filters.filter(
1467
+ (filter) => filter.kind === "multi" || filter.kind === "nested-multi"
1468
+ ).map((filter) => filter.column.key)
1252
1469
  );
1253
1470
  return Object.fromEntries(
1254
1471
  Object.entries(state).filter(
@@ -1269,7 +1486,9 @@ function pruneFilterState(state, filters, kind, isEmpty) {
1269
1486
  filters.filter((filter) => filter.kind === kind).map((filter) => filter.column.key)
1270
1487
  );
1271
1488
  return Object.fromEntries(
1272
- Object.entries(state).filter(([key, value]) => allowed.has(key) && !isEmpty(value))
1489
+ Object.entries(state).filter(
1490
+ ([key, value]) => allowed.has(key) && !isEmpty(value)
1491
+ )
1273
1492
  );
1274
1493
  }
1275
1494
  function resolvePath(obj, path) {
@@ -1395,8 +1614,14 @@ function menuStateFromPointer(event, columnKey) {
1395
1614
  const viewportWidth = typeof window === "undefined" ? event.clientX + width : window.innerWidth;
1396
1615
  const viewportHeight = typeof window === "undefined" ? event.clientY + height : window.innerHeight;
1397
1616
  const position = {
1398
- x: Math.max(padding, Math.min(event.clientX, viewportWidth - width - padding)),
1399
- y: Math.max(padding, Math.min(event.clientY, viewportHeight - height - padding))
1617
+ x: Math.max(
1618
+ padding,
1619
+ Math.min(event.clientX, viewportWidth - width - padding)
1620
+ ),
1621
+ y: Math.max(
1622
+ padding,
1623
+ Math.min(event.clientY, viewportHeight - height - padding)
1624
+ )
1400
1625
  };
1401
1626
  return columnKey ? { ...position, columnKey } : position;
1402
1627
  }
@@ -1408,8 +1633,14 @@ function menuStateFromTrigger(event, columnKey) {
1408
1633
  const viewportWidth = typeof window === "undefined" ? rect.right + width : window.innerWidth;
1409
1634
  const viewportHeight = typeof window === "undefined" ? rect.bottom + height : window.innerHeight;
1410
1635
  const position = {
1411
- x: Math.max(padding, Math.min(rect.right - width, viewportWidth - width - padding)),
1412
- y: Math.max(padding, Math.min(rect.bottom + 6, viewportHeight - height - padding))
1636
+ x: Math.max(
1637
+ padding,
1638
+ Math.min(rect.right - width, viewportWidth - width - padding)
1639
+ ),
1640
+ y: Math.max(
1641
+ padding,
1642
+ Math.min(rect.bottom + 6, viewportHeight - height - padding)
1643
+ )
1413
1644
  };
1414
1645
  return columnKey ? { ...position, columnKey } : position;
1415
1646
  }
@@ -1445,7 +1676,8 @@ function readStoredColumnWidths(storageKey, columns) {
1445
1676
  const raw = window.localStorage.getItem(storageKey);
1446
1677
  if (!raw) return {};
1447
1678
  const parsed = JSON.parse(raw);
1448
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
1679
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1680
+ return {};
1449
1681
  return pruneColumnWidths(parsed, columns);
1450
1682
  } catch {
1451
1683
  return {};
@@ -1468,7 +1700,8 @@ function readStoredHiddenColumns(storageKey, columns) {
1468
1700
  const raw = window.localStorage.getItem(storageKey);
1469
1701
  if (!raw) return {};
1470
1702
  const parsed = JSON.parse(raw);
1471
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
1703
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1704
+ return {};
1472
1705
  return pruneHiddenColumns(parsed, columns);
1473
1706
  } catch {
1474
1707
  return {};
@@ -1513,7 +1746,8 @@ function pruneColumnWidths(widths, columns) {
1513
1746
  const next = {};
1514
1747
  for (const [key, width] of Object.entries(widths)) {
1515
1748
  const column = byKey.get(key);
1516
- if (!column || typeof width !== "number" || !Number.isFinite(width)) continue;
1749
+ if (!column || typeof width !== "number" || !Number.isFinite(width))
1750
+ continue;
1517
1751
  next[key] = clampColumnWidth(column, width);
1518
1752
  }
1519
1753
  return next;
@@ -1527,7 +1761,9 @@ function pruneHiddenColumns(hiddenColumns, columns) {
1527
1761
  next[key] = true;
1528
1762
  }
1529
1763
  const hideable = columns.filter(isColumnHideable);
1530
- const visibleHideableCount = hideable.filter((column) => next[column.key] !== true).length;
1764
+ const visibleHideableCount = hideable.filter(
1765
+ (column) => next[column.key] !== true
1766
+ ).length;
1531
1767
  if (hideable.length > 0 && visibleHideableCount === 0) {
1532
1768
  delete next[hideable[0].key];
1533
1769
  }
@@ -1624,8 +1860,10 @@ function cellContentStyle(width) {
1624
1860
  function cellContentClassName(column, width) {
1625
1861
  const resized = width !== void 0;
1626
1862
  const base = "w-full truncate";
1627
- if (column.grow) return utils.cn(base, resized ? "min-w-0" : "min-w-56 max-w-[36rem]");
1628
- if (column.shrink) return utils.cn(base, "min-w-0 whitespace-nowrap", !resized && "max-w-[16rem]");
1863
+ if (column.grow)
1864
+ return utils.cn(base, resized ? "min-w-0" : "min-w-56 max-w-[36rem]");
1865
+ if (column.shrink)
1866
+ return utils.cn(base, "min-w-0 whitespace-nowrap", !resized && "max-w-[16rem]");
1629
1867
  return utils.cn(base, "min-w-0", !resized && "max-w-[18rem]");
1630
1868
  }
1631
1869
  function inferColumns(data) {
@@ -1717,7 +1955,10 @@ function DataTable({
1717
1955
  }
1718
1956
  );
1719
1957
  }
1720
- function FullscreenButton({ label, onClick }) {
1958
+ function FullscreenButton({
1959
+ label,
1960
+ onClick
1961
+ }) {
1721
1962
  return /* @__PURE__ */ jsxRuntime.jsx(
1722
1963
  "button",
1723
1964
  {