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