@alaarab/ogrid-angular 2.0.2

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.
@@ -0,0 +1,1268 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Injectable, signal, computed, effect, DestroyRef, inject } from '@angular/core';
8
+ import { flattenColumns, getDataGridStatusBarConfig, parseValue, computeAggregations, getCellValue, normalizeSelectionRange, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, } from '@alaarab/ogrid-core';
9
+ /**
10
+ * Single orchestration service for DataGridTable. Takes grid props,
11
+ * returns all derived state and handlers so Angular UI packages can be thin view layers.
12
+ *
13
+ * Port of React's useDataGridState hook.
14
+ */
15
+ let DataGridStateService = class DataGridStateService {
16
+ constructor() {
17
+ this.destroyRef = inject(DestroyRef);
18
+ // --- Input signals ---
19
+ this.props = signal(null);
20
+ this.wrapperEl = signal(null);
21
+ // --- Internal state ---
22
+ this.editingCellSig = signal(null);
23
+ this.pendingEditorValueSig = signal(undefined);
24
+ this.activeCellSig = signal(null);
25
+ this.selectionRangeSig = signal(null);
26
+ this.isDraggingSig = signal(false);
27
+ this.contextMenuPositionSig = signal(null);
28
+ this.internalSelectedRows = signal(new Set());
29
+ this.popoverAnchorElSig = signal(null);
30
+ this.containerWidthSig = signal(0);
31
+ this.columnSizingOverridesSig = signal({});
32
+ // Clipboard state
33
+ this.cutRangeSig = signal(null);
34
+ this.copyRangeSig = signal(null);
35
+ this.internalClipboard = null;
36
+ // Undo/redo state
37
+ this.undoHistory = [];
38
+ this.redoStack = [];
39
+ this.batch = null;
40
+ this.undoLengthSig = signal(0);
41
+ this.redoLengthSig = signal(0);
42
+ // Fill handle state
43
+ this.fillDragStart = null;
44
+ // Row selection
45
+ this.lastClickedRow = -1;
46
+ // Drag selection refs
47
+ this.dragStartPos = null;
48
+ this.dragMoved = false;
49
+ this.isDraggingRef = false;
50
+ this.liveDragRange = null;
51
+ this.rafId = 0;
52
+ this.lastMousePos = null;
53
+ this.autoScrollInterval = null;
54
+ // Window event listeners cleanup
55
+ this.windowCleanups = [];
56
+ // ResizeObserver
57
+ this.resizeObserver = null;
58
+ // --- Derived computed ---
59
+ this.propsResolved = computed(() => this.props());
60
+ this.cellSelection = computed(() => {
61
+ const p = this.props();
62
+ return p ? p.cellSelection !== false : true;
63
+ });
64
+ // Undo/redo wrapped callback
65
+ this.wrappedOnCellValueChanged = computed(() => {
66
+ const p = this.props();
67
+ const original = p?.onCellValueChanged;
68
+ if (!original)
69
+ return undefined;
70
+ return (event) => {
71
+ if (this.batch !== null) {
72
+ this.batch.push(event);
73
+ }
74
+ else {
75
+ this.undoHistory = [...this.undoHistory, [event]].slice(-100);
76
+ this.redoStack = [];
77
+ this.undoLengthSig.set(this.undoHistory.length);
78
+ this.redoLengthSig.set(0);
79
+ }
80
+ original(event);
81
+ };
82
+ });
83
+ this.flatColumnsRaw = computed(() => {
84
+ const p = this.props();
85
+ if (!p)
86
+ return [];
87
+ return flattenColumns(p.columns);
88
+ });
89
+ this.flatColumns = computed(() => {
90
+ const raw = this.flatColumnsRaw();
91
+ const p = this.props();
92
+ const pinnedColumns = p?.pinnedColumns;
93
+ if (!pinnedColumns || Object.keys(pinnedColumns).length === 0)
94
+ return raw;
95
+ return raw.map((col) => {
96
+ const override = pinnedColumns[col.columnId];
97
+ if (override && col.pinned !== override)
98
+ return { ...col, pinned: override };
99
+ return col;
100
+ });
101
+ });
102
+ this.visibleCols = computed(() => {
103
+ const p = this.props();
104
+ if (!p)
105
+ return [];
106
+ const flatCols = this.flatColumns();
107
+ const filtered = p.visibleColumns
108
+ ? flatCols.filter((c) => p.visibleColumns.has(c.columnId))
109
+ : flatCols;
110
+ const order = p.columnOrder;
111
+ if (!order?.length)
112
+ return filtered;
113
+ return [...filtered].sort((a, b) => {
114
+ const ia = order.indexOf(a.columnId);
115
+ const ib = order.indexOf(b.columnId);
116
+ if (ia === -1 && ib === -1)
117
+ return 0;
118
+ if (ia === -1)
119
+ return 1;
120
+ if (ib === -1)
121
+ return -1;
122
+ return ia - ib;
123
+ });
124
+ });
125
+ this.visibleColumnCount = computed(() => this.visibleCols().length);
126
+ this.hasCheckboxCol = computed(() => (this.props()?.rowSelection ?? 'none') === 'multiple');
127
+ this.totalColCount = computed(() => this.visibleColumnCount() + (this.hasCheckboxCol() ? 1 : 0));
128
+ this.colOffset = computed(() => this.hasCheckboxCol() ? 1 : 0);
129
+ this.rowIndexByRowId = computed(() => {
130
+ const p = this.props();
131
+ if (!p)
132
+ return new Map();
133
+ const m = new Map();
134
+ p.items.forEach((item, idx) => m.set(p.getRowId(item), idx));
135
+ return m;
136
+ });
137
+ this.selectedRowIds = computed(() => {
138
+ const p = this.props();
139
+ if (!p)
140
+ return new Set();
141
+ const controlled = p.selectedRows;
142
+ if (controlled != null) {
143
+ return controlled instanceof Set ? controlled : new Set(controlled);
144
+ }
145
+ return this.internalSelectedRows();
146
+ });
147
+ this.allSelected = computed(() => {
148
+ const p = this.props();
149
+ if (!p || p.items.length === 0)
150
+ return false;
151
+ const selected = this.selectedRowIds();
152
+ return p.items.every((item) => selected.has(p.getRowId(item)));
153
+ });
154
+ this.someSelected = computed(() => {
155
+ const p = this.props();
156
+ if (!p)
157
+ return false;
158
+ const selected = this.selectedRowIds();
159
+ return !this.allSelected() && p.items.some((item) => selected.has(p.getRowId(item)));
160
+ });
161
+ this.hasCellSelection = computed(() => this.selectionRangeSig() != null || this.activeCellSig() != null);
162
+ this.canUndo = computed(() => this.undoLengthSig() > 0);
163
+ this.canRedo = computed(() => this.redoLengthSig() > 0);
164
+ // Table layout
165
+ this.minTableWidth = computed(() => {
166
+ const checkboxW = this.hasCheckboxCol() ? CHECKBOX_COLUMN_WIDTH : 0;
167
+ return this.visibleCols().reduce((sum, c) => sum + (c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH) + CELL_PADDING, checkboxW);
168
+ });
169
+ this.desiredTableWidth = computed(() => {
170
+ const checkboxW = this.hasCheckboxCol() ? CHECKBOX_COLUMN_WIDTH : 0;
171
+ const overrides = this.columnSizingOverridesSig();
172
+ return this.visibleCols().reduce((sum, c) => {
173
+ const override = overrides[c.columnId];
174
+ const w = override
175
+ ? override.widthPx
176
+ : (c.idealWidth ?? c.defaultWidth ?? c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
177
+ return sum + Math.max(c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, w) + CELL_PADDING;
178
+ }, checkboxW);
179
+ });
180
+ this.aggregation = computed(() => {
181
+ const p = this.props();
182
+ if (!p)
183
+ return null;
184
+ return computeAggregations(p.items, this.visibleCols(), this.cellSelection() ? this.selectionRangeSig() : null);
185
+ });
186
+ this.statusBarConfig = computed(() => {
187
+ const p = this.props();
188
+ if (!p)
189
+ return null;
190
+ const base = getDataGridStatusBarConfig(p.statusBar, p.items.length, this.selectedRowIds().size);
191
+ if (!base)
192
+ return null;
193
+ return { ...base, aggregation: this.aggregation() ?? undefined };
194
+ });
195
+ this.showEmptyInGrid = computed(() => {
196
+ const p = this.props();
197
+ if (!p)
198
+ return false;
199
+ return p.items.length === 0 && !!p.emptyState && !p.isLoading;
200
+ });
201
+ // Setup window event listeners for cell selection drag
202
+ const onMove = (e) => this.onWindowMouseMove(e);
203
+ const onUp = () => this.onWindowMouseUp();
204
+ window.addEventListener('mousemove', onMove, true);
205
+ window.addEventListener('mouseup', onUp, true);
206
+ this.windowCleanups.push(() => {
207
+ window.removeEventListener('mousemove', onMove, true);
208
+ window.removeEventListener('mouseup', onUp, true);
209
+ });
210
+ // Initialize column sizing overrides from initial widths
211
+ effect(() => {
212
+ const p = this.props();
213
+ if (p?.initialColumnWidths) {
214
+ const result = {};
215
+ for (const [id, width] of Object.entries(p.initialColumnWidths)) {
216
+ result[id] = { widthPx: width };
217
+ }
218
+ this.columnSizingOverridesSig.set(result);
219
+ }
220
+ });
221
+ // Container width measurement via ResizeObserver
222
+ effect(() => {
223
+ const el = this.wrapperEl();
224
+ if (this.resizeObserver) {
225
+ this.resizeObserver.disconnect();
226
+ this.resizeObserver = null;
227
+ }
228
+ if (!el)
229
+ return;
230
+ const measure = () => {
231
+ const rect = el.getBoundingClientRect();
232
+ const cs = window.getComputedStyle(el);
233
+ const borderX = (parseFloat(cs.borderLeftWidth || '0') || 0) +
234
+ (parseFloat(cs.borderRightWidth || '0') || 0);
235
+ this.containerWidthSig.set(Math.max(0, rect.width - borderX));
236
+ };
237
+ this.resizeObserver = new ResizeObserver(measure);
238
+ this.resizeObserver.observe(el);
239
+ measure();
240
+ });
241
+ // Cleanup on destroy
242
+ this.destroyRef.onDestroy(() => {
243
+ this.windowCleanups.forEach((fn) => fn());
244
+ if (this.rafId)
245
+ cancelAnimationFrame(this.rafId);
246
+ if (this.autoScrollInterval)
247
+ clearInterval(this.autoScrollInterval);
248
+ if (this.resizeObserver)
249
+ this.resizeObserver.disconnect();
250
+ });
251
+ // Clean up column sizing overrides for removed columns
252
+ effect(() => {
253
+ const colIds = new Set(this.flatColumns().map((c) => c.columnId));
254
+ this.columnSizingOverridesSig.update((prev) => {
255
+ const next = { ...prev };
256
+ let changed = false;
257
+ for (const id of Object.keys(next)) {
258
+ if (!colIds.has(id)) {
259
+ delete next[id];
260
+ changed = true;
261
+ }
262
+ }
263
+ return changed ? next : prev;
264
+ });
265
+ });
266
+ }
267
+ // --- Row selection methods ---
268
+ updateSelection(newSelectedIds) {
269
+ const p = this.props();
270
+ if (!p)
271
+ return;
272
+ if (p.selectedRows === undefined) {
273
+ this.internalSelectedRows.set(newSelectedIds);
274
+ }
275
+ p.onSelectionChange?.({
276
+ selectedRowIds: Array.from(newSelectedIds),
277
+ selectedItems: p.items.filter((item) => newSelectedIds.has(p.getRowId(item))),
278
+ });
279
+ }
280
+ handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey) {
281
+ const p = this.props();
282
+ if (!p)
283
+ return;
284
+ const rowSelection = p.rowSelection ?? 'none';
285
+ if (rowSelection === 'single') {
286
+ this.updateSelection(checked ? new Set([rowId]) : new Set());
287
+ this.lastClickedRow = rowIndex;
288
+ return;
289
+ }
290
+ const next = new Set(this.selectedRowIds());
291
+ if (shiftKey && this.lastClickedRow >= 0 && this.lastClickedRow !== rowIndex) {
292
+ const start = Math.min(this.lastClickedRow, rowIndex);
293
+ const end = Math.max(this.lastClickedRow, rowIndex);
294
+ for (let i = start; i <= end; i++) {
295
+ if (i < p.items.length) {
296
+ const id = p.getRowId(p.items[i]);
297
+ if (checked)
298
+ next.add(id);
299
+ else
300
+ next.delete(id);
301
+ }
302
+ }
303
+ }
304
+ else {
305
+ if (checked)
306
+ next.add(rowId);
307
+ else
308
+ next.delete(rowId);
309
+ }
310
+ this.lastClickedRow = rowIndex;
311
+ this.updateSelection(next);
312
+ }
313
+ handleSelectAll(checked) {
314
+ const p = this.props();
315
+ if (!p)
316
+ return;
317
+ if (checked) {
318
+ this.updateSelection(new Set(p.items.map((item) => p.getRowId(item))));
319
+ }
320
+ else {
321
+ this.updateSelection(new Set());
322
+ }
323
+ }
324
+ // --- Cell editing ---
325
+ setEditingCell(cell) {
326
+ this.editingCellSig.set(cell);
327
+ }
328
+ setPendingEditorValue(value) {
329
+ this.pendingEditorValueSig.set(value);
330
+ }
331
+ setActiveCell(cell) {
332
+ const prev = this.activeCellSig();
333
+ if (prev === cell)
334
+ return;
335
+ if (prev && cell && prev.rowIndex === cell.rowIndex && prev.columnIndex === cell.columnIndex)
336
+ return;
337
+ this.activeCellSig.set(cell);
338
+ }
339
+ setSelectionRange(range) {
340
+ const prev = this.selectionRangeSig();
341
+ if (prev === range)
342
+ return;
343
+ if (prev && range &&
344
+ prev.startRow === range.startRow && prev.endRow === range.endRow &&
345
+ prev.startCol === range.startCol && prev.endCol === range.endCol)
346
+ return;
347
+ this.selectionRangeSig.set(range);
348
+ }
349
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
350
+ const col = this.visibleCols().find((c) => c.columnId === columnId);
351
+ if (col) {
352
+ const result = parseValue(newValue, oldValue, item, col);
353
+ if (!result.valid) {
354
+ this.editingCellSig.set(null);
355
+ this.popoverAnchorElSig.set(null);
356
+ this.pendingEditorValueSig.set(undefined);
357
+ return;
358
+ }
359
+ newValue = result.value;
360
+ }
361
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
362
+ onCellValueChanged?.({ item, columnId, oldValue, newValue, rowIndex });
363
+ this.editingCellSig.set(null);
364
+ this.popoverAnchorElSig.set(null);
365
+ this.pendingEditorValueSig.set(undefined);
366
+ const p = this.props();
367
+ if (p && rowIndex < p.items.length - 1) {
368
+ this.setActiveCell({ rowIndex: rowIndex + 1, columnIndex: globalColIndex });
369
+ }
370
+ }
371
+ cancelPopoverEdit() {
372
+ this.editingCellSig.set(null);
373
+ this.popoverAnchorElSig.set(null);
374
+ this.pendingEditorValueSig.set(undefined);
375
+ }
376
+ // --- Cell selection / mouse handling ---
377
+ handleCellMouseDown(e, rowIndex, globalColIndex) {
378
+ if (e.button !== 0)
379
+ return;
380
+ const wrapper = this.wrapperEl();
381
+ wrapper?.focus({ preventScroll: true });
382
+ this.clearClipboardRanges();
383
+ const colOffset = this.colOffset();
384
+ if (globalColIndex < colOffset)
385
+ return;
386
+ e.preventDefault();
387
+ const dataColIndex = globalColIndex - colOffset;
388
+ const currentRange = this.selectionRangeSig();
389
+ if (e.shiftKey && currentRange != null) {
390
+ this.setSelectionRange(normalizeSelectionRange({
391
+ startRow: currentRange.startRow,
392
+ startCol: currentRange.startCol,
393
+ endRow: rowIndex,
394
+ endCol: dataColIndex,
395
+ }));
396
+ this.setActiveCell({ rowIndex, columnIndex: globalColIndex });
397
+ }
398
+ else {
399
+ this.dragStartPos = { row: rowIndex, col: dataColIndex };
400
+ this.dragMoved = false;
401
+ const initial = {
402
+ startRow: rowIndex, startCol: dataColIndex,
403
+ endRow: rowIndex, endCol: dataColIndex,
404
+ };
405
+ this.setSelectionRange(initial);
406
+ this.liveDragRange = initial;
407
+ this.setActiveCell({ rowIndex, columnIndex: globalColIndex });
408
+ this.isDraggingRef = true;
409
+ }
410
+ }
411
+ handleSelectAllCells() {
412
+ const p = this.props();
413
+ if (!p)
414
+ return;
415
+ const rowCount = p.items.length;
416
+ const visibleColCount = this.visibleColumnCount();
417
+ if (rowCount === 0 || visibleColCount === 0)
418
+ return;
419
+ this.setSelectionRange({
420
+ startRow: 0, startCol: 0,
421
+ endRow: rowCount - 1, endCol: visibleColCount - 1,
422
+ });
423
+ this.setActiveCell({ rowIndex: 0, columnIndex: this.colOffset() });
424
+ }
425
+ // --- Context menu ---
426
+ setContextMenuPosition(pos) {
427
+ this.contextMenuPositionSig.set(pos);
428
+ }
429
+ handleCellContextMenu(e) {
430
+ e.preventDefault?.();
431
+ this.contextMenuPositionSig.set({ x: e.clientX, y: e.clientY });
432
+ }
433
+ closeContextMenu() {
434
+ this.contextMenuPositionSig.set(null);
435
+ }
436
+ // --- Clipboard ---
437
+ handleCopy() {
438
+ const p = this.props();
439
+ if (!p)
440
+ return;
441
+ const range = this.getEffectiveRange();
442
+ if (range == null)
443
+ return;
444
+ const norm = normalizeSelectionRange(range);
445
+ const visibleCols = this.visibleCols();
446
+ const rows = [];
447
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
448
+ const cells = [];
449
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
450
+ if (r >= p.items.length || c >= visibleCols.length)
451
+ break;
452
+ const item = p.items[r];
453
+ const col = visibleCols[c];
454
+ const raw = getCellValue(item, col);
455
+ const val = col.valueFormatter ? col.valueFormatter(raw, item) : raw;
456
+ cells.push(val != null && val !== '' ? String(val).replace(/\t/g, ' ').replace(/\n/g, ' ') : '');
457
+ }
458
+ rows.push(cells.join('\t'));
459
+ }
460
+ const tsv = rows.join('\r\n');
461
+ this.internalClipboard = tsv;
462
+ this.copyRangeSig.set(norm);
463
+ void navigator.clipboard.writeText(tsv).catch(() => { });
464
+ }
465
+ handleCut() {
466
+ const p = this.props();
467
+ if (!p || this.props()?.editable === false)
468
+ return;
469
+ const range = this.getEffectiveRange();
470
+ if (range == null || !this.wrappedOnCellValueChanged())
471
+ return;
472
+ const norm = normalizeSelectionRange(range);
473
+ this.cutRangeSig.set(norm);
474
+ this.copyRangeSig.set(null);
475
+ this.handleCopy();
476
+ this.copyRangeSig.set(null);
477
+ }
478
+ async handlePaste() {
479
+ const p = this.props();
480
+ if (!p || p.editable === false)
481
+ return;
482
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
483
+ if (!onCellValueChanged)
484
+ return;
485
+ let text;
486
+ try {
487
+ text = await navigator.clipboard.readText();
488
+ }
489
+ catch {
490
+ text = '';
491
+ }
492
+ if (!text.trim() && this.internalClipboard != null) {
493
+ text = this.internalClipboard;
494
+ }
495
+ if (!text.trim())
496
+ return;
497
+ const norm = this.getEffectiveRange();
498
+ const anchorRow = norm ? norm.startRow : 0;
499
+ const anchorCol = norm ? norm.startCol : 0;
500
+ const visibleCols = this.visibleCols();
501
+ const lines = text.split(/\r?\n/).filter((l) => l.length > 0);
502
+ this.beginBatch();
503
+ for (let r = 0; r < lines.length; r++) {
504
+ const cells = lines[r].split('\t');
505
+ for (let c = 0; c < cells.length; c++) {
506
+ const targetRow = anchorRow + r;
507
+ const targetCol = anchorCol + c;
508
+ if (targetRow >= p.items.length || targetCol >= visibleCols.length)
509
+ continue;
510
+ const item = p.items[targetRow];
511
+ const col = visibleCols[targetCol];
512
+ const colEditable = col.editable === true || (typeof col.editable === 'function' && col.editable(item));
513
+ if (!colEditable)
514
+ continue;
515
+ const rawValue = cells[c] ?? '';
516
+ const oldValue = getCellValue(item, col);
517
+ const result = parseValue(rawValue, oldValue, item, col);
518
+ if (!result.valid)
519
+ continue;
520
+ onCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: targetRow });
521
+ }
522
+ }
523
+ const cutRange = this.cutRangeSig();
524
+ if (cutRange) {
525
+ for (let r = cutRange.startRow; r <= cutRange.endRow; r++) {
526
+ for (let c = cutRange.startCol; c <= cutRange.endCol; c++) {
527
+ if (r >= p.items.length || c >= visibleCols.length)
528
+ continue;
529
+ const item = p.items[r];
530
+ const col = visibleCols[c];
531
+ const colEditable = col.editable === true || (typeof col.editable === 'function' && col.editable(item));
532
+ if (!colEditable)
533
+ continue;
534
+ const oldValue = getCellValue(item, col);
535
+ const result = parseValue('', oldValue, item, col);
536
+ if (!result.valid)
537
+ continue;
538
+ onCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: r });
539
+ }
540
+ }
541
+ this.cutRangeSig.set(null);
542
+ }
543
+ this.endBatch();
544
+ this.copyRangeSig.set(null);
545
+ }
546
+ clearClipboardRanges() {
547
+ this.copyRangeSig.set(null);
548
+ this.cutRangeSig.set(null);
549
+ }
550
+ // --- Undo/Redo ---
551
+ beginBatch() {
552
+ this.batch = [];
553
+ }
554
+ endBatch() {
555
+ const batch = this.batch;
556
+ this.batch = null;
557
+ if (!batch || batch.length === 0)
558
+ return;
559
+ this.undoHistory = [...this.undoHistory, batch].slice(-100);
560
+ this.redoStack = [];
561
+ this.undoLengthSig.set(this.undoHistory.length);
562
+ this.redoLengthSig.set(0);
563
+ }
564
+ undo() {
565
+ const p = this.props();
566
+ const original = p?.onCellValueChanged;
567
+ if (!original || this.undoHistory.length === 0)
568
+ return;
569
+ const lastBatch = this.undoHistory[this.undoHistory.length - 1];
570
+ this.undoHistory = this.undoHistory.slice(0, -1);
571
+ this.redoStack = [...this.redoStack, lastBatch];
572
+ this.undoLengthSig.set(this.undoHistory.length);
573
+ this.redoLengthSig.set(this.redoStack.length);
574
+ for (let i = lastBatch.length - 1; i >= 0; i--) {
575
+ const ev = lastBatch[i];
576
+ original({ ...ev, oldValue: ev.newValue, newValue: ev.oldValue });
577
+ }
578
+ }
579
+ redo() {
580
+ const p = this.props();
581
+ const original = p?.onCellValueChanged;
582
+ if (!original || this.redoStack.length === 0)
583
+ return;
584
+ const nextBatch = this.redoStack[this.redoStack.length - 1];
585
+ this.redoStack = this.redoStack.slice(0, -1);
586
+ this.undoHistory = [...this.undoHistory, nextBatch];
587
+ this.redoLengthSig.set(this.redoStack.length);
588
+ this.undoLengthSig.set(this.undoHistory.length);
589
+ for (const ev of nextBatch) {
590
+ original(ev);
591
+ }
592
+ }
593
+ // --- Keyboard navigation ---
594
+ handleGridKeyDown(e) {
595
+ const p = this.props();
596
+ if (!p)
597
+ return;
598
+ const { items, getRowId } = p;
599
+ const visibleCols = this.visibleCols();
600
+ const colOffset = this.colOffset();
601
+ const hasCheckboxCol = this.hasCheckboxCol();
602
+ const visibleColumnCount = this.visibleColumnCount();
603
+ const activeCell = this.activeCellSig();
604
+ const selectionRange = this.selectionRangeSig();
605
+ const editingCell = this.editingCellSig();
606
+ const selectedRowIds = this.selectedRowIds();
607
+ const editable = p.editable;
608
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
609
+ const rowSelection = p.rowSelection ?? 'none';
610
+ const maxRowIndex = items.length - 1;
611
+ const maxColIndex = visibleColumnCount - 1 + colOffset;
612
+ if (items.length === 0)
613
+ return;
614
+ if (activeCell === null) {
615
+ if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter', 'Home', 'End'].includes(e.key)) {
616
+ this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
617
+ e.preventDefault();
618
+ }
619
+ return;
620
+ }
621
+ const { rowIndex, columnIndex } = activeCell;
622
+ const dataColIndex = columnIndex - colOffset;
623
+ const shift = e.shiftKey;
624
+ const ctrl = e.ctrlKey || e.metaKey;
625
+ const isEmptyAt = (r, c) => {
626
+ if (r < 0 || r >= items.length || c < 0 || c >= visibleCols.length)
627
+ return true;
628
+ const v = getCellValue(items[r], visibleCols[c]);
629
+ return v == null || v === '';
630
+ };
631
+ const findCtrlTarget = (pos, edge, step, isEmpty) => {
632
+ if (pos === edge)
633
+ return pos;
634
+ const next = pos + step;
635
+ if (!isEmpty(pos) && !isEmpty(next)) {
636
+ let p = next;
637
+ while (p !== edge) {
638
+ if (isEmpty(p + step))
639
+ return p;
640
+ p += step;
641
+ }
642
+ return edge;
643
+ }
644
+ let pp = next;
645
+ while (pp !== edge) {
646
+ if (!isEmpty(pp))
647
+ return pp;
648
+ pp += step;
649
+ }
650
+ return edge;
651
+ };
652
+ switch (e.key) {
653
+ case 'c':
654
+ if (ctrl) {
655
+ if (editingCell != null)
656
+ break;
657
+ e.preventDefault();
658
+ this.handleCopy();
659
+ }
660
+ break;
661
+ case 'x':
662
+ if (ctrl) {
663
+ if (editingCell != null)
664
+ break;
665
+ e.preventDefault();
666
+ this.handleCut();
667
+ }
668
+ break;
669
+ case 'v':
670
+ if (ctrl) {
671
+ if (editingCell != null)
672
+ break;
673
+ e.preventDefault();
674
+ void this.handlePaste();
675
+ }
676
+ break;
677
+ case 'ArrowDown': {
678
+ e.preventDefault();
679
+ const newRow = ctrl
680
+ ? findCtrlTarget(rowIndex, maxRowIndex, 1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
681
+ : Math.min(rowIndex + 1, maxRowIndex);
682
+ if (shift) {
683
+ this.setSelectionRange(normalizeSelectionRange({
684
+ startRow: selectionRange?.startRow ?? rowIndex,
685
+ startCol: selectionRange?.startCol ?? dataColIndex,
686
+ endRow: newRow,
687
+ endCol: selectionRange?.endCol ?? dataColIndex,
688
+ }));
689
+ }
690
+ else {
691
+ this.setSelectionRange({ startRow: newRow, startCol: dataColIndex, endRow: newRow, endCol: dataColIndex });
692
+ }
693
+ this.setActiveCell({ rowIndex: newRow, columnIndex });
694
+ break;
695
+ }
696
+ case 'ArrowUp': {
697
+ e.preventDefault();
698
+ const newRowUp = ctrl
699
+ ? findCtrlTarget(rowIndex, 0, -1, (r) => isEmptyAt(r, Math.max(0, dataColIndex)))
700
+ : Math.max(rowIndex - 1, 0);
701
+ if (shift) {
702
+ this.setSelectionRange(normalizeSelectionRange({
703
+ startRow: selectionRange?.startRow ?? rowIndex,
704
+ startCol: selectionRange?.startCol ?? dataColIndex,
705
+ endRow: newRowUp,
706
+ endCol: selectionRange?.endCol ?? dataColIndex,
707
+ }));
708
+ }
709
+ else {
710
+ this.setSelectionRange({ startRow: newRowUp, startCol: dataColIndex, endRow: newRowUp, endCol: dataColIndex });
711
+ }
712
+ this.setActiveCell({ rowIndex: newRowUp, columnIndex });
713
+ break;
714
+ }
715
+ case 'ArrowRight': {
716
+ e.preventDefault();
717
+ let newCol;
718
+ if (ctrl && dataColIndex >= 0) {
719
+ newCol = findCtrlTarget(dataColIndex, visibleCols.length - 1, 1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
720
+ }
721
+ else {
722
+ newCol = Math.min(columnIndex + 1, maxColIndex);
723
+ }
724
+ const newDataCol = newCol - colOffset;
725
+ if (shift) {
726
+ this.setSelectionRange(normalizeSelectionRange({
727
+ startRow: selectionRange?.startRow ?? rowIndex,
728
+ startCol: selectionRange?.startCol ?? dataColIndex,
729
+ endRow: selectionRange?.endRow ?? rowIndex,
730
+ endCol: newDataCol,
731
+ }));
732
+ }
733
+ else {
734
+ this.setSelectionRange({ startRow: rowIndex, startCol: newDataCol, endRow: rowIndex, endCol: newDataCol });
735
+ }
736
+ this.setActiveCell({ rowIndex, columnIndex: newCol });
737
+ break;
738
+ }
739
+ case 'ArrowLeft': {
740
+ e.preventDefault();
741
+ let newColLeft;
742
+ if (ctrl && dataColIndex >= 0) {
743
+ newColLeft = findCtrlTarget(dataColIndex, 0, -1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
744
+ }
745
+ else {
746
+ newColLeft = Math.max(columnIndex - 1, colOffset);
747
+ }
748
+ const newDataColLeft = newColLeft - colOffset;
749
+ if (shift) {
750
+ this.setSelectionRange(normalizeSelectionRange({
751
+ startRow: selectionRange?.startRow ?? rowIndex,
752
+ startCol: selectionRange?.startCol ?? dataColIndex,
753
+ endRow: selectionRange?.endRow ?? rowIndex,
754
+ endCol: newDataColLeft,
755
+ }));
756
+ }
757
+ else {
758
+ this.setSelectionRange({ startRow: rowIndex, startCol: newDataColLeft, endRow: rowIndex, endCol: newDataColLeft });
759
+ }
760
+ this.setActiveCell({ rowIndex, columnIndex: newColLeft });
761
+ break;
762
+ }
763
+ case 'Tab': {
764
+ e.preventDefault();
765
+ let newRowTab = rowIndex;
766
+ let newColTab = columnIndex;
767
+ if (e.shiftKey) {
768
+ if (columnIndex > colOffset) {
769
+ newColTab = columnIndex - 1;
770
+ }
771
+ else if (rowIndex > 0) {
772
+ newRowTab = rowIndex - 1;
773
+ newColTab = maxColIndex;
774
+ }
775
+ }
776
+ else {
777
+ if (columnIndex < maxColIndex) {
778
+ newColTab = columnIndex + 1;
779
+ }
780
+ else if (rowIndex < maxRowIndex) {
781
+ newRowTab = rowIndex + 1;
782
+ newColTab = colOffset;
783
+ }
784
+ }
785
+ const newDataColTab = newColTab - colOffset;
786
+ this.setSelectionRange({ startRow: newRowTab, startCol: newDataColTab, endRow: newRowTab, endCol: newDataColTab });
787
+ this.setActiveCell({ rowIndex: newRowTab, columnIndex: newColTab });
788
+ break;
789
+ }
790
+ case 'Home': {
791
+ e.preventDefault();
792
+ const newRowHome = ctrl ? 0 : rowIndex;
793
+ this.setSelectionRange({ startRow: newRowHome, startCol: 0, endRow: newRowHome, endCol: 0 });
794
+ this.setActiveCell({ rowIndex: newRowHome, columnIndex: colOffset });
795
+ break;
796
+ }
797
+ case 'End': {
798
+ e.preventDefault();
799
+ const newRowEnd = ctrl ? maxRowIndex : rowIndex;
800
+ this.setSelectionRange({ startRow: newRowEnd, startCol: visibleColumnCount - 1, endRow: newRowEnd, endCol: visibleColumnCount - 1 });
801
+ this.setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
802
+ break;
803
+ }
804
+ case 'Enter':
805
+ case 'F2': {
806
+ e.preventDefault();
807
+ if (dataColIndex >= 0 && dataColIndex < visibleCols.length) {
808
+ const col = visibleCols[dataColIndex];
809
+ const item = items[rowIndex];
810
+ if (item && col) {
811
+ const colEditable = col.editable === true || (typeof col.editable === 'function' && col.editable(item));
812
+ if (editable !== false && colEditable && onCellValueChanged != null) {
813
+ this.setEditingCell({ rowId: getRowId(item), columnId: col.columnId });
814
+ }
815
+ }
816
+ }
817
+ break;
818
+ }
819
+ case 'Escape':
820
+ e.preventDefault();
821
+ if (editingCell != null) {
822
+ this.setEditingCell(null);
823
+ }
824
+ else {
825
+ this.clearClipboardRanges();
826
+ this.setActiveCell(null);
827
+ this.setSelectionRange(null);
828
+ }
829
+ break;
830
+ case ' ':
831
+ if (rowSelection !== 'none' && columnIndex === 0 && hasCheckboxCol) {
832
+ e.preventDefault();
833
+ const item = items[rowIndex];
834
+ if (item) {
835
+ const id = getRowId(item);
836
+ const isSelected = selectedRowIds.has(id);
837
+ this.handleRowCheckboxChange(id, !isSelected, rowIndex, e.shiftKey);
838
+ }
839
+ }
840
+ break;
841
+ case 'z':
842
+ if (ctrl) {
843
+ if (editingCell == null) {
844
+ if (e.shiftKey) {
845
+ e.preventDefault();
846
+ this.redo();
847
+ }
848
+ else {
849
+ e.preventDefault();
850
+ this.undo();
851
+ }
852
+ }
853
+ }
854
+ break;
855
+ case 'y':
856
+ if (ctrl && editingCell == null) {
857
+ e.preventDefault();
858
+ this.redo();
859
+ }
860
+ break;
861
+ case 'a':
862
+ if (ctrl) {
863
+ if (editingCell != null)
864
+ break;
865
+ e.preventDefault();
866
+ if (items.length > 0 && visibleColumnCount > 0) {
867
+ this.setSelectionRange({ startRow: 0, startCol: 0, endRow: items.length - 1, endCol: visibleColumnCount - 1 });
868
+ this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
869
+ }
870
+ }
871
+ break;
872
+ case 'Delete':
873
+ case 'Backspace': {
874
+ if (editingCell != null)
875
+ break;
876
+ if (editable === false)
877
+ break;
878
+ if (onCellValueChanged == null)
879
+ break;
880
+ const range = selectionRange ?? (activeCell != null
881
+ ? { startRow: activeCell.rowIndex, startCol: activeCell.columnIndex - colOffset, endRow: activeCell.rowIndex, endCol: activeCell.columnIndex - colOffset }
882
+ : null);
883
+ if (range == null)
884
+ break;
885
+ e.preventDefault();
886
+ const norm = normalizeSelectionRange(range);
887
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
888
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
889
+ if (r >= items.length || c >= visibleCols.length)
890
+ continue;
891
+ const item = items[r];
892
+ const col = visibleCols[c];
893
+ const colEditable = col.editable === true || (typeof col.editable === 'function' && col.editable(item));
894
+ if (!colEditable)
895
+ continue;
896
+ const oldValue = getCellValue(item, col);
897
+ const result = parseValue('', oldValue, item, col);
898
+ if (!result.valid)
899
+ continue;
900
+ onCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: r });
901
+ }
902
+ }
903
+ break;
904
+ }
905
+ case 'F10':
906
+ if (e.shiftKey) {
907
+ e.preventDefault();
908
+ const wrapper = this.wrapperEl();
909
+ if (activeCell != null && wrapper) {
910
+ const sel = `[data-row-index="${activeCell.rowIndex}"][data-col-index="${activeCell.columnIndex}"]`;
911
+ const cell = wrapper.querySelector(sel);
912
+ if (cell) {
913
+ const rect = cell.getBoundingClientRect();
914
+ this.setContextMenuPosition({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
915
+ }
916
+ else {
917
+ this.setContextMenuPosition({ x: 100, y: 100 });
918
+ }
919
+ }
920
+ else {
921
+ this.setContextMenuPosition({ x: 100, y: 100 });
922
+ }
923
+ }
924
+ break;
925
+ default:
926
+ break;
927
+ }
928
+ }
929
+ // --- Fill handle ---
930
+ handleFillHandleMouseDown(e) {
931
+ e.preventDefault();
932
+ e.stopPropagation();
933
+ const range = this.selectionRangeSig();
934
+ if (!range)
935
+ return;
936
+ this.fillDragStart = { startRow: range.startRow, startCol: range.startCol };
937
+ this.setupFillHandleDrag();
938
+ }
939
+ // --- Get state result ---
940
+ getState() {
941
+ const p = this.props();
942
+ const cellSel = this.cellSelection();
943
+ const layout = {
944
+ flatColumns: this.flatColumns(),
945
+ visibleCols: this.visibleCols(),
946
+ visibleColumnCount: this.visibleColumnCount(),
947
+ totalColCount: this.totalColCount(),
948
+ colOffset: this.colOffset(),
949
+ hasCheckboxCol: this.hasCheckboxCol(),
950
+ rowIndexByRowId: this.rowIndexByRowId(),
951
+ containerWidth: this.containerWidthSig(),
952
+ minTableWidth: this.minTableWidth(),
953
+ desiredTableWidth: this.desiredTableWidth(),
954
+ columnSizingOverrides: this.columnSizingOverridesSig(),
955
+ setColumnSizingOverrides: (overrides) => this.columnSizingOverridesSig.set(overrides),
956
+ onColumnResized: p?.onColumnResized,
957
+ };
958
+ const rowSelection = {
959
+ selectedRowIds: this.selectedRowIds(),
960
+ updateSelection: (ids) => this.updateSelection(ids),
961
+ handleRowCheckboxChange: (rowId, checked, rowIndex, shiftKey) => this.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey),
962
+ handleSelectAll: (checked) => this.handleSelectAll(checked),
963
+ allSelected: this.allSelected(),
964
+ someSelected: this.someSelected(),
965
+ };
966
+ const editing = {
967
+ editingCell: this.editingCellSig(),
968
+ setEditingCell: (cell) => this.setEditingCell(cell),
969
+ pendingEditorValue: this.pendingEditorValueSig(),
970
+ setPendingEditorValue: (v) => this.setPendingEditorValue(v),
971
+ commitCellEdit: (item, colId, oldVal, newVal, rowIdx, globalColIdx) => this.commitCellEdit(item, colId, oldVal, newVal, rowIdx, globalColIdx),
972
+ cancelPopoverEdit: () => this.cancelPopoverEdit(),
973
+ popoverAnchorEl: this.popoverAnchorElSig(),
974
+ setPopoverAnchorEl: (el) => this.popoverAnchorElSig.set(el),
975
+ };
976
+ const noop = () => { };
977
+ const noopAsync = async () => { };
978
+ const noopMouse = (_e, _r, _c) => { };
979
+ const noopKey = (_e) => { };
980
+ const noopCtx = (_e) => { };
981
+ const interaction = {
982
+ activeCell: cellSel ? this.activeCellSig() : null,
983
+ setActiveCell: cellSel ? (cell) => this.setActiveCell(cell) : noop,
984
+ selectionRange: cellSel ? this.selectionRangeSig() : null,
985
+ setSelectionRange: cellSel ? (range) => this.setSelectionRange(range) : noop,
986
+ handleCellMouseDown: cellSel ? (e, r, c) => this.handleCellMouseDown(e, r, c) : noopMouse,
987
+ handleSelectAllCells: cellSel ? () => this.handleSelectAllCells() : noop,
988
+ hasCellSelection: cellSel ? this.hasCellSelection() : false,
989
+ handleGridKeyDown: cellSel ? (e) => this.handleGridKeyDown(e) : noopKey,
990
+ handleFillHandleMouseDown: cellSel ? (e) => this.handleFillHandleMouseDown(e) : noop,
991
+ handleCopy: cellSel ? () => this.handleCopy() : noop,
992
+ handleCut: cellSel ? () => this.handleCut() : noop,
993
+ handlePaste: cellSel ? () => this.handlePaste() : noopAsync,
994
+ cutRange: cellSel ? this.cutRangeSig() : null,
995
+ copyRange: cellSel ? this.copyRangeSig() : null,
996
+ clearClipboardRanges: cellSel ? () => this.clearClipboardRanges() : noop,
997
+ canUndo: this.canUndo(),
998
+ canRedo: this.canRedo(),
999
+ onUndo: () => this.undo(),
1000
+ onRedo: () => this.redo(),
1001
+ isDragging: cellSel ? this.isDraggingSig() : false,
1002
+ };
1003
+ const contextMenu = {
1004
+ menuPosition: cellSel ? this.contextMenuPositionSig() : null,
1005
+ setMenuPosition: cellSel ? (pos) => this.setContextMenuPosition(pos) : noop,
1006
+ handleCellContextMenu: cellSel ? (e) => this.handleCellContextMenu(e) : noopCtx,
1007
+ closeContextMenu: cellSel ? () => this.closeContextMenu() : noop,
1008
+ };
1009
+ const viewModels = {
1010
+ headerFilterInput: {
1011
+ sortBy: p?.sortBy,
1012
+ sortDirection: p?.sortDirection ?? 'asc',
1013
+ onColumnSort: (columnKey) => p?.onColumnSort(columnKey),
1014
+ filters: p?.filters ?? {},
1015
+ onFilterChange: (key, value) => p?.onFilterChange(key, value),
1016
+ filterOptions: p?.filterOptions ?? {},
1017
+ loadingFilterOptions: p?.loadingFilterOptions ?? {},
1018
+ peopleSearch: p?.peopleSearch,
1019
+ },
1020
+ cellDescriptorInput: {
1021
+ editingCell: this.editingCellSig(),
1022
+ activeCell: cellSel ? this.activeCellSig() : null,
1023
+ selectionRange: cellSel ? this.selectionRangeSig() : null,
1024
+ cutRange: cellSel ? this.cutRangeSig() : null,
1025
+ copyRange: cellSel ? this.copyRangeSig() : null,
1026
+ colOffset: this.colOffset(),
1027
+ itemsLength: p?.items.length ?? 0,
1028
+ getRowId: p?.getRowId ?? ((item) => item['id']),
1029
+ editable: p?.editable,
1030
+ onCellValueChanged: this.wrappedOnCellValueChanged(),
1031
+ isDragging: cellSel ? this.isDraggingSig() : false,
1032
+ },
1033
+ statusBarConfig: this.statusBarConfig(),
1034
+ showEmptyInGrid: this.showEmptyInGrid(),
1035
+ onCellError: p?.onCellError,
1036
+ };
1037
+ return { layout, rowSelection, editing, interaction, contextMenu, viewModels };
1038
+ }
1039
+ // --- Private helpers ---
1040
+ getEffectiveRange() {
1041
+ const sel = this.selectionRangeSig();
1042
+ const ac = this.activeCellSig();
1043
+ const colOffset = this.colOffset();
1044
+ return sel ?? (ac != null
1045
+ ? { startRow: ac.rowIndex, startCol: ac.columnIndex - colOffset, endRow: ac.rowIndex, endCol: ac.columnIndex - colOffset }
1046
+ : null);
1047
+ }
1048
+ onWindowMouseMove(e) {
1049
+ if (!this.isDraggingRef || !this.dragStartPos)
1050
+ return;
1051
+ if (!this.dragMoved) {
1052
+ this.dragMoved = true;
1053
+ this.isDraggingSig.set(true);
1054
+ }
1055
+ this.lastMousePos = { cx: e.clientX, cy: e.clientY };
1056
+ if (this.rafId)
1057
+ cancelAnimationFrame(this.rafId);
1058
+ this.rafId = requestAnimationFrame(() => {
1059
+ this.rafId = 0;
1060
+ const pos = this.lastMousePos;
1061
+ if (!pos)
1062
+ return;
1063
+ const newRange = this.resolveRangeFromMouse(pos.cx, pos.cy);
1064
+ if (!newRange)
1065
+ return;
1066
+ const prev = this.liveDragRange;
1067
+ if (prev && prev.startRow === newRange.startRow && prev.startCol === newRange.startCol &&
1068
+ prev.endRow === newRange.endRow && prev.endCol === newRange.endCol)
1069
+ return;
1070
+ this.liveDragRange = newRange;
1071
+ this.applyDragAttrs(newRange);
1072
+ });
1073
+ }
1074
+ onWindowMouseUp() {
1075
+ if (!this.isDraggingRef)
1076
+ return;
1077
+ if (this.autoScrollInterval) {
1078
+ clearInterval(this.autoScrollInterval);
1079
+ this.autoScrollInterval = null;
1080
+ }
1081
+ if (this.rafId) {
1082
+ cancelAnimationFrame(this.rafId);
1083
+ this.rafId = 0;
1084
+ }
1085
+ this.isDraggingRef = false;
1086
+ const wasDrag = this.dragMoved;
1087
+ if (wasDrag) {
1088
+ const pos = this.lastMousePos;
1089
+ if (pos) {
1090
+ const flushed = this.resolveRangeFromMouse(pos.cx, pos.cy);
1091
+ if (flushed)
1092
+ this.liveDragRange = flushed;
1093
+ }
1094
+ const finalRange = this.liveDragRange;
1095
+ if (finalRange) {
1096
+ this.setSelectionRange(finalRange);
1097
+ this.setActiveCell({
1098
+ rowIndex: finalRange.endRow,
1099
+ columnIndex: finalRange.endCol + this.colOffset(),
1100
+ });
1101
+ }
1102
+ }
1103
+ this.clearDragAttrs();
1104
+ this.liveDragRange = null;
1105
+ this.lastMousePos = null;
1106
+ this.dragStartPos = null;
1107
+ if (wasDrag)
1108
+ this.isDraggingSig.set(false);
1109
+ }
1110
+ resolveRangeFromMouse(cx, cy) {
1111
+ if (!this.dragStartPos)
1112
+ return null;
1113
+ const target = document.elementFromPoint(cx, cy);
1114
+ const cell = target?.closest?.('[data-row-index][data-col-index]');
1115
+ if (!cell)
1116
+ return null;
1117
+ const r = parseInt(cell.getAttribute('data-row-index') ?? '', 10);
1118
+ const c = parseInt(cell.getAttribute('data-col-index') ?? '', 10);
1119
+ const colOff = this.colOffset();
1120
+ if (Number.isNaN(r) || Number.isNaN(c) || c < colOff)
1121
+ return null;
1122
+ const dataCol = c - colOff;
1123
+ const start = this.dragStartPos;
1124
+ return normalizeSelectionRange({
1125
+ startRow: start.row, startCol: start.col,
1126
+ endRow: r, endCol: dataCol,
1127
+ });
1128
+ }
1129
+ applyDragAttrs(range) {
1130
+ const wrapper = this.wrapperEl();
1131
+ if (!wrapper)
1132
+ return;
1133
+ const colOff = this.colOffset();
1134
+ const minR = Math.min(range.startRow, range.endRow);
1135
+ const maxR = Math.max(range.startRow, range.endRow);
1136
+ const minC = Math.min(range.startCol, range.endCol);
1137
+ const maxC = Math.max(range.startCol, range.endCol);
1138
+ const cells = wrapper.querySelectorAll('[data-row-index][data-col-index]');
1139
+ for (let i = 0; i < cells.length; i++) {
1140
+ const el = cells[i];
1141
+ const r = parseInt(el.getAttribute('data-row-index'), 10);
1142
+ const c = parseInt(el.getAttribute('data-col-index'), 10) - colOff;
1143
+ const inRange = r >= minR && r <= maxR && c >= minC && c <= maxC;
1144
+ if (inRange) {
1145
+ if (!el.hasAttribute('data-drag-range'))
1146
+ el.setAttribute('data-drag-range', '');
1147
+ }
1148
+ else {
1149
+ if (el.hasAttribute('data-drag-range'))
1150
+ el.removeAttribute('data-drag-range');
1151
+ }
1152
+ }
1153
+ }
1154
+ clearDragAttrs() {
1155
+ const wrapper = this.wrapperEl();
1156
+ if (!wrapper)
1157
+ return;
1158
+ const marked = wrapper.querySelectorAll('[data-drag-range]');
1159
+ for (let i = 0; i < marked.length; i++)
1160
+ marked[i].removeAttribute('data-drag-range');
1161
+ }
1162
+ setupFillHandleDrag() {
1163
+ const p = this.props();
1164
+ if (!this.fillDragStart || p?.editable === false || !this.wrappedOnCellValueChanged())
1165
+ return;
1166
+ const colOff = this.colOffset();
1167
+ const fillStart = this.fillDragStart;
1168
+ let fillDragEnd = { endRow: fillStart.startRow, endCol: fillStart.startCol };
1169
+ let liveFillRange = null;
1170
+ let fillRafId = 0;
1171
+ let lastFillMousePos = null;
1172
+ const resolveRange = (cx, cy) => {
1173
+ const target = document.elementFromPoint(cx, cy);
1174
+ const cell = target?.closest?.('[data-row-index][data-col-index]');
1175
+ const wrapper = this.wrapperEl();
1176
+ if (!cell || !wrapper?.contains(cell))
1177
+ return null;
1178
+ const r = parseInt(cell.getAttribute('data-row-index') ?? '', 10);
1179
+ const c = parseInt(cell.getAttribute('data-col-index') ?? '', 10);
1180
+ if (Number.isNaN(r) || Number.isNaN(c) || c < colOff)
1181
+ return null;
1182
+ const dataCol = c - colOff;
1183
+ return normalizeSelectionRange({
1184
+ startRow: fillStart.startRow, startCol: fillStart.startCol,
1185
+ endRow: r, endCol: dataCol,
1186
+ });
1187
+ };
1188
+ const onMove = (e) => {
1189
+ lastFillMousePos = { cx: e.clientX, cy: e.clientY };
1190
+ if (fillRafId)
1191
+ cancelAnimationFrame(fillRafId);
1192
+ fillRafId = requestAnimationFrame(() => {
1193
+ fillRafId = 0;
1194
+ if (!lastFillMousePos)
1195
+ return;
1196
+ const newRange = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
1197
+ if (!newRange)
1198
+ return;
1199
+ if (liveFillRange && liveFillRange.startRow === newRange.startRow &&
1200
+ liveFillRange.startCol === newRange.startCol &&
1201
+ liveFillRange.endRow === newRange.endRow &&
1202
+ liveFillRange.endCol === newRange.endCol)
1203
+ return;
1204
+ liveFillRange = newRange;
1205
+ fillDragEnd = { endRow: newRange.endRow, endCol: newRange.endCol };
1206
+ this.applyDragAttrs(newRange);
1207
+ });
1208
+ };
1209
+ const onUp = () => {
1210
+ window.removeEventListener('mousemove', onMove, true);
1211
+ window.removeEventListener('mouseup', onUp, true);
1212
+ if (fillRafId) {
1213
+ cancelAnimationFrame(fillRafId);
1214
+ fillRafId = 0;
1215
+ }
1216
+ if (lastFillMousePos) {
1217
+ const flushed = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
1218
+ if (flushed) {
1219
+ liveFillRange = flushed;
1220
+ fillDragEnd = { endRow: flushed.endRow, endCol: flushed.endCol };
1221
+ }
1222
+ }
1223
+ this.clearDragAttrs();
1224
+ const norm = normalizeSelectionRange({
1225
+ startRow: fillStart.startRow, startCol: fillStart.startCol,
1226
+ endRow: fillDragEnd.endRow, endCol: fillDragEnd.endCol,
1227
+ });
1228
+ this.setSelectionRange(norm);
1229
+ this.setActiveCell({ rowIndex: fillDragEnd.endRow, columnIndex: fillDragEnd.endCol + colOff });
1230
+ // Apply fill values
1231
+ const items = p.items;
1232
+ const visibleCols = this.visibleCols();
1233
+ const startItem = items[norm.startRow];
1234
+ const startColDef = visibleCols[norm.startCol];
1235
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
1236
+ if (startItem && startColDef) {
1237
+ const startValue = getCellValue(startItem, startColDef);
1238
+ this.beginBatch();
1239
+ for (let row = norm.startRow; row <= norm.endRow; row++) {
1240
+ for (let col = norm.startCol; col <= norm.endCol; col++) {
1241
+ if (row === fillStart.startRow && col === fillStart.startCol)
1242
+ continue;
1243
+ if (row >= items.length || col >= visibleCols.length)
1244
+ continue;
1245
+ const item = items[row];
1246
+ const colDef = visibleCols[col];
1247
+ const colEditable = colDef.editable === true || (typeof colDef.editable === 'function' && colDef.editable(item));
1248
+ if (!colEditable)
1249
+ continue;
1250
+ const oldValue = getCellValue(item, colDef);
1251
+ const result = parseValue(startValue, oldValue, item, colDef);
1252
+ if (!result.valid)
1253
+ continue;
1254
+ onCellValueChanged({ item, columnId: colDef.columnId, oldValue, newValue: result.value, rowIndex: row });
1255
+ }
1256
+ }
1257
+ this.endBatch();
1258
+ }
1259
+ this.fillDragStart = null;
1260
+ };
1261
+ window.addEventListener('mousemove', onMove, true);
1262
+ window.addEventListener('mouseup', onUp, true);
1263
+ }
1264
+ };
1265
+ DataGridStateService = __decorate([
1266
+ Injectable()
1267
+ ], DataGridStateService);
1268
+ export { DataGridStateService };