@smallwebco/tinypivot-react 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.js ADDED
@@ -0,0 +1,2755 @@
1
+ // src/components/DataGrid.tsx
2
+ import { useState as useState8, useMemo as useMemo8, useCallback as useCallback8, useEffect as useEffect3, useRef as useRef2 } from "react";
3
+ import { createPortal } from "react-dom";
4
+
5
+ // src/hooks/useExcelGrid.ts
6
+ import { useState, useMemo, useCallback } from "react";
7
+ import {
8
+ useReactTable,
9
+ getCoreRowModel,
10
+ getFilteredRowModel,
11
+ getSortedRowModel
12
+ } from "@tanstack/react-table";
13
+ import { getColumnUniqueValues, formatCellValue } from "@smallwebco/tinypivot-core";
14
+ var multiSelectFilter = (row, columnId, filterValue) => {
15
+ if (!filterValue || !Array.isArray(filterValue) || filterValue.length === 0) {
16
+ return true;
17
+ }
18
+ const cellValue = row.getValue(columnId);
19
+ const cellString = cellValue === null || cellValue === void 0 || cellValue === "" ? "(blank)" : String(cellValue);
20
+ return filterValue.includes(cellString);
21
+ };
22
+ function useExcelGrid(options) {
23
+ const { data, enableSorting = true, enableFiltering = true } = options;
24
+ const [sorting, setSorting] = useState([]);
25
+ const [columnFilters, setColumnFilters] = useState([]);
26
+ const [columnVisibility, setColumnVisibility] = useState({});
27
+ const [globalFilter, setGlobalFilter] = useState("");
28
+ const [columnStatsCache, setColumnStatsCache] = useState({});
29
+ const columnKeys = useMemo(() => {
30
+ if (data.length === 0) return [];
31
+ return Object.keys(data[0]);
32
+ }, [data]);
33
+ const getColumnStats = useCallback(
34
+ (columnKey) => {
35
+ const cacheKey = `${columnKey}-${data.length}`;
36
+ if (!columnStatsCache[cacheKey]) {
37
+ const stats = getColumnUniqueValues(data, columnKey);
38
+ setColumnStatsCache((prev) => ({ ...prev, [cacheKey]: stats }));
39
+ return stats;
40
+ }
41
+ return columnStatsCache[cacheKey];
42
+ },
43
+ [data, columnStatsCache]
44
+ );
45
+ const clearStatsCache = useCallback(() => {
46
+ setColumnStatsCache({});
47
+ }, []);
48
+ const columnDefs = useMemo(() => {
49
+ return columnKeys.map((key) => {
50
+ const stats = getColumnStats(key);
51
+ return {
52
+ id: key,
53
+ accessorKey: key,
54
+ header: key,
55
+ cell: (info) => formatCellValue(info.getValue(), stats.type),
56
+ filterFn: multiSelectFilter,
57
+ meta: {
58
+ type: stats.type,
59
+ uniqueCount: stats.uniqueValues.length
60
+ }
61
+ };
62
+ });
63
+ }, [columnKeys, getColumnStats]);
64
+ const table = useReactTable({
65
+ data,
66
+ columns: columnDefs,
67
+ state: {
68
+ sorting,
69
+ columnFilters,
70
+ columnVisibility,
71
+ globalFilter
72
+ },
73
+ onSortingChange: setSorting,
74
+ onColumnFiltersChange: setColumnFilters,
75
+ onColumnVisibilityChange: setColumnVisibility,
76
+ onGlobalFilterChange: setGlobalFilter,
77
+ getCoreRowModel: getCoreRowModel(),
78
+ getSortedRowModel: enableSorting ? getSortedRowModel() : void 0,
79
+ getFilteredRowModel: enableFiltering ? getFilteredRowModel() : void 0,
80
+ filterFns: {
81
+ multiSelect: multiSelectFilter
82
+ },
83
+ enableSorting,
84
+ enableFilters: enableFiltering
85
+ });
86
+ const filteredRowCount = table.getFilteredRowModel().rows.length;
87
+ const totalRowCount = data.length;
88
+ const activeFilters = useMemo(() => {
89
+ return columnFilters.map((f) => ({
90
+ column: f.id,
91
+ values: f.value
92
+ }));
93
+ }, [columnFilters]);
94
+ const hasActiveFilter = useCallback(
95
+ (columnId) => {
96
+ const column = table.getColumn(columnId);
97
+ if (!column) return false;
98
+ const filterValue = column.getFilterValue();
99
+ return filterValue !== void 0 && Array.isArray(filterValue) && filterValue.length > 0;
100
+ },
101
+ [table]
102
+ );
103
+ const setColumnFilter = useCallback(
104
+ (columnId, values) => {
105
+ const column = table.getColumn(columnId);
106
+ if (column) {
107
+ column.setFilterValue(values.length === 0 ? void 0 : values);
108
+ }
109
+ },
110
+ [table]
111
+ );
112
+ const clearAllFilters = useCallback(() => {
113
+ table.resetColumnFilters();
114
+ setGlobalFilter("");
115
+ }, [table]);
116
+ const getColumnFilterValues = useCallback(
117
+ (columnId) => {
118
+ const column = table.getColumn(columnId);
119
+ if (!column) return [];
120
+ const filterValue = column.getFilterValue();
121
+ return Array.isArray(filterValue) ? filterValue : [];
122
+ },
123
+ [table]
124
+ );
125
+ const toggleSort = useCallback((columnId) => {
126
+ setSorting((prev) => {
127
+ const current = prev.find((s) => s.id === columnId);
128
+ if (!current) {
129
+ return [{ id: columnId, desc: false }];
130
+ } else if (!current.desc) {
131
+ return [{ id: columnId, desc: true }];
132
+ } else {
133
+ return [];
134
+ }
135
+ });
136
+ }, []);
137
+ const getSortDirection = useCallback(
138
+ (columnId) => {
139
+ const sort = sorting.find((s) => s.id === columnId);
140
+ if (!sort) return null;
141
+ return sort.desc ? "desc" : "asc";
142
+ },
143
+ [sorting]
144
+ );
145
+ return {
146
+ // Table instance
147
+ table,
148
+ // State
149
+ sorting,
150
+ columnFilters,
151
+ columnVisibility,
152
+ globalFilter,
153
+ columnKeys,
154
+ setSorting,
155
+ setColumnFilters,
156
+ setGlobalFilter,
157
+ // Computed
158
+ filteredRowCount,
159
+ totalRowCount,
160
+ activeFilters,
161
+ // Methods
162
+ getColumnStats,
163
+ clearStatsCache,
164
+ hasActiveFilter,
165
+ setColumnFilter,
166
+ getColumnFilterValues,
167
+ clearAllFilters,
168
+ toggleSort,
169
+ getSortDirection
170
+ };
171
+ }
172
+
173
+ // src/hooks/usePivotTable.ts
174
+ import { useState as useState3, useMemo as useMemo3, useEffect, useCallback as useCallback3 } from "react";
175
+ import {
176
+ computeAvailableFields,
177
+ getUnassignedFields,
178
+ isPivotConfigured,
179
+ computePivotResult,
180
+ generateStorageKey,
181
+ savePivotConfig,
182
+ loadPivotConfig,
183
+ isConfigValidForFields,
184
+ getAggregationLabel
185
+ } from "@smallwebco/tinypivot-core";
186
+
187
+ // src/hooks/useLicense.ts
188
+ import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
189
+ import {
190
+ validateLicenseKey,
191
+ configureLicenseSecret as coreConfigureLicenseSecret,
192
+ getDemoLicenseInfo,
193
+ getFreeLicenseInfo,
194
+ canUsePivot as coreCanUsePivot,
195
+ isPro as coreIsPro,
196
+ shouldShowWatermark as coreShouldShowWatermark,
197
+ logProRequired
198
+ } from "@smallwebco/tinypivot-core";
199
+ var globalLicenseInfo = getFreeLicenseInfo();
200
+ var globalDemoMode = false;
201
+ var listeners = /* @__PURE__ */ new Set();
202
+ function notifyListeners() {
203
+ listeners.forEach((listener) => listener());
204
+ }
205
+ async function setLicenseKey(key) {
206
+ globalLicenseInfo = await validateLicenseKey(key);
207
+ if (!globalLicenseInfo.isValid) {
208
+ console.warn("[TinyPivot] Invalid or expired license key. Running in free mode.");
209
+ } else if (globalLicenseInfo.type !== "free") {
210
+ console.info(`[TinyPivot] Pro license activated (${globalLicenseInfo.type})`);
211
+ }
212
+ notifyListeners();
213
+ }
214
+ function enableDemoMode() {
215
+ globalDemoMode = true;
216
+ globalLicenseInfo = getDemoLicenseInfo();
217
+ console.info("[TinyPivot] Demo mode enabled - all Pro features unlocked for evaluation");
218
+ notifyListeners();
219
+ }
220
+ function configureLicenseSecret(secret) {
221
+ coreConfigureLicenseSecret(secret);
222
+ }
223
+ function useLicense() {
224
+ const [, forceUpdate] = useState2({});
225
+ useState2(() => {
226
+ const update = () => forceUpdate({});
227
+ listeners.add(update);
228
+ return () => listeners.delete(update);
229
+ });
230
+ const isDemo = globalDemoMode;
231
+ const licenseInfo = globalLicenseInfo;
232
+ const isPro = useMemo2(
233
+ () => globalDemoMode || coreIsPro(licenseInfo),
234
+ [licenseInfo]
235
+ );
236
+ const canUsePivot = useMemo2(
237
+ () => globalDemoMode || coreCanUsePivot(licenseInfo),
238
+ [licenseInfo]
239
+ );
240
+ const canUseAdvancedAggregations = useMemo2(
241
+ () => globalDemoMode || licenseInfo.features.advancedAggregations,
242
+ [licenseInfo]
243
+ );
244
+ const canUsePercentageMode = useMemo2(
245
+ () => globalDemoMode || licenseInfo.features.percentageMode,
246
+ [licenseInfo]
247
+ );
248
+ const showWatermark = useMemo2(
249
+ () => coreShouldShowWatermark(licenseInfo, globalDemoMode),
250
+ [licenseInfo]
251
+ );
252
+ const requirePro = useCallback2((feature) => {
253
+ if (!isPro) {
254
+ logProRequired(feature);
255
+ return false;
256
+ }
257
+ return true;
258
+ }, [isPro]);
259
+ return {
260
+ licenseInfo,
261
+ isDemo,
262
+ isPro,
263
+ canUsePivot,
264
+ canUseAdvancedAggregations,
265
+ canUsePercentageMode,
266
+ showWatermark,
267
+ requirePro
268
+ };
269
+ }
270
+
271
+ // src/hooks/usePivotTable.ts
272
+ function usePivotTable(data) {
273
+ const { canUsePivot, requirePro } = useLicense();
274
+ const [rowFields, setRowFieldsState] = useState3([]);
275
+ const [columnFields, setColumnFieldsState] = useState3([]);
276
+ const [valueFields, setValueFields] = useState3([]);
277
+ const [showRowTotals, setShowRowTotals] = useState3(true);
278
+ const [showColumnTotals, setShowColumnTotals] = useState3(true);
279
+ const [currentStorageKey, setCurrentStorageKey] = useState3(null);
280
+ const availableFields = useMemo3(() => {
281
+ return computeAvailableFields(data);
282
+ }, [data]);
283
+ const unassignedFields = useMemo3(() => {
284
+ return getUnassignedFields(availableFields, rowFields, columnFields, valueFields);
285
+ }, [availableFields, rowFields, columnFields, valueFields]);
286
+ const isConfigured = useMemo3(() => {
287
+ return isPivotConfigured({
288
+ rowFields,
289
+ columnFields,
290
+ valueFields,
291
+ showRowTotals,
292
+ showColumnTotals
293
+ });
294
+ }, [rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
295
+ const pivotResult = useMemo3(() => {
296
+ if (!isConfigured) return null;
297
+ if (!canUsePivot) return null;
298
+ return computePivotResult(data, {
299
+ rowFields,
300
+ columnFields,
301
+ valueFields,
302
+ showRowTotals,
303
+ showColumnTotals
304
+ });
305
+ }, [data, isConfigured, canUsePivot, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
306
+ useEffect(() => {
307
+ if (data.length === 0) return;
308
+ const newKeys = Object.keys(data[0]);
309
+ const storageKey = generateStorageKey(newKeys);
310
+ if (storageKey !== currentStorageKey) {
311
+ setCurrentStorageKey(storageKey);
312
+ const savedConfig = loadPivotConfig(storageKey);
313
+ if (savedConfig && isConfigValidForFields(savedConfig, newKeys)) {
314
+ setRowFieldsState(savedConfig.rowFields);
315
+ setColumnFieldsState(savedConfig.columnFields);
316
+ setValueFields(savedConfig.valueFields);
317
+ setShowRowTotals(savedConfig.showRowTotals);
318
+ setShowColumnTotals(savedConfig.showColumnTotals);
319
+ } else {
320
+ const currentConfig = {
321
+ rowFields,
322
+ columnFields,
323
+ valueFields,
324
+ showRowTotals,
325
+ showColumnTotals
326
+ };
327
+ if (!isConfigValidForFields(currentConfig, newKeys)) {
328
+ setRowFieldsState([]);
329
+ setColumnFieldsState([]);
330
+ setValueFields([]);
331
+ }
332
+ }
333
+ }
334
+ }, [data]);
335
+ useEffect(() => {
336
+ if (!currentStorageKey) return;
337
+ const config = {
338
+ rowFields,
339
+ columnFields,
340
+ valueFields,
341
+ showRowTotals,
342
+ showColumnTotals
343
+ };
344
+ savePivotConfig(currentStorageKey, config);
345
+ }, [currentStorageKey, rowFields, columnFields, valueFields, showRowTotals, showColumnTotals]);
346
+ const addRowField = useCallback3(
347
+ (field) => {
348
+ if (!requirePro("Pivot Table - Row Fields")) return;
349
+ if (!rowFields.includes(field)) {
350
+ setRowFieldsState((prev) => [...prev, field]);
351
+ }
352
+ },
353
+ [rowFields, requirePro]
354
+ );
355
+ const removeRowField = useCallback3((field) => {
356
+ setRowFieldsState((prev) => prev.filter((f) => f !== field));
357
+ }, []);
358
+ const setRowFields = useCallback3((fields) => {
359
+ setRowFieldsState(fields);
360
+ }, []);
361
+ const addColumnField = useCallback3(
362
+ (field) => {
363
+ if (!requirePro("Pivot Table - Column Fields")) return;
364
+ if (!columnFields.includes(field)) {
365
+ setColumnFieldsState((prev) => [...prev, field]);
366
+ }
367
+ },
368
+ [columnFields, requirePro]
369
+ );
370
+ const removeColumnField = useCallback3((field) => {
371
+ setColumnFieldsState((prev) => prev.filter((f) => f !== field));
372
+ }, []);
373
+ const setColumnFields = useCallback3((fields) => {
374
+ setColumnFieldsState(fields);
375
+ }, []);
376
+ const addValueField = useCallback3(
377
+ (field, aggregation = "sum") => {
378
+ if (!requirePro("Pivot Table - Value Fields")) return;
379
+ setValueFields((prev) => {
380
+ if (prev.some((v) => v.field === field && v.aggregation === aggregation)) {
381
+ return prev;
382
+ }
383
+ return [...prev, { field, aggregation }];
384
+ });
385
+ },
386
+ [requirePro]
387
+ );
388
+ const removeValueField = useCallback3((field, aggregation) => {
389
+ setValueFields((prev) => {
390
+ if (aggregation) {
391
+ return prev.filter((v) => !(v.field === field && v.aggregation === aggregation));
392
+ }
393
+ return prev.filter((v) => v.field !== field);
394
+ });
395
+ }, []);
396
+ const updateValueFieldAggregation = useCallback3(
397
+ (field, oldAgg, newAgg) => {
398
+ setValueFields(
399
+ (prev) => prev.map((v) => {
400
+ if (v.field === field && v.aggregation === oldAgg) {
401
+ return { ...v, aggregation: newAgg };
402
+ }
403
+ return v;
404
+ })
405
+ );
406
+ },
407
+ []
408
+ );
409
+ const clearConfig = useCallback3(() => {
410
+ setRowFieldsState([]);
411
+ setColumnFieldsState([]);
412
+ setValueFields([]);
413
+ }, []);
414
+ const autoSuggestConfig = useCallback3(() => {
415
+ if (!requirePro("Pivot Table - Auto Suggest")) return;
416
+ if (availableFields.length === 0) return;
417
+ const categoricalFields = availableFields.filter((f) => !f.isNumeric && f.uniqueCount < 50);
418
+ const numericFields = availableFields.filter((f) => f.isNumeric);
419
+ if (categoricalFields.length > 0 && numericFields.length > 0) {
420
+ setRowFieldsState([categoricalFields[0].field]);
421
+ setValueFields([{ field: numericFields[0].field, aggregation: "sum" }]);
422
+ }
423
+ }, [availableFields, requirePro]);
424
+ return {
425
+ // State
426
+ rowFields,
427
+ columnFields,
428
+ valueFields,
429
+ showRowTotals,
430
+ showColumnTotals,
431
+ // Computed
432
+ availableFields,
433
+ unassignedFields,
434
+ isConfigured,
435
+ pivotResult,
436
+ // Actions
437
+ addRowField,
438
+ removeRowField,
439
+ addColumnField,
440
+ removeColumnField,
441
+ addValueField,
442
+ removeValueField,
443
+ updateValueFieldAggregation,
444
+ clearConfig,
445
+ setShowRowTotals,
446
+ setShowColumnTotals,
447
+ autoSuggestConfig,
448
+ setRowFields,
449
+ setColumnFields
450
+ };
451
+ }
452
+
453
+ // src/hooks/useGridFeatures.ts
454
+ import { useState as useState4, useMemo as useMemo4, useCallback as useCallback4 } from "react";
455
+ import {
456
+ exportToCSV as coreExportToCSV,
457
+ exportPivotToCSV as coreExportPivotToCSV,
458
+ copyToClipboard as coreCopyToClipboard,
459
+ formatSelectionForClipboard as coreFormatSelection
460
+ } from "@smallwebco/tinypivot-core";
461
+ function exportToCSV(data, columns, options) {
462
+ coreExportToCSV(data, columns, options);
463
+ }
464
+ function exportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options) {
465
+ coreExportPivotToCSV(pivotData, rowFields, columnFields, valueFields, options);
466
+ }
467
+ function copyToClipboard(text, onSuccess, onError) {
468
+ coreCopyToClipboard(text, onSuccess, onError);
469
+ }
470
+ function formatSelectionForClipboard(rows, columns, selectionBounds) {
471
+ return coreFormatSelection(rows, columns, selectionBounds);
472
+ }
473
+ function usePagination(data, options = {}) {
474
+ const [pageSize, setPageSize] = useState4(options.pageSize ?? 50);
475
+ const [currentPage, setCurrentPage] = useState4(options.currentPage ?? 1);
476
+ const totalPages = useMemo4(
477
+ () => Math.max(1, Math.ceil(data.length / pageSize)),
478
+ [data.length, pageSize]
479
+ );
480
+ const paginatedData = useMemo4(() => {
481
+ const start = (currentPage - 1) * pageSize;
482
+ const end = start + pageSize;
483
+ return data.slice(start, end);
484
+ }, [data, currentPage, pageSize]);
485
+ const startIndex = useMemo4(() => (currentPage - 1) * pageSize + 1, [currentPage, pageSize]);
486
+ const endIndex = useMemo4(
487
+ () => Math.min(currentPage * pageSize, data.length),
488
+ [currentPage, pageSize, data.length]
489
+ );
490
+ const goToPage = useCallback4(
491
+ (page) => {
492
+ setCurrentPage(Math.max(1, Math.min(page, totalPages)));
493
+ },
494
+ [totalPages]
495
+ );
496
+ const nextPage = useCallback4(() => {
497
+ if (currentPage < totalPages) {
498
+ setCurrentPage((prev) => prev + 1);
499
+ }
500
+ }, [currentPage, totalPages]);
501
+ const prevPage = useCallback4(() => {
502
+ if (currentPage > 1) {
503
+ setCurrentPage((prev) => prev - 1);
504
+ }
505
+ }, [currentPage]);
506
+ const firstPage = useCallback4(() => {
507
+ setCurrentPage(1);
508
+ }, []);
509
+ const lastPage = useCallback4(() => {
510
+ setCurrentPage(totalPages);
511
+ }, [totalPages]);
512
+ const updatePageSize = useCallback4((size) => {
513
+ setPageSize(size);
514
+ setCurrentPage(1);
515
+ }, []);
516
+ return {
517
+ pageSize,
518
+ currentPage,
519
+ totalPages,
520
+ paginatedData,
521
+ startIndex,
522
+ endIndex,
523
+ goToPage,
524
+ nextPage,
525
+ prevPage,
526
+ firstPage,
527
+ lastPage,
528
+ setPageSize: updatePageSize
529
+ };
530
+ }
531
+ function useGlobalSearch(data, columns) {
532
+ const [searchTerm, setSearchTerm] = useState4("");
533
+ const [caseSensitive, setCaseSensitive] = useState4(false);
534
+ const filteredData = useMemo4(() => {
535
+ if (!searchTerm.trim()) {
536
+ return data;
537
+ }
538
+ const term = caseSensitive ? searchTerm.trim() : searchTerm.trim().toLowerCase();
539
+ return data.filter((row) => {
540
+ for (const col of columns) {
541
+ const value = row[col];
542
+ if (value === null || value === void 0) continue;
543
+ const strValue = caseSensitive ? String(value) : String(value).toLowerCase();
544
+ if (strValue.includes(term)) {
545
+ return true;
546
+ }
547
+ }
548
+ return false;
549
+ });
550
+ }, [data, columns, searchTerm, caseSensitive]);
551
+ const clearSearch = useCallback4(() => {
552
+ setSearchTerm("");
553
+ }, []);
554
+ return {
555
+ searchTerm,
556
+ setSearchTerm,
557
+ caseSensitive,
558
+ setCaseSensitive,
559
+ filteredData,
560
+ clearSearch
561
+ };
562
+ }
563
+ function useRowSelection(data) {
564
+ const [selectedRowIndices, setSelectedRowIndices] = useState4(/* @__PURE__ */ new Set());
565
+ const selectedRows = useMemo4(() => {
566
+ return Array.from(selectedRowIndices).sort((a, b) => a - b).map((idx) => data[idx]).filter(Boolean);
567
+ }, [data, selectedRowIndices]);
568
+ const allSelected = useMemo4(() => {
569
+ return data.length > 0 && selectedRowIndices.size === data.length;
570
+ }, [data.length, selectedRowIndices.size]);
571
+ const someSelected = useMemo4(() => {
572
+ return selectedRowIndices.size > 0 && selectedRowIndices.size < data.length;
573
+ }, [data.length, selectedRowIndices.size]);
574
+ const toggleRow = useCallback4((index) => {
575
+ setSelectedRowIndices((prev) => {
576
+ const next = new Set(prev);
577
+ if (next.has(index)) {
578
+ next.delete(index);
579
+ } else {
580
+ next.add(index);
581
+ }
582
+ return next;
583
+ });
584
+ }, []);
585
+ const selectRow = useCallback4((index) => {
586
+ setSelectedRowIndices((prev) => /* @__PURE__ */ new Set([...prev, index]));
587
+ }, []);
588
+ const deselectRow = useCallback4((index) => {
589
+ setSelectedRowIndices((prev) => {
590
+ const next = new Set(prev);
591
+ next.delete(index);
592
+ return next;
593
+ });
594
+ }, []);
595
+ const selectAll = useCallback4(() => {
596
+ setSelectedRowIndices(new Set(data.map((_, idx) => idx)));
597
+ }, [data]);
598
+ const deselectAll = useCallback4(() => {
599
+ setSelectedRowIndices(/* @__PURE__ */ new Set());
600
+ }, []);
601
+ const toggleAll = useCallback4(() => {
602
+ if (allSelected) {
603
+ deselectAll();
604
+ } else {
605
+ selectAll();
606
+ }
607
+ }, [allSelected, selectAll, deselectAll]);
608
+ const isSelected = useCallback4(
609
+ (index) => {
610
+ return selectedRowIndices.has(index);
611
+ },
612
+ [selectedRowIndices]
613
+ );
614
+ const selectRange = useCallback4((startIndex, endIndex) => {
615
+ const min = Math.min(startIndex, endIndex);
616
+ const max = Math.max(startIndex, endIndex);
617
+ setSelectedRowIndices((prev) => {
618
+ const next = new Set(prev);
619
+ for (let i = min; i <= max; i++) {
620
+ next.add(i);
621
+ }
622
+ return next;
623
+ });
624
+ }, []);
625
+ return {
626
+ selectedRowIndices,
627
+ selectedRows,
628
+ allSelected,
629
+ someSelected,
630
+ toggleRow,
631
+ selectRow,
632
+ deselectRow,
633
+ selectAll,
634
+ deselectAll,
635
+ toggleAll,
636
+ isSelected,
637
+ selectRange
638
+ };
639
+ }
640
+ function useColumnResize(initialWidths, minWidth = 60, maxWidth = 600) {
641
+ const [columnWidths, setColumnWidths] = useState4({ ...initialWidths });
642
+ const [isResizing, setIsResizing] = useState4(false);
643
+ const [resizingColumn, setResizingColumn] = useState4(null);
644
+ const startResize = useCallback4(
645
+ (columnId, event) => {
646
+ setIsResizing(true);
647
+ setResizingColumn(columnId);
648
+ const startX = event.clientX;
649
+ const startWidth = columnWidths[columnId] || 150;
650
+ const handleMouseMove = (e) => {
651
+ const diff = e.clientX - startX;
652
+ const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + diff));
653
+ setColumnWidths((prev) => ({
654
+ ...prev,
655
+ [columnId]: newWidth
656
+ }));
657
+ };
658
+ const handleMouseUp = () => {
659
+ setIsResizing(false);
660
+ setResizingColumn(null);
661
+ document.removeEventListener("mousemove", handleMouseMove);
662
+ document.removeEventListener("mouseup", handleMouseUp);
663
+ };
664
+ document.addEventListener("mousemove", handleMouseMove);
665
+ document.addEventListener("mouseup", handleMouseUp);
666
+ },
667
+ [columnWidths, minWidth, maxWidth]
668
+ );
669
+ const resetColumnWidth = useCallback4(
670
+ (columnId) => {
671
+ if (initialWidths[columnId]) {
672
+ setColumnWidths((prev) => ({
673
+ ...prev,
674
+ [columnId]: initialWidths[columnId]
675
+ }));
676
+ }
677
+ },
678
+ [initialWidths]
679
+ );
680
+ const resetAllWidths = useCallback4(() => {
681
+ setColumnWidths({ ...initialWidths });
682
+ }, [initialWidths]);
683
+ return {
684
+ columnWidths,
685
+ setColumnWidths,
686
+ isResizing,
687
+ resizingColumn,
688
+ startResize,
689
+ resetColumnWidth,
690
+ resetAllWidths
691
+ };
692
+ }
693
+
694
+ // src/components/ColumnFilter.tsx
695
+ import { useState as useState5, useEffect as useEffect2, useRef, useCallback as useCallback5, useMemo as useMemo5 } from "react";
696
+ import { jsx, jsxs } from "react/jsx-runtime";
697
+ function ColumnFilter({
698
+ columnName,
699
+ stats,
700
+ selectedValues,
701
+ sortDirection,
702
+ onFilter,
703
+ onSort,
704
+ onClose
705
+ }) {
706
+ const [searchQuery, setSearchQuery] = useState5("");
707
+ const [localSelected, setLocalSelected] = useState5(new Set(selectedValues));
708
+ const dropdownRef = useRef(null);
709
+ const searchInputRef = useRef(null);
710
+ const hasBlankValues = stats.nullCount > 0;
711
+ const filteredValues = useMemo5(() => {
712
+ const values = stats.uniqueValues;
713
+ if (!searchQuery) return values;
714
+ const query = searchQuery.toLowerCase();
715
+ return values.filter((v) => v.toLowerCase().includes(query));
716
+ }, [stats.uniqueValues, searchQuery]);
717
+ const allValues = useMemo5(() => {
718
+ const values = [...filteredValues];
719
+ if (hasBlankValues && (!searchQuery || "(blank)".includes(searchQuery.toLowerCase()))) {
720
+ values.unshift("(blank)");
721
+ }
722
+ return values;
723
+ }, [filteredValues, hasBlankValues, searchQuery]);
724
+ const isAllSelected = useMemo5(
725
+ () => allValues.every((v) => localSelected.has(v)),
726
+ [allValues, localSelected]
727
+ );
728
+ const toggleValue = useCallback5((value) => {
729
+ setLocalSelected((prev) => {
730
+ const next = new Set(prev);
731
+ if (next.has(value)) {
732
+ next.delete(value);
733
+ } else {
734
+ next.add(value);
735
+ }
736
+ return next;
737
+ });
738
+ }, []);
739
+ const selectAll = useCallback5(() => {
740
+ setLocalSelected((prev) => {
741
+ const next = new Set(prev);
742
+ for (const value of allValues) {
743
+ next.add(value);
744
+ }
745
+ return next;
746
+ });
747
+ }, [allValues]);
748
+ const clearAll = useCallback5(() => {
749
+ setLocalSelected(/* @__PURE__ */ new Set());
750
+ }, []);
751
+ const applyFilter = useCallback5(() => {
752
+ if (localSelected.size === 0) {
753
+ onFilter([]);
754
+ } else {
755
+ onFilter(Array.from(localSelected));
756
+ }
757
+ onClose();
758
+ }, [localSelected, onFilter, onClose]);
759
+ const sortAscending = useCallback5(() => {
760
+ onSort(sortDirection === "asc" ? null : "asc");
761
+ }, [sortDirection, onSort]);
762
+ const sortDescending = useCallback5(() => {
763
+ onSort(sortDirection === "desc" ? null : "desc");
764
+ }, [sortDirection, onSort]);
765
+ const clearFilter = useCallback5(() => {
766
+ setLocalSelected(/* @__PURE__ */ new Set());
767
+ onFilter([]);
768
+ onClose();
769
+ }, [onFilter, onClose]);
770
+ useEffect2(() => {
771
+ const handleClickOutside = (event) => {
772
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
773
+ onClose();
774
+ }
775
+ };
776
+ document.addEventListener("mousedown", handleClickOutside);
777
+ return () => document.removeEventListener("mousedown", handleClickOutside);
778
+ }, [onClose]);
779
+ useEffect2(() => {
780
+ const handleKeydown = (event) => {
781
+ if (event.key === "Escape") {
782
+ onClose();
783
+ } else if (event.key === "Enter" && event.ctrlKey) {
784
+ applyFilter();
785
+ }
786
+ };
787
+ document.addEventListener("keydown", handleKeydown);
788
+ return () => document.removeEventListener("keydown", handleKeydown);
789
+ }, [onClose, applyFilter]);
790
+ useEffect2(() => {
791
+ searchInputRef.current?.focus();
792
+ }, []);
793
+ useEffect2(() => {
794
+ setLocalSelected(new Set(selectedValues));
795
+ }, [selectedValues]);
796
+ return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "vpg-filter-dropdown", children: [
797
+ /* @__PURE__ */ jsxs("div", { className: "vpg-filter-header", children: [
798
+ /* @__PURE__ */ jsx("span", { className: "vpg-filter-title", children: columnName }),
799
+ /* @__PURE__ */ jsxs("span", { className: "vpg-filter-count", children: [
800
+ stats.uniqueValues.length.toLocaleString(),
801
+ " unique"
802
+ ] })
803
+ ] }),
804
+ /* @__PURE__ */ jsxs("div", { className: "vpg-sort-controls", children: [
805
+ /* @__PURE__ */ jsxs(
806
+ "button",
807
+ {
808
+ className: `vpg-sort-btn ${sortDirection === "asc" ? "active" : ""}`,
809
+ title: "Sort A to Z",
810
+ onClick: sortAscending,
811
+ children: [
812
+ /* @__PURE__ */ jsx("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
813
+ "path",
814
+ {
815
+ strokeLinecap: "round",
816
+ strokeLinejoin: "round",
817
+ strokeWidth: 2,
818
+ d: "M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12"
819
+ }
820
+ ) }),
821
+ /* @__PURE__ */ jsx("span", { children: "A\u2192Z" })
822
+ ]
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsxs(
826
+ "button",
827
+ {
828
+ className: `vpg-sort-btn ${sortDirection === "desc" ? "active" : ""}`,
829
+ title: "Sort Z to A",
830
+ onClick: sortDescending,
831
+ children: [
832
+ /* @__PURE__ */ jsx("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
833
+ "path",
834
+ {
835
+ strokeLinecap: "round",
836
+ strokeLinejoin: "round",
837
+ strokeWidth: 2,
838
+ d: "M3 4h13M3 8h9m-9 4h9m5-4v12m0 0l-4-4m4 4l4-4"
839
+ }
840
+ ) }),
841
+ /* @__PURE__ */ jsx("span", { children: "Z\u2192A" })
842
+ ]
843
+ }
844
+ )
845
+ ] }),
846
+ /* @__PURE__ */ jsx("div", { className: "vpg-divider" }),
847
+ /* @__PURE__ */ jsxs("div", { className: "vpg-search-container", children: [
848
+ /* @__PURE__ */ jsx("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
849
+ "path",
850
+ {
851
+ strokeLinecap: "round",
852
+ strokeLinejoin: "round",
853
+ strokeWidth: 2,
854
+ d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
855
+ }
856
+ ) }),
857
+ /* @__PURE__ */ jsx(
858
+ "input",
859
+ {
860
+ ref: searchInputRef,
861
+ type: "text",
862
+ value: searchQuery,
863
+ onChange: (e) => setSearchQuery(e.target.value),
864
+ placeholder: "Search values...",
865
+ className: "vpg-search-input"
866
+ }
867
+ ),
868
+ searchQuery && /* @__PURE__ */ jsx("button", { className: "vpg-clear-search", onClick: () => setSearchQuery(""), children: "\xD7" })
869
+ ] }),
870
+ /* @__PURE__ */ jsxs("div", { className: "vpg-bulk-actions", children: [
871
+ /* @__PURE__ */ jsxs("button", { className: "vpg-bulk-btn", onClick: selectAll, children: [
872
+ /* @__PURE__ */ jsx("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
873
+ "path",
874
+ {
875
+ strokeLinecap: "round",
876
+ strokeLinejoin: "round",
877
+ strokeWidth: 2,
878
+ d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
879
+ }
880
+ ) }),
881
+ "Select All"
882
+ ] }),
883
+ /* @__PURE__ */ jsxs("button", { className: "vpg-bulk-btn", onClick: clearAll, children: [
884
+ /* @__PURE__ */ jsx("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx(
885
+ "path",
886
+ {
887
+ strokeLinecap: "round",
888
+ strokeLinejoin: "round",
889
+ strokeWidth: 2,
890
+ d: "M6 18L18 6M6 6l12 12"
891
+ }
892
+ ) }),
893
+ "Clear All"
894
+ ] })
895
+ ] }),
896
+ /* @__PURE__ */ jsxs("div", { className: "vpg-values-list", children: [
897
+ allValues.map((value) => /* @__PURE__ */ jsxs(
898
+ "label",
899
+ {
900
+ className: `vpg-value-item ${localSelected.has(value) ? "selected" : ""}`,
901
+ children: [
902
+ /* @__PURE__ */ jsx(
903
+ "input",
904
+ {
905
+ type: "checkbox",
906
+ checked: localSelected.has(value),
907
+ onChange: () => toggleValue(value),
908
+ className: "vpg-value-checkbox"
909
+ }
910
+ ),
911
+ /* @__PURE__ */ jsx("span", { className: `vpg-value-text ${value === "(blank)" ? "vpg-blank" : ""}`, children: value })
912
+ ]
913
+ },
914
+ value
915
+ )),
916
+ allValues.length === 0 && /* @__PURE__ */ jsx("div", { className: "vpg-no-results", children: "No matching values" })
917
+ ] }),
918
+ /* @__PURE__ */ jsxs("div", { className: "vpg-filter-footer", children: [
919
+ /* @__PURE__ */ jsx("button", { className: "vpg-btn-clear", onClick: clearFilter, children: "Clear Filter" }),
920
+ /* @__PURE__ */ jsx("button", { className: "vpg-btn-apply", onClick: applyFilter, children: "Apply" })
921
+ ] })
922
+ ] });
923
+ }
924
+
925
+ // src/components/PivotConfig.tsx
926
+ import { useState as useState6, useMemo as useMemo6, useCallback as useCallback6 } from "react";
927
+ import { AGGREGATION_OPTIONS, getAggregationSymbol } from "@smallwebco/tinypivot-core";
928
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
929
+ function getFieldIcon(type) {
930
+ switch (type) {
931
+ case "number":
932
+ return "#";
933
+ case "date":
934
+ return "\u{1F4C5}";
935
+ case "boolean":
936
+ return "\u2713";
937
+ default:
938
+ return "Aa";
939
+ }
940
+ }
941
+ function PivotConfig({
942
+ availableFields,
943
+ rowFields,
944
+ columnFields,
945
+ valueFields,
946
+ showRowTotals,
947
+ onShowRowTotalsChange,
948
+ onClearConfig,
949
+ onAutoSuggest,
950
+ onDragStart,
951
+ onDragEnd,
952
+ onUpdateAggregation,
953
+ onRemoveRowField,
954
+ onRemoveColumnField,
955
+ onRemoveValueField,
956
+ onAddRowField,
957
+ onAddColumnField
958
+ }) {
959
+ const { showWatermark } = useLicense();
960
+ const [fieldSearch, setFieldSearch] = useState6("");
961
+ const assignedFields = useMemo6(() => {
962
+ const rowSet = new Set(rowFields);
963
+ const colSet = new Set(columnFields);
964
+ const valueMap = new Map(valueFields.map((v) => [v.field, v]));
965
+ return availableFields.filter((f) => rowSet.has(f.field) || colSet.has(f.field) || valueMap.has(f.field)).map((f) => ({
966
+ ...f,
967
+ assignedTo: rowSet.has(f.field) ? "row" : colSet.has(f.field) ? "column" : "value",
968
+ valueConfig: valueMap.get(f.field)
969
+ }));
970
+ }, [availableFields, rowFields, columnFields, valueFields]);
971
+ const unassignedFields = useMemo6(() => {
972
+ const rowSet = new Set(rowFields);
973
+ const colSet = new Set(columnFields);
974
+ const valSet = new Set(valueFields.map((v) => v.field));
975
+ return availableFields.filter(
976
+ (f) => !rowSet.has(f.field) && !colSet.has(f.field) && !valSet.has(f.field)
977
+ );
978
+ }, [availableFields, rowFields, columnFields, valueFields]);
979
+ const filteredUnassignedFields = useMemo6(() => {
980
+ if (!fieldSearch.trim()) return unassignedFields;
981
+ const search = fieldSearch.toLowerCase().trim();
982
+ return unassignedFields.filter((f) => f.field.toLowerCase().includes(search));
983
+ }, [unassignedFields, fieldSearch]);
984
+ const assignedCount = assignedFields.length;
985
+ const handleDragStart = useCallback6(
986
+ (field, event) => {
987
+ event.dataTransfer?.setData("text/plain", field);
988
+ event.dataTransfer.effectAllowed = "move";
989
+ onDragStart(field, event);
990
+ },
991
+ [onDragStart]
992
+ );
993
+ const handleAggregationChange = useCallback6(
994
+ (field, currentAgg, newAgg) => {
995
+ onUpdateAggregation(field, currentAgg, newAgg);
996
+ },
997
+ [onUpdateAggregation]
998
+ );
999
+ const toggleRowColumn = useCallback6(
1000
+ (field, currentAssignment) => {
1001
+ if (currentAssignment === "row") {
1002
+ onRemoveRowField(field);
1003
+ onAddColumnField(field);
1004
+ } else {
1005
+ onRemoveColumnField(field);
1006
+ onAddRowField(field);
1007
+ }
1008
+ },
1009
+ [onRemoveRowField, onAddColumnField, onRemoveColumnField, onAddRowField]
1010
+ );
1011
+ const removeField = useCallback6(
1012
+ (field, assignedTo, valueConfig) => {
1013
+ if (assignedTo === "row") {
1014
+ onRemoveRowField(field);
1015
+ } else if (assignedTo === "column") {
1016
+ onRemoveColumnField(field);
1017
+ } else if (valueConfig) {
1018
+ onRemoveValueField(field, valueConfig.aggregation);
1019
+ }
1020
+ },
1021
+ [onRemoveRowField, onRemoveColumnField, onRemoveValueField]
1022
+ );
1023
+ return /* @__PURE__ */ jsxs2("div", { className: "vpg-pivot-config", children: [
1024
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-config-header", children: [
1025
+ /* @__PURE__ */ jsxs2("h3", { className: "vpg-config-title", children: [
1026
+ /* @__PURE__ */ jsx2("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
1027
+ "path",
1028
+ {
1029
+ strokeLinecap: "round",
1030
+ strokeLinejoin: "round",
1031
+ strokeWidth: 2,
1032
+ d: "M4 6h16M4 10h16M4 14h16M4 18h16"
1033
+ }
1034
+ ) }),
1035
+ "Fields"
1036
+ ] }),
1037
+ /* @__PURE__ */ jsx2("div", { className: "vpg-header-actions", children: assignedCount > 0 && /* @__PURE__ */ jsx2(
1038
+ "button",
1039
+ {
1040
+ className: "vpg-action-btn vpg-clear-btn",
1041
+ title: "Clear all",
1042
+ onClick: onClearConfig,
1043
+ children: /* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
1044
+ "path",
1045
+ {
1046
+ strokeLinecap: "round",
1047
+ strokeLinejoin: "round",
1048
+ strokeWidth: 2,
1049
+ d: "M6 18L18 6M6 6l12 12"
1050
+ }
1051
+ ) })
1052
+ }
1053
+ ) })
1054
+ ] }),
1055
+ assignedCount > 0 && /* @__PURE__ */ jsxs2("div", { className: "vpg-assigned-section", children: [
1056
+ /* @__PURE__ */ jsx2("div", { className: "vpg-section-label", children: "Active" }),
1057
+ /* @__PURE__ */ jsx2("div", { className: "vpg-assigned-list", children: assignedFields.map((field) => /* @__PURE__ */ jsxs2(
1058
+ "div",
1059
+ {
1060
+ className: `vpg-assigned-item vpg-type-${field.assignedTo}`,
1061
+ title: field.field,
1062
+ draggable: true,
1063
+ onDragStart: (e) => handleDragStart(field.field, e),
1064
+ onDragEnd,
1065
+ children: [
1066
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-item-main", children: [
1067
+ /* @__PURE__ */ jsx2("span", { className: `vpg-item-badge ${field.assignedTo}`, children: field.assignedTo === "row" ? "R" : field.assignedTo === "column" ? "C" : getAggregationSymbol(field.valueConfig?.aggregation || "sum") }),
1068
+ /* @__PURE__ */ jsx2("span", { className: "vpg-item-name", children: field.field })
1069
+ ] }),
1070
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-item-actions", children: [
1071
+ (field.assignedTo === "row" || field.assignedTo === "column") && /* @__PURE__ */ jsx2(
1072
+ "button",
1073
+ {
1074
+ className: "vpg-toggle-btn",
1075
+ title: field.assignedTo === "row" ? "Move to Columns" : "Move to Rows",
1076
+ onClick: (e) => {
1077
+ e.stopPropagation();
1078
+ toggleRowColumn(field.field, field.assignedTo);
1079
+ },
1080
+ children: /* @__PURE__ */ jsx2(
1081
+ "svg",
1082
+ {
1083
+ className: "vpg-icon-xs",
1084
+ fill: "none",
1085
+ stroke: "currentColor",
1086
+ viewBox: "0 0 24 24",
1087
+ children: /* @__PURE__ */ jsx2(
1088
+ "path",
1089
+ {
1090
+ strokeLinecap: "round",
1091
+ strokeLinejoin: "round",
1092
+ strokeWidth: 2,
1093
+ d: "M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
1094
+ }
1095
+ )
1096
+ }
1097
+ )
1098
+ }
1099
+ ),
1100
+ field.assignedTo === "value" && field.valueConfig && /* @__PURE__ */ jsx2(
1101
+ "select",
1102
+ {
1103
+ className: "vpg-agg-select",
1104
+ value: field.valueConfig.aggregation,
1105
+ onChange: (e) => {
1106
+ e.stopPropagation();
1107
+ handleAggregationChange(
1108
+ field.field,
1109
+ field.valueConfig.aggregation,
1110
+ e.target.value
1111
+ );
1112
+ },
1113
+ onClick: (e) => e.stopPropagation(),
1114
+ children: AGGREGATION_OPTIONS.map((agg) => /* @__PURE__ */ jsxs2("option", { value: agg.value, children: [
1115
+ agg.symbol,
1116
+ " ",
1117
+ agg.label
1118
+ ] }, agg.value))
1119
+ }
1120
+ ),
1121
+ /* @__PURE__ */ jsx2(
1122
+ "button",
1123
+ {
1124
+ className: "vpg-remove-btn",
1125
+ title: "Remove",
1126
+ onClick: (e) => {
1127
+ e.stopPropagation();
1128
+ removeField(field.field, field.assignedTo, field.valueConfig);
1129
+ },
1130
+ children: "\xD7"
1131
+ }
1132
+ )
1133
+ ] })
1134
+ ]
1135
+ },
1136
+ field.field
1137
+ )) })
1138
+ ] }),
1139
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-unassigned-section", children: [
1140
+ /* @__PURE__ */ jsx2("div", { className: "vpg-section-header", children: /* @__PURE__ */ jsxs2("div", { className: "vpg-section-label", children: [
1141
+ "Available ",
1142
+ /* @__PURE__ */ jsx2("span", { className: "vpg-count", children: unassignedFields.length })
1143
+ ] }) }),
1144
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-field-search", children: [
1145
+ /* @__PURE__ */ jsx2("svg", { className: "vpg-search-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
1146
+ "path",
1147
+ {
1148
+ strokeLinecap: "round",
1149
+ strokeLinejoin: "round",
1150
+ strokeWidth: 2,
1151
+ d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
1152
+ }
1153
+ ) }),
1154
+ /* @__PURE__ */ jsx2(
1155
+ "input",
1156
+ {
1157
+ type: "text",
1158
+ value: fieldSearch,
1159
+ onChange: (e) => setFieldSearch(e.target.value),
1160
+ placeholder: "Search fields...",
1161
+ className: "vpg-search-input"
1162
+ }
1163
+ ),
1164
+ fieldSearch && /* @__PURE__ */ jsx2("button", { className: "vpg-clear-search", onClick: () => setFieldSearch(""), children: /* @__PURE__ */ jsx2("svg", { className: "vpg-icon-xs", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
1165
+ "path",
1166
+ {
1167
+ strokeLinecap: "round",
1168
+ strokeLinejoin: "round",
1169
+ strokeWidth: 2,
1170
+ d: "M6 18L18 6M6 6l12 12"
1171
+ }
1172
+ ) }) })
1173
+ ] }),
1174
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-field-list", children: [
1175
+ filteredUnassignedFields.map((field) => /* @__PURE__ */ jsxs2(
1176
+ "div",
1177
+ {
1178
+ className: `vpg-field-item ${field.isNumeric ? "vpg-is-numeric" : ""}`,
1179
+ title: field.field,
1180
+ draggable: true,
1181
+ onDragStart: (e) => handleDragStart(field.field, e),
1182
+ onDragEnd,
1183
+ children: [
1184
+ /* @__PURE__ */ jsx2("span", { className: "vpg-field-type-icon", title: field.type, children: getFieldIcon(field.type) }),
1185
+ /* @__PURE__ */ jsx2("span", { className: "vpg-field-name", children: field.field }),
1186
+ /* @__PURE__ */ jsx2("span", { className: "vpg-unique-count", children: field.uniqueCount })
1187
+ ]
1188
+ },
1189
+ field.field
1190
+ )),
1191
+ filteredUnassignedFields.length === 0 && fieldSearch && /* @__PURE__ */ jsxs2("div", { className: "vpg-empty-hint", children: [
1192
+ 'No fields match "',
1193
+ fieldSearch,
1194
+ '"'
1195
+ ] }),
1196
+ unassignedFields.length === 0 && /* @__PURE__ */ jsx2("div", { className: "vpg-empty-hint", children: "All fields assigned" })
1197
+ ] })
1198
+ ] }),
1199
+ /* @__PURE__ */ jsxs2("div", { className: "vpg-options-section", children: [
1200
+ /* @__PURE__ */ jsxs2("label", { className: "vpg-option-toggle", children: [
1201
+ /* @__PURE__ */ jsx2(
1202
+ "input",
1203
+ {
1204
+ type: "checkbox",
1205
+ checked: showRowTotals,
1206
+ onChange: (e) => onShowRowTotalsChange(e.target.checked)
1207
+ }
1208
+ ),
1209
+ /* @__PURE__ */ jsx2("span", { children: "Totals" })
1210
+ ] }),
1211
+ /* @__PURE__ */ jsxs2("button", { className: "vpg-auto-btn", onClick: onAutoSuggest, children: [
1212
+ /* @__PURE__ */ jsx2("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx2(
1213
+ "path",
1214
+ {
1215
+ strokeLinecap: "round",
1216
+ strokeLinejoin: "round",
1217
+ strokeWidth: 2,
1218
+ d: "M13 10V3L4 14h7v7l9-11h-7z"
1219
+ }
1220
+ ) }),
1221
+ "Auto"
1222
+ ] })
1223
+ ] }),
1224
+ showWatermark && /* @__PURE__ */ jsx2("div", { className: "vpg-watermark", children: /* @__PURE__ */ jsx2("a", { href: "https://tiny-pivot.com", target: "_blank", rel: "noopener noreferrer", children: "TinyPivot" }) })
1225
+ ] });
1226
+ }
1227
+
1228
+ // src/components/PivotSkeleton.tsx
1229
+ import { useState as useState7, useMemo as useMemo7, useCallback as useCallback7 } from "react";
1230
+ import { getAggregationLabel as getAggregationLabel2, getAggregationSymbol as getAggregationSymbol2 } from "@smallwebco/tinypivot-core";
1231
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1232
+ function PivotSkeleton({
1233
+ rowFields,
1234
+ columnFields,
1235
+ valueFields,
1236
+ isConfigured,
1237
+ draggingField,
1238
+ pivotResult,
1239
+ fontSize = "xs",
1240
+ activeFilters,
1241
+ totalRowCount,
1242
+ filteredRowCount,
1243
+ onAddRowField,
1244
+ onRemoveRowField,
1245
+ onAddColumnField,
1246
+ onRemoveColumnField,
1247
+ onAddValueField,
1248
+ onRemoveValueField
1249
+ }) {
1250
+ const { showWatermark, canUsePivot, isDemo } = useLicense();
1251
+ const [dragOverArea, setDragOverArea] = useState7(null);
1252
+ const [sortDirection, setSortDirection] = useState7("asc");
1253
+ const [sortTarget, setSortTarget] = useState7("row");
1254
+ const toggleSort = useCallback7((target = "row") => {
1255
+ if (sortTarget === target) {
1256
+ setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
1257
+ } else {
1258
+ setSortTarget(target);
1259
+ setSortDirection("asc");
1260
+ }
1261
+ }, [sortTarget]);
1262
+ const sortedRowIndices = useMemo7(() => {
1263
+ if (!pivotResult) return [];
1264
+ const indices = pivotResult.rowHeaders.map((_, i) => i);
1265
+ const headers = pivotResult.rowHeaders;
1266
+ const data = pivotResult.data;
1267
+ indices.sort((a, b) => {
1268
+ let cmp;
1269
+ if (sortTarget === "row") {
1270
+ const aHeader = headers[a]?.join(" / ") || "";
1271
+ const bHeader = headers[b]?.join(" / ") || "";
1272
+ cmp = aHeader.localeCompare(bHeader, void 0, { numeric: true, sensitivity: "base" });
1273
+ } else {
1274
+ const colIdx = sortTarget;
1275
+ const aVal = data[a]?.[colIdx]?.value ?? null;
1276
+ const bVal = data[b]?.[colIdx]?.value ?? null;
1277
+ if (aVal === null && bVal === null) cmp = 0;
1278
+ else if (aVal === null) cmp = 1;
1279
+ else if (bVal === null) cmp = -1;
1280
+ else cmp = aVal - bVal;
1281
+ }
1282
+ return sortDirection === "asc" ? cmp : -cmp;
1283
+ });
1284
+ return indices;
1285
+ }, [pivotResult, sortTarget, sortDirection]);
1286
+ const columnHeaderCells = useMemo7(() => {
1287
+ if (!pivotResult || pivotResult.headers.length === 0) {
1288
+ return [
1289
+ valueFields.map((vf) => ({
1290
+ label: `${vf.field} (${getAggregationLabel2(vf.aggregation)})`,
1291
+ colspan: 1
1292
+ }))
1293
+ ];
1294
+ }
1295
+ const result = [];
1296
+ for (let level = 0; level < pivotResult.headers.length; level++) {
1297
+ const headerRow = pivotResult.headers[level];
1298
+ const cells = [];
1299
+ let i = 0;
1300
+ while (i < headerRow.length) {
1301
+ const value = headerRow[i];
1302
+ let colspan = 1;
1303
+ while (i + colspan < headerRow.length && headerRow[i + colspan] === value) {
1304
+ colspan++;
1305
+ }
1306
+ cells.push({ label: value, colspan });
1307
+ i += colspan;
1308
+ }
1309
+ result.push(cells);
1310
+ }
1311
+ return result;
1312
+ }, [pivotResult, valueFields]);
1313
+ const hasActiveFilters = activeFilters && activeFilters.length > 0;
1314
+ const filterSummary = useMemo7(() => {
1315
+ if (!activeFilters || activeFilters.length === 0) return "";
1316
+ return activeFilters.map((f) => f.column).join(", ");
1317
+ }, [activeFilters]);
1318
+ const handleDragOver = useCallback7(
1319
+ (area, event) => {
1320
+ event.preventDefault();
1321
+ event.dataTransfer.dropEffect = "move";
1322
+ setDragOverArea(area);
1323
+ },
1324
+ []
1325
+ );
1326
+ const handleDragLeave = useCallback7(() => {
1327
+ setDragOverArea(null);
1328
+ }, []);
1329
+ const handleDrop = useCallback7(
1330
+ (area, event) => {
1331
+ event.preventDefault();
1332
+ const field = event.dataTransfer?.getData("text/plain");
1333
+ if (!field || field.startsWith("reorder:")) {
1334
+ setDragOverArea(null);
1335
+ return;
1336
+ }
1337
+ if (rowFields.includes(field)) onRemoveRowField(field);
1338
+ if (columnFields.includes(field)) onRemoveColumnField(field);
1339
+ const existingValue = valueFields.find((v) => v.field === field);
1340
+ if (existingValue) onRemoveValueField(field, existingValue.aggregation);
1341
+ switch (area) {
1342
+ case "row":
1343
+ onAddRowField(field);
1344
+ break;
1345
+ case "column":
1346
+ onAddColumnField(field);
1347
+ break;
1348
+ case "value":
1349
+ onAddValueField(field, "sum");
1350
+ break;
1351
+ }
1352
+ setDragOverArea(null);
1353
+ },
1354
+ [rowFields, columnFields, valueFields, onAddRowField, onRemoveRowField, onAddColumnField, onRemoveColumnField, onAddValueField, onRemoveValueField]
1355
+ );
1356
+ const currentFontSize = fontSize;
1357
+ return /* @__PURE__ */ jsxs3(
1358
+ "div",
1359
+ {
1360
+ className: `vpg-pivot-skeleton vpg-font-${currentFontSize} ${draggingField ? "vpg-is-dragging" : ""}`,
1361
+ children: [
1362
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-skeleton-header", children: [
1363
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-skeleton-title", children: [
1364
+ /* @__PURE__ */ jsx3("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx3(
1365
+ "path",
1366
+ {
1367
+ strokeLinecap: "round",
1368
+ strokeLinejoin: "round",
1369
+ strokeWidth: 2,
1370
+ d: "M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
1371
+ }
1372
+ ) }),
1373
+ /* @__PURE__ */ jsx3("span", { children: "Pivot Table" })
1374
+ ] }),
1375
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-header-right", children: [
1376
+ hasActiveFilters && /* @__PURE__ */ jsxs3("div", { className: "vpg-filter-indicator", children: [
1377
+ /* @__PURE__ */ jsx3(
1378
+ "svg",
1379
+ {
1380
+ className: "vpg-filter-icon",
1381
+ fill: "none",
1382
+ stroke: "currentColor",
1383
+ viewBox: "0 0 24 24",
1384
+ children: /* @__PURE__ */ jsx3(
1385
+ "path",
1386
+ {
1387
+ strokeLinecap: "round",
1388
+ strokeLinejoin: "round",
1389
+ strokeWidth: 2,
1390
+ d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
1391
+ }
1392
+ )
1393
+ }
1394
+ ),
1395
+ /* @__PURE__ */ jsxs3("span", { className: "vpg-filter-text", children: [
1396
+ "Filtered: ",
1397
+ /* @__PURE__ */ jsx3("strong", { children: filterSummary }),
1398
+ filteredRowCount !== void 0 && totalRowCount !== void 0 && /* @__PURE__ */ jsxs3("span", { className: "vpg-filter-count", children: [
1399
+ "(",
1400
+ filteredRowCount.toLocaleString(),
1401
+ " of ",
1402
+ totalRowCount.toLocaleString(),
1403
+ " rows)"
1404
+ ] })
1405
+ ] })
1406
+ ] }),
1407
+ isConfigured && /* @__PURE__ */ jsxs3("div", { className: "vpg-config-summary", children: [
1408
+ /* @__PURE__ */ jsxs3("span", { className: "vpg-summary-badge vpg-rows", children: [
1409
+ rowFields.length,
1410
+ " row",
1411
+ rowFields.length !== 1 ? "s" : ""
1412
+ ] }),
1413
+ /* @__PURE__ */ jsxs3("span", { className: "vpg-summary-badge vpg-cols", children: [
1414
+ columnFields.length,
1415
+ " col",
1416
+ columnFields.length !== 1 ? "s" : ""
1417
+ ] }),
1418
+ /* @__PURE__ */ jsxs3("span", { className: "vpg-summary-badge vpg-vals", children: [
1419
+ valueFields.length,
1420
+ " val",
1421
+ valueFields.length !== 1 ? "s" : ""
1422
+ ] })
1423
+ ] })
1424
+ ] })
1425
+ ] }),
1426
+ !canUsePivot ? /* @__PURE__ */ jsx3("div", { className: "vpg-pro-required", children: /* @__PURE__ */ jsxs3("div", { className: "vpg-pro-content", children: [
1427
+ /* @__PURE__ */ jsx3("svg", { className: "vpg-pro-icon", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx3(
1428
+ "path",
1429
+ {
1430
+ strokeLinecap: "round",
1431
+ strokeLinejoin: "round",
1432
+ strokeWidth: 2,
1433
+ d: "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
1434
+ }
1435
+ ) }),
1436
+ /* @__PURE__ */ jsx3("h3", { children: "Pro Feature" }),
1437
+ /* @__PURE__ */ jsx3("p", { children: "Pivot Table functionality requires a Pro license." }),
1438
+ /* @__PURE__ */ jsx3("a", { href: "https://tiny-pivot.com/#pricing", target: "_blank", rel: "noopener noreferrer", className: "vpg-pro-link", children: "Get Pro License \u2192" })
1439
+ ] }) }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
1440
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-config-bar", children: [
1441
+ /* @__PURE__ */ jsxs3(
1442
+ "div",
1443
+ {
1444
+ className: `vpg-drop-zone vpg-row-zone ${dragOverArea === "row" ? "vpg-drag-over" : ""}`,
1445
+ onDragOver: (e) => handleDragOver("row", e),
1446
+ onDragLeave: handleDragLeave,
1447
+ onDrop: (e) => handleDrop("row", e),
1448
+ children: [
1449
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-header", children: [
1450
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-icon vpg-row-icon", children: "\u2193" }),
1451
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-label", children: "Rows" })
1452
+ ] }),
1453
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-chips", children: [
1454
+ rowFields.map((field) => /* @__PURE__ */ jsxs3("div", { className: "vpg-mini-chip vpg-row-chip", children: [
1455
+ /* @__PURE__ */ jsx3("span", { className: "vpg-mini-name", children: field }),
1456
+ /* @__PURE__ */ jsx3(
1457
+ "button",
1458
+ {
1459
+ className: "vpg-mini-remove",
1460
+ onClick: () => onRemoveRowField(field),
1461
+ children: "\xD7"
1462
+ }
1463
+ )
1464
+ ] }, field)),
1465
+ rowFields.length === 0 && /* @__PURE__ */ jsx3("span", { className: "vpg-zone-hint", children: "Drop here" })
1466
+ ] })
1467
+ ]
1468
+ }
1469
+ ),
1470
+ /* @__PURE__ */ jsxs3(
1471
+ "div",
1472
+ {
1473
+ className: `vpg-drop-zone vpg-column-zone ${dragOverArea === "column" ? "vpg-drag-over" : ""}`,
1474
+ onDragOver: (e) => handleDragOver("column", e),
1475
+ onDragLeave: handleDragLeave,
1476
+ onDrop: (e) => handleDrop("column", e),
1477
+ children: [
1478
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-header", children: [
1479
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-icon vpg-column-icon", children: "\u2192" }),
1480
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-label", children: "Columns" })
1481
+ ] }),
1482
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-chips", children: [
1483
+ columnFields.map((field) => /* @__PURE__ */ jsxs3("div", { className: "vpg-mini-chip vpg-column-chip", children: [
1484
+ /* @__PURE__ */ jsx3("span", { className: "vpg-mini-name", children: field }),
1485
+ /* @__PURE__ */ jsx3(
1486
+ "button",
1487
+ {
1488
+ className: "vpg-mini-remove",
1489
+ onClick: () => onRemoveColumnField(field),
1490
+ children: "\xD7"
1491
+ }
1492
+ )
1493
+ ] }, field)),
1494
+ columnFields.length === 0 && /* @__PURE__ */ jsx3("span", { className: "vpg-zone-hint", children: "Drop here" })
1495
+ ] })
1496
+ ]
1497
+ }
1498
+ ),
1499
+ /* @__PURE__ */ jsxs3(
1500
+ "div",
1501
+ {
1502
+ className: `vpg-drop-zone vpg-value-zone ${dragOverArea === "value" ? "vpg-drag-over" : ""}`,
1503
+ onDragOver: (e) => handleDragOver("value", e),
1504
+ onDragLeave: handleDragLeave,
1505
+ onDrop: (e) => handleDrop("value", e),
1506
+ children: [
1507
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-header", children: [
1508
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-icon vpg-value-icon", children: "\u03A3" }),
1509
+ /* @__PURE__ */ jsx3("span", { className: "vpg-zone-label", children: "Values" })
1510
+ ] }),
1511
+ /* @__PURE__ */ jsxs3("div", { className: "vpg-zone-chips", children: [
1512
+ valueFields.map((vf) => /* @__PURE__ */ jsxs3(
1513
+ "div",
1514
+ {
1515
+ className: "vpg-mini-chip vpg-value-chip",
1516
+ children: [
1517
+ /* @__PURE__ */ jsx3("span", { className: "vpg-agg-symbol", children: getAggregationSymbol2(vf.aggregation) }),
1518
+ /* @__PURE__ */ jsx3("span", { className: "vpg-mini-name", children: vf.field }),
1519
+ /* @__PURE__ */ jsx3(
1520
+ "button",
1521
+ {
1522
+ className: "vpg-mini-remove",
1523
+ onClick: () => onRemoveValueField(vf.field, vf.aggregation),
1524
+ children: "\xD7"
1525
+ }
1526
+ )
1527
+ ]
1528
+ },
1529
+ `${vf.field}-${vf.aggregation}`
1530
+ )),
1531
+ valueFields.length === 0 && /* @__PURE__ */ jsx3("span", { className: "vpg-zone-hint", children: "Drop numeric" })
1532
+ ] })
1533
+ ]
1534
+ }
1535
+ )
1536
+ ] }),
1537
+ (!isConfigured || !pivotResult) && /* @__PURE__ */ jsx3("div", { className: "vpg-placeholder", children: /* @__PURE__ */ jsxs3("div", { className: "vpg-placeholder-content", children: [
1538
+ /* @__PURE__ */ jsx3(
1539
+ "svg",
1540
+ {
1541
+ className: "vpg-placeholder-icon",
1542
+ fill: "none",
1543
+ viewBox: "0 0 24 24",
1544
+ stroke: "currentColor",
1545
+ children: /* @__PURE__ */ jsx3(
1546
+ "path",
1547
+ {
1548
+ strokeLinecap: "round",
1549
+ strokeLinejoin: "round",
1550
+ strokeWidth: 1.5,
1551
+ d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
1552
+ }
1553
+ )
1554
+ }
1555
+ ),
1556
+ /* @__PURE__ */ jsx3("span", { className: "vpg-placeholder-text", children: valueFields.length === 0 ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1557
+ "Add a ",
1558
+ /* @__PURE__ */ jsx3("strong", { children: "Values" }),
1559
+ " field to see your pivot table"
1560
+ ] }) : rowFields.length === 0 && columnFields.length === 0 ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1561
+ "Add ",
1562
+ /* @__PURE__ */ jsx3("strong", { children: "Row" }),
1563
+ " or ",
1564
+ /* @__PURE__ */ jsx3("strong", { children: "Column" }),
1565
+ " fields to group your data"
1566
+ ] }) : "Your pivot table will appear here" })
1567
+ ] }) }),
1568
+ isConfigured && pivotResult && /* @__PURE__ */ jsx3("div", { className: "vpg-table-container", children: /* @__PURE__ */ jsxs3("table", { className: "vpg-pivot-table", children: [
1569
+ /* @__PURE__ */ jsx3("thead", { children: columnHeaderCells.map((headerRow, levelIdx) => /* @__PURE__ */ jsxs3("tr", { className: "vpg-column-header-row", children: [
1570
+ levelIdx === 0 && /* @__PURE__ */ jsx3(
1571
+ "th",
1572
+ {
1573
+ className: "vpg-row-header-label",
1574
+ rowSpan: columnHeaderCells.length,
1575
+ onClick: () => toggleSort("row"),
1576
+ children: /* @__PURE__ */ jsxs3("div", { className: "vpg-header-content", children: [
1577
+ /* @__PURE__ */ jsx3("span", { children: rowFields.join(" / ") || "Rows" }),
1578
+ /* @__PURE__ */ jsx3("span", { className: `vpg-sort-indicator ${sortTarget === "row" ? "active" : ""}`, children: sortTarget === "row" ? sortDirection === "asc" ? "\u2191" : "\u2193" : "\u21C5" })
1579
+ ] })
1580
+ }
1581
+ ),
1582
+ headerRow.map((cell, idx) => /* @__PURE__ */ jsx3(
1583
+ "th",
1584
+ {
1585
+ className: "vpg-column-header-cell",
1586
+ colSpan: cell.colspan,
1587
+ onClick: () => levelIdx === columnHeaderCells.length - 1 && toggleSort(idx),
1588
+ children: /* @__PURE__ */ jsxs3("div", { className: "vpg-header-content", children: [
1589
+ /* @__PURE__ */ jsx3("span", { children: cell.label }),
1590
+ levelIdx === columnHeaderCells.length - 1 && /* @__PURE__ */ jsx3("span", { className: `vpg-sort-indicator ${sortTarget === idx ? "active" : ""}`, children: sortTarget === idx ? sortDirection === "asc" ? "\u2191" : "\u2193" : "\u21C5" })
1591
+ ] })
1592
+ },
1593
+ idx
1594
+ )),
1595
+ pivotResult.rowTotals.length > 0 && levelIdx === 0 && /* @__PURE__ */ jsx3("th", { className: "vpg-total-header", rowSpan: columnHeaderCells.length, children: "Total" })
1596
+ ] }, `header-${levelIdx}`)) }),
1597
+ /* @__PURE__ */ jsxs3("tbody", { children: [
1598
+ sortedRowIndices.map((sortedIdx) => /* @__PURE__ */ jsxs3("tr", { className: "vpg-data-row", children: [
1599
+ /* @__PURE__ */ jsx3("th", { className: "vpg-row-header-cell", children: pivotResult.rowHeaders[sortedIdx].map((val, idx) => /* @__PURE__ */ jsx3("span", { className: "vpg-row-value", children: val }, idx)) }),
1600
+ pivotResult.data[sortedIdx].map((cell, colIdx) => /* @__PURE__ */ jsx3(
1601
+ "td",
1602
+ {
1603
+ className: `vpg-data-cell ${cell.value === null ? "vpg-is-null" : ""}`,
1604
+ children: cell.formattedValue
1605
+ },
1606
+ colIdx
1607
+ )),
1608
+ pivotResult.rowTotals[sortedIdx] && /* @__PURE__ */ jsx3("td", { className: "vpg-data-cell vpg-total-cell", children: pivotResult.rowTotals[sortedIdx].formattedValue })
1609
+ ] }, sortedIdx)),
1610
+ pivotResult.columnTotals.length > 0 && /* @__PURE__ */ jsxs3("tr", { className: "vpg-totals-row", children: [
1611
+ /* @__PURE__ */ jsx3("th", { className: "vpg-row-header-cell vpg-total-label", children: "Total" }),
1612
+ pivotResult.columnTotals.map((cell, colIdx) => /* @__PURE__ */ jsx3("td", { className: "vpg-data-cell vpg-total-cell", children: cell.formattedValue }, colIdx)),
1613
+ pivotResult.rowTotals.length > 0 && /* @__PURE__ */ jsx3("td", { className: "vpg-data-cell vpg-grand-total-cell", children: pivotResult.grandTotal.formattedValue })
1614
+ ] })
1615
+ ] })
1616
+ ] }) }),
1617
+ isConfigured && pivotResult && /* @__PURE__ */ jsx3("div", { className: "vpg-skeleton-footer", children: /* @__PURE__ */ jsxs3("span", { children: [
1618
+ pivotResult.rowHeaders.length,
1619
+ " rows \xD7 ",
1620
+ pivotResult.data[0]?.length || 0,
1621
+ " columns"
1622
+ ] }) })
1623
+ ] }),
1624
+ showWatermark && canUsePivot && /* @__PURE__ */ jsx3("div", { className: `vpg-watermark ${isDemo ? "vpg-demo-mode" : ""}`, children: isDemo ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1625
+ /* @__PURE__ */ jsx3("span", { className: "vpg-demo-badge", children: "DEMO" }),
1626
+ /* @__PURE__ */ jsx3("span", { children: "Pro features unlocked for evaluation" }),
1627
+ /* @__PURE__ */ jsx3(
1628
+ "a",
1629
+ {
1630
+ href: "https://tiny-pivot.com/#pricing",
1631
+ target: "_blank",
1632
+ rel: "noopener noreferrer",
1633
+ className: "vpg-get-pro",
1634
+ children: "Get Pro License \u2192"
1635
+ }
1636
+ )
1637
+ ] }) : /* @__PURE__ */ jsx3("a", { href: "https://tiny-pivot.com", target: "_blank", rel: "noopener noreferrer", children: "Powered by TinyPivot" }) })
1638
+ ]
1639
+ }
1640
+ );
1641
+ }
1642
+
1643
+ // src/components/DataGrid.tsx
1644
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1645
+ var MIN_COL_WIDTH = 120;
1646
+ var MAX_COL_WIDTH = 350;
1647
+ function DataGrid({
1648
+ data,
1649
+ loading = false,
1650
+ fontSize: initialFontSize = "xs",
1651
+ showPivot = true,
1652
+ enableExport = true,
1653
+ enableSearch = true,
1654
+ enablePagination = false,
1655
+ pageSize = 50,
1656
+ enableColumnResize = true,
1657
+ enableClipboard = true,
1658
+ theme = "light",
1659
+ stripedRows = true,
1660
+ exportFilename = "data-export.csv",
1661
+ enableVerticalResize = true,
1662
+ initialHeight = 600,
1663
+ minHeight = 300,
1664
+ maxHeight = 1200,
1665
+ onCellClick,
1666
+ onExport,
1667
+ onCopy
1668
+ }) {
1669
+ const { showWatermark, canUsePivot, isDemo } = useLicense();
1670
+ const currentTheme = useMemo8(() => {
1671
+ if (theme === "auto") {
1672
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1673
+ }
1674
+ return theme;
1675
+ }, [theme]);
1676
+ const [currentFontSize, setCurrentFontSize] = useState8(initialFontSize);
1677
+ const [globalSearchTerm, setGlobalSearchTerm] = useState8("");
1678
+ const [showSearchInput, setShowSearchInput] = useState8(false);
1679
+ const [currentPage, setCurrentPage] = useState8(1);
1680
+ const [columnWidths, setColumnWidths] = useState8({});
1681
+ const [resizingColumnId, setResizingColumnId] = useState8(null);
1682
+ const [resizeStartX, setResizeStartX] = useState8(0);
1683
+ const [resizeStartWidth, setResizeStartWidth] = useState8(0);
1684
+ const [gridHeight, setGridHeight] = useState8(initialHeight);
1685
+ const [isResizingVertically, setIsResizingVertically] = useState8(false);
1686
+ const [verticalResizeStartY, setVerticalResizeStartY] = useState8(0);
1687
+ const [verticalResizeStartHeight, setVerticalResizeStartHeight] = useState8(0);
1688
+ const [showCopyToast, setShowCopyToast] = useState8(false);
1689
+ const [copyToastMessage, setCopyToastMessage] = useState8("");
1690
+ const [viewMode, setViewMode] = useState8("grid");
1691
+ const [showPivotConfig, setShowPivotConfig] = useState8(true);
1692
+ const [draggingField, setDraggingField] = useState8(null);
1693
+ const [activeFilterColumn, setActiveFilterColumn] = useState8(null);
1694
+ const [filterDropdownPosition, setFilterDropdownPosition] = useState8({ top: 0, left: 0, maxHeight: 400 });
1695
+ const [selectedCell, setSelectedCell] = useState8(null);
1696
+ const [selectionStart, setSelectionStart] = useState8(null);
1697
+ const [selectionEnd, setSelectionEnd] = useState8(null);
1698
+ const [isSelecting, setIsSelecting] = useState8(false);
1699
+ const tableContainerRef = useRef2(null);
1700
+ const tableBodyRef = useRef2(null);
1701
+ const fontSizeOptions = [
1702
+ { value: "xs", label: "S" },
1703
+ { value: "sm", label: "M" },
1704
+ { value: "base", label: "L" }
1705
+ ];
1706
+ const {
1707
+ table,
1708
+ columnKeys,
1709
+ filteredRowCount,
1710
+ totalRowCount,
1711
+ getColumnStats,
1712
+ hasActiveFilter,
1713
+ setColumnFilter,
1714
+ getColumnFilterValues,
1715
+ clearAllFilters,
1716
+ toggleSort,
1717
+ getSortDirection,
1718
+ columnFilters,
1719
+ activeFilters
1720
+ } = useExcelGrid({ data, enableSorting: true, enableFiltering: true });
1721
+ const filteredDataForPivot = useMemo8(() => {
1722
+ const filteredRows = table.getFilteredRowModel().rows;
1723
+ return filteredRows.map((row) => row.original);
1724
+ }, [table]);
1725
+ const {
1726
+ rowFields: pivotRowFields,
1727
+ columnFields: pivotColumnFields,
1728
+ valueFields: pivotValueFields,
1729
+ showRowTotals: pivotShowRowTotals,
1730
+ showColumnTotals: pivotShowColumnTotals,
1731
+ availableFields: pivotAvailableFields,
1732
+ isConfigured: pivotIsConfigured,
1733
+ pivotResult,
1734
+ addRowField,
1735
+ removeRowField,
1736
+ addColumnField,
1737
+ removeColumnField,
1738
+ addValueField,
1739
+ removeValueField,
1740
+ updateValueFieldAggregation,
1741
+ clearConfig: clearPivotConfig,
1742
+ autoSuggestConfig,
1743
+ setShowRowTotals: setPivotShowRowTotals,
1744
+ setShowColumnTotals: setPivotShowColumnTotals,
1745
+ setRowFields,
1746
+ setColumnFields
1747
+ } = usePivotTable(filteredDataForPivot);
1748
+ const activeFilterInfo = useMemo8(() => {
1749
+ if (activeFilters.length === 0) return null;
1750
+ return activeFilters.map((f) => ({
1751
+ column: f.column,
1752
+ valueCount: f.values?.length || 0,
1753
+ values: f.values || []
1754
+ }));
1755
+ }, [activeFilters]);
1756
+ const rows = useMemo8(() => table.getRowModel().rows, [table]);
1757
+ const searchFilteredData = useMemo8(() => {
1758
+ if (!globalSearchTerm.trim() || !enableSearch) {
1759
+ return rows;
1760
+ }
1761
+ const term = globalSearchTerm.toLowerCase().trim();
1762
+ return rows.filter((row) => {
1763
+ for (const col of columnKeys) {
1764
+ const value = row.original[col];
1765
+ if (value === null || value === void 0) continue;
1766
+ if (String(value).toLowerCase().includes(term)) {
1767
+ return true;
1768
+ }
1769
+ }
1770
+ return false;
1771
+ });
1772
+ }, [rows, globalSearchTerm, enableSearch, columnKeys]);
1773
+ const totalSearchedRows = searchFilteredData.length;
1774
+ const totalPages = useMemo8(() => {
1775
+ if (!enablePagination) return 1;
1776
+ return Math.max(1, Math.ceil(totalSearchedRows / pageSize));
1777
+ }, [enablePagination, totalSearchedRows, pageSize]);
1778
+ const paginatedRows = useMemo8(() => {
1779
+ if (!enablePagination) return searchFilteredData;
1780
+ const start = (currentPage - 1) * pageSize;
1781
+ const end = start + pageSize;
1782
+ return searchFilteredData.slice(start, end);
1783
+ }, [enablePagination, searchFilteredData, currentPage, pageSize]);
1784
+ const selectionBounds = useMemo8(() => {
1785
+ if (!selectionStart || !selectionEnd) return null;
1786
+ return {
1787
+ minRow: Math.min(selectionStart.row, selectionEnd.row),
1788
+ maxRow: Math.max(selectionStart.row, selectionEnd.row),
1789
+ minCol: Math.min(selectionStart.col, selectionEnd.col),
1790
+ maxCol: Math.max(selectionStart.col, selectionEnd.col)
1791
+ };
1792
+ }, [selectionStart, selectionEnd]);
1793
+ const selectionStats = useMemo8(() => {
1794
+ if (!selectionBounds) return null;
1795
+ const { minRow, maxRow, minCol, maxCol } = selectionBounds;
1796
+ const values = [];
1797
+ let count = 0;
1798
+ for (let r = minRow; r <= maxRow; r++) {
1799
+ const row = rows[r];
1800
+ if (!row) continue;
1801
+ for (let c = minCol; c <= maxCol; c++) {
1802
+ const colId = columnKeys[c];
1803
+ if (!colId) continue;
1804
+ const value = row.original[colId];
1805
+ count++;
1806
+ if (value !== null && value !== void 0 && value !== "") {
1807
+ const num = typeof value === "number" ? value : Number.parseFloat(String(value));
1808
+ if (!Number.isNaN(num)) {
1809
+ values.push(num);
1810
+ }
1811
+ }
1812
+ }
1813
+ }
1814
+ if (values.length === 0) return { count, sum: null, avg: null, numericCount: 0 };
1815
+ const sum = values.reduce((a, b) => a + b, 0);
1816
+ const avg = sum / values.length;
1817
+ return { count, sum, avg, numericCount: values.length };
1818
+ }, [selectionBounds, rows, columnKeys]);
1819
+ useEffect3(() => {
1820
+ if (data.length === 0) return;
1821
+ const widths = {};
1822
+ const sampleSize = Math.min(100, data.length);
1823
+ const canvas = document.createElement("canvas");
1824
+ const ctx = canvas.getContext("2d");
1825
+ if (!ctx) return;
1826
+ ctx.font = "13px system-ui, -apple-system, sans-serif";
1827
+ for (const key of columnKeys) {
1828
+ let maxWidth = ctx.measureText(key).width + 56;
1829
+ for (let i = 0; i < sampleSize; i++) {
1830
+ const value = data[i][key];
1831
+ const text = value === null || value === void 0 ? "" : String(value);
1832
+ const width = ctx.measureText(text).width + 28;
1833
+ maxWidth = Math.max(maxWidth, width);
1834
+ }
1835
+ widths[key] = Math.min(Math.max(maxWidth, MIN_COL_WIDTH), MAX_COL_WIDTH);
1836
+ }
1837
+ setColumnWidths(widths);
1838
+ }, [data, columnKeys]);
1839
+ const startColumnResize = useCallback8(
1840
+ (columnId, event) => {
1841
+ if (!enableColumnResize) return;
1842
+ event.preventDefault();
1843
+ event.stopPropagation();
1844
+ setResizingColumnId(columnId);
1845
+ setResizeStartX(event.clientX);
1846
+ setResizeStartWidth(columnWidths[columnId] || MIN_COL_WIDTH);
1847
+ },
1848
+ [enableColumnResize, columnWidths]
1849
+ );
1850
+ useEffect3(() => {
1851
+ if (!resizingColumnId) return;
1852
+ const handleResizeMove = (event) => {
1853
+ const diff = event.clientX - resizeStartX;
1854
+ const newWidth = Math.max(MIN_COL_WIDTH, Math.min(MAX_COL_WIDTH, resizeStartWidth + diff));
1855
+ setColumnWidths((prev) => ({
1856
+ ...prev,
1857
+ [resizingColumnId]: newWidth
1858
+ }));
1859
+ };
1860
+ const handleResizeEnd = () => {
1861
+ setResizingColumnId(null);
1862
+ };
1863
+ document.addEventListener("mousemove", handleResizeMove);
1864
+ document.addEventListener("mouseup", handleResizeEnd);
1865
+ return () => {
1866
+ document.removeEventListener("mousemove", handleResizeMove);
1867
+ document.removeEventListener("mouseup", handleResizeEnd);
1868
+ };
1869
+ }, [resizingColumnId, resizeStartX, resizeStartWidth]);
1870
+ const startVerticalResize = useCallback8(
1871
+ (event) => {
1872
+ if (!enableVerticalResize) return;
1873
+ event.preventDefault();
1874
+ setIsResizingVertically(true);
1875
+ setVerticalResizeStartY(event.clientY);
1876
+ setVerticalResizeStartHeight(gridHeight);
1877
+ },
1878
+ [enableVerticalResize, gridHeight]
1879
+ );
1880
+ useEffect3(() => {
1881
+ if (!isResizingVertically) return;
1882
+ const handleVerticalResizeMove = (event) => {
1883
+ const diff = event.clientY - verticalResizeStartY;
1884
+ const newHeight = Math.max(minHeight, Math.min(maxHeight, verticalResizeStartHeight + diff));
1885
+ setGridHeight(newHeight);
1886
+ };
1887
+ const handleVerticalResizeEnd = () => {
1888
+ setIsResizingVertically(false);
1889
+ };
1890
+ document.addEventListener("mousemove", handleVerticalResizeMove);
1891
+ document.addEventListener("mouseup", handleVerticalResizeEnd);
1892
+ return () => {
1893
+ document.removeEventListener("mousemove", handleVerticalResizeMove);
1894
+ document.removeEventListener("mouseup", handleVerticalResizeEnd);
1895
+ };
1896
+ }, [isResizingVertically, verticalResizeStartY, verticalResizeStartHeight, minHeight, maxHeight]);
1897
+ const handleExport = useCallback8(() => {
1898
+ if (viewMode === "pivot") {
1899
+ if (!pivotResult) return;
1900
+ const pivotFilename = exportFilename.replace(".csv", "-pivot.csv");
1901
+ exportPivotToCSV(
1902
+ {
1903
+ headers: pivotResult.headers,
1904
+ rowHeaders: pivotResult.rowHeaders,
1905
+ data: pivotResult.data,
1906
+ rowTotals: pivotResult.rowTotals,
1907
+ columnTotals: pivotResult.columnTotals,
1908
+ grandTotal: pivotResult.grandTotal,
1909
+ showRowTotals: pivotShowRowTotals,
1910
+ showColumnTotals: pivotShowColumnTotals
1911
+ },
1912
+ pivotRowFields,
1913
+ pivotColumnFields,
1914
+ pivotValueFields,
1915
+ { filename: pivotFilename }
1916
+ );
1917
+ onExport?.({ rowCount: pivotResult.rowHeaders.length, filename: pivotFilename });
1918
+ return;
1919
+ }
1920
+ const dataToExport = enableSearch && globalSearchTerm.trim() ? searchFilteredData.map((row) => row.original) : rows.map((row) => row.original);
1921
+ exportToCSV(dataToExport, columnKeys, {
1922
+ filename: exportFilename,
1923
+ includeHeaders: true
1924
+ });
1925
+ onExport?.({ rowCount: dataToExport.length, filename: exportFilename });
1926
+ }, [
1927
+ viewMode,
1928
+ pivotResult,
1929
+ exportFilename,
1930
+ pivotShowRowTotals,
1931
+ pivotShowColumnTotals,
1932
+ pivotRowFields,
1933
+ pivotColumnFields,
1934
+ pivotValueFields,
1935
+ enableSearch,
1936
+ globalSearchTerm,
1937
+ searchFilteredData,
1938
+ rows,
1939
+ columnKeys,
1940
+ onExport
1941
+ ]);
1942
+ const copySelectionToClipboard = useCallback8(() => {
1943
+ if (!selectionBounds || !enableClipboard) return;
1944
+ const text = formatSelectionForClipboard(
1945
+ rows.map((r) => r.original),
1946
+ columnKeys,
1947
+ selectionBounds
1948
+ );
1949
+ copyToClipboard(
1950
+ text,
1951
+ () => {
1952
+ const cellCount = (selectionBounds.maxRow - selectionBounds.minRow + 1) * (selectionBounds.maxCol - selectionBounds.minCol + 1);
1953
+ setCopyToastMessage(`Copied ${cellCount} cell${cellCount > 1 ? "s" : ""}`);
1954
+ setShowCopyToast(true);
1955
+ setTimeout(() => setShowCopyToast(false), 2e3);
1956
+ onCopy?.({ text, cellCount });
1957
+ },
1958
+ (err) => {
1959
+ setCopyToastMessage("Copy failed");
1960
+ setShowCopyToast(true);
1961
+ setTimeout(() => setShowCopyToast(false), 2e3);
1962
+ console.error("Copy failed:", err);
1963
+ }
1964
+ );
1965
+ }, [selectionBounds, enableClipboard, rows, columnKeys, onCopy]);
1966
+ const handleMouseDown = useCallback8(
1967
+ (rowIndex, colIndex, event) => {
1968
+ event.preventDefault();
1969
+ if (event.shiftKey && selectedCell) {
1970
+ setSelectionEnd({ row: rowIndex, col: colIndex });
1971
+ } else {
1972
+ setSelectedCell({ row: rowIndex, col: colIndex });
1973
+ setSelectionStart({ row: rowIndex, col: colIndex });
1974
+ setSelectionEnd({ row: rowIndex, col: colIndex });
1975
+ setIsSelecting(true);
1976
+ }
1977
+ const row = rows[rowIndex];
1978
+ if (row) {
1979
+ const colId = columnKeys[colIndex];
1980
+ onCellClick?.({
1981
+ row: rowIndex,
1982
+ col: colIndex,
1983
+ value: row.original[colId],
1984
+ rowData: row.original
1985
+ });
1986
+ }
1987
+ },
1988
+ [selectedCell, rows, columnKeys, onCellClick]
1989
+ );
1990
+ const handleMouseEnter = useCallback8(
1991
+ (rowIndex, colIndex) => {
1992
+ if (isSelecting) {
1993
+ setSelectionEnd({ row: rowIndex, col: colIndex });
1994
+ }
1995
+ },
1996
+ [isSelecting]
1997
+ );
1998
+ useEffect3(() => {
1999
+ const handleMouseUp = () => setIsSelecting(false);
2000
+ document.addEventListener("mouseup", handleMouseUp);
2001
+ return () => document.removeEventListener("mouseup", handleMouseUp);
2002
+ }, []);
2003
+ useEffect3(() => {
2004
+ const handleKeydown = (event) => {
2005
+ if ((event.ctrlKey || event.metaKey) && event.key === "c" && selectionBounds) {
2006
+ event.preventDefault();
2007
+ copySelectionToClipboard();
2008
+ return;
2009
+ }
2010
+ if (event.key === "Escape") {
2011
+ setSelectedCell(null);
2012
+ setSelectionStart(null);
2013
+ setSelectionEnd(null);
2014
+ setShowSearchInput(false);
2015
+ setGlobalSearchTerm("");
2016
+ }
2017
+ };
2018
+ document.addEventListener("keydown", handleKeydown);
2019
+ return () => document.removeEventListener("keydown", handleKeydown);
2020
+ }, [selectionBounds, copySelectionToClipboard]);
2021
+ const openFilterDropdown = useCallback8(
2022
+ (columnId, event) => {
2023
+ event.stopPropagation();
2024
+ const target = event.currentTarget;
2025
+ const headerCell = target.closest(".vpg-header-cell");
2026
+ const rect = headerCell?.getBoundingClientRect() || target.getBoundingClientRect();
2027
+ const dropdownWidth = 280;
2028
+ const padding = 12;
2029
+ let left = rect.left;
2030
+ if (left + dropdownWidth > window.innerWidth - padding) {
2031
+ left = window.innerWidth - dropdownWidth - padding;
2032
+ }
2033
+ left = Math.max(padding, left);
2034
+ const spaceBelow = window.innerHeight - rect.bottom - padding;
2035
+ const spaceAbove = rect.top - padding;
2036
+ let top;
2037
+ let maxDropdownHeight;
2038
+ if (spaceBelow >= 300 || spaceBelow >= spaceAbove) {
2039
+ top = rect.bottom + 4;
2040
+ maxDropdownHeight = Math.min(400, spaceBelow - 4);
2041
+ } else {
2042
+ maxDropdownHeight = Math.min(400, spaceAbove - 4);
2043
+ top = rect.top - maxDropdownHeight - 4;
2044
+ }
2045
+ setFilterDropdownPosition({ top, left, maxHeight: maxDropdownHeight });
2046
+ setActiveFilterColumn(columnId);
2047
+ },
2048
+ []
2049
+ );
2050
+ const closeFilterDropdown = useCallback8(() => {
2051
+ setActiveFilterColumn(null);
2052
+ }, []);
2053
+ const handleFilter = useCallback8(
2054
+ (columnId, values) => {
2055
+ setColumnFilter(columnId, values);
2056
+ },
2057
+ [setColumnFilter]
2058
+ );
2059
+ const handleSort = useCallback8(
2060
+ (columnId, direction) => {
2061
+ if (direction === null) {
2062
+ const current = getSortDirection(columnId);
2063
+ if (current) {
2064
+ toggleSort(columnId);
2065
+ if (getSortDirection(columnId)) {
2066
+ toggleSort(columnId);
2067
+ }
2068
+ }
2069
+ } else {
2070
+ const current = getSortDirection(columnId);
2071
+ if (current === null) {
2072
+ toggleSort(columnId);
2073
+ if (direction === "desc" && getSortDirection(columnId) === "asc") {
2074
+ toggleSort(columnId);
2075
+ }
2076
+ } else if (current !== direction) {
2077
+ toggleSort(columnId);
2078
+ }
2079
+ }
2080
+ },
2081
+ [getSortDirection, toggleSort]
2082
+ );
2083
+ const isCellSelected = useCallback8(
2084
+ (rowIndex, colIndex) => {
2085
+ if (!selectionBounds) {
2086
+ return selectedCell?.row === rowIndex && selectedCell?.col === colIndex;
2087
+ }
2088
+ const { minRow, maxRow, minCol, maxCol } = selectionBounds;
2089
+ return rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol;
2090
+ },
2091
+ [selectionBounds, selectedCell]
2092
+ );
2093
+ const formatStatValue = (value) => {
2094
+ if (value === null) return "-";
2095
+ if (Math.abs(value) >= 1e3) {
2096
+ return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
2097
+ }
2098
+ return value.toLocaleString("en-US", { maximumFractionDigits: 4 });
2099
+ };
2100
+ const noFormatPatterns = /^(?:.*_)?(?:id|code|year|month|quarter|day|week|date|zip|phone|fax|ssn|ein|npi|ndc|gpi|hcpcs|icd|cpt|rx|bin|pcn|group|member|claim|rx_number|script|fill)(?:_.*)?$/i;
2101
+ const shouldFormatNumber = (columnId) => {
2102
+ return !noFormatPatterns.test(columnId);
2103
+ };
2104
+ const formatCellValueDisplay = (value, columnId) => {
2105
+ if (value === null || value === void 0) return "";
2106
+ if (value === "") return "";
2107
+ const stats = getColumnStats(columnId);
2108
+ if (stats.type === "number") {
2109
+ const num = typeof value === "number" ? value : Number.parseFloat(String(value));
2110
+ if (Number.isNaN(num)) return String(value);
2111
+ if (shouldFormatNumber(columnId) && Math.abs(num) >= 1e3) {
2112
+ return num.toLocaleString("en-US", { maximumFractionDigits: 2 });
2113
+ }
2114
+ if (Number.isInteger(num)) {
2115
+ return String(num);
2116
+ }
2117
+ return num.toLocaleString("en-US", { maximumFractionDigits: 4, useGrouping: false });
2118
+ }
2119
+ return String(value);
2120
+ };
2121
+ const totalTableWidth = useMemo8(() => {
2122
+ return columnKeys.reduce((sum, key) => sum + (columnWidths[key] || MIN_COL_WIDTH), 0);
2123
+ }, [columnKeys, columnWidths]);
2124
+ const activeFilterCount = columnFilters.length;
2125
+ return /* @__PURE__ */ jsxs4(
2126
+ "div",
2127
+ {
2128
+ className: `vpg-data-grid vpg-font-${currentFontSize} vpg-theme-${currentTheme} ${stripedRows ? "vpg-striped" : ""} ${resizingColumnId ? "vpg-resizing" : ""} ${isResizingVertically ? "vpg-resizing-vertical" : ""}`,
2129
+ style: { height: `${gridHeight}px` },
2130
+ children: [
2131
+ showCopyToast && /* @__PURE__ */ jsxs4("div", { className: "vpg-toast", children: [
2132
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) }),
2133
+ copyToastMessage
2134
+ ] }),
2135
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-toolbar", children: [
2136
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-toolbar-left", children: [
2137
+ showPivot && /* @__PURE__ */ jsxs4("div", { className: "vpg-view-toggle", children: [
2138
+ /* @__PURE__ */ jsxs4(
2139
+ "button",
2140
+ {
2141
+ className: `vpg-view-btn ${viewMode === "grid" ? "active" : ""}`,
2142
+ onClick: () => setViewMode("grid"),
2143
+ children: [
2144
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2145
+ "path",
2146
+ {
2147
+ strokeLinecap: "round",
2148
+ strokeLinejoin: "round",
2149
+ strokeWidth: 2,
2150
+ d: "M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
2151
+ }
2152
+ ) }),
2153
+ "Grid"
2154
+ ]
2155
+ }
2156
+ ),
2157
+ /* @__PURE__ */ jsxs4(
2158
+ "button",
2159
+ {
2160
+ className: `vpg-view-btn vpg-pivot-btn ${viewMode === "pivot" ? "active" : ""}`,
2161
+ onClick: () => setViewMode("pivot"),
2162
+ children: [
2163
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2164
+ "path",
2165
+ {
2166
+ strokeLinecap: "round",
2167
+ strokeLinejoin: "round",
2168
+ strokeWidth: 2,
2169
+ d: "M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"
2170
+ }
2171
+ ) }),
2172
+ "Pivot"
2173
+ ]
2174
+ }
2175
+ )
2176
+ ] }),
2177
+ viewMode === "grid" && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2178
+ enableSearch && /* @__PURE__ */ jsx4("div", { className: "vpg-search-container", children: !showSearchInput ? /* @__PURE__ */ jsx4(
2179
+ "button",
2180
+ {
2181
+ className: "vpg-icon-btn",
2182
+ title: "Search (Ctrl+F)",
2183
+ onClick: () => setShowSearchInput(true),
2184
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2185
+ "path",
2186
+ {
2187
+ strokeLinecap: "round",
2188
+ strokeLinejoin: "round",
2189
+ strokeWidth: 2,
2190
+ d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
2191
+ }
2192
+ ) })
2193
+ }
2194
+ ) : /* @__PURE__ */ jsxs4("div", { className: "vpg-search-box", children: [
2195
+ /* @__PURE__ */ jsx4(
2196
+ "svg",
2197
+ {
2198
+ className: "vpg-search-icon",
2199
+ fill: "none",
2200
+ stroke: "currentColor",
2201
+ viewBox: "0 0 24 24",
2202
+ children: /* @__PURE__ */ jsx4(
2203
+ "path",
2204
+ {
2205
+ strokeLinecap: "round",
2206
+ strokeLinejoin: "round",
2207
+ strokeWidth: 2,
2208
+ d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
2209
+ }
2210
+ )
2211
+ }
2212
+ ),
2213
+ /* @__PURE__ */ jsx4(
2214
+ "input",
2215
+ {
2216
+ type: "text",
2217
+ value: globalSearchTerm,
2218
+ onChange: (e) => setGlobalSearchTerm(e.target.value),
2219
+ className: "vpg-search-input",
2220
+ placeholder: "Search all columns...",
2221
+ onKeyDown: (e) => {
2222
+ if (e.key === "Escape") {
2223
+ setShowSearchInput(false);
2224
+ setGlobalSearchTerm("");
2225
+ }
2226
+ },
2227
+ autoFocus: true
2228
+ }
2229
+ ),
2230
+ globalSearchTerm && /* @__PURE__ */ jsx4("button", { className: "vpg-search-clear", onClick: () => setGlobalSearchTerm(""), children: /* @__PURE__ */ jsx4(
2231
+ "svg",
2232
+ {
2233
+ className: "vpg-icon-xs",
2234
+ fill: "none",
2235
+ stroke: "currentColor",
2236
+ viewBox: "0 0 24 24",
2237
+ children: /* @__PURE__ */ jsx4(
2238
+ "path",
2239
+ {
2240
+ strokeLinecap: "round",
2241
+ strokeLinejoin: "round",
2242
+ strokeWidth: 2,
2243
+ d: "M6 18L18 6M6 6l12 12"
2244
+ }
2245
+ )
2246
+ }
2247
+ ) })
2248
+ ] }) }),
2249
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-font-size-control", children: [
2250
+ /* @__PURE__ */ jsx4("span", { className: "vpg-label", children: "Size:" }),
2251
+ /* @__PURE__ */ jsx4("div", { className: "vpg-font-size-toggle", children: fontSizeOptions.map((opt) => /* @__PURE__ */ jsx4(
2252
+ "button",
2253
+ {
2254
+ className: `vpg-font-size-btn ${currentFontSize === opt.value ? "active" : ""}`,
2255
+ onClick: () => setCurrentFontSize(opt.value),
2256
+ children: opt.label
2257
+ },
2258
+ opt.value
2259
+ )) })
2260
+ ] }),
2261
+ activeFilterCount > 0 && /* @__PURE__ */ jsxs4("div", { className: "vpg-filter-info", children: [
2262
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx4(
2263
+ "path",
2264
+ {
2265
+ fillRule: "evenodd",
2266
+ d: "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z",
2267
+ clipRule: "evenodd"
2268
+ }
2269
+ ) }),
2270
+ /* @__PURE__ */ jsxs4("span", { children: [
2271
+ activeFilterCount,
2272
+ " filter",
2273
+ activeFilterCount > 1 ? "s" : ""
2274
+ ] })
2275
+ ] }),
2276
+ globalSearchTerm && /* @__PURE__ */ jsx4("div", { className: "vpg-search-info", children: /* @__PURE__ */ jsxs4("span", { children: [
2277
+ totalSearchedRows,
2278
+ " match",
2279
+ totalSearchedRows !== 1 ? "es" : ""
2280
+ ] }) })
2281
+ ] }),
2282
+ viewMode === "pivot" && canUsePivot && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2283
+ /* @__PURE__ */ jsxs4(
2284
+ "button",
2285
+ {
2286
+ className: `vpg-config-toggle ${showPivotConfig ? "active" : ""}`,
2287
+ onClick: () => setShowPivotConfig(!showPivotConfig),
2288
+ children: [
2289
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2290
+ "path",
2291
+ {
2292
+ strokeLinecap: "round",
2293
+ strokeLinejoin: "round",
2294
+ strokeWidth: 2,
2295
+ d: "M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
2296
+ }
2297
+ ) }),
2298
+ showPivotConfig ? "Hide" : "Show",
2299
+ " Config"
2300
+ ]
2301
+ }
2302
+ ),
2303
+ pivotIsConfigured && /* @__PURE__ */ jsxs4("div", { className: "vpg-pivot-status", children: [
2304
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx4(
2305
+ "path",
2306
+ {
2307
+ fillRule: "evenodd",
2308
+ d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z",
2309
+ clipRule: "evenodd"
2310
+ }
2311
+ ) }),
2312
+ /* @__PURE__ */ jsx4("span", { children: "Pivot configured" })
2313
+ ] })
2314
+ ] })
2315
+ ] }),
2316
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-toolbar-right", children: [
2317
+ viewMode === "grid" && activeFilterCount > 0 && /* @__PURE__ */ jsxs4("button", { className: "vpg-clear-filters", onClick: clearAllFilters, children: [
2318
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2319
+ "path",
2320
+ {
2321
+ strokeLinecap: "round",
2322
+ strokeLinejoin: "round",
2323
+ strokeWidth: 2,
2324
+ d: "M6 18L18 6M6 6l12 12"
2325
+ }
2326
+ ) }),
2327
+ "Clear Filters"
2328
+ ] }),
2329
+ enableClipboard && selectionBounds && viewMode === "grid" && /* @__PURE__ */ jsx4(
2330
+ "button",
2331
+ {
2332
+ className: "vpg-icon-btn",
2333
+ title: "Copy selection (Ctrl+C)",
2334
+ onClick: copySelectionToClipboard,
2335
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2336
+ "path",
2337
+ {
2338
+ strokeLinecap: "round",
2339
+ strokeLinejoin: "round",
2340
+ strokeWidth: 2,
2341
+ d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
2342
+ }
2343
+ ) })
2344
+ }
2345
+ ),
2346
+ enableExport && (viewMode === "grid" || viewMode === "pivot" && pivotIsConfigured) && /* @__PURE__ */ jsxs4(
2347
+ "button",
2348
+ {
2349
+ className: "vpg-export-btn",
2350
+ title: viewMode === "pivot" ? "Export Pivot to CSV" : "Export to CSV",
2351
+ onClick: handleExport,
2352
+ children: [
2353
+ /* @__PURE__ */ jsx4("svg", { className: "vpg-icon", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2354
+ "path",
2355
+ {
2356
+ strokeLinecap: "round",
2357
+ strokeLinejoin: "round",
2358
+ strokeWidth: 2,
2359
+ d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
2360
+ }
2361
+ ) }),
2362
+ "Export",
2363
+ viewMode === "pivot" ? " Pivot" : ""
2364
+ ]
2365
+ }
2366
+ )
2367
+ ] })
2368
+ ] }),
2369
+ viewMode === "grid" && /* @__PURE__ */ jsxs4("div", { ref: tableContainerRef, className: "vpg-grid-container", tabIndex: 0, children: [
2370
+ loading && /* @__PURE__ */ jsxs4("div", { className: "vpg-loading", children: [
2371
+ /* @__PURE__ */ jsx4("div", { className: "vpg-spinner" }),
2372
+ /* @__PURE__ */ jsx4("span", { children: "Loading data..." })
2373
+ ] }),
2374
+ !loading && data.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "vpg-empty", children: [
2375
+ /* @__PURE__ */ jsx4("div", { className: "vpg-empty-icon", children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-lg", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2376
+ "path",
2377
+ {
2378
+ strokeLinecap: "round",
2379
+ strokeLinejoin: "round",
2380
+ strokeWidth: 1.5,
2381
+ d: "M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
2382
+ }
2383
+ ) }) }),
2384
+ /* @__PURE__ */ jsx4("span", { children: "No data available" })
2385
+ ] }),
2386
+ !loading && data.length > 0 && filteredRowCount === 0 && /* @__PURE__ */ jsxs4("div", { className: "vpg-empty", children: [
2387
+ /* @__PURE__ */ jsx4("div", { className: "vpg-empty-icon vpg-warning", children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-lg", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2388
+ "path",
2389
+ {
2390
+ strokeLinecap: "round",
2391
+ strokeLinejoin: "round",
2392
+ strokeWidth: 1.5,
2393
+ d: "M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
2394
+ }
2395
+ ) }) }),
2396
+ /* @__PURE__ */ jsx4("span", { children: "No matching records" }),
2397
+ /* @__PURE__ */ jsx4("button", { className: "vpg-clear-link", onClick: clearAllFilters, children: "Clear all filters" })
2398
+ ] }),
2399
+ !loading && filteredRowCount > 0 && /* @__PURE__ */ jsx4("div", { className: "vpg-table-wrapper", children: /* @__PURE__ */ jsxs4("table", { className: "vpg-table", style: { minWidth: `${totalTableWidth}px` }, children: [
2400
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsx4("tr", { children: columnKeys.map((colId, colIndex) => /* @__PURE__ */ jsxs4(
2401
+ "th",
2402
+ {
2403
+ className: `vpg-header-cell ${hasActiveFilter(colId) ? "vpg-has-filter" : ""} ${getSortDirection(colId) !== null ? "vpg-is-sorted" : ""} ${activeFilterColumn === colId ? "vpg-is-active" : ""}`,
2404
+ style: {
2405
+ width: `${columnWidths[colId] || MIN_COL_WIDTH}px`,
2406
+ minWidth: `${columnWidths[colId] || MIN_COL_WIDTH}px`
2407
+ },
2408
+ onClick: (e) => {
2409
+ const target = e.target;
2410
+ if (target.closest(".vpg-dropdown-arrow")) {
2411
+ openFilterDropdown(colId, e);
2412
+ }
2413
+ },
2414
+ children: [
2415
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-header-content", children: [
2416
+ /* @__PURE__ */ jsx4("span", { className: "vpg-header-text", children: colId }),
2417
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-header-icons", children: [
2418
+ getSortDirection(colId) && /* @__PURE__ */ jsx4("span", { className: "vpg-sort-indicator", children: getSortDirection(colId) === "asc" ? /* @__PURE__ */ jsx4(
2419
+ "svg",
2420
+ {
2421
+ className: "vpg-icon-sm",
2422
+ fill: "currentColor",
2423
+ viewBox: "0 0 20 20",
2424
+ children: /* @__PURE__ */ jsx4(
2425
+ "path",
2426
+ {
2427
+ fillRule: "evenodd",
2428
+ d: "M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z",
2429
+ clipRule: "evenodd"
2430
+ }
2431
+ )
2432
+ }
2433
+ ) : /* @__PURE__ */ jsx4(
2434
+ "svg",
2435
+ {
2436
+ className: "vpg-icon-sm",
2437
+ fill: "currentColor",
2438
+ viewBox: "0 0 20 20",
2439
+ children: /* @__PURE__ */ jsx4(
2440
+ "path",
2441
+ {
2442
+ fillRule: "evenodd",
2443
+ d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z",
2444
+ clipRule: "evenodd"
2445
+ }
2446
+ )
2447
+ }
2448
+ ) }),
2449
+ hasActiveFilter(colId) && /* @__PURE__ */ jsx4("span", { className: "vpg-filter-indicator", children: /* @__PURE__ */ jsx4(
2450
+ "svg",
2451
+ {
2452
+ className: "vpg-icon-xs",
2453
+ fill: "currentColor",
2454
+ viewBox: "0 0 20 20",
2455
+ children: /* @__PURE__ */ jsx4(
2456
+ "path",
2457
+ {
2458
+ fillRule: "evenodd",
2459
+ d: "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z",
2460
+ clipRule: "evenodd"
2461
+ }
2462
+ )
2463
+ }
2464
+ ) }),
2465
+ /* @__PURE__ */ jsx4("span", { className: "vpg-dropdown-arrow", title: "Filter & Sort", children: /* @__PURE__ */ jsx4(
2466
+ "svg",
2467
+ {
2468
+ className: "vpg-icon-sm",
2469
+ fill: "none",
2470
+ stroke: "currentColor",
2471
+ viewBox: "0 0 24 24",
2472
+ children: /* @__PURE__ */ jsx4(
2473
+ "path",
2474
+ {
2475
+ strokeLinecap: "round",
2476
+ strokeLinejoin: "round",
2477
+ strokeWidth: 2,
2478
+ d: "M19 9l-7 7-7-7"
2479
+ }
2480
+ )
2481
+ }
2482
+ ) })
2483
+ ] })
2484
+ ] }),
2485
+ enableColumnResize && /* @__PURE__ */ jsx4(
2486
+ "div",
2487
+ {
2488
+ className: "vpg-resize-handle",
2489
+ onMouseDown: (e) => startColumnResize(colId, e)
2490
+ }
2491
+ )
2492
+ ]
2493
+ },
2494
+ colId
2495
+ )) }) }),
2496
+ /* @__PURE__ */ jsx4("tbody", { ref: tableBodyRef, children: paginatedRows.map((row, rowIndex) => /* @__PURE__ */ jsx4("tr", { className: "vpg-row", children: columnKeys.map((colId, colIndex) => /* @__PURE__ */ jsx4(
2497
+ "td",
2498
+ {
2499
+ className: `vpg-cell ${isCellSelected(rowIndex, colIndex) ? "vpg-selected" : ""} ${getColumnStats(colId).type === "number" ? "vpg-is-number" : ""}`,
2500
+ "data-row": rowIndex,
2501
+ "data-col": colIndex,
2502
+ style: {
2503
+ width: `${columnWidths[colId] || MIN_COL_WIDTH}px`,
2504
+ minWidth: `${columnWidths[colId] || MIN_COL_WIDTH}px`
2505
+ },
2506
+ onMouseDown: (e) => handleMouseDown(rowIndex, colIndex, e),
2507
+ onMouseEnter: () => handleMouseEnter(rowIndex, colIndex),
2508
+ children: formatCellValueDisplay(row.original[colId], colId)
2509
+ },
2510
+ colId
2511
+ )) }, row.id)) })
2512
+ ] }) })
2513
+ ] }),
2514
+ viewMode === "pivot" && /* @__PURE__ */ jsxs4("div", { className: "vpg-pivot-container", children: [
2515
+ showPivotConfig && canUsePivot && /* @__PURE__ */ jsx4("div", { className: "vpg-pivot-config-panel", children: /* @__PURE__ */ jsx4(
2516
+ PivotConfig,
2517
+ {
2518
+ availableFields: pivotAvailableFields,
2519
+ rowFields: pivotRowFields,
2520
+ columnFields: pivotColumnFields,
2521
+ valueFields: pivotValueFields,
2522
+ showRowTotals: pivotShowRowTotals,
2523
+ showColumnTotals: pivotShowColumnTotals,
2524
+ onShowRowTotalsChange: setPivotShowRowTotals,
2525
+ onShowColumnTotalsChange: setPivotShowColumnTotals,
2526
+ onClearConfig: clearPivotConfig,
2527
+ onAutoSuggest: autoSuggestConfig,
2528
+ onDragStart: (field, e) => setDraggingField(field),
2529
+ onDragEnd: () => setDraggingField(null),
2530
+ onUpdateAggregation: updateValueFieldAggregation,
2531
+ onAddRowField: addRowField,
2532
+ onRemoveRowField: removeRowField,
2533
+ onAddColumnField: addColumnField,
2534
+ onRemoveColumnField: removeColumnField,
2535
+ onAddValueField: addValueField,
2536
+ onRemoveValueField: removeValueField
2537
+ }
2538
+ ) }),
2539
+ /* @__PURE__ */ jsx4("div", { className: `vpg-pivot-main ${!showPivotConfig ? "vpg-full-width" : ""}`, children: /* @__PURE__ */ jsx4(
2540
+ PivotSkeleton,
2541
+ {
2542
+ rowFields: pivotRowFields,
2543
+ columnFields: pivotColumnFields,
2544
+ valueFields: pivotValueFields,
2545
+ isConfigured: pivotIsConfigured,
2546
+ draggingField,
2547
+ pivotResult,
2548
+ fontSize: currentFontSize,
2549
+ activeFilters: activeFilterInfo,
2550
+ totalRowCount,
2551
+ filteredRowCount,
2552
+ onAddRowField: addRowField,
2553
+ onRemoveRowField: removeRowField,
2554
+ onAddColumnField: addColumnField,
2555
+ onRemoveColumnField: removeColumnField,
2556
+ onAddValueField: addValueField,
2557
+ onRemoveValueField: removeValueField,
2558
+ onUpdateAggregation: updateValueFieldAggregation,
2559
+ onReorderRowFields: setRowFields,
2560
+ onReorderColumnFields: setColumnFields
2561
+ }
2562
+ ) })
2563
+ ] }),
2564
+ /* @__PURE__ */ jsxs4("div", { className: "vpg-footer", children: [
2565
+ /* @__PURE__ */ jsx4("div", { className: "vpg-footer-left", children: viewMode === "grid" ? enablePagination ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
2566
+ /* @__PURE__ */ jsxs4("span", { children: [
2567
+ ((currentPage - 1) * pageSize + 1).toLocaleString(),
2568
+ "-",
2569
+ Math.min(currentPage * pageSize, totalSearchedRows).toLocaleString()
2570
+ ] }),
2571
+ /* @__PURE__ */ jsx4("span", { className: "vpg-separator", children: "of" }),
2572
+ /* @__PURE__ */ jsx4("span", { children: totalSearchedRows.toLocaleString() }),
2573
+ totalSearchedRows !== totalRowCount && /* @__PURE__ */ jsxs4("span", { className: "vpg-filtered-note", children: [
2574
+ "(",
2575
+ totalRowCount.toLocaleString(),
2576
+ " total)"
2577
+ ] })
2578
+ ] }) : filteredRowCount === totalRowCount && totalSearchedRows === totalRowCount ? /* @__PURE__ */ jsxs4("span", { children: [
2579
+ totalRowCount.toLocaleString(),
2580
+ " records"
2581
+ ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
2582
+ /* @__PURE__ */ jsx4("span", { className: "vpg-filtered-count", children: totalSearchedRows.toLocaleString() }),
2583
+ /* @__PURE__ */ jsx4("span", { className: "vpg-separator", children: "of" }),
2584
+ /* @__PURE__ */ jsx4("span", { children: totalRowCount.toLocaleString() }),
2585
+ /* @__PURE__ */ jsx4("span", { className: "vpg-separator", children: "records" })
2586
+ ] }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
2587
+ /* @__PURE__ */ jsx4("span", { className: "vpg-pivot-label", children: "Pivot Table" }),
2588
+ /* @__PURE__ */ jsx4("span", { className: "vpg-separator", children: "\u2022" }),
2589
+ /* @__PURE__ */ jsxs4("span", { children: [
2590
+ totalRowCount.toLocaleString(),
2591
+ " source records"
2592
+ ] })
2593
+ ] }) }),
2594
+ enablePagination && viewMode === "grid" && totalPages > 1 && /* @__PURE__ */ jsxs4("div", { className: "vpg-pagination", children: [
2595
+ /* @__PURE__ */ jsx4(
2596
+ "button",
2597
+ {
2598
+ className: "vpg-page-btn",
2599
+ disabled: currentPage === 1,
2600
+ onClick: () => setCurrentPage(1),
2601
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2602
+ "path",
2603
+ {
2604
+ strokeLinecap: "round",
2605
+ strokeLinejoin: "round",
2606
+ strokeWidth: 2,
2607
+ d: "M11 19l-7-7 7-7m8 14l-7-7 7-7"
2608
+ }
2609
+ ) })
2610
+ }
2611
+ ),
2612
+ /* @__PURE__ */ jsx4(
2613
+ "button",
2614
+ {
2615
+ className: "vpg-page-btn",
2616
+ disabled: currentPage === 1,
2617
+ onClick: () => setCurrentPage((p) => Math.max(1, p - 1)),
2618
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2619
+ "path",
2620
+ {
2621
+ strokeLinecap: "round",
2622
+ strokeLinejoin: "round",
2623
+ strokeWidth: 2,
2624
+ d: "M15 19l-7-7 7-7"
2625
+ }
2626
+ ) })
2627
+ }
2628
+ ),
2629
+ /* @__PURE__ */ jsxs4("span", { className: "vpg-page-info", children: [
2630
+ "Page ",
2631
+ currentPage,
2632
+ " of ",
2633
+ totalPages
2634
+ ] }),
2635
+ /* @__PURE__ */ jsx4(
2636
+ "button",
2637
+ {
2638
+ className: "vpg-page-btn",
2639
+ disabled: currentPage === totalPages,
2640
+ onClick: () => setCurrentPage((p) => Math.min(totalPages, p + 1)),
2641
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2642
+ "path",
2643
+ {
2644
+ strokeLinecap: "round",
2645
+ strokeLinejoin: "round",
2646
+ strokeWidth: 2,
2647
+ d: "M9 5l7 7-7 7"
2648
+ }
2649
+ ) })
2650
+ }
2651
+ ),
2652
+ /* @__PURE__ */ jsx4(
2653
+ "button",
2654
+ {
2655
+ className: "vpg-page-btn",
2656
+ disabled: currentPage === totalPages,
2657
+ onClick: () => setCurrentPage(totalPages),
2658
+ children: /* @__PURE__ */ jsx4("svg", { className: "vpg-icon-sm", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx4(
2659
+ "path",
2660
+ {
2661
+ strokeLinecap: "round",
2662
+ strokeLinejoin: "round",
2663
+ strokeWidth: 2,
2664
+ d: "M13 5l7 7-7 7M5 5l7 7-7 7"
2665
+ }
2666
+ ) })
2667
+ }
2668
+ )
2669
+ ] }),
2670
+ viewMode === "grid" && selectionStats && selectionStats.count > 1 && /* @__PURE__ */ jsxs4("div", { className: "vpg-selection-stats", children: [
2671
+ /* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
2672
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Count:" }),
2673
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: selectionStats.count })
2674
+ ] }),
2675
+ selectionStats.numericCount > 0 && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2676
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-divider", children: "|" }),
2677
+ /* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
2678
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Sum:" }),
2679
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.sum) })
2680
+ ] }),
2681
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-divider", children: "|" }),
2682
+ /* @__PURE__ */ jsxs4("span", { className: "vpg-stat", children: [
2683
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-label", children: "Avg:" }),
2684
+ /* @__PURE__ */ jsx4("span", { className: "vpg-stat-value", children: formatStatValue(selectionStats.avg) })
2685
+ ] })
2686
+ ] })
2687
+ ] }),
2688
+ /* @__PURE__ */ jsx4("div", { className: "vpg-footer-right", children: isDemo ? /* @__PURE__ */ jsxs4("div", { className: "vpg-demo-banner", children: [
2689
+ /* @__PURE__ */ jsx4("span", { className: "vpg-demo-badge", children: "DEMO" }),
2690
+ /* @__PURE__ */ jsx4("span", { children: "Pro features enabled" }),
2691
+ /* @__PURE__ */ jsx4("a", { href: "https://tiny-pivot.com/#pricing", target: "_blank", rel: "noopener noreferrer", children: "Get License \u2192" })
2692
+ ] }) : showWatermark ? /* @__PURE__ */ jsx4("span", { className: "vpg-watermark-inline", children: /* @__PURE__ */ jsx4("a", { href: "https://tiny-pivot.com", target: "_blank", rel: "noopener noreferrer", children: "TinyPivot" }) }) : null })
2693
+ ] }),
2694
+ enableVerticalResize && /* @__PURE__ */ jsx4("div", { className: "vpg-vertical-resize-handle", onMouseDown: startVerticalResize, children: /* @__PURE__ */ jsxs4("div", { className: "vpg-resize-grip", children: [
2695
+ /* @__PURE__ */ jsx4("span", {}),
2696
+ /* @__PURE__ */ jsx4("span", {}),
2697
+ /* @__PURE__ */ jsx4("span", {})
2698
+ ] }) }),
2699
+ activeFilterColumn && createPortal(
2700
+ /* @__PURE__ */ jsx4(
2701
+ "div",
2702
+ {
2703
+ className: "vpg-filter-portal",
2704
+ style: {
2705
+ position: "fixed",
2706
+ top: `${filterDropdownPosition.top}px`,
2707
+ left: `${filterDropdownPosition.left}px`,
2708
+ maxHeight: `${filterDropdownPosition.maxHeight}px`,
2709
+ zIndex: 9999
2710
+ },
2711
+ children: /* @__PURE__ */ jsx4(
2712
+ ColumnFilter,
2713
+ {
2714
+ columnId: activeFilterColumn,
2715
+ columnName: activeFilterColumn,
2716
+ stats: getColumnStats(activeFilterColumn),
2717
+ selectedValues: getColumnFilterValues(activeFilterColumn),
2718
+ sortDirection: getSortDirection(activeFilterColumn),
2719
+ onFilter: (values) => handleFilter(activeFilterColumn, values),
2720
+ onSort: (dir) => handleSort(activeFilterColumn, dir),
2721
+ onClose: closeFilterDropdown
2722
+ }
2723
+ )
2724
+ }
2725
+ ),
2726
+ document.body
2727
+ )
2728
+ ]
2729
+ }
2730
+ );
2731
+ }
2732
+ export {
2733
+ ColumnFilter,
2734
+ DataGrid,
2735
+ PivotConfig,
2736
+ PivotSkeleton,
2737
+ configureLicenseSecret,
2738
+ copyToClipboard,
2739
+ enableDemoMode,
2740
+ exportPivotToCSV,
2741
+ exportToCSV,
2742
+ formatCellValue,
2743
+ formatSelectionForClipboard,
2744
+ getAggregationLabel,
2745
+ getColumnUniqueValues,
2746
+ setLicenseKey,
2747
+ useColumnResize,
2748
+ useExcelGrid,
2749
+ useGlobalSearch,
2750
+ useLicense,
2751
+ usePagination,
2752
+ usePivotTable,
2753
+ useRowSelection
2754
+ };
2755
+ //# sourceMappingURL=index.js.map