@gp-grid/core 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/index.d.ts +40 -2
- package/dist/index.js +43 -4154
- package/package.json +9 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2438 +1,5 @@
|
|
|
1
|
-
|
|
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))}}},ee=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}}}},y=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))}},b=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}},x=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;getRowId;onCellValueChanged;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;_isDataLoading=!1;constructor(e){if(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.getRowId=e.getRowId,this.onCellValueChanged=e.onCellValueChanged,this.onCellValueChanged&&!this.getRowId)throw Error(`getRowId is required when onCellValueChanged is provided`);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 ee({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 y({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 b({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=>this.clearSelectionIfInvalid(e)}),this.rowMutation.onInstruction(e=>this.emit(e)),this.input=new x(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._isDataLoading=!0,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)})}finally{this._isDataLoading=!1}}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){if(!this._isDataLoading)return this.sortFilter.setSort(e,t,n)}async setFilter(e,t){if(!this._isDataLoading)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._isDataLoading||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];if(!i)return;let a=this.onCellValueChanged?m(r,i.field):void 0;h(r,i.field,n),this.onCellValueChanged&&this.onCellValueChanged({rowId:this.getRowId(r),colIndex:t,field:i.field,oldValue:a,newValue:n,rowData:r})}clearSelectionIfInvalid(e){let t=this.selection.getActiveCell();t&&t.row>=e&&this.selection.clearSelection()}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})}async refreshFromTransaction(){let e=this.getVisibleRowRange(),t=Math.max(0,e.start-this.overscan),n=e.end+this.overscan,r=this.sortFilter.getSortModel(),i=this.sortFilter.getFilterModel(),a=await this.dataSource.fetch({pagination:{pageIndex:0,pageSize:n+1},sort:r.length>0?r:void 0,filter:Object.keys(i).length>0?i:void 0});this.totalRows=a.totalRows;for(let e=t;e<Math.min(n+1,a.rows.length);e++){let t=a.rows[e];t!==void 0&&this.cachedRows.set(e,t)}this.highlight?.clearAllCaches(),this.slotPool.refreshAllSlots(),this.emitContentSize(),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.editManager.getState()&&this.editManager.cancel(),this.dataSource=e,await this.refresh(),this.clearSelectionIfInvalid(this.totalRows)}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 A(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 j(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 le(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 M(e,t,n){return[...e].sort((e,r)=>{for(let{colId:i,direction:a}of t){let t=le(n(e,i),n(r,i));if(t!==0)return a===`asc`?t:-t}return 0})}function N(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()&&e.getDate()===t.getDate()}function ue(e){return e==null||e===``||Array.isArray(e)&&e.length===0}function P(e,t){let n=ue(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 F(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 I(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 N(r,i);case`!=`:return!N(r,i);case`>`:return o>s;case`<`:return o<s;case`between`:return o>=s&&o<=c;default:return!0}}function L(e,t){switch(t.type){case`text`:return P(e,t);case`number`:return F(e,t);case`date`:return I(e,t);default:return!0}}function R(e,t){if(!t.conditions||t.conditions.length===0)return!0;let n=t.conditions[0];if(!n)return!0;let r=L(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=L(e,a);o===`and`?r&&=s:r||=s}return r}function z(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(!R(n(e,t),i))return!1;return!0}function de(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(!R(r,i))return!1}return!0})}function fe(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={}){let{getFieldValue:n=fe,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=de(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=A(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=>j(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=>j(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=M(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 pe(e){return B(e)}function me(e){return{async fetch(t){return e(t)}}}function V(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 H(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 U(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 W(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 G(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 U(e);let t=Number(e);return isNaN(t)?0:t}function he(e,t){let n=[];for(let r of t.sortModel){let i=G(t.getFieldValue(e,r.colId));n.push(i)}return n}function ge(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 _e(e,t,n,r){for(let{colId:i,direction:a}of n){let n=W(r(e,i),r(t,i));if(n!==0)return a===`asc`?n:-n}return 0}var K=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??V},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}getAllRows(){return[...this.rows]}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(H(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=he(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=ge(i,a,this.sortModel);return o===null?_e(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 z(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)}}},q=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 ve(e,t){let{getRowId:n,getFieldValue:r,debounceMs:i=50,onTransactionProcessed:a,useWorker:o=!0,parallelSort:s}=t,c=new K(e,{getRowId:n,getFieldValue:r??((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})}),l=new Set,u=g().emit,d=o?new O(s===!1?{maxWorkers:1}:s):null,f=new q({debounceMs:i,store:c,onProcessed:e=>{a?.(e);for(let t of l)t(e)}});return{async fetch(e){if(f.hasPending()){u({type:`DATA_LOADING`});try{await f.flush()}finally{u({type:`DATA_LOADED`,totalRows:c.getTotalRowCount()})}}let t=c.getAllRows(),n=r??((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});if(e.filter&&Object.keys(e.filter).length>0&&(t=de(t,e.filter,n)),e.sort&&e.sort.length>0)if(d&&d.isAvailable()&&t.length>=2e5){u({type:`DATA_LOADING`});try{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=A(a);for(let e=0;e<3;e++)o[e].push(s[e])}let s=o.map(e=>new Float64Array(e));r=await d.sortStringHashes(s,a,e)}else{let e=t.map(e=>j(n(e,i)));r=await d.sortIndices(e,a)}}else{let i=[],a=[];for(let{colId:r,direction:o}of e.sort){let e=t.map(e=>j(n(e,r)));i.push(e),a.push(o)}r=await d.sortMultiColumn(i,a)}let i=Array(t.length);for(let e=0;e<r.length;e++){let n=r[e];i[e]=t[n]}t=i}finally{u({type:`DATA_LOADED`,totalRows:t.length})}}else t=M(t,e.sort,n);let i=t.length,{pageIndex:a,pageSize:o}=e.pagination,s=a*o;return{rows:t.slice(s,s+o),totalRows:i}},addRows(e){f.add(e)},removeRows(e){f.remove(e)},updateCell(e,t,n){f.updateCell(e,t,n)},updateRow(e,t){f.updateRow(e,t)},async flushTransactions(){await f.flush()},hasPendingTransactions(){return f.hasPending()},getDistinctValues(e){return c.getDistinctValues(e)},getRowById(e){return c.getRowById(e)},getTotalRowCount(){return c.getTotalRowCount()},subscribe(e){return l.add(e),()=>{l.delete(e)}},clear(){c.clear(),l.clear()}}}const J=`
|
|
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
|
+
`,Y=`
|
|
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
|
+
`,X=`
|
|
4519
430
|
/* =============================================================================
|
|
4520
431
|
Header
|
|
4521
432
|
============================================================================= */
|
|
@@ -4629,11 +540,14 @@ const headerStyles = `
|
|
|
4629
540
|
color: var(--gp-grid-primary);
|
|
4630
541
|
background-color: var(--gp-grid-primary-light);
|
|
4631
542
|
}
|
|
4632
|
-
`;
|
|
4633
543
|
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
544
|
+
/* Disabled header state during data loading */
|
|
545
|
+
.gp-grid-header--loading .gp-grid-header-cell {
|
|
546
|
+
pointer-events: none;
|
|
547
|
+
opacity: 0.6;
|
|
548
|
+
cursor: default;
|
|
549
|
+
}
|
|
550
|
+
`,Z=`
|
|
4637
551
|
/* =============================================================================
|
|
4638
552
|
Data Cells
|
|
4639
553
|
============================================================================= */
|
|
@@ -4744,18 +658,38 @@ const cellStyles = `
|
|
|
4744
658
|
.gp-grid-edit-input:focus {
|
|
4745
659
|
outline: none;
|
|
4746
660
|
}
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
//#endregion
|
|
4750
|
-
//#region src/styles/states.ts
|
|
4751
|
-
const statesStyles = `
|
|
661
|
+
`,Q=`
|
|
4752
662
|
/* =============================================================================
|
|
4753
663
|
Loading & Error States
|
|
4754
664
|
============================================================================= */
|
|
4755
665
|
|
|
666
|
+
/* Sticky wrapper: stays pinned at viewport top, takes no layout space */
|
|
667
|
+
.gp-grid-loading-anchor {
|
|
668
|
+
position: sticky;
|
|
669
|
+
top: 0;
|
|
670
|
+
left: 0;
|
|
671
|
+
height: 0;
|
|
672
|
+
z-index: 900;
|
|
673
|
+
overflow: visible;
|
|
674
|
+
pointer-events: none;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* Semi-transparent overlay covering the visible viewport area */
|
|
678
|
+
.gp-grid-loading-overlay {
|
|
679
|
+
position: absolute;
|
|
680
|
+
top: 0;
|
|
681
|
+
left: 0;
|
|
682
|
+
pointer-events: none;
|
|
683
|
+
background-color: var(--gp-grid-loading-overlay-bg, rgba(255, 255, 255, 0.4));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.gp-grid-container--dark .gp-grid-loading-overlay {
|
|
687
|
+
background-color: var(--gp-grid-loading-overlay-bg, rgba(0, 0, 0, 0.3));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/* Loading indicator centered in the visible viewport */
|
|
4756
691
|
.gp-grid-loading {
|
|
4757
692
|
position: absolute;
|
|
4758
|
-
top: 50%;
|
|
4759
693
|
left: 50%;
|
|
4760
694
|
transform: translate(-50%, -50%);
|
|
4761
695
|
padding: 12px 20px;
|
|
@@ -4769,6 +703,7 @@ const statesStyles = `
|
|
|
4769
703
|
display: flex;
|
|
4770
704
|
align-items: center;
|
|
4771
705
|
gap: 10px;
|
|
706
|
+
pointer-events: auto;
|
|
4772
707
|
}
|
|
4773
708
|
|
|
4774
709
|
.gp-grid-loading-spinner {
|
|
@@ -4816,11 +751,7 @@ const statesStyles = `
|
|
|
4816
751
|
font-size: 14px;
|
|
4817
752
|
text-align: center;
|
|
4818
753
|
}
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
//#endregion
|
|
4822
|
-
//#region src/styles/scrollbar.ts
|
|
4823
|
-
const scrollbarStyles = `
|
|
754
|
+
`,ye=`
|
|
4824
755
|
/* =============================================================================
|
|
4825
756
|
Scrollbar Styling
|
|
4826
757
|
============================================================================= */
|
|
@@ -4846,11 +777,7 @@ const scrollbarStyles = `
|
|
|
4846
777
|
.gp-grid-container::-webkit-scrollbar-corner {
|
|
4847
778
|
background-color: var(--gp-grid-scrollbar-track);
|
|
4848
779
|
}
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
//#endregion
|
|
4852
|
-
//#region src/styles/filters.ts
|
|
4853
|
-
const filtersStyles = `
|
|
780
|
+
`,be=`
|
|
4854
781
|
/* =============================================================================
|
|
4855
782
|
Filter Popup
|
|
4856
783
|
============================================================================= */
|
|
@@ -5161,43 +1088,5 @@ const filtersStyles = `
|
|
|
5161
1088
|
.gp-grid-filter-btn-apply:hover {
|
|
5162
1089
|
opacity: 0.9;
|
|
5163
1090
|
}
|
|
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
|
|
1091
|
+
`,xe=`gp-grid-styles`,Se=[J,Y,X,Z,Q,ye,be].join(`
|
|
1092
|
+
`);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{b as EditManager,ee as FillManager,re as GridCore,S as HighlightManager,K as IndexedDataStore,x as InputHandler,O as ParallelSortManager,v as SelectionManager,y as SlotPoolManager,q as TransactionManager,w as WorkerPool,d as buildCellClasses,e as calculateColumnPositions,n as calculateScaledColumnPositions,Z as cellStyles,W as compareValues,G as computeValueHash,Y as containerStyles,B as createClientDataSource,pe as createDataSourceFromArray,ve as createMutableClientDataSource,me as createServerDataSource,ce as detectBoundaryCollisions,R as evaluateColumnFilter,I as evaluateDateCondition,F as evaluateNumberCondition,P as evaluateTextCondition,be as filtersStyles,r as findColumnAtX,V as getFieldValue,t as getTotalWidth,Se as gridStyles,X 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,N as isSameDay,E as kWayMerge,D as kWayMergeMultiColumn,z as rowPassesFilter,ye as scrollbarStyles,H as setFieldValue,Q as statesStyles,U as stringToSortableNumber,J as variablesStyles};
|