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