@gp-grid/core 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2438 +1,5 @@
1
- //#region src/utils/positioning.ts
2
- /**
3
- * Calculate cumulative column positions (prefix sums)
4
- * Returns an array where positions[i] is the left position of column i
5
- * positions[columns.length] is the total width
6
- */
7
- const calculateColumnPositions = (columns) => {
8
- const positions = [0];
9
- let pos = 0;
10
- for (const col of columns) {
11
- pos += col.width;
12
- positions.push(pos);
13
- }
14
- return positions;
15
- };
16
- /**
17
- * Get total width from column positions
18
- */
19
- const getTotalWidth = (columnPositions) => columnPositions[columnPositions.length - 1] ?? 0;
20
- /**
21
- * Calculate scaled column positions when container is wider than total column widths.
22
- * Columns expand proportionally based on their original width ratios.
23
- *
24
- * @param columns - Column definitions with original widths
25
- * @param containerWidth - Available container width
26
- * @returns Object with positions array and widths array
27
- */
28
- const calculateScaledColumnPositions = (columns, containerWidth) => {
29
- const originalPositions = calculateColumnPositions(columns);
30
- const totalOriginalWidth = getTotalWidth(originalPositions);
31
- if (containerWidth <= totalOriginalWidth || totalOriginalWidth === 0) return {
32
- positions: originalPositions,
33
- widths: columns.map((col) => col.width)
34
- };
35
- const scaleFactor = containerWidth / totalOriginalWidth;
36
- const scaledWidths = columns.map((col) => col.width * scaleFactor);
37
- const scaledPositions = [0];
38
- let pos = 0;
39
- for (const width of scaledWidths) {
40
- pos += width;
41
- scaledPositions.push(pos);
42
- }
43
- return {
44
- positions: scaledPositions,
45
- widths: scaledWidths
46
- };
47
- };
48
- /**
49
- * Find column index at a given X coordinate
50
- */
51
- const findColumnAtX = (x, columnPositions) => {
52
- for (let i = 0; i < columnPositions.length - 1; i++) if (x >= columnPositions[i] && x < columnPositions[i + 1]) return i;
53
- if (x >= columnPositions[columnPositions.length - 1]) return columnPositions.length - 2;
54
- return 0;
55
- };
56
-
57
- //#endregion
58
- //#region src/utils/classNames.ts
59
- /**
60
- * Normalize a cell range to ensure min/max values are correct
61
- * Handles ranges where start > end
62
- */
63
- const normalizeRange = (range) => ({
64
- minRow: Math.min(range.startRow, range.endRow),
65
- maxRow: Math.max(range.startRow, range.endRow),
66
- minCol: Math.min(range.startCol, range.endCol),
67
- maxCol: Math.max(range.startCol, range.endCol)
68
- });
69
- /**
70
- * Check if a cell position is within a normalized range
71
- */
72
- const isCellInRange = (row, col, range) => row >= range.minRow && row <= range.maxRow && col >= range.minCol && col <= range.maxCol;
73
- /**
74
- * Check if a cell is within the selection range
75
- */
76
- const isCellSelected = (row, col, selectionRange) => {
77
- if (!selectionRange) return false;
78
- return isCellInRange(row, col, normalizeRange(selectionRange));
79
- };
80
- /**
81
- * Check if a cell is the active cell
82
- */
83
- const isCellActive = (row, col, activeCell) => activeCell?.row === row && activeCell?.col === col;
84
- /**
85
- * Check if a row is within the visible range (not in overscan)
86
- */
87
- const isRowVisible = (row, visibleRowRange) => {
88
- if (!visibleRowRange) return true;
89
- if (visibleRowRange.end < 0 || visibleRowRange.start > visibleRowRange.end) return true;
90
- return row >= visibleRowRange.start && row <= visibleRowRange.end;
91
- };
92
- /**
93
- * Check if a cell is being edited
94
- */
95
- const isCellEditing = (row, col, editingCell) => editingCell?.row === row && editingCell?.col === col;
96
- /**
97
- * Check if a cell is in the fill preview range (vertical-only fill)
98
- */
99
- const isCellInFillPreview = (row, col, isDraggingFill, fillSourceRange, fillTarget) => {
100
- if (!isDraggingFill || !fillSourceRange || !fillTarget) return false;
101
- const { minRow, maxRow, minCol, maxCol } = normalizeRange(fillSourceRange);
102
- const fillDown = fillTarget.row > maxRow;
103
- const fillUp = fillTarget.row < minRow;
104
- if (fillDown) return row > maxRow && row <= fillTarget.row && col >= minCol && col <= maxCol;
105
- if (fillUp) return row < minRow && row >= fillTarget.row && col >= minCol && col <= maxCol;
106
- return false;
107
- };
108
- /**
109
- * Build cell CSS classes based on state
110
- */
111
- const buildCellClasses = (isActive, isSelected, isEditing, inFillPreview) => {
112
- const classes = ["gp-grid-cell"];
113
- if (isActive) classes.push("gp-grid-cell--active");
114
- if (isSelected && !isActive) classes.push("gp-grid-cell--selected");
115
- if (isEditing) classes.push("gp-grid-cell--editing");
116
- if (inFillPreview) classes.push("gp-grid-cell--fill-preview");
117
- return classes.join(" ");
118
- };
119
- /**
120
- * Check if a row overlaps the selection range
121
- */
122
- const isRowInSelectionRange = (rowIndex, range) => {
123
- if (!range) return false;
124
- const { minRow, maxRow } = normalizeRange(range);
125
- return rowIndex >= minRow && rowIndex <= maxRow;
126
- };
127
- /**
128
- * Check if a column overlaps the selection range
129
- */
130
- const isColumnInSelectionRange = (colIndex, range) => {
131
- if (!range) return false;
132
- const { minCol, maxCol } = normalizeRange(range);
133
- return colIndex >= minCol && colIndex <= maxCol;
134
- };
135
-
136
- //#endregion
137
- //#region src/utils/field-accessor.ts
138
- /**
139
- * Get a nested value from an object using dot notation path
140
- * @param data - The object to read from
141
- * @param field - Dot-separated path (e.g., "address.city")
142
- * @returns The value at the path or null if not found
143
- */
144
- const getFieldValue$2 = (data, field) => {
145
- const parts = field.split(".");
146
- let value = data;
147
- for (const part of parts) {
148
- if (value == null || typeof value !== "object") return null;
149
- value = value[part];
150
- }
151
- return value ?? null;
152
- };
153
- /**
154
- * Set a nested value in an object using dot notation path
155
- * Creates intermediate objects if they don't exist
156
- * @param data - The object to modify
157
- * @param field - Dot-separated path (e.g., "address.city")
158
- * @param value - The value to set
159
- */
160
- const setFieldValue$1 = (data, field, value) => {
161
- const parts = field.split(".");
162
- let obj = data;
163
- for (let i = 0; i < parts.length - 1; i++) {
164
- const part = parts[i];
165
- if (!(part in obj)) obj[part] = {};
166
- obj = obj[part];
167
- }
168
- const lastPart = parts[parts.length - 1];
169
- obj[lastPart] = value;
170
- };
171
-
172
- //#endregion
173
- //#region src/utils/event-emitter.ts
174
- /**
175
- * Create a simple instruction emitter for managers
176
- * Eliminates boilerplate listener management code
177
- */
178
- const createInstructionEmitter = () => {
179
- let listeners = [];
180
- const onInstruction = (listener) => {
181
- listeners.push(listener);
182
- return () => {
183
- listeners = listeners.filter((l) => l !== listener);
184
- };
185
- };
186
- const emit = (instruction) => {
187
- for (const listener of listeners) listener(instruction);
188
- };
189
- const clearListeners = () => {
190
- listeners = [];
191
- };
192
- return {
193
- onInstruction,
194
- emit,
195
- clearListeners
196
- };
197
- };
198
- /**
199
- * Create an instruction emitter with batch support
200
- * Used by GridCore and SlotPoolManager for efficient updates
201
- */
202
- const createBatchInstructionEmitter = () => {
203
- let listeners = [];
204
- let batchListeners = [];
205
- const onInstruction = (listener) => {
206
- listeners.push(listener);
207
- return () => {
208
- listeners = listeners.filter((l) => l !== listener);
209
- };
210
- };
211
- const onBatchInstruction = (listener) => {
212
- batchListeners.push(listener);
213
- return () => {
214
- batchListeners = batchListeners.filter((l) => l !== listener);
215
- };
216
- };
217
- const emit = (instruction) => {
218
- for (const listener of listeners) listener(instruction);
219
- for (const listener of batchListeners) listener([instruction]);
220
- };
221
- const emitBatch = (instructions) => {
222
- if (instructions.length === 0) return;
223
- for (const listener of batchListeners) listener(instructions);
224
- for (const instruction of instructions) for (const listener of listeners) listener(instruction);
225
- };
226
- const clearListeners = () => {
227
- listeners = [];
228
- batchListeners = [];
229
- };
230
- return {
231
- onInstruction,
232
- onBatchInstruction,
233
- emit,
234
- emitBatch,
235
- clearListeners
236
- };
237
- };
238
-
239
- //#endregion
240
- //#region src/selection.ts
241
- /**
242
- * Manages Excel-style cell selection, keyboard navigation, and clipboard operations.
243
- */
244
- var SelectionManager = class {
245
- state = {
246
- activeCell: null,
247
- range: null,
248
- anchor: null,
249
- selectionMode: false
250
- };
251
- options;
252
- emitter = createInstructionEmitter();
253
- onInstruction = this.emitter.onInstruction;
254
- emit = this.emitter.emit;
255
- constructor(options) {
256
- this.options = options;
257
- }
258
- getState() {
259
- return { ...this.state };
260
- }
261
- getActiveCell() {
262
- return this.state.activeCell;
263
- }
264
- getSelectionRange() {
265
- return this.state.range;
266
- }
267
- isSelected(row, col) {
268
- const { range } = this.state;
269
- if (!range) return false;
270
- const { minRow, maxRow, minCol, maxCol } = normalizeRange(range);
271
- return row >= minRow && row <= maxRow && col >= minCol && col <= maxCol;
272
- }
273
- isActiveCell(row, col) {
274
- const { activeCell } = this.state;
275
- return activeCell?.row === row && activeCell?.col === col;
276
- }
277
- /**
278
- * Start a selection at the given cell.
279
- * @param cell - The cell to select
280
- * @param opts.shift - Extend selection from anchor (range select)
281
- * @param opts.ctrl - Toggle selection mode
282
- */
283
- startSelection(cell, opts = {}) {
284
- const { shift = false, ctrl = false } = opts;
285
- const { row, col } = this.clampPosition(cell);
286
- if (shift && this.state.anchor) {
287
- this.state.range = {
288
- startRow: this.state.anchor.row,
289
- startCol: this.state.anchor.col,
290
- endRow: row,
291
- endCol: col
292
- };
293
- this.state.activeCell = {
294
- row,
295
- col
296
- };
297
- } else {
298
- this.state.activeCell = {
299
- row,
300
- col
301
- };
302
- this.state.anchor = {
303
- row,
304
- col
305
- };
306
- this.state.range = null;
307
- }
308
- this.state.selectionMode = ctrl;
309
- this.emit({
310
- type: "SET_ACTIVE_CELL",
311
- position: this.state.activeCell
312
- });
313
- this.emit({
314
- type: "SET_SELECTION_RANGE",
315
- range: this.state.range
316
- });
317
- }
318
- /**
319
- * Move focus in a direction, optionally extending the selection.
320
- */
321
- moveFocus(direction, extend = false) {
322
- if (!this.state.activeCell) {
323
- this.startSelection({
324
- row: 0,
325
- col: 0
326
- });
327
- return;
328
- }
329
- const { row, col } = this.state.activeCell;
330
- let newRow = row;
331
- let newCol = col;
332
- switch (direction) {
333
- case "up":
334
- newRow = Math.max(0, row - 1);
335
- break;
336
- case "down":
337
- newRow = Math.min(this.options.getRowCount() - 1, row + 1);
338
- break;
339
- case "left":
340
- newCol = Math.max(0, col - 1);
341
- break;
342
- case "right":
343
- newCol = Math.min(this.options.getColumnCount() - 1, col + 1);
344
- break;
345
- }
346
- if (extend) {
347
- if (!this.state.anchor) this.state.anchor = {
348
- row,
349
- col
350
- };
351
- this.state.range = {
352
- startRow: this.state.anchor.row,
353
- startCol: this.state.anchor.col,
354
- endRow: newRow,
355
- endCol: newCol
356
- };
357
- this.state.activeCell = {
358
- row: newRow,
359
- col: newCol
360
- };
361
- this.emit({
362
- type: "SET_ACTIVE_CELL",
363
- position: this.state.activeCell
364
- });
365
- this.emit({
366
- type: "SET_SELECTION_RANGE",
367
- range: this.state.range
368
- });
369
- } else {
370
- this.state.activeCell = {
371
- row: newRow,
372
- col: newCol
373
- };
374
- this.state.anchor = {
375
- row: newRow,
376
- col: newCol
377
- };
378
- this.state.range = null;
379
- this.emit({
380
- type: "SET_ACTIVE_CELL",
381
- position: this.state.activeCell
382
- });
383
- this.emit({
384
- type: "SET_SELECTION_RANGE",
385
- range: null
386
- });
387
- }
388
- }
389
- /**
390
- * Select all cells in the grid (Ctrl+A).
391
- */
392
- selectAll() {
393
- const rowCount = this.options.getRowCount();
394
- const colCount = this.options.getColumnCount();
395
- if (rowCount === 0 || colCount === 0) return;
396
- this.state.range = {
397
- startRow: 0,
398
- startCol: 0,
399
- endRow: rowCount - 1,
400
- endCol: colCount - 1
401
- };
402
- if (!this.state.activeCell) {
403
- this.state.activeCell = {
404
- row: 0,
405
- col: 0
406
- };
407
- this.emit({
408
- type: "SET_ACTIVE_CELL",
409
- position: this.state.activeCell
410
- });
411
- }
412
- this.emit({
413
- type: "SET_SELECTION_RANGE",
414
- range: this.state.range
415
- });
416
- }
417
- /**
418
- * Clear the current selection.
419
- */
420
- clearSelection() {
421
- this.state.activeCell = null;
422
- this.state.range = null;
423
- this.state.anchor = null;
424
- this.state.selectionMode = false;
425
- this.emit({
426
- type: "SET_ACTIVE_CELL",
427
- position: null
428
- });
429
- this.emit({
430
- type: "SET_SELECTION_RANGE",
431
- range: null
432
- });
433
- }
434
- /**
435
- * Set the active cell directly.
436
- */
437
- setActiveCell(row, col) {
438
- const clamped = this.clampPosition({
439
- row,
440
- col
441
- });
442
- this.state.activeCell = clamped;
443
- this.state.anchor = clamped;
444
- this.state.range = null;
445
- this.emit({
446
- type: "SET_ACTIVE_CELL",
447
- position: this.state.activeCell
448
- });
449
- this.emit({
450
- type: "SET_SELECTION_RANGE",
451
- range: null
452
- });
453
- }
454
- /**
455
- * Set the selection range directly.
456
- */
457
- setSelectionRange(range) {
458
- this.state.range = range;
459
- this.emit({
460
- type: "SET_SELECTION_RANGE",
461
- range: this.state.range
462
- });
463
- }
464
- /**
465
- * Get the data from the currently selected cells as a 2D array.
466
- */
467
- getSelectedData() {
468
- const { range, activeCell } = this.state;
469
- if (!range && !activeCell) return [];
470
- const { minRow, maxRow, minCol, maxCol } = normalizeRange(range || {
471
- startRow: activeCell.row,
472
- startCol: activeCell.col,
473
- endRow: activeCell.row,
474
- endCol: activeCell.col
475
- });
476
- const data = [];
477
- for (let r = minRow; r <= maxRow; r++) {
478
- const row = [];
479
- for (let c = minCol; c <= maxCol; c++) row.push(this.options.getCellValue(r, c));
480
- data.push(row);
481
- }
482
- return data;
483
- }
484
- /**
485
- * Copy the selected data to the clipboard (Ctrl+C).
486
- */
487
- async copySelectionToClipboard() {
488
- if (typeof navigator === "undefined" || typeof document === "undefined") return;
489
- const data = this.getSelectedData();
490
- if (data.length === 0) return;
491
- const tsv = data.map((row) => row.map((cell) => cell == null ? "" : String(cell)).join(" ")).join("\n");
492
- try {
493
- await navigator.clipboard.writeText(tsv);
494
- } catch (err) {
495
- const textarea = document.createElement("textarea");
496
- textarea.value = tsv;
497
- textarea.style.position = "fixed";
498
- textarea.style.left = "-9999px";
499
- document.body.appendChild(textarea);
500
- textarea.select();
501
- document.execCommand("copy");
502
- document.body.removeChild(textarea);
503
- }
504
- }
505
- /**
506
- * Clean up resources for garbage collection.
507
- */
508
- destroy() {
509
- this.emitter.clearListeners();
510
- this.state = {
511
- activeCell: null,
512
- range: null,
513
- anchor: null,
514
- selectionMode: false
515
- };
516
- }
517
- clampPosition(pos) {
518
- const rowCount = this.options.getRowCount();
519
- const colCount = this.options.getColumnCount();
520
- return {
521
- row: Math.max(0, Math.min(pos.row, rowCount - 1)),
522
- col: Math.max(0, Math.min(pos.col, colCount - 1))
523
- };
524
- }
525
- };
526
-
527
- //#endregion
528
- //#region src/fill.ts
529
- /**
530
- * Manages fill handle operations including pattern detection and auto-fill.
531
- */
532
- var FillManager = class {
533
- state = null;
534
- options;
535
- emitter = createInstructionEmitter();
536
- onInstruction = this.emitter.onInstruction;
537
- emit = this.emitter.emit;
538
- constructor(options) {
539
- this.options = options;
540
- }
541
- getState() {
542
- return this.state ? { ...this.state } : null;
543
- }
544
- isActive() {
545
- return this.state !== null;
546
- }
547
- /**
548
- * Start a fill drag operation from a source range.
549
- */
550
- startFillDrag(sourceRange) {
551
- this.state = {
552
- sourceRange,
553
- targetRow: sourceRange.endRow,
554
- targetCol: sourceRange.endCol
555
- };
556
- this.emit({
557
- type: "START_FILL",
558
- sourceRange
559
- });
560
- }
561
- /**
562
- * Update the fill drag target position.
563
- */
564
- updateFillDrag(targetRow, targetCol) {
565
- if (!this.state) return;
566
- const rowCount = this.options.getRowCount();
567
- const colCount = this.options.getColumnCount();
568
- targetRow = Math.max(0, Math.min(targetRow, rowCount - 1));
569
- targetCol = Math.max(0, Math.min(targetCol, colCount - 1));
570
- this.state.targetRow = targetRow;
571
- this.state.targetCol = targetCol;
572
- this.emit({
573
- type: "UPDATE_FILL",
574
- targetRow,
575
- targetCol
576
- });
577
- }
578
- /**
579
- * Commit the fill operation - apply pattern to target cells.
580
- */
581
- commitFillDrag() {
582
- if (!this.state) return;
583
- const { sourceRange, targetRow } = this.state;
584
- const filledCells = this.calculateFilledCells(sourceRange, targetRow);
585
- for (const { row, col, value } of filledCells) this.options.setCellValue(row, col, value);
586
- this.emit({
587
- type: "COMMIT_FILL",
588
- filledCells
589
- });
590
- this.state = null;
591
- }
592
- /**
593
- * Cancel the fill operation.
594
- */
595
- cancelFillDrag() {
596
- if (!this.state) return;
597
- this.state = null;
598
- this.emit({ type: "CANCEL_FILL" });
599
- }
600
- /**
601
- * Clean up resources for garbage collection.
602
- */
603
- destroy() {
604
- this.emitter.clearListeners();
605
- this.state = null;
606
- }
607
- /**
608
- * Calculate the values to fill based on source pattern.
609
- */
610
- calculateFilledCells(sourceRange, targetRow) {
611
- const result = [];
612
- const { minRow: srcMinRow, maxRow: srcMaxRow, minCol: srcMinCol, maxCol: srcMaxCol } = normalizeRange(sourceRange);
613
- const fillDown = targetRow > srcMaxRow;
614
- const fillUp = targetRow < srcMinRow;
615
- if (fillDown || fillUp) for (let col = srcMinCol; col <= srcMaxCol; col++) {
616
- const sourceValues = this.getSourceColumnValues(srcMinRow, srcMaxRow, col);
617
- const pattern = this.detectPattern(sourceValues);
618
- if (fillDown) for (let row = srcMaxRow + 1; row <= targetRow; row++) {
619
- const fillIndex = row - srcMaxRow - 1;
620
- const value = this.applyPattern(pattern, sourceValues, fillIndex);
621
- result.push({
622
- row,
623
- col,
624
- value
625
- });
626
- }
627
- else if (fillUp) for (let row = srcMinRow - 1; row >= targetRow; row--) {
628
- const fillIndex = srcMinRow - row - 1;
629
- const value = this.applyPattern(pattern, sourceValues, fillIndex, true);
630
- result.push({
631
- row,
632
- col,
633
- value
634
- });
635
- }
636
- }
637
- return result;
638
- }
639
- getSourceColumnValues(minRow, maxRow, col) {
640
- const values = [];
641
- for (let row = minRow; row <= maxRow; row++) values.push(this.options.getCellValue(row, col));
642
- return values;
643
- }
644
- detectPattern(values) {
645
- if (values.length === 0) return {
646
- type: "constant",
647
- value: null
648
- };
649
- if (values.length === 1) return {
650
- type: "constant",
651
- value: values[0] ?? null
652
- };
653
- const numbers = values.map((v) => typeof v === "number" ? v : Number(v));
654
- if (numbers.every((n) => !isNaN(n))) {
655
- const diffs = [];
656
- for (let i = 1; i < numbers.length; i++) diffs.push(numbers[i] - numbers[i - 1]);
657
- if (diffs.every((d) => d === diffs[0]) && diffs[0] !== void 0) return {
658
- type: "arithmetic",
659
- start: numbers[0],
660
- step: diffs[0]
661
- };
662
- }
663
- return {
664
- type: "repeat",
665
- values
666
- };
667
- }
668
- applyPattern(pattern, sourceValues, fillIndex, reverse = false) {
669
- switch (pattern.type) {
670
- case "constant": return pattern.value;
671
- case "arithmetic": {
672
- const multiplier = reverse ? -(fillIndex + 1) : fillIndex + 1;
673
- return (reverse ? pattern.start : pattern.start + pattern.step * (sourceValues.length - 1)) + pattern.step * multiplier;
674
- }
675
- case "repeat": {
676
- const len = pattern.values.length;
677
- if (len === 0) return null;
678
- if (reverse) {
679
- const idx = (len - 1 - fillIndex % len + len) % len;
680
- return pattern.values[idx] ?? null;
681
- }
682
- return pattern.values[fillIndex % len] ?? null;
683
- }
684
- }
685
- }
686
- };
687
-
688
- //#endregion
689
- //#region src/slot-pool.ts
690
- /**
691
- * Manages the slot pool for virtual scrolling.
692
- * Handles slot creation, recycling, positioning, and destruction.
693
- */
694
- var SlotPoolManager = class {
695
- state = {
696
- slots: /* @__PURE__ */ new Map(),
697
- rowToSlot: /* @__PURE__ */ new Map(),
698
- nextSlotId: 0
699
- };
700
- options;
701
- emitter = createBatchInstructionEmitter();
702
- isDestroyed = false;
703
- onInstruction = this.emitter.onInstruction;
704
- onBatchInstruction = this.emitter.onBatchInstruction;
705
- emit = this.emitter.emit;
706
- emitBatch = this.emitter.emitBatch;
707
- constructor(options) {
708
- this.options = options;
709
- }
710
- /**
711
- * Get the slot ID for a given row index.
712
- */
713
- getSlotForRow(rowIndex) {
714
- return this.state.rowToSlot.get(rowIndex);
715
- }
716
- /**
717
- * Get all current slots.
718
- */
719
- getSlots() {
720
- return this.state.slots;
721
- }
722
- /**
723
- * Synchronize slots with current viewport position.
724
- * This implements the slot recycling strategy.
725
- */
726
- syncSlots() {
727
- const scrollTop = this.options.getScrollTop();
728
- const rowHeight = this.options.getRowHeight();
729
- const viewportHeight = this.options.getViewportHeight();
730
- const totalRows = this.options.getTotalRows();
731
- const overscan = this.options.getOverscan();
732
- const visibleStartRow = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
733
- const visibleEndRow = Math.min(totalRows - 1, Math.ceil((scrollTop + viewportHeight) / rowHeight) + overscan);
734
- if (totalRows === 0 || visibleEndRow < visibleStartRow) {
735
- this.destroyAllSlots();
736
- return;
737
- }
738
- const requiredRows = /* @__PURE__ */ new Set();
739
- for (let row = visibleStartRow; row <= visibleEndRow; row++) requiredRows.add(row);
740
- const instructions = [];
741
- const slotsToRecycle = [];
742
- for (const [slotId, slot] of this.state.slots) if (!requiredRows.has(slot.rowIndex)) {
743
- slotsToRecycle.push(slotId);
744
- this.state.rowToSlot.delete(slot.rowIndex);
745
- } else requiredRows.delete(slot.rowIndex);
746
- const rowsNeedingSlots = Array.from(requiredRows);
747
- for (let i = 0; i < rowsNeedingSlots.length; i++) {
748
- const rowIndex = rowsNeedingSlots[i];
749
- const rowData = this.options.getRowData(rowIndex);
750
- if (i < slotsToRecycle.length) {
751
- const slotId = slotsToRecycle[i];
752
- const slot = this.state.slots.get(slotId);
753
- const translateY = this.getRowTranslateY(rowIndex);
754
- slot.rowIndex = rowIndex;
755
- slot.rowData = rowData ?? {};
756
- slot.translateY = translateY;
757
- this.state.rowToSlot.set(rowIndex, slotId);
758
- instructions.push({
759
- type: "ASSIGN_SLOT",
760
- slotId,
761
- rowIndex,
762
- rowData: rowData ?? {}
763
- });
764
- instructions.push({
765
- type: "MOVE_SLOT",
766
- slotId,
767
- translateY
768
- });
769
- } else {
770
- const slotId = `slot-${this.state.nextSlotId++}`;
771
- const translateY = this.getRowTranslateY(rowIndex);
772
- const newSlot = {
773
- slotId,
774
- rowIndex,
775
- rowData: rowData ?? {},
776
- translateY
777
- };
778
- this.state.slots.set(slotId, newSlot);
779
- this.state.rowToSlot.set(rowIndex, slotId);
780
- instructions.push({
781
- type: "CREATE_SLOT",
782
- slotId
783
- });
784
- instructions.push({
785
- type: "ASSIGN_SLOT",
786
- slotId,
787
- rowIndex,
788
- rowData: rowData ?? {}
789
- });
790
- instructions.push({
791
- type: "MOVE_SLOT",
792
- slotId,
793
- translateY
794
- });
795
- }
796
- }
797
- for (let i = rowsNeedingSlots.length; i < slotsToRecycle.length; i++) {
798
- const slotId = slotsToRecycle[i];
799
- this.state.slots.delete(slotId);
800
- instructions.push({
801
- type: "DESTROY_SLOT",
802
- slotId
803
- });
804
- }
805
- for (const [slotId, slot] of this.state.slots) {
806
- const expectedY = this.getRowTranslateY(slot.rowIndex);
807
- if (slot.translateY !== expectedY) {
808
- slot.translateY = expectedY;
809
- instructions.push({
810
- type: "MOVE_SLOT",
811
- slotId,
812
- translateY: expectedY
813
- });
814
- }
815
- }
816
- this.emitBatch(instructions);
817
- }
818
- /**
819
- * Destroy all slots.
820
- */
821
- destroyAllSlots() {
822
- const instructions = [];
823
- for (const slotId of this.state.slots.keys()) instructions.push({
824
- type: "DESTROY_SLOT",
825
- slotId
826
- });
827
- this.state.slots.clear();
828
- this.state.rowToSlot.clear();
829
- this.emitBatch(instructions);
830
- }
831
- /**
832
- * Clean up resources for garbage collection.
833
- * This method is idempotent - safe to call multiple times.
834
- */
835
- destroy() {
836
- if (this.isDestroyed) return;
837
- this.isDestroyed = true;
838
- this.state.slots.clear();
839
- this.state.rowToSlot.clear();
840
- this.emitter.clearListeners();
841
- }
842
- /**
843
- * Refresh all slot data without changing which rows are displayed.
844
- * Used after filtering/sorting when data changes.
845
- */
846
- refreshAllSlots() {
847
- const instructions = [];
848
- const totalRows = this.options.getTotalRows();
849
- for (const [slotId, slot] of this.state.slots) if (slot.rowIndex >= 0 && slot.rowIndex < totalRows) {
850
- const rowData = this.options.getRowData(slot.rowIndex);
851
- const translateY = this.getRowTranslateY(slot.rowIndex);
852
- slot.rowData = rowData ?? {};
853
- slot.translateY = translateY;
854
- instructions.push({
855
- type: "ASSIGN_SLOT",
856
- slotId,
857
- rowIndex: slot.rowIndex,
858
- rowData: rowData ?? {}
859
- });
860
- instructions.push({
861
- type: "MOVE_SLOT",
862
- slotId,
863
- translateY
864
- });
865
- }
866
- this.emitBatch(instructions);
867
- this.syncSlots();
868
- }
869
- /**
870
- * Update a single slot's data.
871
- */
872
- updateSlot(rowIndex) {
873
- const slotId = this.state.rowToSlot.get(rowIndex);
874
- if (slotId) {
875
- const rowData = this.options.getRowData(rowIndex);
876
- if (rowData) this.emit({
877
- type: "ASSIGN_SLOT",
878
- slotId,
879
- rowIndex,
880
- rowData
881
- });
882
- }
883
- }
884
- /**
885
- * Calculate the translateY position for a row.
886
- * Handles scroll virtualization for very large datasets.
887
- */
888
- getRowTranslateY(rowIndex) {
889
- const rowHeight = this.options.getRowHeight();
890
- const headerHeight = this.options.getHeaderHeight();
891
- const scrollRatio = this.options.getScrollRatio();
892
- const virtualContentHeight = this.options.getVirtualContentHeight();
893
- const scrollTop = this.options.getScrollTop();
894
- const naturalY = rowIndex * rowHeight + headerHeight;
895
- if (scrollRatio >= 1) return naturalY;
896
- const naturalScrollTop = scrollTop;
897
- const translateY = naturalY - (naturalScrollTop - naturalScrollTop * scrollRatio);
898
- return Math.max(0, Math.min(translateY, virtualContentHeight));
899
- }
900
- };
901
-
902
- //#endregion
903
- //#region src/edit-manager.ts
904
- /**
905
- * Manages cell editing state and operations.
906
- */
907
- var EditManager = class {
908
- editState = null;
909
- options;
910
- emitter = createInstructionEmitter();
911
- onInstruction = this.emitter.onInstruction;
912
- emit = this.emitter.emit;
913
- constructor(options) {
914
- this.options = options;
915
- }
916
- /**
917
- * Get the current edit state.
918
- */
919
- getState() {
920
- return this.editState ? { ...this.editState } : null;
921
- }
922
- /**
923
- * Check if currently editing.
924
- */
925
- isEditing() {
926
- return this.editState !== null;
927
- }
928
- /**
929
- * Check if a specific cell is being edited.
930
- */
931
- isEditingCell(row, col) {
932
- return this.editState !== null && this.editState.row === row && this.editState.col === col;
933
- }
934
- /**
935
- * Start editing a cell.
936
- * Returns true if edit was started, false if cell is not editable.
937
- */
938
- startEdit(row, col) {
939
- const column = this.options.getColumn(col);
940
- if (!column || column.editable !== true) return false;
941
- const initialValue = this.options.getCellValue(row, col);
942
- this.editState = {
943
- row,
944
- col,
945
- initialValue,
946
- currentValue: initialValue
947
- };
948
- this.emit({
949
- type: "START_EDIT",
950
- row,
951
- col,
952
- initialValue
953
- });
954
- return true;
955
- }
956
- /**
957
- * Update the current edit value.
958
- */
959
- updateValue(value) {
960
- if (this.editState) this.editState.currentValue = value;
961
- }
962
- /**
963
- * Commit the current edit.
964
- * Saves the value and closes the editor.
965
- */
966
- commit() {
967
- if (!this.editState) return;
968
- const { row, col, currentValue } = this.editState;
969
- this.options.setCellValue(row, col, currentValue);
970
- this.emit({
971
- type: "COMMIT_EDIT",
972
- row,
973
- col,
974
- value: currentValue
975
- });
976
- this.editState = null;
977
- this.emit({ type: "STOP_EDIT" });
978
- this.options.onCommit?.(row, col, currentValue);
979
- }
980
- /**
981
- * Cancel the current edit.
982
- * Discards changes and closes the editor.
983
- */
984
- cancel() {
985
- this.editState = null;
986
- this.emit({ type: "STOP_EDIT" });
987
- }
988
- /**
989
- * Clean up resources for garbage collection.
990
- */
991
- destroy() {
992
- this.emitter.clearListeners();
993
- this.editState = null;
994
- }
995
- };
996
-
997
- //#endregion
998
- //#region src/input-handler.ts
999
- const AUTO_SCROLL_THRESHOLD = 40;
1000
- const AUTO_SCROLL_SPEED = 10;
1001
- var InputHandler = class {
1002
- core;
1003
- deps;
1004
- isDraggingSelection = false;
1005
- isDraggingFill = false;
1006
- fillSourceRange = null;
1007
- fillTarget = null;
1008
- constructor(core, deps) {
1009
- this.core = core;
1010
- this.deps = deps;
1011
- }
1012
- /**
1013
- * Update dependencies (called when options change)
1014
- */
1015
- updateDeps(deps) {
1016
- this.deps = {
1017
- ...this.deps,
1018
- ...deps
1019
- };
1020
- }
1021
- /**
1022
- * Get current drag state for UI rendering
1023
- */
1024
- getDragState() {
1025
- return {
1026
- isDragging: this.isDraggingSelection || this.isDraggingFill,
1027
- dragType: this.isDraggingFill ? "fill" : this.isDraggingSelection ? "selection" : null,
1028
- fillSourceRange: this.fillSourceRange,
1029
- fillTarget: this.fillTarget
1030
- };
1031
- }
1032
- /**
1033
- * Handle cell mouse down event
1034
- */
1035
- handleCellMouseDown(rowIndex, colIndex, event) {
1036
- if (event.button !== 0) return {
1037
- preventDefault: false,
1038
- stopPropagation: false
1039
- };
1040
- if (this.core.getEditState() !== null) return {
1041
- preventDefault: false,
1042
- stopPropagation: false
1043
- };
1044
- this.core.selection.startSelection({
1045
- row: rowIndex,
1046
- col: colIndex
1047
- }, {
1048
- shift: event.shiftKey,
1049
- ctrl: event.ctrlKey || event.metaKey
1050
- });
1051
- return {
1052
- preventDefault: false,
1053
- stopPropagation: false,
1054
- focusContainer: true,
1055
- startDrag: event.shiftKey ? void 0 : "selection"
1056
- };
1057
- }
1058
- /**
1059
- * Handle cell double click event (start editing)
1060
- */
1061
- handleCellDoubleClick(rowIndex, colIndex) {
1062
- this.core.startEdit(rowIndex, colIndex);
1063
- }
1064
- /**
1065
- * Handle cell mouse enter event (for hover highlighting)
1066
- */
1067
- handleCellMouseEnter(rowIndex, colIndex) {
1068
- this.core.highlight?.setHoverPosition({
1069
- row: rowIndex,
1070
- col: colIndex
1071
- });
1072
- }
1073
- /**
1074
- * Handle cell mouse leave event (for hover highlighting)
1075
- */
1076
- handleCellMouseLeave() {
1077
- this.core.highlight?.setHoverPosition(null);
1078
- }
1079
- /**
1080
- * Handle fill handle mouse down event
1081
- */
1082
- handleFillHandleMouseDown(activeCell, selectionRange, _event) {
1083
- if (!activeCell && !selectionRange) return {
1084
- preventDefault: false,
1085
- stopPropagation: false
1086
- };
1087
- const sourceRange = selectionRange ?? {
1088
- startRow: activeCell.row,
1089
- startCol: activeCell.col,
1090
- endRow: activeCell.row,
1091
- endCol: activeCell.col
1092
- };
1093
- this.core.fill.startFillDrag(sourceRange);
1094
- this.fillSourceRange = sourceRange;
1095
- this.fillTarget = {
1096
- row: Math.max(sourceRange.startRow, sourceRange.endRow),
1097
- col: Math.max(sourceRange.startCol, sourceRange.endCol)
1098
- };
1099
- this.isDraggingFill = true;
1100
- return {
1101
- preventDefault: true,
1102
- stopPropagation: true,
1103
- startDrag: "fill"
1104
- };
1105
- }
1106
- /**
1107
- * Handle header click event (cycle sort direction)
1108
- */
1109
- handleHeaderClick(colId, addToExisting) {
1110
- const currentDirection = this.core.getSortModel().find((s) => s.colId === colId)?.direction;
1111
- const nextDirection = currentDirection === void 0 || currentDirection === null ? "asc" : currentDirection === "asc" ? "desc" : null;
1112
- this.core.setSort(colId, nextDirection, addToExisting);
1113
- }
1114
- /**
1115
- * Start selection drag (called by framework after handleCellMouseDown returns startDrag: 'selection')
1116
- */
1117
- startSelectionDrag() {
1118
- this.isDraggingSelection = true;
1119
- }
1120
- /**
1121
- * Handle drag move event (selection or fill)
1122
- */
1123
- handleDragMove(event, bounds) {
1124
- if (!this.isDraggingSelection && !this.isDraggingFill) return null;
1125
- const { top, left, width, height, scrollTop, scrollLeft } = bounds;
1126
- const headerHeight = this.deps.getHeaderHeight();
1127
- const columnPositions = this.deps.getColumnPositions();
1128
- const columnCount = this.deps.getColumnCount();
1129
- const mouseX = event.clientX - left + scrollLeft;
1130
- const viewportY = event.clientY - top - headerHeight;
1131
- const targetRow = Math.max(0, Math.min(this.core.getRowIndexAtDisplayY(viewportY, scrollTop), this.core.getRowCount() - 1));
1132
- const visibleColIndex = Math.max(0, Math.min(findColumnAtX(mouseX, columnPositions), columnCount - 1));
1133
- const targetCol = this.deps.getOriginalColumnIndex ? this.deps.getOriginalColumnIndex(visibleColIndex) : visibleColIndex;
1134
- if (this.isDraggingSelection) this.core.selection.startSelection({
1135
- row: targetRow,
1136
- col: targetCol
1137
- }, { shift: true });
1138
- if (this.isDraggingFill) {
1139
- this.core.fill.updateFillDrag(targetRow, targetCol);
1140
- this.fillTarget = {
1141
- row: targetRow,
1142
- col: targetCol
1143
- };
1144
- }
1145
- const mouseYInContainer = event.clientY - top;
1146
- const mouseXInContainer = event.clientX - left;
1147
- return {
1148
- targetRow,
1149
- targetCol,
1150
- autoScroll: this.calculateAutoScroll(mouseYInContainer, mouseXInContainer, height, width, headerHeight)
1151
- };
1152
- }
1153
- /**
1154
- * Handle drag end event
1155
- */
1156
- handleDragEnd() {
1157
- if (this.isDraggingFill) {
1158
- this.core.fill.commitFillDrag();
1159
- this.core.refreshSlotData();
1160
- }
1161
- this.isDraggingSelection = false;
1162
- this.isDraggingFill = false;
1163
- this.fillSourceRange = null;
1164
- this.fillTarget = null;
1165
- }
1166
- /**
1167
- * Handle wheel event with dampening for large datasets
1168
- * Returns scroll deltas or null if no dampening needed
1169
- */
1170
- handleWheel(deltaY, deltaX, dampening) {
1171
- if (!this.core.isScalingActive()) return null;
1172
- return {
1173
- dy: deltaY * dampening,
1174
- dx: deltaX * dampening
1175
- };
1176
- }
1177
- /**
1178
- * Handle keyboard event
1179
- */
1180
- handleKeyDown(event, activeCell, editingCell, filterPopupOpen) {
1181
- if (filterPopupOpen) return { preventDefault: false };
1182
- if (editingCell && event.key !== "Enter" && event.key !== "Escape" && event.key !== "Tab") return { preventDefault: false };
1183
- const { selection } = this.core;
1184
- const isShift = event.shiftKey;
1185
- const isCtrl = event.ctrlKey || event.metaKey;
1186
- const keyToDirection = (key) => {
1187
- switch (key) {
1188
- case "ArrowUp": return "up";
1189
- case "ArrowDown": return "down";
1190
- case "ArrowLeft": return "left";
1191
- case "ArrowRight": return "right";
1192
- default: return null;
1193
- }
1194
- };
1195
- const direction = keyToDirection(event.key);
1196
- if (direction) {
1197
- selection.moveFocus(direction, isShift);
1198
- return {
1199
- preventDefault: true,
1200
- scrollToCell: selection.getActiveCell() ?? void 0
1201
- };
1202
- }
1203
- switch (event.key) {
1204
- case "Enter":
1205
- if (editingCell) this.core.commitEdit();
1206
- else if (activeCell) this.core.startEdit(activeCell.row, activeCell.col);
1207
- return { preventDefault: true };
1208
- case "Escape":
1209
- if (editingCell) this.core.cancelEdit();
1210
- else selection.clearSelection();
1211
- return { preventDefault: true };
1212
- case "Tab":
1213
- if (editingCell) this.core.commitEdit();
1214
- selection.moveFocus(isShift ? "left" : "right", false);
1215
- return { preventDefault: true };
1216
- case "a":
1217
- if (isCtrl) {
1218
- selection.selectAll();
1219
- return { preventDefault: true };
1220
- }
1221
- break;
1222
- case "c":
1223
- if (isCtrl) {
1224
- selection.copySelectionToClipboard();
1225
- return { preventDefault: true };
1226
- }
1227
- break;
1228
- case "F2":
1229
- if (activeCell && !editingCell) this.core.startEdit(activeCell.row, activeCell.col);
1230
- return { preventDefault: true };
1231
- case "Delete":
1232
- case "Backspace":
1233
- if (activeCell && !editingCell) {
1234
- this.core.startEdit(activeCell.row, activeCell.col);
1235
- return { preventDefault: true };
1236
- }
1237
- break;
1238
- default:
1239
- if (activeCell && !editingCell && !isCtrl && event.key.length === 1) this.core.startEdit(activeCell.row, activeCell.col);
1240
- break;
1241
- }
1242
- return { preventDefault: false };
1243
- }
1244
- /**
1245
- * Calculate auto-scroll deltas based on mouse position
1246
- */
1247
- calculateAutoScroll(mouseYInContainer, mouseXInContainer, containerHeight, containerWidth, headerHeight) {
1248
- let dx = 0;
1249
- let dy = 0;
1250
- if (mouseYInContainer < AUTO_SCROLL_THRESHOLD + headerHeight) dy = -AUTO_SCROLL_SPEED;
1251
- else if (mouseYInContainer > containerHeight - AUTO_SCROLL_THRESHOLD) dy = AUTO_SCROLL_SPEED;
1252
- if (mouseXInContainer < AUTO_SCROLL_THRESHOLD) dx = -AUTO_SCROLL_SPEED;
1253
- else if (mouseXInContainer > containerWidth - AUTO_SCROLL_THRESHOLD) dx = AUTO_SCROLL_SPEED;
1254
- return dx !== 0 || dy !== 0 ? {
1255
- dx,
1256
- dy
1257
- } : null;
1258
- }
1259
- };
1260
-
1261
- //#endregion
1262
- //#region src/highlight-manager.ts
1263
- /**
1264
- * Manages row/column/cell highlighting state and class computation.
1265
- * Emits SET_HOVER_POSITION instructions when hover position changes.
1266
- */
1267
- var HighlightManager = class {
1268
- options;
1269
- highlightingOptions;
1270
- hoverPosition = null;
1271
- emitter = createInstructionEmitter();
1272
- onInstruction = this.emitter.onInstruction;
1273
- emit = this.emitter.emit;
1274
- rowClassCache = /* @__PURE__ */ new Map();
1275
- columnClassCache = /* @__PURE__ */ new Map();
1276
- cellClassCache = /* @__PURE__ */ new Map();
1277
- constructor(options, highlightingOptions = {}) {
1278
- this.options = options;
1279
- this.highlightingOptions = highlightingOptions;
1280
- }
1281
- /**
1282
- * Check if highlighting is enabled (any callback defined).
1283
- * Hover tracking is automatically enabled when highlighting is enabled.
1284
- */
1285
- isEnabled() {
1286
- return !!(this.highlightingOptions.computeRowClasses || this.highlightingOptions.computeColumnClasses || this.highlightingOptions.computeCellClasses);
1287
- }
1288
- /**
1289
- * Update highlighting options. Clears all caches.
1290
- */
1291
- updateOptions(options) {
1292
- this.highlightingOptions = options;
1293
- this.clearAllCaches();
1294
- }
1295
- /**
1296
- * Set the current hover position. Clears caches and emits instruction.
1297
- * Hover tracking is automatically enabled when any highlighting callback is defined.
1298
- */
1299
- setHoverPosition(position) {
1300
- if (!this.isEnabled()) return;
1301
- if (this.hoverPosition?.row === position?.row && this.hoverPosition?.col === position?.col) return;
1302
- this.rowClassCache.clear();
1303
- this.columnClassCache.clear();
1304
- this.cellClassCache.clear();
1305
- this.hoverPosition = position;
1306
- this.emit({
1307
- type: "SET_HOVER_POSITION",
1308
- position
1309
- });
1310
- }
1311
- /**
1312
- * Get the current hover position
1313
- */
1314
- getHoverPosition() {
1315
- return this.hoverPosition;
1316
- }
1317
- /**
1318
- * Called when selection changes. Clears all caches.
1319
- */
1320
- onSelectionChange() {
1321
- this.rowClassCache.clear();
1322
- this.columnClassCache.clear();
1323
- this.cellClassCache.clear();
1324
- }
1325
- /**
1326
- * Build context for row highlighting callback.
1327
- * Returns context with `rowIndex` set, `colIndex` is null.
1328
- * `isHovered` is true when the mouse is on any cell in this row.
1329
- */
1330
- buildRowContext(rowIndex, rowData) {
1331
- const activeCell = this.options.getActiveCell();
1332
- const selectionRange = this.options.getSelectionRange();
1333
- return {
1334
- rowIndex,
1335
- colIndex: null,
1336
- column: void 0,
1337
- rowData,
1338
- hoverPosition: this.hoverPosition,
1339
- activeCell,
1340
- selectionRange,
1341
- isHovered: this.hoverPosition?.row === rowIndex,
1342
- isActive: activeCell?.row === rowIndex,
1343
- isSelected: isRowInSelectionRange(rowIndex, selectionRange)
1344
- };
1345
- }
1346
- /**
1347
- * Build context for column highlighting callback.
1348
- * Returns context with `colIndex` set, `rowIndex` is null.
1349
- * `isHovered` is true when the mouse is on any cell in this column.
1350
- */
1351
- buildColumnContext(colIndex, column) {
1352
- const activeCell = this.options.getActiveCell();
1353
- const selectionRange = this.options.getSelectionRange();
1354
- return {
1355
- rowIndex: null,
1356
- colIndex,
1357
- column,
1358
- rowData: void 0,
1359
- hoverPosition: this.hoverPosition,
1360
- activeCell,
1361
- selectionRange,
1362
- isHovered: this.hoverPosition?.col === colIndex,
1363
- isActive: activeCell?.col === colIndex,
1364
- isSelected: isColumnInSelectionRange(colIndex, selectionRange)
1365
- };
1366
- }
1367
- /**
1368
- * Build context for cell highlighting callback.
1369
- * Returns context with both `rowIndex` and `colIndex` set.
1370
- * `isHovered` is true only when the mouse is on this exact cell.
1371
- */
1372
- buildCellContext(rowIndex, colIndex, column, rowData) {
1373
- const activeCell = this.options.getActiveCell();
1374
- const selectionRange = this.options.getSelectionRange();
1375
- const isHovered = this.hoverPosition?.row === rowIndex && this.hoverPosition?.col === colIndex;
1376
- let isSelected = false;
1377
- if (selectionRange) {
1378
- const { minRow, maxRow, minCol, maxCol } = normalizeRange(selectionRange);
1379
- isSelected = rowIndex >= minRow && rowIndex <= maxRow && colIndex >= minCol && colIndex <= maxCol;
1380
- }
1381
- return {
1382
- rowIndex,
1383
- colIndex,
1384
- column,
1385
- rowData,
1386
- hoverPosition: this.hoverPosition,
1387
- activeCell,
1388
- selectionRange,
1389
- isHovered,
1390
- isActive: activeCell?.row === rowIndex && activeCell?.col === colIndex,
1391
- isSelected
1392
- };
1393
- }
1394
- /**
1395
- * Compute row classes using cache and user callback
1396
- */
1397
- computeRowClasses(rowIndex, rowData) {
1398
- const callback = this.highlightingOptions.computeRowClasses;
1399
- if (!callback) return [];
1400
- const cached = this.rowClassCache.get(rowIndex);
1401
- if (cached !== void 0) return cached;
1402
- const result = callback(this.buildRowContext(rowIndex, rowData));
1403
- this.rowClassCache.set(rowIndex, result);
1404
- return result;
1405
- }
1406
- /**
1407
- * Compute column classes using cache and user callback (or per-column override)
1408
- */
1409
- computeColumnClasses(colIndex, column) {
1410
- const cached = this.columnClassCache.get(colIndex);
1411
- if (cached !== void 0) return cached;
1412
- const context = this.buildColumnContext(colIndex, column);
1413
- let result;
1414
- if (column.computeColumnClasses) result = column.computeColumnClasses(context);
1415
- else if (this.highlightingOptions.computeColumnClasses) result = this.highlightingOptions.computeColumnClasses(context);
1416
- else return [];
1417
- this.columnClassCache.set(colIndex, result);
1418
- return result;
1419
- }
1420
- /**
1421
- * Compute cell classes using cache and user callback (or per-column override)
1422
- */
1423
- computeCellClasses(rowIndex, colIndex, column, rowData) {
1424
- const cacheKey = `${rowIndex},${colIndex}`;
1425
- const cached = this.cellClassCache.get(cacheKey);
1426
- if (cached !== void 0) return cached;
1427
- const context = this.buildCellContext(rowIndex, colIndex, column, rowData);
1428
- let result;
1429
- if (column.computeCellClasses) result = column.computeCellClasses(context);
1430
- else if (this.highlightingOptions.computeCellClasses) result = this.highlightingOptions.computeCellClasses(context);
1431
- else return [];
1432
- this.cellClassCache.set(cacheKey, result);
1433
- return result;
1434
- }
1435
- /**
1436
- * Compute combined cell classes (column + cell classes flattened)
1437
- */
1438
- computeCombinedCellClasses(rowIndex, colIndex, column, rowData) {
1439
- const columnClasses = this.computeColumnClasses(colIndex, column);
1440
- const cellClasses = this.computeCellClasses(rowIndex, colIndex, column, rowData);
1441
- return [...columnClasses, ...cellClasses];
1442
- }
1443
- /**
1444
- * Clear all caches
1445
- */
1446
- clearAllCaches() {
1447
- this.rowClassCache.clear();
1448
- this.columnClassCache.clear();
1449
- this.cellClassCache.clear();
1450
- }
1451
- /**
1452
- * Destroy the manager and release resources
1453
- */
1454
- destroy() {
1455
- this.emitter.clearListeners();
1456
- this.clearAllCaches();
1457
- this.hoverPosition = null;
1458
- }
1459
- };
1460
-
1461
- //#endregion
1462
- //#region src/sort-filter-manager.ts
1463
- /**
1464
- * Manages sorting and filtering state and operations.
1465
- */
1466
- var SortFilterManager = class {
1467
- options;
1468
- emitter = createInstructionEmitter();
1469
- sortModel = [];
1470
- filterModel = {};
1471
- openFilterColIndex = null;
1472
- onInstruction = this.emitter.onInstruction;
1473
- emit = this.emitter.emit;
1474
- constructor(options) {
1475
- this.options = options;
1476
- }
1477
- async setSort(colId, direction, addToExisting = false) {
1478
- if (!this.options.isSortingEnabled()) return;
1479
- if (this.options.getColumns().find((c) => (c.colId ?? c.field) === colId)?.sortable === false) return;
1480
- const existingIndex = this.sortModel.findIndex((s) => s.colId === colId);
1481
- if (!addToExisting) this.sortModel = direction === null ? [] : [{
1482
- colId,
1483
- direction
1484
- }];
1485
- else if (direction === null) {
1486
- if (existingIndex >= 0) this.sortModel.splice(existingIndex, 1);
1487
- } else if (existingIndex >= 0) this.sortModel[existingIndex].direction = direction;
1488
- else this.sortModel.push({
1489
- colId,
1490
- direction
1491
- });
1492
- await this.options.onSortFilterChange();
1493
- this.options.onDataRefreshed();
1494
- }
1495
- getSortModel() {
1496
- return [...this.sortModel];
1497
- }
1498
- async setFilter(colId, filter) {
1499
- if (this.options.getColumns().find((c) => (c.colId ?? c.field) === colId)?.filterable === false) return;
1500
- if (filter === null || typeof filter === "string" && filter.trim() === "" || typeof filter === "object" && filter.conditions && filter.conditions.length === 0) delete this.filterModel[colId];
1501
- else if (typeof filter === "string") this.filterModel[colId] = {
1502
- conditions: [{
1503
- type: "text",
1504
- operator: "contains",
1505
- value: filter
1506
- }],
1507
- combination: "and"
1508
- };
1509
- else this.filterModel[colId] = filter;
1510
- await this.options.onSortFilterChange();
1511
- this.options.onDataRefreshed();
1512
- }
1513
- getFilterModel() {
1514
- return { ...this.filterModel };
1515
- }
1516
- /**
1517
- * Check if a column has an active filter
1518
- */
1519
- hasActiveFilter(colId) {
1520
- const filter = this.filterModel[colId];
1521
- if (!filter) return false;
1522
- return filter.conditions.length > 0;
1523
- }
1524
- /**
1525
- * Check if a column is sortable
1526
- */
1527
- isColumnSortable(colIndex) {
1528
- if (!this.options.isSortingEnabled()) return false;
1529
- return this.options.getColumns()[colIndex]?.sortable !== false;
1530
- }
1531
- /**
1532
- * Check if a column is filterable
1533
- */
1534
- isColumnFilterable(colIndex) {
1535
- return this.options.getColumns()[colIndex]?.filterable !== false;
1536
- }
1537
- /**
1538
- * Get distinct values for a column (for filter dropdowns)
1539
- * For array-type columns (like tags), each unique array combination is returned.
1540
- * Arrays are sorted internally for consistent comparison.
1541
- * Limited to maxValues to avoid performance issues with large datasets.
1542
- */
1543
- getDistinctValuesForColumn(colId, maxValues = 500) {
1544
- const column = this.options.getColumns().find((c) => (c.colId ?? c.field) === colId);
1545
- if (!column) return [];
1546
- const cachedRows = this.options.getCachedRows();
1547
- const valuesMap = /* @__PURE__ */ new Map();
1548
- for (const row of cachedRows.values()) {
1549
- const value = getFieldValue$2(row, column.field);
1550
- if (Array.isArray(value)) {
1551
- const sortedArray = [...value].sort((a, b) => String(a).localeCompare(String(b), void 0, {
1552
- numeric: true,
1553
- sensitivity: "base"
1554
- }));
1555
- const key = JSON.stringify(sortedArray);
1556
- if (!valuesMap.has(key)) {
1557
- valuesMap.set(key, sortedArray);
1558
- if (valuesMap.size >= maxValues) break;
1559
- }
1560
- } else {
1561
- const key = JSON.stringify(value);
1562
- if (!valuesMap.has(key)) {
1563
- valuesMap.set(key, value);
1564
- if (valuesMap.size >= maxValues) break;
1565
- }
1566
- }
1567
- }
1568
- const results = Array.from(valuesMap.values());
1569
- results.sort((a, b) => {
1570
- const strA = Array.isArray(a) ? a.join(", ") : String(a ?? "");
1571
- const strB = Array.isArray(b) ? b.join(", ") : String(b ?? "");
1572
- return strA.localeCompare(strB, void 0, {
1573
- numeric: true,
1574
- sensitivity: "base"
1575
- });
1576
- });
1577
- return results;
1578
- }
1579
- /**
1580
- * Open filter popup for a column (toggles if already open for same column)
1581
- */
1582
- openFilterPopup(colIndex, anchorRect) {
1583
- if (this.openFilterColIndex === colIndex) {
1584
- this.closeFilterPopup();
1585
- return;
1586
- }
1587
- const column = this.options.getColumns()[colIndex];
1588
- if (!column || !this.isColumnFilterable(colIndex)) return;
1589
- const colId = column.colId ?? column.field;
1590
- const distinctValues = this.getDistinctValuesForColumn(colId);
1591
- this.openFilterColIndex = colIndex;
1592
- this.emit({
1593
- type: "OPEN_FILTER_POPUP",
1594
- colIndex,
1595
- column,
1596
- anchorRect,
1597
- distinctValues,
1598
- currentFilter: this.filterModel[colId]
1599
- });
1600
- }
1601
- /**
1602
- * Close filter popup
1603
- */
1604
- closeFilterPopup() {
1605
- this.openFilterColIndex = null;
1606
- this.emit({ type: "CLOSE_FILTER_POPUP" });
1607
- }
1608
- /**
1609
- * Get sort info map for header rendering
1610
- */
1611
- getSortInfoMap() {
1612
- const sortInfoMap = /* @__PURE__ */ new Map();
1613
- this.sortModel.forEach((sort, index) => {
1614
- sortInfoMap.set(sort.colId, {
1615
- direction: sort.direction,
1616
- index: index + 1
1617
- });
1618
- });
1619
- return sortInfoMap;
1620
- }
1621
- destroy() {
1622
- this.emitter.clearListeners();
1623
- this.sortModel = [];
1624
- this.filterModel = {};
1625
- this.openFilterColIndex = null;
1626
- }
1627
- };
1628
-
1629
- //#endregion
1630
- //#region src/row-mutation-manager.ts
1631
- /**
1632
- * Manages row CRUD operations and cache management.
1633
- */
1634
- var RowMutationManager = class {
1635
- options;
1636
- emitter = createInstructionEmitter();
1637
- onInstruction = this.emitter.onInstruction;
1638
- emit = this.emitter.emit;
1639
- constructor(options) {
1640
- this.options = options;
1641
- }
1642
- /**
1643
- * Get a row by index.
1644
- */
1645
- getRow(index) {
1646
- return this.options.getCachedRows().get(index);
1647
- }
1648
- /**
1649
- * Add rows to the grid at the specified index.
1650
- * If no index is provided, rows are added at the end.
1651
- */
1652
- addRows(rows, index) {
1653
- if (rows.length === 0) return;
1654
- const cachedRows = this.options.getCachedRows();
1655
- const totalRows = this.options.getTotalRows();
1656
- const insertIndex = index ?? totalRows;
1657
- const newTotalRows = totalRows + rows.length;
1658
- if (insertIndex < totalRows) {
1659
- const newCache = /* @__PURE__ */ new Map();
1660
- for (const [rowIndex, rowData] of cachedRows) if (rowIndex >= insertIndex) newCache.set(rowIndex + rows.length, rowData);
1661
- else newCache.set(rowIndex, rowData);
1662
- this.options.setCachedRows(newCache);
1663
- }
1664
- const currentCache = this.options.getCachedRows();
1665
- rows.forEach((row, i) => {
1666
- currentCache.set(insertIndex + i, row);
1667
- });
1668
- this.options.setTotalRows(newTotalRows);
1669
- const addedIndices = rows.map((_, i) => insertIndex + i);
1670
- this.emit({
1671
- type: "ROWS_ADDED",
1672
- indices: addedIndices,
1673
- count: addedIndices.length,
1674
- totalRows: newTotalRows
1675
- });
1676
- this.options.emitContentSize();
1677
- this.options.refreshAllSlots();
1678
- }
1679
- /**
1680
- * Update existing rows with partial data.
1681
- */
1682
- updateRows(updates) {
1683
- if (updates.length === 0) return;
1684
- const cachedRows = this.options.getCachedRows();
1685
- const updatedIndices = [];
1686
- for (const update of updates) {
1687
- const existing = cachedRows.get(update.index);
1688
- if (existing) {
1689
- cachedRows.set(update.index, {
1690
- ...existing,
1691
- ...update.data
1692
- });
1693
- updatedIndices.push(update.index);
1694
- }
1695
- }
1696
- if (updatedIndices.length === 0) return;
1697
- this.emit({
1698
- type: "ROWS_UPDATED",
1699
- indices: updatedIndices
1700
- });
1701
- for (const index of updatedIndices) this.options.updateSlot(index);
1702
- }
1703
- /**
1704
- * Delete rows at the specified indices.
1705
- */
1706
- deleteRows(indices) {
1707
- if (indices.length === 0) return;
1708
- const cachedRows = this.options.getCachedRows();
1709
- let totalRows = this.options.getTotalRows();
1710
- const sortedIndices = [...indices].sort((a, b) => b - a);
1711
- for (const index of sortedIndices) {
1712
- if (index < 0 || index >= totalRows) continue;
1713
- cachedRows.delete(index);
1714
- const newCache = /* @__PURE__ */ new Map();
1715
- for (const [rowIndex, rowData] of cachedRows) if (rowIndex > index) newCache.set(rowIndex - 1, rowData);
1716
- else newCache.set(rowIndex, rowData);
1717
- this.options.setCachedRows(newCache);
1718
- totalRows--;
1719
- }
1720
- this.options.setTotalRows(totalRows);
1721
- this.options.clearSelectionIfInvalid(totalRows);
1722
- this.emit({
1723
- type: "ROWS_REMOVED",
1724
- indices: sortedIndices,
1725
- totalRows
1726
- });
1727
- this.options.emitContentSize();
1728
- this.options.refreshAllSlots();
1729
- }
1730
- /**
1731
- * Set a complete row at the specified index.
1732
- * Use this for complete row replacement. For partial updates, use updateRows.
1733
- */
1734
- setRow(index, data) {
1735
- const totalRows = this.options.getTotalRows();
1736
- if (index < 0 || index >= totalRows) return;
1737
- this.options.getCachedRows().set(index, data);
1738
- this.emit({
1739
- type: "ROWS_UPDATED",
1740
- indices: [index]
1741
- });
1742
- this.options.updateSlot(index);
1743
- }
1744
- destroy() {
1745
- this.emitter.clearListeners();
1746
- }
1747
- };
1748
-
1749
- //#endregion
1750
- //#region src/grid-core.ts
1751
- const MAX_SCROLL_HEIGHT = 1e7;
1752
- var GridCore = class {
1753
- columns;
1754
- dataSource;
1755
- rowHeight;
1756
- headerHeight;
1757
- overscan;
1758
- sortingEnabled;
1759
- scrollTop = 0;
1760
- scrollLeft = 0;
1761
- viewportWidth = 800;
1762
- viewportHeight = 600;
1763
- cachedRows = /* @__PURE__ */ new Map();
1764
- totalRows = 0;
1765
- currentPageIndex = 0;
1766
- pageSize = 1e6;
1767
- selection;
1768
- fill;
1769
- input;
1770
- highlight;
1771
- sortFilter;
1772
- rowMutation;
1773
- slotPool;
1774
- editManager;
1775
- columnPositions = [];
1776
- emitter = createBatchInstructionEmitter();
1777
- onInstruction = this.emitter.onInstruction;
1778
- onBatchInstruction = this.emitter.onBatchInstruction;
1779
- emit = this.emitter.emit;
1780
- emitBatch = this.emitter.emitBatch;
1781
- naturalContentHeight = 0;
1782
- virtualContentHeight = 0;
1783
- scrollRatio = 1;
1784
- isDestroyed = false;
1785
- constructor(options) {
1786
- this.columns = options.columns;
1787
- this.dataSource = options.dataSource;
1788
- this.rowHeight = options.rowHeight;
1789
- this.headerHeight = options.headerHeight ?? options.rowHeight;
1790
- this.overscan = options.overscan ?? 3;
1791
- this.sortingEnabled = options.sortingEnabled ?? true;
1792
- this.computeColumnPositions();
1793
- this.selection = new SelectionManager({
1794
- getRowCount: () => this.totalRows,
1795
- getColumnCount: () => this.columns.length,
1796
- getCellValue: (row, col) => this.getCellValue(row, col),
1797
- getRowData: (row) => this.cachedRows.get(row),
1798
- getColumn: (col) => this.columns[col]
1799
- });
1800
- this.selection.onInstruction((instruction) => {
1801
- this.emit(instruction);
1802
- this.highlight?.onSelectionChange();
1803
- });
1804
- if (options.highlighting) {
1805
- this.highlight = new HighlightManager({
1806
- getActiveCell: () => this.selection.getActiveCell(),
1807
- getSelectionRange: () => this.selection.getSelectionRange(),
1808
- getColumn: (colIndex) => this.columns[colIndex]
1809
- }, options.highlighting);
1810
- this.highlight.onInstruction((instruction) => this.emit(instruction));
1811
- } else this.highlight = null;
1812
- this.fill = new FillManager({
1813
- getRowCount: () => this.totalRows,
1814
- getColumnCount: () => this.columns.length,
1815
- getCellValue: (row, col) => this.getCellValue(row, col),
1816
- getColumn: (col) => this.columns[col],
1817
- setCellValue: (row, col, value) => this.setCellValue(row, col, value)
1818
- });
1819
- this.fill.onInstruction((instruction) => this.emit(instruction));
1820
- this.slotPool = new SlotPoolManager({
1821
- getRowHeight: () => this.rowHeight,
1822
- getHeaderHeight: () => this.headerHeight,
1823
- getOverscan: () => this.overscan,
1824
- getScrollTop: () => this.scrollTop,
1825
- getViewportHeight: () => this.viewportHeight,
1826
- getTotalRows: () => this.totalRows,
1827
- getScrollRatio: () => this.scrollRatio,
1828
- getVirtualContentHeight: () => this.virtualContentHeight,
1829
- getRowData: (rowIndex) => this.cachedRows.get(rowIndex)
1830
- });
1831
- this.slotPool.onBatchInstruction((instructions) => this.emitBatch(instructions));
1832
- this.editManager = new EditManager({
1833
- getColumn: (col) => this.columns[col],
1834
- getCellValue: (row, col) => this.getCellValue(row, col),
1835
- setCellValue: (row, col, value) => this.setCellValue(row, col, value),
1836
- onCommit: (row) => {
1837
- this.slotPool.updateSlot(row);
1838
- }
1839
- });
1840
- this.editManager.onInstruction((instruction) => this.emit(instruction));
1841
- this.sortFilter = new SortFilterManager({
1842
- getColumns: () => this.columns,
1843
- isSortingEnabled: () => this.sortingEnabled,
1844
- getCachedRows: () => this.cachedRows,
1845
- onSortFilterChange: async () => {
1846
- await this.fetchData();
1847
- this.highlight?.clearAllCaches();
1848
- this.slotPool.refreshAllSlots();
1849
- },
1850
- onDataRefreshed: () => {
1851
- this.emitContentSize();
1852
- this.emitHeaders();
1853
- }
1854
- });
1855
- this.sortFilter.onInstruction((instruction) => this.emit(instruction));
1856
- this.rowMutation = new RowMutationManager({
1857
- getCachedRows: () => this.cachedRows,
1858
- setCachedRows: (rows) => {
1859
- this.cachedRows = rows;
1860
- },
1861
- getTotalRows: () => this.totalRows,
1862
- setTotalRows: (count) => {
1863
- this.totalRows = count;
1864
- },
1865
- updateSlot: (rowIndex) => this.slotPool.updateSlot(rowIndex),
1866
- refreshAllSlots: () => this.slotPool.refreshAllSlots(),
1867
- emitContentSize: () => this.emitContentSize(),
1868
- clearSelectionIfInvalid: (maxValidRow) => {
1869
- const activeCell = this.selection.getActiveCell();
1870
- if (activeCell && activeCell.row >= maxValidRow) this.selection.clearSelection();
1871
- }
1872
- });
1873
- this.rowMutation.onInstruction((instruction) => this.emit(instruction));
1874
- this.input = new InputHandler(this, {
1875
- getHeaderHeight: () => this.headerHeight,
1876
- getRowHeight: () => this.rowHeight,
1877
- getColumnPositions: () => this.columnPositions,
1878
- getColumnCount: () => this.columns.length
1879
- });
1880
- }
1881
- /**
1882
- * Initialize the grid and load initial data.
1883
- */
1884
- async initialize() {
1885
- await this.fetchData();
1886
- this.slotPool.syncSlots();
1887
- this.emitContentSize();
1888
- this.emitHeaders();
1889
- }
1890
- /**
1891
- * Update viewport measurements and sync slots.
1892
- * When scroll virtualization is active, maps the DOM scroll position to the actual row position.
1893
- */
1894
- setViewport(scrollTop, scrollLeft, width, height) {
1895
- const effectiveScrollTop = this.scrollRatio < 1 ? scrollTop / this.scrollRatio : scrollTop;
1896
- const viewportSizeChanged = this.viewportWidth !== width || this.viewportHeight !== height;
1897
- if (!(this.scrollTop !== effectiveScrollTop || this.scrollLeft !== scrollLeft || viewportSizeChanged)) return;
1898
- this.scrollTop = effectiveScrollTop;
1899
- this.scrollLeft = scrollLeft;
1900
- this.viewportWidth = width;
1901
- this.viewportHeight = height;
1902
- this.slotPool.syncSlots();
1903
- const visibleRange = this.getVisibleRowRange();
1904
- this.emit({
1905
- type: "UPDATE_VISIBLE_RANGE",
1906
- start: visibleRange.start,
1907
- end: visibleRange.end
1908
- });
1909
- if (viewportSizeChanged) this.emitContentSize();
1910
- }
1911
- async fetchData() {
1912
- this.emit({ type: "DATA_LOADING" });
1913
- try {
1914
- const sortModel = this.sortFilter.getSortModel();
1915
- const filterModel = this.sortFilter.getFilterModel();
1916
- const request = {
1917
- pagination: {
1918
- pageIndex: this.currentPageIndex,
1919
- pageSize: this.pageSize
1920
- },
1921
- sort: sortModel.length > 0 ? sortModel : void 0,
1922
- filter: Object.keys(filterModel).length > 0 ? filterModel : void 0
1923
- };
1924
- const response = await this.dataSource.fetch(request);
1925
- this.cachedRows.clear();
1926
- response.rows.forEach((row, index) => {
1927
- this.cachedRows.set(this.currentPageIndex * this.pageSize + index, row);
1928
- });
1929
- this.totalRows = response.totalRows;
1930
- if (response.totalRows > response.rows.length && this.currentPageIndex === 0) await this.fetchAllData();
1931
- this.emit({
1932
- type: "DATA_LOADED",
1933
- totalRows: this.totalRows
1934
- });
1935
- } catch (error) {
1936
- this.emit({
1937
- type: "DATA_ERROR",
1938
- error: error instanceof Error ? error.message : String(error)
1939
- });
1940
- }
1941
- }
1942
- async fetchAllData() {
1943
- const totalPages = Math.ceil(this.totalRows / this.pageSize);
1944
- const sortModel = this.sortFilter.getSortModel();
1945
- const filterModel = this.sortFilter.getFilterModel();
1946
- for (let page = 1; page < totalPages; page++) {
1947
- const request = {
1948
- pagination: {
1949
- pageIndex: page,
1950
- pageSize: this.pageSize
1951
- },
1952
- sort: sortModel.length > 0 ? sortModel : void 0,
1953
- filter: Object.keys(filterModel).length > 0 ? filterModel : void 0
1954
- };
1955
- (await this.dataSource.fetch(request)).rows.forEach((row, index) => {
1956
- this.cachedRows.set(page * this.pageSize + index, row);
1957
- });
1958
- }
1959
- }
1960
- async setSort(colId, direction, addToExisting = false) {
1961
- return this.sortFilter.setSort(colId, direction, addToExisting);
1962
- }
1963
- async setFilter(colId, filter) {
1964
- return this.sortFilter.setFilter(colId, filter);
1965
- }
1966
- hasActiveFilter(colId) {
1967
- return this.sortFilter.hasActiveFilter(colId);
1968
- }
1969
- isColumnSortable(colIndex) {
1970
- return this.sortFilter.isColumnSortable(colIndex);
1971
- }
1972
- isColumnFilterable(colIndex) {
1973
- return this.sortFilter.isColumnFilterable(colIndex);
1974
- }
1975
- getDistinctValuesForColumn(colId, maxValues = 500) {
1976
- return this.sortFilter.getDistinctValuesForColumn(colId, maxValues);
1977
- }
1978
- openFilterPopup(colIndex, anchorRect) {
1979
- this.sortFilter.openFilterPopup(colIndex, anchorRect);
1980
- }
1981
- closeFilterPopup() {
1982
- this.sortFilter.closeFilterPopup();
1983
- }
1984
- getSortModel() {
1985
- return this.sortFilter.getSortModel();
1986
- }
1987
- getFilterModel() {
1988
- return this.sortFilter.getFilterModel();
1989
- }
1990
- startEdit(row, col) {
1991
- this.editManager.startEdit(row, col);
1992
- }
1993
- updateEditValue(value) {
1994
- this.editManager.updateValue(value);
1995
- }
1996
- commitEdit() {
1997
- this.editManager.commit();
1998
- }
1999
- cancelEdit() {
2000
- this.editManager.cancel();
2001
- }
2002
- getEditState() {
2003
- return this.editManager.getState();
2004
- }
2005
- getCellValue(row, col) {
2006
- const rowData = this.cachedRows.get(row);
2007
- if (!rowData) return null;
2008
- const column = this.columns[col];
2009
- if (!column) return null;
2010
- return getFieldValue$2(rowData, column.field);
2011
- }
2012
- setCellValue(row, col, value) {
2013
- const rowData = this.cachedRows.get(row);
2014
- if (!rowData || typeof rowData !== "object") return;
2015
- const column = this.columns[col];
2016
- if (!column) return;
2017
- setFieldValue$1(rowData, column.field, value);
2018
- }
2019
- computeColumnPositions() {
2020
- this.columnPositions = [0];
2021
- let pos = 0;
2022
- for (const col of this.columns) if (!col.hidden) {
2023
- pos += col.width;
2024
- this.columnPositions.push(pos);
2025
- }
2026
- }
2027
- emitContentSize() {
2028
- const width = this.columnPositions[this.columnPositions.length - 1] ?? 0;
2029
- this.naturalContentHeight = this.totalRows * this.rowHeight + this.headerHeight;
2030
- if (this.naturalContentHeight > MAX_SCROLL_HEIGHT) {
2031
- this.virtualContentHeight = MAX_SCROLL_HEIGHT;
2032
- this.scrollRatio = MAX_SCROLL_HEIGHT / this.naturalContentHeight;
2033
- } else {
2034
- this.virtualContentHeight = this.naturalContentHeight;
2035
- this.scrollRatio = 1;
2036
- }
2037
- this.emit({
2038
- type: "SET_CONTENT_SIZE",
2039
- width,
2040
- height: this.virtualContentHeight,
2041
- viewportWidth: this.viewportWidth
2042
- });
2043
- }
2044
- emitHeaders() {
2045
- const sortInfoMap = this.sortFilter.getSortInfoMap();
2046
- for (let i = 0; i < this.columns.length; i++) {
2047
- const column = this.columns[i];
2048
- const colId = column.colId ?? column.field;
2049
- const sortInfo = sortInfoMap.get(colId);
2050
- this.emit({
2051
- type: "UPDATE_HEADER",
2052
- colIndex: i,
2053
- column,
2054
- sortDirection: sortInfo?.direction,
2055
- sortIndex: sortInfo?.index,
2056
- sortable: this.sortFilter.isColumnSortable(i),
2057
- filterable: this.sortFilter.isColumnFilterable(i),
2058
- hasFilter: this.sortFilter.hasActiveFilter(colId)
2059
- });
2060
- }
2061
- }
2062
- getColumns() {
2063
- return this.columns;
2064
- }
2065
- getColumnPositions() {
2066
- return [...this.columnPositions];
2067
- }
2068
- getRowCount() {
2069
- return this.totalRows;
2070
- }
2071
- getRowHeight() {
2072
- return this.rowHeight;
2073
- }
2074
- getHeaderHeight() {
2075
- return this.headerHeight;
2076
- }
2077
- getTotalWidth() {
2078
- return this.columnPositions[this.columnPositions.length - 1] ?? 0;
2079
- }
2080
- getTotalHeight() {
2081
- return this.virtualContentHeight || this.totalRows * this.rowHeight + this.headerHeight;
2082
- }
2083
- /**
2084
- * Check if scroll scaling is active (large datasets exceeding browser scroll limits).
2085
- * When scaling is active, scrollRatio < 1 and scroll positions are compressed.
2086
- */
2087
- isScalingActive() {
2088
- return this.scrollRatio < 1;
2089
- }
2090
- /**
2091
- * Get the natural (uncapped) content height.
2092
- * Useful for debugging or displaying actual content size.
2093
- */
2094
- getNaturalHeight() {
2095
- return this.naturalContentHeight || this.totalRows * this.rowHeight + this.headerHeight;
2096
- }
2097
- /**
2098
- * Get the scroll ratio used for scroll virtualization.
2099
- * Returns 1 when no virtualization is needed, < 1 when content exceeds browser limits.
2100
- */
2101
- getScrollRatio() {
2102
- return this.scrollRatio;
2103
- }
2104
- /**
2105
- * Get the visible row range (excluding overscan).
2106
- * Returns the first and last row indices that are actually visible in the viewport.
2107
- * Includes partially visible rows to avoid false positives when clicking on edge rows.
2108
- */
2109
- getVisibleRowRange() {
2110
- const contentHeight = this.viewportHeight - this.headerHeight;
2111
- const firstVisibleRow = Math.max(0, Math.floor(this.scrollTop / this.rowHeight));
2112
- const lastVisibleRow = Math.min(this.totalRows - 1, Math.ceil((this.scrollTop + contentHeight) / this.rowHeight) - 1);
2113
- return {
2114
- start: firstVisibleRow,
2115
- end: Math.max(firstVisibleRow, lastVisibleRow)
2116
- };
2117
- }
2118
- /**
2119
- * Get the scroll position needed to bring a row into view.
2120
- * Accounts for scroll scaling when active.
2121
- */
2122
- getScrollTopForRow(rowIndex) {
2123
- return rowIndex * this.rowHeight * this.scrollRatio;
2124
- }
2125
- /**
2126
- * Get the row index at a given viewport Y position.
2127
- * Accounts for scroll scaling when active.
2128
- * @param viewportY Y position in viewport (physical pixels below header, NOT including scroll)
2129
- * @param virtualScrollTop Current scroll position from container.scrollTop (virtual/scaled)
2130
- */
2131
- getRowIndexAtDisplayY(viewportY, virtualScrollTop) {
2132
- const naturalY = viewportY + (this.scrollRatio < 1 ? virtualScrollTop / this.scrollRatio : virtualScrollTop);
2133
- return Math.floor(naturalY / this.rowHeight);
2134
- }
2135
- getRowData(rowIndex) {
2136
- return this.cachedRows.get(rowIndex);
2137
- }
2138
- /**
2139
- * Refresh data from the data source.
2140
- */
2141
- async refresh() {
2142
- await this.fetchData();
2143
- this.highlight?.clearAllCaches();
2144
- this.slotPool.refreshAllSlots();
2145
- this.emitContentSize();
2146
- const visibleRange = this.getVisibleRowRange();
2147
- this.emit({
2148
- type: "UPDATE_VISIBLE_RANGE",
2149
- start: visibleRange.start,
2150
- end: visibleRange.end
2151
- });
2152
- }
2153
- /**
2154
- * Refresh slot display without refetching data.
2155
- * Useful after in-place data modifications like fill operations.
2156
- */
2157
- refreshSlotData() {
2158
- this.slotPool.refreshAllSlots();
2159
- }
2160
- /**
2161
- * Add rows to the grid at the specified index.
2162
- * If no index is provided, rows are added at the end.
2163
- */
2164
- addRows(rows, index) {
2165
- this.rowMutation.addRows(rows, index);
2166
- }
2167
- /**
2168
- * Update existing rows with partial data.
2169
- */
2170
- updateRows(updates) {
2171
- this.rowMutation.updateRows(updates);
2172
- }
2173
- /**
2174
- * Delete rows at the specified indices.
2175
- */
2176
- deleteRows(indices) {
2177
- this.rowMutation.deleteRows(indices);
2178
- }
2179
- /**
2180
- * Get a row by index.
2181
- */
2182
- getRow(index) {
2183
- return this.rowMutation.getRow(index);
2184
- }
2185
- /**
2186
- * Set a complete row at the specified index.
2187
- * Use this for complete row replacement. For partial updates, use updateRows.
2188
- */
2189
- setRow(index, data) {
2190
- this.rowMutation.setRow(index, data);
2191
- }
2192
- /**
2193
- * Update the data source and refresh.
2194
- */
2195
- async setDataSource(dataSource) {
2196
- this.dataSource = dataSource;
2197
- await this.refresh();
2198
- }
2199
- /**
2200
- * Update columns and recompute layout.
2201
- */
2202
- setColumns(columns) {
2203
- this.columns = columns;
2204
- this.computeColumnPositions();
2205
- this.emitContentSize();
2206
- this.emitHeaders();
2207
- this.slotPool.syncSlots();
2208
- }
2209
- /**
2210
- * Destroy the grid core and release all references.
2211
- * Call this before discarding the GridCore to ensure proper cleanup.
2212
- * This method is idempotent - safe to call multiple times.
2213
- */
2214
- destroy() {
2215
- if (this.isDestroyed) return;
2216
- this.isDestroyed = true;
2217
- this.slotPool.destroy();
2218
- this.highlight?.destroy();
2219
- this.sortFilter.destroy();
2220
- this.rowMutation.destroy();
2221
- this.cachedRows.clear();
2222
- this.emitter.clearListeners();
2223
- this.totalRows = 0;
2224
- }
2225
- };
2226
-
2227
- //#endregion
2228
- //#region src/sorting/worker-pool.ts
2229
- /**
2230
- * Manages a pool of Web Workers for parallel task execution.
2231
- * Workers are created lazily and reused across operations.
2232
- */
2233
- var WorkerPool = class {
2234
- workerCode;
2235
- maxWorkers;
2236
- workers = [];
2237
- workerUrl = null;
2238
- nextRequestId = 0;
2239
- isTerminated = false;
2240
- constructor(workerCode, options = {}) {
2241
- this.workerCode = workerCode;
2242
- this.maxWorkers = options.maxWorkers ?? (typeof navigator !== "undefined" ? navigator.hardwareConcurrency : 4) ?? 4;
2243
- if (options.preWarm) this.preWarmWorkers();
2244
- }
2245
- /**
2246
- * Get the current pool size (number of active workers).
2247
- */
2248
- getPoolSize() {
2249
- return this.workers.length;
2250
- }
2251
- /**
2252
- * Get the maximum pool size.
2253
- */
2254
- getMaxWorkers() {
2255
- return this.maxWorkers;
2256
- }
2257
- /**
2258
- * Check if the pool is available for use.
2259
- */
2260
- isAvailable() {
2261
- return !this.isTerminated && typeof Worker !== "undefined";
2262
- }
2263
- /**
2264
- * Execute a single task on an available worker.
2265
- * Returns the worker's response.
2266
- */
2267
- async execute(request, transferables) {
2268
- if (this.isTerminated) throw new Error("WorkerPool has been terminated");
2269
- if (typeof Worker === "undefined") throw new Error("Web Workers are not available in this environment");
2270
- const worker = this.getAvailableWorker();
2271
- const id = this.nextRequestId++;
2272
- const requestWithId = {
2273
- ...request,
2274
- id
2275
- };
2276
- return new Promise((resolve, reject) => {
2277
- worker.pendingRequests.set(id, {
2278
- resolve,
2279
- reject
2280
- });
2281
- worker.busy = true;
2282
- if (transferables && transferables.length > 0) worker.worker.postMessage(requestWithId, transferables);
2283
- else worker.worker.postMessage(requestWithId);
2284
- });
2285
- }
2286
- /**
2287
- * Execute multiple tasks in parallel across available workers.
2288
- * Each task is assigned to a different worker if possible.
2289
- * Returns results in the same order as the input requests.
2290
- */
2291
- async executeParallel(tasks) {
2292
- if (this.isTerminated) throw new Error("WorkerPool has been terminated");
2293
- if (tasks.length === 0) return [];
2294
- const numWorkers = Math.min(tasks.length, this.maxWorkers);
2295
- this.ensureWorkers(numWorkers);
2296
- const promises = tasks.map((task, index) => {
2297
- const workerIndex = index % this.workers.length;
2298
- const worker = this.workers[workerIndex];
2299
- const id = this.nextRequestId++;
2300
- const requestWithId = {
2301
- ...task.request,
2302
- id
2303
- };
2304
- return new Promise((resolve, reject) => {
2305
- worker.pendingRequests.set(id, {
2306
- resolve,
2307
- reject
2308
- });
2309
- worker.busy = true;
2310
- if (task.transferables && task.transferables.length > 0) worker.worker.postMessage(requestWithId, task.transferables);
2311
- else worker.worker.postMessage(requestWithId);
2312
- });
2313
- });
2314
- return Promise.all(promises);
2315
- }
2316
- /**
2317
- * Terminate all workers and clean up resources.
2318
- */
2319
- terminate() {
2320
- for (const workerState of this.workers) {
2321
- workerState.worker.terminate();
2322
- for (const [, pending] of workerState.pendingRequests) pending.reject(/* @__PURE__ */ new Error("Worker pool terminated"));
2323
- workerState.pendingRequests.clear();
2324
- }
2325
- this.workers = [];
2326
- if (this.workerUrl) {
2327
- URL.revokeObjectURL(this.workerUrl);
2328
- this.workerUrl = null;
2329
- }
2330
- this.isTerminated = true;
2331
- }
2332
- /**
2333
- * Pre-warm workers by creating them ahead of time.
2334
- */
2335
- preWarmWorkers() {
2336
- this.ensureWorkers(this.maxWorkers);
2337
- }
2338
- /**
2339
- * Ensure at least `count` workers exist in the pool.
2340
- */
2341
- ensureWorkers(count) {
2342
- const needed = Math.min(count, this.maxWorkers) - this.workers.length;
2343
- for (let i = 0; i < needed; i++) this.createWorker();
2344
- }
2345
- /**
2346
- * Get an available worker, creating one if needed.
2347
- */
2348
- getAvailableWorker() {
2349
- const idleWorker = this.workers.find((w) => !w.busy);
2350
- if (idleWorker) return idleWorker;
2351
- if (this.workers.length < this.maxWorkers) return this.createWorker();
2352
- return this.workers.reduce((min, w) => w.pendingRequests.size < min.pendingRequests.size ? w : min);
2353
- }
2354
- /**
2355
- * Create a new worker and add it to the pool.
2356
- */
2357
- createWorker() {
2358
- if (!this.workerUrl) {
2359
- const blob = new Blob([this.workerCode], { type: "application/javascript" });
2360
- this.workerUrl = URL.createObjectURL(blob);
2361
- }
2362
- const worker = new Worker(this.workerUrl);
2363
- const workerState = {
2364
- worker,
2365
- busy: false,
2366
- pendingRequests: /* @__PURE__ */ new Map()
2367
- };
2368
- worker.onmessage = (e) => {
2369
- const { id } = e.data;
2370
- const pending = workerState.pendingRequests.get(id);
2371
- if (pending) {
2372
- workerState.pendingRequests.delete(id);
2373
- if (workerState.pendingRequests.size === 0) workerState.busy = false;
2374
- if (e.data.type === "error") pending.reject(new Error(e.data.error));
2375
- else pending.resolve(e.data);
2376
- }
2377
- };
2378
- worker.onerror = (error) => {
2379
- for (const [, pending] of workerState.pendingRequests) pending.reject(/* @__PURE__ */ new Error(`Worker error: ${error.message}`));
2380
- workerState.pendingRequests.clear();
2381
- workerState.busy = false;
2382
- this.respawnWorker(workerState);
2383
- };
2384
- this.workers.push(workerState);
2385
- return workerState;
2386
- }
2387
- /**
2388
- * Respawn a failed worker.
2389
- */
2390
- respawnWorker(failedWorker) {
2391
- const index = this.workers.indexOf(failedWorker);
2392
- if (index === -1) return;
2393
- try {
2394
- failedWorker.worker.terminate();
2395
- } catch {}
2396
- this.workers.splice(index, 1);
2397
- if (this.workers.length < this.maxWorkers && !this.isTerminated) this.createWorker();
2398
- }
2399
- };
2400
-
2401
- //#endregion
2402
- //#region src/sorting/sort-worker.ts
2403
- function getFieldValue$1(row, field) {
2404
- const parts = field.split(".");
2405
- let value = row;
2406
- for (const part of parts) {
2407
- if (value == null || typeof value !== "object") return null;
2408
- value = value[part];
2409
- }
2410
- return value ?? null;
2411
- }
2412
- function compareValues$2(a, b) {
2413
- if (a == null && b == null) return 0;
2414
- if (a == null) return 1;
2415
- if (b == null) return -1;
2416
- const aNum = Number(a);
2417
- const bNum = Number(b);
2418
- if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum;
2419
- if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
2420
- return String(a).localeCompare(String(b));
2421
- }
2422
- function sortData(data, sortModel) {
2423
- return [...data].sort((a, b) => {
2424
- for (const { colId, direction } of sortModel) {
2425
- const comparison = compareValues$2(getFieldValue$1(a, colId), getFieldValue$1(b, colId));
2426
- if (comparison !== 0) return direction === "asc" ? comparison : -comparison;
2427
- }
2428
- return 0;
2429
- });
2430
- }
2431
- /**
2432
- * Inline worker code as a string for Blob URL creation.
2433
- * This allows the worker to function without bundler-specific configuration.
2434
- */
2435
- const SORT_WORKER_CODE = `
1
+ const e=e=>{let t=[0],n=0;for(let r of e)n+=r.width,t.push(n);return t},t=e=>e[e.length-1]??0,n=(n,r)=>{let i=e(n),a=t(i);if(r<=a||a===0)return{positions:i,widths:n.map(e=>e.width)};let o=r/a,s=n.map(e=>e.width*o),c=[0],l=0;for(let e of s)l+=e,c.push(l);return{positions:c,widths:s}},r=(e,t)=>{for(let n=0;n<t.length-1;n++)if(e>=t[n]&&e<t[n+1])return n;return e>=t[t.length-1]?t.length-2:0},i=e=>({minRow:Math.min(e.startRow,e.endRow),maxRow:Math.max(e.startRow,e.endRow),minCol:Math.min(e.startCol,e.endCol),maxCol:Math.max(e.startCol,e.endCol)}),a=(e,t,n)=>e>=n.minRow&&e<=n.maxRow&&t>=n.minCol&&t<=n.maxCol,o=(e,t,n)=>n?a(e,t,i(n)):!1,s=(e,t,n)=>n?.row===e&&n?.col===t,c=(e,t)=>!t||t.end<0||t.start>t.end?!0:e>=t.start&&e<=t.end,l=(e,t,n)=>n?.row===e&&n?.col===t,u=(e,t,n,r,a)=>{if(!n||!r||!a)return!1;let{minRow:o,maxRow:s,minCol:c,maxCol:l}=i(r),u=a.row>s,d=a.row<o;return u?e>s&&e<=a.row&&t>=c&&t<=l:d?e<o&&e>=a.row&&t>=c&&t<=l:!1},d=(e,t,n,r)=>{let i=[`gp-grid-cell`];return e&&i.push(`gp-grid-cell--active`),t&&!e&&i.push(`gp-grid-cell--selected`),n&&i.push(`gp-grid-cell--editing`),r&&i.push(`gp-grid-cell--fill-preview`),i.join(` `)},f=(e,t)=>{if(!t)return!1;let{minRow:n,maxRow:r}=i(t);return e>=n&&e<=r},p=(e,t)=>{if(!t)return!1;let{minCol:n,maxCol:r}=i(t);return e>=n&&e<=r},m=(e,t)=>{let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null},h=(e,t,n)=>{let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e++){let t=r[e];t in i||(i[t]={}),i=i[t]}let a=r[r.length-1];i[a]=n},g=()=>{let e=[];return{onInstruction:t=>(e.push(t),()=>{e=e.filter(e=>e!==t)}),emit:t=>{for(let n of e)n(t)},clearListeners:()=>{e=[]}}},_=()=>{let e=[],t=[];return{onInstruction:t=>(e.push(t),()=>{e=e.filter(e=>e!==t)}),onBatchInstruction:e=>(t.push(e),()=>{t=t.filter(t=>t!==e)}),emit:n=>{for(let t of e)t(n);for(let e of t)e([n])},emitBatch:n=>{if(n.length!==0){for(let e of t)e(n);for(let t of n)for(let n of e)n(t)}},clearListeners:()=>{e=[],t=[]}}};var v=class{state={activeCell:null,range:null,anchor:null,selectionMode:!1};options;emitter=g();onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;constructor(e){this.options=e}getState(){return{...this.state}}getActiveCell(){return this.state.activeCell}getSelectionRange(){return this.state.range}isSelected(e,t){let{range:n}=this.state;if(!n)return!1;let{minRow:r,maxRow:a,minCol:o,maxCol:s}=i(n);return e>=r&&e<=a&&t>=o&&t<=s}isActiveCell(e,t){let{activeCell:n}=this.state;return n?.row===e&&n?.col===t}startSelection(e,t={}){let{shift:n=!1,ctrl:r=!1}=t,{row:i,col:a}=this.clampPosition(e);n&&this.state.anchor?(this.state.range={startRow:this.state.anchor.row,startCol:this.state.anchor.col,endRow:i,endCol:a},this.state.activeCell={row:i,col:a}):(this.state.activeCell={row:i,col:a},this.state.anchor={row:i,col:a},this.state.range=null),this.state.selectionMode=r,this.emit({type:`SET_ACTIVE_CELL`,position:this.state.activeCell}),this.emit({type:`SET_SELECTION_RANGE`,range:this.state.range})}moveFocus(e,t=!1){if(!this.state.activeCell){this.startSelection({row:0,col:0});return}let{row:n,col:r}=this.state.activeCell,i=n,a=r;switch(e){case`up`:i=Math.max(0,n-1);break;case`down`:i=Math.min(this.options.getRowCount()-1,n+1);break;case`left`:a=Math.max(0,r-1);break;case`right`:a=Math.min(this.options.getColumnCount()-1,r+1);break}t?(this.state.anchor||(this.state.anchor={row:n,col:r}),this.state.range={startRow:this.state.anchor.row,startCol:this.state.anchor.col,endRow:i,endCol:a},this.state.activeCell={row:i,col:a},this.emit({type:`SET_ACTIVE_CELL`,position:this.state.activeCell}),this.emit({type:`SET_SELECTION_RANGE`,range:this.state.range})):(this.state.activeCell={row:i,col:a},this.state.anchor={row:i,col:a},this.state.range=null,this.emit({type:`SET_ACTIVE_CELL`,position:this.state.activeCell}),this.emit({type:`SET_SELECTION_RANGE`,range:null}))}selectAll(){let e=this.options.getRowCount(),t=this.options.getColumnCount();e===0||t===0||(this.state.range={startRow:0,startCol:0,endRow:e-1,endCol:t-1},this.state.activeCell||(this.state.activeCell={row:0,col:0},this.emit({type:`SET_ACTIVE_CELL`,position:this.state.activeCell})),this.emit({type:`SET_SELECTION_RANGE`,range:this.state.range}))}clearSelection(){this.state.activeCell=null,this.state.range=null,this.state.anchor=null,this.state.selectionMode=!1,this.emit({type:`SET_ACTIVE_CELL`,position:null}),this.emit({type:`SET_SELECTION_RANGE`,range:null})}setActiveCell(e,t){let n=this.clampPosition({row:e,col:t});this.state.activeCell=n,this.state.anchor=n,this.state.range=null,this.emit({type:`SET_ACTIVE_CELL`,position:this.state.activeCell}),this.emit({type:`SET_SELECTION_RANGE`,range:null})}setSelectionRange(e){this.state.range=e,this.emit({type:`SET_SELECTION_RANGE`,range:this.state.range})}getSelectedData(){let{range:e,activeCell:t}=this.state;if(!e&&!t)return[];let{minRow:n,maxRow:r,minCol:a,maxCol:o}=i(e||{startRow:t.row,startCol:t.col,endRow:t.row,endCol:t.col}),s=[];for(let e=n;e<=r;e++){let t=[];for(let n=a;n<=o;n++)t.push(this.options.getCellValue(e,n));s.push(t)}return s}async copySelectionToClipboard(){if(typeof navigator>`u`||typeof document>`u`)return;let e=this.getSelectedData();if(e.length===0)return;let t=e.map(e=>e.map(e=>e==null?``:String(e)).join(` `)).join(`
2
+ `);try{await navigator.clipboard.writeText(t)}catch{let e=document.createElement(`textarea`);e.value=t,e.style.position=`fixed`,e.style.left=`-9999px`,document.body.appendChild(e),e.select(),document.execCommand(`copy`),document.body.removeChild(e)}}destroy(){this.emitter.clearListeners(),this.state={activeCell:null,range:null,anchor:null,selectionMode:!1}}clampPosition(e){let t=this.options.getRowCount(),n=this.options.getColumnCount();return{row:Math.max(0,Math.min(e.row,t-1)),col:Math.max(0,Math.min(e.col,n-1))}}},y=class{state=null;options;emitter=g();onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;constructor(e){this.options=e}getState(){return this.state?{...this.state}:null}isActive(){return this.state!==null}startFillDrag(e){this.state={sourceRange:e,targetRow:e.endRow,targetCol:e.endCol},this.emit({type:`START_FILL`,sourceRange:e})}updateFillDrag(e,t){if(!this.state)return;let n=this.options.getRowCount(),r=this.options.getColumnCount();e=Math.max(0,Math.min(e,n-1)),t=Math.max(0,Math.min(t,r-1)),this.state.targetRow=e,this.state.targetCol=t,this.emit({type:`UPDATE_FILL`,targetRow:e,targetCol:t})}commitFillDrag(){if(!this.state)return;let{sourceRange:e,targetRow:t}=this.state,n=this.calculateFilledCells(e,t);for(let{row:e,col:t,value:r}of n)this.options.setCellValue(e,t,r);this.emit({type:`COMMIT_FILL`,filledCells:n}),this.state=null}cancelFillDrag(){this.state&&(this.state=null,this.emit({type:`CANCEL_FILL`}))}destroy(){this.emitter.clearListeners(),this.state=null}calculateFilledCells(e,t){let n=[],{minRow:r,maxRow:a,minCol:o,maxCol:s}=i(e),c=t>a,l=t<r;if(c||l)for(let e=o;e<=s;e++){let i=this.getSourceColumnValues(r,a,e),o=this.detectPattern(i);if(c)for(let r=a+1;r<=t;r++){let t=r-a-1,s=this.applyPattern(o,i,t);n.push({row:r,col:e,value:s})}else if(l)for(let a=r-1;a>=t;a--){let t=r-a-1,s=this.applyPattern(o,i,t,!0);n.push({row:a,col:e,value:s})}}return n}getSourceColumnValues(e,t,n){let r=[];for(let i=e;i<=t;i++)r.push(this.options.getCellValue(i,n));return r}detectPattern(e){if(e.length===0)return{type:`constant`,value:null};if(e.length===1)return{type:`constant`,value:e[0]??null};let t=e.map(e=>typeof e==`number`?e:Number(e));if(t.every(e=>!isNaN(e))){let e=[];for(let n=1;n<t.length;n++)e.push(t[n]-t[n-1]);if(e.every(t=>t===e[0])&&e[0]!==void 0)return{type:`arithmetic`,start:t[0],step:e[0]}}return{type:`repeat`,values:e}}applyPattern(e,t,n,r=!1){switch(e.type){case`constant`:return e.value;case`arithmetic`:{let i=r?-(n+1):n+1;return(r?e.start:e.start+e.step*(t.length-1))+e.step*i}case`repeat`:{let t=e.values.length;if(t===0)return null;if(r){let r=(t-1-n%t+t)%t;return e.values[r]??null}return e.values[n%t]??null}}}},b=class{state={slots:new Map,rowToSlot:new Map,nextSlotId:0};options;emitter=_();isDestroyed=!1;onInstruction=this.emitter.onInstruction;onBatchInstruction=this.emitter.onBatchInstruction;emit=this.emitter.emit;emitBatch=this.emitter.emitBatch;constructor(e){this.options=e}getSlotForRow(e){return this.state.rowToSlot.get(e)}getSlots(){return this.state.slots}syncSlots(){let e=this.options.getScrollTop(),t=this.options.getRowHeight(),n=this.options.getViewportHeight(),r=this.options.getTotalRows(),i=this.options.getOverscan(),a=Math.max(0,Math.floor(e/t)-i),o=Math.min(r-1,Math.ceil((e+n)/t)+i);if(r===0||o<a){this.destroyAllSlots();return}let s=new Set;for(let e=a;e<=o;e++)s.add(e);let c=[],l=[];for(let[e,t]of this.state.slots)s.has(t.rowIndex)?s.delete(t.rowIndex):(l.push(e),this.state.rowToSlot.delete(t.rowIndex));let u=Array.from(s);for(let e=0;e<u.length;e++){let t=u[e],n=this.options.getRowData(t);if(e<l.length){let r=l[e],i=this.state.slots.get(r),a=this.getRowTranslateY(t);i.rowIndex=t,i.rowData=n??{},i.translateY=a,this.state.rowToSlot.set(t,r),c.push({type:`ASSIGN_SLOT`,slotId:r,rowIndex:t,rowData:n??{}}),c.push({type:`MOVE_SLOT`,slotId:r,translateY:a})}else{let e=`slot-${this.state.nextSlotId++}`,r=this.getRowTranslateY(t),i={slotId:e,rowIndex:t,rowData:n??{},translateY:r};this.state.slots.set(e,i),this.state.rowToSlot.set(t,e),c.push({type:`CREATE_SLOT`,slotId:e}),c.push({type:`ASSIGN_SLOT`,slotId:e,rowIndex:t,rowData:n??{}}),c.push({type:`MOVE_SLOT`,slotId:e,translateY:r})}}for(let e=u.length;e<l.length;e++){let t=l[e];this.state.slots.delete(t),c.push({type:`DESTROY_SLOT`,slotId:t})}for(let[e,t]of this.state.slots){let n=this.getRowTranslateY(t.rowIndex);t.translateY!==n&&(t.translateY=n,c.push({type:`MOVE_SLOT`,slotId:e,translateY:n}))}this.emitBatch(c)}destroyAllSlots(){let e=[];for(let t of this.state.slots.keys())e.push({type:`DESTROY_SLOT`,slotId:t});this.state.slots.clear(),this.state.rowToSlot.clear(),this.emitBatch(e)}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.state.slots.clear(),this.state.rowToSlot.clear(),this.emitter.clearListeners())}refreshAllSlots(){let e=[],t=this.options.getTotalRows();for(let[n,r]of this.state.slots)if(r.rowIndex>=0&&r.rowIndex<t){let t=this.options.getRowData(r.rowIndex),i=this.getRowTranslateY(r.rowIndex);r.rowData=t??{},r.translateY=i,e.push({type:`ASSIGN_SLOT`,slotId:n,rowIndex:r.rowIndex,rowData:t??{}}),e.push({type:`MOVE_SLOT`,slotId:n,translateY:i})}this.emitBatch(e),this.syncSlots()}updateSlot(e){let t=this.state.rowToSlot.get(e);if(t){let n=this.options.getRowData(e);n&&this.emit({type:`ASSIGN_SLOT`,slotId:t,rowIndex:e,rowData:n})}}getRowTranslateY(e){let t=this.options.getRowHeight(),n=this.options.getHeaderHeight(),r=this.options.getScrollRatio(),i=this.options.getVirtualContentHeight(),a=this.options.getScrollTop(),o=e*t+n;if(r>=1)return o;let s=a,c=o-(s-s*r);return Math.max(0,Math.min(c,i))}},x=class{editState=null;options;emitter=g();onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;constructor(e){this.options=e}getState(){return this.editState?{...this.editState}:null}isEditing(){return this.editState!==null}isEditingCell(e,t){return this.editState!==null&&this.editState.row===e&&this.editState.col===t}startEdit(e,t){let n=this.options.getColumn(t);if(!n||n.editable!==!0)return!1;let r=this.options.getCellValue(e,t);return this.editState={row:e,col:t,initialValue:r,currentValue:r},this.emit({type:`START_EDIT`,row:e,col:t,initialValue:r}),!0}updateValue(e){this.editState&&(this.editState.currentValue=e)}commit(){if(!this.editState)return;let{row:e,col:t,currentValue:n}=this.editState;this.options.setCellValue(e,t,n),this.emit({type:`COMMIT_EDIT`,row:e,col:t,value:n}),this.editState=null,this.emit({type:`STOP_EDIT`}),this.options.onCommit?.(e,t,n)}cancel(){this.editState=null,this.emit({type:`STOP_EDIT`})}destroy(){this.emitter.clearListeners(),this.editState=null}},ee=class{core;deps;isDraggingSelection=!1;isDraggingFill=!1;fillSourceRange=null;fillTarget=null;constructor(e,t){this.core=e,this.deps=t}updateDeps(e){this.deps={...this.deps,...e}}getDragState(){return{isDragging:this.isDraggingSelection||this.isDraggingFill,dragType:this.isDraggingFill?`fill`:this.isDraggingSelection?`selection`:null,fillSourceRange:this.fillSourceRange,fillTarget:this.fillTarget}}handleCellMouseDown(e,t,n){return n.button!==0||this.core.getEditState()!==null?{preventDefault:!1,stopPropagation:!1}:(this.core.selection.startSelection({row:e,col:t},{shift:n.shiftKey,ctrl:n.ctrlKey||n.metaKey}),{preventDefault:!1,stopPropagation:!1,focusContainer:!0,startDrag:n.shiftKey?void 0:`selection`})}handleCellDoubleClick(e,t){this.core.startEdit(e,t)}handleCellMouseEnter(e,t){this.core.highlight?.setHoverPosition({row:e,col:t})}handleCellMouseLeave(){this.core.highlight?.setHoverPosition(null)}handleFillHandleMouseDown(e,t,n){if(!e&&!t)return{preventDefault:!1,stopPropagation:!1};let r=t??{startRow:e.row,startCol:e.col,endRow:e.row,endCol:e.col};return this.core.fill.startFillDrag(r),this.fillSourceRange=r,this.fillTarget={row:Math.max(r.startRow,r.endRow),col:Math.max(r.startCol,r.endCol)},this.isDraggingFill=!0,{preventDefault:!0,stopPropagation:!0,startDrag:`fill`}}handleHeaderClick(e,t){let n=this.core.getSortModel().find(t=>t.colId===e)?.direction,r=n==null?`asc`:n===`asc`?`desc`:null;this.core.setSort(e,r,t)}startSelectionDrag(){this.isDraggingSelection=!0}handleDragMove(e,t){if(!this.isDraggingSelection&&!this.isDraggingFill)return null;let{top:n,left:i,width:a,height:o,scrollTop:s,scrollLeft:c}=t,l=this.deps.getHeaderHeight(),u=this.deps.getColumnPositions(),d=this.deps.getColumnCount(),f=e.clientX-i+c,p=e.clientY-n-l,m=Math.max(0,Math.min(this.core.getRowIndexAtDisplayY(p,s),this.core.getRowCount()-1)),h=Math.max(0,Math.min(r(f,u),d-1)),g=this.deps.getOriginalColumnIndex?this.deps.getOriginalColumnIndex(h):h;this.isDraggingSelection&&this.core.selection.startSelection({row:m,col:g},{shift:!0}),this.isDraggingFill&&(this.core.fill.updateFillDrag(m,g),this.fillTarget={row:m,col:g});let _=e.clientY-n,v=e.clientX-i;return{targetRow:m,targetCol:g,autoScroll:this.calculateAutoScroll(_,v,o,a,l)}}handleDragEnd(){this.isDraggingFill&&(this.core.fill.commitFillDrag(),this.core.refreshSlotData()),this.isDraggingSelection=!1,this.isDraggingFill=!1,this.fillSourceRange=null,this.fillTarget=null}handleWheel(e,t,n){return this.core.isScalingActive()?{dy:e*n,dx:t*n}:null}handleKeyDown(e,t,n,r){if(r||n&&e.key!==`Enter`&&e.key!==`Escape`&&e.key!==`Tab`)return{preventDefault:!1};let{selection:i}=this.core,a=e.shiftKey,o=e.ctrlKey||e.metaKey,s=(e=>{switch(e){case`ArrowUp`:return`up`;case`ArrowDown`:return`down`;case`ArrowLeft`:return`left`;case`ArrowRight`:return`right`;default:return null}})(e.key);if(s)return i.moveFocus(s,a),{preventDefault:!0,scrollToCell:i.getActiveCell()??void 0};switch(e.key){case`Enter`:return n?this.core.commitEdit():t&&this.core.startEdit(t.row,t.col),{preventDefault:!0};case`Escape`:return n?this.core.cancelEdit():i.clearSelection(),{preventDefault:!0};case`Tab`:return n&&this.core.commitEdit(),i.moveFocus(a?`left`:`right`,!1),{preventDefault:!0};case`a`:if(o)return i.selectAll(),{preventDefault:!0};break;case`c`:if(o)return i.copySelectionToClipboard(),{preventDefault:!0};break;case`F2`:return t&&!n&&this.core.startEdit(t.row,t.col),{preventDefault:!0};case`Delete`:case`Backspace`:if(t&&!n)return this.core.startEdit(t.row,t.col),{preventDefault:!0};break;default:t&&!n&&!o&&e.key.length===1&&this.core.startEdit(t.row,t.col);break}return{preventDefault:!1}}calculateAutoScroll(e,t,n,r,i){let a=0,o=0;return e<40+i?o=-10:e>n-40&&(o=10),t<40?a=-10:t>r-40&&(a=10),a!==0||o!==0?{dx:a,dy:o}:null}},S=class{options;highlightingOptions;hoverPosition=null;emitter=g();onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;rowClassCache=new Map;columnClassCache=new Map;cellClassCache=new Map;constructor(e,t={}){this.options=e,this.highlightingOptions=t}isEnabled(){return!!(this.highlightingOptions.computeRowClasses||this.highlightingOptions.computeColumnClasses||this.highlightingOptions.computeCellClasses)}updateOptions(e){this.highlightingOptions=e,this.clearAllCaches()}setHoverPosition(e){this.isEnabled()&&(this.hoverPosition?.row===e?.row&&this.hoverPosition?.col===e?.col||(this.rowClassCache.clear(),this.columnClassCache.clear(),this.cellClassCache.clear(),this.hoverPosition=e,this.emit({type:`SET_HOVER_POSITION`,position:e})))}getHoverPosition(){return this.hoverPosition}onSelectionChange(){this.rowClassCache.clear(),this.columnClassCache.clear(),this.cellClassCache.clear()}buildRowContext(e,t){let n=this.options.getActiveCell(),r=this.options.getSelectionRange();return{rowIndex:e,colIndex:null,column:void 0,rowData:t,hoverPosition:this.hoverPosition,activeCell:n,selectionRange:r,isHovered:this.hoverPosition?.row===e,isActive:n?.row===e,isSelected:f(e,r)}}buildColumnContext(e,t){let n=this.options.getActiveCell(),r=this.options.getSelectionRange();return{rowIndex:null,colIndex:e,column:t,rowData:void 0,hoverPosition:this.hoverPosition,activeCell:n,selectionRange:r,isHovered:this.hoverPosition?.col===e,isActive:n?.col===e,isSelected:p(e,r)}}buildCellContext(e,t,n,r){let a=this.options.getActiveCell(),o=this.options.getSelectionRange(),s=this.hoverPosition?.row===e&&this.hoverPosition?.col===t,c=!1;if(o){let{minRow:n,maxRow:r,minCol:a,maxCol:s}=i(o);c=e>=n&&e<=r&&t>=a&&t<=s}return{rowIndex:e,colIndex:t,column:n,rowData:r,hoverPosition:this.hoverPosition,activeCell:a,selectionRange:o,isHovered:s,isActive:a?.row===e&&a?.col===t,isSelected:c}}computeRowClasses(e,t){let n=this.highlightingOptions.computeRowClasses;if(!n)return[];let r=this.rowClassCache.get(e);if(r!==void 0)return r;let i=n(this.buildRowContext(e,t));return this.rowClassCache.set(e,i),i}computeColumnClasses(e,t){let n=this.columnClassCache.get(e);if(n!==void 0)return n;let r=this.buildColumnContext(e,t),i;if(t.computeColumnClasses)i=t.computeColumnClasses(r);else if(this.highlightingOptions.computeColumnClasses)i=this.highlightingOptions.computeColumnClasses(r);else return[];return this.columnClassCache.set(e,i),i}computeCellClasses(e,t,n,r){let i=`${e},${t}`,a=this.cellClassCache.get(i);if(a!==void 0)return a;let o=this.buildCellContext(e,t,n,r),s;if(n.computeCellClasses)s=n.computeCellClasses(o);else if(this.highlightingOptions.computeCellClasses)s=this.highlightingOptions.computeCellClasses(o);else return[];return this.cellClassCache.set(i,s),s}computeCombinedCellClasses(e,t,n,r){let i=this.computeColumnClasses(t,n),a=this.computeCellClasses(e,t,n,r);return[...i,...a]}clearAllCaches(){this.rowClassCache.clear(),this.columnClassCache.clear(),this.cellClassCache.clear()}destroy(){this.emitter.clearListeners(),this.clearAllCaches(),this.hoverPosition=null}},te=class{options;emitter=g();sortModel=[];filterModel={};openFilterColIndex=null;onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;constructor(e){this.options=e}async setSort(e,t,n=!1){if(!this.options.isSortingEnabled()||this.options.getColumns().find(t=>(t.colId??t.field)===e)?.sortable===!1)return;let r=this.sortModel.findIndex(t=>t.colId===e);n?t===null?r>=0&&this.sortModel.splice(r,1):r>=0?this.sortModel[r].direction=t:this.sortModel.push({colId:e,direction:t}):this.sortModel=t===null?[]:[{colId:e,direction:t}],await this.options.onSortFilterChange(),this.options.onDataRefreshed()}getSortModel(){return[...this.sortModel]}async setFilter(e,t){this.options.getColumns().find(t=>(t.colId??t.field)===e)?.filterable!==!1&&(t===null||typeof t==`string`&&t.trim()===``||typeof t==`object`&&t.conditions&&t.conditions.length===0?delete this.filterModel[e]:typeof t==`string`?this.filterModel[e]={conditions:[{type:`text`,operator:`contains`,value:t}],combination:`and`}:this.filterModel[e]=t,await this.options.onSortFilterChange(),this.options.onDataRefreshed())}getFilterModel(){return{...this.filterModel}}hasActiveFilter(e){let t=this.filterModel[e];return t?t.conditions.length>0:!1}isColumnSortable(e){return this.options.isSortingEnabled()?this.options.getColumns()[e]?.sortable!==!1:!1}isColumnFilterable(e){return this.options.getColumns()[e]?.filterable!==!1}getDistinctValuesForColumn(e,t=500){let n=this.options.getColumns().find(t=>(t.colId??t.field)===e);if(!n)return[];let r=this.options.getCachedRows(),i=new Map;for(let e of r.values()){let r=m(e,n.field);if(Array.isArray(r)){let e=[...r].sort((e,t)=>String(e).localeCompare(String(t),void 0,{numeric:!0,sensitivity:`base`})),n=JSON.stringify(e);if(!i.has(n)&&(i.set(n,e),i.size>=t))break}else{let e=JSON.stringify(r);if(!i.has(e)&&(i.set(e,r),i.size>=t))break}}let a=Array.from(i.values());return a.sort((e,t)=>{let n=Array.isArray(e)?e.join(`, `):String(e??``),r=Array.isArray(t)?t.join(`, `):String(t??``);return n.localeCompare(r,void 0,{numeric:!0,sensitivity:`base`})}),a}openFilterPopup(e,t){if(this.openFilterColIndex===e){this.closeFilterPopup();return}let n=this.options.getColumns()[e];if(!n||!this.isColumnFilterable(e))return;let r=n.colId??n.field,i=this.getDistinctValuesForColumn(r);this.openFilterColIndex=e,this.emit({type:`OPEN_FILTER_POPUP`,colIndex:e,column:n,anchorRect:t,distinctValues:i,currentFilter:this.filterModel[r]})}closeFilterPopup(){this.openFilterColIndex=null,this.emit({type:`CLOSE_FILTER_POPUP`})}getSortInfoMap(){let e=new Map;return this.sortModel.forEach((t,n)=>{e.set(t.colId,{direction:t.direction,index:n+1})}),e}destroy(){this.emitter.clearListeners(),this.sortModel=[],this.filterModel={},this.openFilterColIndex=null}},ne=class{options;emitter=g();onInstruction=this.emitter.onInstruction;emit=this.emitter.emit;constructor(e){this.options=e}getRow(e){return this.options.getCachedRows().get(e)}addRows(e,t){if(e.length===0)return;let n=this.options.getCachedRows(),r=this.options.getTotalRows(),i=t??r,a=r+e.length;if(i<r){let t=new Map;for(let[r,a]of n)r>=i?t.set(r+e.length,a):t.set(r,a);this.options.setCachedRows(t)}let o=this.options.getCachedRows();e.forEach((e,t)=>{o.set(i+t,e)}),this.options.setTotalRows(a);let s=e.map((e,t)=>i+t);this.emit({type:`ROWS_ADDED`,indices:s,count:s.length,totalRows:a}),this.options.emitContentSize(),this.options.refreshAllSlots()}updateRows(e){if(e.length===0)return;let t=this.options.getCachedRows(),n=[];for(let r of e){let e=t.get(r.index);e&&(t.set(r.index,{...e,...r.data}),n.push(r.index))}if(n.length!==0){this.emit({type:`ROWS_UPDATED`,indices:n});for(let e of n)this.options.updateSlot(e)}}deleteRows(e){if(e.length===0)return;let t=this.options.getCachedRows(),n=this.options.getTotalRows(),r=[...e].sort((e,t)=>t-e);for(let e of r){if(e<0||e>=n)continue;t.delete(e);let r=new Map;for(let[n,i]of t)n>e?r.set(n-1,i):r.set(n,i);this.options.setCachedRows(r),n--}this.options.setTotalRows(n),this.options.clearSelectionIfInvalid(n),this.emit({type:`ROWS_REMOVED`,indices:r,totalRows:n}),this.options.emitContentSize(),this.options.refreshAllSlots()}setRow(e,t){let n=this.options.getTotalRows();e<0||e>=n||(this.options.getCachedRows().set(e,t),this.emit({type:`ROWS_UPDATED`,indices:[e]}),this.options.updateSlot(e))}destroy(){this.emitter.clearListeners()}};const C=1e7;var re=class{columns;dataSource;rowHeight;headerHeight;overscan;sortingEnabled;scrollTop=0;scrollLeft=0;viewportWidth=800;viewportHeight=600;cachedRows=new Map;totalRows=0;currentPageIndex=0;pageSize=1e6;selection;fill;input;highlight;sortFilter;rowMutation;slotPool;editManager;columnPositions=[];emitter=_();onInstruction=this.emitter.onInstruction;onBatchInstruction=this.emitter.onBatchInstruction;emit=this.emitter.emit;emitBatch=this.emitter.emitBatch;naturalContentHeight=0;virtualContentHeight=0;scrollRatio=1;isDestroyed=!1;constructor(e){this.columns=e.columns,this.dataSource=e.dataSource,this.rowHeight=e.rowHeight,this.headerHeight=e.headerHeight??e.rowHeight,this.overscan=e.overscan??3,this.sortingEnabled=e.sortingEnabled??!0,this.computeColumnPositions(),this.selection=new v({getRowCount:()=>this.totalRows,getColumnCount:()=>this.columns.length,getCellValue:(e,t)=>this.getCellValue(e,t),getRowData:e=>this.cachedRows.get(e),getColumn:e=>this.columns[e]}),this.selection.onInstruction(e=>{this.emit(e),this.highlight?.onSelectionChange()}),e.highlighting?(this.highlight=new S({getActiveCell:()=>this.selection.getActiveCell(),getSelectionRange:()=>this.selection.getSelectionRange(),getColumn:e=>this.columns[e]},e.highlighting),this.highlight.onInstruction(e=>this.emit(e))):this.highlight=null,this.fill=new y({getRowCount:()=>this.totalRows,getColumnCount:()=>this.columns.length,getCellValue:(e,t)=>this.getCellValue(e,t),getColumn:e=>this.columns[e],setCellValue:(e,t,n)=>this.setCellValue(e,t,n)}),this.fill.onInstruction(e=>this.emit(e)),this.slotPool=new b({getRowHeight:()=>this.rowHeight,getHeaderHeight:()=>this.headerHeight,getOverscan:()=>this.overscan,getScrollTop:()=>this.scrollTop,getViewportHeight:()=>this.viewportHeight,getTotalRows:()=>this.totalRows,getScrollRatio:()=>this.scrollRatio,getVirtualContentHeight:()=>this.virtualContentHeight,getRowData:e=>this.cachedRows.get(e)}),this.slotPool.onBatchInstruction(e=>this.emitBatch(e)),this.editManager=new x({getColumn:e=>this.columns[e],getCellValue:(e,t)=>this.getCellValue(e,t),setCellValue:(e,t,n)=>this.setCellValue(e,t,n),onCommit:e=>{this.slotPool.updateSlot(e)}}),this.editManager.onInstruction(e=>this.emit(e)),this.sortFilter=new te({getColumns:()=>this.columns,isSortingEnabled:()=>this.sortingEnabled,getCachedRows:()=>this.cachedRows,onSortFilterChange:async()=>{await this.fetchData(),this.highlight?.clearAllCaches(),this.slotPool.refreshAllSlots()},onDataRefreshed:()=>{this.emitContentSize(),this.emitHeaders()}}),this.sortFilter.onInstruction(e=>this.emit(e)),this.rowMutation=new ne({getCachedRows:()=>this.cachedRows,setCachedRows:e=>{this.cachedRows=e},getTotalRows:()=>this.totalRows,setTotalRows:e=>{this.totalRows=e},updateSlot:e=>this.slotPool.updateSlot(e),refreshAllSlots:()=>this.slotPool.refreshAllSlots(),emitContentSize:()=>this.emitContentSize(),clearSelectionIfInvalid:e=>{let t=this.selection.getActiveCell();t&&t.row>=e&&this.selection.clearSelection()}}),this.rowMutation.onInstruction(e=>this.emit(e)),this.input=new ee(this,{getHeaderHeight:()=>this.headerHeight,getRowHeight:()=>this.rowHeight,getColumnPositions:()=>this.columnPositions,getColumnCount:()=>this.columns.length})}async initialize(){await this.fetchData(),this.slotPool.syncSlots(),this.emitContentSize(),this.emitHeaders()}setViewport(e,t,n,r){let i=this.scrollRatio<1?e/this.scrollRatio:e,a=this.viewportWidth!==n||this.viewportHeight!==r;if(!(this.scrollTop!==i||this.scrollLeft!==t||a))return;this.scrollTop=i,this.scrollLeft=t,this.viewportWidth=n,this.viewportHeight=r,this.slotPool.syncSlots();let o=this.getVisibleRowRange();this.emit({type:`UPDATE_VISIBLE_RANGE`,start:o.start,end:o.end}),a&&this.emitContentSize()}async fetchData(){this.emit({type:`DATA_LOADING`});try{let e=this.sortFilter.getSortModel(),t=this.sortFilter.getFilterModel(),n={pagination:{pageIndex:this.currentPageIndex,pageSize:this.pageSize},sort:e.length>0?e:void 0,filter:Object.keys(t).length>0?t:void 0},r=await this.dataSource.fetch(n);this.cachedRows.clear(),r.rows.forEach((e,t)=>{this.cachedRows.set(this.currentPageIndex*this.pageSize+t,e)}),this.totalRows=r.totalRows,r.totalRows>r.rows.length&&this.currentPageIndex===0&&await this.fetchAllData(),this.emit({type:`DATA_LOADED`,totalRows:this.totalRows})}catch(e){this.emit({type:`DATA_ERROR`,error:e instanceof Error?e.message:String(e)})}}async fetchAllData(){let e=Math.ceil(this.totalRows/this.pageSize),t=this.sortFilter.getSortModel(),n=this.sortFilter.getFilterModel();for(let r=1;r<e;r++){let e={pagination:{pageIndex:r,pageSize:this.pageSize},sort:t.length>0?t:void 0,filter:Object.keys(n).length>0?n:void 0};(await this.dataSource.fetch(e)).rows.forEach((e,t)=>{this.cachedRows.set(r*this.pageSize+t,e)})}}async setSort(e,t,n=!1){return this.sortFilter.setSort(e,t,n)}async setFilter(e,t){return this.sortFilter.setFilter(e,t)}hasActiveFilter(e){return this.sortFilter.hasActiveFilter(e)}isColumnSortable(e){return this.sortFilter.isColumnSortable(e)}isColumnFilterable(e){return this.sortFilter.isColumnFilterable(e)}getDistinctValuesForColumn(e,t=500){return this.sortFilter.getDistinctValuesForColumn(e,t)}openFilterPopup(e,t){this.sortFilter.openFilterPopup(e,t)}closeFilterPopup(){this.sortFilter.closeFilterPopup()}getSortModel(){return this.sortFilter.getSortModel()}getFilterModel(){return this.sortFilter.getFilterModel()}startEdit(e,t){this.editManager.startEdit(e,t)}updateEditValue(e){this.editManager.updateValue(e)}commitEdit(){this.editManager.commit()}cancelEdit(){this.editManager.cancel()}getEditState(){return this.editManager.getState()}getCellValue(e,t){let n=this.cachedRows.get(e);if(!n)return null;let r=this.columns[t];return r?m(n,r.field):null}setCellValue(e,t,n){let r=this.cachedRows.get(e);if(!r||typeof r!=`object`)return;let i=this.columns[t];i&&h(r,i.field,n)}computeColumnPositions(){this.columnPositions=[0];let e=0;for(let t of this.columns)t.hidden||(e+=t.width,this.columnPositions.push(e))}emitContentSize(){let e=this.columnPositions[this.columnPositions.length-1]??0;this.naturalContentHeight=this.totalRows*this.rowHeight+this.headerHeight,this.naturalContentHeight>C?(this.virtualContentHeight=C,this.scrollRatio=C/this.naturalContentHeight):(this.virtualContentHeight=this.naturalContentHeight,this.scrollRatio=1),this.emit({type:`SET_CONTENT_SIZE`,width:e,height:this.virtualContentHeight,viewportWidth:this.viewportWidth})}emitHeaders(){let e=this.sortFilter.getSortInfoMap();for(let t=0;t<this.columns.length;t++){let n=this.columns[t],r=n.colId??n.field,i=e.get(r);this.emit({type:`UPDATE_HEADER`,colIndex:t,column:n,sortDirection:i?.direction,sortIndex:i?.index,sortable:this.sortFilter.isColumnSortable(t),filterable:this.sortFilter.isColumnFilterable(t),hasFilter:this.sortFilter.hasActiveFilter(r)})}}getColumns(){return this.columns}getColumnPositions(){return[...this.columnPositions]}getRowCount(){return this.totalRows}getRowHeight(){return this.rowHeight}getHeaderHeight(){return this.headerHeight}getTotalWidth(){return this.columnPositions[this.columnPositions.length-1]??0}getTotalHeight(){return this.virtualContentHeight||this.totalRows*this.rowHeight+this.headerHeight}isScalingActive(){return this.scrollRatio<1}getNaturalHeight(){return this.naturalContentHeight||this.totalRows*this.rowHeight+this.headerHeight}getScrollRatio(){return this.scrollRatio}getVisibleRowRange(){let e=this.viewportHeight-this.headerHeight,t=Math.max(0,Math.floor(this.scrollTop/this.rowHeight)),n=Math.min(this.totalRows-1,Math.ceil((this.scrollTop+e)/this.rowHeight)-1);return{start:t,end:Math.max(t,n)}}getScrollTopForRow(e){return e*this.rowHeight*this.scrollRatio}getRowIndexAtDisplayY(e,t){let n=e+(this.scrollRatio<1?t/this.scrollRatio:t);return Math.floor(n/this.rowHeight)}getRowData(e){return this.cachedRows.get(e)}async refresh(){await this.fetchData(),this.highlight?.clearAllCaches(),this.slotPool.refreshAllSlots(),this.emitContentSize();let e=this.getVisibleRowRange();this.emit({type:`UPDATE_VISIBLE_RANGE`,start:e.start,end:e.end})}refreshSlotData(){this.slotPool.refreshAllSlots()}addRows(e,t){this.rowMutation.addRows(e,t)}updateRows(e){this.rowMutation.updateRows(e)}deleteRows(e){this.rowMutation.deleteRows(e)}getRow(e){return this.rowMutation.getRow(e)}setRow(e,t){this.rowMutation.setRow(e,t)}async setDataSource(e){this.dataSource=e,await this.refresh()}setColumns(e){this.columns=e,this.computeColumnPositions(),this.emitContentSize(),this.emitHeaders(),this.slotPool.syncSlots()}destroy(){this.isDestroyed||(this.isDestroyed=!0,this.slotPool.destroy(),this.highlight?.destroy(),this.sortFilter.destroy(),this.rowMutation.destroy(),this.cachedRows.clear(),this.emitter.clearListeners(),this.totalRows=0)}},w=class{workerCode;maxWorkers;workers=[];workerUrl=null;nextRequestId=0;isTerminated=!1;constructor(e,t={}){this.workerCode=e,this.maxWorkers=t.maxWorkers??(typeof navigator<`u`?navigator.hardwareConcurrency:4)??4,t.preWarm&&this.preWarmWorkers()}getPoolSize(){return this.workers.length}getMaxWorkers(){return this.maxWorkers}isAvailable(){return!this.isTerminated&&typeof Worker<`u`}async execute(e,t){if(this.isTerminated)throw Error(`WorkerPool has been terminated`);if(typeof Worker>`u`)throw Error(`Web Workers are not available in this environment`);let n=this.getAvailableWorker(),r=this.nextRequestId++,i={...e,id:r};return new Promise((e,a)=>{n.pendingRequests.set(r,{resolve:e,reject:a}),n.busy=!0,t&&t.length>0?n.worker.postMessage(i,t):n.worker.postMessage(i)})}async executeParallel(e){if(this.isTerminated)throw Error(`WorkerPool has been terminated`);if(e.length===0)return[];let t=Math.min(e.length,this.maxWorkers);this.ensureWorkers(t);let n=e.map((e,t)=>{let n=t%this.workers.length,r=this.workers[n],i=this.nextRequestId++,a={...e.request,id:i};return new Promise((t,n)=>{r.pendingRequests.set(i,{resolve:t,reject:n}),r.busy=!0,e.transferables&&e.transferables.length>0?r.worker.postMessage(a,e.transferables):r.worker.postMessage(a)})});return Promise.all(n)}terminate(){for(let e of this.workers){e.worker.terminate();for(let[,t]of e.pendingRequests)t.reject(Error(`Worker pool terminated`));e.pendingRequests.clear()}this.workers=[],this.workerUrl&&=(URL.revokeObjectURL(this.workerUrl),null),this.isTerminated=!0}preWarmWorkers(){this.ensureWorkers(this.maxWorkers)}ensureWorkers(e){let t=Math.min(e,this.maxWorkers)-this.workers.length;for(let e=0;e<t;e++)this.createWorker()}getAvailableWorker(){return this.workers.find(e=>!e.busy)||(this.workers.length<this.maxWorkers?this.createWorker():this.workers.reduce((e,t)=>t.pendingRequests.size<e.pendingRequests.size?t:e))}createWorker(){if(!this.workerUrl){let e=new Blob([this.workerCode],{type:`application/javascript`});this.workerUrl=URL.createObjectURL(e)}let e=new Worker(this.workerUrl),t={worker:e,busy:!1,pendingRequests:new Map};return e.onmessage=e=>{let{id:n}=e.data,r=t.pendingRequests.get(n);r&&(t.pendingRequests.delete(n),t.pendingRequests.size===0&&(t.busy=!1),e.data.type===`error`?r.reject(Error(e.data.error)):r.resolve(e.data))},e.onerror=e=>{for(let[,n]of t.pendingRequests)n.reject(Error(`Worker error: ${e.message}`));t.pendingRequests.clear(),t.busy=!1,this.respawnWorker(t)},this.workers.push(t),t}respawnWorker(e){let t=this.workers.indexOf(e);if(t!==-1){try{e.worker.terminate()}catch{}this.workers.splice(t,1),this.workers.length<this.maxWorkers&&!this.isTerminated&&this.createWorker()}}};function T(e,t){let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null}function ie(e,t){if(e==null&&t==null)return 0;if(e==null)return 1;if(t==null)return-1;let n=Number(e),r=Number(t);return!isNaN(n)&&!isNaN(r)?n-r:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():String(e).localeCompare(String(t))}function ae(e,t){return[...e].sort((e,n)=>{for(let{colId:r,direction:i}of t){let t=ie(T(e,r),T(n,r));if(t!==0)return i===`asc`?t:-t}return 0})}typeof self<`u`&&self.onmessage!==void 0&&(self.onmessage=e=>{let{type:t,id:n,data:r,sortModel:i}=e.data;if(t===`sort`)try{let e=ae(r,i);self.postMessage({type:`sorted`,id:n,data:e})}catch(e){self.postMessage({type:`error`,id:n,error:String(e)})}});var oe=class{heap=[];multiplier;constructor(e){this.multiplier=e===`asc`?1:-1}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){if(this.heap.length===0)return;let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t&&(this.heap[0]=t,this.bubbleDown(0)),e}size(){return this.heap.length}bubbleUp(e){for(;e>0;){let t=Math.floor((e-1)/2);if(this.compare(this.heap[e],this.heap[t])>=0)break;this.swap(e,t),e=t}}bubbleDown(e){let t=this.heap.length;for(;;){let n=2*e+1,r=2*e+2,i=e;if(n<t&&this.compare(this.heap[n],this.heap[i])<0&&(i=n),r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),i===e)break;this.swap(e,i),e=i}}compare(e,t){return(e.value-t.value)*this.multiplier}swap(e,t){let n=this.heap[e];this.heap[e]=this.heap[t],this.heap[t]=n}},se=class{heap=[];directions;constructor(e){this.directions=e}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){if(this.heap.length===0)return;let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t&&(this.heap[0]=t,this.bubbleDown(0)),e}size(){return this.heap.length}bubbleUp(e){for(;e>0;){let t=Math.floor((e-1)/2);if(this.compare(this.heap[e],this.heap[t])>=0)break;this.swap(e,t),e=t}}bubbleDown(e){let t=this.heap.length;for(;;){let n=2*e+1,r=2*e+2,i=e;if(n<t&&this.compare(this.heap[n],this.heap[i])<0&&(i=n),r<t&&this.compare(this.heap[r],this.heap[i])<0&&(i=r),i===e)break;this.swap(e,i),e=i}}compare(e,t){for(let n=0;n<this.directions.length;n++){let r=(e.values[n]-t.values[n])*this.directions[n];if(r!==0)return r}return 0}swap(e,t){let n=this.heap[e];this.heap[e]=this.heap[t],this.heap[t]=n}};function E(e,t){if(e.length===0)return new Uint32Array;if(e.length===1){let t=e[0],n=new Uint32Array(t.indices.length);for(let e=0;e<t.indices.length;e++)n[e]=t.indices[e]+t.offset;return n}let n=0;for(let t of e)n+=t.indices.length;let r=new Uint32Array(n),i=new oe(t);for(let t=0;t<e.length;t++){let n=e[t];if(n.indices.length>0){let e=n.indices[0];i.push({chunkIndex:t,positionInChunk:0,value:n.values[0],globalIndex:e+n.offset})}}let a=0;for(;i.size()>0;){let t=i.pop();r[a++]=t.globalIndex;let n=e[t.chunkIndex],o=t.positionInChunk+1;if(o<n.indices.length){let e=n.indices[o];i.push({chunkIndex:t.chunkIndex,positionInChunk:o,value:n.values[o],globalIndex:e+n.offset})}}return r}function D(e){if(e.length===0)return new Uint32Array;if(e.length===1){let t=e[0],n=new Uint32Array(t.indices.length);for(let e=0;e<t.indices.length;e++)n[e]=t.indices[e]+t.offset;return n}let t=e[0].directions,n=t.length,r=0;for(let t of e)r+=t.indices.length;let i=new Uint32Array(r),a=new se(t);for(let t=0;t<e.length;t++){let r=e[t];if(r.indices.length>0){let e=r.indices[0],i=[];for(let e=0;e<n;e++)i.push(r.columns[e][0]);a.push({chunkIndex:t,positionInChunk:0,values:i,globalIndex:e+r.offset})}}let o=0;for(;a.size()>0;){let t=a.pop();i[o++]=t.globalIndex;let r=e[t.chunkIndex],s=t.positionInChunk+1;if(s<r.indices.length){let e=r.indices[s],i=[];for(let e=0;e<n;e++)i.push(r.columns[e][s]);a.push({chunkIndex:t.chunkIndex,positionInChunk:s,values:i,globalIndex:e+r.offset})}}return i}function ce(e,t){if(e.length<=1)return new Uint32Array;let n=[],r=0;for(let t=0;t<e.length-1;t++){let i=e[t],a=e[t+1];if(i.indices.length===0||a.indices.length===0){r+=i.indices.length;continue}let o=i.indices.length-1,s=i.values[o],c=a.values[0];if(s===c){let e=o;for(;e>0&&i.values[e-1]===s;)e--;let t=0;for(;t<a.indices.length-1&&a.values[t+1]===c;)t++;let l=r+e,u=r+i.indices.length+t+1;n.push(l,u)}r+=i.indices.length}return new Uint32Array(n)}var O=class{pool;parallelThreshold;minChunkSize;isTerminated=!1;constructor(e={}){this.pool=new w(`
2436
3
  // Inline sort worker code
