@loykin/gridkit 0.0.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @loykin/gridkit might be problematic. Click here for more details.

package/dist/index.js ADDED
@@ -0,0 +1,2454 @@
1
+ import * as React from 'react';
2
+ import React__default, { useEffect, useState, useContext, useMemo, useRef, useCallback, useSyncExternalStore, useLayoutEffect } from 'react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { useReactTable, getPaginationRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, getCoreRowModel, memo, createRow, getMemoOptions, flexRender } from '@tanstack/react-table';
5
+ import { create } from 'zustand';
6
+ import { Columns3, Search, X, ChevronDown, Check, ChevronRight, GripVertical, ChevronsLeft, ChevronLeft, ChevronsRight, Loader2, ChevronsUpDown, ArrowUp, ArrowDown, ArrowUpDown, ChevronUp, Filter, SlidersHorizontal, MoreHorizontal } from 'lucide-react';
7
+ import { Button as Button$1 } from '@base-ui/react/button';
8
+ import { cva } from 'class-variance-authority';
9
+ import { clsx } from 'clsx';
10
+ import { twMerge } from 'tailwind-merge';
11
+ import { Select as Select$1 } from '@base-ui/react/select';
12
+ import { useVirtualizer } from '@tanstack/react-virtual';
13
+ import { Menu } from '@base-ui/react/menu';
14
+ import { Input as Input$1 } from '@base-ui/react/input';
15
+ import { Checkbox as Checkbox$1 } from '@base-ui/react/checkbox';
16
+ import { Popover as Popover$1 } from '@base-ui/react/popover';
17
+ import { useSensors, useSensor, PointerSensor, DndContext, closestCenter, DragOverlay } from '@dnd-kit/core';
18
+ import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
19
+ import { CSS } from '@dnd-kit/utilities';
20
+
21
+ // src/DataGrid.tsx
22
+ function IndeterminateCheckbox({ checked, indeterminate, onChange }) {
23
+ const ref = useRef(null);
24
+ useEffect(() => {
25
+ if (ref.current) {
26
+ ref.current.indeterminate = indeterminate;
27
+ }
28
+ }, [indeterminate]);
29
+ return /* @__PURE__ */ jsx(
30
+ "input",
31
+ {
32
+ ref,
33
+ type: "checkbox",
34
+ checked,
35
+ onChange: (e) => onChange(e.target.checked),
36
+ className: "size-4 cursor-pointer rounded border-input accent-primary"
37
+ }
38
+ );
39
+ }
40
+ function createCheckboxColumn(checkboxConfig) {
41
+ const { getRowId, selectedIds, onSelectAll, onSelectOne } = checkboxConfig;
42
+ return {
43
+ id: "__select__",
44
+ size: 40,
45
+ enableResizing: false,
46
+ enableSorting: false,
47
+ enableColumnFilter: false,
48
+ header: ({ table }) => {
49
+ const rows = table.getRowModel().rows;
50
+ const allSelected = rows.length > 0 && rows.every((r) => selectedIds.has(getRowId(r.original)));
51
+ const someSelected = !allSelected && rows.some((r) => selectedIds.has(getRowId(r.original)));
52
+ return /* @__PURE__ */ jsx(
53
+ IndeterminateCheckbox,
54
+ {
55
+ checked: allSelected,
56
+ indeterminate: someSelected,
57
+ onChange: (checked) => onSelectAll(rows, checked)
58
+ }
59
+ );
60
+ },
61
+ cell: ({ row }) => {
62
+ const id = getRowId(row.original);
63
+ return /* @__PURE__ */ jsx(
64
+ "input",
65
+ {
66
+ type: "checkbox",
67
+ checked: selectedIds.has(id),
68
+ onChange: (e) => onSelectOne(id, e.target.checked),
69
+ onClick: (e) => e.stopPropagation(),
70
+ className: "size-4 cursor-pointer rounded border-input accent-primary"
71
+ }
72
+ );
73
+ }
74
+ };
75
+ }
76
+ var DEFAULT_STATE = {
77
+ pagination: { pageIndex: 0, pageSize: 20 },
78
+ searchTerm: ""
79
+ };
80
+ var useTableStore = create((set, get) => ({
81
+ tables: {},
82
+ register: (key, initial) => {
83
+ if (get().tables[key]) return;
84
+ set((s) => ({
85
+ tables: {
86
+ ...s.tables,
87
+ [key]: { ...DEFAULT_STATE, ...initial }
88
+ }
89
+ }));
90
+ },
91
+ update: (key, partial) => set((s) => ({
92
+ tables: {
93
+ ...s.tables,
94
+ [key]: { ...s.tables[key] ?? DEFAULT_STATE, ...partial }
95
+ }
96
+ })),
97
+ reset: (key) => set((s) => ({
98
+ tables: { ...s.tables, [key]: { ...DEFAULT_STATE } }
99
+ }))
100
+ }));
101
+ function getDataStoreCoreRowModel() {
102
+ return (table) => {
103
+ const rowCache = /* @__PURE__ */ new Map();
104
+ return memo(
105
+ () => {
106
+ const store = table.options.dataStore;
107
+ return [store?.getVersion() ?? -1];
108
+ },
109
+ () => {
110
+ const store = table.options.dataStore;
111
+ const data = store.getSnapshot();
112
+ const rowModel = { rows: [], flatRows: [], rowsById: {} };
113
+ const seenIds = /* @__PURE__ */ new Set();
114
+ for (let i = 0; i < data.length; i++) {
115
+ const item = data[i];
116
+ const id = table._getRowId(item, i);
117
+ seenIds.add(id);
118
+ let row = rowCache.get(id);
119
+ if (row) {
120
+ if (row.original !== item) {
121
+ row.original = item;
122
+ row._valuesCache = {};
123
+ }
124
+ } else {
125
+ row = createRow(table, id, item, i, 0);
126
+ rowCache.set(id, row);
127
+ }
128
+ rowModel.rows.push(row);
129
+ rowModel.flatRows.push(row);
130
+ rowModel.rowsById[id] = row;
131
+ }
132
+ for (const id of rowCache.keys()) {
133
+ if (!seenIds.has(id)) rowCache.delete(id);
134
+ }
135
+ return rowModel;
136
+ },
137
+ getMemoOptions(
138
+ table.options,
139
+ "debugTable",
140
+ "getRowModel",
141
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
142
+ () => table._autoResetPageIndex?.()
143
+ )
144
+ );
145
+ };
146
+ }
147
+ var DataStoreFeature = {
148
+ createTable: (table) => {
149
+ const store = table.options.dataStore;
150
+ table._dataStore = store;
151
+ table.applyTransaction = (tx) => {
152
+ if (!store) {
153
+ console.warn("[GridKit] applyTransaction called without dataStore");
154
+ return;
155
+ }
156
+ store.applyTransaction(tx);
157
+ };
158
+ table.getRowNodeById = (id) => store?.get(id);
159
+ }
160
+ };
161
+
162
+ // src/core/engine/extensions/ColumnFlexFeature.ts
163
+ var ColumnFlexFeature = {
164
+ getDefaultColumnDef: () => ({ minSize: 60 }),
165
+ createTable: (table) => {
166
+ table.getFlexColumnSizing = (containerWidth) => {
167
+ const columns = table.getAllLeafColumns();
168
+ const sizing = {};
169
+ const flexCols = columns.filter((col) => col.columnDef.meta?.flex != null);
170
+ if (flexCols.length === 0) return sizing;
171
+ const fixedWidth = columns.filter((col) => col.columnDef.meta?.flex == null).reduce((sum, col) => sum + col.getSize(), 0);
172
+ const totalFlex = flexCols.reduce((sum, col) => sum + col.columnDef.meta.flex, 0);
173
+ const available = Math.max(0, containerWidth - fixedWidth);
174
+ let distributed = 0;
175
+ flexCols.forEach((col, i) => {
176
+ const flex = col.columnDef.meta.flex;
177
+ const minW = col.columnDef.meta?.minWidth ?? 60;
178
+ const isLast = i === flexCols.length - 1;
179
+ const w = isLast ? Math.max(minW, available - distributed) : Math.max(minW, Math.floor(flex / totalFlex * available));
180
+ sizing[col.id] = w;
181
+ distributed += w;
182
+ });
183
+ return sizing;
184
+ };
185
+ }
186
+ };
187
+
188
+ // src/core/engine/extensions/ColumnFilterExtension.ts
189
+ var ColumnFilterExtension = {};
190
+
191
+ // src/core/engine/extensions/RowActionsFeature.ts
192
+ var RowActionsFeature = {};
193
+
194
+ // src/core/engine/gridKitFeatures.ts
195
+ var gridKitFeatures = [
196
+ DataStoreFeature,
197
+ ColumnFlexFeature,
198
+ ColumnFilterExtension,
199
+ RowActionsFeature
200
+ ];
201
+
202
+ // src/core/hooks/useDataGridCore.ts
203
+ var _noopSubscribe = (_listener) => () => {
204
+ };
205
+ var _noopGetVersion = () => 0;
206
+ var defaultGlobalFilterFn = (row, columnId, value) => String(row.getValue(columnId) ?? "").toLowerCase().includes(value.toLowerCase());
207
+ var multiSelectFilterFn = (row, columnId, value) => value.includes(String(row.getValue(columnId) ?? ""));
208
+ multiSelectFilterFn.autoRemove = (val) => !val || val.length === 0;
209
+ var betweenFilterFn = (row, columnId, value) => {
210
+ const raw = row.getValue(columnId);
211
+ const [minStr, maxStr] = value;
212
+ const min = minStr !== "" ? Number(minStr) : -Infinity;
213
+ const max = maxStr !== "" ? Number(maxStr) : Infinity;
214
+ return raw >= min && raw <= max;
215
+ };
216
+ betweenFilterFn.autoRemove = (val) => !val || val[0] === "" && val[1] === "";
217
+ function useDataGridCore({
218
+ data = [],
219
+ dataStore,
220
+ columns,
221
+ enableSorting = true,
222
+ initialSorting,
223
+ onSortingChange,
224
+ manualSorting = false,
225
+ columnFilters: externalColumnFilters,
226
+ globalFilter: externalGlobalFilter,
227
+ onGlobalFilterChange,
228
+ searchableColumns,
229
+ enableColumnResizing = true,
230
+ enableColumnFilters = false,
231
+ visibilityState,
232
+ initialPinning,
233
+ tableKey,
234
+ syncState = false,
235
+ enablePagination = true,
236
+ paginationConfig,
237
+ totalCount,
238
+ onPageChange,
239
+ onTableReady,
240
+ onColumnSizingChange,
241
+ enableExpanding = false,
242
+ getSubRows,
243
+ getRowId,
244
+ sizing,
245
+ setSizing
246
+ }) {
247
+ const { register, update, tables } = useTableStore();
248
+ const persisted = tableKey ? tables[tableKey] : void 0;
249
+ const [expanded, setExpanded] = useState({});
250
+ const [sorting, setSorting] = useState(initialSorting ?? []);
251
+ const [columnPinning] = useState(() => {
252
+ const fromMeta = { left: [], right: [] };
253
+ for (const col of columns) {
254
+ const pin = col.meta?.pin;
255
+ const id = col.accessorKey ?? col.id;
256
+ if (!pin || !id) continue;
257
+ if (pin === "left") fromMeta.left.push(id);
258
+ else fromMeta.right.push(id);
259
+ }
260
+ return {
261
+ left: [...fromMeta.left ?? [], ...initialPinning?.left ?? []],
262
+ right: [...fromMeta.right ?? [], ...initialPinning?.right ?? []]
263
+ };
264
+ });
265
+ const [internalFilters, setInternalFilters] = useState([]);
266
+ const [internalGlobal, setInternalGlobal] = useState(persisted?.searchTerm ?? "");
267
+ const [columnVisibility, setColumnVisibility] = useState(visibilityState ?? {});
268
+ const [pagination, setPagination] = useState({
269
+ pageIndex: persisted?.pagination.pageIndex ?? paginationConfig?.initialPageIndex ?? 0,
270
+ pageSize: persisted?.pagination.pageSize ?? paginationConfig?.pageSize ?? 20
271
+ });
272
+ const tableReadyCalled = useRef(false);
273
+ useEffect(() => {
274
+ if (tableKey && syncState) {
275
+ register(tableKey, {
276
+ pagination: {
277
+ pageIndex: paginationConfig?.initialPageIndex ?? 0,
278
+ pageSize: paginationConfig?.pageSize ?? 20
279
+ }
280
+ });
281
+ }
282
+ }, [tableKey, syncState, register, paginationConfig]);
283
+ const effectiveGlobalFilter = externalGlobalFilter ?? internalGlobal;
284
+ const effectiveColumnFilters = externalColumnFilters ?? internalFilters;
285
+ const searchableFilterFn = searchableColumns?.length ? (row, _, value) => {
286
+ const search = String(value).toLowerCase();
287
+ return searchableColumns.some(
288
+ (colId) => String(row.getValue(colId) ?? "").toLowerCase().includes(search)
289
+ );
290
+ } : void 0;
291
+ const enrichedColumns = columns.map((col) => {
292
+ if (col.meta?.filterType === "number" && !col.filterFn) {
293
+ return { ...col, filterFn: betweenFilterFn };
294
+ }
295
+ if (col.meta?.filterType === "multi-select" && !col.filterFn) {
296
+ return { ...col, filterFn: multiSelectFilterFn };
297
+ }
298
+ return col;
299
+ });
300
+ useSyncExternalStore(
301
+ dataStore ? dataStore.subscribe : _noopSubscribe,
302
+ dataStore ? dataStore.getVersion : _noopGetVersion
303
+ );
304
+ const table = useReactTable({
305
+ // When dataStore is active, data is irrelevant — getDataStoreCoreRowModel
306
+ // reads directly from the store. Pass empty array to satisfy the type.
307
+ data: dataStore ? [] : data,
308
+ columns: enrichedColumns,
309
+ getRowId,
310
+ _features: [...gridKitFeatures],
311
+ dataStore,
312
+ state: {
313
+ sorting,
314
+ columnFilters: effectiveColumnFilters,
315
+ globalFilter: effectiveGlobalFilter,
316
+ columnVisibility,
317
+ columnSizing: sizing,
318
+ columnPinning,
319
+ ...enablePagination ? { pagination } : {},
320
+ ...enableExpanding ? { expanded } : {}
321
+ },
322
+ manualSorting,
323
+ manualPagination: totalCount !== void 0,
324
+ pageCount: totalCount !== void 0 && pagination.pageSize > 0 ? Math.ceil(totalCount / pagination.pageSize) : void 0,
325
+ onSortingChange: (updater) => {
326
+ setSorting((prev) => {
327
+ const next = typeof updater === "function" ? updater(prev) : updater;
328
+ onSortingChange?.(next);
329
+ return next;
330
+ });
331
+ },
332
+ onColumnFiltersChange: externalColumnFilters ? void 0 : (updater) => {
333
+ setInternalFilters((prev) => typeof updater === "function" ? updater(prev) : updater);
334
+ },
335
+ onGlobalFilterChange: externalGlobalFilter ? void 0 : (updater) => {
336
+ const next = typeof updater === "function" ? updater(internalGlobal) : updater;
337
+ setInternalGlobal(next);
338
+ onGlobalFilterChange?.(next);
339
+ },
340
+ onColumnVisibilityChange: (updater) => {
341
+ setColumnVisibility((prev) => typeof updater === "function" ? updater(prev) : updater);
342
+ },
343
+ onColumnSizingChange: (updater) => {
344
+ setSizing((prev) => {
345
+ const next = typeof updater === "function" ? updater(prev) : updater;
346
+ onColumnSizingChange?.(next);
347
+ return next;
348
+ });
349
+ },
350
+ onPaginationChange: enablePagination ? (updater) => {
351
+ setPagination((prev) => {
352
+ const next = typeof updater === "function" ? updater(prev) : updater;
353
+ if (tableKey && syncState) update(tableKey, { pagination: next });
354
+ onPageChange?.(next.pageIndex, next.pageSize);
355
+ return next;
356
+ });
357
+ } : void 0,
358
+ onExpandedChange: enableExpanding ? setExpanded : void 0,
359
+ getSubRows,
360
+ autoResetExpanded: false,
361
+ // Explicitly derive canExpand from source data so depth-N rows show the
362
+ // toggle button even before their children have been loaded into subRows
363
+ // by the filtered/sorted row models.
364
+ getRowCanExpand: getSubRows ? (row) => {
365
+ const subs = getSubRows(row.original, row.index);
366
+ return Array.isArray(subs) && subs.length > 0;
367
+ } : void 0,
368
+ // Phase 3: row-caching model when DataStore is present; stock model otherwise
369
+ getCoreRowModel: dataStore ? getDataStoreCoreRowModel() : getCoreRowModel(),
370
+ getExpandedRowModel: enableExpanding ? getExpandedRowModel() : void 0,
371
+ getSortedRowModel: manualSorting ? void 0 : getSortedRowModel(),
372
+ getFilteredRowModel: getFilteredRowModel(),
373
+ getPaginationRowModel: enablePagination ? getPaginationRowModel() : void 0,
374
+ globalFilterFn: searchableFilterFn ?? defaultGlobalFilterFn,
375
+ enableColumnResizing,
376
+ columnResizeMode: "onChange",
377
+ enableSorting,
378
+ enableColumnFilters
379
+ });
380
+ const handleGlobalFilterChange = useCallback(
381
+ (value) => {
382
+ table.setGlobalFilter(value);
383
+ if (externalGlobalFilter === void 0) {
384
+ setInternalGlobal(value);
385
+ onGlobalFilterChange?.(value);
386
+ }
387
+ if (tableKey && syncState) update(tableKey, { searchTerm: value });
388
+ },
389
+ [table, externalGlobalFilter, onGlobalFilterChange, tableKey, syncState, update]
390
+ );
391
+ useEffect(() => {
392
+ if (!tableReadyCalled.current && onTableReady) {
393
+ tableReadyCalled.current = true;
394
+ onTableReady(table);
395
+ }
396
+ }, [table, onTableReady]);
397
+ return {
398
+ table,
399
+ pagination,
400
+ globalFilter: effectiveGlobalFilter,
401
+ handleGlobalFilterChange
402
+ };
403
+ }
404
+ var VIRTUAL_THRESHOLD = 100;
405
+ function useColumnSizing({
406
+ columns,
407
+ containerRef,
408
+ mode
409
+ }) {
410
+ const userResized = useRef(/* @__PURE__ */ new Set());
411
+ const lastComputed = useRef({});
412
+ const lastContainerWidth = useRef(0);
413
+ const lastFlexFixedWidth = useRef(null);
414
+ const hasSized = useRef(false);
415
+ const [state, setState] = useState({ sizing: {}, isSized: false });
416
+ const setSizing = useCallback(
417
+ (updater) => {
418
+ setState((prev) => ({
419
+ ...prev,
420
+ sizing: typeof updater === "function" ? updater(prev.sizing) : updater
421
+ }));
422
+ },
423
+ []
424
+ );
425
+ const columnsRef = useRef(columns);
426
+ columnsRef.current = columns;
427
+ const modeRef = useRef(mode);
428
+ modeRef.current = mode;
429
+ const sizingRef = useRef(state.sizing);
430
+ sizingRef.current = state.sizing;
431
+ const measure = useCallback(() => {
432
+ const container = containerRef.current;
433
+ if (!container) return;
434
+ const containerWidth = container.clientWidth;
435
+ if (containerWidth === 0) return;
436
+ const prevContainerWidth = lastContainerWidth.current;
437
+ lastContainerWidth.current = containerWidth;
438
+ const cols = columnsRef.current;
439
+ const currentSizing = sizingRef.current;
440
+ const m = modeRef.current;
441
+ for (const [colId, currentSize] of Object.entries(currentSizing)) {
442
+ const computed = lastComputed.current[colId];
443
+ if (computed !== void 0 && computed !== currentSize) {
444
+ userResized.current.add(colId);
445
+ }
446
+ }
447
+ const newSizing = {};
448
+ if (m === "auto") {
449
+ const cells = container.querySelectorAll("[data-col-id]");
450
+ cells.forEach((cell) => {
451
+ const colId = cell.dataset.colId;
452
+ if (!colId || userResized.current.has(colId)) return;
453
+ const w = cell.scrollWidth;
454
+ const current = newSizing[colId] ?? currentSizing[colId] ?? 0;
455
+ if (w > current) newSizing[colId] = w;
456
+ });
457
+ }
458
+ const getColId = (col) => col.id ?? col.accessorKey;
459
+ const flexCols = cols.filter((col) => col.meta?.flex != null);
460
+ if (flexCols.length > 0) {
461
+ const containerWidthChanged = Math.abs(containerWidth - prevContainerWidth) > 1;
462
+ const anyUserResized = flexCols.some((col) => userResized.current.has(getColId(col)));
463
+ const freeCols = flexCols.filter((col) => !userResized.current.has(getColId(col)));
464
+ const totalFlex = freeCols.reduce((sum, col) => sum + col.meta.flex, 0);
465
+ const fixedWidth = cols.filter((col) => col.meta?.flex == null).reduce((sum, col) => {
466
+ const colId = getColId(col);
467
+ const colDefSize = typeof col.size === "number" ? col.size : 150;
468
+ return sum + (newSizing[colId] ?? currentSizing[colId] ?? colDefSize);
469
+ }, 0);
470
+ const userResizedFlexWidth = flexCols.filter((col) => userResized.current.has(getColId(col))).reduce((sum, col) => sum + (currentSizing[getColId(col)] ?? 0), 0);
471
+ const fixedWidthChanged = lastFlexFixedWidth.current !== null && lastFlexFixedWidth.current !== fixedWidth;
472
+ if (anyUserResized && !containerWidthChanged) ; else if (!containerWidthChanged && hasSized.current && !fixedWidthChanged) ; else {
473
+ lastFlexFixedWidth.current = fixedWidth;
474
+ const availableWidth = Math.max(0, containerWidth - fixedWidth - userResizedFlexWidth);
475
+ let distributed = 0;
476
+ for (let i = 0; i < freeCols.length; i++) {
477
+ const col = freeCols[i];
478
+ const colId = getColId(col);
479
+ if (!colId) continue;
480
+ const flex = col.meta.flex;
481
+ const minW = col.meta?.minWidth ?? 60;
482
+ const isLast = i === freeCols.length - 1;
483
+ const w = isLast ? Math.max(minW, availableWidth - distributed) : Math.max(minW, Math.floor(flex / totalFlex * availableWidth));
484
+ newSizing[colId] = w;
485
+ distributed += w;
486
+ }
487
+ }
488
+ }
489
+ const sizingChanged = Object.keys(newSizing).length > 0 && Object.entries(newSizing).some(([id, w]) => currentSizing[id] !== w);
490
+ const firstTime = !hasSized.current;
491
+ if (sizingChanged || firstTime) {
492
+ if (sizingChanged) Object.assign(lastComputed.current, newSizing);
493
+ if (firstTime) hasSized.current = true;
494
+ setState((prev) => ({
495
+ sizing: sizingChanged ? { ...prev.sizing, ...newSizing } : prev.sizing,
496
+ isSized: true
497
+ }));
498
+ }
499
+ }, [containerRef]);
500
+ const roTimerRef = useRef(null);
501
+ useEffect(() => {
502
+ const el = containerRef.current;
503
+ if (!el) return;
504
+ const ro = new ResizeObserver((entries) => {
505
+ const entry = entries[0];
506
+ if (!entry) return;
507
+ const width = entry.contentRect.width;
508
+ if (width === 0 || Math.abs(width - lastContainerWidth.current) < 1) return;
509
+ if (roTimerRef.current) clearTimeout(roTimerRef.current);
510
+ roTimerRef.current = setTimeout(measure, 150);
511
+ });
512
+ ro.observe(el);
513
+ return () => {
514
+ ro.disconnect();
515
+ if (roTimerRef.current) clearTimeout(roTimerRef.current);
516
+ };
517
+ }, [measure, containerRef]);
518
+ const resetSizing = useCallback(() => {
519
+ userResized.current.clear();
520
+ lastComputed.current = {};
521
+ lastContainerWidth.current = 0;
522
+ lastFlexFixedWidth.current = null;
523
+ hasSized.current = false;
524
+ setState({ sizing: {}, isSized: false });
525
+ measure();
526
+ }, [measure]);
527
+ return { sizing: state.sizing, isSized: state.isSized, setSizing, resetSizing, measure };
528
+ }
529
+
530
+ // src/core/hooks/useDataGridBase.ts
531
+ function useDataGridBase(options) {
532
+ const {
533
+ data = [],
534
+ dataStore,
535
+ columns,
536
+ enableSorting = true,
537
+ initialSorting,
538
+ onSortingChange,
539
+ manualSorting,
540
+ columnFilters,
541
+ globalFilter,
542
+ onGlobalFilterChange,
543
+ searchableColumns,
544
+ enableColumnResizing = true,
545
+ enableColumnFilters = false,
546
+ visibilityState,
547
+ initialPinning,
548
+ columnSizingMode = "flex",
549
+ checkboxConfig,
550
+ enableExpanding,
551
+ getSubRows,
552
+ getRowId,
553
+ tableKey,
554
+ syncState,
555
+ enablePagination = true,
556
+ paginationConfig,
557
+ totalCount,
558
+ onPageChange,
559
+ onTableReady,
560
+ onColumnSizingChange,
561
+ bordered
562
+ } = options;
563
+ const wrapperRef = useRef(null);
564
+ const containerRef = useRef(null);
565
+ const columnsWithCheckbox = useMemo(() => {
566
+ if (!checkboxConfig) return columns;
567
+ return [createCheckboxColumn(checkboxConfig), ...columns];
568
+ }, [columns, checkboxConfig]);
569
+ const { sizing, isSized, setSizing, measure } = useColumnSizing({
570
+ columns,
571
+ containerRef,
572
+ mode: columnSizingMode
573
+ });
574
+ const { table } = useDataGridCore({
575
+ data,
576
+ dataStore,
577
+ columns: columnsWithCheckbox,
578
+ enableSorting,
579
+ initialSorting,
580
+ onSortingChange,
581
+ manualSorting,
582
+ columnFilters,
583
+ globalFilter,
584
+ onGlobalFilterChange,
585
+ searchableColumns,
586
+ enableColumnResizing,
587
+ enableColumnFilters,
588
+ visibilityState,
589
+ initialPinning,
590
+ tableKey,
591
+ syncState,
592
+ enablePagination,
593
+ paginationConfig,
594
+ totalCount,
595
+ onPageChange,
596
+ onTableReady,
597
+ onColumnSizingChange,
598
+ enableExpanding,
599
+ getSubRows,
600
+ getRowId,
601
+ sizing,
602
+ setSizing
603
+ });
604
+ const rows = table.getRowModel().rows;
605
+ return { wrapperRef, containerRef, table, rows, isSized, bordered, measure };
606
+ }
607
+ function cn(...inputs) {
608
+ return twMerge(clsx(inputs));
609
+ }
610
+ var buttonVariants = cva(
611
+ "group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
612
+ {
613
+ variants: {
614
+ variant: {
615
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
616
+ outline: "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
617
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
618
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
619
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
620
+ link: "text-primary underline-offset-4 hover:underline"
621
+ },
622
+ size: {
623
+ default: "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
624
+ xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
625
+ sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
626
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
627
+ icon: "size-8",
628
+ "icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
629
+ "icon-sm": "size-7 rounded-none",
630
+ "icon-lg": "size-9"
631
+ }
632
+ },
633
+ defaultVariants: {
634
+ variant: "default",
635
+ size: "default"
636
+ }
637
+ }
638
+ );
639
+ function Button({
640
+ className,
641
+ variant = "default",
642
+ size = "default",
643
+ ...props
644
+ }) {
645
+ return /* @__PURE__ */ jsx(
646
+ Button$1,
647
+ {
648
+ "data-slot": "button",
649
+ className: cn(buttonVariants({ variant, size, className })),
650
+ ...props
651
+ }
652
+ );
653
+ }
654
+ var Select = Select$1.Root;
655
+ function SelectValue({ className, ...props }) {
656
+ return /* @__PURE__ */ jsx(
657
+ Select$1.Value,
658
+ {
659
+ "data-slot": "select-value",
660
+ className: cn("flex flex-1 text-left", className),
661
+ ...props
662
+ }
663
+ );
664
+ }
665
+ function SelectTrigger({
666
+ className,
667
+ size = "default",
668
+ children,
669
+ ...props
670
+ }) {
671
+ return /* @__PURE__ */ jsxs(
672
+ Select$1.Trigger,
673
+ {
674
+ "data-slot": "select-trigger",
675
+ "data-size": size,
676
+ className: cn(
677
+ "flex w-fit items-center justify-between gap-1.5 rounded-none border border-input bg-transparent py-2 pr-2 pl-2.5 text-xs whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-none *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
678
+ className
679
+ ),
680
+ ...props,
681
+ children: [
682
+ children,
683
+ /* @__PURE__ */ jsx(
684
+ Select$1.Icon,
685
+ {
686
+ render: /* @__PURE__ */ jsx(ChevronsUpDown, { className: "pointer-events-none size-4 text-muted-foreground" })
687
+ }
688
+ )
689
+ ]
690
+ }
691
+ );
692
+ }
693
+ function SelectContent({
694
+ className,
695
+ children,
696
+ side = "bottom",
697
+ sideOffset = 4,
698
+ align = "center",
699
+ alignOffset = 0,
700
+ alignItemWithTrigger = true,
701
+ ...props
702
+ }) {
703
+ return /* @__PURE__ */ jsx(Select$1.Portal, { children: /* @__PURE__ */ jsx(
704
+ Select$1.Positioner,
705
+ {
706
+ side,
707
+ sideOffset,
708
+ align,
709
+ alignOffset,
710
+ alignItemWithTrigger,
711
+ className: "isolate z-50",
712
+ children: /* @__PURE__ */ jsxs(
713
+ Select$1.Popup,
714
+ {
715
+ "data-slot": "select-content",
716
+ "data-align-trigger": alignItemWithTrigger,
717
+ className: cn(
718
+ "dark isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-none text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!",
719
+ className
720
+ ),
721
+ ...props,
722
+ children: [
723
+ /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
724
+ /* @__PURE__ */ jsx(Select$1.List, { children }),
725
+ /* @__PURE__ */ jsx(SelectScrollDownButton, {})
726
+ ]
727
+ }
728
+ )
729
+ }
730
+ ) });
731
+ }
732
+ function SelectItem({ className, children, ...props }) {
733
+ return /* @__PURE__ */ jsxs(
734
+ Select$1.Item,
735
+ {
736
+ "data-slot": "select-item",
737
+ className: cn(
738
+ "relative flex w-full cursor-default items-center gap-2 rounded-none py-2 pr-8 pl-2 text-xs outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
739
+ className
740
+ ),
741
+ ...props,
742
+ children: [
743
+ /* @__PURE__ */ jsx(Select$1.ItemText, { className: "flex flex-1 shrink-0 gap-2 whitespace-nowrap", children }),
744
+ /* @__PURE__ */ jsx(
745
+ Select$1.ItemIndicator,
746
+ {
747
+ render: /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center" }),
748
+ children: /* @__PURE__ */ jsx(Check, { className: "pointer-events-none size-3.5" })
749
+ }
750
+ )
751
+ ]
752
+ }
753
+ );
754
+ }
755
+ function SelectScrollUpButton({
756
+ className,
757
+ ...props
758
+ }) {
759
+ return /* @__PURE__ */ jsx(
760
+ Select$1.ScrollUpArrow,
761
+ {
762
+ "data-slot": "select-scroll-up-button",
763
+ className: cn(
764
+ "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
765
+ className
766
+ ),
767
+ ...props,
768
+ children: /* @__PURE__ */ jsx(ChevronUp, {})
769
+ }
770
+ );
771
+ }
772
+ function SelectScrollDownButton({
773
+ className,
774
+ ...props
775
+ }) {
776
+ return /* @__PURE__ */ jsx(
777
+ Select$1.ScrollDownArrow,
778
+ {
779
+ "data-slot": "select-scroll-down-button",
780
+ className: cn(
781
+ "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
782
+ className
783
+ ),
784
+ ...props,
785
+ children: /* @__PURE__ */ jsx(ChevronDown, {})
786
+ }
787
+ );
788
+ }
789
+ function DataGridPaginationBar({
790
+ table,
791
+ pageSizes,
792
+ totalCount
793
+ }) {
794
+ const { pageIndex, pageSize } = table.getState().pagination;
795
+ const pageCount = table.getPageCount();
796
+ const pageSizeItems = pageSizes.map((size) => ({ label: String(size), value: size }));
797
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 px-1 py-1 text-sm text-muted-foreground", children: [
798
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
799
+ /* @__PURE__ */ jsx("span", { children: "Rows per page" }),
800
+ /* @__PURE__ */ jsxs(
801
+ Select,
802
+ {
803
+ items: pageSizeItems,
804
+ value: pageSize,
805
+ onValueChange: (val) => table.setPageSize(val),
806
+ children: [
807
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", className: "w-16", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
808
+ /* @__PURE__ */ jsx(SelectContent, { children: pageSizeItems.map((item) => /* @__PURE__ */ jsx(SelectItem, { value: item.value, children: item.label }, item.value)) })
809
+ ]
810
+ }
811
+ )
812
+ ] }),
813
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
814
+ /* @__PURE__ */ jsx("span", { children: totalCount !== void 0 ? `${pageIndex * pageSize + 1}\u2013${Math.min((pageIndex + 1) * pageSize, totalCount)} of ${totalCount}` : `Page ${pageIndex + 1} of ${Math.max(pageCount, 1)}` }),
815
+ /* @__PURE__ */ jsx(
816
+ Button,
817
+ {
818
+ variant: "ghost",
819
+ size: "icon-sm",
820
+ onClick: () => table.firstPage(),
821
+ disabled: !table.getCanPreviousPage(),
822
+ children: /* @__PURE__ */ jsx(ChevronsLeft, {})
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx(
826
+ Button,
827
+ {
828
+ variant: "ghost",
829
+ size: "icon-sm",
830
+ onClick: () => table.previousPage(),
831
+ disabled: !table.getCanPreviousPage(),
832
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsx(
836
+ Button,
837
+ {
838
+ variant: "ghost",
839
+ size: "icon-sm",
840
+ onClick: () => table.nextPage(),
841
+ disabled: !table.getCanNextPage(),
842
+ children: /* @__PURE__ */ jsx(ChevronRight, {})
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsx(
846
+ Button,
847
+ {
848
+ variant: "ghost",
849
+ size: "icon-sm",
850
+ onClick: () => table.lastPage(),
851
+ disabled: !table.getCanNextPage(),
852
+ children: /* @__PURE__ */ jsx(ChevronsRight, {})
853
+ }
854
+ )
855
+ ] })
856
+ ] });
857
+ }
858
+ function DataGridToolbar({
859
+ table,
860
+ leftFilters,
861
+ rightFilters
862
+ }) {
863
+ if (!(leftFilters || rightFilters)) {
864
+ return null;
865
+ }
866
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 shrink-0", children: [
867
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: leftFilters?.(table) }),
868
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: rightFilters?.(table) })
869
+ ] });
870
+ }
871
+ function Input({ className, type, ...props }) {
872
+ return /* @__PURE__ */ jsx(
873
+ Input$1,
874
+ {
875
+ type,
876
+ "data-slot": "input",
877
+ className: cn(
878
+ "h-8 w-full min-w-0 rounded-none border border-input bg-transparent px-2.5 py-1 text-xs transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-xs file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
879
+ className
880
+ ),
881
+ ...props
882
+ }
883
+ );
884
+ }
885
+ function Checkbox({ className, ...props }) {
886
+ return /* @__PURE__ */ jsx(
887
+ Checkbox$1.Root,
888
+ {
889
+ "data-slot": "checkbox",
890
+ className: cn(
891
+ "peer relative flex size-4 shrink-0 items-center justify-center rounded-none border border-input transition-colors outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
892
+ className
893
+ ),
894
+ ...props,
895
+ children: /* @__PURE__ */ jsx(
896
+ Checkbox$1.Indicator,
897
+ {
898
+ "data-slot": "checkbox-indicator",
899
+ className: "grid place-content-center text-current transition-none [&>svg]:size-3.5",
900
+ children: /* @__PURE__ */ jsx(Check, {})
901
+ }
902
+ )
903
+ }
904
+ );
905
+ }
906
+ function Popover({ ...props }) {
907
+ return /* @__PURE__ */ jsx(Popover$1.Root, { "data-slot": "popover", ...props });
908
+ }
909
+ function PopoverTrigger({ ...props }) {
910
+ return /* @__PURE__ */ jsx(Popover$1.Trigger, { "data-slot": "popover-trigger", ...props });
911
+ }
912
+ function PopoverContent({
913
+ className,
914
+ align = "center",
915
+ alignOffset = 0,
916
+ side = "bottom",
917
+ sideOffset = 4,
918
+ ...props
919
+ }) {
920
+ return /* @__PURE__ */ jsx(Popover$1.Portal, { children: /* @__PURE__ */ jsx(
921
+ Popover$1.Positioner,
922
+ {
923
+ align,
924
+ alignOffset,
925
+ side,
926
+ sideOffset,
927
+ className: "isolate z-50",
928
+ children: /* @__PURE__ */ jsx(
929
+ Popover$1.Popup,
930
+ {
931
+ "data-slot": "popover-content",
932
+ className: cn(
933
+ "z-50 flex w-72 origin-(--transform-origin) flex-col gap-2.5 rounded-none bg-popover p-2.5 text-xs text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
934
+ className
935
+ ),
936
+ ...props
937
+ }
938
+ )
939
+ }
940
+ ) });
941
+ }
942
+ var ScrollTable = React.forwardRef(
943
+ ({ className, ...props }, ref) => /* @__PURE__ */ jsx(
944
+ "div",
945
+ {
946
+ ref,
947
+ role: "table",
948
+ className: ["text-sm", className].filter(Boolean).join(" "),
949
+ ...props
950
+ }
951
+ )
952
+ );
953
+ ScrollTable.displayName = "ScrollTable";
954
+ function CustomScrollbar({ scrollRef, direction, className, style }) {
955
+ const isV = direction === "vertical";
956
+ const trackRef = useRef(null);
957
+ const scrollSizeRef = useRef(0);
958
+ const clientSizeRef = useRef(0);
959
+ const [thumbStart, setThumbStart] = useState(0);
960
+ const [thumbEnd, setThumbEnd] = useState(1);
961
+ const sync = useCallback(() => {
962
+ const el = scrollRef.current;
963
+ if (!el) return;
964
+ const scrollPos = isV ? el.scrollTop : el.scrollLeft;
965
+ const scrollSize = isV ? el.scrollHeight : el.scrollWidth;
966
+ const clientSize = isV ? el.clientHeight : el.clientWidth;
967
+ scrollSizeRef.current = scrollSize;
968
+ clientSizeRef.current = clientSize;
969
+ if (scrollSize <= clientSize) {
970
+ setThumbStart(0);
971
+ setThumbEnd(1);
972
+ return;
973
+ }
974
+ setThumbStart(scrollPos / scrollSize);
975
+ setThumbEnd((scrollPos + clientSize) / scrollSize);
976
+ }, [scrollRef, isV]);
977
+ useLayoutEffect(() => {
978
+ sync();
979
+ }, [sync]);
980
+ useEffect(() => {
981
+ const el = scrollRef.current;
982
+ if (!el) return;
983
+ el.addEventListener("scroll", sync, { passive: true });
984
+ const ro = new ResizeObserver(sync);
985
+ ro.observe(el);
986
+ if (el.firstElementChild) ro.observe(el.firstElementChild);
987
+ return () => {
988
+ el.removeEventListener("scroll", sync);
989
+ ro.disconnect();
990
+ };
991
+ }, [scrollRef, sync]);
992
+ const thumbFrac = thumbEnd - thumbStart;
993
+ const visible = thumbFrac < 0.9999;
994
+ const handleThumbMouseDown = (e) => {
995
+ e.preventDefault();
996
+ e.stopPropagation();
997
+ const el = scrollRef.current;
998
+ if (!el) return;
999
+ const startMouse = isV ? e.clientY : e.clientX;
1000
+ const startScroll = isV ? el.scrollTop : el.scrollLeft;
1001
+ const onMove = (e2) => {
1002
+ const el2 = scrollRef.current;
1003
+ const track = trackRef.current;
1004
+ if (!el2 || !track) return;
1005
+ const trackPx = isV ? track.clientHeight : track.clientWidth;
1006
+ const thumbPx = trackPx * thumbFrac;
1007
+ const delta = (isV ? e2.clientY : e2.clientX) - startMouse;
1008
+ const maxScroll = scrollSizeRef.current - clientSizeRef.current;
1009
+ const scrollDelta = delta / (trackPx - thumbPx) * maxScroll;
1010
+ if (isV) el2.scrollTop = startScroll + scrollDelta;
1011
+ else el2.scrollLeft = startScroll + scrollDelta;
1012
+ };
1013
+ const onUp = () => {
1014
+ window.removeEventListener("mousemove", onMove);
1015
+ window.removeEventListener("mouseup", onUp);
1016
+ };
1017
+ window.addEventListener("mousemove", onMove);
1018
+ window.addEventListener("mouseup", onUp);
1019
+ };
1020
+ const handleTrackClick = (e) => {
1021
+ const track = trackRef.current;
1022
+ const el = scrollRef.current;
1023
+ if (!track || !el) return;
1024
+ const rect = track.getBoundingClientRect();
1025
+ const clickFrac = isV ? (e.clientY - rect.top) / rect.height : (e.clientX - rect.left) / rect.width;
1026
+ const targetFrac = Math.max(0, Math.min(1, (clickFrac - thumbFrac / 2) / (1 - thumbFrac)));
1027
+ const maxScroll = scrollSizeRef.current - clientSizeRef.current;
1028
+ if (isV) el.scrollTop = targetFrac * maxScroll;
1029
+ else el.scrollLeft = targetFrac * maxScroll;
1030
+ };
1031
+ return /* @__PURE__ */ jsx(
1032
+ "div",
1033
+ {
1034
+ ref: trackRef,
1035
+ onClick: handleTrackClick,
1036
+ className: cn("relative", !visible && "hidden", className),
1037
+ style,
1038
+ children: /* @__PURE__ */ jsx(
1039
+ "div",
1040
+ {
1041
+ className: "absolute rounded-full bg-foreground/20 hover:bg-foreground/35 transition-colors cursor-pointer",
1042
+ onMouseDown: handleThumbMouseDown,
1043
+ onClick: (e) => e.stopPropagation(),
1044
+ style: isV ? { top: `${thumbStart * 100}%`, bottom: `${(1 - thumbEnd) * 100}%`, left: 2, right: 2 } : { left: `${thumbStart * 100}%`, right: `${(1 - thumbEnd) * 100}%`, top: 2, bottom: 2 }
1045
+ }
1046
+ )
1047
+ }
1048
+ );
1049
+ }
1050
+ var RowWrapperContext = React__default.createContext(null);
1051
+ function colStyle(col) {
1052
+ const pinned = col.getIsPinned();
1053
+ return {
1054
+ width: col.getSize(),
1055
+ flexShrink: 0,
1056
+ ...pinned === "left" && {
1057
+ position: "sticky",
1058
+ left: col.getStart("left"),
1059
+ zIndex: 1
1060
+ },
1061
+ ...pinned === "right" && {
1062
+ position: "sticky",
1063
+ right: col.getAfter("right"),
1064
+ zIndex: 1
1065
+ }
1066
+ };
1067
+ }
1068
+ function isPinnedEdge(col, table) {
1069
+ const pinned = col.getIsPinned();
1070
+ if (pinned === "left") {
1071
+ const leftCols = table.getLeftLeafColumns();
1072
+ return leftCols[leftCols.length - 1]?.id === col.id ? "left-edge" : false;
1073
+ }
1074
+ if (pinned === "right") {
1075
+ const rightCols = table.getRightLeafColumns();
1076
+ return rightCols[0]?.id === col.id ? "right-edge" : false;
1077
+ }
1078
+ return false;
1079
+ }
1080
+ function DataGridHeaderRow({
1081
+ headerGroup,
1082
+ table,
1083
+ enableColumnResizing,
1084
+ virtual,
1085
+ bordered,
1086
+ tableWidthMode = "spacer",
1087
+ enableColumnFilters,
1088
+ filterDisplay = "row",
1089
+ classNames
1090
+ }) {
1091
+ const headers = headerGroup.headers;
1092
+ return /* @__PURE__ */ jsxs(
1093
+ "div",
1094
+ {
1095
+ role: "row",
1096
+ className: "border-b border-border",
1097
+ style: { display: "flex", width: "100%", height: "36px" },
1098
+ children: [
1099
+ headers.map((header, idx) => {
1100
+ const edge = isPinnedEdge(header.column, table);
1101
+ const isLast = idx === headers.length - 1;
1102
+ const isFillLast = tableWidthMode === "fill-last" && isLast;
1103
+ return /* @__PURE__ */ jsxs(
1104
+ "div",
1105
+ {
1106
+ role: "columnheader",
1107
+ "data-col-id": header.column.id,
1108
+ className: cn(
1109
+ "relative px-3 text-xs font-medium h-full bg-muted",
1110
+ "text-muted-foreground whitespace-normal",
1111
+ "select-none group",
1112
+ header.column.getCanSort() && "cursor-pointer",
1113
+ bordered && "border-r border-border",
1114
+ edge === "left-edge" && "shadow-[1px_0_0_0_hsl(var(--border))]",
1115
+ edge === "right-edge" && "shadow-[-1px_0_0_0_hsl(var(--border))]",
1116
+ classNames?.headerCell
1117
+ ),
1118
+ style: virtual ? {
1119
+ display: "flex",
1120
+ alignItems: "center",
1121
+ width: isFillLast ? void 0 : header.getSize(),
1122
+ ...isFillLast && { flex: 1, minWidth: header.getSize() }
1123
+ } : {
1124
+ ...colStyle(header.column),
1125
+ display: "flex",
1126
+ alignItems: "center",
1127
+ overflow: "hidden",
1128
+ ...isFillLast && { flex: 1, width: "auto" }
1129
+ },
1130
+ onClick: header.column.getCanSort() ? header.column.getToggleSortingHandler() : void 0,
1131
+ children: [
1132
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 min-w-0 overflow-hidden flex-1", children: [
1133
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }),
1134
+ header.column.getCanSort() && /* @__PURE__ */ jsx("span", { className: "ml-1 shrink-0", children: header.column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3.5 w-3.5" }) : header.column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3.5 w-3.5 opacity-40 group-hover:opacity-100" }) })
1135
+ ] }),
1136
+ enableColumnFilters && filterDisplay === "icon" && /* @__PURE__ */ jsx(HeaderFilterPopover, { col: header.column, table }),
1137
+ enableColumnResizing && header.column.getCanResize() && /* @__PURE__ */ jsx(
1138
+ "div",
1139
+ {
1140
+ onMouseDown: (e) => {
1141
+ e.stopPropagation();
1142
+ header.getResizeHandler()(e);
1143
+ },
1144
+ onTouchStart: (e) => {
1145
+ e.stopPropagation();
1146
+ header.getResizeHandler()(e);
1147
+ },
1148
+ onClick: (e) => e.stopPropagation(),
1149
+ className: "absolute right-0 top-0 h-full w-3 cursor-col-resize select-none touch-none",
1150
+ children: /* @__PURE__ */ jsx(
1151
+ "div",
1152
+ {
1153
+ className: cn(
1154
+ "absolute right-1.5 top-2 bottom-2 w-px rounded-full transition-colors",
1155
+ "opacity-0 group-hover:opacity-100",
1156
+ header.column.getIsResizing() ? "opacity-100 bg-primary" : "bg-border hover:bg-primary"
1157
+ )
1158
+ }
1159
+ )
1160
+ }
1161
+ )
1162
+ ]
1163
+ },
1164
+ header.id
1165
+ );
1166
+ }),
1167
+ !virtual && tableWidthMode === "spacer" && /* @__PURE__ */ jsx(
1168
+ "div",
1169
+ {
1170
+ role: "columnheader",
1171
+ style: { flex: 1, minWidth: 0, padding: 0 },
1172
+ className: "bg-muted"
1173
+ }
1174
+ )
1175
+ ]
1176
+ }
1177
+ );
1178
+ }
1179
+ function HeaderFilterPopover({
1180
+ col,
1181
+ table
1182
+ }) {
1183
+ const [open, setOpen] = useState(false);
1184
+ const focusRef = useCallback((el) => {
1185
+ el?.focus({ preventScroll: true });
1186
+ }, []);
1187
+ const ft = col.columnDef.meta?.filterType;
1188
+ if (ft === false || ft === void 0) return null;
1189
+ const hasFilter = col.getIsFiltered();
1190
+ const filterValue = col.getFilterValue() ?? "";
1191
+ const numFilter = col.getFilterValue();
1192
+ const min = numFilter?.[0] ?? "";
1193
+ const max = numFilter?.[1] ?? "";
1194
+ return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
1195
+ /* @__PURE__ */ jsx(
1196
+ PopoverTrigger,
1197
+ {
1198
+ render: (props) => /* @__PURE__ */ jsx(
1199
+ Button,
1200
+ {
1201
+ ...props,
1202
+ variant: "ghost",
1203
+ size: "icon-xs",
1204
+ className: cn(
1205
+ "h-5 w-5 shrink-0",
1206
+ hasFilter ? "text-primary opacity-100" : "opacity-0 group-hover:opacity-60"
1207
+ ),
1208
+ children: /* @__PURE__ */ jsx(Filter, { className: "h-3 w-3" })
1209
+ }
1210
+ )
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsx(PopoverContent, { side: "bottom", align: "start", className: "w-52", children: ft === "select" ? /* @__PURE__ */ jsx(SelectFilterCell, { col, table, onSelect: () => setOpen(false) }) : ft === "multi-select" ? /* @__PURE__ */ jsx(MultiSelectContent, { col, table }) : ft === "number" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1214
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1215
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Min" }),
1216
+ /* @__PURE__ */ jsx(
1217
+ Input,
1218
+ {
1219
+ type: "number",
1220
+ placeholder: "Min",
1221
+ value: min,
1222
+ onChange: (e) => col.setFilterValue((old = ["", ""]) => [
1223
+ e.target.value,
1224
+ old[1]
1225
+ ]),
1226
+ className: "h-7 text-xs"
1227
+ }
1228
+ )
1229
+ ] }),
1230
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1231
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Max" }),
1232
+ /* @__PURE__ */ jsx(
1233
+ Input,
1234
+ {
1235
+ type: "number",
1236
+ placeholder: "Max",
1237
+ value: max,
1238
+ onChange: (e) => col.setFilterValue((old = ["", ""]) => [
1239
+ old[0],
1240
+ e.target.value
1241
+ ]),
1242
+ className: "h-7 text-xs"
1243
+ }
1244
+ )
1245
+ ] }),
1246
+ hasFilter && /* @__PURE__ */ jsx(
1247
+ Button,
1248
+ {
1249
+ variant: "ghost",
1250
+ size: "sm",
1251
+ className: "h-7 text-xs",
1252
+ onClick: () => col.setFilterValue(void 0),
1253
+ children: "Clear"
1254
+ }
1255
+ )
1256
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1257
+ /* @__PURE__ */ jsx(
1258
+ Input,
1259
+ {
1260
+ type: "text",
1261
+ placeholder: "Filter\u2026",
1262
+ value: filterValue,
1263
+ onChange: (e) => col.setFilterValue(e.target.value || void 0),
1264
+ className: "h-7 text-xs pr-6",
1265
+ ref: focusRef
1266
+ }
1267
+ ),
1268
+ filterValue && /* @__PURE__ */ jsx(
1269
+ Button,
1270
+ {
1271
+ variant: "ghost",
1272
+ size: "icon-xs",
1273
+ onClick: () => col.setFilterValue(void 0),
1274
+ className: "absolute right-0.5 top-1/2 -translate-y-1/2",
1275
+ children: /* @__PURE__ */ jsx(X, {})
1276
+ }
1277
+ )
1278
+ ] }) })
1279
+ ] }) });
1280
+ }
1281
+ function NumberFilterPopover({ col }) {
1282
+ const numFilter = col.getFilterValue();
1283
+ const min = numFilter?.[0] ?? "";
1284
+ const max = numFilter?.[1] ?? "";
1285
+ const hasFilter = min !== "" || max !== "";
1286
+ const label = hasFilter ? [min && `\u2265${min}`, max && `\u2264${max}`].filter(Boolean).join(" ") : "Filter\u2026";
1287
+ return /* @__PURE__ */ jsxs(Popover, { children: [
1288
+ /* @__PURE__ */ jsx(
1289
+ PopoverTrigger,
1290
+ {
1291
+ render: (props) => /* @__PURE__ */ jsxs(
1292
+ Button,
1293
+ {
1294
+ ...props,
1295
+ variant: hasFilter ? "outline" : "ghost",
1296
+ size: "sm",
1297
+ className: "h-7 w-full justify-start text-xs font-normal",
1298
+ children: [
1299
+ /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-3 w-3 shrink-0" }),
1300
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
1301
+ ]
1302
+ }
1303
+ )
1304
+ }
1305
+ ),
1306
+ /* @__PURE__ */ jsx(PopoverContent, { side: "bottom", align: "start", className: "w-48", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1307
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1308
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Min" }),
1309
+ /* @__PURE__ */ jsx(
1310
+ Input,
1311
+ {
1312
+ type: "number",
1313
+ placeholder: "Min",
1314
+ value: min,
1315
+ onChange: (e) => col.setFilterValue((old = ["", ""]) => [e.target.value, old[1]]),
1316
+ className: "h-7 text-xs"
1317
+ }
1318
+ )
1319
+ ] }),
1320
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1321
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Max" }),
1322
+ /* @__PURE__ */ jsx(
1323
+ Input,
1324
+ {
1325
+ type: "number",
1326
+ placeholder: "Max",
1327
+ value: max,
1328
+ onChange: (e) => col.setFilterValue((old = ["", ""]) => [old[0], e.target.value]),
1329
+ className: "h-7 text-xs"
1330
+ }
1331
+ )
1332
+ ] }),
1333
+ hasFilter && /* @__PURE__ */ jsx(
1334
+ Button,
1335
+ {
1336
+ variant: "ghost",
1337
+ size: "sm",
1338
+ className: "h-7 text-xs",
1339
+ onClick: () => col.setFilterValue(void 0),
1340
+ children: "Clear"
1341
+ }
1342
+ )
1343
+ ] }) })
1344
+ ] });
1345
+ }
1346
+ function SelectFilterCell({
1347
+ col,
1348
+ table,
1349
+ onSelect
1350
+ }) {
1351
+ const [options, setOptions] = useState(null);
1352
+ const filterValue = col.getFilterValue() ?? "";
1353
+ const handleOpenChange = (open) => {
1354
+ if (open && options === null) {
1355
+ const vals = /* @__PURE__ */ new Set();
1356
+ table.getCoreRowModel().rows.forEach((row) => {
1357
+ const v = row.getValue(col.id);
1358
+ if (v != null) vals.add(String(v));
1359
+ });
1360
+ const sorted = Array.from(vals).sort();
1361
+ setOptions([{ label: "All", value: null }, ...sorted.map((v) => ({ label: v, value: v }))]);
1362
+ }
1363
+ };
1364
+ const items = options ?? [];
1365
+ return /* @__PURE__ */ jsxs(
1366
+ Select,
1367
+ {
1368
+ items,
1369
+ value: filterValue || null,
1370
+ onValueChange: (val) => {
1371
+ col.setFilterValue(val ?? void 0);
1372
+ onSelect?.();
1373
+ },
1374
+ onOpenChange: handleOpenChange,
1375
+ children: [
1376
+ /* @__PURE__ */ jsx(SelectTrigger, { size: "sm", className: "h-7 w-full text-xs", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1377
+ /* @__PURE__ */ jsx(SelectContent, { children: items.map((item) => /* @__PURE__ */ jsx(SelectItem, { value: item.value, children: item.label }, item.value ?? "__all__")) })
1378
+ ]
1379
+ }
1380
+ );
1381
+ }
1382
+ function MultiSelectContent({ col, table }) {
1383
+ const [options, setOptions] = useState(null);
1384
+ const selected = col.getFilterValue() ?? [];
1385
+ useEffect(() => {
1386
+ const vals = /* @__PURE__ */ new Set();
1387
+ table.getCoreRowModel().rows.forEach((row) => {
1388
+ const v = row.getValue(col.id);
1389
+ if (v != null) vals.add(String(v));
1390
+ });
1391
+ setOptions(Array.from(vals).sort());
1392
+ }, []);
1393
+ const toggle = (val) => {
1394
+ const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val];
1395
+ col.setFilterValue(next.length > 0 ? next : void 0);
1396
+ };
1397
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
1398
+ /* @__PURE__ */ jsx("div", { className: "max-h-48 overflow-y-auto flex flex-col gap-0.5", children: (options ?? []).map((opt) => /* @__PURE__ */ jsxs(
1399
+ "label",
1400
+ {
1401
+ className: "flex items-center gap-2 px-1 py-1 cursor-pointer hover:bg-muted rounded-sm text-xs select-none",
1402
+ children: [
1403
+ /* @__PURE__ */ jsx(
1404
+ Checkbox,
1405
+ {
1406
+ checked: selected.includes(opt),
1407
+ onCheckedChange: () => toggle(opt),
1408
+ className: "shrink-0"
1409
+ }
1410
+ ),
1411
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: opt })
1412
+ ]
1413
+ },
1414
+ opt
1415
+ )) }),
1416
+ selected.length > 0 && /* @__PURE__ */ jsxs(
1417
+ Button,
1418
+ {
1419
+ variant: "ghost",
1420
+ size: "sm",
1421
+ className: "h-6 text-xs mt-1",
1422
+ onClick: () => col.setFilterValue(void 0),
1423
+ children: [
1424
+ "Clear (",
1425
+ selected.length,
1426
+ ")"
1427
+ ]
1428
+ }
1429
+ )
1430
+ ] });
1431
+ }
1432
+ function MultiSelectFilterCell({
1433
+ col,
1434
+ table
1435
+ }) {
1436
+ const selected = col.getFilterValue() ?? [];
1437
+ const label = selected.length > 0 ? `${selected.length} selected` : "Filter\u2026";
1438
+ return /* @__PURE__ */ jsxs(Popover, { children: [
1439
+ /* @__PURE__ */ jsx(
1440
+ PopoverTrigger,
1441
+ {
1442
+ render: (props) => /* @__PURE__ */ jsx(
1443
+ Button,
1444
+ {
1445
+ ...props,
1446
+ variant: selected.length > 0 ? "outline" : "ghost",
1447
+ size: "sm",
1448
+ className: "h-7 w-full justify-start text-xs font-normal",
1449
+ children: /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
1450
+ }
1451
+ )
1452
+ }
1453
+ ),
1454
+ /* @__PURE__ */ jsx(PopoverContent, { side: "bottom", align: "start", className: "w-48", children: /* @__PURE__ */ jsx(MultiSelectContent, { col, table }) })
1455
+ ] });
1456
+ }
1457
+ function DataGridFilterRow({
1458
+ visibleLeafColumns,
1459
+ table,
1460
+ virtual,
1461
+ bordered,
1462
+ tableWidthMode = "spacer"
1463
+ }) {
1464
+ return /* @__PURE__ */ jsxs(
1465
+ "div",
1466
+ {
1467
+ role: "row",
1468
+ className: "border-b border-border bg-muted",
1469
+ style: { display: "flex", width: "100%", height: "36px" },
1470
+ children: [
1471
+ visibleLeafColumns.map((col) => {
1472
+ const ft = col.columnDef.meta?.filterType;
1473
+ const filterValue = col.getFilterValue() ?? "";
1474
+ const cellStyle = virtual ? { display: "flex", alignItems: "center", width: col.getSize() } : { ...colStyle(col), display: "flex", alignItems: "center" };
1475
+ if (ft === false) {
1476
+ return /* @__PURE__ */ jsx(
1477
+ "div",
1478
+ {
1479
+ role: "columnheader",
1480
+ className: cn("px-2 py-1", bordered && "border-r border-border"),
1481
+ style: cellStyle
1482
+ },
1483
+ col.id
1484
+ );
1485
+ }
1486
+ return /* @__PURE__ */ jsx(
1487
+ "div",
1488
+ {
1489
+ role: "columnheader",
1490
+ className: cn("px-2 py-1 font-normal", bordered && "border-r border-border"),
1491
+ style: cellStyle,
1492
+ children: ft === "select" ? /* @__PURE__ */ jsx(SelectFilterCell, { col, table }) : ft === "multi-select" ? /* @__PURE__ */ jsx(MultiSelectFilterCell, { col, table }) : ft === "number" ? /* @__PURE__ */ jsx(NumberFilterPopover, { col }) : /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
1493
+ /* @__PURE__ */ jsx(
1494
+ Input,
1495
+ {
1496
+ type: "text",
1497
+ placeholder: "Filter\u2026",
1498
+ value: filterValue,
1499
+ onChange: (e) => col.setFilterValue(e.target.value || void 0),
1500
+ className: "h-7 text-xs pr-6"
1501
+ }
1502
+ ),
1503
+ filterValue && /* @__PURE__ */ jsx(
1504
+ Button,
1505
+ {
1506
+ variant: "ghost",
1507
+ size: "icon-xs",
1508
+ onClick: () => col.setFilterValue(void 0),
1509
+ className: "absolute right-0.5 top-1/2 -translate-y-1/2",
1510
+ children: /* @__PURE__ */ jsx(X, {})
1511
+ }
1512
+ )
1513
+ ] })
1514
+ },
1515
+ col.id
1516
+ );
1517
+ }),
1518
+ !virtual && tableWidthMode === "spacer" && /* @__PURE__ */ jsx(
1519
+ "div",
1520
+ {
1521
+ role: "columnheader",
1522
+ style: { flex: 1, minWidth: 0, padding: 0 },
1523
+ className: "bg-muted"
1524
+ }
1525
+ )
1526
+ ]
1527
+ }
1528
+ );
1529
+ }
1530
+ function DataGridBodyRow({
1531
+ row,
1532
+ table,
1533
+ onRowClick,
1534
+ rowCursor,
1535
+ style,
1536
+ dataIndex,
1537
+ measureRef,
1538
+ showSpacer = false,
1539
+ fillLast = false,
1540
+ bordered = false,
1541
+ rowHeight,
1542
+ onActionTrigger,
1543
+ classNames
1544
+ }) {
1545
+ const visibleCells = row.getVisibleCells();
1546
+ return /* @__PURE__ */ jsxs(
1547
+ "div",
1548
+ {
1549
+ role: "row",
1550
+ "data-index": dataIndex,
1551
+ ref: measureRef,
1552
+ onClick: onRowClick ? () => onRowClick(row.original) : void 0,
1553
+ className: cn(
1554
+ "flex w-full border-b border-border transition-colors",
1555
+ onRowClick || rowCursor ? "cursor-pointer hover:bg-muted/50" : "hover:bg-muted/30",
1556
+ classNames?.row
1557
+ ),
1558
+ style: { minHeight: rowHeight, ...style },
1559
+ children: [
1560
+ visibleCells.map((cell, idx) => {
1561
+ const meta = cell.column.columnDef.meta;
1562
+ const edge = isPinnedEdge(cell.column, table);
1563
+ const isLast = idx === visibleCells.length - 1;
1564
+ const isFillCell = fillLast && isLast;
1565
+ return /* @__PURE__ */ jsx(
1566
+ "div",
1567
+ {
1568
+ role: "gridcell",
1569
+ "data-col-id": cell.column.id,
1570
+ className: cn(
1571
+ "flex items-center px-3 py-1 overflow-hidden bg-background",
1572
+ meta?.align === "right" && "justify-end",
1573
+ meta?.align === "center" && "justify-center",
1574
+ meta?.wrap && "items-start whitespace-normal",
1575
+ bordered && "border-r border-border",
1576
+ edge === "left-edge" && "shadow-[1px_0_0_0_hsl(var(--border))]",
1577
+ edge === "right-edge" && "shadow-[-1px_0_0_0_hsl(var(--border))]",
1578
+ classNames?.cell
1579
+ ),
1580
+ style: { ...colStyle(cell.column), ...isFillCell && { flex: 1, width: "auto" } },
1581
+ children: meta?.actions != null ? /* @__PURE__ */ jsx(
1582
+ Button,
1583
+ {
1584
+ variant: "ghost",
1585
+ size: "icon-xs",
1586
+ className: "h-6 w-6",
1587
+ onClick: (e) => {
1588
+ e.stopPropagation();
1589
+ onActionTrigger?.(row.original, e.currentTarget);
1590
+ },
1591
+ children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4" })
1592
+ }
1593
+ ) : flexRender(cell.column.columnDef.cell, cell.getContext())
1594
+ },
1595
+ cell.id
1596
+ );
1597
+ }),
1598
+ showSpacer && /* @__PURE__ */ jsx(
1599
+ "div",
1600
+ {
1601
+ role: "gridcell",
1602
+ style: { flex: 1, minWidth: 0, padding: 0 },
1603
+ className: "bg-background"
1604
+ }
1605
+ )
1606
+ ]
1607
+ }
1608
+ );
1609
+ }
1610
+ function DataGridVirtualBody({
1611
+ rows,
1612
+ table,
1613
+ rowVirtualizer,
1614
+ onRowClick,
1615
+ rowCursor,
1616
+ bordered,
1617
+ rowHeight,
1618
+ onActionTrigger,
1619
+ tableWidthMode = "spacer",
1620
+ classNames
1621
+ }) {
1622
+ const virtualItems = rowVirtualizer.getVirtualItems();
1623
+ const totalSize = rowVirtualizer.getTotalSize();
1624
+ return /* @__PURE__ */ jsx("div", { role: "rowgroup", style: { display: "block", height: totalSize, position: "relative" }, children: virtualItems.map((virtualRow) => {
1625
+ const row = rows[virtualRow.index];
1626
+ return /* @__PURE__ */ jsx(
1627
+ DataGridBodyRow,
1628
+ {
1629
+ row,
1630
+ table,
1631
+ onRowClick,
1632
+ rowCursor,
1633
+ bordered,
1634
+ rowHeight,
1635
+ dataIndex: virtualRow.index,
1636
+ measureRef: rowVirtualizer.measureElement,
1637
+ style: {
1638
+ position: "absolute",
1639
+ width: "100%",
1640
+ transform: `translateY(${virtualRow.start}px)`
1641
+ },
1642
+ onActionTrigger,
1643
+ fillLast: tableWidthMode === "fill-last",
1644
+ classNames
1645
+ },
1646
+ row.id
1647
+ );
1648
+ }) });
1649
+ }
1650
+ function DataGridFlexBody({
1651
+ rows,
1652
+ table,
1653
+ visibleLeafColumns,
1654
+ isLoading,
1655
+ emptyMessage,
1656
+ emptyContent,
1657
+ onRowClick,
1658
+ rowCursor,
1659
+ bordered,
1660
+ rowHeight,
1661
+ onActionTrigger,
1662
+ tableWidthMode = "spacer",
1663
+ classNames
1664
+ }) {
1665
+ const showSpacer = tableWidthMode === "spacer";
1666
+ const fillLast = tableWidthMode === "fill-last";
1667
+ const RowWrapper = useContext(RowWrapperContext);
1668
+ if (isLoading) {
1669
+ return /* @__PURE__ */ jsx("div", { role: "rowgroup", style: { display: "block" }, className: "[&>div:last-child]:border-b-0", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs(
1670
+ "div",
1671
+ {
1672
+ role: "row",
1673
+ className: "flex w-full border-b border-border",
1674
+ style: { minHeight: rowHeight },
1675
+ children: [
1676
+ visibleLeafColumns.map((col, colIdx) => {
1677
+ const isLast = colIdx === visibleLeafColumns.length - 1;
1678
+ return /* @__PURE__ */ jsx(
1679
+ "div",
1680
+ {
1681
+ role: "gridcell",
1682
+ "data-col-id": col.id,
1683
+ className: cn(
1684
+ "flex items-center px-3 py-1",
1685
+ bordered && "border-r border-border"
1686
+ ),
1687
+ style: {
1688
+ ...colStyle(col),
1689
+ ...fillLast && isLast && { flex: 1, width: "auto" }
1690
+ },
1691
+ children: /* @__PURE__ */ jsx("div", { className: "h-4 w-full animate-pulse rounded bg-muted" })
1692
+ },
1693
+ col.id
1694
+ );
1695
+ }),
1696
+ showSpacer && /* @__PURE__ */ jsx("div", { role: "gridcell", style: { flex: 1, minWidth: 0, padding: 0 } })
1697
+ ]
1698
+ },
1699
+ i
1700
+ )) });
1701
+ }
1702
+ if (rows.length === 0) {
1703
+ return /* @__PURE__ */ jsx("div", { role: "rowgroup", style: { display: "block" }, children: /* @__PURE__ */ jsx("div", { role: "row", className: "flex w-full", children: /* @__PURE__ */ jsx("div", { role: "gridcell", className: "flex-1", children: emptyContent ?? /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-muted-foreground text-sm", children: emptyMessage }) }) }) });
1704
+ }
1705
+ return /* @__PURE__ */ jsx("div", { role: "rowgroup", style: { display: "block" }, className: "[&>div:last-child]:border-b-0", children: rows.map((row) => {
1706
+ const bodyRow = /* @__PURE__ */ jsx(
1707
+ DataGridBodyRow,
1708
+ {
1709
+ row,
1710
+ table,
1711
+ onRowClick,
1712
+ rowCursor,
1713
+ bordered,
1714
+ rowHeight,
1715
+ showSpacer,
1716
+ fillLast,
1717
+ onActionTrigger,
1718
+ classNames
1719
+ }
1720
+ );
1721
+ if (RowWrapper) {
1722
+ return /* @__PURE__ */ jsx(RowWrapper, { row, children: bodyRow }, row.id);
1723
+ }
1724
+ return /* @__PURE__ */ jsx(React__default.Fragment, { children: bodyRow }, row.id);
1725
+ }) });
1726
+ }
1727
+ function DataGridTableView({
1728
+ table,
1729
+ rows,
1730
+ containerRef,
1731
+ isLoading,
1732
+ emptyMessage = "No data",
1733
+ emptyContent,
1734
+ showHeader = true,
1735
+ onRowClick,
1736
+ rowCursor,
1737
+ enableColumnResizing = true,
1738
+ enableColumnFilters = false,
1739
+ filterDisplay = "row",
1740
+ tableHeight,
1741
+ tableWidthMode = "spacer",
1742
+ rowHeight,
1743
+ estimateRowHeight,
1744
+ overscan = 10,
1745
+ loadMoreRef,
1746
+ isFetchingNextPage,
1747
+ bordered = false,
1748
+ onMeasureColumns,
1749
+ classNames
1750
+ }) {
1751
+ const effectiveEstimate = estimateRowHeight ?? rowHeight ?? 33;
1752
+ const [actionMenuOpen, setActionMenuOpen] = useState(false);
1753
+ const [activeRow, setActiveRow] = useState(null);
1754
+ const anchorRef = useRef(null);
1755
+ const handleActionTrigger = useCallback((row, el) => {
1756
+ const rect = el.getBoundingClientRect();
1757
+ anchorRef.current = { getBoundingClientRect: () => rect };
1758
+ setActiveRow(row);
1759
+ setActionMenuOpen(true);
1760
+ }, []);
1761
+ const headerGroups = table.getHeaderGroups();
1762
+ const visibleLeafColumns = table.getVisibleLeafColumns();
1763
+ const actionCol = visibleLeafColumns.find((col) => col.columnDef.meta?.actions != null);
1764
+ const actionItems = actionCol && activeRow ? actionCol.columnDef.meta.actions(activeRow) : [];
1765
+ const hasFixedHeight = tableHeight != null && tableHeight !== "auto";
1766
+ const virtual = hasFixedHeight && rows.length >= VIRTUAL_THRESHOLD;
1767
+ useEffect(() => {
1768
+ if (!table.getState().columnSizingInfo.isResizingColumn) {
1769
+ onMeasureColumns?.();
1770
+ }
1771
+ });
1772
+ const headerScrollRef = useRef(null);
1773
+ const bodyScrollRef = useRef(null);
1774
+ const syncScroll = useCallback((e) => {
1775
+ if (headerScrollRef.current) {
1776
+ headerScrollRef.current.scrollLeft = e.currentTarget.scrollLeft;
1777
+ }
1778
+ }, []);
1779
+ const rowVirtualizer = useVirtualizer({
1780
+ count: rows.length,
1781
+ getScrollElement: () => bodyScrollRef.current,
1782
+ estimateSize: () => effectiveEstimate,
1783
+ overscan,
1784
+ enabled: virtual
1785
+ });
1786
+ const bodyWrapperStyle = {
1787
+ display: "flex",
1788
+ flexDirection: "column",
1789
+ position: "relative",
1790
+ ...tableHeight && tableHeight !== "auto" ? { height: tableHeight } : {}
1791
+ };
1792
+ const bodyStyle = {
1793
+ flex: 1,
1794
+ minHeight: 0,
1795
+ overflow: "auto"
1796
+ };
1797
+ const innerWidth = table.getTotalSize();
1798
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1799
+ /* @__PURE__ */ jsxs(
1800
+ "div",
1801
+ {
1802
+ ref: containerRef,
1803
+ style: {
1804
+ position: "relative",
1805
+ width: "100%",
1806
+ minWidth: 0,
1807
+ isolation: "isolate",
1808
+ // Limit reflow scope to this subtree.
1809
+ // 'layout paint' isolates layout and paint without size containment,
1810
+ // which is required since the outer div's height is determined by its children.
1811
+ contain: "layout paint"
1812
+ },
1813
+ className: cn("rounded-md border border-border", classNames?.container),
1814
+ children: [
1815
+ showHeader && /* @__PURE__ */ jsx("div", { ref: headerScrollRef, style: { overflow: "hidden" }, className: cn("bg-muted", classNames?.header), children: /* @__PURE__ */ jsxs("div", { style: { width: innerWidth, minWidth: "100%" }, children: [
1816
+ headerGroups.map((headerGroup) => /* @__PURE__ */ jsx(
1817
+ DataGridHeaderRow,
1818
+ {
1819
+ headerGroup,
1820
+ table,
1821
+ enableColumnResizing,
1822
+ enableColumnFilters,
1823
+ filterDisplay,
1824
+ virtual,
1825
+ bordered,
1826
+ tableWidthMode,
1827
+ classNames
1828
+ },
1829
+ headerGroup.id
1830
+ )),
1831
+ enableColumnFilters && filterDisplay !== "icon" && /* @__PURE__ */ jsx(
1832
+ DataGridFilterRow,
1833
+ {
1834
+ visibleLeafColumns,
1835
+ table,
1836
+ virtual,
1837
+ bordered,
1838
+ tableWidthMode
1839
+ }
1840
+ )
1841
+ ] }) }),
1842
+ /* @__PURE__ */ jsxs("div", { style: bodyWrapperStyle, children: [
1843
+ /* @__PURE__ */ jsxs(
1844
+ "div",
1845
+ {
1846
+ ref: bodyScrollRef,
1847
+ style: bodyStyle,
1848
+ onScroll: syncScroll,
1849
+ className: "scrollbar-none",
1850
+ children: [
1851
+ /* @__PURE__ */ jsx(ScrollTable, { style: { width: innerWidth, minWidth: "100%" }, children: virtual ? /* @__PURE__ */ jsx(
1852
+ DataGridVirtualBody,
1853
+ {
1854
+ rows,
1855
+ table,
1856
+ rowVirtualizer,
1857
+ onRowClick,
1858
+ rowCursor,
1859
+ bordered,
1860
+ rowHeight,
1861
+ onActionTrigger: actionCol ? handleActionTrigger : void 0,
1862
+ tableWidthMode,
1863
+ classNames
1864
+ }
1865
+ ) : /* @__PURE__ */ jsx(
1866
+ DataGridFlexBody,
1867
+ {
1868
+ rows,
1869
+ table,
1870
+ visibleLeafColumns,
1871
+ isLoading,
1872
+ emptyMessage,
1873
+ emptyContent,
1874
+ onRowClick,
1875
+ rowCursor,
1876
+ bordered,
1877
+ rowHeight,
1878
+ onActionTrigger: actionCol ? handleActionTrigger : void 0,
1879
+ tableWidthMode,
1880
+ classNames
1881
+ }
1882
+ ) }),
1883
+ loadMoreRef && /* @__PURE__ */ jsx("div", { ref: loadMoreRef, className: "py-2 flex justify-center", children: isFetchingNextPage && /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) })
1884
+ ]
1885
+ }
1886
+ ),
1887
+ /* @__PURE__ */ jsx(
1888
+ CustomScrollbar,
1889
+ {
1890
+ scrollRef: bodyScrollRef,
1891
+ direction: "vertical",
1892
+ className: "absolute right-0 top-0 bottom-0",
1893
+ style: { width: 8 }
1894
+ }
1895
+ ),
1896
+ /* @__PURE__ */ jsx(CustomScrollbar, { scrollRef: bodyScrollRef, direction: "horizontal", style: { height: 8 } })
1897
+ ] })
1898
+ ]
1899
+ }
1900
+ ),
1901
+ actionCol && /* @__PURE__ */ jsx(Menu.Root, { open: actionMenuOpen, onOpenChange: setActionMenuOpen, children: /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(
1902
+ Menu.Positioner,
1903
+ {
1904
+ anchor: anchorRef.current,
1905
+ side: "bottom",
1906
+ align: "end",
1907
+ sideOffset: 4,
1908
+ className: "isolate z-50 outline-none",
1909
+ children: /* @__PURE__ */ jsx(Menu.Popup, { className: "min-w-32 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", children: actionItems.map((item, i) => /* @__PURE__ */ jsxs(
1910
+ Menu.Item,
1911
+ {
1912
+ disabled: item.disabled,
1913
+ "data-variant": item.variant ?? "default",
1914
+ onClick: () => {
1915
+ item.onClick(activeRow);
1916
+ setActionMenuOpen(false);
1917
+ },
1918
+ className: "relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1919
+ children: [
1920
+ item.icon,
1921
+ item.label
1922
+ ]
1923
+ },
1924
+ i
1925
+ )) })
1926
+ }
1927
+ ) }) })
1928
+ ] });
1929
+ }
1930
+ function DataGridShell({
1931
+ wrapperRef,
1932
+ containerRef,
1933
+ table,
1934
+ rows,
1935
+ isSized,
1936
+ measure,
1937
+ error,
1938
+ leftFilters,
1939
+ rightFilters,
1940
+ loadMoreRef,
1941
+ isFetchingNextPage,
1942
+ footer,
1943
+ ...viewConfig
1944
+ }) {
1945
+ if (error) {
1946
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12 text-sm text-destructive", children: error.message });
1947
+ }
1948
+ return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "flex flex-col gap-3 w-full min-w-0 overflow-hidden", children: [
1949
+ /* @__PURE__ */ jsx(DataGridToolbar, { table, leftFilters, rightFilters }),
1950
+ /* @__PURE__ */ jsx("div", { className: cn("min-w-0", !isSized && "invisible"), children: /* @__PURE__ */ jsx(
1951
+ DataGridTableView,
1952
+ {
1953
+ table,
1954
+ rows,
1955
+ containerRef,
1956
+ loadMoreRef,
1957
+ isFetchingNextPage,
1958
+ onMeasureColumns: measure,
1959
+ ...viewConfig
1960
+ }
1961
+ ) }),
1962
+ footer
1963
+ ] });
1964
+ }
1965
+ function DataGrid(props) {
1966
+ const {
1967
+ error,
1968
+ leftFilters,
1969
+ rightFilters,
1970
+ enablePagination = true,
1971
+ pageSizes = [10, 20, 50, 100],
1972
+ totalCount,
1973
+ tableRef
1974
+ } = props;
1975
+ const { wrapperRef, containerRef, table, rows, isSized, measure } = useDataGridBase(props);
1976
+ useEffect(() => {
1977
+ if (tableRef) {
1978
+ tableRef.current = table;
1979
+ return () => {
1980
+ tableRef.current = null;
1981
+ };
1982
+ }
1983
+ }, [table, tableRef]);
1984
+ return /* @__PURE__ */ jsx(
1985
+ DataGridShell,
1986
+ {
1987
+ ...props,
1988
+ wrapperRef,
1989
+ containerRef,
1990
+ table,
1991
+ rows,
1992
+ isSized,
1993
+ measure,
1994
+ error,
1995
+ leftFilters,
1996
+ rightFilters,
1997
+ footer: enablePagination ? /* @__PURE__ */ jsx(DataGridPaginationBar, { table, pageSizes, totalCount }) : null
1998
+ }
1999
+ );
2000
+ }
2001
+ function useInfiniteScroll({
2002
+ hasNextPage,
2003
+ isFetchingNextPage,
2004
+ fetchNextPage,
2005
+ rootMargin = "100px",
2006
+ enabled = true
2007
+ }) {
2008
+ const loadMoreRef = useRef(null);
2009
+ useEffect(() => {
2010
+ if (!enabled || !loadMoreRef.current) return;
2011
+ const observer = new IntersectionObserver(
2012
+ ([entry]) => {
2013
+ if (entry?.isIntersecting && hasNextPage && !isFetchingNextPage) {
2014
+ fetchNextPage?.();
2015
+ }
2016
+ },
2017
+ { rootMargin }
2018
+ );
2019
+ observer.observe(loadMoreRef.current);
2020
+ return () => observer.disconnect();
2021
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage, rootMargin, enabled]);
2022
+ return { loadMoreRef };
2023
+ }
2024
+ function DataGridInfinity(props) {
2025
+ const {
2026
+ error,
2027
+ leftFilters,
2028
+ rightFilters,
2029
+ hasNextPage,
2030
+ isFetchingNextPage,
2031
+ fetchNextPage,
2032
+ rootMargin = "100px",
2033
+ isLoading
2034
+ } = props;
2035
+ const { wrapperRef, containerRef, table, rows, isSized, measure } = useDataGridBase({
2036
+ ...props,
2037
+ enablePagination: false
2038
+ });
2039
+ const { loadMoreRef } = useInfiniteScroll({
2040
+ hasNextPage,
2041
+ isFetchingNextPage,
2042
+ fetchNextPage,
2043
+ rootMargin,
2044
+ enabled: !isLoading
2045
+ });
2046
+ return /* @__PURE__ */ jsx(
2047
+ DataGridShell,
2048
+ {
2049
+ ...props,
2050
+ wrapperRef,
2051
+ containerRef,
2052
+ table,
2053
+ rows,
2054
+ isSized,
2055
+ measure,
2056
+ error,
2057
+ leftFilters,
2058
+ rightFilters,
2059
+ loadMoreRef,
2060
+ isFetchingNextPage
2061
+ }
2062
+ );
2063
+ }
2064
+ var RowDragContext = React__default.createContext(null);
2065
+ function SortableRow({ row, children }) {
2066
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
2067
+ id: row.id
2068
+ });
2069
+ return /* @__PURE__ */ jsx(RowDragContext.Provider, { value: { listeners, attributes, isDragging }, children: /* @__PURE__ */ jsx(
2070
+ "div",
2071
+ {
2072
+ ref: setNodeRef,
2073
+ style: {
2074
+ transform: CSS.Transform.toString(transform),
2075
+ transition,
2076
+ opacity: isDragging ? 0.4 : 1,
2077
+ position: "relative",
2078
+ zIndex: isDragging ? 1 : 0
2079
+ },
2080
+ children
2081
+ }
2082
+ ) });
2083
+ }
2084
+ function DataGridDrag(props) {
2085
+ const { data = [], onRowReorder, getRowId } = props;
2086
+ const { wrapperRef, containerRef, table, rows, isSized, measure } = useDataGridBase({
2087
+ ...props,
2088
+ // Sorting changes visual order — incompatible with manual row reordering
2089
+ enableSorting: false,
2090
+ // Pagination splits data — reorder applies within the loaded set only
2091
+ enablePagination: false,
2092
+ getRowId
2093
+ });
2094
+ const [activeRowId, setActiveRowId] = useState(null);
2095
+ const sensors = useSensors(
2096
+ useSensor(PointerSensor, {
2097
+ // Require a small move before drag starts — prevents click from triggering drag
2098
+ activationConstraint: { distance: 5 }
2099
+ })
2100
+ );
2101
+ const rowIds = rows.map((r) => r.id);
2102
+ const handleDragStart = ({ active }) => {
2103
+ setActiveRowId(String(active.id));
2104
+ };
2105
+ const handleDragEnd = ({ active, over }) => {
2106
+ setActiveRowId(null);
2107
+ if (!over || active.id === over.id) return;
2108
+ const activeRow2 = rows.find((r) => r.id === String(active.id));
2109
+ const overRow = rows.find((r) => r.id === String(over.id));
2110
+ if (!activeRow2 || !overRow) return;
2111
+ const fromIdx = data.indexOf(activeRow2.original);
2112
+ const toIdx = data.indexOf(overRow.original);
2113
+ if (fromIdx === -1 || toIdx === -1) return;
2114
+ onRowReorder(arrayMove(data, fromIdx, toIdx));
2115
+ };
2116
+ const activeRow = activeRowId ? rows.find((r) => r.id === activeRowId) : null;
2117
+ return /* @__PURE__ */ jsx(RowWrapperContext.Provider, { value: SortableRow, children: /* @__PURE__ */ jsxs(
2118
+ DndContext,
2119
+ {
2120
+ sensors,
2121
+ collisionDetection: closestCenter,
2122
+ onDragStart: handleDragStart,
2123
+ onDragEnd: handleDragEnd,
2124
+ children: [
2125
+ /* @__PURE__ */ jsx(SortableContext, { items: rowIds, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsx(
2126
+ DataGridShell,
2127
+ {
2128
+ ...props,
2129
+ wrapperRef,
2130
+ containerRef,
2131
+ table,
2132
+ rows,
2133
+ isSized,
2134
+ measure,
2135
+ footer: null
2136
+ }
2137
+ ) }),
2138
+ /* @__PURE__ */ jsx(DragOverlay, { children: activeRow && /* @__PURE__ */ jsx(
2139
+ "div",
2140
+ {
2141
+ className: "rounded border border-primary/40 bg-primary/5 shadow-xl ring-1 ring-primary/20",
2142
+ style: { height: props.rowHeight ?? 36 }
2143
+ }
2144
+ ) })
2145
+ ]
2146
+ }
2147
+ ) });
2148
+ }
2149
+ function ColumnVisibilityDropdown({ table }) {
2150
+ return /* @__PURE__ */ jsxs(Popover, { children: [
2151
+ /* @__PURE__ */ jsx(
2152
+ PopoverTrigger,
2153
+ {
2154
+ render: (props) => /* @__PURE__ */ jsxs(Button, { ...props, variant: "outline", size: "sm", className: "gap-1.5", children: [
2155
+ /* @__PURE__ */ jsx(Columns3, { className: "h-4 w-4" }),
2156
+ "Columns"
2157
+ ] })
2158
+ }
2159
+ ),
2160
+ /* @__PURE__ */ jsx(PopoverContent, { align: "end", className: "w-48", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: table.getAllLeafColumns().filter((col) => col.id !== "__select__").map((col) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer text-sm", children: [
2161
+ /* @__PURE__ */ jsx(Checkbox, { checked: col.getIsVisible(), onCheckedChange: col.toggleVisibility }),
2162
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: typeof col.columnDef.header === "string" ? col.columnDef.header : col.id })
2163
+ ] }, col.id)) }) })
2164
+ ] });
2165
+ }
2166
+ function GlobalSearch({
2167
+ table,
2168
+ placeholder = "Search\u2026",
2169
+ className
2170
+ }) {
2171
+ const [value, setValue] = useState(String(table.getState().globalFilter ?? ""));
2172
+ useEffect(() => {
2173
+ const timeout = setTimeout(() => table.setGlobalFilter(value || void 0), 200);
2174
+ return () => clearTimeout(timeout);
2175
+ }, [value, table]);
2176
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
2177
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" }),
2178
+ /* @__PURE__ */ jsx(
2179
+ Input,
2180
+ {
2181
+ value,
2182
+ onChange: (e) => setValue(e.target.value),
2183
+ placeholder,
2184
+ className: "h-8 pl-8 pr-7 text-xs w-52"
2185
+ }
2186
+ ),
2187
+ value && /* @__PURE__ */ jsx(
2188
+ Button,
2189
+ {
2190
+ variant: "ghost",
2191
+ size: "icon-xs",
2192
+ onClick: () => {
2193
+ setValue("");
2194
+ table.setGlobalFilter(void 0);
2195
+ },
2196
+ className: "absolute right-0.5 top-1/2 -translate-y-1/2",
2197
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
2198
+ }
2199
+ )
2200
+ ] });
2201
+ }
2202
+ function SelectFilter({ table, columnId, label }) {
2203
+ const [options, setOptions] = useState(null);
2204
+ const col = table.getColumn(columnId);
2205
+ const value = col?.getFilterValue() ?? "";
2206
+ const handleOpenChange = (open) => {
2207
+ if (open && options === null) {
2208
+ const vals = /* @__PURE__ */ new Set();
2209
+ table.getCoreRowModel().rows.forEach((row) => {
2210
+ const v = row.getValue(columnId);
2211
+ if (v != null) vals.add(String(v));
2212
+ });
2213
+ setOptions(Array.from(vals).sort());
2214
+ }
2215
+ };
2216
+ if (!col) return null;
2217
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: handleOpenChange, children: [
2218
+ /* @__PURE__ */ jsx(
2219
+ PopoverTrigger,
2220
+ {
2221
+ render: (props) => /* @__PURE__ */ jsxs(
2222
+ Button,
2223
+ {
2224
+ ...props,
2225
+ variant: value ? "secondary" : "outline",
2226
+ size: "sm",
2227
+ className: "h-8 gap-1.5 text-xs",
2228
+ children: [
2229
+ value ? /* @__PURE__ */ jsxs(Fragment, { children: [
2230
+ /* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
2231
+ label,
2232
+ ":"
2233
+ ] }),
2234
+ " ",
2235
+ value
2236
+ ] }) : label,
2237
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
2238
+ ]
2239
+ }
2240
+ )
2241
+ }
2242
+ ),
2243
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-44 p-1", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
2244
+ value && /* @__PURE__ */ jsxs(
2245
+ "button",
2246
+ {
2247
+ onClick: () => col.setFilterValue(void 0),
2248
+ className: "flex items-center gap-2 px-2 py-1.5 text-xs rounded-sm hover:bg-muted text-muted-foreground",
2249
+ children: [
2250
+ /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }),
2251
+ " Clear"
2252
+ ]
2253
+ }
2254
+ ),
2255
+ (options ?? []).map((opt) => /* @__PURE__ */ jsxs(
2256
+ "button",
2257
+ {
2258
+ onClick: () => col.setFilterValue(opt === value ? void 0 : opt),
2259
+ className: "flex items-center gap-2 px-2 py-1.5 text-xs rounded-sm hover:bg-muted",
2260
+ children: [
2261
+ /* @__PURE__ */ jsx(Check, { className: cn("h-3 w-3", opt === value ? "opacity-100" : "opacity-0") }),
2262
+ opt
2263
+ ]
2264
+ },
2265
+ opt
2266
+ ))
2267
+ ] }) })
2268
+ ] });
2269
+ }
2270
+ function MultiSelectFilter({
2271
+ table,
2272
+ columnId,
2273
+ label
2274
+ }) {
2275
+ const [options, setOptions] = useState(null);
2276
+ const col = table.getColumn(columnId);
2277
+ const selected = col?.getFilterValue() ?? [];
2278
+ useEffect(() => {
2279
+ const vals = /* @__PURE__ */ new Set();
2280
+ table.getCoreRowModel().rows.forEach((row) => {
2281
+ const v = row.getValue(columnId);
2282
+ if (v != null) vals.add(String(v));
2283
+ });
2284
+ setOptions(Array.from(vals).sort());
2285
+ }, []);
2286
+ const toggle = (val) => {
2287
+ if (!col) return;
2288
+ const next = selected.includes(val) ? selected.filter((v) => v !== val) : [...selected, val];
2289
+ col.setFilterValue(next.length > 0 ? next : void 0);
2290
+ };
2291
+ if (!col) return null;
2292
+ return /* @__PURE__ */ jsxs(Popover, { children: [
2293
+ /* @__PURE__ */ jsx(
2294
+ PopoverTrigger,
2295
+ {
2296
+ render: (props) => /* @__PURE__ */ jsxs(
2297
+ Button,
2298
+ {
2299
+ ...props,
2300
+ variant: selected.length > 0 ? "secondary" : "outline",
2301
+ size: "sm",
2302
+ className: "h-8 gap-1.5 text-xs",
2303
+ children: [
2304
+ selected.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2305
+ /* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
2306
+ label,
2307
+ ":"
2308
+ ] }),
2309
+ " ",
2310
+ selected.length,
2311
+ " selected"
2312
+ ] }) : label,
2313
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
2314
+ ]
2315
+ }
2316
+ )
2317
+ }
2318
+ ),
2319
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-48", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
2320
+ /* @__PURE__ */ jsx("div", { className: "max-h-52 overflow-y-auto flex flex-col gap-0.5", children: (options ?? []).map((opt) => /* @__PURE__ */ jsxs(
2321
+ "label",
2322
+ {
2323
+ className: "flex items-center gap-2 px-1 py-1 cursor-pointer hover:bg-muted rounded-sm text-xs select-none",
2324
+ children: [
2325
+ /* @__PURE__ */ jsx(
2326
+ Checkbox,
2327
+ {
2328
+ checked: selected.includes(opt),
2329
+ onCheckedChange: () => toggle(opt),
2330
+ className: "shrink-0"
2331
+ }
2332
+ ),
2333
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: opt })
2334
+ ]
2335
+ },
2336
+ opt
2337
+ )) }),
2338
+ selected.length > 0 && /* @__PURE__ */ jsxs(
2339
+ Button,
2340
+ {
2341
+ variant: "ghost",
2342
+ size: "sm",
2343
+ className: "h-6 text-xs mt-1",
2344
+ onClick: () => col.setFilterValue(void 0),
2345
+ children: [
2346
+ "Clear (",
2347
+ selected.length,
2348
+ ")"
2349
+ ]
2350
+ }
2351
+ )
2352
+ ] }) })
2353
+ ] });
2354
+ }
2355
+ function TreeCell({ row, indentSize = 16, children }) {
2356
+ const canExpand = row.getCanExpand();
2357
+ const indent = row.depth * indentSize;
2358
+ return /* @__PURE__ */ jsxs("div", { style: { paddingLeft: indent }, className: "flex items-center gap-1 min-w-0", children: [
2359
+ canExpand ? /* @__PURE__ */ jsx(
2360
+ "button",
2361
+ {
2362
+ type: "button",
2363
+ onClick: (e) => {
2364
+ e.stopPropagation();
2365
+ row.toggleExpanded();
2366
+ },
2367
+ className: "flex items-center justify-center w-5 h-5 shrink-0 rounded hover:bg-black/10 dark:hover:bg-white/10 transition-colors",
2368
+ children: row.getIsExpanded() ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-foreground/60" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-foreground/60" })
2369
+ }
2370
+ ) : (
2371
+ // Leaf or non-expandable: spacer keeps content aligned with siblings
2372
+ /* @__PURE__ */ jsx("span", { className: "w-5 h-5 shrink-0" })
2373
+ ),
2374
+ children
2375
+ ] });
2376
+ }
2377
+ function DragHandleCell() {
2378
+ const ctx = useContext(RowDragContext);
2379
+ return /* @__PURE__ */ jsx(
2380
+ "button",
2381
+ {
2382
+ type: "button",
2383
+ ...ctx?.listeners,
2384
+ ...ctx?.attributes,
2385
+ className: "flex items-center justify-center w-full h-full cursor-grab active:cursor-grabbing text-muted-foreground/40 hover:text-muted-foreground transition-colors",
2386
+ style: { touchAction: "none" },
2387
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
2388
+ }
2389
+ );
2390
+ }
2391
+
2392
+ // src/core/engine/DataStore.ts
2393
+ function createDataStore(getRowId) {
2394
+ const map = /* @__PURE__ */ new Map();
2395
+ const orderedIds = [];
2396
+ const listeners = /* @__PURE__ */ new Set();
2397
+ let version = 0;
2398
+ let cachedSnapshot = [];
2399
+ let cachedSnapshotVersion = -1;
2400
+ function notify() {
2401
+ version++;
2402
+ listeners.forEach((fn) => fn());
2403
+ }
2404
+ return {
2405
+ get: (id) => map.get(id),
2406
+ getSnapshot: () => {
2407
+ if (cachedSnapshotVersion !== version) {
2408
+ cachedSnapshot = orderedIds.map((id) => map.get(id));
2409
+ cachedSnapshotVersion = version;
2410
+ }
2411
+ return cachedSnapshot;
2412
+ },
2413
+ getVersion: () => version,
2414
+ subscribe: (listener) => {
2415
+ listeners.add(listener);
2416
+ return () => listeners.delete(listener);
2417
+ },
2418
+ applyTransaction: (tx) => {
2419
+ let changed = false;
2420
+ tx.add?.forEach((item, i) => {
2421
+ const id = getRowId(item, map.size + i);
2422
+ if (!map.has(id)) {
2423
+ map.set(id, item);
2424
+ orderedIds.push(id);
2425
+ changed = true;
2426
+ }
2427
+ });
2428
+ tx.update?.forEach(({ id, data }) => {
2429
+ if (map.has(id)) {
2430
+ map.set(id, { ...map.get(id), ...data });
2431
+ changed = true;
2432
+ }
2433
+ });
2434
+ tx.remove?.forEach((id) => {
2435
+ if (map.delete(id)) {
2436
+ const idx = orderedIds.indexOf(id);
2437
+ if (idx !== -1) orderedIds.splice(idx, 1);
2438
+ changed = true;
2439
+ }
2440
+ });
2441
+ if (changed) notify();
2442
+ }
2443
+ };
2444
+ }
2445
+
2446
+ // src/core/engine/useDataStore.ts
2447
+ function useDataStore(options) {
2448
+ const { getRowId } = options;
2449
+ return useMemo(() => createDataStore(getRowId), []);
2450
+ }
2451
+
2452
+ export { ColumnVisibilityDropdown, DataGrid, DataGridDrag, DataGridInfinity, DragHandleCell, GlobalSearch, MultiSelectFilter, SelectFilter, TreeCell, createDataStore, useDataStore };
2453
+ //# sourceMappingURL=index.js.map
2454
+ //# sourceMappingURL=index.js.map