@structyl/data-table 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,4434 @@
1
+ import * as React from 'react';
2
+ import { useReactTable, getFacetedUniqueValues, getFacetedRowModel, getGroupedRowModel, getExpandedRowModel, getPaginationRowModel, getFilteredRowModel, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
3
+ export { createColumnHelper } from '@tanstack/react-table';
4
+ import { useVirtualizer } from '@tanstack/react-virtual';
5
+ import { ChevronsDown, ChevronsUpDown, Filter, GitBranch, Bookmark, RefreshCw, Printer, Minimize2, Maximize2, HelpCircle, ChevronUp, ChevronDown, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight, Columns3, X, BarChart2, AlignJustify, Download, FileSpreadsheet, Trash2 } from '@structyl/icons';
6
+ import * as XLSX from 'xlsx';
7
+ import { Input, Button, DropdownMenu, Select, Popover, Combobox, Checkbox, Drawer, Tooltip, ColorPicker } from '@structyl/styled';
8
+ import { hsvaToHex } from '@structyl/primitives';
9
+ import { cn } from '@structyl/utils';
10
+
11
+ // src/data-table.tsx
12
+ var defaultLocaleText = {
13
+ searchPlaceholder: "Search\u2026",
14
+ filters: "Filters",
15
+ filterColumnLabel: "Column",
16
+ filterOperatorLabel: "Operator",
17
+ filterValueLabel: "Value",
18
+ filterLogicLabel: "Logic",
19
+ addFilter: "Add filter",
20
+ addFilterGroup: "Add group",
21
+ clearFilters: "Clear filters",
22
+ removeFilter: "Remove filter",
23
+ removeFilterGroup: "Remove group",
24
+ columns: "Columns",
25
+ columnMenu: "Column menu",
26
+ sortAsc: "Sort ascending",
27
+ sortDesc: "Sort descending",
28
+ clearSort: "Clear sort",
29
+ hideColumn: "Hide column",
30
+ pinLeft: "Pin left",
31
+ pinRight: "Pin right",
32
+ unpin: "Unpin",
33
+ groupBy: "Group by",
34
+ ungroup: "Ungroup",
35
+ selectColumn: "Select column",
36
+ selectedRows: (selected, total) => `${selected} of ${total} row(s) selected.`,
37
+ page: (page, pageCount) => `Page ${page} of ${pageCount}`,
38
+ previous: "Previous",
39
+ next: "Next",
40
+ noResults: "No results.",
41
+ loading: "Loading\u2026",
42
+ loadingMore: "Loading more\u2026",
43
+ error: "Something went wrong.",
44
+ addRow: "Add row",
45
+ cancel: "Cancel",
46
+ save: "Save",
47
+ rowActions: "Actions",
48
+ rowTotal: "Row total",
49
+ pinRowTop: "Pin top",
50
+ pinRowBottom: "Pin bottom",
51
+ total: "Total",
52
+ expandRow: "Expand row",
53
+ collapseRow: "Collapse row",
54
+ expandAll: "Expand all",
55
+ collapseAll: "Collapse all",
56
+ copyRows: "Copy rows",
57
+ copyRow: "Copy row",
58
+ copyColumn: "Copy column",
59
+ density: "Density",
60
+ densityCompact: "Compact",
61
+ densityStandard: "Standard",
62
+ densityComfortable: "Comfortable",
63
+ refresh: "Refresh",
64
+ exportCsv: "Export CSV",
65
+ exportJson: "Export JSON",
66
+ noRows: "No data.",
67
+ rowsPerPage: "Rows per page",
68
+ totalRows: (count) => `${count.toLocaleString()} rows`,
69
+ goToPage: "Go to page",
70
+ firstPage: "First page",
71
+ lastPage: "Last page",
72
+ bulkActionsTitle: (count) => `${count} selected`,
73
+ clearSelection: "Clear selection",
74
+ rowNumberHeader: "#",
75
+ statusBarRows: (count) => `${count.toLocaleString()} rows`,
76
+ statusBarSelected: (count) => `${count} selected`,
77
+ printTitle: "Print",
78
+ enterFullscreen: "Enter fullscreen",
79
+ exitFullscreen: "Exit fullscreen",
80
+ autoSizeColumn: "Auto-size column",
81
+ quickFilterPlaceholder: "Filter\u2026",
82
+ lockColumn: "Lock column",
83
+ unlockColumn: "Unlock column",
84
+ export: "Export",
85
+ exportCSV: "Export as CSV",
86
+ exportJSON: "Export as JSON",
87
+ exportSelectedCSV: "Export selected rows",
88
+ conditionalFormatting: "Conditional formatting"
89
+ };
90
+ var liveDataStyleInjected = false;
91
+ var printStyleInjected = false;
92
+ function injectLiveDataStyles() {
93
+ if (liveDataStyleInjected || typeof document === "undefined") return;
94
+ liveDataStyleInjected = true;
95
+ const styleEl = document.createElement("style");
96
+ styleEl.textContent = `
97
+ @keyframes cell-flash {
98
+ 0% { background-color: hsl(var(--primary) / 0.3); }
99
+ 100% { background-color: transparent; }
100
+ }
101
+ @keyframes row-slide-in {
102
+ 0% { opacity: 0; transform: translateX(-12px); }
103
+ 100% { opacity: 1; transform: translateX(0); }
104
+ }
105
+ @keyframes row-fade-out {
106
+ 0% { opacity: 1; background-color: hsl(var(--destructive) / 0.12); }
107
+ 100% { opacity: 0; background-color: hsl(var(--destructive) / 0.12); }
108
+ }
109
+ [data-flash="true"] { animation: cell-flash 1.2s ease-out; }
110
+ [data-new-row="true"] { animation: row-slide-in 0.4s ease-out; }
111
+ [data-removed-row="true"] { animation: row-fade-out 0.6s ease-out forwards; }
112
+ `;
113
+ document.head.appendChild(styleEl);
114
+ }
115
+ function injectPrintStyles() {
116
+ if (printStyleInjected || typeof document === "undefined") return;
117
+ printStyleInjected = true;
118
+ const styleEl = document.createElement("style");
119
+ styleEl.textContent = `
120
+ @media print {
121
+ [data-datatable-root] .data-table-toolbar { display: none !important; }
122
+ [data-datatable-root] .data-table-pagination { display: none !important; }
123
+ [data-datatable-root] { overflow: visible !important; height: auto !important; max-height: none !important; }
124
+ [data-datatable-root] table { page-break-inside: auto; width: 100% !important; }
125
+ [data-datatable-root] tr { page-break-inside: avoid; page-break-after: auto; }
126
+ [data-datatable-root] thead { display: table-header-group; }
127
+ [data-datatable-root] tfoot { display: table-footer-group; }
128
+ }
129
+ `;
130
+ document.head.appendChild(styleEl);
131
+ }
132
+ function applyPrintTheme() {
133
+ if (typeof document === "undefined") return () => {
134
+ };
135
+ const rootStyle = getComputedStyle(document.documentElement);
136
+ const tokens = [
137
+ "--background",
138
+ "--foreground",
139
+ "--muted",
140
+ "--muted-foreground",
141
+ "--border",
142
+ "--primary",
143
+ "--primary-foreground",
144
+ "--secondary",
145
+ "--secondary-foreground",
146
+ "--accent",
147
+ "--accent-foreground",
148
+ "--card",
149
+ "--card-foreground",
150
+ "--destructive",
151
+ "--destructive-foreground",
152
+ "--ring",
153
+ "--radius",
154
+ "--sidebar",
155
+ "--sidebar-foreground"
156
+ ];
157
+ const vars = tokens.map((t) => {
158
+ const v = rootStyle.getPropertyValue(t).trim();
159
+ return v ? `${t}: ${v};` : "";
160
+ }).filter(Boolean).join(" ");
161
+ const el = document.createElement("style");
162
+ el.setAttribute("data-structyl-print-theme", "");
163
+ el.textContent = `
164
+ @media print {
165
+ :root { ${vars} color-scheme: light dark; }
166
+ *, *::before, *::after { print-color-adjust: exact !important; -webkit-print-color-adjust: exact !important; }
167
+ [data-datatable-root] .data-table-toolbar { display: none !important; }
168
+ [data-datatable-root] .data-table-pagination { display: none !important; }
169
+ [data-datatable-root] { overflow: visible !important; height: auto !important; max-height: none !important; }
170
+ [data-datatable-root] table { page-break-inside: auto; width: 100% !important; }
171
+ [data-datatable-root] tr { page-break-inside: avoid; page-break-after: auto; }
172
+ [data-datatable-root] thead { display: table-header-group; }
173
+ [data-datatable-root] tfoot { display: table-footer-group; }
174
+ }
175
+ `;
176
+ document.head.appendChild(el);
177
+ return () => el.remove();
178
+ }
179
+ function useDataFlash(data, liveDataKey, enabled) {
180
+ const idKey = liveDataKey ?? "id";
181
+ const prevDataRef = React.useRef(/* @__PURE__ */ new Map());
182
+ const [flashedCells, setFlashedCells] = React.useState(/* @__PURE__ */ new Map());
183
+ const [newRowIds, setNewRowIds] = React.useState(/* @__PURE__ */ new Set());
184
+ const [removedRowIds, setRemovedRowIds] = React.useState(/* @__PURE__ */ new Set());
185
+ React.useEffect(() => {
186
+ if (!enabled) return;
187
+ const prevMap = prevDataRef.current;
188
+ const currentMap = /* @__PURE__ */ new Map();
189
+ const changedCells = /* @__PURE__ */ new Map();
190
+ const addedIds = /* @__PURE__ */ new Set();
191
+ for (const row of data) {
192
+ const rowId = String(row[idKey] ?? "");
193
+ const rowRecord = row;
194
+ currentMap.set(rowId, rowRecord);
195
+ if (!prevMap.has(rowId)) {
196
+ addedIds.add(rowId);
197
+ } else {
198
+ const prevRow = prevMap.get(rowId);
199
+ for (const key of Object.keys(rowRecord)) {
200
+ if (rowRecord[key] !== prevRow[key]) {
201
+ if (!changedCells.has(rowId)) changedCells.set(rowId, /* @__PURE__ */ new Set());
202
+ changedCells.get(rowId).add(key);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ const removedIds = /* @__PURE__ */ new Set();
208
+ for (const id of prevMap.keys()) {
209
+ if (!currentMap.has(id)) removedIds.add(id);
210
+ }
211
+ prevDataRef.current = currentMap;
212
+ let timer;
213
+ if (changedCells.size > 0) {
214
+ setFlashedCells(changedCells);
215
+ timer = setTimeout(() => setFlashedCells(/* @__PURE__ */ new Map()), 1200);
216
+ } else if (addedIds.size > 0) {
217
+ setNewRowIds(addedIds);
218
+ timer = setTimeout(() => setNewRowIds(/* @__PURE__ */ new Set()), 400);
219
+ } else if (removedIds.size > 0) {
220
+ setRemovedRowIds(removedIds);
221
+ timer = setTimeout(() => setRemovedRowIds(/* @__PURE__ */ new Set()), 600);
222
+ }
223
+ return () => {
224
+ if (timer !== void 0) clearTimeout(timer);
225
+ };
226
+ }, [data, enabled]);
227
+ return { flashedCells, newRowIds, removedRowIds };
228
+ }
229
+ function DataTable(props) {
230
+ const {
231
+ columns,
232
+ data,
233
+ virtual,
234
+ virtualColumns,
235
+ enableSorting = true,
236
+ enableFiltering = false,
237
+ enableAdvancedFiltering = false,
238
+ enableGlobalSearch = false,
239
+ enableRowSelection = false,
240
+ enableColumnSelection = false,
241
+ enablePagination = false,
242
+ enableExpanding = false,
243
+ enableGrouping = false,
244
+ enableColumnResizing = false,
245
+ enableColumnReordering = false,
246
+ enableRowReordering = false,
247
+ enableColumnPinning = false,
248
+ enableRowPinning = false,
249
+ enableColumnConfiguration = false,
250
+ pageSize = 10,
251
+ loading,
252
+ loadingMore,
253
+ loadingVariant = "text",
254
+ skeletonRows = 5,
255
+ error,
256
+ emptyState,
257
+ className,
258
+ tableClassName,
259
+ toolbar,
260
+ toolbarStart,
261
+ toolbarEnd,
262
+ globalFilter: globalFilterProp,
263
+ defaultGlobalFilter,
264
+ onGlobalFilterChange,
265
+ globalFilterPlaceholder,
266
+ advancedFilter: advancedFilterProp,
267
+ defaultAdvancedFilter,
268
+ onAdvancedFilterChange,
269
+ getAdvancedFilterValue,
270
+ rowActions,
271
+ inlineCreateRow,
272
+ aggregations,
273
+ showColumnTotals = !!aggregations,
274
+ rowTotals,
275
+ rowPinning: rowPinningProp,
276
+ defaultRowPinning,
277
+ onRowPinningChange,
278
+ columnSelection: columnSelectionProp,
279
+ defaultColumnSelection,
280
+ onColumnSelectionChange,
281
+ renderDetailPanel,
282
+ getCellColSpan,
283
+ getCellRowSpan,
284
+ getRowClassName,
285
+ getRowStyle,
286
+ getRowHeight,
287
+ height,
288
+ maxHeight,
289
+ fullHeight,
290
+ autoHeight,
291
+ onLoadMore,
292
+ hasMore,
293
+ loadMoreThreshold = 96,
294
+ onRowOrderChange,
295
+ onColumnOrderChange,
296
+ localeText,
297
+ serverSide,
298
+ getRowId,
299
+ getSubRows,
300
+ onRowSelectionChange,
301
+ onSortingChange,
302
+ tableRef,
303
+ density: densityProp,
304
+ defaultDensity = "standard",
305
+ enableDensityToggle = false,
306
+ onDensityChange,
307
+ treeData = false,
308
+ enableCopyPaste = false,
309
+ onCopy,
310
+ toolbarActions,
311
+ onRefresh,
312
+ slots,
313
+ pageSizeOptions = [10, 25, 50, 100],
314
+ showTotalRows = true,
315
+ noRowsOverlay,
316
+ noResultsOverlay,
317
+ bulkActions,
318
+ rowActionMenu,
319
+ rowActionButtons,
320
+ enableRowCopy = false,
321
+ enableColumnCopy = false,
322
+ getCellClassName,
323
+ enableRowNumbers = false,
324
+ striped = false,
325
+ enableCellTooltip = false,
326
+ stateKey,
327
+ enableStatusBar = false,
328
+ fullscreen: fullscreenProp,
329
+ onFullscreenChange,
330
+ enableFullscreen = false,
331
+ loadingRowIds,
332
+ onPrint,
333
+ quickFilterColumns,
334
+ onCellContextMenu,
335
+ onRowContextMenu,
336
+ enableColumnAutoSize = false,
337
+ lockedColumns: lockedColumnsProp,
338
+ onLockedColumnsChange,
339
+ mobileBreakpoint,
340
+ enableExport = false,
341
+ enableConditionalFormatting = false,
342
+ conditionalFormattingRules: conditionalFormattingRulesProp,
343
+ onConditionalFormattingRulesChange,
344
+ // Feature 3
345
+ getRowStatus,
346
+ // Feature 4
347
+ rowHeight,
348
+ // Feature 5
349
+ enableFilterChips = false,
350
+ // Feature 6
351
+ enablePaste = false,
352
+ // Feature 7
353
+ dir,
354
+ // Feature 8
355
+ enableKeyboardShortcuts = false,
356
+ // Feature 10
357
+ editMode: _editMode,
358
+ onCellEditCommit: _onCellEditCommit,
359
+ enableUndoRedo = false,
360
+ dirtyRows: dirtyRowsProp,
361
+ onDirtyRowsChange,
362
+ // Round 2: Feature 1
363
+ enableCellSelection = false,
364
+ onCellSelectionChange,
365
+ // Round 2: Feature 2
366
+ enableToolPanel = false,
367
+ defaultToolPanelTab = "columns",
368
+ // Round 2: Feature 3
369
+ enableLiveData = false,
370
+ liveDataKey,
371
+ onLiveDataUpdate,
372
+ // Round 2: Feature 5A
373
+ loadDetailPanel,
374
+ detailPanelCacheSize = 20,
375
+ // Round 2: Feature 5B
376
+ enablePrintStyles = false,
377
+ // Round 3: Feature 1
378
+ enablePivot = false,
379
+ pivotConfig: pivotConfigProp,
380
+ onPivotConfigChange,
381
+ // Round 3: Feature 2
382
+ enableSavedViews = false,
383
+ savedViews: savedViewsProp,
384
+ onSavedViewsChange,
385
+ // Round 3: Feature 3
386
+ ariaLabel,
387
+ ariaLabelledBy,
388
+ // Round 3: Feature 4
389
+ enableHeaderStats = false,
390
+ headerStatsConfig,
391
+ // Round 3: Feature 5
392
+ enableValidation = false,
393
+ onValidationChange,
394
+ // Round 3: Feature 6
395
+ locale: propLocale
396
+ } = props;
397
+ const [internalLockedColumns, setInternalLockedColumns] = React.useState(
398
+ lockedColumnsProp ?? []
399
+ );
400
+ const lockedColumns = lockedColumnsProp ?? internalLockedColumns;
401
+ const handleLockedColumnsChange = React.useCallback(
402
+ (cols) => {
403
+ setInternalLockedColumns(cols);
404
+ onLockedColumnsChange?.(cols);
405
+ },
406
+ [onLockedColumnsChange]
407
+ );
408
+ const [internalConditionalRules, setInternalConditionalRules] = React.useState(
409
+ conditionalFormattingRulesProp ?? []
410
+ );
411
+ const conditionalFormattingRules = conditionalFormattingRulesProp ?? internalConditionalRules;
412
+ const handleConditionalRulesChange = React.useCallback(
413
+ (rules) => {
414
+ setInternalConditionalRules(rules);
415
+ onConditionalFormattingRulesChange?.(rules);
416
+ },
417
+ [onConditionalFormattingRulesChange]
418
+ );
419
+ const [cfDrawerOpen, setCfDrawerOpen] = React.useState(false);
420
+ const [cfInitialColumnId, setCfInitialColumnId] = React.useState();
421
+ const handleOpenCfDrawer = React.useCallback((columnId) => {
422
+ setCfInitialColumnId(columnId);
423
+ setCfDrawerOpen(true);
424
+ }, []);
425
+ const text = React.useMemo(
426
+ () => ({ ...defaultLocaleText, ...localeText }),
427
+ [localeText]
428
+ );
429
+ const [sorting, setSorting] = React.useState(serverSide?.state.sorting ?? []);
430
+ const [columnFilters, setColumnFilters] = React.useState(
431
+ serverSide?.state.filters ?? []
432
+ );
433
+ const [internalGlobalFilter, setInternalGlobalFilter] = React.useState(
434
+ serverSide?.state.globalFilter ?? defaultGlobalFilter ?? ""
435
+ );
436
+ const [internalAdvancedFilter, setInternalAdvancedFilter] = React.useState(serverSide?.state.advancedFilter ?? defaultAdvancedFilter);
437
+ const [columnVisibility, setColumnVisibility] = React.useState({});
438
+ const [rowSelection, setRowSelection] = React.useState(
439
+ serverSide?.state.rowSelection ?? {}
440
+ );
441
+ const [expanded, setExpanded] = React.useState({});
442
+ const [grouping, setGrouping] = React.useState([]);
443
+ const [columnSizing, setColumnSizing] = React.useState({});
444
+ const [columnOrder, setColumnOrder] = React.useState([]);
445
+ const [columnPinning, setColumnPinning] = React.useState({});
446
+ const [pagination, setPagination] = React.useState(
447
+ serverSide?.state.pagination ?? { pageIndex: 0, pageSize }
448
+ );
449
+ const [internalRowPinning, setInternalRowPinning] = React.useState(
450
+ serverSide?.state.rowSelection ? {} : defaultRowPinning ?? {}
451
+ );
452
+ const [internalColumnSelection, setInternalColumnSelection] = React.useState(
453
+ defaultColumnSelection ?? []
454
+ );
455
+ const [draggedRowId, setDraggedRowId] = React.useState(null);
456
+ const [draggedColumnId, setDraggedColumnId] = React.useState(null);
457
+ const [internalDensity, setInternalDensity] = React.useState(
458
+ densityProp ?? defaultDensity
459
+ );
460
+ const [internalFullscreen, setInternalFullscreen] = React.useState(false);
461
+ const [_pasteVersion, setPasteVersion] = React.useState(0);
462
+ const [internalPivotConfig, setInternalPivotConfig] = React.useState();
463
+ const pivotConfig = pivotConfigProp ?? internalPivotConfig;
464
+ const [pivotDrawerOpen, setPivotDrawerOpen] = React.useState(false);
465
+ const handlePivotConfigChange = React.useCallback(
466
+ (config) => {
467
+ setInternalPivotConfig(config);
468
+ onPivotConfigChange?.(config);
469
+ },
470
+ [onPivotConfigChange]
471
+ );
472
+ const [internalViews, setInternalViews] = React.useState(savedViewsProp ?? []);
473
+ const savedViews = savedViewsProp ?? internalViews;
474
+ const [savedViewsDrawerOpen, setSavedViewsDrawerOpen] = React.useState(false);
475
+ const handleViewsChange = React.useCallback(
476
+ (views) => {
477
+ setInternalViews(views);
478
+ onSavedViewsChange?.(views);
479
+ },
480
+ [onSavedViewsChange]
481
+ );
482
+ const [shortcutsOpen, setShortcutsOpen] = React.useState(false);
483
+ const [cellSelection, setCellSelection] = React.useState(null);
484
+ const [isSelectingCells, setIsSelectingCells] = React.useState(false);
485
+ const handleCellSelectionChange = React.useCallback(
486
+ (next) => {
487
+ setCellSelection(next);
488
+ onCellSelectionChange?.(next);
489
+ },
490
+ [onCellSelectionChange]
491
+ );
492
+ React.useEffect(() => {
493
+ if (!enableCellSelection) return;
494
+ const handleMouseUp = () => setIsSelectingCells(false);
495
+ document.addEventListener("mouseup", handleMouseUp);
496
+ return () => document.removeEventListener("mouseup", handleMouseUp);
497
+ }, [enableCellSelection]);
498
+ const [bulkEditValue, setBulkEditValue] = React.useState("");
499
+ const [toolPanelTab, setToolPanelTab] = React.useState(
500
+ defaultToolPanelTab
501
+ );
502
+ const [toolPanelOpen, setToolPanelOpen] = React.useState(false);
503
+ const [statsColumnId, setStatsColumnId] = React.useState();
504
+ const handleViewStats = React.useCallback((columnId) => {
505
+ setStatsColumnId(columnId);
506
+ setToolPanelTab("stats");
507
+ setToolPanelOpen(true);
508
+ }, []);
509
+ const detailPanelCacheRef = React.useRef(/* @__PURE__ */ new Map());
510
+ const detailPanelLoadingRef = React.useRef(/* @__PURE__ */ new Set());
511
+ const [detailPanelVersion, setDetailPanelVersion] = React.useState(0);
512
+ const [_editHistory, setEditHistory] = React.useState([]);
513
+ const [_redoStack, setRedoStack] = React.useState([]);
514
+ const [internalDirtyRows, setInternalDirtyRows] = React.useState(/* @__PURE__ */ new Set());
515
+ const dirtyRows = dirtyRowsProp ?? internalDirtyRows;
516
+ const handleDirtyRowsChange = React.useCallback(
517
+ (next) => {
518
+ setInternalDirtyRows(next);
519
+ onDirtyRowsChange?.(next);
520
+ },
521
+ [onDirtyRowsChange]
522
+ );
523
+ const hasMounted = React.useRef(false);
524
+ React.useEffect(() => {
525
+ hasMounted.current = true;
526
+ }, []);
527
+ const { flashedCells, newRowIds, removedRowIds } = useDataFlash(data, liveDataKey, enableLiveData);
528
+ React.useEffect(() => {
529
+ if (!enableLiveData || flashedCells.size === 0) return;
530
+ const updatedIds = new Set(flashedCells.keys());
531
+ const updatedRows = data.filter((row) => {
532
+ const id = String(row[liveDataKey ?? "id"] ?? "");
533
+ return updatedIds.has(id);
534
+ });
535
+ if (updatedRows.length > 0) onLiveDataUpdate?.(updatedRows);
536
+ }, [flashedCells]);
537
+ React.useEffect(() => {
538
+ if (enableLiveData) injectLiveDataStyles();
539
+ }, [enableLiveData]);
540
+ React.useEffect(() => {
541
+ if (enablePrintStyles) injectPrintStyles();
542
+ }, [enablePrintStyles]);
543
+ React.useEffect(() => {
544
+ if (!stateKey) return;
545
+ try {
546
+ const raw = localStorage.getItem(`structyl-dt:${stateKey}`);
547
+ if (!raw) return;
548
+ const saved = JSON.parse(raw);
549
+ if (saved.sorting) setSorting(saved.sorting);
550
+ if (saved.columnFilters) setColumnFilters(saved.columnFilters);
551
+ if (saved.columnVisibility) setColumnVisibility(saved.columnVisibility);
552
+ if (saved.columnOrder) setColumnOrder(saved.columnOrder);
553
+ if (saved.columnSizing) setColumnSizing(saved.columnSizing);
554
+ if (saved.pagination) setPagination(saved.pagination);
555
+ if (saved.density) setInternalDensity(saved.density);
556
+ } catch {
557
+ }
558
+ }, [stateKey]);
559
+ const globalFilter = globalFilterProp ?? internalGlobalFilter;
560
+ const advancedFilter = advancedFilterProp ?? internalAdvancedFilter;
561
+ const rowPinning = rowPinningProp ?? internalRowPinning;
562
+ const selectedColumnIds = columnSelectionProp ?? internalColumnSelection;
563
+ const density = densityProp ?? internalDensity;
564
+ const isFullscreen = fullscreenProp ?? internalFullscreen;
565
+ const handleDensityChange = (next) => {
566
+ setInternalDensity(next);
567
+ onDensityChange?.(next);
568
+ };
569
+ const handleFullscreenToggle = React.useCallback(() => {
570
+ const el = rootElRef.current;
571
+ const supportsApi = typeof document !== "undefined" && !!document.fullscreenEnabled && !!el?.requestFullscreen;
572
+ if (supportsApi && el) {
573
+ if (!document.fullscreenElement) {
574
+ el.requestFullscreen().catch(() => {
575
+ setInternalFullscreen(true);
576
+ onFullscreenChange?.(true);
577
+ });
578
+ } else {
579
+ document.exitFullscreen().catch(() => {
580
+ setInternalFullscreen(false);
581
+ onFullscreenChange?.(false);
582
+ });
583
+ }
584
+ } else {
585
+ const next = !isFullscreen;
586
+ setInternalFullscreen(next);
587
+ onFullscreenChange?.(next);
588
+ }
589
+ }, [isFullscreen, onFullscreenChange]);
590
+ React.useEffect(() => {
591
+ if (typeof document === "undefined") return;
592
+ const onChange = () => {
593
+ const isFull = !!document.fullscreenElement;
594
+ setInternalFullscreen(isFull);
595
+ onFullscreenChange?.(isFull);
596
+ };
597
+ document.addEventListener("fullscreenchange", onChange);
598
+ return () => document.removeEventListener("fullscreenchange", onChange);
599
+ }, [onFullscreenChange]);
600
+ const persistState = React.useCallback(() => {
601
+ if (!stateKey) return;
602
+ try {
603
+ localStorage.setItem(
604
+ `structyl-dt:${stateKey}`,
605
+ JSON.stringify({
606
+ sorting,
607
+ columnFilters,
608
+ columnVisibility,
609
+ columnOrder,
610
+ columnSizing,
611
+ pagination,
612
+ density
613
+ })
614
+ );
615
+ } catch {
616
+ }
617
+ }, [stateKey, sorting, columnFilters, columnVisibility, columnOrder, columnSizing, pagination, density]);
618
+ React.useEffect(() => {
619
+ if (hasMounted.current) persistState();
620
+ }, [persistState]);
621
+ const globalFilterFn = React.useMemo(
622
+ () => (row, columnId, filterValue) => {
623
+ const query = normalizeSearch(filterValue);
624
+ if (!query) return true;
625
+ return normalizeSearch(row.getValue(columnId)).includes(query);
626
+ },
627
+ []
628
+ );
629
+ const advancedFilteredData = React.useMemo(() => {
630
+ if (!advancedFilter || serverSide) return data;
631
+ return data.filter(
632
+ (row) => evaluateFilterGroup(advancedFilter, row, getAdvancedFilterValue ?? getValueByPath)
633
+ );
634
+ }, [advancedFilter, data, getAdvancedFilterValue, serverSide]);
635
+ const normalizedColumns = React.useMemo(
636
+ () => normalizeColumnDefs(columns, propLocale),
637
+ [columns, propLocale]
638
+ );
639
+ const tableColumns = React.useMemo(() => {
640
+ const next = [];
641
+ if (enableRowNumbers) {
642
+ next.push(createRowNumberColumn(text));
643
+ }
644
+ if (enableRowSelection) {
645
+ next.push(createSelectionColumn(enableRowSelection === true));
646
+ }
647
+ next.push(...normalizedColumns);
648
+ if (rowTotals) {
649
+ next.push(createRowTotalColumn(rowTotals, text));
650
+ }
651
+ if (rowActionButtons?.length) {
652
+ next.push(createRowActionButtonsColumn(rowActionButtons, text));
653
+ }
654
+ if (rowActionMenu?.length) {
655
+ next.push(createRowActionMenuColumn(rowActionMenu, text));
656
+ }
657
+ if (rowActions) {
658
+ next.push(createActionColumn(rowActions, text));
659
+ }
660
+ return next;
661
+ }, [normalizedColumns, enableRowSelection, enableRowNumbers, rowActions, rowActionMenu, rowActionButtons, rowTotals, text]);
662
+ const handleSorting = (updater) => {
663
+ if (!hasMounted.current) return;
664
+ const next = typeof updater === "function" ? updater(sorting) : updater;
665
+ setSorting(next);
666
+ onSortingChange?.(next);
667
+ serverSide?.onStateChange({ ...serverSide.state, sorting: next });
668
+ };
669
+ const handleSelection = (updater) => {
670
+ if (!hasMounted.current) return;
671
+ const next = typeof updater === "function" ? updater(rowSelection) : updater;
672
+ setRowSelection(next);
673
+ onRowSelectionChange?.(next);
674
+ serverSide?.onStateChange({ ...serverSide.state, rowSelection: next });
675
+ };
676
+ const handlePagination = (updater) => {
677
+ if (!hasMounted.current) return;
678
+ const next = typeof updater === "function" ? updater(pagination) : updater;
679
+ setPagination(next);
680
+ serverSide?.onStateChange({ ...serverSide.state, pagination: next });
681
+ };
682
+ const handleFilters = (updater) => {
683
+ if (!hasMounted.current) return;
684
+ const next = typeof updater === "function" ? updater(columnFilters) : updater;
685
+ setColumnFilters(next);
686
+ serverSide?.onStateChange({ ...serverSide.state, filters: next });
687
+ };
688
+ const handleGlobalFilter = (next) => {
689
+ if (!hasMounted.current) return;
690
+ setInternalGlobalFilter(next);
691
+ onGlobalFilterChange?.(next);
692
+ serverSide?.onStateChange({ ...serverSide.state, globalFilter: next });
693
+ };
694
+ const handleAdvancedFilter = (next) => {
695
+ if (!hasMounted.current) return;
696
+ setInternalAdvancedFilter(next);
697
+ onAdvancedFilterChange?.(next);
698
+ serverSide?.onStateChange({ ...serverSide.state, advancedFilter: next });
699
+ };
700
+ const handleRowPinning = (next) => {
701
+ setInternalRowPinning(next);
702
+ onRowPinningChange?.(next);
703
+ };
704
+ const handleColumnSelection = (next) => {
705
+ setInternalColumnSelection(next);
706
+ onColumnSelectionChange?.(next);
707
+ };
708
+ const handleColumnOrder = (updater) => {
709
+ const next = typeof updater === "function" ? updater(columnOrder) : updater;
710
+ setColumnOrder(next);
711
+ onColumnOrderChange?.(next);
712
+ };
713
+ const tableRef2 = React.useRef(null);
714
+ const handleCopyPaste = React.useCallback(
715
+ (event) => {
716
+ const isCtrlOrMeta = event.ctrlKey || event.metaKey;
717
+ if (enableCopyPaste && isCtrlOrMeta && event.key === "c") {
718
+ const t = tableRef2.current;
719
+ if (!t) return;
720
+ const selected = t.getFilteredSelectedRowModel().rows;
721
+ const rowsToCopy = selected.length > 0 ? selected : t.getRowModel().rows.slice(0, 1);
722
+ const cols = t.getVisibleLeafColumns().filter((c) => !c.id.startsWith("__"));
723
+ const tsv = rowsToCopy.map((row) => cols.map((col) => String(row.getValue(col.id) ?? "")).join(" ")).join("\n");
724
+ navigator.clipboard?.writeText(tsv).catch(() => void 0);
725
+ onCopy?.(rowsToCopy, tsv);
726
+ return;
727
+ }
728
+ if (enablePaste && isCtrlOrMeta && event.key === "v") {
729
+ event.preventDefault();
730
+ const t = tableRef2.current;
731
+ if (!t) return;
732
+ const selectedRows = t.getSelectedRowModel().rows;
733
+ if (selectedRows.length === 0) {
734
+ console.warn("[DataTable] enablePaste: no rows selected \u2014 paste skipped");
735
+ return;
736
+ }
737
+ navigator.clipboard?.readText().then((clipText) => {
738
+ const tsvRows = clipText.split("\n").map((line) => line.split(" "));
739
+ const visibleCols = t.getVisibleLeafColumns().filter(
740
+ (c) => !c.id.startsWith("__") && c.columnDef.meta?._structyl && c.columnDef.meta._structyl?.valueSetter
741
+ );
742
+ tsvRows.forEach((tsvCols, rowIdx) => {
743
+ const row = selectedRows[rowIdx];
744
+ if (!row) return;
745
+ tsvCols.forEach((cellValue, colIdx) => {
746
+ const col = visibleCols[colIdx];
747
+ if (!col) return;
748
+ const structylMeta = col.columnDef.meta?._structyl;
749
+ const vs = structylMeta?.valueSetter;
750
+ if (vs) vs(row.original, cellValue, row.index);
751
+ });
752
+ });
753
+ setPasteVersion((v) => v + 1);
754
+ }).catch(() => void 0);
755
+ return;
756
+ }
757
+ if (enableUndoRedo && isCtrlOrMeta && event.key === "z") {
758
+ event.preventDefault();
759
+ setEditHistory((prev) => {
760
+ if (prev.length === 0) return prev;
761
+ const last = prev[prev.length - 1];
762
+ if (!last) return prev;
763
+ const t = tableRef2.current;
764
+ if (t) {
765
+ const row = t.getRowModel().rows.find((r) => r.id === last.rowId);
766
+ if (row) {
767
+ const col = t.getColumn(last.field);
768
+ if (col) {
769
+ const structylMeta = col.columnDef.meta?._structyl;
770
+ const vs = structylMeta?.valueSetter;
771
+ if (vs) vs(row.original, last.oldValue, row.index);
772
+ }
773
+ }
774
+ }
775
+ setRedoStack((redo) => [...redo, last]);
776
+ const remaining = prev.slice(0, -1);
777
+ const stillDirty = new Set(remaining.map((e) => e.rowId));
778
+ if (!stillDirty.has(last.rowId)) {
779
+ const next = new Set(dirtyRows);
780
+ next.delete(last.rowId);
781
+ handleDirtyRowsChange(next);
782
+ }
783
+ return remaining;
784
+ });
785
+ return;
786
+ }
787
+ if (enableUndoRedo && isCtrlOrMeta && event.key === "y") {
788
+ event.preventDefault();
789
+ setRedoStack((prev) => {
790
+ if (prev.length === 0) return prev;
791
+ const last = prev[prev.length - 1];
792
+ if (!last) return prev;
793
+ const t = tableRef2.current;
794
+ if (t) {
795
+ const row = t.getRowModel().rows.find((r) => r.id === last.rowId);
796
+ if (row) {
797
+ const col = t.getColumn(last.field);
798
+ if (col) {
799
+ const structylMeta = col.columnDef.meta?._structyl;
800
+ const vs = structylMeta?.valueSetter;
801
+ if (vs) vs(row.original, last.newValue, row.index);
802
+ }
803
+ }
804
+ }
805
+ setEditHistory((history) => [...history, last]);
806
+ const next = new Set(dirtyRows);
807
+ next.add(last.rowId);
808
+ handleDirtyRowsChange(next);
809
+ return prev.slice(0, -1);
810
+ });
811
+ return;
812
+ }
813
+ },
814
+ [enableCopyPaste, onCopy, enablePaste, enableUndoRedo, dirtyRows, handleDirtyRowsChange]
815
+ );
816
+ const table = useReactTable({
817
+ data: advancedFilteredData,
818
+ columns: tableColumns,
819
+ state: {
820
+ sorting,
821
+ columnFilters,
822
+ globalFilter,
823
+ columnVisibility,
824
+ rowSelection,
825
+ expanded,
826
+ grouping,
827
+ columnSizing,
828
+ columnOrder,
829
+ columnPinning,
830
+ pagination
831
+ },
832
+ enableRowSelection: enableRowSelection !== false,
833
+ enableMultiRowSelection: enableRowSelection === true,
834
+ enableSorting,
835
+ enableFilters: enableFiltering || enableAdvancedFiltering || enableGlobalSearch,
836
+ enableGlobalFilter: enableGlobalSearch,
837
+ enableExpanding: enableExpanding || !!renderDetailPanel || treeData,
838
+ enableGrouping,
839
+ enableColumnResizing,
840
+ enableColumnPinning,
841
+ columnResizeMode: "onChange",
842
+ manualPagination: !!serverSide,
843
+ manualSorting: !!serverSide,
844
+ manualFiltering: !!serverSide,
845
+ rowCount: serverSide?.rowCount,
846
+ globalFilterFn,
847
+ getRowId,
848
+ getSubRows,
849
+ onSortingChange: handleSorting,
850
+ onColumnFiltersChange: handleFilters,
851
+ onGlobalFilterChange: handleGlobalFilter,
852
+ onColumnVisibilityChange: setColumnVisibility,
853
+ onRowSelectionChange: handleSelection,
854
+ onExpandedChange: setExpanded,
855
+ onGroupingChange: setGrouping,
856
+ onColumnSizingChange: setColumnSizing,
857
+ onColumnOrderChange: handleColumnOrder,
858
+ onColumnPinningChange: setColumnPinning,
859
+ onPaginationChange: handlePagination,
860
+ getCoreRowModel: getCoreRowModel(),
861
+ getSortedRowModel: enableSorting && !serverSide ? getSortedRowModel() : void 0,
862
+ getFilteredRowModel: (enableFiltering || enableGlobalSearch) && !serverSide ? getFilteredRowModel() : void 0,
863
+ getPaginationRowModel: enablePagination && !serverSide ? getPaginationRowModel() : void 0,
864
+ getExpandedRowModel: enableExpanding || renderDetailPanel || treeData ? getExpandedRowModel() : void 0,
865
+ getGroupedRowModel: enableGrouping ? getGroupedRowModel() : void 0,
866
+ getFacetedRowModel: enableFiltering ? getFacetedRowModel() : void 0,
867
+ getFacetedUniqueValues: enableFiltering ? getFacetedUniqueValues() : void 0
868
+ });
869
+ React.useEffect(() => {
870
+ tableRef2.current = table;
871
+ if (tableRef) tableRef.current = table;
872
+ }, [table, tableRef]);
873
+ const validationErrors = React.useMemo(() => {
874
+ const map = /* @__PURE__ */ new Map();
875
+ if (!enableValidation) return map;
876
+ const rows = table.getFilteredRowModel().rows;
877
+ for (const row of rows) {
878
+ for (const cell of row.getVisibleCells()) {
879
+ const structylMeta = getStructylMeta(cell.column);
880
+ const dv = structylMeta.displayValidate;
881
+ if (!dv) continue;
882
+ const cellValue = cell.getValue();
883
+ const msg = dv(cellValue, row.original);
884
+ if (msg) {
885
+ if (!map.has(row.id)) map.set(row.id, /* @__PURE__ */ new Map());
886
+ map.get(row.id).set(cell.column.id, msg);
887
+ }
888
+ }
889
+ }
890
+ return map;
891
+ }, [enableValidation, table.getFilteredRowModel().rows]);
892
+ React.useEffect(() => {
893
+ if (!enableValidation) return;
894
+ const errors = [];
895
+ for (const [rowId, fieldMap] of validationErrors.entries()) {
896
+ for (const [field, message] of fieldMap.entries()) {
897
+ const row = table.getRow(rowId);
898
+ errors.push({ rowId, field, value: row ? row.getValue(field) : void 0, message });
899
+ }
900
+ }
901
+ onValidationChange?.(errors);
902
+ }, [validationErrors]);
903
+ React.useEffect(() => {
904
+ if (!enableKeyboardShortcuts) return;
905
+ const handleKeyDown = (event) => {
906
+ if (event.key === "?" && !event.ctrlKey && !event.metaKey && !event.altKey) {
907
+ const tag = event.target?.tagName?.toLowerCase();
908
+ if (tag === "input" || tag === "textarea") return;
909
+ event.preventDefault();
910
+ setShortcutsOpen((prev) => !prev);
911
+ }
912
+ };
913
+ document.addEventListener("keydown", handleKeyDown);
914
+ return () => document.removeEventListener("keydown", handleKeyDown);
915
+ }, [enableKeyboardShortcuts]);
916
+ const tableContainerRef = React.useRef(null);
917
+ const [overlayBoundary, setOverlayBoundary] = React.useState(null);
918
+ const rootElRef = React.useRef(null);
919
+ const handleRootRef = React.useCallback((node) => {
920
+ rootElRef.current = node;
921
+ setOverlayBoundary(node);
922
+ }, []);
923
+ const allRows = table.getRowModel().rows;
924
+ const pinnedRows = splitPinnedRows(allRows, rowPinning);
925
+ const scrollRows = pinnedRows.center;
926
+ const isVirtual = !!virtual;
927
+ const rowVirtualOpts = typeof virtual === "object" && virtual !== null ? virtual : {};
928
+ const rowVirtualizer = useVirtualizer({
929
+ count: scrollRows.length,
930
+ estimateSize: (index) => {
931
+ const row = scrollRows[index];
932
+ return (row ? getRowHeight?.(row) : void 0) ?? rowVirtualOpts.estimatedRowHeight ?? 44;
933
+ },
934
+ getScrollElement: () => tableContainerRef.current,
935
+ overscan: rowVirtualOpts.overscan ?? 10,
936
+ enabled: isVirtual
937
+ });
938
+ const virtualRows = isVirtual ? rowVirtualizer.getVirtualItems() : [];
939
+ const fallbackVirtualRows = isVirtual && virtualRows.length === 0 ? scrollRows.slice(0, Math.min(scrollRows.length, Math.max(1, rowVirtualOpts.overscan ?? 10))) : [];
940
+ const paddingTop = isVirtual && virtualRows.length > 0 ? virtualRows[0]?.start ?? 0 : 0;
941
+ const paddingBottom = isVirtual && virtualRows.length > 0 ? rowVirtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0) : 0;
942
+ const leafColumns = table.getVisibleLeafColumns();
943
+ const isColumnVirtual = !!virtualColumns;
944
+ const columnVirtualOpts = typeof virtualColumns === "object" && virtualColumns !== null ? virtualColumns : {};
945
+ const columnVirtualizer = useVirtualizer({
946
+ count: leafColumns.length,
947
+ estimateSize: (index) => leafColumns[index]?.getSize() ?? columnVirtualOpts.estimatedColumnWidth ?? 160,
948
+ getScrollElement: () => tableContainerRef.current,
949
+ horizontal: true,
950
+ overscan: columnVirtualOpts.overscan ?? 4,
951
+ enabled: isColumnVirtual
952
+ });
953
+ const virtualColumnItems = isColumnVirtual ? columnVirtualizer.getVirtualItems() : [];
954
+ const fallbackColumnCount = Math.min(
955
+ leafColumns.length,
956
+ Math.max(1, (columnVirtualOpts.overscan ?? 4) * 2 + 4)
957
+ );
958
+ const renderedColumns = isColumnVirtual ? virtualColumnItems.length > 0 ? virtualColumnItems.map((item) => leafColumns[item.index]).filter(isDefined) : leafColumns.slice(0, fallbackColumnCount) : leafColumns;
959
+ const renderedColumnIds = new Set(renderedColumns.map((column) => column.id));
960
+ const columnPaddingLeft = isColumnVirtual && virtualColumnItems.length > 0 ? virtualColumnItems[0]?.start ?? 0 : 0;
961
+ const columnPaddingRight = isColumnVirtual && virtualColumnItems.length > 0 ? columnVirtualizer.getTotalSize() - (virtualColumnItems[virtualColumnItems.length - 1]?.end ?? 0) : 0;
962
+ const colSpan = Math.max(
963
+ renderedColumns.length + (columnPaddingLeft ? 1 : 0) + (columnPaddingRight ? 1 : 0),
964
+ 1
965
+ );
966
+ const showToolbar = enableGlobalSearch || enableAdvancedFiltering || enableColumnConfiguration || toolbar || toolbarStart || toolbarEnd || toolbarActions && toolbarActions.length > 0 || enableDensityToggle || treeData || onRefresh || enableKeyboardShortcuts || enableExport || enableConditionalFormatting || enableFullscreen || onPrint || enablePivot || enableSavedViews;
967
+ const densityCellClass = density === "compact" ? "py-1 px-2" : density === "comfortable" ? "py-4 px-4" : "p-3";
968
+ const containerStyle = {
969
+ height: fullHeight ? "100%" : height,
970
+ maxHeight: autoHeight ? void 0 : maxHeight ?? (isVirtual ? 600 : void 0)
971
+ };
972
+ const handleScroll = React.useCallback(
973
+ (event) => {
974
+ if (!onLoadMore || !hasMore || loadingMore) return;
975
+ const element = event.currentTarget;
976
+ const remaining = element.scrollHeight - element.scrollTop - element.clientHeight;
977
+ if (remaining <= loadMoreThreshold) onLoadMore();
978
+ },
979
+ [hasMore, loadMoreThreshold, loadingMore, onLoadMore]
980
+ );
981
+ const pinRow = (rowId, position) => {
982
+ const top = new Set(rowPinning.top ?? []);
983
+ const bottom = new Set(rowPinning.bottom ?? []);
984
+ top.delete(rowId);
985
+ bottom.delete(rowId);
986
+ if (position === "top") top.add(rowId);
987
+ if (position === "bottom") bottom.add(rowId);
988
+ handleRowPinning({ top: Array.from(top), bottom: Array.from(bottom) });
989
+ };
990
+ const rowStatusBorderColor = {
991
+ success: "#22c55e",
992
+ warning: "#f59e0b",
993
+ error: "#ef4444",
994
+ info: "#3b82f6"
995
+ };
996
+ const normalizedCellSelection = React.useMemo(() => {
997
+ if (!cellSelection) return null;
998
+ return {
999
+ startRowIndex: Math.min(cellSelection.startRowIndex, cellSelection.endRowIndex),
1000
+ startColIndex: Math.min(cellSelection.startColIndex, cellSelection.endColIndex),
1001
+ endRowIndex: Math.max(cellSelection.startRowIndex, cellSelection.endRowIndex),
1002
+ endColIndex: Math.max(cellSelection.startColIndex, cellSelection.endColIndex)
1003
+ };
1004
+ }, [cellSelection]);
1005
+ const isCellSelected = React.useCallback(
1006
+ (rowIndex, colIndex) => {
1007
+ if (!normalizedCellSelection) return false;
1008
+ return rowIndex >= normalizedCellSelection.startRowIndex && rowIndex <= normalizedCellSelection.endRowIndex && colIndex >= normalizedCellSelection.startColIndex && colIndex <= normalizedCellSelection.endColIndex;
1009
+ },
1010
+ [normalizedCellSelection]
1011
+ );
1012
+ const renderDataRow = (row, rowIndex) => {
1013
+ const rowIsPinnedTop = rowPinning.top?.includes(row.id) ?? false;
1014
+ const rowIsPinnedBottom = rowPinning.bottom?.includes(row.id) ?? false;
1015
+ const isRowLoading = loadingRowIds?.includes(row.id) ?? false;
1016
+ const rowStatus = getRowStatus?.(row);
1017
+ const isDirty = dirtyRows.has(row.id);
1018
+ const isNewRow = enableLiveData && newRowIds.has(row.id);
1019
+ const isRemovedRow = enableLiveData && removedRowIds.has(row.id);
1020
+ const computedRowHeight = rowHeight != null ? typeof rowHeight === "function" ? rowHeight(row, rowIndex) : rowHeight : void 0;
1021
+ const rowStyle = {
1022
+ ...getRowStyle?.(row),
1023
+ borderLeft: rowStatus ? `3px solid ${rowStatusBorderColor[rowStatus] ?? "transparent"}` : isDirty ? "3px solid #f59e0b" : void 0,
1024
+ height: computedRowHeight
1025
+ };
1026
+ const resolveDetailContent = () => {
1027
+ if (loadDetailPanel) {
1028
+ const cache = detailPanelCacheRef.current;
1029
+ if (cache.has(row.id)) return cache.get(row.id);
1030
+ if (!detailPanelLoadingRef.current.has(row.id)) {
1031
+ detailPanelLoadingRef.current.add(row.id);
1032
+ loadDetailPanel(row).then((content) => {
1033
+ if (cache.size >= detailPanelCacheSize) {
1034
+ const firstKey = cache.keys().next().value;
1035
+ if (firstKey !== void 0) cache.delete(firstKey);
1036
+ }
1037
+ cache.set(row.id, content);
1038
+ detailPanelLoadingRef.current.delete(row.id);
1039
+ setDetailPanelVersion((v) => v + 1);
1040
+ }).catch(() => {
1041
+ detailPanelLoadingRef.current.delete(row.id);
1042
+ });
1043
+ }
1044
+ return /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground inline-flex items-center gap-2 text-sm" }, /* @__PURE__ */ React.createElement("span", { className: "border-primary h-4 w-4 animate-spin rounded-full border-2 border-t-transparent" }), "Loading\u2026");
1045
+ }
1046
+ return renderDetailPanel?.(row);
1047
+ };
1048
+ const rowValidationErrors = enableValidation ? validationErrors.get(row.id) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map();
1049
+ return /* @__PURE__ */ React.createElement(React.Fragment, { key: row.id }, /* @__PURE__ */ React.createElement(
1050
+ "tr",
1051
+ {
1052
+ role: "row",
1053
+ "aria-rowindex": rowIndex + 2,
1054
+ "aria-selected": enableRowSelection !== false ? row.getIsSelected() : void 0,
1055
+ "data-state": row.getIsSelected() ? "selected" : void 0,
1056
+ "data-pinned": rowIsPinnedTop ? "top" : rowIsPinnedBottom ? "bottom" : void 0,
1057
+ "data-loading": isRowLoading || void 0,
1058
+ "data-new-row": isNewRow || void 0,
1059
+ "data-removed-row": isRemovedRow || void 0,
1060
+ draggable: enableRowReordering,
1061
+ onContextMenu: onRowContextMenu ? (event) => {
1062
+ event.preventDefault();
1063
+ onRowContextMenu(row, event);
1064
+ } : void 0,
1065
+ onDragStart: (event) => {
1066
+ if (!enableRowReordering) return;
1067
+ setDraggedRowId(row.id);
1068
+ event.dataTransfer.effectAllowed = "move";
1069
+ },
1070
+ onDragOver: (event) => {
1071
+ if (enableRowReordering) event.preventDefault();
1072
+ },
1073
+ onDrop: (event) => {
1074
+ if (!enableRowReordering || !draggedRowId) return;
1075
+ event.preventDefault();
1076
+ const nextRows = reorderRows(scrollRows, draggedRowId, row.id);
1077
+ onRowOrderChange?.(
1078
+ nextRows.map((item) => item.original),
1079
+ nextRows.map((item) => item.id)
1080
+ );
1081
+ setDraggedRowId(null);
1082
+ },
1083
+ style: rowStyle,
1084
+ className: cn(
1085
+ "border-border hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
1086
+ striped && rowIndex % 2 !== 0 && "bg-muted/30",
1087
+ rowIsPinnedTop && "bg-bg shadow-sm",
1088
+ rowIsPinnedBottom && "bg-bg shadow-sm",
1089
+ enableRowReordering && "cursor-grab active:cursor-grabbing",
1090
+ isRowLoading && "opacity-60 pointer-events-none",
1091
+ getRowClassName?.(row)
1092
+ )
1093
+ },
1094
+ columnPaddingLeft > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingLeft } }) : null,
1095
+ renderRowCells({
1096
+ row,
1097
+ rowIndex,
1098
+ renderedColumnIds,
1099
+ enableExpanding,
1100
+ renderDetailPanel,
1101
+ getCellColSpan,
1102
+ getCellRowSpan,
1103
+ enableRowPinning,
1104
+ pinRow,
1105
+ text,
1106
+ overlayBoundary,
1107
+ treeData,
1108
+ densityCellClass,
1109
+ enableRowCopy,
1110
+ getCellClassName,
1111
+ enableCellTooltip,
1112
+ onCellContextMenu,
1113
+ conditionalFormattingRules,
1114
+ enableCellSelection,
1115
+ isCellSelected,
1116
+ isSelectingCells,
1117
+ onCellMouseDown: enableCellSelection ? (ri, ci) => {
1118
+ handleCellSelectionChange({ startRowIndex: ri, startColIndex: ci, endRowIndex: ri, endColIndex: ci });
1119
+ setIsSelectingCells(true);
1120
+ } : void 0,
1121
+ onCellMouseEnter: enableCellSelection ? (ri, ci) => {
1122
+ if (!isSelectingCells) return;
1123
+ setCellSelection((prev) => prev ? { ...prev, endRowIndex: ri, endColIndex: ci } : prev);
1124
+ } : void 0,
1125
+ flashedCells: enableLiveData ? flashedCells.get(row.id) : void 0,
1126
+ onViewStats: enableToolPanel ? handleViewStats : void 0,
1127
+ rowValidationErrors
1128
+ }),
1129
+ columnPaddingRight > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingRight } }) : null
1130
+ ), (renderDetailPanel || loadDetailPanel) && row.getIsExpanded() ? /* @__PURE__ */ React.createElement("tr", { className: "border-border bg-muted/20 border-b" }, /* @__PURE__ */ React.createElement("td", { colSpan, className: "p-4" }, detailPanelVersion >= 0 ? resolveDetailContent() : null)) : null);
1131
+ };
1132
+ const tbodyId = React.useId();
1133
+ const tbodyIdAttr = tbodyId ? `datatable-tbody-${tbodyId.replace(/:/g, "")}` : void 0;
1134
+ if (enablePivot && pivotConfig) {
1135
+ return /* @__PURE__ */ React.createElement(
1136
+ DataTablePivotView,
1137
+ {
1138
+ data,
1139
+ pivotConfig,
1140
+ columns,
1141
+ className,
1142
+ onClearPivot: () => {
1143
+ setInternalPivotConfig(void 0);
1144
+ onPivotConfigChange?.({ rowGroupField: "", pivotField: "", valueField: "", aggregation: "sum" });
1145
+ }
1146
+ }
1147
+ );
1148
+ }
1149
+ return /* @__PURE__ */ React.createElement(
1150
+ "div",
1151
+ {
1152
+ ref: handleRootRef,
1153
+ "data-datatable-root": "",
1154
+ "data-fullscreen": isFullscreen || void 0,
1155
+ dir,
1156
+ "aria-label": ariaLabel,
1157
+ "aria-labelledby": ariaLabelledBy,
1158
+ tabIndex: enableCopyPaste || enablePaste || enableUndoRedo ? -1 : void 0,
1159
+ onKeyDown: enableCopyPaste || enablePaste || enableUndoRedo ? handleCopyPaste : void 0,
1160
+ className: cn(
1161
+ "relative isolate flex w-full min-w-0 flex-col rounded-lg border border-border",
1162
+ isFullscreen ? "overflow-auto" : "overflow-hidden",
1163
+ fullHeight && "h-full",
1164
+ isFullscreen && "fixed inset-0 z-50 bg-bg h-screen w-screen rounded-none border-none",
1165
+ "[&:fullscreen]:bg-bg [&:fullscreen]:overflow-auto [&:fullscreen]:p-4 [&:fullscreen]:border-none",
1166
+ mobileBreakpoint === "sm" && "sm:[display:revert]",
1167
+ mobileBreakpoint === "md" && "md:[display:revert]",
1168
+ className
1169
+ )
1170
+ },
1171
+ showToolbar ? slots?.Toolbar ? /* @__PURE__ */ React.createElement(slots.Toolbar, { table, localeText: text }) : /* @__PURE__ */ React.createElement("div", { className: "data-table-toolbar relative flex flex-wrap items-center justify-between gap-2 border-b border-border px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, enableGlobalSearch ? slots?.Search ? /* @__PURE__ */ React.createElement(
1172
+ slots.Search,
1173
+ {
1174
+ value: globalFilter,
1175
+ onValueChange: handleGlobalFilter,
1176
+ placeholder: globalFilterPlaceholder ?? text.searchPlaceholder
1177
+ }
1178
+ ) : /* @__PURE__ */ React.createElement(
1179
+ DataTableGlobalSearch,
1180
+ {
1181
+ value: globalFilter,
1182
+ onValueChange: handleGlobalFilter,
1183
+ placeholder: globalFilterPlaceholder ?? text.searchPlaceholder
1184
+ }
1185
+ ) : null, enableAdvancedFiltering ? slots?.Filter ? /* @__PURE__ */ React.createElement(
1186
+ slots.Filter,
1187
+ {
1188
+ table,
1189
+ filter: advancedFilter,
1190
+ onFilterChange: handleAdvancedFilter,
1191
+ localeText: text,
1192
+ overlayBoundary
1193
+ }
1194
+ ) : /* @__PURE__ */ React.createElement(
1195
+ DataTableAdvancedFilter,
1196
+ {
1197
+ table,
1198
+ filter: advancedFilter,
1199
+ onFilterChange: handleAdvancedFilter,
1200
+ localeText: text,
1201
+ overlayBoundary
1202
+ }
1203
+ ) : null, toolbarStart), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, typeof toolbar === "function" ? toolbar(table) : toolbar, treeData ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1204
+ DataTableToolbarButton,
1205
+ {
1206
+ tooltip: text.expandAll,
1207
+ onClick: () => table.toggleAllRowsExpanded(true),
1208
+ "aria-label": text.expandAll
1209
+ },
1210
+ /* @__PURE__ */ React.createElement(ChevronsDown, { className: "mr-1.5 size-4" }),
1211
+ text.expandAll
1212
+ ), /* @__PURE__ */ React.createElement(
1213
+ DataTableToolbarButton,
1214
+ {
1215
+ tooltip: text.collapseAll,
1216
+ onClick: () => table.toggleAllRowsExpanded(false),
1217
+ "aria-label": text.collapseAll
1218
+ },
1219
+ /* @__PURE__ */ React.createElement(ChevronsUpDown, { className: "mr-1.5 size-4" }),
1220
+ text.collapseAll
1221
+ )) : null, toolbarActions?.map(
1222
+ (action) => action.render ? /* @__PURE__ */ React.createElement(React.Fragment, { key: action.id }, action.render(table)) : /* @__PURE__ */ React.createElement(
1223
+ DataTableToolbarButton,
1224
+ {
1225
+ key: action.id,
1226
+ tooltip: action.tooltip,
1227
+ disabled: action.disabled,
1228
+ onClick: () => action.onClick?.(table),
1229
+ "aria-label": typeof action.label === "string" ? action.label : action.id
1230
+ },
1231
+ action.icon ?? action.label
1232
+ )
1233
+ ), enableDensityToggle ? /* @__PURE__ */ React.createElement(
1234
+ DataTableDensityMenu,
1235
+ {
1236
+ density,
1237
+ onDensityChange: handleDensityChange,
1238
+ localeText: text,
1239
+ overlayBoundary
1240
+ }
1241
+ ) : null, enableExport ? /* @__PURE__ */ React.createElement(
1242
+ DataTableExportMenu,
1243
+ {
1244
+ table,
1245
+ localeText: text,
1246
+ overlayBoundary,
1247
+ options: typeof enableExport === "object" ? enableExport : { csv: true, json: true, selectedCsv: true, xlsx: true }
1248
+ }
1249
+ ) : null, enableConditionalFormatting ? /* @__PURE__ */ React.createElement(
1250
+ DataTableToolbarButton,
1251
+ {
1252
+ tooltip: text.conditionalFormatting ?? "Conditional formatting",
1253
+ onClick: () => handleOpenCfDrawer(),
1254
+ "aria-label": text.conditionalFormatting ?? "Conditional formatting"
1255
+ },
1256
+ /* @__PURE__ */ React.createElement(Filter, { className: "mr-1.5 size-4" }),
1257
+ "Format"
1258
+ ) : null, enablePivot ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1259
+ DataTableToolbarButton,
1260
+ {
1261
+ tooltip: "Pivot mode",
1262
+ onClick: () => setPivotDrawerOpen(true),
1263
+ "aria-label": "Pivot mode"
1264
+ },
1265
+ /* @__PURE__ */ React.createElement(GitBranch, { className: "mr-1.5 size-4" }),
1266
+ "Pivot"
1267
+ ), pivotConfig ? /* @__PURE__ */ React.createElement("span", { className: "bg-primary/10 text-primary border-primary/30 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium" }, "Pivot active", /* @__PURE__ */ React.createElement(
1268
+ "button",
1269
+ {
1270
+ type: "button",
1271
+ "aria-label": "Clear pivot",
1272
+ className: "hover:text-destructive ml-0.5 leading-none",
1273
+ onClick: () => {
1274
+ setInternalPivotConfig(void 0);
1275
+ onPivotConfigChange?.({ rowGroupField: "", pivotField: "", valueField: "", aggregation: "sum" });
1276
+ }
1277
+ },
1278
+ "\xD7"
1279
+ )) : null) : null, enableSavedViews ? /* @__PURE__ */ React.createElement(
1280
+ DataTableToolbarButton,
1281
+ {
1282
+ tooltip: "Saved views",
1283
+ onClick: () => setSavedViewsDrawerOpen(true),
1284
+ "aria-label": "Saved views"
1285
+ },
1286
+ /* @__PURE__ */ React.createElement(Bookmark, { className: "mr-1.5 size-4" }),
1287
+ "Views"
1288
+ ) : null, onRefresh ? /* @__PURE__ */ React.createElement(
1289
+ DataTableToolbarButton,
1290
+ {
1291
+ tooltip: text.refresh,
1292
+ onClick: onRefresh,
1293
+ "aria-label": text.refresh
1294
+ },
1295
+ /* @__PURE__ */ React.createElement(RefreshCw, { className: "mr-1.5 size-4" }),
1296
+ text.refresh
1297
+ ) : null, onPrint ? /* @__PURE__ */ React.createElement(
1298
+ DataTableToolbarButton,
1299
+ {
1300
+ tooltip: text.printTitle,
1301
+ onClick: () => {
1302
+ onPrint?.();
1303
+ const cleanup = applyPrintTheme();
1304
+ window.print();
1305
+ window.addEventListener("afterprint", cleanup, { once: true });
1306
+ },
1307
+ "aria-label": text.printTitle
1308
+ },
1309
+ /* @__PURE__ */ React.createElement(Printer, { className: "mr-1.5 size-4" }),
1310
+ text.printTitle
1311
+ ) : null, enableFullscreen ? /* @__PURE__ */ React.createElement(
1312
+ DataTableToolbarButton,
1313
+ {
1314
+ tooltip: isFullscreen ? text.exitFullscreen : text.enterFullscreen,
1315
+ onClick: handleFullscreenToggle,
1316
+ "aria-label": isFullscreen ? text.exitFullscreen : text.enterFullscreen
1317
+ },
1318
+ isFullscreen ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Minimize2, { className: "mr-1.5 size-4" }), text.exitFullscreen) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Maximize2, { className: "mr-1.5 size-4" }), text.enterFullscreen)
1319
+ ) : null, enableColumnConfiguration ? /* @__PURE__ */ React.createElement(
1320
+ DataTableColumnConfiguration,
1321
+ {
1322
+ table,
1323
+ selectedColumnIds,
1324
+ onSelectedColumnIdsChange: handleColumnSelection,
1325
+ localeText: text,
1326
+ enableColumnSelection,
1327
+ enableGrouping,
1328
+ enableColumnPinning,
1329
+ overlayBoundary
1330
+ }
1331
+ ) : null, enableKeyboardShortcuts ? /* @__PURE__ */ React.createElement(
1332
+ DataTableToolbarButton,
1333
+ {
1334
+ tooltip: "Keyboard shortcuts",
1335
+ onClick: () => setShortcutsOpen(true),
1336
+ "aria-label": "Keyboard shortcuts"
1337
+ },
1338
+ /* @__PURE__ */ React.createElement(HelpCircle, { className: "mr-1.5 size-4" }),
1339
+ "Shortcuts"
1340
+ ) : null, toolbarEnd)) : null,
1341
+ bulkActions && bulkActions.length > 0 && table.getFilteredSelectedRowModel().rows.length > 0 ? /* @__PURE__ */ React.createElement(
1342
+ DataTableBulkActionsBar,
1343
+ {
1344
+ table,
1345
+ actions: bulkActions,
1346
+ localeText: text
1347
+ }
1348
+ ) : null,
1349
+ enableCellSelection && normalizedCellSelection && (() => {
1350
+ const rowSpan = normalizedCellSelection.endRowIndex - normalizedCellSelection.startRowIndex + 1;
1351
+ const colSpan2 = normalizedCellSelection.endColIndex - normalizedCellSelection.startColIndex + 1;
1352
+ const totalCells = rowSpan * colSpan2;
1353
+ if (totalCells <= 1) return null;
1354
+ return /* @__PURE__ */ React.createElement("div", { className: "bg-primary/10 border-primary/30 flex flex-wrap items-center gap-3 rounded-md border px-3 py-2 text-sm" }, /* @__PURE__ */ React.createElement("span", { className: "text-primary font-medium" }, totalCells, " cells selected across ", rowSpan, " rows"), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement(
1355
+ Input,
1356
+ {
1357
+ value: bulkEditValue,
1358
+ onChange: (e) => setBulkEditValue(e.target.value),
1359
+ placeholder: "Set all to\u2026",
1360
+ className: "h-7 w-36 text-xs"
1361
+ }
1362
+ ), /* @__PURE__ */ React.createElement(
1363
+ Button,
1364
+ {
1365
+ type: "button",
1366
+ variant: "default",
1367
+ size: "sm",
1368
+ className: "h-7 px-2 text-xs",
1369
+ onClick: () => {
1370
+ const visibleLeafCols = table.getVisibleLeafColumns();
1371
+ const rowModel = table.getRowModel().rows;
1372
+ for (let ri = normalizedCellSelection.startRowIndex; ri <= normalizedCellSelection.endRowIndex; ri++) {
1373
+ const row = rowModel[ri];
1374
+ if (!row) continue;
1375
+ for (let ci = normalizedCellSelection.startColIndex; ci <= normalizedCellSelection.endColIndex; ci++) {
1376
+ const col = visibleLeafCols[ci];
1377
+ if (!col) continue;
1378
+ const structylMeta = getStructylMeta(col);
1379
+ if (structylMeta.valueSetter) {
1380
+ structylMeta.valueSetter(row.original, bulkEditValue, ri);
1381
+ }
1382
+ }
1383
+ }
1384
+ setBulkEditValue("");
1385
+ }
1386
+ },
1387
+ "Apply"
1388
+ ), /* @__PURE__ */ React.createElement(
1389
+ Button,
1390
+ {
1391
+ type: "button",
1392
+ variant: "ghost",
1393
+ size: "sm",
1394
+ className: "h-7 px-2 text-xs",
1395
+ onClick: () => handleCellSelectionChange(null)
1396
+ },
1397
+ "Clear"
1398
+ )));
1399
+ })(),
1400
+ enableFilterChips && table.getState().columnFilters.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 pb-2 pt-1.5" }, table.getState().columnFilters.map((filter) => {
1401
+ const col = table.getColumn(filter.id);
1402
+ const label = col ? typeof col.columnDef.header === "string" ? col.columnDef.header : filter.id : filter.id;
1403
+ return /* @__PURE__ */ React.createElement(
1404
+ "span",
1405
+ {
1406
+ key: filter.id,
1407
+ className: "border-border bg-muted inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs"
1408
+ },
1409
+ /* @__PURE__ */ React.createElement("span", { className: "font-medium" }, label, ":"),
1410
+ /* @__PURE__ */ React.createElement("span", null, String(filter.value ?? "")),
1411
+ /* @__PURE__ */ React.createElement(
1412
+ "button",
1413
+ {
1414
+ type: "button",
1415
+ className: "text-muted-foreground hover:text-foreground ml-0.5 leading-none",
1416
+ "aria-label": `Remove ${label} filter`,
1417
+ onClick: () => col?.setFilterValue(void 0)
1418
+ },
1419
+ "\xD7"
1420
+ )
1421
+ );
1422
+ }), /* @__PURE__ */ React.createElement(
1423
+ Button,
1424
+ {
1425
+ type: "button",
1426
+ variant: "ghost",
1427
+ size: "sm",
1428
+ className: "h-6 px-2 text-xs",
1429
+ onClick: () => table.resetColumnFilters()
1430
+ },
1431
+ "Clear filters"
1432
+ )) : null,
1433
+ mobileBreakpoint ? /* @__PURE__ */ React.createElement(
1434
+ DataTableCardView,
1435
+ {
1436
+ table,
1437
+ breakpoint: mobileBreakpoint,
1438
+ allRows
1439
+ }
1440
+ ) : null,
1441
+ /* @__PURE__ */ React.createElement("div", { className: cn("flex min-h-0 min-w-0", enableToolPanel && "flex-1 gap-0", fullHeight && "flex-1") }, /* @__PURE__ */ React.createElement(
1442
+ "div",
1443
+ {
1444
+ ref: tableContainerRef,
1445
+ onScroll: handleScroll,
1446
+ style: containerStyle,
1447
+ className: cn(
1448
+ "min-w-0",
1449
+ !autoHeight && "overflow-auto",
1450
+ fullHeight && "min-h-0 flex-1",
1451
+ enableToolPanel && "flex-1",
1452
+ mobileBreakpoint === "sm" && "hidden sm:block",
1453
+ mobileBreakpoint === "md" && "hidden md:block",
1454
+ mobileBreakpoint === "lg" && "hidden lg:block",
1455
+ tableClassName
1456
+ )
1457
+ },
1458
+ /* @__PURE__ */ React.createElement(
1459
+ "table",
1460
+ {
1461
+ className: "w-full caption-bottom text-sm",
1462
+ role: "grid",
1463
+ "aria-rowcount": serverSide ? serverSide.rowCount : table.getFilteredRowModel().rows.length + 1,
1464
+ "aria-colcount": table.getVisibleLeafColumns().length
1465
+ },
1466
+ /* @__PURE__ */ React.createElement("thead", { className: "bg-bg [&_tr]:border-border sticky top-0 z-20 [&_tr]:border-b" }, isColumnVirtual ? /* @__PURE__ */ React.createElement("tr", { role: "row", "aria-rowindex": 1, className: "hover:bg-muted/50" }, columnPaddingLeft > 0 ? /* @__PURE__ */ React.createElement("th", { style: { width: columnPaddingLeft } }) : null, renderedColumns.map((column, colIdx) => /* @__PURE__ */ React.createElement(
1467
+ DataTableLeafHeader,
1468
+ {
1469
+ key: column.id,
1470
+ column,
1471
+ colIndex: colIdx,
1472
+ table,
1473
+ enableSorting,
1474
+ enableColumnResizing,
1475
+ enableColumnReordering,
1476
+ enableColumnPinning,
1477
+ enableGrouping,
1478
+ enableColumnSelection,
1479
+ selectedColumnIds,
1480
+ onSelectedColumnIdsChange: handleColumnSelection,
1481
+ draggedColumnId,
1482
+ onDraggedColumnIdChange: setDraggedColumnId,
1483
+ onColumnOrderChange: handleColumnOrder,
1484
+ localeText: text,
1485
+ overlayBoundary,
1486
+ CustomColumnMenu: slots?.ColumnMenu,
1487
+ enableColumnCopy,
1488
+ enableColumnAutoSize,
1489
+ lockedColumns,
1490
+ onLockedColumnsChange: handleLockedColumnsChange,
1491
+ quickFilterColumns,
1492
+ onOpenConditionalFormatting: enableConditionalFormatting ? handleOpenCfDrawer : void 0,
1493
+ onViewStats: enableToolPanel ? handleViewStats : void 0
1494
+ }
1495
+ )), columnPaddingRight > 0 ? /* @__PURE__ */ React.createElement("th", { style: { width: columnPaddingRight } }) : null) : table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ React.createElement("tr", { key: headerGroup.id, role: "row", "aria-rowindex": 1, className: "hover:bg-muted/50" }, headerGroup.headers.map((header, colIdx) => /* @__PURE__ */ React.createElement(
1496
+ DataTableHeader,
1497
+ {
1498
+ key: header.id,
1499
+ header,
1500
+ colIndex: colIdx,
1501
+ table,
1502
+ enableSorting,
1503
+ enableColumnResizing,
1504
+ enableColumnReordering,
1505
+ enableColumnPinning,
1506
+ enableGrouping,
1507
+ enableColumnSelection,
1508
+ selectedColumnIds,
1509
+ onSelectedColumnIdsChange: handleColumnSelection,
1510
+ draggedColumnId,
1511
+ onDraggedColumnIdChange: setDraggedColumnId,
1512
+ onColumnOrderChange: handleColumnOrder,
1513
+ localeText: text,
1514
+ overlayBoundary,
1515
+ CustomColumnMenu: slots?.ColumnMenu,
1516
+ enableColumnCopy,
1517
+ enableColumnAutoSize,
1518
+ lockedColumns,
1519
+ onLockedColumnsChange: handleLockedColumnsChange,
1520
+ quickFilterColumns,
1521
+ onOpenConditionalFormatting: enableConditionalFormatting ? handleOpenCfDrawer : void 0,
1522
+ onViewStats: enableToolPanel ? handleViewStats : void 0
1523
+ }
1524
+ )))), enableHeaderStats ? /* @__PURE__ */ React.createElement(
1525
+ DataTableHeaderStatsRow,
1526
+ {
1527
+ table,
1528
+ renderedColumnIds,
1529
+ columnPaddingLeft,
1530
+ columnPaddingRight,
1531
+ headerStatsConfig
1532
+ }
1533
+ ) : null),
1534
+ /* @__PURE__ */ React.createElement("tbody", { id: tbodyIdAttr, className: "[&_tr:last-child]:border-0" }, error ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "text-destructive h-24 text-center" }, error)) : loading ? renderLoadingRows({
1535
+ colSpan,
1536
+ columns: renderedColumns.length,
1537
+ variant: loadingVariant,
1538
+ rows: skeletonRows,
1539
+ text,
1540
+ LoadingSkeleton: slots?.LoadingSkeleton,
1541
+ LoadingOverlay: slots?.LoadingOverlay
1542
+ }) : allRows.length === 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "h-24 text-center" }, data.length === 0 ? slots?.NoRowsOverlay ? /* @__PURE__ */ React.createElement(slots.NoRowsOverlay, null) : noRowsOverlay ?? emptyState ?? /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground" }, text.noRows) : slots?.NoResultsOverlay ? /* @__PURE__ */ React.createElement(slots.NoResultsOverlay, null) : noResultsOverlay ?? emptyState ?? /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground" }, text.noResults))) : /* @__PURE__ */ React.createElement(React.Fragment, null, inlineCreateRow ? /* @__PURE__ */ React.createElement(
1543
+ DataTableInlineCreateRow,
1544
+ {
1545
+ createRow: inlineCreateRow,
1546
+ colSpan,
1547
+ localeText: text
1548
+ }
1549
+ ) : null, pinnedRows.top.map((row, i) => renderDataRow(row, i)), isVirtual ? virtualRows.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, paddingTop > 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { style: { height: paddingTop }, colSpan })) : null, virtualRows.map((virtualRow) => {
1550
+ const row = scrollRows[virtualRow.index];
1551
+ return row ? renderDataRow(row, virtualRow.index) : null;
1552
+ }), paddingBottom > 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { style: { height: paddingBottom }, colSpan })) : null) : fallbackVirtualRows.map((row, i) => renderDataRow(row, i)) : scrollRows.map((row, i) => renderDataRow(row, i)), pinnedRows.bottom.map((row, i) => renderDataRow(row, scrollRows.length + i)), loadingMore ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "text-muted-foreground h-12 text-center" }, text.loadingMore)) : null)),
1553
+ showColumnTotals && aggregations ? /* @__PURE__ */ React.createElement(
1554
+ DataTableFooter,
1555
+ {
1556
+ table,
1557
+ aggregations,
1558
+ renderedColumnIds,
1559
+ columnPaddingLeft,
1560
+ columnPaddingRight,
1561
+ localeText: text
1562
+ }
1563
+ ) : null
1564
+ )
1565
+ ), enableToolPanel ? /* @__PURE__ */ React.createElement(
1566
+ DataTableToolPanel,
1567
+ {
1568
+ table,
1569
+ open: toolPanelOpen,
1570
+ tab: toolPanelTab,
1571
+ onTabChange: setToolPanelTab,
1572
+ onOpenChange: setToolPanelOpen,
1573
+ statsColumnId,
1574
+ onStatsColumnChange: setStatsColumnId
1575
+ }
1576
+ ) : null),
1577
+ enablePagination ? /* @__PURE__ */ React.createElement("div", { className: "data-table-pagination border-t border-border" }, slots?.Pagination ? /* @__PURE__ */ React.createElement(slots.Pagination, { table, localeText: text, pageSizeOptions }) : /* @__PURE__ */ React.createElement(
1578
+ DataTablePagination,
1579
+ {
1580
+ table,
1581
+ localeText: text,
1582
+ pageSizeOptions,
1583
+ showTotalRows,
1584
+ overlayBoundary
1585
+ }
1586
+ )) : null,
1587
+ enableStatusBar ? /* @__PURE__ */ React.createElement(
1588
+ DataTableStatusBar,
1589
+ {
1590
+ table,
1591
+ localeText: text,
1592
+ validationErrorCount: enableValidation ? (() => {
1593
+ let count = 0;
1594
+ for (const m of validationErrors.values()) count += m.size;
1595
+ return count;
1596
+ })() : 0
1597
+ }
1598
+ ) : null,
1599
+ enableConditionalFormatting ? /* @__PURE__ */ React.createElement(
1600
+ DataTableConditionalFormattingDrawer,
1601
+ {
1602
+ table,
1603
+ open: cfDrawerOpen,
1604
+ onOpenChange: setCfDrawerOpen,
1605
+ initialColumnId: cfInitialColumnId,
1606
+ rules: conditionalFormattingRules,
1607
+ onRulesChange: handleConditionalRulesChange
1608
+ }
1609
+ ) : null,
1610
+ enableKeyboardShortcuts ? /* @__PURE__ */ React.createElement(
1611
+ DataTableKeyboardShortcutsModal,
1612
+ {
1613
+ open: shortcutsOpen,
1614
+ onOpenChange: setShortcutsOpen,
1615
+ enablePaste
1616
+ }
1617
+ ) : null,
1618
+ enablePivot ? /* @__PURE__ */ React.createElement(
1619
+ DataTablePivotConfigPanel,
1620
+ {
1621
+ open: pivotDrawerOpen,
1622
+ onOpenChange: setPivotDrawerOpen,
1623
+ columns,
1624
+ currentConfig: pivotConfig,
1625
+ onApply: handlePivotConfigChange,
1626
+ onClear: () => {
1627
+ setInternalPivotConfig(void 0);
1628
+ onPivotConfigChange?.({ rowGroupField: "", pivotField: "", valueField: "", aggregation: "sum" });
1629
+ }
1630
+ }
1631
+ ) : null,
1632
+ enableSavedViews ? /* @__PURE__ */ React.createElement(
1633
+ DataTableSavedViewsDrawer,
1634
+ {
1635
+ open: savedViewsDrawerOpen,
1636
+ onOpenChange: setSavedViewsDrawerOpen,
1637
+ views: savedViews,
1638
+ onViewsChange: handleViewsChange,
1639
+ table,
1640
+ density,
1641
+ setDensity: handleDensityChange,
1642
+ conditionalFormattingRules,
1643
+ handleConditionalRulesChange
1644
+ }
1645
+ ) : null
1646
+ );
1647
+ }
1648
+ function DataTableHeader({ header, colIndex, ...props }) {
1649
+ if (header.isPlaceholder) {
1650
+ return /* @__PURE__ */ React.createElement("th", { colSpan: header.colSpan, "aria-colindex": colIndex !== void 0 ? colIndex + 1 : void 0 });
1651
+ }
1652
+ if (header.colSpan > 1) {
1653
+ return /* @__PURE__ */ React.createElement(
1654
+ "th",
1655
+ {
1656
+ colSpan: header.colSpan,
1657
+ "aria-colindex": colIndex !== void 0 ? colIndex + 1 : void 0,
1658
+ className: "border-b border-border bg-muted/30 px-3 py-1.5 text-center text-xs font-semibold tracking-wide text-muted-foreground"
1659
+ },
1660
+ flexRender(header.column.columnDef.header, header.getContext())
1661
+ );
1662
+ }
1663
+ return /* @__PURE__ */ React.createElement(
1664
+ DataTableLeafHeader,
1665
+ {
1666
+ column: header.column,
1667
+ colSpan: header.colSpan,
1668
+ colIndex,
1669
+ headerContent: flexRender(header.column.columnDef.header, header.getContext()),
1670
+ resizeHandler: header.getResizeHandler(),
1671
+ ...props
1672
+ }
1673
+ );
1674
+ }
1675
+ function getColumnHeaderText(column) {
1676
+ return typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1677
+ }
1678
+ function DataTableLeafHeader({
1679
+ column,
1680
+ table,
1681
+ colSpan,
1682
+ headerContent,
1683
+ resizeHandler,
1684
+ colIndex,
1685
+ enableSorting,
1686
+ enableColumnResizing,
1687
+ enableColumnReordering,
1688
+ enableColumnPinning,
1689
+ enableGrouping,
1690
+ enableColumnSelection,
1691
+ selectedColumnIds,
1692
+ onSelectedColumnIdsChange,
1693
+ draggedColumnId,
1694
+ onDraggedColumnIdChange,
1695
+ onColumnOrderChange,
1696
+ localeText,
1697
+ overlayBoundary,
1698
+ CustomColumnMenu,
1699
+ enableColumnCopy,
1700
+ enableColumnAutoSize,
1701
+ lockedColumns,
1702
+ onLockedColumnsChange,
1703
+ quickFilterColumns,
1704
+ onOpenConditionalFormatting,
1705
+ onViewStats
1706
+ }) {
1707
+ const [quickFilter, setQuickFilter] = React.useState("");
1708
+ const isLocked = lockedColumns?.includes(column.id) ?? false;
1709
+ const showQuickFilter = quickFilterColumns?.includes(column.id) ?? false;
1710
+ const canSort = enableSorting && column.getCanSort();
1711
+ const sortDir = column.getIsSorted();
1712
+ const isPinned = column.getIsPinned();
1713
+ const selected = selectedColumnIds.includes(column.id);
1714
+ const structylMeta = getStructylMeta(column);
1715
+ const align = structylMeta.align ?? "left";
1716
+ const flex = structylMeta.flex;
1717
+ const description = structylMeta.description;
1718
+ return /* @__PURE__ */ React.createElement(
1719
+ "th",
1720
+ {
1721
+ scope: "col",
1722
+ role: "columnheader",
1723
+ colSpan,
1724
+ "aria-colindex": colIndex !== void 0 ? colIndex + 1 : void 0,
1725
+ "aria-selected": selected || void 0,
1726
+ "aria-sort": sortDir === "asc" ? "ascending" : sortDir === "desc" ? "descending" : canSort ? "none" : void 0,
1727
+ "data-pinned": isPinned || void 0,
1728
+ "data-state": selected ? "selected" : void 0,
1729
+ draggable: enableColumnReordering,
1730
+ onDragStart: (event) => {
1731
+ if (!enableColumnReordering) return;
1732
+ onDraggedColumnIdChange(column.id);
1733
+ event.dataTransfer.effectAllowed = "move";
1734
+ },
1735
+ onDragOver: (event) => {
1736
+ if (enableColumnReordering) event.preventDefault();
1737
+ },
1738
+ onDrop: (event) => {
1739
+ if (!enableColumnReordering || !draggedColumnId) return;
1740
+ event.preventDefault();
1741
+ const ids = table.getAllLeafColumns().map((item) => item.id);
1742
+ const next = reorderIds(ids, draggedColumnId, column.id);
1743
+ onColumnOrderChange(next);
1744
+ onDraggedColumnIdChange(null);
1745
+ },
1746
+ style: {
1747
+ width: flex ? void 0 : column.getSize(),
1748
+ minWidth: flex ? column.getSize() : void 0,
1749
+ flex: flex ? String(flex) : void 0,
1750
+ position: isPinned ? "sticky" : void 0,
1751
+ left: isPinned === "left" ? column.getStart("left") : void 0,
1752
+ right: isPinned === "right" ? column.getAfter("right") : void 0,
1753
+ background: isPinned ? "inherit" : void 0,
1754
+ zIndex: isPinned ? 1 : void 0
1755
+ },
1756
+ className: cn(
1757
+ "text-muted-foreground group relative h-10 whitespace-nowrap px-3 align-middle font-medium",
1758
+ align === "left" && "text-left",
1759
+ align === "center" && "text-center",
1760
+ align === "right" && "text-right",
1761
+ canSort && "cursor-pointer select-none",
1762
+ enableColumnReordering && "cursor-grab active:cursor-grabbing",
1763
+ selected && "bg-muted text-foreground"
1764
+ ),
1765
+ onClick: canSort ? column.getToggleSortingHandler() : void 0
1766
+ },
1767
+ /* @__PURE__ */ React.createElement(
1768
+ "div",
1769
+ {
1770
+ className: cn(
1771
+ "flex min-w-0 items-center gap-1.5",
1772
+ align === "center" && "justify-center",
1773
+ align === "right" && "justify-end"
1774
+ )
1775
+ },
1776
+ canSort && align === "right" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex shrink-0 items-center gap-0.5" }, sortDir === "asc" ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "size-4" }) : sortDir === "desc" ? /* @__PURE__ */ React.createElement(ChevronDown, { className: "size-4" }) : /* @__PURE__ */ React.createElement(ChevronsUpDown, { className: "size-4 opacity-50" }), sortDir && (() => {
1777
+ const sortedCols = table.getState().sorting;
1778
+ const idx = sortedCols.findIndex((s) => s.id === column.id);
1779
+ return sortedCols.length > 1 && idx >= 0 ? /* @__PURE__ */ React.createElement("span", { className: "text-primary text-[10px] font-bold leading-none" }, idx + 1) : null;
1780
+ })()) : null,
1781
+ /* @__PURE__ */ React.createElement("span", { className: "min-w-0 truncate", title: description }, headerContent ?? getColumnHeaderText(column)),
1782
+ description ? /* @__PURE__ */ React.createElement(
1783
+ "span",
1784
+ {
1785
+ className: "text-muted-foreground/60 inline-flex shrink-0 cursor-help",
1786
+ title: description,
1787
+ "aria-label": description
1788
+ },
1789
+ "\u2139"
1790
+ ) : null,
1791
+ canSort && align !== "right" ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex shrink-0 items-center gap-0.5" }, sortDir === "asc" ? /* @__PURE__ */ React.createElement(ChevronUp, { className: "size-4" }) : sortDir === "desc" ? /* @__PURE__ */ React.createElement(ChevronDown, { className: "size-4" }) : /* @__PURE__ */ React.createElement(ChevronsUpDown, { className: "size-4 opacity-50" }), sortDir && (() => {
1792
+ const sortedCols = table.getState().sorting;
1793
+ const idx = sortedCols.findIndex((s) => s.id === column.id);
1794
+ return sortedCols.length > 1 && idx >= 0 ? /* @__PURE__ */ React.createElement("span", { className: "text-primary text-[10px] font-bold leading-none" }, idx + 1) : null;
1795
+ })()) : null
1796
+ ),
1797
+ /* @__PURE__ */ React.createElement(
1798
+ "div",
1799
+ {
1800
+ className: "absolute right-0 top-0 z-[5] flex h-full items-center px-1 opacity-0 focus-within:opacity-100 group-hover:opacity-100",
1801
+ onClick: (e) => e.stopPropagation(),
1802
+ onPointerDown: (e) => e.stopPropagation()
1803
+ },
1804
+ CustomColumnMenu ? /* @__PURE__ */ React.createElement(CustomColumnMenu, { column, table }) : /* @__PURE__ */ React.createElement(
1805
+ DataTableColumnMenu,
1806
+ {
1807
+ column,
1808
+ table,
1809
+ localeText,
1810
+ enableColumnPinning,
1811
+ enableGrouping,
1812
+ enableColumnSelection,
1813
+ selectedColumnIds,
1814
+ onSelectedColumnIdsChange,
1815
+ overlayBoundary,
1816
+ enableColumnCopy,
1817
+ lockedColumns,
1818
+ onLockedColumnsChange,
1819
+ onOpenConditionalFormatting,
1820
+ onViewStats
1821
+ }
1822
+ )
1823
+ ),
1824
+ enableColumnResizing && column.getCanResize() && resizeHandler && !isLocked ? /* @__PURE__ */ React.createElement(
1825
+ "div",
1826
+ {
1827
+ onMouseDown: (event) => resizeHandler(event),
1828
+ onTouchStart: (event) => resizeHandler(event),
1829
+ onDoubleClick: enableColumnAutoSize ? () => {
1830
+ const tableEl = document.querySelector("[data-datatable-root] table");
1831
+ if (!tableEl) return;
1832
+ const colIndex2 = table.getAllLeafColumns().findIndex((c) => c.id === column.id);
1833
+ const cells = tableEl.querySelectorAll(`tr td:nth-child(${colIndex2 + 1}), tr th:nth-child(${colIndex2 + 1})`);
1834
+ let maxWidth = 60;
1835
+ cells.forEach((el) => {
1836
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
1837
+ });
1838
+ table.setColumnSizing((prev) => ({ ...prev, [column.id]: maxWidth }));
1839
+ } : void 0,
1840
+ className: cn(
1841
+ "absolute right-0 top-0 z-10 flex h-full w-3 cursor-col-resize touch-none select-none items-center justify-center",
1842
+ "opacity-0 group-hover:opacity-100",
1843
+ column.getIsResizing() && "opacity-100"
1844
+ ),
1845
+ title: enableColumnAutoSize ? localeText.autoSizeColumn : void 0
1846
+ },
1847
+ /* @__PURE__ */ React.createElement("div", { className: cn(
1848
+ "bg-border h-full w-px",
1849
+ column.getIsResizing() && "bg-primary w-0.5"
1850
+ ) })
1851
+ ) : null,
1852
+ showQuickFilter ? /* @__PURE__ */ React.createElement(
1853
+ "div",
1854
+ {
1855
+ className: "px-1 pb-1 pt-0.5",
1856
+ onClick: (event) => event.stopPropagation(),
1857
+ onPointerDown: (event) => event.stopPropagation()
1858
+ },
1859
+ /* @__PURE__ */ React.createElement(
1860
+ Input,
1861
+ {
1862
+ value: quickFilter,
1863
+ onChange: (event) => {
1864
+ setQuickFilter(event.target.value);
1865
+ column.setFilterValue(event.target.value || void 0);
1866
+ },
1867
+ placeholder: localeText.quickFilterPlaceholder,
1868
+ className: "h-6 text-xs",
1869
+ "aria-label": `Filter ${getColumnHeaderText(column)}`
1870
+ }
1871
+ )
1872
+ ) : null
1873
+ );
1874
+ }
1875
+ function DataTableColumnMenu({
1876
+ column,
1877
+ table,
1878
+ localeText,
1879
+ enableColumnPinning,
1880
+ enableGrouping,
1881
+ enableColumnSelection,
1882
+ selectedColumnIds,
1883
+ onSelectedColumnIdsChange,
1884
+ overlayBoundary,
1885
+ enableColumnCopy,
1886
+ lockedColumns,
1887
+ onLockedColumnsChange,
1888
+ onOpenConditionalFormatting,
1889
+ onViewStats
1890
+ }) {
1891
+ const handleCopyColumn = () => {
1892
+ const rows = table.getFilteredRowModel().rows;
1893
+ const header = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
1894
+ const values = [header, ...rows.map((row) => String(row.getValue(column.id) ?? ""))].join("\n");
1895
+ navigator.clipboard?.writeText(values).catch(() => void 0);
1896
+ };
1897
+ const isLocked = lockedColumns?.includes(column.id) ?? false;
1898
+ const handleToggleLock = () => {
1899
+ if (!onLockedColumnsChange) return;
1900
+ const current = lockedColumns ?? [];
1901
+ onLockedColumnsChange(
1902
+ isLocked ? current.filter((id) => id !== column.id) : [...current, column.id]
1903
+ );
1904
+ };
1905
+ return /* @__PURE__ */ React.createElement("span", { className: "inline-flex" }, /* @__PURE__ */ React.createElement(DropdownMenu.Root, null, /* @__PURE__ */ React.createElement(DropdownMenu.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(
1906
+ Button,
1907
+ {
1908
+ type: "button",
1909
+ variant: "ghost",
1910
+ size: "icon-sm",
1911
+ "aria-label": `${column.id} ${localeText.columnMenu}`,
1912
+ className: "h-7 w-7"
1913
+ },
1914
+ "\u22EE"
1915
+ )), /* @__PURE__ */ React.createElement(
1916
+ DropdownMenu.Content,
1917
+ {
1918
+ align: "end",
1919
+ container: overlayBoundary,
1920
+ collisionBoundary: overlayBoundary,
1921
+ collisionPadding: 8,
1922
+ strategy: "absolute",
1923
+ sticky: "always",
1924
+ className: "bg-popover relative z-[1000] max-h-[min(45dvh,18rem,var(--structyl-popper-available-height))] min-w-44 overflow-y-auto"
1925
+ },
1926
+ /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.toggleSorting(false) }, localeText.sortAsc),
1927
+ /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.toggleSorting(true) }, localeText.sortDesc),
1928
+ /* @__PURE__ */ React.createElement(
1929
+ MenuButton,
1930
+ {
1931
+ onClick: () => table.setSorting((state) => state.filter((item) => item.id !== column.id))
1932
+ },
1933
+ localeText.clearSort
1934
+ ),
1935
+ column.getCanHide() ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.toggleVisibility(false) }, localeText.hideColumn) : null,
1936
+ enableColumnPinning ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.pin("left") }, localeText.pinLeft), /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.pin("right") }, localeText.pinRight), /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.pin(false) }, localeText.unpin)) : null,
1937
+ enableGrouping && column.getCanGroup() ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => column.toggleGrouping() }, column.getIsGrouped() ? localeText.ungroup : localeText.groupBy) : null,
1938
+ enableColumnSelection ? /* @__PURE__ */ React.createElement(
1939
+ MenuButton,
1940
+ {
1941
+ onClick: () => onSelectedColumnIdsChange(toggleId(selectedColumnIds, column.id))
1942
+ },
1943
+ localeText.selectColumn
1944
+ ) : null,
1945
+ enableColumnCopy ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: handleCopyColumn }, localeText.copyColumn) : null,
1946
+ onLockedColumnsChange ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: handleToggleLock }, isLocked ? localeText.unlockColumn : localeText.lockColumn) : null,
1947
+ onOpenConditionalFormatting ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => onOpenConditionalFormatting(column.id) }, localeText.conditionalFormatting ?? "Conditional formatting") : null,
1948
+ onViewStats ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => onViewStats(column.id) }, "View statistics") : null
1949
+ )));
1950
+ }
1951
+ function MenuButton({ children, onClick }) {
1952
+ return /* @__PURE__ */ React.createElement(DropdownMenu.Item, { className: "cursor-pointer", onSelect: onClick }, children);
1953
+ }
1954
+ function renderRowCells({
1955
+ row,
1956
+ rowIndex,
1957
+ renderedColumnIds,
1958
+ enableExpanding,
1959
+ renderDetailPanel,
1960
+ getCellColSpan,
1961
+ getCellRowSpan,
1962
+ enableRowPinning,
1963
+ pinRow,
1964
+ text,
1965
+ overlayBoundary,
1966
+ treeData,
1967
+ densityCellClass,
1968
+ enableRowCopy,
1969
+ getCellClassName,
1970
+ enableCellTooltip,
1971
+ onCellContextMenu,
1972
+ conditionalFormattingRules,
1973
+ enableCellSelection,
1974
+ isCellSelected,
1975
+ isSelectingCells: _isSelectingCells,
1976
+ onCellMouseDown,
1977
+ onCellMouseEnter,
1978
+ flashedCells,
1979
+ onViewStats: _onViewStats,
1980
+ rowValidationErrors
1981
+ }) {
1982
+ const copyRow = (row2) => {
1983
+ const cells2 = row2.getVisibleCells().filter((c) => !c.column.id.startsWith("__"));
1984
+ const tsv = cells2.map((c) => String(c.getValue() ?? "")).join(" ");
1985
+ navigator.clipboard?.writeText(tsv).catch(() => void 0);
1986
+ };
1987
+ const cells = row.getVisibleCells().filter((cell) => renderedColumnIds.has(cell.column.id));
1988
+ let skip = 0;
1989
+ return cells.map((cell, index) => {
1990
+ if (skip > 0) {
1991
+ skip -= 1;
1992
+ return null;
1993
+ }
1994
+ const isPinned = cell.column.getIsPinned();
1995
+ const requestedColSpan = Math.max(getCellColSpan?.(cell, row) ?? 1, 1);
1996
+ const colSpan = Math.min(requestedColSpan, cells.length - index);
1997
+ const rowSpan = Math.max(getCellRowSpan?.(cell, row) ?? 1, 1);
1998
+ skip = colSpan - 1;
1999
+ const canExpand = enableExpanding && row.getCanExpand() || !!renderDetailPanel;
2000
+ const structylMeta = getStructylMeta(cell.column);
2001
+ const align = structylMeta.align ?? "left";
2002
+ const flex = structylMeta.flex;
2003
+ const treeIndent = treeData && index === 0 ? row.depth * 16 : 0;
2004
+ const cellIsSelected = isCellSelected ? isCellSelected(rowIndex, index) : false;
2005
+ const cellIsFlashed = flashedCells ? flashedCells.has(cell.column.id) : false;
2006
+ const validationError = rowValidationErrors?.get(cell.column.id);
2007
+ const conditionalRule = conditionalFormattingRules?.find((r) => {
2008
+ if (r.columnId !== cell.column.id) return false;
2009
+ const v = cell.getValue();
2010
+ const sv = String(v ?? "");
2011
+ const rv = r.value ?? "";
2012
+ switch (r.operator) {
2013
+ case "equals":
2014
+ return sv === rv;
2015
+ case "notEquals":
2016
+ return sv !== rv;
2017
+ case "contains":
2018
+ return sv.toLowerCase().includes(rv.toLowerCase());
2019
+ case "gt":
2020
+ return Number(v) > Number(rv);
2021
+ case "gte":
2022
+ return Number(v) >= Number(rv);
2023
+ case "lt":
2024
+ return Number(v) < Number(rv);
2025
+ case "lte":
2026
+ return Number(v) <= Number(rv);
2027
+ case "empty":
2028
+ return sv === "";
2029
+ case "notEmpty":
2030
+ return sv !== "";
2031
+ default:
2032
+ return false;
2033
+ }
2034
+ });
2035
+ const tdEl = /* @__PURE__ */ React.createElement(
2036
+ "td",
2037
+ {
2038
+ key: cell.id,
2039
+ role: "gridcell",
2040
+ "aria-colindex": index + 1,
2041
+ colSpan,
2042
+ rowSpan,
2043
+ "data-pinned": isPinned || void 0,
2044
+ "data-flash": cellIsFlashed || void 0,
2045
+ style: {
2046
+ width: flex ? void 0 : cell.column.getSize(),
2047
+ minWidth: flex ? cell.column.getSize() : void 0,
2048
+ flex: flex ? String(flex) : void 0,
2049
+ position: isPinned ? "sticky" : "relative",
2050
+ left: isPinned === "left" ? cell.column.getStart("left") : void 0,
2051
+ right: isPinned === "right" ? cell.column.getAfter("right") : void 0,
2052
+ background: isPinned ? "hsl(var(--color-bg))" : conditionalRule?.backgroundColor ?? void 0,
2053
+ color: conditionalRule?.textColor ?? void 0,
2054
+ zIndex: isPinned ? 1 : void 0,
2055
+ paddingLeft: treeIndent > 0 ? treeIndent : void 0
2056
+ },
2057
+ onContextMenu: onCellContextMenu ? (event) => {
2058
+ event.preventDefault();
2059
+ onCellContextMenu(cell, row, event);
2060
+ } : void 0,
2061
+ onMouseDown: enableCellSelection ? (event) => {
2062
+ event.preventDefault();
2063
+ onCellMouseDown?.(rowIndex, index);
2064
+ } : void 0,
2065
+ onMouseEnter: enableCellSelection ? () => {
2066
+ onCellMouseEnter?.(rowIndex, index);
2067
+ } : void 0,
2068
+ className: cn(
2069
+ densityCellClass,
2070
+ "relative align-middle",
2071
+ align === "center" && "text-center",
2072
+ align === "right" && "text-right",
2073
+ getCellClassName?.(cell, row),
2074
+ cellIsSelected && "bg-primary/10 outline outline-1 outline-primary",
2075
+ enableCellSelection && "select-none cursor-cell",
2076
+ validationError && "outline outline-1 outline-destructive"
2077
+ )
2078
+ },
2079
+ validationError ? /* @__PURE__ */ React.createElement("span", { className: "absolute top-0.5 right-0.5 h-1.5 w-1.5 rounded-full bg-destructive", "aria-hidden": "true" }) : null,
2080
+ /* @__PURE__ */ React.createElement(
2081
+ "div",
2082
+ {
2083
+ className: cn(
2084
+ "flex min-w-0 items-center gap-1",
2085
+ align === "center" && "justify-center",
2086
+ align === "right" && "justify-end"
2087
+ )
2088
+ },
2089
+ index === 0 && canExpand ? /* @__PURE__ */ React.createElement(
2090
+ Button,
2091
+ {
2092
+ type: "button",
2093
+ variant: "ghost",
2094
+ size: "icon-sm",
2095
+ onClick: () => row.toggleExpanded(),
2096
+ className: "mr-1 h-5 w-5 shrink-0",
2097
+ "aria-label": row.getIsExpanded() ? text.collapseRow : text.expandRow
2098
+ },
2099
+ row.getIsExpanded() ? /* @__PURE__ */ React.createElement(ChevronDown, { className: "size-3.5" }) : /* @__PURE__ */ React.createElement(ChevronRight, { className: "size-3.5" })
2100
+ ) : null,
2101
+ enableRowPinning && index === 0 ? /* @__PURE__ */ React.createElement(
2102
+ "span",
2103
+ {
2104
+ className: "mr-1 inline-flex",
2105
+ onClick: (event) => event.stopPropagation(),
2106
+ onPointerDown: (event) => event.stopPropagation()
2107
+ },
2108
+ /* @__PURE__ */ React.createElement(DropdownMenu.Root, null, /* @__PURE__ */ React.createElement(DropdownMenu.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(
2109
+ Button,
2110
+ {
2111
+ type: "button",
2112
+ variant: "ghost",
2113
+ size: "icon-sm",
2114
+ "aria-label": text.rowActions,
2115
+ className: "h-5 w-5"
2116
+ },
2117
+ "\u22EE"
2118
+ )), /* @__PURE__ */ React.createElement(
2119
+ DropdownMenu.Content,
2120
+ {
2121
+ align: "start",
2122
+ container: overlayBoundary,
2123
+ collisionBoundary: overlayBoundary,
2124
+ collisionPadding: 8,
2125
+ strategy: "absolute",
2126
+ sticky: "always",
2127
+ className: "bg-popover relative z-[1000] max-h-[min(45dvh,18rem,var(--structyl-popper-available-height))] min-w-28 overflow-y-auto"
2128
+ },
2129
+ /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => pinRow(row.id, "top") }, text.pinRowTop),
2130
+ /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => pinRow(row.id, "bottom") }, text.pinRowBottom),
2131
+ /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => pinRow(row.id, false) }, text.unpin),
2132
+ enableRowCopy ? /* @__PURE__ */ React.createElement(MenuButton, { onClick: () => copyRow(row) }, text.copyRow) : null
2133
+ ))
2134
+ ) : null,
2135
+ /* @__PURE__ */ React.createElement(
2136
+ "span",
2137
+ {
2138
+ className: "min-w-0 truncate",
2139
+ title: enableCellTooltip ? String(cell.getValue() ?? "") : void 0
2140
+ },
2141
+ flexRender(cell.column.columnDef.cell, cell.getContext())
2142
+ )
2143
+ )
2144
+ );
2145
+ if (validationError) {
2146
+ return /* @__PURE__ */ React.createElement(Tooltip.Provider, { key: cell.id }, /* @__PURE__ */ React.createElement(Tooltip.Root, null, /* @__PURE__ */ React.createElement(Tooltip.Trigger, { asChild: true }, tdEl), /* @__PURE__ */ React.createElement(Tooltip.Content, null, validationError)));
2147
+ }
2148
+ return tdEl;
2149
+ });
2150
+ }
2151
+ function renderLoadingRows({
2152
+ colSpan,
2153
+ columns,
2154
+ variant,
2155
+ rows,
2156
+ text,
2157
+ LoadingSkeleton,
2158
+ LoadingOverlay
2159
+ }) {
2160
+ if (LoadingOverlay && variant !== "skeleton") {
2161
+ return /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "h-24 text-center" }, /* @__PURE__ */ React.createElement(LoadingOverlay, { text: text.loading, variant })));
2162
+ }
2163
+ if (LoadingSkeleton && variant === "skeleton") {
2164
+ return /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan }, /* @__PURE__ */ React.createElement(LoadingSkeleton, { rows, columns })));
2165
+ }
2166
+ if (variant === "spinner") {
2167
+ return /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "text-muted-foreground h-24 text-center" }, /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "border-primary h-4 w-4 animate-spin rounded-full border-2 border-t-transparent" }), text.loading)));
2168
+ }
2169
+ if (variant === "skeleton") {
2170
+ return Array.from({ length: rows }, (_, rowIndex) => /* @__PURE__ */ React.createElement("tr", { key: rowIndex, className: "border-border border-b" }, Array.from({ length: Math.max(columns, 1) }, (_2, columnIndex) => /* @__PURE__ */ React.createElement("td", { key: columnIndex, className: "p-3" }, /* @__PURE__ */ React.createElement("div", { className: "bg-muted h-4 w-full animate-pulse rounded" })))));
2171
+ }
2172
+ return /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan, className: "text-muted-foreground h-24 text-center" }, text.loading));
2173
+ }
2174
+ function DataTableFooter({
2175
+ table,
2176
+ aggregations,
2177
+ renderedColumnIds,
2178
+ columnPaddingLeft,
2179
+ columnPaddingRight,
2180
+ localeText
2181
+ }) {
2182
+ const rows = table.getFilteredRowModel().rows;
2183
+ return /* @__PURE__ */ React.createElement("tfoot", { className: "border-border bg-bg sticky bottom-0 z-10 border-t" }, /* @__PURE__ */ React.createElement("tr", null, columnPaddingLeft > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingLeft } }) : null, table.getVisibleLeafColumns().filter((column) => renderedColumnIds.has(column.id)).map((column, index) => {
2184
+ const aggregation = aggregations[column.id];
2185
+ return /* @__PURE__ */ React.createElement("td", { key: column.id, className: "p-3 font-medium" }, aggregation ? aggregateColumn(
2186
+ aggregation,
2187
+ rows.map((row) => row.getValue(column.id)),
2188
+ rows
2189
+ ) : index === 0 ? localeText.total : null);
2190
+ }), columnPaddingRight > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingRight } }) : null));
2191
+ }
2192
+ function DataTablePagination({
2193
+ table,
2194
+ localeText = defaultLocaleText,
2195
+ pageSizeOptions = [10, 25, 50, 100],
2196
+ showTotalRows = true,
2197
+ overlayBoundary
2198
+ }) {
2199
+ const { pageIndex, pageSize } = table.getState().pagination;
2200
+ const pageCount = table.getPageCount();
2201
+ const totalRows = table.getFilteredRowModel().rows.length;
2202
+ const pageOptions = Array.from({ length: Math.max(pageCount, 1) }, (_, i) => i + 1);
2203
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-3 py-3 sm:flex-row sm:items-center sm:justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-muted-foreground flex flex-wrap items-center gap-4 text-sm" }, showTotalRows ? /* @__PURE__ */ React.createElement("span", { className: "tabular-nums" }, localeText.totalRows(totalRows)) : null, /* @__PURE__ */ React.createElement("label", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "whitespace-nowrap" }, localeText.rowsPerPage), /* @__PURE__ */ React.createElement(
2204
+ Select.Root,
2205
+ {
2206
+ value: String(pageSize),
2207
+ onValueChange: (v) => table.setPageSize(Number(v))
2208
+ },
2209
+ /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-20" }, /* @__PURE__ */ React.createElement(Select.Value, null)),
2210
+ /* @__PURE__ */ React.createElement(
2211
+ Select.Content,
2212
+ {
2213
+ options: pageSizeOptions.map((s) => ({ value: String(s), label: String(s) })),
2214
+ container: overlayBoundary
2215
+ }
2216
+ )
2217
+ ))), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-1" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground mr-1 text-sm tabular-nums" }, localeText.page(pageIndex + 1, Math.max(pageCount, 1))), /* @__PURE__ */ React.createElement(
2218
+ Button,
2219
+ {
2220
+ type: "button",
2221
+ variant: "outline",
2222
+ size: "icon-sm",
2223
+ className: "h-8 w-8",
2224
+ onClick: () => table.setPageIndex(0),
2225
+ disabled: !table.getCanPreviousPage(),
2226
+ "aria-label": localeText.firstPage,
2227
+ title: localeText.firstPage
2228
+ },
2229
+ /* @__PURE__ */ React.createElement(ChevronsLeft, { className: "size-4" })
2230
+ ), /* @__PURE__ */ React.createElement(
2231
+ Button,
2232
+ {
2233
+ type: "button",
2234
+ variant: "outline",
2235
+ size: "icon-sm",
2236
+ className: "h-8 w-8",
2237
+ onClick: () => table.previousPage(),
2238
+ disabled: !table.getCanPreviousPage(),
2239
+ "aria-label": localeText.previous,
2240
+ title: localeText.previous
2241
+ },
2242
+ /* @__PURE__ */ React.createElement(ChevronLeft, { className: "size-4" })
2243
+ ), /* @__PURE__ */ React.createElement(
2244
+ Select.Root,
2245
+ {
2246
+ value: String(pageIndex + 1),
2247
+ onValueChange: (v) => table.setPageIndex(Number(v) - 1)
2248
+ },
2249
+ /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-16", "aria-label": localeText.goToPage }, /* @__PURE__ */ React.createElement(Select.Value, null)),
2250
+ /* @__PURE__ */ React.createElement(
2251
+ Select.Content,
2252
+ {
2253
+ options: pageOptions.map((p) => ({ value: String(p), label: String(p) })),
2254
+ container: overlayBoundary
2255
+ }
2256
+ )
2257
+ ), /* @__PURE__ */ React.createElement(
2258
+ Button,
2259
+ {
2260
+ type: "button",
2261
+ variant: "outline",
2262
+ size: "icon-sm",
2263
+ className: "h-8 w-8",
2264
+ onClick: () => table.nextPage(),
2265
+ disabled: !table.getCanNextPage(),
2266
+ "aria-label": localeText.next,
2267
+ title: localeText.next
2268
+ },
2269
+ /* @__PURE__ */ React.createElement(ChevronRight, { className: "size-4" })
2270
+ ), /* @__PURE__ */ React.createElement(
2271
+ Button,
2272
+ {
2273
+ type: "button",
2274
+ variant: "outline",
2275
+ size: "icon-sm",
2276
+ className: "h-8 w-8",
2277
+ onClick: () => table.setPageIndex(pageCount - 1),
2278
+ disabled: !table.getCanNextPage(),
2279
+ "aria-label": localeText.lastPage,
2280
+ title: localeText.lastPage
2281
+ },
2282
+ /* @__PURE__ */ React.createElement(ChevronsRight, { className: "size-4" })
2283
+ )));
2284
+ }
2285
+ function DataTableToolbar({
2286
+ table,
2287
+ filterColumnId,
2288
+ filterPlaceholder = "Search\u2026",
2289
+ globalFilter,
2290
+ onGlobalFilterChange,
2291
+ children
2292
+ }) {
2293
+ const column = filterColumnId ? table.getColumn(filterColumnId) : null;
2294
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-col gap-2 py-2 sm:flex-row sm:items-center sm:justify-between" }, onGlobalFilterChange ? /* @__PURE__ */ React.createElement(
2295
+ DataTableGlobalSearch,
2296
+ {
2297
+ value: globalFilter ?? "",
2298
+ onValueChange: onGlobalFilterChange,
2299
+ placeholder: filterPlaceholder
2300
+ }
2301
+ ) : column ? /* @__PURE__ */ React.createElement(
2302
+ Input,
2303
+ {
2304
+ type: "text",
2305
+ value: column.getFilterValue() ?? "",
2306
+ onChange: (event) => column.setFilterValue(event.target.value),
2307
+ placeholder: filterPlaceholder,
2308
+ className: "min-w-0 sm:w-64"
2309
+ }
2310
+ ) : null, children);
2311
+ }
2312
+ function DataTableGlobalSearch({
2313
+ value,
2314
+ onValueChange,
2315
+ placeholder
2316
+ }) {
2317
+ return /* @__PURE__ */ React.createElement(
2318
+ Input,
2319
+ {
2320
+ type: "search",
2321
+ value,
2322
+ onChange: (event) => onValueChange(event.target.value),
2323
+ placeholder,
2324
+ className: "min-w-0 sm:w-64"
2325
+ }
2326
+ );
2327
+ }
2328
+ function DataTableSelect({
2329
+ value,
2330
+ onValueChange,
2331
+ options,
2332
+ ariaLabel,
2333
+ className,
2334
+ searchable,
2335
+ overlayBoundary
2336
+ }) {
2337
+ const selectedOption = options.find((option) => option.value === value);
2338
+ return /* @__PURE__ */ React.createElement(Select.Root, { value, onValueChange, searchable }, /* @__PURE__ */ React.createElement(Select.Trigger, { "aria-label": ariaLabel, className: cn("min-w-0", className) }, /* @__PURE__ */ React.createElement(Select.Value, { placeholder: selectedOption?.label ?? value }, selectedOption?.label ?? value)), /* @__PURE__ */ React.createElement(
2339
+ Select.Content,
2340
+ {
2341
+ container: overlayBoundary,
2342
+ collisionBoundary: overlayBoundary,
2343
+ collisionPadding: 8,
2344
+ strategy: "absolute",
2345
+ sticky: "always",
2346
+ showCreateItem: false,
2347
+ className: "bg-popover relative z-[1100] max-h-[min(45dvh,18rem,var(--structyl-popper-available-height))] min-w-44"
2348
+ },
2349
+ options.map((option) => /* @__PURE__ */ React.createElement(Select.Item, { key: option.value, value: option.value }, option.label))
2350
+ ));
2351
+ }
2352
+ function DataTableAdvancedFilter({
2353
+ table,
2354
+ filter,
2355
+ onFilterChange,
2356
+ localeText = defaultLocaleText,
2357
+ overlayBoundary
2358
+ }) {
2359
+ const columns = table.getAllLeafColumns().filter((column) => column.getCanFilter());
2360
+ const group = filter ?? createFilterGroup("root");
2361
+ return /* @__PURE__ */ React.createElement(Popover.Root, null, /* @__PURE__ */ React.createElement(Popover.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm" }, /* @__PURE__ */ React.createElement(Filter, { className: "mr-1.5 size-4" }), localeText.filters)), /* @__PURE__ */ React.createElement(
2362
+ Popover.Content,
2363
+ {
2364
+ align: "start",
2365
+ sideOffset: 6,
2366
+ container: overlayBoundary,
2367
+ collisionBoundary: overlayBoundary,
2368
+ collisionPadding: 12,
2369
+ strategy: "absolute",
2370
+ sticky: "always",
2371
+ role: "region",
2372
+ "aria-label": localeText.filters,
2373
+ "data-datatable-filter-panel": "",
2374
+ className: "bg-popover relative z-[1000] flex max-h-[min(55dvh,24rem,var(--structyl-popper-available-height))] w-[min(44rem,var(--structyl-popper-available-width),calc(100vw-2rem))] min-w-0 flex-col gap-2 overflow-hidden rounded-md p-3"
2375
+ },
2376
+ /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto" }, /* @__PURE__ */ React.createElement(
2377
+ DataTableFilterGroupEditor,
2378
+ {
2379
+ group,
2380
+ columns,
2381
+ localeText,
2382
+ overlayBoundary,
2383
+ onGroupChange: (next) => onFilterChange(next.items.length ? next : void 0)
2384
+ }
2385
+ )),
2386
+ /* @__PURE__ */ React.createElement("div", { className: "border-border flex justify-end border-t pt-2" }, /* @__PURE__ */ React.createElement(
2387
+ Button,
2388
+ {
2389
+ type: "button",
2390
+ variant: "outline",
2391
+ size: "sm",
2392
+ onClick: () => onFilterChange(void 0)
2393
+ },
2394
+ localeText.clearFilters
2395
+ ))
2396
+ ));
2397
+ }
2398
+ function DataTableFilterGroupEditor({
2399
+ group,
2400
+ columns,
2401
+ localeText,
2402
+ overlayBoundary,
2403
+ depth = 0,
2404
+ onGroupChange,
2405
+ onRemove
2406
+ }) {
2407
+ const addRule = () => {
2408
+ const firstColumn = columns[0];
2409
+ if (!firstColumn) return;
2410
+ onGroupChange({
2411
+ ...group,
2412
+ items: [...group.items, createFilterRule(firstColumn.id)]
2413
+ });
2414
+ };
2415
+ const addGroup = () => {
2416
+ onGroupChange({
2417
+ ...group,
2418
+ items: [...group.items, createFilterGroup()]
2419
+ });
2420
+ };
2421
+ const updateItem = (item) => {
2422
+ onGroupChange({
2423
+ ...group,
2424
+ items: group.items.map((current) => current.id === item.id ? item : current)
2425
+ });
2426
+ };
2427
+ const removeItem = (itemId) => {
2428
+ onGroupChange({
2429
+ ...group,
2430
+ items: group.items.filter((item) => item.id !== itemId)
2431
+ });
2432
+ };
2433
+ return /* @__PURE__ */ React.createElement(
2434
+ "div",
2435
+ {
2436
+ className: cn("border-border grid gap-2 rounded-md border p-2", depth > 0 && "bg-muted/30")
2437
+ },
2438
+ /* @__PURE__ */ React.createElement("div", { className: "grid gap-2 sm:flex sm:flex-wrap sm:items-end" }, /* @__PURE__ */ React.createElement("div", { className: "text-muted-foreground grid gap-1 text-xs font-medium sm:min-w-40" }, /* @__PURE__ */ React.createElement("span", null, localeText.filterLogicLabel), /* @__PURE__ */ React.createElement(
2439
+ DataTableSelect,
2440
+ {
2441
+ value: group.logic,
2442
+ onValueChange: (value) => onGroupChange({ ...group, logic: value }),
2443
+ options: [
2444
+ { value: "and", label: "AND" },
2445
+ { value: "or", label: "OR" }
2446
+ ],
2447
+ ariaLabel: localeText.filterLogicLabel,
2448
+ className: "sm:w-40",
2449
+ overlayBoundary
2450
+ }
2451
+ )), /* @__PURE__ */ React.createElement(
2452
+ Button,
2453
+ {
2454
+ type: "button",
2455
+ variant: "outline",
2456
+ size: "sm",
2457
+ className: "w-full sm:w-auto",
2458
+ onClick: addRule,
2459
+ disabled: columns.length === 0
2460
+ },
2461
+ localeText.addFilter
2462
+ ), /* @__PURE__ */ React.createElement(
2463
+ Button,
2464
+ {
2465
+ type: "button",
2466
+ variant: "outline",
2467
+ size: "sm",
2468
+ className: "w-full sm:w-auto",
2469
+ onClick: addGroup
2470
+ },
2471
+ localeText.addFilterGroup
2472
+ ), onRemove ? /* @__PURE__ */ React.createElement(
2473
+ Button,
2474
+ {
2475
+ type: "button",
2476
+ variant: "outline",
2477
+ size: "sm",
2478
+ className: "w-full sm:w-auto",
2479
+ onClick: onRemove
2480
+ },
2481
+ localeText.removeFilterGroup
2482
+ ) : null),
2483
+ group.items.map(
2484
+ (item) => isFilterRule(item) ? /* @__PURE__ */ React.createElement(
2485
+ DataTableFilterRuleEditor,
2486
+ {
2487
+ key: item.id,
2488
+ rule: item,
2489
+ columns,
2490
+ localeText,
2491
+ overlayBoundary,
2492
+ onRuleChange: updateItem,
2493
+ onRemove: () => removeItem(item.id)
2494
+ }
2495
+ ) : /* @__PURE__ */ React.createElement(
2496
+ DataTableFilterGroupEditor,
2497
+ {
2498
+ key: item.id,
2499
+ group: item,
2500
+ columns,
2501
+ localeText,
2502
+ overlayBoundary,
2503
+ depth: depth + 1,
2504
+ onGroupChange: updateItem,
2505
+ onRemove: () => removeItem(item.id)
2506
+ }
2507
+ )
2508
+ )
2509
+ );
2510
+ }
2511
+ function DataTableFilterRuleEditor({
2512
+ rule,
2513
+ columns,
2514
+ localeText,
2515
+ overlayBoundary,
2516
+ onRuleChange,
2517
+ onRemove
2518
+ }) {
2519
+ return /* @__PURE__ */ React.createElement("div", { className: "grid min-w-0 gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "text-muted-foreground grid gap-1 text-xs font-medium" }, /* @__PURE__ */ React.createElement("span", null, localeText.filterColumnLabel), /* @__PURE__ */ React.createElement(
2520
+ DataTableSelect,
2521
+ {
2522
+ value: rule.columnId,
2523
+ onValueChange: (value) => onRuleChange({ ...rule, columnId: value }),
2524
+ options: columns.map((column) => ({ value: column.id, label: column.id })),
2525
+ ariaLabel: localeText.filterColumnLabel,
2526
+ searchable: columns.length > 8,
2527
+ overlayBoundary
2528
+ }
2529
+ )), /* @__PURE__ */ React.createElement("div", { className: "text-muted-foreground grid gap-1 text-xs font-medium" }, /* @__PURE__ */ React.createElement("span", null, localeText.filterOperatorLabel), /* @__PURE__ */ React.createElement(
2530
+ DataTableSelect,
2531
+ {
2532
+ value: rule.operator,
2533
+ onValueChange: (value) => onRuleChange({ ...rule, operator: value }),
2534
+ options: filterOperators.map((operator) => ({ value: operator, label: operator })),
2535
+ ariaLabel: localeText.filterOperatorLabel,
2536
+ overlayBoundary
2537
+ }
2538
+ ))), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-[1fr_auto] items-end gap-2" }, /* @__PURE__ */ React.createElement("label", { className: "text-muted-foreground grid min-w-0 gap-1 text-xs font-medium" }, localeText.filterValueLabel, /* @__PURE__ */ React.createElement(
2539
+ Input,
2540
+ {
2541
+ value: String(rule.value ?? ""),
2542
+ onChange: (event) => onRuleChange({ ...rule, value: event.target.value }),
2543
+ className: "min-w-0"
2544
+ }
2545
+ )), /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: onRemove }, localeText.removeFilter)));
2546
+ }
2547
+ function DataTableColumnFilter({
2548
+ column,
2549
+ title
2550
+ }) {
2551
+ const facets = column.getFacetedUniqueValues();
2552
+ const filterValue = String(column.getFilterValue() ?? "");
2553
+ const options = Array.from(facets.keys()).map((option) => String(option)).filter((option) => normalizeSearch(option).includes(normalizeSearch(filterValue))).slice(0, 200);
2554
+ return /* @__PURE__ */ React.createElement("div", { className: "flex w-full max-w-sm flex-col gap-1" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-medium" }, title ?? column.id), /* @__PURE__ */ React.createElement(
2555
+ Combobox.Root,
2556
+ {
2557
+ inputValue: filterValue,
2558
+ onInputValueChange: (value) => column.setFilterValue(value),
2559
+ value: filterValue,
2560
+ onValueChange: (value) => column.setFilterValue(value)
2561
+ },
2562
+ /* @__PURE__ */ React.createElement(Combobox.Input, { "aria-label": title ?? column.id }),
2563
+ /* @__PURE__ */ React.createElement(Combobox.Content, { className: "bg-popover relative z-[1100] max-h-[min(45dvh,18rem)] w-[var(--structyl-popper-anchor-width)]" }, options.length ? options.map((option) => /* @__PURE__ */ React.createElement(Combobox.Item, { key: option, value: option }, option)) : /* @__PURE__ */ React.createElement(Combobox.Empty, null, "No options"))
2564
+ ));
2565
+ }
2566
+ function DataTableColumnVisibility({ table }) {
2567
+ return /* @__PURE__ */ React.createElement(DataTableColumnConfiguration, { table });
2568
+ }
2569
+ function DataTableColumnConfiguration({
2570
+ table,
2571
+ selectedColumnIds = [],
2572
+ onSelectedColumnIdsChange,
2573
+ localeText = defaultLocaleText,
2574
+ enableColumnSelection,
2575
+ enableGrouping,
2576
+ enableColumnPinning,
2577
+ overlayBoundary
2578
+ }) {
2579
+ return /* @__PURE__ */ React.createElement(Popover.Root, null, /* @__PURE__ */ React.createElement(Popover.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm" }, /* @__PURE__ */ React.createElement(Columns3, { className: "mr-1.5 size-4" }), localeText.columns)), /* @__PURE__ */ React.createElement(
2580
+ Popover.Content,
2581
+ {
2582
+ align: "end",
2583
+ sideOffset: 6,
2584
+ container: overlayBoundary,
2585
+ collisionBoundary: overlayBoundary,
2586
+ collisionPadding: 8,
2587
+ strategy: "absolute",
2588
+ sticky: "always",
2589
+ "data-datatable-column-panel": "",
2590
+ className: "bg-popover relative z-[1000] grid max-h-[min(45dvh,18rem,var(--structyl-popper-available-height))] w-[min(18rem,var(--structyl-popper-available-width),calc(100vw-2rem))] gap-1 overflow-auto rounded-md p-1"
2591
+ },
2592
+ table.getAllLeafColumns().map((column) => /* @__PURE__ */ React.createElement("div", { key: column.id, className: "hover:bg-accent grid gap-1 rounded-sm px-2 py-1 text-sm" }, /* @__PURE__ */ React.createElement("label", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement(
2593
+ Checkbox,
2594
+ {
2595
+ checked: column.getIsVisible(),
2596
+ disabled: !column.getCanHide(),
2597
+ onCheckedChange: (checked) => column.toggleVisibility(checked === true),
2598
+ "aria-label": `Toggle ${column.id}`
2599
+ }
2600
+ ), /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate" }, column.id)), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap gap-1 pl-5" }, enableColumnSelection ? /* @__PURE__ */ React.createElement(
2601
+ Button,
2602
+ {
2603
+ type: "button",
2604
+ variant: "outline",
2605
+ size: "sm",
2606
+ className: "h-7 px-2 text-xs",
2607
+ onClick: () => onSelectedColumnIdsChange?.(toggleId(selectedColumnIds, column.id))
2608
+ },
2609
+ localeText.selectColumn
2610
+ ) : null, enableColumnPinning ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2611
+ Button,
2612
+ {
2613
+ type: "button",
2614
+ variant: "outline",
2615
+ size: "sm",
2616
+ className: "h-7 px-2 text-xs",
2617
+ onClick: () => column.pin("left")
2618
+ },
2619
+ localeText.pinLeft
2620
+ ), /* @__PURE__ */ React.createElement(
2621
+ Button,
2622
+ {
2623
+ type: "button",
2624
+ variant: "outline",
2625
+ size: "sm",
2626
+ className: "h-7 px-2 text-xs",
2627
+ onClick: () => column.pin(false)
2628
+ },
2629
+ localeText.unpin
2630
+ )) : null, enableGrouping && column.getCanGroup() ? /* @__PURE__ */ React.createElement(
2631
+ Button,
2632
+ {
2633
+ type: "button",
2634
+ variant: "outline",
2635
+ size: "sm",
2636
+ className: "h-7 px-2 text-xs",
2637
+ onClick: () => column.toggleGrouping()
2638
+ },
2639
+ column.getIsGrouped() ? localeText.ungroup : localeText.groupBy
2640
+ ) : null)))
2641
+ ));
2642
+ }
2643
+ function DataTableInlineCreateRow({
2644
+ createRow,
2645
+ colSpan,
2646
+ localeText
2647
+ }) {
2648
+ const initialValues = React.useMemo(
2649
+ () => Object.fromEntries(createRow.fields.map((field) => [field.id, ""])),
2650
+ [createRow.fields]
2651
+ );
2652
+ const [values, setValues] = React.useState(initialValues);
2653
+ React.useEffect(() => setValues(initialValues), [initialValues]);
2654
+ return /* @__PURE__ */ React.createElement("tr", { className: "border-border bg-muted/20 border-b" }, /* @__PURE__ */ React.createElement("td", { colSpan, className: "p-3" }, /* @__PURE__ */ React.createElement(
2655
+ "form",
2656
+ {
2657
+ className: "flex flex-wrap items-end gap-2",
2658
+ onSubmit: (event) => {
2659
+ event.preventDefault();
2660
+ createRow.onAdd(values);
2661
+ setValues(initialValues);
2662
+ }
2663
+ },
2664
+ createRow.fields.map((field) => /* @__PURE__ */ React.createElement("label", { key: field.id, className: "text-muted-foreground grid gap-1 text-xs font-medium" }, field.label ?? field.id, /* @__PURE__ */ React.createElement(
2665
+ Input,
2666
+ {
2667
+ type: field.type ?? "text",
2668
+ value: values[field.id] ?? "",
2669
+ placeholder: field.placeholder,
2670
+ onChange: (event) => setValues((current) => ({ ...current, [field.id]: event.target.value }))
2671
+ }
2672
+ ))),
2673
+ /* @__PURE__ */ React.createElement(Button, { type: "submit", variant: "outline", size: "sm" }, createRow.label ?? localeText.addRow)
2674
+ )));
2675
+ }
2676
+ function exportToCSV(table, filename = "export.csv", options) {
2677
+ const cols = options?.includeHidden ? table.getAllLeafColumns() : table.getVisibleLeafColumns();
2678
+ const headers = cols.map((column) => column.id);
2679
+ const rows = options?.onlySelected ? table.getFilteredSelectedRowModel().rows : table.getFilteredRowModel().rows;
2680
+ const lines = [headers.join(",")];
2681
+ for (const row of rows) {
2682
+ lines.push(cols.map((column) => escapeCsv(row.getValue(column.id))).join(","));
2683
+ }
2684
+ download(lines.join("\n"), filename, "text/csv;charset=utf-8;");
2685
+ }
2686
+ function exportToJSON(table, filename = "export.json", options) {
2687
+ const rows = options?.onlySelected ? table.getFilteredSelectedRowModel().rows : table.getFilteredRowModel().rows;
2688
+ const data = rows.map((row) => row.original);
2689
+ download(JSON.stringify(data, null, 2), filename, "application/json");
2690
+ }
2691
+ function download(content, filename, mime) {
2692
+ if (typeof document === "undefined") return;
2693
+ const blob = new Blob([content], { type: mime });
2694
+ const url = URL.createObjectURL(blob);
2695
+ const anchor = document.createElement("a");
2696
+ anchor.href = url;
2697
+ anchor.download = filename;
2698
+ anchor.click();
2699
+ setTimeout(() => URL.revokeObjectURL(url), 0);
2700
+ }
2701
+ function EditableCell({ value, onCommit, type = "text" }) {
2702
+ const [editing, setEditing] = React.useState(false);
2703
+ const [local, setLocal] = React.useState(String(value ?? ""));
2704
+ React.useEffect(() => {
2705
+ if (!editing) setLocal(String(value ?? ""));
2706
+ }, [value, editing]);
2707
+ if (!editing) {
2708
+ return /* @__PURE__ */ React.createElement(
2709
+ "span",
2710
+ {
2711
+ tabIndex: 0,
2712
+ onClick: () => setEditing(true),
2713
+ onKeyDown: (event) => {
2714
+ if (event.key === "Enter" || event.key === " ") setEditing(true);
2715
+ },
2716
+ className: "hover:bg-accent cursor-text rounded px-1"
2717
+ },
2718
+ String(value ?? "")
2719
+ );
2720
+ }
2721
+ return /* @__PURE__ */ React.createElement(
2722
+ Input,
2723
+ {
2724
+ autoFocus: true,
2725
+ type,
2726
+ value: local,
2727
+ onChange: (event) => setLocal(event.target.value),
2728
+ onBlur: () => {
2729
+ const next = type === "number" ? Number(local) : local;
2730
+ onCommit(next);
2731
+ setEditing(false);
2732
+ },
2733
+ onKeyDown: (event) => {
2734
+ if (event.key === "Enter") {
2735
+ const next = type === "number" ? Number(local) : local;
2736
+ onCommit(next);
2737
+ setEditing(false);
2738
+ } else if (event.key === "Escape") {
2739
+ setLocal(String(value ?? ""));
2740
+ setEditing(false);
2741
+ }
2742
+ },
2743
+ className: "w-full"
2744
+ }
2745
+ );
2746
+ }
2747
+ function DataTableCardView({
2748
+ table,
2749
+ breakpoint,
2750
+ allRows
2751
+ }) {
2752
+ const leafColumns = table.getAllLeafColumns().filter(
2753
+ (c) => c.getIsVisible() && !c.id.startsWith("__")
2754
+ );
2755
+ const hiddenClass = breakpoint === "sm" ? "sm:hidden" : breakpoint === "md" ? "md:hidden" : "lg:hidden";
2756
+ return /* @__PURE__ */ React.createElement("div", { className: cn("grid gap-3", hiddenClass) }, allRows.map((row) => /* @__PURE__ */ React.createElement(
2757
+ "div",
2758
+ {
2759
+ key: row.id,
2760
+ className: "border-border bg-card rounded-lg border p-4",
2761
+ "data-state": row.getIsSelected() ? "selected" : void 0
2762
+ },
2763
+ leafColumns.map((col) => {
2764
+ const cell = row.getAllCells().find((c) => c.column.id === col.id);
2765
+ if (!cell) return null;
2766
+ const label = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
2767
+ return /* @__PURE__ */ React.createElement("div", { key: col.id, className: "mb-2 flex items-start justify-between gap-2 text-sm last:mb-0" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground shrink-0 font-medium" }, label), /* @__PURE__ */ React.createElement("span", { className: "text-right" }, flexRender(col.columnDef.cell, cell.getContext())));
2768
+ })
2769
+ )));
2770
+ }
2771
+ function createRowNumberColumn(text) {
2772
+ return {
2773
+ id: "__rownum",
2774
+ size: 48,
2775
+ enableSorting: false,
2776
+ enableHiding: false,
2777
+ enableResizing: false,
2778
+ header: () => /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground text-xs font-medium" }, text.rowNumberHeader),
2779
+ cell: ({ row }) => /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground tabular-nums text-xs" }, row.index + 1)
2780
+ };
2781
+ }
2782
+ function DataTableStatusBar({
2783
+ table,
2784
+ localeText,
2785
+ validationErrorCount = 0
2786
+ }) {
2787
+ const totalRows = table.getFilteredRowModel().rows.length;
2788
+ const selectedRows = table.getFilteredSelectedRowModel().rows.length;
2789
+ return /* @__PURE__ */ React.createElement("div", { className: "border-border text-muted-foreground flex items-center gap-4 border-t px-3 py-1.5 text-xs" }, /* @__PURE__ */ React.createElement("span", null, localeText.statusBarRows(totalRows)), selectedRows > 0 ? /* @__PURE__ */ React.createElement("span", null, localeText.statusBarSelected(selectedRows)) : null, validationErrorCount > 0 ? /* @__PURE__ */ React.createElement("span", { className: "text-destructive" }, validationErrorCount, " validation error", validationErrorCount !== 1 ? "s" : "") : null);
2790
+ }
2791
+ function createSelectionColumn(multi) {
2792
+ return {
2793
+ id: "__select",
2794
+ size: 42,
2795
+ enableSorting: false,
2796
+ enableHiding: false,
2797
+ header: ({ table }) => multi ? /* @__PURE__ */ React.createElement(
2798
+ Checkbox,
2799
+ {
2800
+ "aria-label": "Select all rows",
2801
+ checked: table.getIsAllPageRowsSelected() ? true : table.getIsSomePageRowsSelected() ? "indeterminate" : false,
2802
+ onCheckedChange: (checked) => table.toggleAllPageRowsSelected(checked === true)
2803
+ }
2804
+ ) : null,
2805
+ cell: ({ row }) => /* @__PURE__ */ React.createElement(
2806
+ Checkbox,
2807
+ {
2808
+ "aria-label": "Select row",
2809
+ checked: row.getIsSelected(),
2810
+ disabled: !row.getCanSelect(),
2811
+ onCheckedChange: (checked) => row.toggleSelected(checked === true)
2812
+ }
2813
+ )
2814
+ };
2815
+ }
2816
+ function createActionColumn(rowActions, text) {
2817
+ return {
2818
+ id: "__actions",
2819
+ header: text.rowActions,
2820
+ enableSorting: false,
2821
+ enableHiding: false,
2822
+ cell: ({ row }) => rowActions(row)
2823
+ };
2824
+ }
2825
+ function createRowTotalColumn(rowTotals, text) {
2826
+ const options = typeof rowTotals === "object" ? rowTotals : {};
2827
+ const id = options.id ?? "__row_total";
2828
+ return {
2829
+ id,
2830
+ header: () => options.header ?? text.rowTotal,
2831
+ enableSorting: false,
2832
+ cell: ({ row, table }) => {
2833
+ const columns = options.columns ?? table.getVisibleLeafColumns().map((column) => column.id);
2834
+ const total = columns.reduce((sum, columnId) => sum + toNumber(row.getValue(columnId)), 0);
2835
+ return options.format?.(total, row) ?? total;
2836
+ }
2837
+ };
2838
+ }
2839
+ function DataTableBulkActionsBar({
2840
+ table,
2841
+ actions,
2842
+ localeText
2843
+ }) {
2844
+ const selectedRows = table.getFilteredSelectedRowModel().rows;
2845
+ const count = selectedRows.length;
2846
+ if (count === 0) return null;
2847
+ return /* @__PURE__ */ React.createElement("div", { className: "bg-primary/10 border-primary/30 flex flex-wrap items-center justify-between gap-2 rounded-md border px-3 py-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React.createElement("span", { className: "text-primary text-sm font-medium" }, localeText.bulkActionsTitle(count)), /* @__PURE__ */ React.createElement(
2848
+ Button,
2849
+ {
2850
+ type: "button",
2851
+ variant: "ghost",
2852
+ size: "sm",
2853
+ className: "text-muted-foreground h-7 px-2 text-xs",
2854
+ onClick: () => table.resetRowSelection()
2855
+ },
2856
+ localeText.clearSelection
2857
+ )), /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-2" }, actions.map((action) => {
2858
+ const isDisabled = typeof action.disabled === "function" ? action.disabled(selectedRows) : action.disabled ?? false;
2859
+ return /* @__PURE__ */ React.createElement(
2860
+ Button,
2861
+ {
2862
+ key: action.id,
2863
+ type: "button",
2864
+ variant: action.variant === "destructive" ? "destructive" : "outline",
2865
+ size: "sm",
2866
+ disabled: isDisabled,
2867
+ title: action.tooltip,
2868
+ "aria-label": typeof action.label === "string" ? action.label : action.id,
2869
+ onClick: () => action.onClick(selectedRows, table),
2870
+ className: "h-8"
2871
+ },
2872
+ action.icon ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-1.5" }, action.icon, action.label) : action.label
2873
+ );
2874
+ })));
2875
+ }
2876
+ function createRowActionMenuColumn(items, text) {
2877
+ return {
2878
+ id: "__action_menu",
2879
+ header: text.rowActions,
2880
+ size: 52,
2881
+ enableSorting: false,
2882
+ enableHiding: false,
2883
+ cell: ({ row }) => {
2884
+ const visibleItems = items.filter((item) => {
2885
+ const hidden = typeof item.hidden === "function" ? item.hidden(row) : item.hidden ?? false;
2886
+ return !hidden;
2887
+ });
2888
+ if (!visibleItems.length) return null;
2889
+ return /* @__PURE__ */ React.createElement(
2890
+ "span",
2891
+ {
2892
+ className: "inline-flex",
2893
+ onClick: (e) => e.stopPropagation(),
2894
+ onPointerDown: (e) => e.stopPropagation()
2895
+ },
2896
+ /* @__PURE__ */ React.createElement(DropdownMenu.Root, null, /* @__PURE__ */ React.createElement(DropdownMenu.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(
2897
+ Button,
2898
+ {
2899
+ type: "button",
2900
+ variant: "ghost",
2901
+ size: "icon-sm",
2902
+ "aria-label": text.rowActions,
2903
+ className: "h-7 w-7"
2904
+ },
2905
+ "\u22EE"
2906
+ )), /* @__PURE__ */ React.createElement(
2907
+ DropdownMenu.Content,
2908
+ {
2909
+ align: "end",
2910
+ strategy: "absolute",
2911
+ sticky: "always",
2912
+ className: "bg-popover relative z-[1000] min-w-36 overflow-hidden"
2913
+ },
2914
+ visibleItems.map((item) => {
2915
+ const isDisabled = typeof item.disabled === "function" ? item.disabled(row) : item.disabled ?? false;
2916
+ return /* @__PURE__ */ React.createElement(React.Fragment, { key: item.id }, item.separator ? /* @__PURE__ */ React.createElement(DropdownMenu.Separator, null) : null, /* @__PURE__ */ React.createElement(
2917
+ DropdownMenu.Item,
2918
+ {
2919
+ disabled: isDisabled,
2920
+ className: cn(
2921
+ "flex cursor-pointer items-center gap-2",
2922
+ item.variant === "destructive" && "text-destructive",
2923
+ isDisabled && "cursor-not-allowed opacity-50"
2924
+ ),
2925
+ onSelect: () => item.onClick(row)
2926
+ },
2927
+ item.icon ? /* @__PURE__ */ React.createElement("span", { className: "size-4 shrink-0" }, item.icon) : null,
2928
+ item.label
2929
+ ));
2930
+ })
2931
+ ))
2932
+ );
2933
+ }
2934
+ };
2935
+ }
2936
+ function createRowActionButtonsColumn(items, text) {
2937
+ return {
2938
+ id: "__action_buttons",
2939
+ header: text.rowActions,
2940
+ size: Math.max(items.length * 80, 100),
2941
+ enableSorting: false,
2942
+ enableHiding: false,
2943
+ cell: ({ row }) => {
2944
+ const visibleItems = items.filter((item) => {
2945
+ const hidden = typeof item.hidden === "function" ? item.hidden(row) : item.hidden ?? false;
2946
+ return !hidden;
2947
+ });
2948
+ return /* @__PURE__ */ React.createElement("div", { className: "flex flex-wrap items-center gap-1" }, visibleItems.map((item) => {
2949
+ const isDisabled = typeof item.disabled === "function" ? item.disabled(row) : item.disabled ?? false;
2950
+ return /* @__PURE__ */ React.createElement(
2951
+ Button,
2952
+ {
2953
+ key: item.id,
2954
+ type: "button",
2955
+ variant: item.variant === "destructive" ? "destructive" : "outline",
2956
+ size: "sm",
2957
+ disabled: isDisabled,
2958
+ title: item.tooltip,
2959
+ "aria-label": typeof item.label === "string" ? item.label : item.id,
2960
+ onClick: () => item.onClick(row),
2961
+ className: "h-7"
2962
+ },
2963
+ item.icon ? /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-1" }, item.icon, item.label) : item.label
2964
+ );
2965
+ }));
2966
+ }
2967
+ };
2968
+ }
2969
+ function SparklineSVG({
2970
+ data,
2971
+ type = "line"
2972
+ }) {
2973
+ const width = 60;
2974
+ const height = 20;
2975
+ if (!data.length) return /* @__PURE__ */ React.createElement("svg", { width, height });
2976
+ const min = Math.min(...data);
2977
+ const max = Math.max(...data);
2978
+ const range = max - min || 1;
2979
+ const normalize = (v) => height - (v - min) / range * (height - 2) - 1;
2980
+ if (type === "bar") {
2981
+ const barW = Math.max(1, width / data.length - 1);
2982
+ return /* @__PURE__ */ React.createElement("svg", { width, height, "aria-hidden": "true" }, data.map((v, i) => {
2983
+ const barH = Math.max(1, (v - min) / range * (height - 2));
2984
+ return /* @__PURE__ */ React.createElement(
2985
+ "rect",
2986
+ {
2987
+ key: i,
2988
+ x: i * (width / data.length),
2989
+ y: height - barH,
2990
+ width: barW,
2991
+ height: barH,
2992
+ fill: "hsl(var(--primary))"
2993
+ }
2994
+ );
2995
+ }));
2996
+ }
2997
+ const points = data.map((v, i) => `${i / Math.max(data.length - 1, 1) * width},${normalize(v)}`).join(" ");
2998
+ if (type === "area") {
2999
+ const firstX = 0;
3000
+ const lastX = width;
3001
+ const areaPath = `M${firstX},${height} L${points.split(" ").map((p) => p).join(" L")} L${lastX},${height} Z`;
3002
+ return /* @__PURE__ */ React.createElement("svg", { width, height, "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: areaPath, fill: "hsl(var(--primary))", fillOpacity: "0.3", stroke: "none" }), /* @__PURE__ */ React.createElement(
3003
+ "polyline",
3004
+ {
3005
+ points,
3006
+ fill: "none",
3007
+ stroke: "hsl(var(--primary))",
3008
+ strokeWidth: "1.5",
3009
+ strokeLinecap: "round",
3010
+ strokeLinejoin: "round"
3011
+ }
3012
+ ));
3013
+ }
3014
+ return /* @__PURE__ */ React.createElement("svg", { width, height, "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
3015
+ "polyline",
3016
+ {
3017
+ points,
3018
+ fill: "none",
3019
+ stroke: "hsl(var(--primary))",
3020
+ strokeWidth: "1.5",
3021
+ strokeLinecap: "round",
3022
+ strokeLinejoin: "round"
3023
+ }
3024
+ ));
3025
+ }
3026
+ function buildBuiltinCellRenderer({
3027
+ type,
3028
+ badgeMap,
3029
+ currencyCode,
3030
+ currencyLocale,
3031
+ linkHref,
3032
+ linkTarget,
3033
+ avatarSrc,
3034
+ sparklineData,
3035
+ sparklineType,
3036
+ progressMax = 100,
3037
+ ratingMax = 5,
3038
+ locale: colLocale,
3039
+ dateFormat,
3040
+ numberFormat,
3041
+ timezone
3042
+ }) {
3043
+ const resolvedLocale = colLocale ?? "en-US";
3044
+ switch (type) {
3045
+ case "badge":
3046
+ return ({ getValue }) => {
3047
+ const value = getValue();
3048
+ const str = String(value ?? "");
3049
+ const spec = badgeMap?.[str];
3050
+ const label = spec?.label ?? str;
3051
+ const bg = spec?.color ?? "#6b7280";
3052
+ const fg = spec?.textColor ?? "#ffffff";
3053
+ return /* @__PURE__ */ React.createElement(
3054
+ "span",
3055
+ {
3056
+ style: { background: bg, color: fg },
3057
+ className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium"
3058
+ },
3059
+ label
3060
+ );
3061
+ };
3062
+ case "progress":
3063
+ return ({ getValue }) => {
3064
+ const value = Number(getValue() ?? 0);
3065
+ const percent = Math.min(100, Math.max(0, value / progressMax * 100));
3066
+ return /* @__PURE__ */ React.createElement("div", { className: "relative h-4 w-full min-w-12 overflow-hidden rounded bg-muted" }, /* @__PURE__ */ React.createElement(
3067
+ "div",
3068
+ {
3069
+ className: "h-full rounded bg-primary transition-all",
3070
+ style: { width: `${percent}%` }
3071
+ }
3072
+ ), /* @__PURE__ */ React.createElement("span", { className: "absolute inset-0 flex items-center justify-center text-[10px] font-medium leading-none" }, value, "%"));
3073
+ };
3074
+ case "link":
3075
+ return ({ getValue, row }) => {
3076
+ const value = getValue();
3077
+ const str = String(value ?? "");
3078
+ const href = typeof linkHref === "function" ? linkHref(value, row.original) : linkHref ?? str;
3079
+ return /* @__PURE__ */ React.createElement(
3080
+ "a",
3081
+ {
3082
+ href,
3083
+ target: linkTarget ?? "_blank",
3084
+ rel: "noopener noreferrer",
3085
+ className: "text-primary underline-offset-2 hover:underline",
3086
+ onClick: (e) => e.stopPropagation()
3087
+ },
3088
+ str
3089
+ );
3090
+ };
3091
+ case "avatar":
3092
+ return ({ getValue, row }) => {
3093
+ const value = getValue();
3094
+ const str = String(value ?? "");
3095
+ const src = avatarSrc ? avatarSrc(value, row.original) : "";
3096
+ return /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, src ? /* @__PURE__ */ React.createElement(
3097
+ "img",
3098
+ {
3099
+ src,
3100
+ alt: str,
3101
+ className: "h-7 w-7 rounded-full object-cover"
3102
+ }
3103
+ ) : /* @__PURE__ */ React.createElement("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-muted text-xs font-medium" }, str.charAt(0).toUpperCase()), /* @__PURE__ */ React.createElement("span", null, str));
3104
+ };
3105
+ case "date":
3106
+ return ({ getValue }) => {
3107
+ const raw = getValue();
3108
+ if (raw == null || raw === "") return null;
3109
+ try {
3110
+ const d = new Date(raw);
3111
+ if (isNaN(d.getTime())) return /* @__PURE__ */ React.createElement("span", null, String(raw));
3112
+ return /* @__PURE__ */ React.createElement("span", null, new Intl.DateTimeFormat(resolvedLocale, dateFormat ?? { year: "numeric", month: "short", day: "numeric" }).format(d));
3113
+ } catch {
3114
+ return /* @__PURE__ */ React.createElement("span", null, String(raw));
3115
+ }
3116
+ };
3117
+ case "dateTime":
3118
+ return ({ getValue }) => {
3119
+ const raw = getValue();
3120
+ if (raw == null || raw === "") return null;
3121
+ try {
3122
+ const d = new Date(raw);
3123
+ if (isNaN(d.getTime())) return /* @__PURE__ */ React.createElement("span", null, String(raw));
3124
+ const opts = dateFormat ?? {
3125
+ year: "numeric",
3126
+ month: "short",
3127
+ day: "numeric",
3128
+ hour: "2-digit",
3129
+ minute: "2-digit",
3130
+ ...timezone ? { timeZone: timezone } : {}
3131
+ };
3132
+ return /* @__PURE__ */ React.createElement("span", null, new Intl.DateTimeFormat(resolvedLocale, opts).format(d));
3133
+ } catch {
3134
+ return /* @__PURE__ */ React.createElement("span", null, String(raw));
3135
+ }
3136
+ };
3137
+ case "number":
3138
+ return ({ getValue }) => {
3139
+ const raw = getValue();
3140
+ if (raw == null || raw === "") return null;
3141
+ const num = Number(raw);
3142
+ if (!Number.isFinite(num)) return /* @__PURE__ */ React.createElement("span", null, String(raw));
3143
+ return /* @__PURE__ */ React.createElement("span", { className: "tabular-nums" }, new Intl.NumberFormat(resolvedLocale, numberFormat).format(num));
3144
+ };
3145
+ case "currency":
3146
+ return ({ getValue }) => {
3147
+ const value = Number(getValue() ?? 0);
3148
+ const formatted = new Intl.NumberFormat(colLocale ?? currencyLocale ?? "en-US", {
3149
+ style: "currency",
3150
+ currency: currencyCode ?? "USD",
3151
+ ...numberFormat ?? {}
3152
+ }).format(value);
3153
+ return /* @__PURE__ */ React.createElement("span", { className: "tabular-nums" }, formatted);
3154
+ };
3155
+ case "sparkline":
3156
+ return ({ row }) => {
3157
+ const data = sparklineData ? sparklineData(row.original) : [];
3158
+ return /* @__PURE__ */ React.createElement(SparklineSVG, { data, type: sparklineType });
3159
+ };
3160
+ case "rating":
3161
+ return ({ getValue }) => {
3162
+ const value = Number(getValue() ?? 0);
3163
+ const stars = Array.from({ length: ratingMax }, (_, i) => i + 1);
3164
+ return /* @__PURE__ */ React.createElement("span", { className: "inline-flex items-center gap-0.5", "aria-label": `${value} out of ${ratingMax} stars` }, stars.map((star) => /* @__PURE__ */ React.createElement(
3165
+ "span",
3166
+ {
3167
+ key: star,
3168
+ className: star <= value ? "text-yellow-400" : "text-muted-foreground/30",
3169
+ "aria-hidden": "true"
3170
+ },
3171
+ "\u2605"
3172
+ )));
3173
+ };
3174
+ case "boolean":
3175
+ return ({ getValue }) => {
3176
+ const value = getValue();
3177
+ const isTrue = value === true || value === "true" || value === 1 || value === "1";
3178
+ return isTrue ? /* @__PURE__ */ React.createElement("span", { className: "text-green-500 font-bold", "aria-label": "Yes" }, "\u2713") : /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground", "aria-label": "No" }, "\u2717");
3179
+ };
3180
+ default:
3181
+ return void 0;
3182
+ }
3183
+ }
3184
+ function DataTableKeyboardShortcutsModal({
3185
+ open,
3186
+ onOpenChange,
3187
+ enablePaste: showPaste
3188
+ }) {
3189
+ const shortcuts = [
3190
+ { key: "Ctrl+F", action: "Focus search" },
3191
+ { key: "Ctrl+C", action: "Copy selected rows" },
3192
+ { key: "Ctrl+V", action: "Paste (if enablePaste)", show: showPaste },
3193
+ { key: "Shift+Click", action: "Multi-sort column" },
3194
+ { key: "Space", action: "Toggle row selection" },
3195
+ { key: "Enter", action: "Expand / collapse row" },
3196
+ { key: "?", action: "Show this help" },
3197
+ { key: "Escape", action: "Close panels" },
3198
+ { key: "F11", action: "Toggle fullscreen" },
3199
+ { key: "Ctrl+Z", action: "Undo last edit" },
3200
+ { key: "Ctrl+Y", action: "Redo last edit" }
3201
+ ];
3202
+ const visible = shortcuts.filter((s) => s.show !== false);
3203
+ return /* @__PURE__ */ React.createElement(Drawer.Root, { open, onOpenChange }, /* @__PURE__ */ React.createElement(
3204
+ Drawer.Content,
3205
+ {
3206
+ style: {
3207
+ top: "50%",
3208
+ left: "50%",
3209
+ right: "auto",
3210
+ bottom: "auto",
3211
+ transform: "translate(-50%, -50%)",
3212
+ height: "auto",
3213
+ maxHeight: "90vh",
3214
+ width: "480px",
3215
+ maxWidth: "95vw",
3216
+ marginTop: 0,
3217
+ borderRadius: "0.5rem",
3218
+ "--tw-enter-translate-y": "0",
3219
+ "--tw-exit-translate-y": "0"
3220
+ },
3221
+ className: "flex flex-col overflow-hidden [&>div:first-child]:hidden"
3222
+ },
3223
+ /* @__PURE__ */ React.createElement(Drawer.Header, { className: "border-border flex-shrink-0 border-b px-4 py-3 text-left" }, /* @__PURE__ */ React.createElement(Drawer.Title, null, "Keyboard Shortcuts"), /* @__PURE__ */ React.createElement(Drawer.Description, null, "All keyboard shortcuts available in the data table.")),
3224
+ /* @__PURE__ */ React.createElement("div", { className: "overflow-y-auto p-4" }, /* @__PURE__ */ React.createElement("table", { className: "w-full text-sm" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", { className: "border-border border-b" }, /* @__PURE__ */ React.createElement("th", { className: "pb-2 text-left font-medium" }, "Shortcut"), /* @__PURE__ */ React.createElement("th", { className: "pb-2 text-left font-medium" }, "Action"))), /* @__PURE__ */ React.createElement("tbody", null, visible.map((s) => /* @__PURE__ */ React.createElement("tr", { key: s.key, className: "border-border border-b last:border-0" }, /* @__PURE__ */ React.createElement("td", { className: "py-2 pr-4" }, /* @__PURE__ */ React.createElement("kbd", { className: "bg-muted border-border rounded border px-1.5 py-0.5 font-mono text-xs" }, s.key)), /* @__PURE__ */ React.createElement("td", { className: "text-muted-foreground py-2" }, s.action)))))),
3225
+ /* @__PURE__ */ React.createElement("div", { className: "border-border border-t p-3" }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm", className: "w-full", onClick: () => onOpenChange(false) }, "Close"))
3226
+ ));
3227
+ }
3228
+ function computeColumnStats(rows, columnId) {
3229
+ const values = rows.map((row) => row.getValue(columnId));
3230
+ const count = values.length;
3231
+ const nullCount = values.filter((v) => v == null || v === "").length;
3232
+ const uniqueValues = new Set(values.map((v) => String(v ?? "")));
3233
+ const uniqueCount = uniqueValues.size;
3234
+ const numericValues = values.map((v) => Number(v)).filter((n) => Number.isFinite(n));
3235
+ const isNumeric = numericValues.length > 0 && numericValues.length === values.filter((v) => v != null && v !== "").length;
3236
+ let min;
3237
+ let max;
3238
+ let mean;
3239
+ let median;
3240
+ let sum;
3241
+ if (isNumeric && numericValues.length > 0) {
3242
+ min = Math.min(...numericValues);
3243
+ max = Math.max(...numericValues);
3244
+ sum = numericValues.reduce((a, b) => a + b, 0);
3245
+ mean = sum / numericValues.length;
3246
+ const sorted = [...numericValues].sort((a, b) => a - b);
3247
+ const mid = Math.floor(sorted.length / 2);
3248
+ median = sorted.length % 2 === 0 ? ((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2 : sorted[mid] ?? 0;
3249
+ }
3250
+ const stringValues = values.map((v) => String(v ?? ""));
3251
+ const minLength = Math.min(...stringValues.map((s) => s.length));
3252
+ const maxLength = Math.max(...stringValues.map((s) => s.length));
3253
+ const avgLength = stringValues.length > 0 ? stringValues.reduce((a, s) => a + s.length, 0) / stringValues.length : 0;
3254
+ const freq = /* @__PURE__ */ new Map();
3255
+ for (const sv of stringValues) {
3256
+ freq.set(sv, (freq.get(sv) ?? 0) + 1);
3257
+ }
3258
+ const topValues = [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([value, cnt]) => ({
3259
+ value,
3260
+ count: cnt,
3261
+ pct: count > 0 ? cnt / count * 100 : 0
3262
+ }));
3263
+ return {
3264
+ count,
3265
+ nullCount,
3266
+ uniqueCount,
3267
+ min: isNumeric ? min : void 0,
3268
+ max: isNumeric ? max : void 0,
3269
+ mean: isNumeric ? mean : void 0,
3270
+ median: isNumeric ? median : void 0,
3271
+ sum: isNumeric ? sum : void 0,
3272
+ minLength: !isNumeric ? minLength : void 0,
3273
+ maxLength: !isNumeric ? maxLength : void 0,
3274
+ avgLength: !isNumeric ? avgLength : void 0,
3275
+ topValues
3276
+ };
3277
+ }
3278
+ function ColumnStatsPanel({
3279
+ table,
3280
+ columnId
3281
+ }) {
3282
+ const col = columnId ? table.getColumn(columnId) : void 0;
3283
+ const rows = table.getFilteredRowModel().rows;
3284
+ const stats = React.useMemo(() => {
3285
+ if (!col) return null;
3286
+ return computeColumnStats(rows, col.id);
3287
+ }, [col, rows]);
3288
+ if (!col || !stats) {
3289
+ return /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground py-6 text-center text-xs" }, "Select a column to view statistics.");
3290
+ }
3291
+ const colHeader = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
3292
+ const isNumeric = stats.min !== void 0;
3293
+ const histData = (() => {
3294
+ if (isNumeric) {
3295
+ const numVals = rows.map((r) => Number(r.getValue(col.id))).filter((n) => Number.isFinite(n));
3296
+ if (numVals.length === 0) return [];
3297
+ const minV = stats.min ?? 0;
3298
+ const maxV = stats.max ?? 0;
3299
+ const bucketCount = 8;
3300
+ const bucketSize = (maxV - minV) / bucketCount || 1;
3301
+ const buckets = Array.from({ length: bucketCount }, (_, i) => ({
3302
+ label: (minV + i * bucketSize).toFixed(1),
3303
+ count: 0
3304
+ }));
3305
+ for (const v of numVals) {
3306
+ const idx = Math.min(Math.floor((v - minV) / bucketSize), bucketCount - 1);
3307
+ if (buckets[idx]) buckets[idx].count++;
3308
+ }
3309
+ return buckets;
3310
+ } else {
3311
+ return stats.topValues.slice(0, 8).map((tv) => ({ label: tv.value || "(empty)", count: tv.count }));
3312
+ }
3313
+ })();
3314
+ const maxHistCount = Math.max(...histData.map((b) => b.count), 1);
3315
+ const svgW = 200;
3316
+ const svgH = 60;
3317
+ const barW = Math.floor((svgW - (histData.length - 1) * 2) / Math.max(histData.length, 1));
3318
+ return /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 p-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-xs font-semibold" }, colHeader), /* @__PURE__ */ React.createElement("div", { className: "grid grid-cols-2 gap-1 text-xs" }, [
3319
+ { label: "Count", value: stats.count },
3320
+ { label: "Nulls", value: stats.nullCount },
3321
+ { label: "Unique", value: stats.uniqueCount },
3322
+ ...isNumeric ? [
3323
+ { label: "Min", value: stats.min?.toFixed(2) },
3324
+ { label: "Max", value: stats.max?.toFixed(2) },
3325
+ { label: "Mean", value: stats.mean?.toFixed(2) },
3326
+ { label: "Median", value: stats.median?.toFixed(2) },
3327
+ { label: "Sum", value: stats.sum?.toFixed(2) }
3328
+ ] : [
3329
+ { label: "Min len", value: stats.minLength },
3330
+ { label: "Max len", value: stats.maxLength },
3331
+ { label: "Avg len", value: stats.avgLength?.toFixed(1) }
3332
+ ]
3333
+ ].map((metric) => /* @__PURE__ */ React.createElement("div", { key: metric.label, className: "bg-muted/40 rounded px-2 py-1" }, /* @__PURE__ */ React.createElement("div", { className: "text-muted-foreground text-[10px]" }, metric.label), /* @__PURE__ */ React.createElement("div", { className: "font-medium tabular-nums" }, String(metric.value ?? "\u2014"))))), histData.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground text-[10px] font-medium uppercase tracking-wide" }, "Distribution"), /* @__PURE__ */ React.createElement("svg", { width: svgW, height: svgH, "aria-label": "Distribution histogram" }, histData.map((bucket, i) => {
3334
+ const barH = Math.max(2, bucket.count / maxHistCount * (svgH - 4));
3335
+ return /* @__PURE__ */ React.createElement(
3336
+ "rect",
3337
+ {
3338
+ key: i,
3339
+ x: i * (barW + 2),
3340
+ y: svgH - barH,
3341
+ width: barW,
3342
+ height: barH,
3343
+ fill: "hsl(var(--primary))",
3344
+ fillOpacity: 0.7,
3345
+ rx: 1
3346
+ },
3347
+ /* @__PURE__ */ React.createElement("title", null, bucket.label, ": ", bucket.count)
3348
+ );
3349
+ }))) : null, stats.topValues.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground text-[10px] font-medium uppercase tracking-wide" }, "Top values"), stats.topValues.map((tv) => /* @__PURE__ */ React.createElement("div", { key: tv.value, className: "grid gap-0.5" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between text-[10px]" }, /* @__PURE__ */ React.createElement("span", { className: "max-w-[130px] truncate" }, tv.value || "(empty)"), /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground tabular-nums" }, tv.count)), /* @__PURE__ */ React.createElement("div", { className: "bg-muted h-1.5 w-full rounded-full" }, /* @__PURE__ */ React.createElement(
3350
+ "div",
3351
+ {
3352
+ className: "bg-primary h-1.5 rounded-full",
3353
+ style: { width: `${tv.pct.toFixed(1)}%` }
3354
+ }
3355
+ ))))) : null);
3356
+ }
3357
+ function DataTableToolPanel({
3358
+ table,
3359
+ open,
3360
+ tab,
3361
+ onTabChange,
3362
+ onOpenChange,
3363
+ statsColumnId,
3364
+ onStatsColumnChange
3365
+ }) {
3366
+ const tabTitles = {
3367
+ columns: "Columns",
3368
+ filters: "Filters",
3369
+ stats: "Statistics"
3370
+ };
3371
+ const handleTabClick = (t) => {
3372
+ if (open && tab === t) {
3373
+ onOpenChange(false);
3374
+ } else {
3375
+ onTabChange(t);
3376
+ onOpenChange(true);
3377
+ }
3378
+ };
3379
+ const allColumns = table.getAllLeafColumns().filter((c) => !c.id.startsWith("__"));
3380
+ const filterableColumns = allColumns.filter((c) => c.getCanFilter());
3381
+ return /* @__PURE__ */ React.createElement("div", { className: "flex" }, /* @__PURE__ */ React.createElement(
3382
+ "div",
3383
+ {
3384
+ className: cn(
3385
+ "border-border bg-bg overflow-hidden border-l transition-all duration-200",
3386
+ open ? "w-64" : "w-0"
3387
+ ),
3388
+ "aria-hidden": !open
3389
+ },
3390
+ open ? /* @__PURE__ */ React.createElement("div", { className: "flex h-full w-64 flex-col overflow-hidden" }, /* @__PURE__ */ React.createElement("div", { className: "border-border flex items-center justify-between border-b px-3 py-2" }, /* @__PURE__ */ React.createElement("span", { className: "text-sm font-semibold" }, tabTitles[tab]), /* @__PURE__ */ React.createElement(
3391
+ Button,
3392
+ {
3393
+ type: "button",
3394
+ variant: "ghost",
3395
+ size: "icon-sm",
3396
+ onClick: () => onOpenChange(false),
3397
+ "aria-label": "Close panel",
3398
+ className: "h-6 w-6"
3399
+ },
3400
+ /* @__PURE__ */ React.createElement(X, { className: "size-3.5" })
3401
+ )), /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto p-2" }, tab === "columns" ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-0.5" }, /* @__PURE__ */ React.createElement("div", { className: "mb-1 flex gap-1" }, /* @__PURE__ */ React.createElement(
3402
+ Button,
3403
+ {
3404
+ type: "button",
3405
+ variant: "outline",
3406
+ size: "sm",
3407
+ className: "h-6 flex-1 px-1 text-xs",
3408
+ onClick: () => table.getAllLeafColumns().forEach((c) => c.toggleVisibility(true))
3409
+ },
3410
+ "Show all"
3411
+ ), /* @__PURE__ */ React.createElement(
3412
+ Button,
3413
+ {
3414
+ type: "button",
3415
+ variant: "outline",
3416
+ size: "sm",
3417
+ className: "h-6 flex-1 px-1 text-xs",
3418
+ onClick: () => table.getAllLeafColumns().filter((c) => c.getCanHide()).forEach((c) => c.toggleVisibility(false))
3419
+ },
3420
+ "Hide all"
3421
+ )), allColumns.map((col) => {
3422
+ const structylMeta = getStructylMeta(col);
3423
+ const label = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
3424
+ return /* @__PURE__ */ React.createElement(
3425
+ "label",
3426
+ {
3427
+ key: col.id,
3428
+ className: "hover:bg-accent flex items-center gap-2 rounded px-2 py-1 text-xs"
3429
+ },
3430
+ /* @__PURE__ */ React.createElement(
3431
+ Checkbox,
3432
+ {
3433
+ checked: col.getIsVisible(),
3434
+ disabled: !col.getCanHide(),
3435
+ onCheckedChange: (checked) => col.toggleVisibility(checked === true),
3436
+ "aria-label": `Toggle ${label}`
3437
+ }
3438
+ ),
3439
+ /* @__PURE__ */ React.createElement("span", { className: "min-w-0 flex-1 truncate" }, label),
3440
+ structylMeta.type ? /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground bg-muted rounded px-1 py-0.5 font-mono text-[9px]" }, structylMeta.type) : null
3441
+ );
3442
+ })) : tab === "filters" ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, filterableColumns.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground py-4 text-center text-xs" }, "No filterable columns.") : /* @__PURE__ */ React.createElement(React.Fragment, null, filterableColumns.map((col) => {
3443
+ const label = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
3444
+ return /* @__PURE__ */ React.createElement("label", { key: col.id, className: "grid gap-0.5 text-xs" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground font-medium" }, label), /* @__PURE__ */ React.createElement(
3445
+ Input,
3446
+ {
3447
+ value: String(col.getFilterValue() ?? ""),
3448
+ onChange: (e) => col.setFilterValue(e.target.value || void 0),
3449
+ placeholder: "Filter\u2026",
3450
+ className: "h-7 text-xs"
3451
+ }
3452
+ ));
3453
+ }), /* @__PURE__ */ React.createElement(
3454
+ Button,
3455
+ {
3456
+ type: "button",
3457
+ variant: "outline",
3458
+ size: "sm",
3459
+ className: "mt-1 h-7 text-xs",
3460
+ onClick: () => filterableColumns.forEach((c) => c.setFilterValue(void 0))
3461
+ },
3462
+ "Clear all"
3463
+ ))) : /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, statsColumnId ? null : /* @__PURE__ */ React.createElement("div", { className: "grid gap-0.5" }, /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground mb-1 text-xs" }, "Select column:"), allColumns.map((col) => {
3464
+ const label = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
3465
+ return /* @__PURE__ */ React.createElement(
3466
+ "button",
3467
+ {
3468
+ key: col.id,
3469
+ type: "button",
3470
+ onClick: () => onStatsColumnChange?.(col.id),
3471
+ className: cn(
3472
+ "hover:bg-accent rounded px-2 py-1 text-left text-xs",
3473
+ statsColumnId === col.id && "bg-accent"
3474
+ )
3475
+ },
3476
+ label
3477
+ );
3478
+ })), /* @__PURE__ */ React.createElement(ColumnStatsPanel, { table, columnId: statsColumnId }), statsColumnId ? /* @__PURE__ */ React.createElement(
3479
+ Button,
3480
+ {
3481
+ type: "button",
3482
+ variant: "ghost",
3483
+ size: "sm",
3484
+ className: "h-6 text-xs",
3485
+ onClick: () => onStatsColumnChange?.(void 0)
3486
+ },
3487
+ "\u2190 Back to column list"
3488
+ ) : null))) : null
3489
+ ), /* @__PURE__ */ React.createElement("div", { className: "border-border bg-muted/20 flex flex-col border-l" }, [["columns", /* @__PURE__ */ React.createElement(Columns3, { key: "c", className: "size-4" })], ["filters", /* @__PURE__ */ React.createElement(Filter, { key: "f", className: "size-4" })], ["stats", /* @__PURE__ */ React.createElement(BarChart2, { key: "s", className: "size-4" })]].map(([t, icon]) => /* @__PURE__ */ React.createElement(
3490
+ "button",
3491
+ {
3492
+ key: t,
3493
+ type: "button",
3494
+ onClick: () => handleTabClick(t),
3495
+ title: tabTitles[t],
3496
+ "aria-label": tabTitles[t],
3497
+ className: cn(
3498
+ "flex h-10 w-8 items-center justify-center transition-colors",
3499
+ open && tab === t ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-accent hover:text-foreground"
3500
+ )
3501
+ },
3502
+ icon
3503
+ ))));
3504
+ }
3505
+ function exportToXLSX(table, options) {
3506
+ if (typeof document === "undefined") return;
3507
+ const cols = table.getVisibleLeafColumns().filter(
3508
+ (c) => !["__select", "__rownum", "__row_actions", "__row_total", "__action_menu", "__action_buttons", "__actions"].includes(c.id)
3509
+ );
3510
+ const rows = options?.onlySelected ? table.getFilteredSelectedRowModel().rows : table.getFilteredRowModel().rows;
3511
+ const headerRow = cols.map(
3512
+ (c) => typeof c.columnDef.header === "string" ? c.columnDef.header : c.id
3513
+ );
3514
+ const dataRows = rows.map((row) => cols.map((c) => row.getValue(c.id) ?? ""));
3515
+ const wsData = [headerRow, ...dataRows];
3516
+ const ws = XLSX.utils.aoa_to_sheet(wsData);
3517
+ ws["!cols"] = headerRow.map((h) => ({ wch: Math.max(String(h).length + 4, 10) }));
3518
+ const wb = XLSX.utils.book_new();
3519
+ XLSX.utils.book_append_sheet(wb, ws, options?.sheetName ?? "Sheet1");
3520
+ XLSX.writeFile(wb, options?.filename ?? "export.xlsx");
3521
+ }
3522
+ function getStructylMeta(column) {
3523
+ const meta = column.columnDef.meta;
3524
+ return meta?._structyl ?? {};
3525
+ }
3526
+ function getDefaultColumnAlign(type) {
3527
+ if (type === "number" || type === "currency") return "right";
3528
+ if (type === "date" || type === "dateTime") return "right";
3529
+ if (type === "boolean" || type === "rating") return "center";
3530
+ return void 0;
3531
+ }
3532
+ function normalizeColumnDefs(defs, tablePropLocale) {
3533
+ return defs.map((def) => {
3534
+ const {
3535
+ field,
3536
+ fieldId,
3537
+ headerName,
3538
+ type,
3539
+ align,
3540
+ flex,
3541
+ valueGetter,
3542
+ valueSetter,
3543
+ renderCell,
3544
+ renderHeader,
3545
+ description,
3546
+ filterOperators: colFilterOps,
3547
+ // Extended column type fields
3548
+ badgeMap,
3549
+ currencyCode,
3550
+ currencyLocale,
3551
+ linkHref,
3552
+ linkTarget,
3553
+ avatarSrc,
3554
+ sparklineData,
3555
+ sparklineType,
3556
+ progressMax,
3557
+ ratingMax,
3558
+ editable,
3559
+ validate,
3560
+ displayValidate,
3561
+ // Feature 6: formatting
3562
+ locale: colLocale,
3563
+ dateFormat,
3564
+ numberFormat,
3565
+ timezone,
3566
+ ...rest
3567
+ } = def;
3568
+ const col = { ...rest };
3569
+ const restRecord = rest;
3570
+ if (def.columns && def.columns.length > 0) {
3571
+ col.columns = normalizeColumnDefs(def.columns, tablePropLocale);
3572
+ if (headerName && !restRecord.header && !renderHeader) col.header = headerName;
3573
+ if (renderHeader && !restRecord.header) {
3574
+ col.header = (ctx) => renderHeader({ column: ctx.column });
3575
+ }
3576
+ delete col.accessorKey;
3577
+ delete col.accessorFn;
3578
+ col.meta = { ...restRecord.meta };
3579
+ return col;
3580
+ }
3581
+ if (fieldId && !restRecord.id) col.id = fieldId;
3582
+ if (field && !restRecord.accessorKey && !restRecord.accessorFn) col.accessorKey = field;
3583
+ if (valueGetter && !restRecord.accessorFn && !(field ?? restRecord.accessorKey)) {
3584
+ col.accessorFn = (row) => valueGetter(row);
3585
+ }
3586
+ if (headerName && !restRecord.header && !renderHeader) col.header = headerName;
3587
+ if (renderHeader && !restRecord.header) {
3588
+ col.header = (ctx) => renderHeader({ column: ctx.column });
3589
+ }
3590
+ if (renderCell && !restRecord.cell) {
3591
+ col.cell = (ctx) => renderCell({
3592
+ row: ctx.row,
3593
+ value: ctx.getValue(),
3594
+ field: String(field ?? restRecord.accessorKey ?? restRecord.id ?? "")
3595
+ });
3596
+ }
3597
+ if (!renderCell && !restRecord.cell && type) {
3598
+ col.cell = buildBuiltinCellRenderer({
3599
+ type,
3600
+ badgeMap,
3601
+ currencyCode,
3602
+ currencyLocale,
3603
+ linkHref,
3604
+ linkTarget,
3605
+ avatarSrc,
3606
+ sparklineData,
3607
+ sparklineType,
3608
+ progressMax,
3609
+ ratingMax,
3610
+ field: String(field ?? restRecord.accessorKey ?? restRecord.id ?? ""),
3611
+ locale: colLocale ?? tablePropLocale,
3612
+ dateFormat,
3613
+ numberFormat,
3614
+ timezone
3615
+ });
3616
+ }
3617
+ col.meta = {
3618
+ ...restRecord.meta,
3619
+ _structyl: {
3620
+ type,
3621
+ align: align ?? getDefaultColumnAlign(type),
3622
+ flex,
3623
+ fieldId,
3624
+ description,
3625
+ valueSetter,
3626
+ filterOperators: colFilterOps,
3627
+ badgeMap,
3628
+ currencyCode,
3629
+ currencyLocale,
3630
+ // Cast TData-specific function types to unknown to satisfy StructylColumnMeta
3631
+ linkHref,
3632
+ linkTarget,
3633
+ avatarSrc,
3634
+ sparklineData,
3635
+ sparklineType,
3636
+ progressMax,
3637
+ ratingMax,
3638
+ editable,
3639
+ validate,
3640
+ displayValidate,
3641
+ locale: colLocale,
3642
+ dateFormat,
3643
+ numberFormat,
3644
+ timezone,
3645
+ propLocale: tablePropLocale
3646
+ }
3647
+ };
3648
+ return col;
3649
+ });
3650
+ }
3651
+ function DataTableToolbarButton({
3652
+ tooltip,
3653
+ children,
3654
+ className,
3655
+ ...props
3656
+ }) {
3657
+ const btn = /* @__PURE__ */ React.createElement(
3658
+ Button,
3659
+ {
3660
+ type: "button",
3661
+ variant: "outline",
3662
+ size: "sm",
3663
+ "aria-label": tooltip ?? (typeof children === "string" ? children : void 0),
3664
+ className,
3665
+ ...props
3666
+ },
3667
+ children
3668
+ );
3669
+ if (!tooltip) return btn;
3670
+ return /* @__PURE__ */ React.createElement(Tooltip.Provider, null, /* @__PURE__ */ React.createElement(Tooltip.Root, null, /* @__PURE__ */ React.createElement(Tooltip.Trigger, { asChild: true }, btn), /* @__PURE__ */ React.createElement(Tooltip.Content, null, tooltip)));
3671
+ }
3672
+ function DataTableDensityMenu({
3673
+ density,
3674
+ onDensityChange,
3675
+ localeText,
3676
+ overlayBoundary
3677
+ }) {
3678
+ const options = [
3679
+ { value: "compact", label: localeText.densityCompact },
3680
+ { value: "standard", label: localeText.densityStandard },
3681
+ { value: "comfortable", label: localeText.densityComfortable }
3682
+ ];
3683
+ return /* @__PURE__ */ React.createElement(DropdownMenu.Root, null, /* @__PURE__ */ React.createElement(DropdownMenu.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm", title: localeText.density }, /* @__PURE__ */ React.createElement(AlignJustify, { className: "mr-1.5 size-4" }), localeText.density)), /* @__PURE__ */ React.createElement(
3684
+ DropdownMenu.Content,
3685
+ {
3686
+ align: "end",
3687
+ container: overlayBoundary,
3688
+ collisionBoundary: overlayBoundary,
3689
+ collisionPadding: 8,
3690
+ strategy: "absolute",
3691
+ sticky: "always",
3692
+ className: "bg-popover relative z-[1000] min-w-36 overflow-hidden"
3693
+ },
3694
+ options.map((option) => /* @__PURE__ */ React.createElement(
3695
+ DropdownMenu.Item,
3696
+ {
3697
+ key: option.value,
3698
+ className: cn("cursor-pointer", density === option.value && "font-semibold"),
3699
+ onSelect: () => onDensityChange(option.value)
3700
+ },
3701
+ option.label
3702
+ ))
3703
+ ));
3704
+ }
3705
+ function DataTableExportMenu({ table, localeText, overlayBoundary, options }) {
3706
+ return /* @__PURE__ */ React.createElement(Tooltip.Provider, null, /* @__PURE__ */ React.createElement(Tooltip.Root, null, /* @__PURE__ */ React.createElement(DropdownMenu.Root, null, /* @__PURE__ */ React.createElement(Tooltip.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(DropdownMenu.Trigger, { asChild: true }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm" }, /* @__PURE__ */ React.createElement(Download, { className: "mr-1.5 size-4" }), localeText.export ?? "Export", /* @__PURE__ */ React.createElement(ChevronDown, { className: "ml-1 size-3" })))), /* @__PURE__ */ React.createElement(Tooltip.Content, null, localeText.export ?? "Export data"), /* @__PURE__ */ React.createElement(
3707
+ DropdownMenu.Content,
3708
+ {
3709
+ align: "end",
3710
+ container: overlayBoundary,
3711
+ collisionBoundary: overlayBoundary,
3712
+ collisionPadding: 8,
3713
+ strategy: "absolute",
3714
+ sticky: "always",
3715
+ className: "bg-popover relative z-[1000] min-w-40"
3716
+ },
3717
+ options.csv !== false ? /* @__PURE__ */ React.createElement(DropdownMenu.Item, { className: "cursor-pointer", onSelect: () => exportToCSV(table, "export.csv") }, localeText.exportCSV ?? "Export as CSV") : null,
3718
+ options.json !== false ? /* @__PURE__ */ React.createElement(DropdownMenu.Item, { className: "cursor-pointer", onSelect: () => exportToJSON(table, "export.json") }, localeText.exportJSON ?? "Export as JSON") : null,
3719
+ options.selectedCsv !== false ? /* @__PURE__ */ React.createElement(DropdownMenu.Item, { className: "cursor-pointer", onSelect: () => exportToCSV(table, "selected.csv", { onlySelected: true }) }, localeText.exportSelectedCSV ?? "Export selected rows") : null,
3720
+ options.xlsx ? /* @__PURE__ */ React.createElement(
3721
+ DropdownMenu.Item,
3722
+ {
3723
+ className: "cursor-pointer flex items-center gap-2",
3724
+ onSelect: () => exportToXLSX(table)
3725
+ },
3726
+ /* @__PURE__ */ React.createElement(FileSpreadsheet, { className: "size-4" }),
3727
+ "Export as Excel"
3728
+ ) : null
3729
+ ))));
3730
+ }
3731
+ function hexToHsva(hex) {
3732
+ let h = hex.replace("#", "");
3733
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
3734
+ const r = parseInt(h.slice(0, 2), 16) / 255;
3735
+ const g = parseInt(h.slice(2, 4), 16) / 255;
3736
+ const b = parseInt(h.slice(4, 6), 16) / 255;
3737
+ const max = Math.max(r, g, b);
3738
+ const min = Math.min(r, g, b);
3739
+ const d = max - min;
3740
+ let hue = 0;
3741
+ if (d !== 0) {
3742
+ if (max === r) hue = (g - b) / d % 6;
3743
+ else if (max === g) hue = (b - r) / d + 2;
3744
+ else hue = (r - g) / d + 4;
3745
+ hue *= 60;
3746
+ if (hue < 0) hue += 360;
3747
+ }
3748
+ return { h: hue, s: max === 0 ? 0 : d / max, v: max, a: 1 };
3749
+ }
3750
+ var CF_OPERATORS = [
3751
+ { value: "equals", label: "Equals" },
3752
+ { value: "notEquals", label: "Not equals" },
3753
+ { value: "contains", label: "Contains" },
3754
+ { value: "gt", label: "Greater than" },
3755
+ { value: "gte", label: "Greater than or equal" },
3756
+ { value: "lt", label: "Less than" },
3757
+ { value: "lte", label: "Less than or equal" },
3758
+ { value: "empty", label: "Is empty" },
3759
+ { value: "notEmpty", label: "Is not empty" }
3760
+ ];
3761
+ function ColorSection({ label, color, onChange }) {
3762
+ const enabled = !!color;
3763
+ const [localHex, setLocalHex] = React.useState(color ?? "#3b82f6");
3764
+ React.useEffect(() => {
3765
+ if (color && color !== localHex) setLocalHex(color);
3766
+ }, [color]);
3767
+ const hsva = React.useMemo(() => {
3768
+ try {
3769
+ return hexToHsva(localHex);
3770
+ } catch {
3771
+ return { h: 217, s: 0.91, v: 0.96, a: 1 };
3772
+ }
3773
+ }, [localHex]);
3774
+ const handleHsvaChange = (v) => {
3775
+ const hex = hsvaToHex(v);
3776
+ setLocalHex(hex);
3777
+ onChange(hex);
3778
+ };
3779
+ const handleHexInput = (raw) => {
3780
+ const clean = raw.replace(/[^0-9a-fA-F]/g, "").slice(0, 6);
3781
+ setLocalHex("#" + clean);
3782
+ if (clean.length === 6) onChange("#" + clean);
3783
+ };
3784
+ return /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("span", { className: "text-xs font-medium" }, label), /* @__PURE__ */ React.createElement("div", { className: "flex gap-1" }, /* @__PURE__ */ React.createElement(
3785
+ "button",
3786
+ {
3787
+ type: "button",
3788
+ onClick: () => onChange(void 0),
3789
+ className: cn(
3790
+ "rounded px-2 py-0.5 text-xs transition-colors",
3791
+ !enabled ? "bg-muted text-foreground font-medium" : "text-muted-foreground hover:bg-muted"
3792
+ )
3793
+ },
3794
+ "None"
3795
+ ), /* @__PURE__ */ React.createElement(
3796
+ "button",
3797
+ {
3798
+ type: "button",
3799
+ onClick: () => {
3800
+ if (!enabled) onChange(localHex || "#3b82f6");
3801
+ },
3802
+ className: cn(
3803
+ "rounded px-2 py-0.5 text-xs transition-colors",
3804
+ enabled ? "bg-primary text-primary-foreground font-medium" : "text-muted-foreground hover:bg-muted"
3805
+ )
3806
+ },
3807
+ "Apply"
3808
+ ))), enabled ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React.createElement(ColorPicker.Root, { value: hsva, onValueChange: handleHsvaChange, className: "w-full gap-2 p-0" }, /* @__PURE__ */ React.createElement(ColorPicker.Area, { className: "h-28 w-full" }), /* @__PURE__ */ React.createElement(ColorPicker.HueSlider, { className: "h-3 w-full rounded-full" })), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground font-mono text-xs" }, "#"), /* @__PURE__ */ React.createElement(
3809
+ Input,
3810
+ {
3811
+ value: localHex.replace("#", ""),
3812
+ onChange: (e) => handleHexInput(e.target.value),
3813
+ className: "h-7 flex-1 font-mono text-xs",
3814
+ maxLength: 6,
3815
+ placeholder: "ffffff",
3816
+ spellCheck: false
3817
+ }
3818
+ ), /* @__PURE__ */ React.createElement(
3819
+ "div",
3820
+ {
3821
+ className: "border-border h-7 w-7 flex-shrink-0 rounded border",
3822
+ style: { background: color },
3823
+ "aria-label": "Color preview"
3824
+ }
3825
+ ))) : /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground py-1 text-xs" }, "No color \u2014 click Apply to set."));
3826
+ }
3827
+ function DataTableConditionalFormattingDrawer({
3828
+ table,
3829
+ open,
3830
+ onOpenChange,
3831
+ initialColumnId,
3832
+ rules,
3833
+ onRulesChange
3834
+ }) {
3835
+ const columns = table.getAllLeafColumns().filter(
3836
+ (col) => !["__select", "__rownum", "__row_actions"].includes(col.id)
3837
+ );
3838
+ const defaultColumnId = initialColumnId ?? columns[0]?.id ?? "";
3839
+ const [draft, setDraft] = React.useState(rules);
3840
+ React.useEffect(() => {
3841
+ if (open) setDraft(rules);
3842
+ }, [open]);
3843
+ const addRule = () => {
3844
+ setDraft((prev) => [
3845
+ ...prev,
3846
+ {
3847
+ id: Math.random().toString(36).slice(2),
3848
+ columnId: defaultColumnId,
3849
+ operator: "equals",
3850
+ value: "",
3851
+ backgroundColor: "#fef9c3",
3852
+ textColor: void 0
3853
+ }
3854
+ ]);
3855
+ };
3856
+ const updateRule = (id, patch) => {
3857
+ setDraft((prev) => prev.map((r) => r.id === id ? { ...r, ...patch } : r));
3858
+ };
3859
+ const removeRule = (id) => {
3860
+ setDraft((prev) => prev.filter((r) => r.id !== id));
3861
+ };
3862
+ const handleSave = () => {
3863
+ onRulesChange(draft);
3864
+ onOpenChange(false);
3865
+ };
3866
+ const needsValue = (op) => !["empty", "notEmpty"].includes(op);
3867
+ return /* @__PURE__ */ React.createElement(Drawer.Root, { open, onOpenChange }, /* @__PURE__ */ React.createElement(
3868
+ Drawer.Content,
3869
+ {
3870
+ style: {
3871
+ // Override bottom-drawer positioning to right-side full-height panel
3872
+ top: 0,
3873
+ right: 0,
3874
+ bottom: 0,
3875
+ left: "auto",
3876
+ height: "100%",
3877
+ width: "400px",
3878
+ maxWidth: "95vw",
3879
+ marginTop: 0,
3880
+ borderRadius: 0,
3881
+ borderTop: "none",
3882
+ borderRight: "none",
3883
+ borderBottom: "none",
3884
+ // Reset vertical slide animation variable so it only slides from right
3885
+ "--tw-enter-translate-y": "0",
3886
+ "--tw-exit-translate-y": "0"
3887
+ },
3888
+ className: "flex flex-col overflow-hidden data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right [&>div:first-child]:hidden"
3889
+ },
3890
+ /* @__PURE__ */ React.createElement(Drawer.Header, { className: "border-border flex-shrink-0 border-b px-4 py-3 text-left" }, /* @__PURE__ */ React.createElement(Drawer.Title, null, "Conditional Formatting"), /* @__PURE__ */ React.createElement(Drawer.Description, null, "Highlight cells based on column values. Rules are applied top-to-bottom; the first match wins.")),
3891
+ /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-3 p-4" }, draft.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground py-6 text-center text-sm" }, "No rules yet. Click \u201C+ Add rule\u201D to get started.") : null, draft.map((rule, idx) => /* @__PURE__ */ React.createElement(
3892
+ "div",
3893
+ {
3894
+ key: rule.id,
3895
+ className: "border-border bg-muted/20 grid gap-3 rounded-lg border p-3"
3896
+ },
3897
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground text-xs font-medium" }, "Rule ", idx + 1), /* @__PURE__ */ React.createElement(
3898
+ Button,
3899
+ {
3900
+ type: "button",
3901
+ variant: "ghost",
3902
+ size: "sm",
3903
+ onClick: () => removeRule(rule.id),
3904
+ className: "text-destructive h-6 px-2 text-xs"
3905
+ },
3906
+ "Remove"
3907
+ )),
3908
+ /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Column"), /* @__PURE__ */ React.createElement(
3909
+ Select.Root,
3910
+ {
3911
+ value: rule.columnId,
3912
+ onValueChange: (v) => updateRule(rule.id, { columnId: v })
3913
+ },
3914
+ /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)),
3915
+ /* @__PURE__ */ React.createElement(
3916
+ Select.Content,
3917
+ {
3918
+ options: columns.map((col) => ({
3919
+ value: col.id,
3920
+ label: typeof col.columnDef.header === "string" ? col.columnDef.header : col.id
3921
+ }))
3922
+ }
3923
+ )
3924
+ )),
3925
+ /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Condition"), /* @__PURE__ */ React.createElement(
3926
+ Select.Root,
3927
+ {
3928
+ value: rule.operator,
3929
+ onValueChange: (v) => updateRule(rule.id, { operator: v })
3930
+ },
3931
+ /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)),
3932
+ /* @__PURE__ */ React.createElement(Select.Content, { options: CF_OPERATORS.map((o) => ({ value: o.value, label: o.label })) })
3933
+ )),
3934
+ needsValue(rule.operator) ? /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Value"), /* @__PURE__ */ React.createElement(
3935
+ Input,
3936
+ {
3937
+ value: rule.value ?? "",
3938
+ onChange: (e) => updateRule(rule.id, { value: e.target.value }),
3939
+ placeholder: "Enter value\u2026",
3940
+ className: "h-8"
3941
+ }
3942
+ )) : null,
3943
+ /* @__PURE__ */ React.createElement("div", { className: "border-border rounded-md border p-2" }, /* @__PURE__ */ React.createElement(
3944
+ ColorSection,
3945
+ {
3946
+ label: "Background color",
3947
+ color: rule.backgroundColor,
3948
+ onChange: (c) => updateRule(rule.id, { backgroundColor: c })
3949
+ }
3950
+ )),
3951
+ /* @__PURE__ */ React.createElement("div", { className: "border-border rounded-md border p-2" }, /* @__PURE__ */ React.createElement(
3952
+ ColorSection,
3953
+ {
3954
+ label: "Text color",
3955
+ color: rule.textColor,
3956
+ onChange: (c) => updateRule(rule.id, { textColor: c })
3957
+ }
3958
+ )),
3959
+ /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "text-muted-foreground text-xs" }, "Preview:"), /* @__PURE__ */ React.createElement(
3960
+ "span",
3961
+ {
3962
+ className: "rounded px-2 py-0.5 text-xs font-medium",
3963
+ style: {
3964
+ backgroundColor: rule.backgroundColor ?? "transparent",
3965
+ color: rule.textColor ?? "inherit",
3966
+ border: "1px solid var(--border)"
3967
+ }
3968
+ },
3969
+ "Cell value"
3970
+ ))
3971
+ )))),
3972
+ /* @__PURE__ */ React.createElement("div", { className: "border-border flex-shrink-0 border-t p-4" }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: addRule, className: "mb-3 w-full" }, "+ Add rule"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement(
3973
+ Button,
3974
+ {
3975
+ type: "button",
3976
+ variant: "outline",
3977
+ size: "sm",
3978
+ onClick: () => onOpenChange(false),
3979
+ className: "flex-1"
3980
+ },
3981
+ "Cancel"
3982
+ ), /* @__PURE__ */ React.createElement(
3983
+ Button,
3984
+ {
3985
+ type: "button",
3986
+ variant: "default",
3987
+ size: "sm",
3988
+ onClick: handleSave,
3989
+ className: "flex-1"
3990
+ },
3991
+ "Save"
3992
+ )))
3993
+ ));
3994
+ }
3995
+ var filterOperators = [
3996
+ "contains",
3997
+ "equals",
3998
+ "startsWith",
3999
+ "endsWith",
4000
+ "gt",
4001
+ "gte",
4002
+ "lt",
4003
+ "lte",
4004
+ "empty",
4005
+ "notEmpty"
4006
+ ];
4007
+ function createFilterRule(columnId) {
4008
+ return {
4009
+ id: `filter-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
4010
+ columnId,
4011
+ operator: "contains",
4012
+ value: ""
4013
+ };
4014
+ }
4015
+ function createFilterGroup(id = `group-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`) {
4016
+ return {
4017
+ id,
4018
+ logic: "and",
4019
+ items: []
4020
+ };
4021
+ }
4022
+ function isFilterRule(item) {
4023
+ return "columnId" in item;
4024
+ }
4025
+ function evaluateFilterGroup(group, row, getValue) {
4026
+ const results = group.items.map(
4027
+ (item) => isFilterRule(item) ? evaluateFilterRule(item, getValue(row, item.columnId)) : evaluateFilterGroup(item, row, getValue)
4028
+ );
4029
+ return group.logic === "or" ? results.some(Boolean) : results.every(Boolean);
4030
+ }
4031
+ function evaluateFilterRule(rule, value) {
4032
+ const actual = String(value ?? "").toLowerCase();
4033
+ const expected = String(rule.value ?? "").toLowerCase();
4034
+ const actualNumber = Number(value);
4035
+ const expectedNumber = Number(rule.value);
4036
+ if (rule.operator === "contains") return actual.includes(expected);
4037
+ if (rule.operator === "equals") return actual === expected;
4038
+ if (rule.operator === "startsWith") return actual.startsWith(expected);
4039
+ if (rule.operator === "endsWith") return actual.endsWith(expected);
4040
+ if (rule.operator === "empty") return value == null || actual === "";
4041
+ if (rule.operator === "notEmpty") return value != null && actual !== "";
4042
+ if (!Number.isFinite(actualNumber) || !Number.isFinite(expectedNumber)) return false;
4043
+ if (rule.operator === "gt") return actualNumber > expectedNumber;
4044
+ if (rule.operator === "gte") return actualNumber >= expectedNumber;
4045
+ if (rule.operator === "lt") return actualNumber < expectedNumber;
4046
+ return actualNumber <= expectedNumber;
4047
+ }
4048
+ function getValueByPath(row, columnId) {
4049
+ let current = row;
4050
+ for (const part of columnId.split(".")) {
4051
+ if (typeof current !== "object" || current === null) return void 0;
4052
+ current = current[part];
4053
+ }
4054
+ return current;
4055
+ }
4056
+ function normalizeSearch(value) {
4057
+ return String(value ?? "").trim().toLowerCase();
4058
+ }
4059
+ function splitPinnedRows(rows, rowPinning) {
4060
+ const topIds = new Set(rowPinning.top ?? []);
4061
+ const bottomIds = new Set(rowPinning.bottom ?? []);
4062
+ const top = [];
4063
+ const center = [];
4064
+ const bottom = [];
4065
+ for (const row of rows) {
4066
+ if (topIds.has(row.id)) top.push(row);
4067
+ else if (bottomIds.has(row.id)) bottom.push(row);
4068
+ else center.push(row);
4069
+ }
4070
+ return { top, center, bottom };
4071
+ }
4072
+ function reorderRows(rows, fromId, toId) {
4073
+ const ids = reorderIds(
4074
+ rows.map((row) => row.id),
4075
+ fromId,
4076
+ toId
4077
+ );
4078
+ const byId = new Map(rows.map((row) => [row.id, row]));
4079
+ return ids.map((id) => byId.get(id)).filter(isDefined);
4080
+ }
4081
+ function reorderIds(ids, fromId, toId) {
4082
+ if (fromId === toId) return ids;
4083
+ const fromIndex = ids.indexOf(fromId);
4084
+ const toIndex = ids.indexOf(toId);
4085
+ if (fromIndex < 0 || toIndex < 0) return ids;
4086
+ const next = [...ids];
4087
+ const [item] = next.splice(fromIndex, 1);
4088
+ if (!item) return ids;
4089
+ next.splice(toIndex, 0, item);
4090
+ return next;
4091
+ }
4092
+ function toggleId(ids, id) {
4093
+ return ids.includes(id) ? ids.filter((item) => item !== id) : [...ids, id];
4094
+ }
4095
+ function aggregateColumn(aggregation, values, rows) {
4096
+ if (typeof aggregation === "function") {
4097
+ return aggregation(values, rows);
4098
+ }
4099
+ if (aggregation === "count") return values.length;
4100
+ const numbers = values.map(toNumber).filter((value) => Number.isFinite(value));
4101
+ if (!numbers.length) return 0;
4102
+ if (aggregation === "sum") return numbers.reduce((sum, value) => sum + value, 0);
4103
+ if (aggregation === "avg") return numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
4104
+ if (aggregation === "min") return Math.min(...numbers);
4105
+ return Math.max(...numbers);
4106
+ }
4107
+ function toNumber(value) {
4108
+ if (typeof value === "number") return value;
4109
+ const parsed = Number(value);
4110
+ return Number.isFinite(parsed) ? parsed : 0;
4111
+ }
4112
+ function escapeCsv(value) {
4113
+ const stringValue = value == null ? "" : String(value);
4114
+ return /[",\n]/.test(stringValue) ? `"${stringValue.replace(/"/g, '""')}"` : stringValue;
4115
+ }
4116
+ function isDefined(value) {
4117
+ return value !== void 0;
4118
+ }
4119
+ function computePivotAgg(values, aggregation) {
4120
+ if (aggregation === "count") return values.length;
4121
+ const nums = values.map(Number).filter(Number.isFinite);
4122
+ if (!nums.length) return 0;
4123
+ if (aggregation === "sum") return nums.reduce((a, b) => a + b, 0);
4124
+ if (aggregation === "avg") return nums.reduce((a, b) => a + b, 0) / nums.length;
4125
+ if (aggregation === "min") return Math.min(...nums);
4126
+ return Math.max(...nums);
4127
+ }
4128
+ function DataTablePivotView({
4129
+ data,
4130
+ pivotConfig,
4131
+ columns,
4132
+ className,
4133
+ onClearPivot
4134
+ }) {
4135
+ const { rowGroupField, pivotField, valueField, aggregation } = pivotConfig;
4136
+ const { pivotValues, rowGroupValues, cellMap } = React.useMemo(() => {
4137
+ const pivotSet = /* @__PURE__ */ new Set();
4138
+ const rowGroupSet = /* @__PURE__ */ new Set();
4139
+ const cellMap2 = /* @__PURE__ */ new Map();
4140
+ for (const row of data) {
4141
+ const rec = row;
4142
+ const rv = String(rec[rowGroupField] ?? "");
4143
+ const pv = String(rec[pivotField] ?? "");
4144
+ const val = rec[valueField];
4145
+ rowGroupSet.add(rv);
4146
+ pivotSet.add(pv);
4147
+ if (!cellMap2.has(rv)) cellMap2.set(rv, /* @__PURE__ */ new Map());
4148
+ const inner = cellMap2.get(rv);
4149
+ if (!inner.has(pv)) inner.set(pv, []);
4150
+ inner.get(pv).push(val);
4151
+ }
4152
+ const pivotValues2 = Array.from(pivotSet).sort();
4153
+ const rowGroupValues2 = Array.from(rowGroupSet).sort();
4154
+ return { pivotValues: pivotValues2, rowGroupValues: rowGroupValues2, cellMap: cellMap2 };
4155
+ }, [data, rowGroupField, pivotField, valueField]);
4156
+ const getColLabel = (fieldId) => {
4157
+ const col = columns.find((c) => c.field === fieldId || c.fieldId === fieldId || c.id === fieldId);
4158
+ return col?.headerName ?? fieldId;
4159
+ };
4160
+ return /* @__PURE__ */ React.createElement("div", { className: cn("flex flex-col gap-2", className) }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React.createElement("span", { className: "bg-primary/10 text-primary border-primary/30 inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium" }, /* @__PURE__ */ React.createElement(GitBranch, { className: "size-3" }), "Pivot mode active: ", getColLabel(rowGroupField), " \xD7 ", getColLabel(pivotField), " (", aggregation, ")"), onClearPivot ? /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "ghost", size: "sm", className: "h-6 px-2 text-xs", onClick: onClearPivot }, "Clear pivot") : null), /* @__PURE__ */ React.createElement("div", { className: "border-border overflow-auto rounded-md border" }, /* @__PURE__ */ React.createElement("table", { className: "w-full caption-bottom text-sm", role: "grid" }, /* @__PURE__ */ React.createElement("thead", { className: "bg-bg sticky top-0 z-20" }, /* @__PURE__ */ React.createElement("tr", { role: "row" }, /* @__PURE__ */ React.createElement("th", { className: "text-muted-foreground border-border border-b px-3 py-2 text-left font-medium" }, getColLabel(rowGroupField)), pivotValues.map((pv) => /* @__PURE__ */ React.createElement("th", { key: pv, className: "text-muted-foreground border-border border-b px-3 py-2 text-right font-medium whitespace-nowrap" }, pv)))), /* @__PURE__ */ React.createElement("tbody", null, rowGroupValues.map((rgv) => /* @__PURE__ */ React.createElement("tr", { key: rgv, className: "border-border hover:bg-muted/50 border-b" }, /* @__PURE__ */ React.createElement("td", { className: "px-3 py-2 font-medium" }, rgv), pivotValues.map((pv) => {
4161
+ const vals = cellMap.get(rgv)?.get(pv) ?? [];
4162
+ const agg = computePivotAgg(vals, aggregation);
4163
+ return /* @__PURE__ */ React.createElement("td", { key: pv, className: "px-3 py-2 text-right tabular-nums" }, agg);
4164
+ })))))));
4165
+ }
4166
+ function DataTablePivotConfigPanel({
4167
+ open,
4168
+ onOpenChange,
4169
+ columns,
4170
+ currentConfig,
4171
+ onApply,
4172
+ onClear
4173
+ }) {
4174
+ const colOptions = columns.filter((c) => c.field ?? c.fieldId ?? c.id).map((c) => ({ value: String(c.field ?? c.fieldId ?? c.id ?? ""), label: c.headerName ?? String(c.field ?? c.id ?? "") }));
4175
+ const firstColId = colOptions[0]?.value ?? "";
4176
+ const [rowGroupField, setRowGroupField] = React.useState(currentConfig?.rowGroupField ?? firstColId);
4177
+ const [pivotField, setPivotField] = React.useState(currentConfig?.pivotField ?? firstColId);
4178
+ const [valueField, setValueField] = React.useState(currentConfig?.valueField ?? firstColId);
4179
+ const [aggregation, setAggregation] = React.useState(currentConfig?.aggregation ?? "sum");
4180
+ React.useEffect(() => {
4181
+ if (open && currentConfig) {
4182
+ setRowGroupField(currentConfig.rowGroupField);
4183
+ setPivotField(currentConfig.pivotField);
4184
+ setValueField(currentConfig.valueField);
4185
+ setAggregation(currentConfig.aggregation);
4186
+ }
4187
+ }, [open, currentConfig]);
4188
+ const aggOptions = [
4189
+ { value: "count", label: "Count" },
4190
+ { value: "sum", label: "Sum" },
4191
+ { value: "avg", label: "Average" },
4192
+ { value: "min", label: "Minimum" },
4193
+ { value: "max", label: "Maximum" }
4194
+ ];
4195
+ return /* @__PURE__ */ React.createElement(Drawer.Root, { open, onOpenChange }, /* @__PURE__ */ React.createElement(
4196
+ Drawer.Content,
4197
+ {
4198
+ style: {
4199
+ top: 0,
4200
+ right: 0,
4201
+ bottom: 0,
4202
+ left: "auto",
4203
+ height: "100%",
4204
+ width: "380px",
4205
+ maxWidth: "95vw",
4206
+ marginTop: 0,
4207
+ borderRadius: 0,
4208
+ borderTop: "none",
4209
+ borderRight: "none",
4210
+ borderBottom: "none",
4211
+ "--tw-enter-translate-y": "0",
4212
+ "--tw-exit-translate-y": "0"
4213
+ },
4214
+ className: "flex flex-col overflow-hidden data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right [&>div:first-child]:hidden"
4215
+ },
4216
+ /* @__PURE__ */ React.createElement(Drawer.Header, { className: "border-border flex-shrink-0 border-b px-4 py-3 text-left" }, /* @__PURE__ */ React.createElement(Drawer.Title, null, "Pivot Mode"), /* @__PURE__ */ React.createElement(Drawer.Description, null, "Configure pivot table settings.")),
4217
+ /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto p-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Group rows by"), /* @__PURE__ */ React.createElement(Select.Root, { value: rowGroupField, onValueChange: setRowGroupField }, /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)), /* @__PURE__ */ React.createElement(Select.Content, { options: colOptions }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Pivot columns by"), /* @__PURE__ */ React.createElement(Select.Root, { value: pivotField, onValueChange: setPivotField }, /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)), /* @__PURE__ */ React.createElement(Select.Content, { options: colOptions }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Value field"), /* @__PURE__ */ React.createElement(Select.Root, { value: valueField, onValueChange: setValueField }, /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)), /* @__PURE__ */ React.createElement(Select.Content, { options: colOptions }))), /* @__PURE__ */ React.createElement("div", { className: "grid gap-1" }, /* @__PURE__ */ React.createElement("label", { className: "text-xs font-medium" }, "Aggregation"), /* @__PURE__ */ React.createElement(Select.Root, { value: aggregation, onValueChange: (v) => setAggregation(v) }, /* @__PURE__ */ React.createElement(Select.Trigger, { className: "h-8 w-full" }, /* @__PURE__ */ React.createElement(Select.Value, null)), /* @__PURE__ */ React.createElement(Select.Content, { options: aggOptions }))))),
4218
+ /* @__PURE__ */ React.createElement("div", { className: "border-border flex-shrink-0 border-t p-4 grid gap-2" }, /* @__PURE__ */ React.createElement(
4219
+ Button,
4220
+ {
4221
+ type: "button",
4222
+ variant: "default",
4223
+ size: "sm",
4224
+ className: "w-full",
4225
+ onClick: () => {
4226
+ onApply({ rowGroupField, pivotField, valueField, aggregation });
4227
+ onOpenChange(false);
4228
+ }
4229
+ },
4230
+ "Apply"
4231
+ ), /* @__PURE__ */ React.createElement(
4232
+ Button,
4233
+ {
4234
+ type: "button",
4235
+ variant: "outline",
4236
+ size: "sm",
4237
+ className: "w-full",
4238
+ onClick: () => {
4239
+ onClear();
4240
+ onOpenChange(false);
4241
+ }
4242
+ },
4243
+ "Clear pivot"
4244
+ ))
4245
+ ));
4246
+ }
4247
+ function DataTableSavedViewsDrawer({
4248
+ open,
4249
+ onOpenChange,
4250
+ views,
4251
+ onViewsChange,
4252
+ table,
4253
+ density,
4254
+ setDensity,
4255
+ conditionalFormattingRules,
4256
+ handleConditionalRulesChange
4257
+ }) {
4258
+ const [newViewName, setNewViewName] = React.useState("");
4259
+ const captureCurrentState = () => ({
4260
+ sorting: table.getState().sorting,
4261
+ columnFilters: table.getState().columnFilters,
4262
+ globalFilter: table.getState().globalFilter,
4263
+ columnVisibility: table.getState().columnVisibility,
4264
+ columnOrder: table.getState().columnOrder,
4265
+ columnSizing: table.getState().columnSizing,
4266
+ columnPinning: table.getState().columnPinning,
4267
+ density,
4268
+ grouping: table.getState().grouping,
4269
+ conditionalFormattingRules
4270
+ });
4271
+ const loadView = (view) => {
4272
+ table.setSorting(view.state.sorting ?? []);
4273
+ table.setColumnFilters(view.state.columnFilters ?? []);
4274
+ table.setGlobalFilter(view.state.globalFilter ?? "");
4275
+ table.setColumnVisibility(view.state.columnVisibility ?? {});
4276
+ table.setColumnOrder(view.state.columnOrder ?? []);
4277
+ table.setColumnSizing(view.state.columnSizing ?? {});
4278
+ table.setColumnPinning(view.state.columnPinning ?? {});
4279
+ if (view.state.density) setDensity(view.state.density);
4280
+ if (view.state.grouping) table.setGrouping(view.state.grouping);
4281
+ if (view.state.conditionalFormattingRules) handleConditionalRulesChange(view.state.conditionalFormattingRules);
4282
+ };
4283
+ const saveNewView = () => {
4284
+ const name = newViewName.trim();
4285
+ if (!name) return;
4286
+ const newView = {
4287
+ id: `view-${Date.now()}-${Math.random().toString(36).slice(2)}`,
4288
+ name,
4289
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4290
+ state: captureCurrentState()
4291
+ };
4292
+ onViewsChange([...views, newView]);
4293
+ setNewViewName("");
4294
+ };
4295
+ const updateView = (id) => {
4296
+ onViewsChange(views.map((v) => v.id === id ? { ...v, state: captureCurrentState() } : v));
4297
+ };
4298
+ const deleteView = (id) => {
4299
+ onViewsChange(views.filter((v) => v.id !== id));
4300
+ };
4301
+ return /* @__PURE__ */ React.createElement(Drawer.Root, { open, onOpenChange }, /* @__PURE__ */ React.createElement(
4302
+ Drawer.Content,
4303
+ {
4304
+ style: {
4305
+ top: 0,
4306
+ right: 0,
4307
+ bottom: 0,
4308
+ left: "auto",
4309
+ height: "100%",
4310
+ width: "380px",
4311
+ maxWidth: "95vw",
4312
+ marginTop: 0,
4313
+ borderRadius: 0,
4314
+ borderTop: "none",
4315
+ borderRight: "none",
4316
+ borderBottom: "none",
4317
+ "--tw-enter-translate-y": "0",
4318
+ "--tw-exit-translate-y": "0"
4319
+ },
4320
+ className: "flex flex-col overflow-hidden data-[state=open]:slide-in-from-right data-[state=closed]:slide-out-to-right [&>div:first-child]:hidden"
4321
+ },
4322
+ /* @__PURE__ */ React.createElement(Drawer.Header, { className: "border-border flex-shrink-0 border-b px-4 py-3 text-left" }, /* @__PURE__ */ React.createElement(Drawer.Title, null, "Saved Views"), /* @__PURE__ */ React.createElement(Drawer.Description, null, "Save and restore table display states.")),
4323
+ /* @__PURE__ */ React.createElement("div", { className: "min-h-0 flex-1 overflow-y-auto p-4" }, /* @__PURE__ */ React.createElement("div", { className: "border-border mb-4 rounded-lg border p-3" }, /* @__PURE__ */ React.createElement("p", { className: "mb-2 text-xs font-semibold" }, "Save current view"), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement(
4324
+ Input,
4325
+ {
4326
+ value: newViewName,
4327
+ onChange: (e) => setNewViewName(e.target.value),
4328
+ placeholder: "View name\u2026",
4329
+ className: "flex-1 h-8 text-xs",
4330
+ onKeyDown: (e) => {
4331
+ if (e.key === "Enter") saveNewView();
4332
+ }
4333
+ }
4334
+ ), /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "default", size: "sm", className: "h-8 px-3 text-xs", onClick: saveNewView }, "Save"))), views.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground py-6 text-center text-sm" }, "No saved views yet.") : /* @__PURE__ */ React.createElement("div", { className: "grid gap-2" }, views.map((view) => /* @__PURE__ */ React.createElement("div", { key: view.id, className: "border-border bg-muted/20 rounded-lg border p-3" }, /* @__PURE__ */ React.createElement("div", { className: "mb-2 flex items-start justify-between gap-2" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("p", { className: "text-sm font-semibold" }, view.name), /* @__PURE__ */ React.createElement("p", { className: "text-muted-foreground text-[10px]" }, new Date(view.createdAt).toLocaleDateString())), /* @__PURE__ */ React.createElement(
4335
+ Button,
4336
+ {
4337
+ type: "button",
4338
+ variant: "ghost",
4339
+ size: "icon-sm",
4340
+ className: "text-destructive h-6 w-6 shrink-0",
4341
+ "aria-label": `Delete view ${view.name}`,
4342
+ onClick: () => deleteView(view.id)
4343
+ },
4344
+ /* @__PURE__ */ React.createElement(Trash2, { className: "size-3.5" })
4345
+ )), /* @__PURE__ */ React.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React.createElement(
4346
+ Button,
4347
+ {
4348
+ type: "button",
4349
+ variant: "outline",
4350
+ size: "sm",
4351
+ className: "flex-1 h-7 text-xs",
4352
+ onClick: () => loadView(view)
4353
+ },
4354
+ "Load"
4355
+ ), /* @__PURE__ */ React.createElement(
4356
+ Button,
4357
+ {
4358
+ type: "button",
4359
+ variant: "outline",
4360
+ size: "sm",
4361
+ className: "flex-1 h-7 text-xs",
4362
+ onClick: () => updateView(view.id)
4363
+ },
4364
+ "Update"
4365
+ )))))),
4366
+ /* @__PURE__ */ React.createElement("div", { className: "border-border flex-shrink-0 border-t p-4" }, /* @__PURE__ */ React.createElement(Button, { type: "button", variant: "outline", size: "sm", className: "w-full", onClick: () => onOpenChange(false) }, "Close"))
4367
+ ));
4368
+ }
4369
+ function computeHeaderStat(rows, columnId, statType) {
4370
+ const values = rows.map((r) => r.getValue(columnId));
4371
+ switch (statType) {
4372
+ case "count":
4373
+ return String(values.length);
4374
+ case "nullCount":
4375
+ return String(values.filter((v) => v == null || v === "").length);
4376
+ case "unique":
4377
+ return String(new Set(values.map((v) => String(v ?? ""))).size);
4378
+ default: {
4379
+ const nums = values.map(Number).filter(Number.isFinite);
4380
+ if (!nums.length) return "\u2014";
4381
+ if (statType === "sum") return new Intl.NumberFormat().format(nums.reduce((a, b) => a + b, 0));
4382
+ if (statType === "avg") return new Intl.NumberFormat().format(nums.reduce((a, b) => a + b, 0) / nums.length);
4383
+ if (statType === "min") return new Intl.NumberFormat().format(Math.min(...nums));
4384
+ return new Intl.NumberFormat().format(Math.max(...nums));
4385
+ }
4386
+ }
4387
+ }
4388
+ function getDefaultStatType(columnId, table) {
4389
+ const col = table.getColumn(columnId);
4390
+ if (!col) return "count";
4391
+ const structylMeta = getStructylMeta(col);
4392
+ if (structylMeta.type === "number" || structylMeta.type === "currency") return "sum";
4393
+ return "count";
4394
+ }
4395
+ function DataTableHeaderStatsRow({
4396
+ table,
4397
+ renderedColumnIds,
4398
+ columnPaddingLeft,
4399
+ columnPaddingRight,
4400
+ headerStatsConfig
4401
+ }) {
4402
+ const rows = table.getFilteredRowModel().rows;
4403
+ const visibleLeafCols = table.getVisibleLeafColumns().filter((c) => renderedColumnIds.has(c.id));
4404
+ const statsMap = React.useMemo(() => {
4405
+ const map = /* @__PURE__ */ new Map();
4406
+ for (const col of visibleLeafCols) {
4407
+ const statType = headerStatsConfig?.[col.id] ?? getDefaultStatType(col.id, table);
4408
+ const value = computeHeaderStat(rows, col.id, statType);
4409
+ map.set(col.id, { label: statType, value });
4410
+ }
4411
+ return map;
4412
+ }, [rows, visibleLeafCols, headerStatsConfig]);
4413
+ return /* @__PURE__ */ React.createElement("tr", { className: "bg-muted/40 border-border border-b text-xs text-muted-foreground" }, columnPaddingLeft > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingLeft } }) : null, visibleLeafCols.map((col) => {
4414
+ const stat = statsMap.get(col.id);
4415
+ const structylMeta = getStructylMeta(col);
4416
+ const align = structylMeta.align ?? "left";
4417
+ return /* @__PURE__ */ React.createElement(
4418
+ "td",
4419
+ {
4420
+ key: col.id,
4421
+ className: cn(
4422
+ "px-3 py-1",
4423
+ align === "center" && "text-center",
4424
+ align === "right" && "text-right"
4425
+ )
4426
+ },
4427
+ stat ? /* @__PURE__ */ React.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React.createElement("span", { className: "text-[9px] font-medium uppercase tracking-wide opacity-60" }, stat.label), /* @__PURE__ */ React.createElement("span", { className: "tabular-nums" }, stat.value)) : null
4428
+ );
4429
+ }), columnPaddingRight > 0 ? /* @__PURE__ */ React.createElement("td", { style: { width: columnPaddingRight } }) : null);
4430
+ }
4431
+
4432
+ export { DataTable, DataTableAdvancedFilter, DataTableColumnConfiguration, DataTableColumnFilter, DataTableColumnVisibility, DataTablePagination, DataTableToolbar, DataTableToolbarButton, EditableCell, exportToCSV, exportToJSON, exportToXLSX };
4433
+ //# sourceMappingURL=index.mjs.map
4434
+ //# sourceMappingURL=index.mjs.map