2437
4
  function getFieldValue(row, field) {
2438
5
  const parts = field.split(".");
@@ -2752,1655 +319,7 @@ self.onmessage = function(e) {
2752
319
  }
2753
320
  }
2754
321
  };
2755
- `;
2756
- if (typeof self !== "undefined" && typeof self.onmessage !== "undefined") self.onmessage = (e) => {
2757
- const { type, id, data, sortModel } = e.data;
2758
- if (type === "sort") try {
2759
- const sorted = sortData(data, sortModel);
2760
- self.postMessage({
2761
- type: "sorted",
2762
- id,
2763
- data: sorted
2764
- });
2765
- } catch (error) {
2766
- self.postMessage({
2767
- type: "error",
2768
- id,
2769
- error: String(error)
2770
- });
2771
- }
2772
- };
2773
-
2774
- //#endregion
2775
- //#region src/sorting/k-way-merge.ts
2776
- /**
2777
- * Binary min-heap for k-way merge.
2778
- * Time complexity: O(log k) for push/pop where k is heap size.
2779
- */
2780
- var MinHeap = class {
2781
- heap = [];
2782
- multiplier;
2783
- constructor(direction) {
2784
- this.multiplier = direction === "asc" ? 1 : -1;
2785
- }
2786
- push(entry) {
2787
- this.heap.push(entry);
2788
- this.bubbleUp(this.heap.length - 1);
2789
- }
2790
- pop() {
2791
- if (this.heap.length === 0) return void 0;
2792
- const result = this.heap[0];
2793
- const last = this.heap.pop();
2794
- if (this.heap.length > 0 && last) {
2795
- this.heap[0] = last;
2796
- this.bubbleDown(0);
2797
- }
2798
- return result;
2799
- }
2800
- size() {
2801
- return this.heap.length;
2802
- }
2803
- bubbleUp(index) {
2804
- while (index > 0) {
2805
- const parentIndex = Math.floor((index - 1) / 2);
2806
- if (this.compare(this.heap[index], this.heap[parentIndex]) >= 0) break;
2807
- this.swap(index, parentIndex);
2808
- index = parentIndex;
2809
- }
2810
- }
2811
- bubbleDown(index) {
2812
- const length = this.heap.length;
2813
- while (true) {
2814
- const leftChild = 2 * index + 1;
2815
- const rightChild = 2 * index + 2;
2816
- let smallest = index;
2817
- if (leftChild < length && this.compare(this.heap[leftChild], this.heap[smallest]) < 0) smallest = leftChild;
2818
- if (rightChild < length && this.compare(this.heap[rightChild], this.heap[smallest]) < 0) smallest = rightChild;
2819
- if (smallest === index) break;
2820
- this.swap(index, smallest);
2821
- index = smallest;
2822
- }
2823
- }
2824
- compare(a, b) {
2825
- return (a.value - b.value) * this.multiplier;
2826
- }
2827
- swap(i, j) {
2828
- const temp = this.heap[i];
2829
- this.heap[i] = this.heap[j];
2830
- this.heap[j] = temp;
2831
- }
2832
- };
2833
- /**
2834
- * Binary heap for multi-column k-way merge.
2835
- */
2836
- var MultiColumnMinHeap = class {
2837
- heap = [];
2838
- directions;
2839
- constructor(directions) {
2840
- this.directions = directions;
2841
- }
2842
- push(entry) {
2843
- this.heap.push(entry);
2844
- this.bubbleUp(this.heap.length - 1);
2845
- }
2846
- pop() {
2847
- if (this.heap.length === 0) return void 0;
2848
- const result = this.heap[0];
2849
- const last = this.heap.pop();
2850
- if (this.heap.length > 0 && last) {
2851
- this.heap[0] = last;
2852
- this.bubbleDown(0);
2853
- }
2854
- return result;
2855
- }
2856
- size() {
2857
- return this.heap.length;
2858
- }
2859
- bubbleUp(index) {
2860
- while (index > 0) {
2861
- const parentIndex = Math.floor((index - 1) / 2);
2862
- if (this.compare(this.heap[index], this.heap[parentIndex]) >= 0) break;
2863
- this.swap(index, parentIndex);
2864
- index = parentIndex;
2865
- }
2866
- }
2867
- bubbleDown(index) {
2868
- const length = this.heap.length;
2869
- while (true) {
2870
- const leftChild = 2 * index + 1;
2871
- const rightChild = 2 * index + 2;
2872
- let smallest = index;
2873
- if (leftChild < length && this.compare(this.heap[leftChild], this.heap[smallest]) < 0) smallest = leftChild;
2874
- if (rightChild < length && this.compare(this.heap[rightChild], this.heap[smallest]) < 0) smallest = rightChild;
2875
- if (smallest === index) break;
2876
- this.swap(index, smallest);
2877
- index = smallest;
2878
- }
2879
- }
2880
- compare(a, b) {
2881
- for (let i = 0; i < this.directions.length; i++) {
2882
- const diff = (a.values[i] - b.values[i]) * this.directions[i];
2883
- if (diff !== 0) return diff;
2884
- }
2885
- return 0;
2886
- }
2887
- swap(i, j) {
2888
- const temp = this.heap[i];
2889
- this.heap[i] = this.heap[j];
2890
- this.heap[j] = temp;
2891
- }
2892
- };
2893
- /**
2894
- * Merge multiple sorted chunks into a single sorted result.
2895
- * Uses a min-heap for O(n log k) time complexity.
2896
- *
2897
- * @param chunks - Array of sorted chunks to merge
2898
- * @param direction - Sort direction ('asc' or 'desc')
2899
- * @returns Uint32Array of globally sorted indices
2900
- */
2901
- function kWayMerge(chunks, direction) {
2902
- if (chunks.length === 0) return new Uint32Array(0);
2903
- if (chunks.length === 1) {
2904
- const chunk = chunks[0];
2905
- const result$1 = new Uint32Array(chunk.indices.length);
2906
- for (let i = 0; i < chunk.indices.length; i++) result$1[i] = chunk.indices[i] + chunk.offset;
2907
- return result$1;
2908
- }
2909
- let totalLength = 0;
2910
- for (const chunk of chunks) totalLength += chunk.indices.length;
2911
- const result = new Uint32Array(totalLength);
2912
- const heap = new MinHeap(direction);
2913
- for (let i = 0; i < chunks.length; i++) {
2914
- const chunk = chunks[i];
2915
- if (chunk.indices.length > 0) {
2916
- const localIndex = chunk.indices[0];
2917
- heap.push({
2918
- chunkIndex: i,
2919
- positionInChunk: 0,
2920
- value: chunk.values[0],
2921
- globalIndex: localIndex + chunk.offset
2922
- });
2923
- }
2924
- }
2925
- let resultIndex = 0;
2926
- while (heap.size() > 0) {
2927
- const entry = heap.pop();
2928
- result[resultIndex++] = entry.globalIndex;
2929
- const chunk = chunks[entry.chunkIndex];
2930
- const nextPosition = entry.positionInChunk + 1;
2931
- if (nextPosition < chunk.indices.length) {
2932
- const localIndex = chunk.indices[nextPosition];
2933
- heap.push({
2934
- chunkIndex: entry.chunkIndex,
2935
- positionInChunk: nextPosition,
2936
- value: chunk.values[nextPosition],
2937
- globalIndex: localIndex + chunk.offset
2938
- });
2939
- }
2940
- }
2941
- return result;
2942
- }
2943
- /**
2944
- * Merge multiple sorted chunks for multi-column sorting.
2945
- *
2946
- * @param chunks - Array of multi-column sorted chunks
2947
- * @returns Uint32Array of globally sorted indices
2948
- */
2949
- function kWayMergeMultiColumn(chunks) {
2950
- if (chunks.length === 0) return new Uint32Array(0);
2951
- if (chunks.length === 1) {
2952
- const chunk = chunks[0];
2953
- const result$1 = new Uint32Array(chunk.indices.length);
2954
- for (let i = 0; i < chunk.indices.length; i++) result$1[i] = chunk.indices[i] + chunk.offset;
2955
- return result$1;
2956
- }
2957
- const directions = chunks[0].directions;
2958
- const numColumns = directions.length;
2959
- let totalLength = 0;
2960
- for (const chunk of chunks) totalLength += chunk.indices.length;
2961
- const result = new Uint32Array(totalLength);
2962
- const heap = new MultiColumnMinHeap(directions);
2963
- for (let i = 0; i < chunks.length; i++) {
2964
- const chunk = chunks[i];
2965
- if (chunk.indices.length > 0) {
2966
- const localIndex = chunk.indices[0];
2967
- const values = [];
2968
- for (let c = 0; c < numColumns; c++) values.push(chunk.columns[c][0]);
2969
- heap.push({
2970
- chunkIndex: i,
2971
- positionInChunk: 0,
2972
- values,
2973
- globalIndex: localIndex + chunk.offset
2974
- });
2975
- }
2976
- }
2977
- let resultIndex = 0;
2978
- while (heap.size() > 0) {
2979
- const entry = heap.pop();
2980
- result[resultIndex++] = entry.globalIndex;
2981
- const chunk = chunks[entry.chunkIndex];
2982
- const nextPosition = entry.positionInChunk + 1;
2983
- if (nextPosition < chunk.indices.length) {
2984
- const localIndex = chunk.indices[nextPosition];
2985
- const values = [];
2986
- for (let c = 0; c < numColumns; c++) values.push(chunk.columns[c][nextPosition]);
2987
- heap.push({
2988
- chunkIndex: entry.chunkIndex,
2989
- positionInChunk: nextPosition,
2990
- values,
2991
- globalIndex: localIndex + chunk.offset
2992
- });
2993
- }
2994
- }
2995
- return result;
2996
- }
2997
- /**
2998
- * Detect collision runs at chunk boundaries after merge.
2999
- * This is used for string sorting where hashes may collide across chunks.
3000
- *
3001
- * @param chunks - Original sorted chunks with their hash values
3002
- * @param _direction - Sort direction
3003
- * @returns Array of boundary collision positions [start1, end1, start2, end2, ...]
3004
- */
3005
- function detectBoundaryCollisions(chunks, _direction) {
3006
- if (chunks.length <= 1) return new Uint32Array(0);
3007
- const collisions = [];
3008
- let globalPosition = 0;
3009
- for (let i = 0; i < chunks.length - 1; i++) {
3010
- const currentChunk = chunks[i];
3011
- const nextChunk = chunks[i + 1];
3012
- if (currentChunk.indices.length === 0 || nextChunk.indices.length === 0) {
3013
- globalPosition += currentChunk.indices.length;
3014
- continue;
3015
- }
3016
- const lastPos = currentChunk.indices.length - 1;
3017
- const lastValue = currentChunk.values[lastPos];
3018
- const firstValue = nextChunk.values[0];
3019
- if (lastValue === firstValue) {
3020
- let startInCurrent = lastPos;
3021
- while (startInCurrent > 0 && currentChunk.values[startInCurrent - 1] === lastValue) startInCurrent--;
3022
- let endInNext = 0;
3023
- while (endInNext < nextChunk.indices.length - 1 && nextChunk.values[endInNext + 1] === firstValue) endInNext++;
3024
- const collisionStart = globalPosition + startInCurrent;
3025
- const collisionEnd = globalPosition + currentChunk.indices.length + endInNext + 1;
3026
- collisions.push(collisionStart, collisionEnd);
3027
- }
3028
- globalPosition += currentChunk.indices.length;
3029
- }
3030
- return new Uint32Array(collisions);
3031
- }
3032
-
3033
- //#endregion
3034
- //#region src/sorting/parallel-sort-manager.ts
3035
- /** Threshold for using parallel sorting (rows). Below this, use single worker. */
3036
- const PARALLEL_THRESHOLD = 4e5;
3037
- /** Minimum chunk size to avoid overhead for small chunks */
3038
- const MIN_CHUNK_SIZE = 5e4;
3039
- /**
3040
- * Manages parallel sorting operations using a worker pool.
3041
- * Automatically decides between single-worker and parallel sorting based on data size.
3042
- */
3043
- var ParallelSortManager = class {
3044
- pool;
3045
- parallelThreshold;
3046
- minChunkSize;
3047
- isTerminated = false;
3048
- constructor(options = {}) {
3049
- this.pool = new WorkerPool(SORT_WORKER_CODE, { maxWorkers: options.maxWorkers ?? (typeof navigator !== "undefined" ? navigator.hardwareConcurrency : 4) ?? 4 });
3050
- this.parallelThreshold = options.parallelThreshold ?? PARALLEL_THRESHOLD;
3051
- this.minChunkSize = options.minChunkSize ?? MIN_CHUNK_SIZE;
3052
- }
3053
- /**
3054
- * Check if the manager is available for use.
3055
- */
3056
- isAvailable() {
3057
- return !this.isTerminated && this.pool.isAvailable();
3058
- }
3059
- /**
3060
- * Terminate all workers and clean up resources.
3061
- */
3062
- terminate() {
3063
- this.pool.terminate();
3064
- this.isTerminated = true;
3065
- }
3066
- /**
3067
- * Sort indices using values array.
3068
- * Automatically uses parallel sorting for large datasets.
3069
- */
3070
- async sortIndices(values, direction) {
3071
- if (this.isTerminated) throw new Error("ParallelSortManager has been terminated");
3072
- if (values.length < this.parallelThreshold) return this.sortIndicesSingle(values, direction);
3073
- return this.sortIndicesParallel(values, direction);
3074
- }
3075
- /**
3076
- * Sort string hashes with collision detection.
3077
- * Automatically uses parallel sorting for large datasets.
3078
- */
3079
- async sortStringHashes(hashChunks, direction, originalStrings) {
3080
- if (this.isTerminated) throw new Error("ParallelSortManager has been terminated");
3081
- if ((hashChunks[0]?.length ?? 0) < this.parallelThreshold) return this.sortStringHashesSingle(hashChunks, direction, originalStrings);
3082
- return this.sortStringHashesParallel(hashChunks, direction, originalStrings);
3083
- }
3084
- /**
3085
- * Multi-column sort.
3086
- * Automatically uses parallel sorting for large datasets.
3087
- */
3088
- async sortMultiColumn(columns, directions) {
3089
- if (this.isTerminated) throw new Error("ParallelSortManager has been terminated");
3090
- if ((columns[0]?.length ?? 0) < this.parallelThreshold) return this.sortMultiColumnSingle(columns, directions);
3091
- return this.sortMultiColumnParallel(columns, directions);
3092
- }
3093
- async sortIndicesSingle(values, direction) {
3094
- const valuesArray = new Float64Array(values);
3095
- const request = {
3096
- type: "sortIndices",
3097
- id: 0,
3098
- values: valuesArray,
3099
- direction
3100
- };
3101
- return (await this.pool.execute(request, [valuesArray.buffer])).indices;
3102
- }
3103
- async sortStringHashesSingle(hashChunks, direction, originalStrings) {
3104
- const request = {
3105
- type: "sortStringHashes",
3106
- id: 0,
3107
- hashChunks,
3108
- direction
3109
- };
3110
- const transferables = hashChunks.map((chunk) => chunk.buffer);
3111
- const response = await this.pool.execute(request, transferables);
3112
- if (response.collisionRuns.length > 0) this.resolveCollisions(response.indices, response.collisionRuns, originalStrings, direction);
3113
- return response.indices;
3114
- }
3115
- async sortMultiColumnSingle(columns, directions) {
3116
- const columnArrays = columns.map((col) => new Float64Array(col));
3117
- const directionArray = new Int8Array(directions.map((d) => d === "asc" ? 1 : -1));
3118
- const request = {
3119
- type: "sortMultiColumn",
3120
- id: 0,
3121
- columns: columnArrays,
3122
- directions: directionArray
3123
- };
3124
- const transferables = [...columnArrays.map((arr) => arr.buffer), directionArray.buffer];
3125
- return (await this.pool.execute(request, transferables)).indices;
3126
- }
3127
- async sortIndicesParallel(values, direction) {
3128
- const tasks = this.splitIntoChunks(values).map((chunk) => {
3129
- const valuesArray = new Float64Array(chunk.data);
3130
- return {
3131
- request: {
3132
- type: "sortChunk",
3133
- id: 0,
3134
- values: valuesArray,
3135
- direction,
3136
- chunkOffset: chunk.offset
3137
- },
3138
- transferables: [valuesArray.buffer]
3139
- };
3140
- });
3141
- const responses = await this.pool.executeParallel(tasks);
3142
- responses.sort((a, b) => a.chunkOffset - b.chunkOffset);
3143
- return kWayMerge(responses.map((response) => ({
3144
- indices: response.indices,
3145
- values: response.sortedValues,
3146
- offset: response.chunkOffset
3147
- })), direction);
3148
- }
3149
- async sortStringHashesParallel(hashChunks, direction, originalStrings) {
3150
- const length = hashChunks[0].length;
3151
- const tasks = this.calculateChunkBoundaries(length).map((boundary) => {
3152
- const copiedChunks = hashChunks.map((hc) => new Float64Array(hc.buffer, boundary.offset * 8, boundary.length)).map((hc) => {
3153
- const copy = new Float64Array(hc.length);
3154
- copy.set(hc);
3155
- return copy;
3156
- });
3157
- return {
3158
- request: {
3159
- type: "sortStringChunk",
3160
- id: 0,
3161
- hashChunks: copiedChunks,
3162
- direction,
3163
- chunkOffset: boundary.offset
3164
- },
3165
- transferables: copiedChunks.map((c) => c.buffer)
3166
- };
3167
- });
3168
- const responses = await this.pool.executeParallel(tasks);
3169
- responses.sort((a, b) => a.chunkOffset - b.chunkOffset);
3170
- const sortedChunks = responses.map((response) => ({
3171
- indices: response.indices,
3172
- values: response.sortedHashes,
3173
- offset: response.chunkOffset
3174
- }));
3175
- const allCollisionRuns = [];
3176
- for (const response of responses) for (let i = 0; i < response.collisionRuns.length; i += 2) allCollisionRuns.push(response.collisionRuns[i] + response.chunkOffset, response.collisionRuns[i + 1] + response.chunkOffset);
3177
- const mergedIndices = kWayMerge(sortedChunks, direction);
3178
- const boundaryCollisions = this.detectBoundaryCollisionsForStrings(sortedChunks, direction);
3179
- const combinedCollisions = new Uint32Array([...allCollisionRuns, ...boundaryCollisions]);
3180
- if (combinedCollisions.length > 0) this.resolveCollisions(mergedIndices, combinedCollisions, originalStrings, direction);
3181
- return mergedIndices;
3182
- }
3183
- async sortMultiColumnParallel(columns, directions) {
3184
- const length = columns[0].length;
3185
- const chunkBoundaries = this.calculateChunkBoundaries(length);
3186
- const directionArray = new Int8Array(directions.map((d) => d === "asc" ? 1 : -1));
3187
- const tasks = chunkBoundaries.map((boundary) => {
3188
- const chunkColumns = columns.map((col) => {
3189
- const chunk = new Float64Array(boundary.length);
3190
- for (let i = 0; i < boundary.length; i++) chunk[i] = col[boundary.offset + i];
3191
- return chunk;
3192
- });
3193
- const dirCopy = new Int8Array(directionArray);
3194
- return {
3195
- request: {
3196
- type: "sortMultiColumnChunk",
3197
- id: 0,
3198
- columns: chunkColumns,
3199
- directions: dirCopy,
3200
- chunkOffset: boundary.offset
3201
- },
3202
- transferables: [...chunkColumns.map((c) => c.buffer), dirCopy.buffer]
3203
- };
3204
- });
3205
- const responses = await this.pool.executeParallel(tasks);
3206
- responses.sort((a, b) => a.chunkOffset - b.chunkOffset);
3207
- return kWayMergeMultiColumn(responses.map((response) => ({
3208
- indices: response.indices,
3209
- columns: response.sortedColumns,
3210
- directions: directionArray,
3211
- offset: response.chunkOffset
3212
- })));
3213
- }
3214
- /**
3215
- * Split data into chunks for parallel processing.
3216
- */
3217
- splitIntoChunks(data) {
3218
- const length = data.length;
3219
- const maxWorkers = this.pool.getMaxWorkers();
3220
- const chunkSize = Math.max(this.minChunkSize, Math.ceil(length / maxWorkers));
3221
- const chunks = [];
3222
- for (let offset = 0; offset < length; offset += chunkSize) {
3223
- const end = Math.min(offset + chunkSize, length);
3224
- chunks.push({
3225
- data: data.slice(offset, end),
3226
- offset
3227
- });
3228
- }
3229
- return chunks;
3230
- }
3231
- /**
3232
- * Calculate chunk boundaries without copying data.
3233
- */
3234
- calculateChunkBoundaries(length) {
3235
- const maxWorkers = this.pool.getMaxWorkers();
3236
- const chunkSize = Math.max(this.minChunkSize, Math.ceil(length / maxWorkers));
3237
- const boundaries = [];
3238
- for (let offset = 0; offset < length; offset += chunkSize) boundaries.push({
3239
- offset,
3240
- length: Math.min(chunkSize, length - offset)
3241
- });
3242
- return boundaries;
3243
- }
3244
- /**
3245
- * Detect collisions at chunk boundaries for string sorting.
3246
- */
3247
- detectBoundaryCollisionsForStrings(chunks, _direction) {
3248
- if (chunks.length <= 1) return [];
3249
- const collisions = [];
3250
- let globalPosition = 0;
3251
- for (let i = 0; i < chunks.length - 1; i++) {
3252
- const current = chunks[i];
3253
- const next = chunks[i + 1];
3254
- if (current.indices.length === 0 || next.indices.length === 0) {
3255
- globalPosition += current.indices.length;
3256
- continue;
3257
- }
3258
- const lastValue = current.values[current.indices.length - 1];
3259
- const firstValue = next.values[0];
3260
- if (lastValue === firstValue) {
3261
- let startInCurrent = current.indices.length - 1;
3262
- while (startInCurrent > 0 && current.values[startInCurrent - 1] === lastValue) startInCurrent--;
3263
- let endInNext = 0;
3264
- while (endInNext < next.indices.length - 1 && next.values[endInNext + 1] === firstValue) endInNext++;
3265
- collisions.push(globalPosition + startInCurrent, globalPosition + current.indices.length + endInNext + 1);
3266
- }
3267
- globalPosition += current.indices.length;
3268
- }
3269
- return collisions;
3270
- }
3271
- /**
3272
- * Resolve hash collisions using localeCompare.
3273
- */
3274
- resolveCollisions(indices, collisionRuns, originalStrings, direction) {
3275
- const mult = direction === "asc" ? 1 : -1;
3276
- for (let r = 0; r < collisionRuns.length; r += 2) {
3277
- const start = collisionRuns[r];
3278
- const end = collisionRuns[r + 1];
3279
- if (end <= start || end > indices.length) continue;
3280
- const slice = Array.from(indices.slice(start, end));
3281
- const firstString = originalStrings[slice[0]];
3282
- let allIdentical = true;
3283
- for (let i = 1; i < slice.length; i++) if (originalStrings[slice[i]] !== firstString) {
3284
- allIdentical = false;
3285
- break;
3286
- }
3287
- if (allIdentical) continue;
3288
- slice.sort((a, b) => mult * originalStrings[a].localeCompare(originalStrings[b]));
3289
- for (let i = 0; i < slice.length; i++) indices[start + i] = slice[i];
3290
- }
3291
- }
3292
- };
3293
-
3294
- //#endregion
3295
- //#region src/data-source/sorting.ts
3296
- /** Number of 10-character chunks for string hashing (30 chars total) */
3297
- const HASH_CHUNK_COUNT = 3;
3298
- /**
3299
- * Convert a string to a sortable number using first 10 characters.
3300
- * Uses base 36 (alphanumeric) to fit more characters within float64 safe precision.
3301
- * (36^10 ≈ 3.6×10¹⁵, within MAX_SAFE_INTEGER ~9×10¹⁵)
3302
- */
3303
- function stringToSortableNumber$1(str) {
3304
- const s = str.toLowerCase();
3305
- const len = Math.min(s.length, 10);
3306
- let hash = 0;
3307
- for (let i = 0; i < len; i++) {
3308
- const code = s.charCodeAt(i);
3309
- let mapped;
3310
- if (code >= 97 && code <= 122) mapped = code - 97;
3311
- else if (code >= 48 && code <= 57) mapped = code - 48 + 26;
3312
- else mapped = 0;
3313
- hash = hash * 36 + mapped;
3314
- }
3315
- for (let i = len; i < 10; i++) hash = hash * 36;
3316
- return hash;
3317
- }
3318
- /**
3319
- * Convert a string to multiple sortable hash values (one per 10-char chunk).
3320
- * This allows correct sorting of strings longer than 10 characters.
3321
- * Returns HASH_CHUNK_COUNT hashes, each covering 10 characters.
3322
- */
3323
- function stringToSortableHashes(str) {
3324
- const s = str.toLowerCase();
3325
- const hashes = [];
3326
- for (let chunk = 0; chunk < HASH_CHUNK_COUNT; chunk++) {
3327
- const start = chunk * 10;
3328
- let hash = 0;
3329
- for (let i = 0; i < 10; i++) {
3330
- const charIndex = start + i;
3331
- const code = charIndex < s.length ? s.charCodeAt(charIndex) : 0;
3332
- let mapped;
3333
- if (code >= 97 && code <= 122) mapped = code - 97;
3334
- else if (code >= 48 && code <= 57) mapped = code - 48 + 26;
3335
- else mapped = 0;
3336
- hash = hash * 36 + mapped;
3337
- }
3338
- hashes.push(hash);
3339
- }
3340
- return hashes;
3341
- }
3342
- /**
3343
- * Convert any cell value to a sortable number.
3344
- * Strings are converted using a lexicographic hash of the first 8 characters.
3345
- */
3346
- function toSortableNumber(val) {
3347
- if (val == null) return Number.MAX_VALUE;
3348
- if (Array.isArray(val)) {
3349
- if (val.length === 0) return Number.MAX_VALUE;
3350
- return stringToSortableNumber$1(val.join(", "));
3351
- }
3352
- if (typeof val === "number") return val;
3353
- if (val instanceof Date) return val.getTime();
3354
- if (typeof val === "string") return stringToSortableNumber$1(val);
3355
- const num = Number(val);
3356
- return isNaN(num) ? 0 : num;
3357
- }
3358
- /**
3359
- * Compare two cell values for sorting
3360
- */
3361
- function compareValues$1(a, b) {
3362
- const aIsEmpty = a == null || Array.isArray(a) && a.length === 0;
3363
- const bIsEmpty = b == null || Array.isArray(b) && b.length === 0;
3364
- if (aIsEmpty && bIsEmpty) return 0;
3365
- if (aIsEmpty) return 1;
3366
- if (bIsEmpty) return -1;
3367
- if (Array.isArray(a) || Array.isArray(b)) {
3368
- const strA = Array.isArray(a) ? a.join(", ") : String(a ?? "");
3369
- const strB = Array.isArray(b) ? b.join(", ") : String(b ?? "");
3370
- return strA.localeCompare(strB);
3371
- }
3372
- const aNum = Number(a);
3373
- const bNum = Number(b);
3374
- if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum;
3375
- if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
3376
- return String(a).localeCompare(String(b));
3377
- }
3378
- /**
3379
- * Apply sort model to data array
3380
- */
3381
- function applySort(data, sortModel, getFieldValue$3) {
3382
- return [...data].sort((a, b) => {
3383
- for (const { colId, direction } of sortModel) {
3384
- const comparison = compareValues$1(getFieldValue$3(a, colId), getFieldValue$3(b, colId));
3385
- if (comparison !== 0) return direction === "asc" ? comparison : -comparison;
3386
- }
3387
- return 0;
3388
- });
3389
- }
3390
-
3391
- //#endregion
3392
- //#region src/filtering/index.ts
3393
- /**
3394
- * Check if two dates are on the same day.
3395
- */
3396
- function isSameDay(date1, date2) {
3397
- return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate();
3398
- }
3399
- /**
3400
- * Check if a cell value is considered blank.
3401
- */
3402
- function isBlankValue(cellValue) {
3403
- return cellValue == null || cellValue === "" || Array.isArray(cellValue) && cellValue.length === 0;
3404
- }
3405
- /**
3406
- * Evaluate a text filter condition against a cell value.
3407
- */
3408
- function evaluateTextCondition(cellValue, condition) {
3409
- const isBlank = isBlankValue(cellValue);
3410
- if (condition.selectedValues && condition.selectedValues.size > 0) {
3411
- const includesBlank = condition.includeBlank === true && isBlank;
3412
- if (Array.isArray(cellValue)) {
3413
- const arrayStr = [...cellValue].sort((a, b) => String(a).localeCompare(String(b), void 0, {
3414
- numeric: true,
3415
- sensitivity: "base"
3416
- })).join(", ");
3417
- return condition.selectedValues.has(arrayStr) || includesBlank;
3418
- }
3419
- const cellStr = String(cellValue ?? "");
3420
- return condition.selectedValues.has(cellStr) || includesBlank;
3421
- }
3422
- const strValue = String(cellValue ?? "").toLowerCase();
3423
- const filterValue = String(condition.value ?? "").toLowerCase();
3424
- switch (condition.operator) {
3425
- case "contains": return strValue.includes(filterValue);
3426
- case "notContains": return !strValue.includes(filterValue);
3427
- case "equals": return strValue === filterValue;
3428
- case "notEquals": return strValue !== filterValue;
3429
- case "startsWith": return strValue.startsWith(filterValue);
3430
- case "endsWith": return strValue.endsWith(filterValue);
3431
- case "blank": return isBlank;
3432
- case "notBlank": return !isBlank;
3433
- default: return true;
3434
- }
3435
- }
3436
- /**
3437
- * Evaluate a number filter condition against a cell value.
3438
- */
3439
- function evaluateNumberCondition(cellValue, condition) {
3440
- const isBlank = cellValue == null || cellValue === "";
3441
- if (condition.operator === "blank") return isBlank;
3442
- if (condition.operator === "notBlank") return !isBlank;
3443
- if (isBlank) return false;
3444
- const numValue = typeof cellValue === "number" ? cellValue : Number(cellValue);
3445
- if (isNaN(numValue)) return false;
3446
- const filterValue = condition.value ?? 0;
3447
- const filterValueTo = condition.valueTo ?? 0;
3448
- switch (condition.operator) {
3449
- case "=": return numValue === filterValue;
3450
- case "!=": return numValue !== filterValue;
3451
- case ">": return numValue > filterValue;
3452
- case "<": return numValue < filterValue;
3453
- case ">=": return numValue >= filterValue;
3454
- case "<=": return numValue <= filterValue;
3455
- case "between": return numValue >= filterValue && numValue <= filterValueTo;
3456
- default: return true;
3457
- }
3458
- }
3459
- /**
3460
- * Evaluate a date filter condition against a cell value.
3461
- */
3462
- function evaluateDateCondition(cellValue, condition) {
3463
- const isBlank = cellValue == null || cellValue === "";
3464
- if (condition.operator === "blank") return isBlank;
3465
- if (condition.operator === "notBlank") return !isBlank;
3466
- if (isBlank) return false;
3467
- const dateValue = cellValue instanceof Date ? cellValue : new Date(String(cellValue));
3468
- if (isNaN(dateValue.getTime())) return false;
3469
- const filterDate = condition.value instanceof Date ? condition.value : new Date(String(condition.value ?? ""));
3470
- const filterDateTo = condition.valueTo instanceof Date ? condition.valueTo : new Date(String(condition.valueTo ?? ""));
3471
- const dateTime = dateValue.getTime();
3472
- const filterTime = filterDate.getTime();
3473
- const filterTimeTo = filterDateTo.getTime();
3474
- switch (condition.operator) {
3475
- case "=": return isSameDay(dateValue, filterDate);
3476
- case "!=": return !isSameDay(dateValue, filterDate);
3477
- case ">": return dateTime > filterTime;
3478
- case "<": return dateTime < filterTime;
3479
- case "between": return dateTime >= filterTime && dateTime <= filterTimeTo;
3480
- default: return true;
3481
- }
3482
- }
3483
- /**
3484
- * Evaluate a single filter condition against a cell value.
3485
- */
3486
- function evaluateCondition(cellValue, condition) {
3487
- switch (condition.type) {
3488
- case "text": return evaluateTextCondition(cellValue, condition);
3489
- case "number": return evaluateNumberCondition(cellValue, condition);
3490
- case "date": return evaluateDateCondition(cellValue, condition);
3491
- default: return true;
3492
- }
3493
- }
3494
- /**
3495
- * Evaluate a column filter model against a cell value.
3496
- * Uses left-to-right evaluation with per-condition operators.
3497
- */
3498
- function evaluateColumnFilter(cellValue, filter) {
3499
- if (!filter.conditions || filter.conditions.length === 0) return true;
3500
- const firstCondition = filter.conditions[0];
3501
- if (!firstCondition) return true;
3502
- let result = evaluateCondition(cellValue, firstCondition);
3503
- for (let i = 1; i < filter.conditions.length; i++) {
3504
- const prevCondition = filter.conditions[i - 1];
3505
- const currentCondition = filter.conditions[i];
3506
- const operator = prevCondition.nextOperator ?? filter.combination;
3507
- const conditionResult = evaluateCondition(cellValue, currentCondition);
3508
- if (operator === "and") result = result && conditionResult;
3509
- else result = result || conditionResult;
3510
- }
3511
- return result;
3512
- }
3513
- /**
3514
- * Check if a row passes all filters in a filter model.
3515
- */
3516
- function rowPassesFilter(row, filterModel, getFieldValue$3) {
3517
- const filterEntries = Object.entries(filterModel).filter(([, value]) => value != null);
3518
- if (filterEntries.length === 0) return true;
3519
- for (const [field, filter] of filterEntries) if (!evaluateColumnFilter(getFieldValue$3(row, field), filter)) return false;
3520
- return true;
3521
- }
3522
- /**
3523
- * Apply filters to a data array.
3524
- * Supports both new ColumnFilterModel format and legacy string format.
3525
- */
3526
- function applyFilters(data, filterModel, getFieldValue$3) {
3527
- const filterEntries = Object.entries(filterModel).filter(([, filter]) => {
3528
- if (typeof filter === "string") return filter.trim() !== "";
3529
- return filter.conditions && filter.conditions.length > 0;
3530
- });
3531
- if (filterEntries.length === 0) return data;
3532
- return data.filter((row) => {
3533
- for (const [field, filter] of filterEntries) {
3534
- const cellValue = getFieldValue$3(row, field);
3535
- if (typeof filter === "string") {
3536
- if (!String(cellValue ?? "").toLowerCase().includes(filter.toLowerCase())) return false;
3537
- continue;
3538
- }
3539
- if (!evaluateColumnFilter(cellValue, filter)) return false;
3540
- }
3541
- return true;
3542
- });
3543
- }
3544
-
3545
- //#endregion
3546
- //#region src/data-source/client-data-source.ts
3547
- /** Threshold for using Web Worker (rows). Below this, sync sort is used. */
3548
- const WORKER_THRESHOLD = 2e5;
3549
- /**
3550
- * Default field value accessor supporting dot-notation for nested properties
3551
- */
3552
- function defaultGetFieldValue(row, field) {
3553
- const parts = field.split(".");
3554
- let value = row;
3555
- for (const part of parts) {
3556
- if (value == null || typeof value !== "object") return null;
3557
- value = value[part];
3558
- }
3559
- return value ?? null;
3560
- }
3561
- /**
3562
- * Creates a client-side data source that holds all data in memory.
3563
- * Sorting and filtering are performed client-side.
3564
- * For large datasets, sorting is automatically offloaded to a Web Worker.
3565
- */
3566
- function createClientDataSource(data, options = {}) {
3567
- const { getFieldValue: getFieldValue$3 = defaultGetFieldValue, useWorker = true, parallelSort } = options;
3568
- let internalData = data;
3569
- let isDestroyed = false;
3570
- const sortManager = useWorker ? new ParallelSortManager(parallelSort === false ? { maxWorkers: 1 } : parallelSort) : null;
3571
- return {
3572
- async fetch(request) {
3573
- let processedData = internalData ? [...internalData] : [];
3574
- if (request.filter && Object.keys(request.filter).length > 0) processedData = applyFilters(processedData, request.filter, getFieldValue$3);
3575
- if (request.sort && request.sort.length > 0) if (sortManager && sortManager.isAvailable() && processedData.length >= WORKER_THRESHOLD) {
3576
- let sortedIndices;
3577
- if (request.sort.length === 1) {
3578
- const { colId, direction } = request.sort[0];
3579
- let isStringColumn = false;
3580
- for (const row of processedData) {
3581
- const val = getFieldValue$3(row, colId);
3582
- if (val != null) {
3583
- isStringColumn = typeof val === "string" || Array.isArray(val);
3584
- break;
3585
- }
3586
- }
3587
- if (isStringColumn) {
3588
- const originalStrings = [];
3589
- const hashChunks = Array.from({ length: HASH_CHUNK_COUNT }, () => []);
3590
- for (const row of processedData) {
3591
- const val = getFieldValue$3(row, colId);
3592
- const str = val == null ? "" : Array.isArray(val) ? val.join(", ") : String(val);
3593
- originalStrings.push(str);
3594
- const hashes = stringToSortableHashes(str);
3595
- for (let c = 0; c < HASH_CHUNK_COUNT; c++) hashChunks[c].push(hashes[c]);
3596
- }
3597
- const hashChunkArrays = hashChunks.map((chunk) => new Float64Array(chunk));
3598
- sortedIndices = await sortManager.sortStringHashes(hashChunkArrays, direction, originalStrings);
3599
- } else {
3600
- const values = processedData.map((row) => {
3601
- return toSortableNumber(getFieldValue$3(row, colId));
3602
- });
3603
- sortedIndices = await sortManager.sortIndices(values, direction);
3604
- }
3605
- } else {
3606
- const columnValues = [];
3607
- const directions = [];
3608
- for (const { colId, direction } of request.sort) {
3609
- const values = processedData.map((row) => {
3610
- return toSortableNumber(getFieldValue$3(row, colId));
3611
- });
3612
- columnValues.push(values);
3613
- directions.push(direction);
3614
- }
3615
- sortedIndices = await sortManager.sortMultiColumn(columnValues, directions);
3616
- }
3617
- const reordered = new Array(processedData.length);
3618
- for (let i = 0; i < sortedIndices.length; i++) reordered[i] = processedData[sortedIndices[i]];
3619
- processedData = reordered;
3620
- } else processedData = applySort(processedData, request.sort, getFieldValue$3);
3621
- const totalRows = processedData.length;
3622
- const { pageIndex, pageSize } = request.pagination;
3623
- const startIndex = pageIndex * pageSize;
3624
- return {
3625
- rows: processedData.slice(startIndex, startIndex + pageSize),
3626
- totalRows
3627
- };
3628
- },
3629
- destroy() {
3630
- if (isDestroyed) return;
3631
- isDestroyed = true;
3632
- internalData = null;
3633
- if (sortManager) sortManager.terminate();
3634
- }
3635
- };
3636
- }
3637
- /**
3638
- * Convenience function to create a data source from an array.
3639
- * This provides backwards compatibility with the old `rowData` prop.
3640
- */
3641
- function createDataSourceFromArray(data) {
3642
- return createClientDataSource(data);
3643
- }
3644
-
3645
- //#endregion
3646
- //#region src/data-source/server-data-source.ts
3647
- /**
3648
- * Creates a server-side data source that delegates all operations to the server.
3649
- * The fetch function receives sort/filter/pagination params to pass to the API.
3650
- */
3651
- function createServerDataSource(fetchFn) {
3652
- return { async fetch(request) {
3653
- return fetchFn(request);
3654
- } };
3655
- }
3656
-
3657
- //#endregion
3658
- //#region src/indexed-data-store/field-helpers.ts
3659
- /**
3660
- * Default field value accessor supporting dot notation.
3661
- * @example
3662
- * getFieldValue({ user: { name: "John" } }, "user.name") // "John"
3663
- */
3664
- function getFieldValue(row, field) {
3665
- const parts = field.split(".");
3666
- let value = row;
3667
- for (const part of parts) {
3668
- if (value == null || typeof value !== "object") return null;
3669
- value = value[part];
3670
- }
3671
- return value ?? null;
3672
- }
3673
- /**
3674
- * Set field value supporting dot notation.
3675
- * Creates nested objects if they don't exist.
3676
- * @example
3677
- * const obj = { user: {} };
3678
- * setFieldValue(obj, "user.name", "John");
3679
- * // obj is now { user: { name: "John" } }
3680
- */
3681
- function setFieldValue(row, field, value) {
3682
- const parts = field.split(".");
3683
- let current = row;
3684
- for (let i = 0; i < parts.length - 1; i++) {
3685
- const part = parts[i];
3686
- if (current == null || typeof current !== "object") return;
3687
- current = current[part];
3688
- }
3689
- if (current != null && typeof current === "object") current[parts[parts.length - 1]] = value;
3690
- }
3691
-
3692
- //#endregion
3693
- //#region src/indexed-data-store/sorting.ts
3694
- /**
3695
- * Convert a string to a sortable number using first 10 characters.
3696
- * Uses base-36 encoding (a-z = 0-25, 0-9 = 26-35).
3697
- */
3698
- function stringToSortableNumber(str) {
3699
- const s = str.toLowerCase();
3700
- const len = Math.min(s.length, 10);
3701
- let hash = 0;
3702
- for (let i = 0; i < len; i++) {
3703
- const code = s.charCodeAt(i);
3704
- let mapped;
3705
- if (code >= 97 && code <= 122) mapped = code - 97;
3706
- else if (code >= 48 && code <= 57) mapped = code - 48 + 26;
3707
- else mapped = 0;
3708
- hash = hash * 36 + mapped;
3709
- }
3710
- for (let i = len; i < 10; i++) hash = hash * 36;
3711
- return hash;
3712
- }
3713
- /**
3714
- * Compare two cell values for sorting.
3715
- * Handles null/undefined, arrays, numbers, dates, and strings.
3716
- */
3717
- function compareValues(a, b) {
3718
- const aIsEmpty = a == null || Array.isArray(a) && a.length === 0;
3719
- const bIsEmpty = b == null || Array.isArray(b) && b.length === 0;
3720
- if (aIsEmpty && bIsEmpty) return 0;
3721
- if (aIsEmpty) return 1;
3722
- if (bIsEmpty) return -1;
3723
- if (Array.isArray(a) || Array.isArray(b)) {
3724
- const strA = Array.isArray(a) ? a.join(", ") : String(a ?? "");
3725
- const strB = Array.isArray(b) ? b.join(", ") : String(b ?? "");
3726
- return strA.localeCompare(strB);
3727
- }
3728
- const aNum = Number(a);
3729
- const bNum = Number(b);
3730
- if (!isNaN(aNum) && !isNaN(bNum)) return aNum - bNum;
3731
- if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
3732
- return String(a).localeCompare(String(b));
3733
- }
3734
- /**
3735
- * Compute a sortable hash for a cell value.
3736
- * Used for fast comparisons in sorted indices.
3737
- */
3738
- function computeValueHash(value) {
3739
- if (value == null) return Number.MAX_VALUE;
3740
- if (typeof value === "number") return value;
3741
- if (value instanceof Date) return value.getTime();
3742
- if (typeof value === "string") return stringToSortableNumber(value);
3743
- const num = Number(value);
3744
- return isNaN(num) ? 0 : num;
3745
- }
3746
- /**
3747
- * Compute sort hashes for a row based on sort model.
3748
- * Returns array of hashes, one for each sort column.
3749
- */
3750
- function computeRowSortHashes(row, config) {
3751
- const hashes = [];
3752
- for (const sort of config.sortModel) {
3753
- const hash = computeValueHash(config.getFieldValue(row, sort.colId));
3754
- hashes.push(hash);
3755
- }
3756
- return hashes;
3757
- }
3758
- /**
3759
- * Compare two rows using precomputed hash arrays.
3760
- * Falls back to direct comparison if hashes unavailable.
3761
- */
3762
- function compareRowsByHashes(hashesA, hashesB, sortModel) {
3763
- if (!hashesA || !hashesB) return null;
3764
- for (let i = 0; i < sortModel.length; i++) {
3765
- const diff = hashesA[i] - hashesB[i];
3766
- if (diff !== 0) return sortModel[i].direction === "asc" ? diff : -diff;
3767
- }
3768
- return 0;
3769
- }
3770
- /**
3771
- * Compare two rows directly without hash cache.
3772
- */
3773
- function compareRowsDirect(rowA, rowB, sortModel, getFieldValue$3) {
3774
- for (const { colId, direction } of sortModel) {
3775
- const comparison = compareValues(getFieldValue$3(rowA, colId), getFieldValue$3(rowB, colId));
3776
- if (comparison !== 0) return direction === "asc" ? comparison : -comparison;
3777
- }
3778
- return 0;
3779
- }
3780
-
3781
- //#endregion
3782
- //#region src/indexed-data-store/indexed-data-store.ts
3783
- /**
3784
- * Efficient data structure for incremental operations on grid data.
3785
- * Supports:
3786
- * - O(1) lookup by row ID
3787
- * - O(log n) binary insertion to maintain sort order
3788
- * - Filter state caching with distinct values
3789
- * - Hash caching for fast sorted comparisons
3790
- */
3791
- var IndexedDataStore = class {
3792
- rows = [];
3793
- rowById = /* @__PURE__ */ new Map();
3794
- sortedIndices = [];
3795
- sortModel = [];
3796
- sortModelHash = "";
3797
- filterModel = {};
3798
- filteredIndices = /* @__PURE__ */ new Set();
3799
- distinctValues = /* @__PURE__ */ new Map();
3800
- rowSortCache = /* @__PURE__ */ new Map();
3801
- options;
3802
- constructor(initialData = [], options) {
3803
- this.options = {
3804
- getRowId: options.getRowId,
3805
- getFieldValue: options.getFieldValue ?? getFieldValue
3806
- };
3807
- this.setData(initialData);
3808
- }
3809
- /**
3810
- * Clear all data and internal caches.
3811
- * Used for proper memory cleanup when the store is no longer needed.
3812
- */
3813
- clear() {
3814
- this.rows = [];
3815
- this.rowById.clear();
3816
- this.sortedIndices = [];
3817
- this.filterModel = {};
3818
- this.filteredIndices.clear();
3819
- this.rowSortCache.clear();
3820
- this.distinctValues.clear();
3821
- this.sortModel = [];
3822
- this.sortModelHash = "";
3823
- }
3824
- /**
3825
- * Replace all data (used for initial load or full refresh).
3826
- */
3827
- setData(data) {
3828
- this.rows = [...data];
3829
- this.rowById.clear();
3830
- this.rowSortCache.clear();
3831
- this.distinctValues.clear();
3832
- for (let i = 0; i < this.rows.length; i++) {
3833
- const row = this.rows[i];
3834
- const id = this.options.getRowId(row);
3835
- this.rowById.set(id, i);
3836
- }
3837
- this.rebuildSortedIndices();
3838
- this.rebuildFilteredIndices();
3839
- this.rebuildDistinctValues();
3840
- }
3841
- /**
3842
- * Query data with sorting, filtering, and pagination.
3843
- * Compatible with DataSource.fetch() interface.
3844
- */
3845
- query(request) {
3846
- this.setSortModel(request.sort ?? []);
3847
- this.setFilterModel(request.filter ?? {});
3848
- const visibleIndices = this.getVisibleIndices();
3849
- const totalRows = visibleIndices.length;
3850
- const { pageIndex, pageSize } = request.pagination;
3851
- const startIndex = pageIndex * pageSize;
3852
- const endIndex = Math.min(startIndex + pageSize, totalRows);
3853
- const rows = [];
3854
- for (let i = startIndex; i < endIndex; i++) {
3855
- const rowIndex = visibleIndices[i];
3856
- if (rowIndex !== void 0) rows.push(this.rows[rowIndex]);
3857
- }
3858
- return {
3859
- rows,
3860
- totalRows
3861
- };
3862
- }
3863
- /**
3864
- * Get row by ID.
3865
- */
3866
- getRowById(id) {
3867
- const index = this.rowById.get(id);
3868
- return index !== void 0 ? this.rows[index] : void 0;
3869
- }
3870
- /**
3871
- * Get row by index.
3872
- */
3873
- getRowByIndex(index) {
3874
- return this.rows[index];
3875
- }
3876
- /**
3877
- * Get total row count (unfiltered).
3878
- */
3879
- getTotalRowCount() {
3880
- return this.rows.length;
3881
- }
3882
- /**
3883
- * Get visible row count (after filtering).
3884
- */
3885
- getVisibleRowCount() {
3886
- if (Object.keys(this.filterModel).length === 0) return this.rows.length;
3887
- return this.filteredIndices.size;
3888
- }
3889
- /**
3890
- * Get distinct values for a field (for filter UI).
3891
- */
3892
- getDistinctValues(field) {
3893
- const values = this.distinctValues.get(field);
3894
- return values ? Array.from(values) : [];
3895
- }
3896
- /**
3897
- * Add rows to the store.
3898
- * Rows are inserted at their correct sorted position.
3899
- */
3900
- addRows(rows) {
3901
- for (const row of rows) this.addRow(row);
3902
- }
3903
- /**
3904
- * Add a single row.
3905
- */
3906
- addRow(row) {
3907
- const id = this.options.getRowId(row);
3908
- if (this.rowById.has(id)) {
3909
- console.warn(`Row with ID ${id} already exists. Skipping.`);
3910
- return;
3911
- }
3912
- const index = this.rows.length;
3913
- this.rows.push(row);
3914
- this.rowById.set(id, index);
3915
- this.updateDistinctValuesForRow(row, "add");
3916
- if (this.sortModel.length > 0) this.computeRowHashes(index, row);
3917
- if (this.sortModel.length > 0) {
3918
- const insertPos = this.binarySearchInsertPosition(index);
3919
- this.sortedIndices.splice(insertPos, 0, index);
3920
- } else this.sortedIndices.push(index);
3921
- if (this.rowPassesFilter(row)) this.filteredIndices.add(index);
3922
- }
3923
- /**
3924
- * Remove rows by ID.
3925
- */
3926
- removeRows(ids) {
3927
- const indicesToRemove = [];
3928
- for (const id of ids) {
3929
- const index = this.rowById.get(id);
3930
- if (index !== void 0) indicesToRemove.push(index);
3931
- }
3932
- if (indicesToRemove.length === 0) return;
3933
- indicesToRemove.sort((a, b) => b - a);
3934
- for (const index of indicesToRemove) this.removeRowByIndex(index);
3935
- }
3936
- /**
3937
- * Remove a single row by index.
3938
- */
3939
- removeRowByIndex(index) {
3940
- const row = this.rows[index];
3941
- if (!row) return;
3942
- const id = this.options.getRowId(row);
3943
- this.updateDistinctValuesForRow(row, "remove");
3944
- const sortedPos = this.sortedIndices.indexOf(index);
3945
- if (sortedPos !== -1) this.sortedIndices.splice(sortedPos, 1);
3946
- this.filteredIndices.delete(index);
3947
- this.rowSortCache.delete(index);
3948
- this.rowById.delete(id);
3949
- this.rows.splice(index, 1);
3950
- this.reindexAfterRemoval(index);
3951
- }
3952
- /**
3953
- * Update indices after a row removal.
3954
- */
3955
- reindexAfterRemoval(removedIndex) {
3956
- for (const [id, idx] of this.rowById.entries()) if (idx > removedIndex) this.rowById.set(id, idx - 1);
3957
- for (let i = 0; i < this.sortedIndices.length; i++) if (this.sortedIndices[i] > removedIndex) this.sortedIndices[i]--;
3958
- const newFiltered = /* @__PURE__ */ new Set();
3959
- for (const idx of this.filteredIndices) if (idx > removedIndex) newFiltered.add(idx - 1);
3960
- else newFiltered.add(idx);
3961
- this.filteredIndices = newFiltered;
3962
- const newCache = /* @__PURE__ */ new Map();
3963
- for (const [idx, cache] of this.rowSortCache) if (idx > removedIndex) newCache.set(idx - 1, cache);
3964
- else newCache.set(idx, cache);
3965
- this.rowSortCache = newCache;
3966
- }
3967
- /**
3968
- * Update a cell value.
3969
- */
3970
- updateCell(id, field, value) {
3971
- const index = this.rowById.get(id);
3972
- if (index === void 0) {
3973
- console.warn(`Row with ID ${id} not found.`);
3974
- return;
3975
- }
3976
- const row = this.rows[index];
3977
- const oldValue = this.options.getFieldValue(row, field);
3978
- setFieldValue(row, field, value);
3979
- this.updateDistinctValueForField(field, oldValue, value);
3980
- if (this.sortModel.some((s) => s.colId === field) && this.sortModel.length > 0) {
3981
- this.computeRowHashes(index, row);
3982
- const currentPos = this.sortedIndices.indexOf(index);
3983
- if (currentPos !== -1) this.sortedIndices.splice(currentPos, 1);
3984
- const newPos = this.binarySearchInsertPosition(index);
3985
- this.sortedIndices.splice(newPos, 0, index);
3986
- }
3987
- if (field in this.filterModel) if (this.rowPassesFilter(row)) this.filteredIndices.add(index);
3988
- else this.filteredIndices.delete(index);
3989
- }
3990
- /**
3991
- * Update multiple fields on a row.
3992
- */
3993
- updateRow(id, data) {
3994
- for (const [field, value] of Object.entries(data)) this.updateCell(id, field, value);
3995
- }
3996
- /**
3997
- * Set the sort model. Triggers full re-sort if model changed.
3998
- */
3999
- setSortModel(model) {
4000
- const newHash = JSON.stringify(model);
4001
- if (newHash === this.sortModelHash) return;
4002
- this.sortModelHash = newHash;
4003
- this.sortModel = [...model];
4004
- this.rebuildHashCache();
4005
- this.rebuildSortedIndices();
4006
- }
4007
- /**
4008
- * Get current sort model.
4009
- */
4010
- getSortModel() {
4011
- return [...this.sortModel];
4012
- }
4013
- /**
4014
- * Set the filter model.
4015
- */
4016
- setFilterModel(model) {
4017
- if (JSON.stringify(model) === JSON.stringify(this.filterModel)) return;
4018
- this.filterModel = { ...model };
4019
- this.rebuildFilteredIndices();
4020
- }
4021
- /**
4022
- * Get current filter model.
4023
- */
4024
- getFilterModel() {
4025
- return { ...this.filterModel };
4026
- }
4027
- /**
4028
- * Rebuild sorted indices (full re-sort).
4029
- */
4030
- rebuildSortedIndices() {
4031
- this.sortedIndices = Array.from({ length: this.rows.length }, (_, i) => i);
4032
- if (this.sortModel.length === 0) return;
4033
- this.sortedIndices.sort((a, b) => this.compareRows(a, b));
4034
- }
4035
- /**
4036
- * Rebuild hash cache for all rows.
4037
- */
4038
- rebuildHashCache() {
4039
- this.rowSortCache.clear();
4040
- if (this.sortModel.length === 0) return;
4041
- for (let i = 0; i < this.rows.length; i++) this.computeRowHashes(i, this.rows[i]);
4042
- }
4043
- /**
4044
- * Compute and cache sort hashes for a row.
4045
- */
4046
- computeRowHashes(rowIndex, row) {
4047
- if (this.sortModel.length === 0) return;
4048
- const hashes = computeRowSortHashes(row, {
4049
- sortModel: this.sortModel,
4050
- sortModelHash: this.sortModelHash,
4051
- getFieldValue: this.options.getFieldValue
4052
- });
4053
- let cache = this.rowSortCache.get(rowIndex);
4054
- if (!cache) {
4055
- cache = { hashes: /* @__PURE__ */ new Map() };
4056
- this.rowSortCache.set(rowIndex, cache);
4057
- }
4058
- cache.hashes.set(this.sortModelHash, hashes);
4059
- }
4060
- /**
4061
- * Compare two rows using cached hashes.
4062
- */
4063
- compareRows(indexA, indexB) {
4064
- const cacheA = this.rowSortCache.get(indexA);
4065
- const cacheB = this.rowSortCache.get(indexB);
4066
- const hashesA = cacheA?.hashes.get(this.sortModelHash);
4067
- const hashesB = cacheB?.hashes.get(this.sortModelHash);
4068
- const hashResult = compareRowsByHashes(hashesA, hashesB, this.sortModel);
4069
- if (hashResult !== null) return hashResult;
4070
- return compareRowsDirect(this.rows[indexA], this.rows[indexB], this.sortModel, this.options.getFieldValue);
4071
- }
4072
- /**
4073
- * Binary search for insertion position in sortedIndices.
4074
- */
4075
- binarySearchInsertPosition(rowIndex) {
4076
- let low = 0;
4077
- let high = this.sortedIndices.length;
4078
- while (low < high) {
4079
- const mid = low + high >>> 1;
4080
- const midIndex = this.sortedIndices[mid];
4081
- if (this.compareRows(rowIndex, midIndex) > 0) low = mid + 1;
4082
- else high = mid;
4083
- }
4084
- return low;
4085
- }
4086
- /**
4087
- * Rebuild filtered indices.
4088
- */
4089
- rebuildFilteredIndices() {
4090
- this.filteredIndices.clear();
4091
- if (Object.entries(this.filterModel).filter(([, value]) => value != null).length === 0) return;
4092
- for (let i = 0; i < this.rows.length; i++) if (this.rowPassesFilter(this.rows[i])) this.filteredIndices.add(i);
4093
- }
4094
- /**
4095
- * Check if a row passes the current filter.
4096
- */
4097
- rowPassesFilter(row) {
4098
- return rowPassesFilter(row, this.filterModel, this.options.getFieldValue);
4099
- }
4100
- /**
4101
- * Get visible indices (filtered + sorted).
4102
- */
4103
- getVisibleIndices() {
4104
- if (!(Object.entries(this.filterModel).filter(([, v]) => v != null).length > 0)) return this.sortedIndices;
4105
- return this.sortedIndices.filter((idx) => this.filteredIndices.has(idx));
4106
- }
4107
- /**
4108
- * Rebuild distinct values cache for all fields.
4109
- */
4110
- rebuildDistinctValues() {
4111
- this.distinctValues.clear();
4112
- for (const row of this.rows) this.updateDistinctValuesForRow(row, "add");
4113
- }
4114
- /**
4115
- * Update distinct values when a row is added or removed.
4116
- */
4117
- updateDistinctValuesForRow(row, operation) {
4118
- if (typeof row !== "object" || row === null) return;
4119
- for (const [field, value] of Object.entries(row)) {
4120
- if (value == null) continue;
4121
- if (operation === "add") {
4122
- let values = this.distinctValues.get(field);
4123
- if (!values) {
4124
- values = /* @__PURE__ */ new Set();
4125
- this.distinctValues.set(field, values);
4126
- }
4127
- if (Array.isArray(value)) {
4128
- for (const item of value) if (item != null) values.add(item);
4129
- } else values.add(value);
4130
- }
4131
- }
4132
- }
4133
- /**
4134
- * Update distinct value for a specific field when cell value changes.
4135
- */
4136
- updateDistinctValueForField(field, _oldValue, newValue) {
4137
- if (newValue != null) {
4138
- let values = this.distinctValues.get(field);
4139
- if (!values) {
4140
- values = /* @__PURE__ */ new Set();
4141
- this.distinctValues.set(field, values);
4142
- }
4143
- if (Array.isArray(newValue)) {
4144
- for (const item of newValue) if (item != null) values.add(item);
4145
- } else values.add(newValue);
4146
- }
4147
- }
4148
- };
4149
-
4150
- //#endregion
4151
- //#region src/transaction-manager.ts
4152
- /**
4153
- * Manages a queue of data mutations with debounced batch processing.
4154
- * Supports ADD, REMOVE, UPDATE_CELL, and UPDATE_ROW operations.
4155
- */
4156
- var TransactionManager = class {
4157
- queue = [];
4158
- debounceTimer = null;
4159
- pendingPromise = null;
4160
- options;
4161
- constructor(options) {
4162
- this.options = options;
4163
- }
4164
- /**
4165
- * Queue rows to be added.
4166
- */
4167
- add(rows) {
4168
- if (rows.length === 0) return;
4169
- this.queue.push({
4170
- type: "ADD",
4171
- rows
4172
- });
4173
- this.scheduleProcessing();
4174
- }
4175
- /**
4176
- * Queue rows to be removed by ID.
4177
- */
4178
- remove(rowIds) {
4179
- if (rowIds.length === 0) return;
4180
- this.queue.push({
4181
- type: "REMOVE",
4182
- rowIds
4183
- });
4184
- this.scheduleProcessing();
4185
- }
4186
- /**
4187
- * Queue a cell update.
4188
- */
4189
- updateCell(rowId, field, value) {
4190
- this.queue.push({
4191
- type: "UPDATE_CELL",
4192
- rowId,
4193
- field,
4194
- value
4195
- });
4196
- this.scheduleProcessing();
4197
- }
4198
- /**
4199
- * Queue a row update (multiple fields).
4200
- */
4201
- updateRow(rowId, data) {
4202
- if (Object.keys(data).length === 0) return;
4203
- this.queue.push({
4204
- type: "UPDATE_ROW",
4205
- rowId,
4206
- data
4207
- });
4208
- this.scheduleProcessing();
4209
- }
4210
- /**
4211
- * Force immediate processing of queued transactions.
4212
- * Returns a promise that resolves when processing is complete.
4213
- */
4214
- flush() {
4215
- if (this.queue.length === 0) return Promise.resolve();
4216
- if (this.debounceTimer !== null) {
4217
- clearTimeout(this.debounceTimer);
4218
- this.debounceTimer = null;
4219
- }
4220
- if (this.pendingPromise) return new Promise((resolve, reject) => {
4221
- const existing = this.pendingPromise;
4222
- const originalResolve = existing.resolve;
4223
- const originalReject = existing.reject;
4224
- existing.resolve = () => {
4225
- originalResolve();
4226
- resolve();
4227
- };
4228
- existing.reject = (error) => {
4229
- originalReject(error);
4230
- reject(error);
4231
- };
4232
- });
4233
- return new Promise((resolve, reject) => {
4234
- this.pendingPromise = {
4235
- resolve,
4236
- reject
4237
- };
4238
- this.processQueue();
4239
- });
4240
- }
4241
- /**
4242
- * Check if there are pending transactions.
4243
- */
4244
- hasPending() {
4245
- return this.queue.length > 0;
4246
- }
4247
- /**
4248
- * Get count of pending transactions.
4249
- */
4250
- getPendingCount() {
4251
- return this.queue.length;
4252
- }
4253
- /**
4254
- * Clear all pending transactions without processing.
4255
- */
4256
- clear() {
4257
- this.queue = [];
4258
- if (this.debounceTimer !== null) {
4259
- clearTimeout(this.debounceTimer);
4260
- this.debounceTimer = null;
4261
- }
4262
- if (this.pendingPromise) {
4263
- this.pendingPromise.resolve();
4264
- this.pendingPromise = null;
4265
- }
4266
- }
4267
- /**
4268
- * Schedule processing after throttle delay.
4269
- * Uses throttle pattern: if a timer is already pending, new transactions
4270
- * are added to the queue but don't reset the timer. This ensures updates
4271
- * are processed even when they arrive faster than the throttle interval.
4272
- */
4273
- scheduleProcessing() {
4274
- if (this.options.debounceMs === 0) {
4275
- this.processQueue();
4276
- return;
4277
- }
4278
- if (this.debounceTimer !== null) return;
4279
- this.debounceTimer = setTimeout(() => {
4280
- this.debounceTimer = null;
4281
- this.processQueue();
4282
- }, this.options.debounceMs);
4283
- }
4284
- /**
4285
- * Process all queued transactions.
4286
- */
4287
- processQueue() {
4288
- if (this.queue.length === 0) {
4289
- if (this.pendingPromise) {
4290
- this.pendingPromise.resolve();
4291
- this.pendingPromise = null;
4292
- }
4293
- return;
4294
- }
4295
- const transactions = this.queue;
4296
- this.queue = [];
4297
- const result = {
4298
- added: 0,
4299
- removed: 0,
4300
- updated: 0
4301
- };
4302
- try {
4303
- for (const tx of transactions) switch (tx.type) {
4304
- case "ADD":
4305
- this.options.store.addRows(tx.rows);
4306
- result.added += tx.rows.length;
4307
- break;
4308
- case "REMOVE":
4309
- this.options.store.removeRows(tx.rowIds);
4310
- result.removed += tx.rowIds.length;
4311
- break;
4312
- case "UPDATE_CELL":
4313
- this.options.store.updateCell(tx.rowId, tx.field, tx.value);
4314
- result.updated++;
4315
- break;
4316
- case "UPDATE_ROW":
4317
- this.options.store.updateRow(tx.rowId, tx.data);
4318
- result.updated++;
4319
- break;
4320
- }
4321
- if (this.options.onProcessed) this.options.onProcessed(result);
4322
- if (this.pendingPromise) {
4323
- this.pendingPromise.resolve();
4324
- this.pendingPromise = null;
4325
- }
4326
- } catch (error) {
4327
- if (this.pendingPromise) {
4328
- this.pendingPromise.reject(error instanceof Error ? error : new Error(String(error)));
4329
- this.pendingPromise = null;
4330
- }
4331
- }
4332
- }
4333
- };
4334
-
4335
- //#endregion
4336
- //#region src/data-source/mutable-data-source.ts
4337
- /**
4338
- * Creates a mutable client-side data source with transaction support.
4339
- * Uses IndexedDataStore for efficient incremental operations.
4340
- */
4341
- function createMutableClientDataSource(data, options) {
4342
- const { getRowId, getFieldValue: getFieldValue$3, debounceMs = 50, onTransactionProcessed } = options;
4343
- const store = new IndexedDataStore(data, {
4344
- getRowId,
4345
- getFieldValue: getFieldValue$3
4346
- });
4347
- const subscribers = /* @__PURE__ */ new Set();
4348
- const transactionManager = new TransactionManager({
4349
- debounceMs,
4350
- store,
4351
- onProcessed: (result) => {
4352
- onTransactionProcessed?.(result);
4353
- for (const listener of subscribers) listener(result);
4354
- }
4355
- });
4356
- return {
4357
- async fetch(request) {
4358
- if (transactionManager.hasPending()) await transactionManager.flush();
4359
- return store.query(request);
4360
- },
4361
- addRows(rows) {
4362
- transactionManager.add(rows);
4363
- },
4364
- removeRows(ids) {
4365
- transactionManager.remove(ids);
4366
- },
4367
- updateCell(id, field, value) {
4368
- transactionManager.updateCell(id, field, value);
4369
- },
4370
- updateRow(id, data$1) {
4371
- transactionManager.updateRow(id, data$1);
4372
- },
4373
- async flushTransactions() {
4374
- await transactionManager.flush();
4375
- },
4376
- hasPendingTransactions() {
4377
- return transactionManager.hasPending();
4378
- },
4379
- getDistinctValues(field) {
4380
- return store.getDistinctValues(field);
4381
- },
4382
- getRowById(id) {
4383
- return store.getRowById(id);
4384
- },
4385
- getTotalRowCount() {
4386
- return store.getTotalRowCount();
4387
- },
4388
- subscribe(listener) {
4389
- subscribers.add(listener);
4390
- return () => {
4391
- subscribers.delete(listener);
4392
- };
4393
- },
4394
- clear() {
4395
- store.clear();
4396
- subscribers.clear();
4397
- }
4398
- };
4399
- }
4400
-
4401
- //#endregion
4402
- //#region src/styles/variables.ts
4403
- const variablesStyles = `
322
+ `,{maxWorkers:e.maxWorkers??(typeof navigator<`u`?navigator.hardwareConcurrency:4)??4}),this.parallelThreshold=e.parallelThreshold??4e5,this.minChunkSize=e.minChunkSize??5e4}isAvailable(){return!this.isTerminated&&this.pool.isAvailable()}terminate(){this.pool.terminate(),this.isTerminated=!0}async sortIndices(e,t){if(this.isTerminated)throw Error(`ParallelSortManager has been terminated`);return e.length<this.parallelThreshold?this.sortIndicesSingle(e,t):this.sortIndicesParallel(e,t)}async sortStringHashes(e,t,n){if(this.isTerminated)throw Error(`ParallelSortManager has been terminated`);return(e[0]?.length??0)<this.parallelThreshold?this.sortStringHashesSingle(e,t,n):this.sortStringHashesParallel(e,t,n)}async sortMultiColumn(e,t){if(this.isTerminated)throw Error(`ParallelSortManager has been terminated`);return(e[0]?.length??0)<this.parallelThreshold?this.sortMultiColumnSingle(e,t):this.sortMultiColumnParallel(e,t)}async sortIndicesSingle(e,t){let n=new Float64Array(e),r={type:`sortIndices`,id:0,values:n,direction:t};return(await this.pool.execute(r,[n.buffer])).indices}async sortStringHashesSingle(e,t,n){let r={type:`sortStringHashes`,id:0,hashChunks:e,direction:t},i=e.map(e=>e.buffer),a=await this.pool.execute(r,i);return a.collisionRuns.length>0&&this.resolveCollisions(a.indices,a.collisionRuns,n,t),a.indices}async sortMultiColumnSingle(e,t){let n=e.map(e=>new Float64Array(e)),r=new Int8Array(t.map(e=>e===`asc`?1:-1)),i={type:`sortMultiColumn`,id:0,columns:n,directions:r},a=[...n.map(e=>e.buffer),r.buffer];return(await this.pool.execute(i,a)).indices}async sortIndicesParallel(e,t){let n=this.splitIntoChunks(e).map(e=>{let n=new Float64Array(e.data);return{request:{type:`sortChunk`,id:0,values:n,direction:t,chunkOffset:e.offset},transferables:[n.buffer]}}),r=await this.pool.executeParallel(n);return r.sort((e,t)=>e.chunkOffset-t.chunkOffset),E(r.map(e=>({indices:e.indices,values:e.sortedValues,offset:e.chunkOffset})),t)}async sortStringHashesParallel(e,t,n){let r=e[0].length,i=this.calculateChunkBoundaries(r).map(n=>{let r=e.map(e=>new Float64Array(e.buffer,n.offset*8,n.length)).map(e=>{let t=new Float64Array(e.length);return t.set(e),t});return{request:{type:`sortStringChunk`,id:0,hashChunks:r,direction:t,chunkOffset:n.offset},transferables:r.map(e=>e.buffer)}}),a=await this.pool.executeParallel(i);a.sort((e,t)=>e.chunkOffset-t.chunkOffset);let o=a.map(e=>({indices:e.indices,values:e.sortedHashes,offset:e.chunkOffset})),s=[];for(let e of a)for(let t=0;t<e.collisionRuns.length;t+=2)s.push(e.collisionRuns[t]+e.chunkOffset,e.collisionRuns[t+1]+e.chunkOffset);let c=E(o,t),l=this.detectBoundaryCollisionsForStrings(o,t),u=new Uint32Array([...s,...l]);return u.length>0&&this.resolveCollisions(c,u,n,t),c}async sortMultiColumnParallel(e,t){let n=e[0].length,r=this.calculateChunkBoundaries(n),i=new Int8Array(t.map(e=>e===`asc`?1:-1)),a=r.map(t=>{let n=e.map(e=>{let n=new Float64Array(t.length);for(let r=0;r<t.length;r++)n[r]=e[t.offset+r];return n}),r=new Int8Array(i);return{request:{type:`sortMultiColumnChunk`,id:0,columns:n,directions:r,chunkOffset:t.offset},transferables:[...n.map(e=>e.buffer),r.buffer]}}),o=await this.pool.executeParallel(a);return o.sort((e,t)=>e.chunkOffset-t.chunkOffset),D(o.map(e=>({indices:e.indices,columns:e.sortedColumns,directions:i,offset:e.chunkOffset})))}splitIntoChunks(e){let t=e.length,n=this.pool.getMaxWorkers(),r=Math.max(this.minChunkSize,Math.ceil(t/n)),i=[];for(let n=0;n<t;n+=r){let a=Math.min(n+r,t);i.push({data:e.slice(n,a),offset:n})}return i}calculateChunkBoundaries(e){let t=this.pool.getMaxWorkers(),n=Math.max(this.minChunkSize,Math.ceil(e/t)),r=[];for(let t=0;t<e;t+=n)r.push({offset:t,length:Math.min(n,e-t)});return r}detectBoundaryCollisionsForStrings(e,t){if(e.length<=1)return[];let n=[],r=0;for(let t=0;t<e.length-1;t++){let i=e[t],a=e[t+1];if(i.indices.length===0||a.indices.length===0){r+=i.indices.length;continue}let o=i.values[i.indices.length-1],s=a.values[0];if(o===s){let e=i.indices.length-1;for(;e>0&&i.values[e-1]===o;)e--;let t=0;for(;t<a.indices.length-1&&a.values[t+1]===s;)t++;n.push(r+e,r+i.indices.length+t+1)}r+=i.indices.length}return n}resolveCollisions(e,t,n,r){let i=r===`asc`?1:-1;for(let r=0;r<t.length;r+=2){let a=t[r],o=t[r+1];if(o<=a||o>e.length)continue;let s=Array.from(e.slice(a,o)),c=n[s[0]],l=!0;for(let e=1;e<s.length;e++)if(n[s[e]]!==c){l=!1;break}if(!l){s.sort((e,t)=>i*n[e].localeCompare(n[t]));for(let t=0;t<s.length;t++)e[a+t]=s[t]}}}};function k(e){let t=e.toLowerCase(),n=Math.min(t.length,10),r=0;for(let e=0;e<n;e++){let n=t.charCodeAt(e),i;i=n>=97&&n<=122?n-97:n>=48&&n<=57?n-48+26:0,r=r*36+i}for(let e=n;e<10;e++)r*=36;return r}function le(e){let t=e.toLowerCase(),n=[];for(let e=0;e<3;e++){let r=e*10,i=0;for(let e=0;e<10;e++){let n=r+e,a=n<t.length?t.charCodeAt(n):0,o;o=a>=97&&a<=122?a-97:a>=48&&a<=57?a-48+26:0,i=i*36+o}n.push(i)}return n}function A(e){if(e==null)return Number.MAX_VALUE;if(Array.isArray(e))return e.length===0?Number.MAX_VALUE:k(e.join(`, `));if(typeof e==`number`)return e;if(e instanceof Date)return e.getTime();if(typeof e==`string`)return k(e);let t=Number(e);return isNaN(t)?0:t}function ue(e,t){let n=e==null||Array.isArray(e)&&e.length===0,r=t==null||Array.isArray(t)&&t.length===0;if(n&&r)return 0;if(n)return 1;if(r)return-1;if(Array.isArray(e)||Array.isArray(t)){let n=Array.isArray(e)?e.join(`, `):String(e??``),r=Array.isArray(t)?t.join(`, `):String(t??``);return n.localeCompare(r)}let i=Number(e),a=Number(t);return!isNaN(i)&&!isNaN(a)?i-a:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():String(e).localeCompare(String(t))}function de(e,t,n){return[...e].sort((e,r)=>{for(let{colId:i,direction:a}of t){let t=ue(n(e,i),n(r,i));if(t!==0)return a===`asc`?t:-t}return 0})}function j(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function fe(e){return e==null||e===``||Array.isArray(e)&&e.length===0}function M(e,t){let n=fe(e);if(t.selectedValues&&t.selectedValues.size>0){let r=t.includeBlank===!0&&n;if(Array.isArray(e)){let n=[...e].sort((e,t)=>String(e).localeCompare(String(t),void 0,{numeric:!0,sensitivity:`base`})).join(`, `);return t.selectedValues.has(n)||r}let i=String(e??``);return t.selectedValues.has(i)||r}let r=String(e??``).toLowerCase(),i=String(t.value??``).toLowerCase();switch(t.operator){case`contains`:return r.includes(i);case`notContains`:return!r.includes(i);case`equals`:return r===i;case`notEquals`:return r!==i;case`startsWith`:return r.startsWith(i);case`endsWith`:return r.endsWith(i);case`blank`:return n;case`notBlank`:return!n;default:return!0}}function N(e,t){let n=e==null||e===``;if(t.operator===`blank`)return n;if(t.operator===`notBlank`)return!n;if(n)return!1;let r=typeof e==`number`?e:Number(e);if(isNaN(r))return!1;let i=t.value??0,a=t.valueTo??0;switch(t.operator){case`=`:return r===i;case`!=`:return r!==i;case`>`:return r>i;case`<`:return r<i;case`>=`:return r>=i;case`<=`:return r<=i;case`between`:return r>=i&&r<=a;default:return!0}}function P(e,t){let n=e==null||e===``;if(t.operator===`blank`)return n;if(t.operator===`notBlank`)return!n;if(n)return!1;let r=e instanceof Date?e:new Date(String(e));if(isNaN(r.getTime()))return!1;let i=t.value instanceof Date?t.value:new Date(String(t.value??``)),a=t.valueTo instanceof Date?t.valueTo:new Date(String(t.valueTo??``)),o=r.getTime(),s=i.getTime(),c=a.getTime();switch(t.operator){case`=`:return j(r,i);case`!=`:return!j(r,i);case`>`:return o>s;case`<`:return o<s;case`between`:return o>=s&&o<=c;default:return!0}}function F(e,t){switch(t.type){case`text`:return M(e,t);case`number`:return N(e,t);case`date`:return P(e,t);default:return!0}}function I(e,t){if(!t.conditions||t.conditions.length===0)return!0;let n=t.conditions[0];if(!n)return!0;let r=F(e,n);for(let n=1;n<t.conditions.length;n++){let i=t.conditions[n-1],a=t.conditions[n],o=i.nextOperator??t.combination,s=F(e,a);o===`and`?r&&=s:r||=s}return r}function L(e,t,n){let r=Object.entries(t).filter(([,e])=>e!=null);if(r.length===0)return!0;for(let[t,i]of r)if(!I(n(e,t),i))return!1;return!0}function pe(e,t,n){let r=Object.entries(t).filter(([,e])=>typeof e==`string`?e.trim()!==``:e.conditions&&e.conditions.length>0);return r.length===0?e:e.filter(e=>{for(let[t,i]of r){let r=n(e,t);if(typeof i==`string`){if(!String(r??``).toLowerCase().includes(i.toLowerCase()))return!1;continue}if(!I(r,i))return!1}return!0})}function me(e,t){let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null}function R(e,t={}){let{getFieldValue:n=me,useWorker:r=!0,parallelSort:i}=t,a=e,o=!1,s=r?new O(i===!1?{maxWorkers:1}:i):null;return{async fetch(e){let t=a?[...a]:[];if(e.filter&&Object.keys(e.filter).length>0&&(t=pe(t,e.filter,n)),e.sort&&e.sort.length>0)if(s&&s.isAvailable()&&t.length>=2e5){let r;if(e.sort.length===1){let{colId:i,direction:a}=e.sort[0],o=!1;for(let e of t){let t=n(e,i);if(t!=null){o=typeof t==`string`||Array.isArray(t);break}}if(o){let e=[],o=Array.from({length:3},()=>[]);for(let r of t){let t=n(r,i),a=t==null?``:Array.isArray(t)?t.join(`, `):String(t);e.push(a);let s=le(a);for(let e=0;e<3;e++)o[e].push(s[e])}let c=o.map(e=>new Float64Array(e));r=await s.sortStringHashes(c,a,e)}else{let e=t.map(e=>A(n(e,i)));r=await s.sortIndices(e,a)}}else{let i=[],a=[];for(let{colId:r,direction:o}of e.sort){let e=t.map(e=>A(n(e,r)));i.push(e),a.push(o)}r=await s.sortMultiColumn(i,a)}let i=Array(t.length);for(let e=0;e<r.length;e++)i[e]=t[r[e]];t=i}else t=de(t,e.sort,n);let r=t.length,{pageIndex:i,pageSize:o}=e.pagination,c=i*o;return{rows:t.slice(c,c+o),totalRows:r}},destroy(){o||(o=!0,a=null,s&&s.terminate())}}}function he(e){return R(e)}function ge(e){return{async fetch(t){return e(t)}}}function z(e,t){let n=t.split(`.`),r=e;for(let e of n){if(typeof r!=`object`||!r)return null;r=r[e]}return r??null}function B(e,t,n){let r=t.split(`.`),i=e;for(let e=0;e<r.length-1;e++){let t=r[e];if(typeof i!=`object`||!i)return;i=i[t]}typeof i==`object`&&i&&(i[r[r.length-1]]=n)}function V(e){let t=e.toLowerCase(),n=Math.min(t.length,10),r=0;for(let e=0;e<n;e++){let n=t.charCodeAt(e),i;i=n>=97&&n<=122?n-97:n>=48&&n<=57?n-48+26:0,r=r*36+i}for(let e=n;e<10;e++)r*=36;return r}function H(e,t){let n=e==null||Array.isArray(e)&&e.length===0,r=t==null||Array.isArray(t)&&t.length===0;if(n&&r)return 0;if(n)return 1;if(r)return-1;if(Array.isArray(e)||Array.isArray(t)){let n=Array.isArray(e)?e.join(`, `):String(e??``),r=Array.isArray(t)?t.join(`, `):String(t??``);return n.localeCompare(r)}let i=Number(e),a=Number(t);return!isNaN(i)&&!isNaN(a)?i-a:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():String(e).localeCompare(String(t))}function U(e){if(e==null)return Number.MAX_VALUE;if(typeof e==`number`)return e;if(e instanceof Date)return e.getTime();if(typeof e==`string`)return V(e);let t=Number(e);return isNaN(t)?0:t}function _e(e,t){let n=[];for(let r of t.sortModel){let i=U(t.getFieldValue(e,r.colId));n.push(i)}return n}function ve(e,t,n){if(!e||!t)return null;for(let r=0;r<n.length;r++){let i=e[r]-t[r];if(i!==0)return n[r].direction===`asc`?i:-i}return 0}function ye(e,t,n,r){for(let{colId:i,direction:a}of n){let n=H(r(e,i),r(t,i));if(n!==0)return a===`asc`?n:-n}return 0}var W=class{rows=[];rowById=new Map;sortedIndices=[];sortModel=[];sortModelHash=``;filterModel={};filteredIndices=new Set;distinctValues=new Map;rowSortCache=new Map;options;constructor(e=[],t){this.options={getRowId:t.getRowId,getFieldValue:t.getFieldValue??z},this.setData(e)}clear(){this.rows=[],this.rowById.clear(),this.sortedIndices=[],this.filterModel={},this.filteredIndices.clear(),this.rowSortCache.clear(),this.distinctValues.clear(),this.sortModel=[],this.sortModelHash=``}setData(e){this.rows=[...e],this.rowById.clear(),this.rowSortCache.clear(),this.distinctValues.clear();for(let e=0;e<this.rows.length;e++){let t=this.rows[e],n=this.options.getRowId(t);this.rowById.set(n,e)}this.rebuildSortedIndices(),this.rebuildFilteredIndices(),this.rebuildDistinctValues()}query(e){this.setSortModel(e.sort??[]),this.setFilterModel(e.filter??{});let t=this.getVisibleIndices(),n=t.length,{pageIndex:r,pageSize:i}=e.pagination,a=r*i,o=Math.min(a+i,n),s=[];for(let e=a;e<o;e++){let n=t[e];n!==void 0&&s.push(this.rows[n])}return{rows:s,totalRows:n}}getRowById(e){let t=this.rowById.get(e);return t===void 0?void 0:this.rows[t]}getRowByIndex(e){return this.rows[e]}getTotalRowCount(){return this.rows.length}getVisibleRowCount(){return Object.keys(this.filterModel).length===0?this.rows.length:this.filteredIndices.size}getDistinctValues(e){let t=this.distinctValues.get(e);return t?Array.from(t):[]}addRows(e){for(let t of e)this.addRow(t)}addRow(e){let t=this.options.getRowId(e);if(this.rowById.has(t)){console.warn(`Row with ID ${t} already exists. Skipping.`);return}let n=this.rows.length;if(this.rows.push(e),this.rowById.set(t,n),this.updateDistinctValuesForRow(e,`add`),this.sortModel.length>0&&this.computeRowHashes(n,e),this.sortModel.length>0){let e=this.binarySearchInsertPosition(n);this.sortedIndices.splice(e,0,n)}else this.sortedIndices.push(n);this.rowPassesFilter(e)&&this.filteredIndices.add(n)}removeRows(e){let t=[];for(let n of e){let e=this.rowById.get(n);e!==void 0&&t.push(e)}if(t.length!==0){t.sort((e,t)=>t-e);for(let e of t)this.removeRowByIndex(e)}}removeRowByIndex(e){let t=this.rows[e];if(!t)return;let n=this.options.getRowId(t);this.updateDistinctValuesForRow(t,`remove`);let r=this.sortedIndices.indexOf(e);r!==-1&&this.sortedIndices.splice(r,1),this.filteredIndices.delete(e),this.rowSortCache.delete(e),this.rowById.delete(n),this.rows.splice(e,1),this.reindexAfterRemoval(e)}reindexAfterRemoval(e){for(let[t,n]of this.rowById.entries())n>e&&this.rowById.set(t,n-1);for(let t=0;t<this.sortedIndices.length;t++)this.sortedIndices[t]>e&&this.sortedIndices[t]--;let t=new Set;for(let n of this.filteredIndices)n>e?t.add(n-1):t.add(n);this.filteredIndices=t;let n=new Map;for(let[t,r]of this.rowSortCache)t>e?n.set(t-1,r):n.set(t,r);this.rowSortCache=n}updateCell(e,t,n){let r=this.rowById.get(e);if(r===void 0){console.warn(`Row with ID ${e} not found.`);return}let i=this.rows[r],a=this.options.getFieldValue(i,t);if(B(i,t,n),this.updateDistinctValueForField(t,a,n),this.sortModel.some(e=>e.colId===t)&&this.sortModel.length>0){this.computeRowHashes(r,i);let e=this.sortedIndices.indexOf(r);e!==-1&&this.sortedIndices.splice(e,1);let t=this.binarySearchInsertPosition(r);this.sortedIndices.splice(t,0,r)}t in this.filterModel&&(this.rowPassesFilter(i)?this.filteredIndices.add(r):this.filteredIndices.delete(r))}updateRow(e,t){for(let[n,r]of Object.entries(t))this.updateCell(e,n,r)}setSortModel(e){let t=JSON.stringify(e);t!==this.sortModelHash&&(this.sortModelHash=t,this.sortModel=[...e],this.rebuildHashCache(),this.rebuildSortedIndices())}getSortModel(){return[...this.sortModel]}setFilterModel(e){JSON.stringify(e)!==JSON.stringify(this.filterModel)&&(this.filterModel={...e},this.rebuildFilteredIndices())}getFilterModel(){return{...this.filterModel}}rebuildSortedIndices(){this.sortedIndices=Array.from({length:this.rows.length},(e,t)=>t),this.sortModel.length!==0&&this.sortedIndices.sort((e,t)=>this.compareRows(e,t))}rebuildHashCache(){if(this.rowSortCache.clear(),this.sortModel.length!==0)for(let e=0;e<this.rows.length;e++)this.computeRowHashes(e,this.rows[e])}computeRowHashes(e,t){if(this.sortModel.length===0)return;let n=_e(t,{sortModel:this.sortModel,sortModelHash:this.sortModelHash,getFieldValue:this.options.getFieldValue}),r=this.rowSortCache.get(e);r||(r={hashes:new Map},this.rowSortCache.set(e,r)),r.hashes.set(this.sortModelHash,n)}compareRows(e,t){let n=this.rowSortCache.get(e),r=this.rowSortCache.get(t),i=n?.hashes.get(this.sortModelHash),a=r?.hashes.get(this.sortModelHash),o=ve(i,a,this.sortModel);return o===null?ye(this.rows[e],this.rows[t],this.sortModel,this.options.getFieldValue):o}binarySearchInsertPosition(e){let t=0,n=this.sortedIndices.length;for(;t<n;){let r=t+n>>>1,i=this.sortedIndices[r];this.compareRows(e,i)>0?t=r+1:n=r}return t}rebuildFilteredIndices(){if(this.filteredIndices.clear(),Object.entries(this.filterModel).filter(([,e])=>e!=null).length!==0)for(let e=0;e<this.rows.length;e++)this.rowPassesFilter(this.rows[e])&&this.filteredIndices.add(e)}rowPassesFilter(e){return L(e,this.filterModel,this.options.getFieldValue)}getVisibleIndices(){return Object.entries(this.filterModel).filter(([,e])=>e!=null).length>0?this.sortedIndices.filter(e=>this.filteredIndices.has(e)):this.sortedIndices}rebuildDistinctValues(){this.distinctValues.clear();for(let e of this.rows)this.updateDistinctValuesForRow(e,`add`)}updateDistinctValuesForRow(e,t){if(!(typeof e!=`object`||!e)){for(let[n,r]of Object.entries(e))if(r!=null&&t===`add`){let e=this.distinctValues.get(n);if(e||(e=new Set,this.distinctValues.set(n,e)),Array.isArray(r))for(let t of r)t!=null&&e.add(t);else e.add(r)}}}updateDistinctValueForField(e,t,n){if(n!=null){let t=this.distinctValues.get(e);if(t||(t=new Set,this.distinctValues.set(e,t)),Array.isArray(n))for(let e of n)e!=null&&t.add(e);else t.add(n)}}},G=class{queue=[];debounceTimer=null;pendingPromise=null;options;constructor(e){this.options=e}add(e){e.length!==0&&(this.queue.push({type:`ADD`,rows:e}),this.scheduleProcessing())}remove(e){e.length!==0&&(this.queue.push({type:`REMOVE`,rowIds:e}),this.scheduleProcessing())}updateCell(e,t,n){this.queue.push({type:`UPDATE_CELL`,rowId:e,field:t,value:n}),this.scheduleProcessing()}updateRow(e,t){Object.keys(t).length!==0&&(this.queue.push({type:`UPDATE_ROW`,rowId:e,data:t}),this.scheduleProcessing())}flush(){return this.queue.length===0?Promise.resolve():(this.debounceTimer!==null&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.pendingPromise?new Promise((e,t)=>{let n=this.pendingPromise,r=n.resolve,i=n.reject;n.resolve=()=>{r(),e()},n.reject=e=>{i(e),t(e)}}):new Promise((e,t)=>{this.pendingPromise={resolve:e,reject:t},this.processQueue()}))}hasPending(){return this.queue.length>0}getPendingCount(){return this.queue.length}clear(){this.queue=[],this.debounceTimer!==null&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.pendingPromise&&=(this.pendingPromise.resolve(),null)}scheduleProcessing(){if(this.options.debounceMs===0){this.processQueue();return}this.debounceTimer===null&&(this.debounceTimer=setTimeout(()=>{this.debounceTimer=null,this.processQueue()},this.options.debounceMs))}processQueue(){if(this.queue.length===0){this.pendingPromise&&=(this.pendingPromise.resolve(),null);return}let e=this.queue;this.queue=[];let t={added:0,removed:0,updated:0};try{for(let n of e)switch(n.type){case`ADD`:this.options.store.addRows(n.rows),t.added+=n.rows.length;break;case`REMOVE`:this.options.store.removeRows(n.rowIds),t.removed+=n.rowIds.length;break;case`UPDATE_CELL`:this.options.store.updateCell(n.rowId,n.field,n.value),t.updated++;break;case`UPDATE_ROW`:this.options.store.updateRow(n.rowId,n.data),t.updated++;break}this.options.onProcessed&&this.options.onProcessed(t),this.pendingPromise&&=(this.pendingPromise.resolve(),null)}catch(e){this.pendingPromise&&=(this.pendingPromise.reject(e instanceof Error?e:Error(String(e))),null)}}};function be(e,t){let{getRowId:n,getFieldValue:r,debounceMs:i=50,onTransactionProcessed:a}=t,o=new W(e,{getRowId:n,getFieldValue:r}),s=new Set,c=new G({debounceMs:i,store:o,onProcessed:e=>{a?.(e);for(let t of s)t(e)}});return{async fetch(e){return c.hasPending()&&await c.flush(),o.query(e)},addRows(e){c.add(e)},removeRows(e){c.remove(e)},updateCell(e,t,n){c.updateCell(e,t,n)},updateRow(e,t){c.updateRow(e,t)},async flushTransactions(){await c.flush()},hasPendingTransactions(){return c.hasPending()},getDistinctValues(e){return o.getDistinctValues(e)},getRowById(e){return o.getRowById(e)},getTotalRowCount(){return o.getTotalRowCount()},subscribe(e){return s.add(e),()=>{s.delete(e)}},clear(){o.clear(),s.clear()}}}const K=`
4404
323
  /* =============================================================================
4405
324
  GP Grid - CSS Variables for Theming
4406
325
  ============================================================================= */
