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