@@ -4484,11 +403,7 @@ const variablesStyles = `
4484
403
  --gp-grid-scrollbar-thumb: #373a40;
4485
404
  --gp-grid-scrollbar-thumb-hover: #4a4d52;
4486
405
  }
4487
- `;
4488
-
4489
- //#endregion
4490
- //#region src/styles/container.ts
4491
- const containerStyles = `
406
+ `,q=`
4492
407
  /* =============================================================================
4493
408
  GP Grid - Clean Flat Design
4494
409
  ============================================================================= */
@@ -4511,11 +426,7 @@ const containerStyles = `
4511
426
  outline: none;
4512
427
  border-color: var(--gp-grid-primary);
4513
428
  }
4514
- `;
4515
-
4516
- //#endregion
4517
- //#region src/styles/header.ts
4518
- const headerStyles = `
429
+ `,J=`
4519
430
  /* =============================================================================
4520
431
  Header
4521
432
  ============================================================================= */
@@ -4629,11 +540,7 @@ const headerStyles = `
4629
540
  color: var(--gp-grid-primary);
4630
541
  background-color: var(--gp-grid-primary-light);
4631
542
  }
4632
- `;
4633
-
4634
- //#endregion
4635
- //#region src/styles/cells.ts
4636
- const cellStyles = `
543
+ `,Y=`
4637
544
  /* =============================================================================
4638
545
  Data Cells
4639
546
  ============================================================================= */
@@ -4744,11 +651,7 @@ const cellStyles = `
4744
651
  .gp-grid-edit-input:focus {
4745
652
  outline: none;
4746
653
  }
4747
- `;
4748
-
4749
- //#endregion
4750
- //#region src/styles/states.ts
4751
- const statesStyles = `
654
+ `,X=`
4752
655
  /* =============================================================================
4753
656
  Loading & Error States
4754
657
  ============================================================================= */
@@ -4816,11 +719,7 @@ const statesStyles = `
4816
719
  font-size: 14px;
4817
720
  text-align: center;
4818
721
  }
4819
- `;
4820
-
4821
- //#endregion
4822
- //#region src/styles/scrollbar.ts
4823
- const scrollbarStyles = `
722
+ `,Z=`
4824
723
  /* =============================================================================
4825
724
  Scrollbar Styling
4826
725
  ============================================================================= */
@@ -4846,11 +745,7 @@ const scrollbarStyles = `
4846
745
  .gp-grid-container::-webkit-scrollbar-corner {
4847
746
  background-color: var(--gp-grid-scrollbar-track);
4848
747
  }
4849
- `;
4850
-
4851
- //#endregion
4852
- //#region src/styles/filters.ts
4853
- const filtersStyles = `
748
+ `,Q=`
4854
749
  /* =============================================================================
4855
750
  Filter Popup
4856
751
  ============================================================================= */
@@ -5161,43 +1056,5 @@ const filtersStyles = `
5161
1056
  .gp-grid-filter-btn-apply:hover {
5162
1057
  opacity: 0.9;
5163
1058
  }
5164
- `;
5165
-
5166
- //#endregion
5167
- //#region src/styles/index.ts
5168
- const STYLE_ID = "gp-grid-styles";
5169
- /**
5170
- * Combined grid styles from all modules
5171
- */
5172
- const gridStyles = [
5173
- variablesStyles,
5174
- containerStyles,
5175
- headerStyles,
5176
- cellStyles,
5177
- statesStyles,
5178
- scrollbarStyles,
5179
- filtersStyles
5180
- ].join("\n");
5181
- let stylesInjected = false;
5182
- /**
5183
- * Inject grid styles into the document head.
5184
- * This is called automatically when the Grid component mounts.
5185
- * Styles are only injected once, even if multiple Grid instances exist.
5186
- */
5187
- function injectStyles() {
5188
- if (stylesInjected) return;
5189
- if (typeof document === "undefined") return;
5190
- if (document.getElementById(STYLE_ID)) {
5191
- stylesInjected = true;
5192
- return;
5193
- }
5194
- const styleElement = document.createElement("style");
5195
- styleElement.id = STYLE_ID;
5196
- styleElement.textContent = gridStyles;
5197
- document.head.appendChild(styleElement);
5198
- stylesInjected = true;
5199
- }
5200
-
5201
- //#endregion
5202
- export { EditManager, FillManager, GridCore, HighlightManager, IndexedDataStore, InputHandler, ParallelSortManager, SelectionManager, SlotPoolManager, TransactionManager, WorkerPool, buildCellClasses, calculateColumnPositions, calculateScaledColumnPositions, cellStyles, compareValues, computeValueHash, containerStyles, createClientDataSource, createDataSourceFromArray, createMutableClientDataSource, createServerDataSource, detectBoundaryCollisions, evaluateColumnFilter, evaluateDateCondition, evaluateNumberCondition, evaluateTextCondition, filtersStyles, findColumnAtX, getFieldValue, getTotalWidth, gridStyles, headerStyles, injectStyles, isCellActive, isCellEditing, isCellInFillPreview, isCellSelected, isColumnInSelectionRange, isRowInSelectionRange, isRowVisible, isSameDay, kWayMerge, kWayMergeMultiColumn, rowPassesFilter, scrollbarStyles, setFieldValue, statesStyles, stringToSortableNumber, variablesStyles };
5203
- //# sourceMappingURL=index.js.map
1059
+ `,xe=`gp-grid-styles`,Se=[K,q,J,Y,X,Z,Q].join(`
1060
+ `);let $=!1;function Ce(){if($||typeof document>`u`)return;if(document.getElementById(xe)){$=!0;return}let e=document.createElement(`style`);e.id=xe,e.textContent=Se,document.head.appendChild(e),$=!0}export{x as EditManager,y as FillManager,re as GridCore,S as HighlightManager,W as IndexedDataStore,ee as InputHandler,O as ParallelSortManager,v as SelectionManager,b as SlotPoolManager,G as TransactionManager,w as WorkerPool,d as buildCellClasses,e as calculateColumnPositions,n as calculateScaledColumnPositions,Y as cellStyles,H as compareValues,U as computeValueHash,q as containerStyles,R as createClientDataSource,he as createDataSourceFromArray,be as createMutableClientDataSource,ge as createServerDataSource,ce as detectBoundaryCollisions,I as evaluateColumnFilter,P as evaluateDateCondition,N as evaluateNumberCondition,M as evaluateTextCondition,Q as filtersStyles,r as findColumnAtX,z as getFieldValue,t as getTotalWidth,Se as gridStyles,J as headerStyles,Ce as injectStyles,s as isCellActive,l as isCellEditing,u as isCellInFillPreview,o as isCellSelected,p as isColumnInSelectionRange,f as isRowInSelectionRange,c as isRowVisible,j as isSameDay,E as kWayMerge,D as kWayMergeMultiColumn,L as rowPassesFilter,Z as scrollbarStyles,B as setFieldValue,X as statesStyles,V as stringToSortableNumber,K as variablesStyles};