@alaarab/ogrid-js 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/esm/index.js +6343 -32
  2. package/package.json +7 -5
  3. package/dist/esm/OGrid.js +0 -578
  4. package/dist/esm/OGridEventWiring.js +0 -178
  5. package/dist/esm/OGridRendering.js +0 -269
  6. package/dist/esm/components/ColumnChooser.js +0 -91
  7. package/dist/esm/components/ContextMenu.js +0 -125
  8. package/dist/esm/components/HeaderFilter.js +0 -281
  9. package/dist/esm/components/InlineCellEditor.js +0 -434
  10. package/dist/esm/components/MarchingAntsOverlay.js +0 -156
  11. package/dist/esm/components/PaginationControls.js +0 -85
  12. package/dist/esm/components/SideBar.js +0 -353
  13. package/dist/esm/components/StatusBar.js +0 -34
  14. package/dist/esm/renderer/TableRenderer.js +0 -846
  15. package/dist/esm/state/ClipboardState.js +0 -111
  16. package/dist/esm/state/ColumnPinningState.js +0 -82
  17. package/dist/esm/state/ColumnReorderState.js +0 -135
  18. package/dist/esm/state/ColumnResizeState.js +0 -55
  19. package/dist/esm/state/EventEmitter.js +0 -28
  20. package/dist/esm/state/FillHandleState.js +0 -206
  21. package/dist/esm/state/GridState.js +0 -324
  22. package/dist/esm/state/HeaderFilterState.js +0 -213
  23. package/dist/esm/state/KeyboardNavState.js +0 -216
  24. package/dist/esm/state/RowSelectionState.js +0 -72
  25. package/dist/esm/state/SelectionState.js +0 -109
  26. package/dist/esm/state/SideBarState.js +0 -41
  27. package/dist/esm/state/TableLayoutState.js +0 -97
  28. package/dist/esm/state/UndoRedoState.js +0 -71
  29. package/dist/esm/state/VirtualScrollState.js +0 -128
  30. package/dist/esm/types/columnTypes.js +0 -1
  31. package/dist/esm/types/gridTypes.js +0 -1
  32. package/dist/esm/types/index.js +0 -2
  33. package/dist/esm/utils/debounce.js +0 -2
  34. package/dist/esm/utils/getCellCoordinates.js +0 -15
  35. package/dist/esm/utils/index.js +0 -2
@@ -1,216 +0,0 @@
1
- import { getCellValue, computeTabNavigation, computeArrowNavigation, applyCellDeletion } from '@alaarab/ogrid-core';
2
- export class KeyboardNavState {
3
- constructor(params, getActiveCell, getSelectionRange, setActiveCell, setSelectionRange) {
4
- this.wrapperRef = null;
5
- this.handleKeyDown = (e) => {
6
- const { items, visibleCols, colOffset, editable, onCellValueChanged, onCopy, onCut, onPaste, onUndo, onRedo, onContextMenu, onStartEdit, getRowId, clearClipboardRanges } = this.params;
7
- const activeCell = this.getActiveCell();
8
- const selectionRange = this.getSelectionRange();
9
- const maxRowIndex = items.length - 1;
10
- const maxColIndex = visibleCols.length - 1 + colOffset;
11
- if (items.length === 0)
12
- return;
13
- if (activeCell === null) {
14
- if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter', 'Home', 'End'].includes(e.key)) {
15
- this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
16
- e.preventDefault();
17
- }
18
- return;
19
- }
20
- const { rowIndex, columnIndex } = activeCell;
21
- const dataColIndex = columnIndex - colOffset;
22
- const shift = e.shiftKey;
23
- const isEmptyAt = (r, c) => {
24
- if (r < 0 || r >= items.length || c < 0 || c >= visibleCols.length)
25
- return true;
26
- const v = getCellValue(items[r], visibleCols[c]);
27
- return v == null || v === '';
28
- };
29
- switch (e.key) {
30
- case 'c':
31
- if (e.ctrlKey || e.metaKey) {
32
- e.preventDefault();
33
- onCopy?.();
34
- }
35
- break;
36
- case 'x':
37
- if (e.ctrlKey || e.metaKey) {
38
- e.preventDefault();
39
- onCut?.();
40
- }
41
- break;
42
- case 'v':
43
- if (e.ctrlKey || e.metaKey) {
44
- e.preventDefault();
45
- void onPaste?.();
46
- }
47
- break;
48
- case 'ArrowDown':
49
- case 'ArrowUp':
50
- case 'ArrowRight':
51
- case 'ArrowLeft': {
52
- e.preventDefault();
53
- const { newRowIndex, newColumnIndex, newRange } = computeArrowNavigation({
54
- direction: e.key,
55
- rowIndex, columnIndex, dataColIndex, colOffset,
56
- maxRowIndex, maxColIndex,
57
- visibleColCount: visibleCols.length,
58
- isCtrl: e.ctrlKey || e.metaKey,
59
- isShift: shift,
60
- selectionRange,
61
- isEmptyAt,
62
- });
63
- this.setActiveCell({ rowIndex: newRowIndex, columnIndex: newColumnIndex });
64
- this.setSelectionRange(newRange);
65
- break;
66
- }
67
- case 'Tab': {
68
- e.preventDefault();
69
- const tabResult = computeTabNavigation(rowIndex, columnIndex, maxRowIndex, maxColIndex, colOffset, e.shiftKey);
70
- const newDataColTab = tabResult.columnIndex - colOffset;
71
- this.setActiveCell({ rowIndex: tabResult.rowIndex, columnIndex: tabResult.columnIndex });
72
- this.setSelectionRange({
73
- startRow: tabResult.rowIndex,
74
- startCol: newDataColTab,
75
- endRow: tabResult.rowIndex,
76
- endCol: newDataColTab,
77
- });
78
- break;
79
- }
80
- case 'Home': {
81
- e.preventDefault();
82
- const newRowHome = e.ctrlKey ? 0 : rowIndex;
83
- this.setActiveCell({ rowIndex: newRowHome, columnIndex: colOffset });
84
- this.setSelectionRange({
85
- startRow: newRowHome,
86
- startCol: 0,
87
- endRow: newRowHome,
88
- endCol: 0,
89
- });
90
- break;
91
- }
92
- case 'End': {
93
- e.preventDefault();
94
- const newRowEnd = e.ctrlKey ? maxRowIndex : rowIndex;
95
- this.setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
96
- this.setSelectionRange({
97
- startRow: newRowEnd,
98
- startCol: visibleCols.length - 1,
99
- endRow: newRowEnd,
100
- endCol: visibleCols.length - 1,
101
- });
102
- break;
103
- }
104
- case 'Enter':
105
- case 'F2': {
106
- e.preventDefault();
107
- if (dataColIndex >= 0 && dataColIndex < visibleCols.length) {
108
- const col = visibleCols[dataColIndex];
109
- const item = items[rowIndex];
110
- if (item && col) {
111
- const colEditable = col.editable === true ||
112
- (typeof col.editable === 'function' && col.editable(item));
113
- if (editable !== false && colEditable) {
114
- onStartEdit?.(getRowId(item), col.columnId);
115
- }
116
- }
117
- }
118
- break;
119
- }
120
- case 'Escape':
121
- e.preventDefault();
122
- clearClipboardRanges?.();
123
- this.setActiveCell(null);
124
- this.setSelectionRange(null);
125
- break;
126
- case 'z':
127
- if (e.ctrlKey || e.metaKey) {
128
- if (e.shiftKey && onRedo) {
129
- e.preventDefault();
130
- onRedo();
131
- }
132
- else if (!e.shiftKey && onUndo) {
133
- e.preventDefault();
134
- onUndo();
135
- }
136
- }
137
- break;
138
- case 'y':
139
- if (e.ctrlKey || e.metaKey) {
140
- e.preventDefault();
141
- onRedo?.();
142
- }
143
- break;
144
- case 'a':
145
- if (e.ctrlKey || e.metaKey) {
146
- e.preventDefault();
147
- if (items.length > 0 && visibleCols.length > 0) {
148
- this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
149
- this.setSelectionRange({
150
- startRow: 0,
151
- startCol: 0,
152
- endRow: items.length - 1,
153
- endCol: visibleCols.length - 1,
154
- });
155
- }
156
- }
157
- break;
158
- case 'Delete':
159
- case 'Backspace': {
160
- if (editable === false)
161
- break;
162
- if (onCellValueChanged == null)
163
- break;
164
- const range = selectionRange ??
165
- (activeCell != null
166
- ? {
167
- startRow: activeCell.rowIndex,
168
- startCol: activeCell.columnIndex - colOffset,
169
- endRow: activeCell.rowIndex,
170
- endCol: activeCell.columnIndex - colOffset,
171
- }
172
- : null);
173
- if (range == null)
174
- break;
175
- e.preventDefault();
176
- const deleteEvents = applyCellDeletion(range, items, visibleCols);
177
- for (const evt of deleteEvents)
178
- onCellValueChanged(evt);
179
- break;
180
- }
181
- case 'F10':
182
- if (e.shiftKey) {
183
- e.preventDefault();
184
- if (activeCell != null && this.wrapperRef) {
185
- const sel = `[data-row-index="${activeCell.rowIndex}"][data-col-index="${activeCell.columnIndex}"]`;
186
- const cell = this.wrapperRef.querySelector(sel);
187
- if (cell) {
188
- const rect = cell.getBoundingClientRect();
189
- onContextMenu?.(rect.left + rect.width / 2, rect.top + rect.height / 2);
190
- }
191
- else {
192
- onContextMenu?.(100, 100);
193
- }
194
- }
195
- else {
196
- onContextMenu?.(100, 100);
197
- }
198
- }
199
- break;
200
- default:
201
- break;
202
- }
203
- };
204
- this.params = params;
205
- this.getActiveCell = getActiveCell;
206
- this.getSelectionRange = getSelectionRange;
207
- this.setActiveCell = setActiveCell;
208
- this.setSelectionRange = setSelectionRange;
209
- }
210
- setWrapperRef(ref) {
211
- this.wrapperRef = ref;
212
- }
213
- updateParams(params) {
214
- this.params = params;
215
- }
216
- }
@@ -1,72 +0,0 @@
1
- import { applyRangeRowSelection, computeRowSelectionState } from '@alaarab/ogrid-core';
2
- import { EventEmitter } from './EventEmitter';
3
- /**
4
- * Manages row selection state for single or multiple selection modes with shift-click range support.
5
- * Vanilla JS equivalent of React's `useRowSelection` hook.
6
- */
7
- export class RowSelectionState {
8
- constructor(rowSelection, getRowId) {
9
- this.emitter = new EventEmitter();
10
- this._selectedRowIds = new Set();
11
- this._lastClickedRow = -1;
12
- this._rowSelection = rowSelection;
13
- this._getRowId = getRowId;
14
- }
15
- get selectedRowIds() {
16
- return this._selectedRowIds;
17
- }
18
- get rowSelection() {
19
- return this._rowSelection;
20
- }
21
- updateSelection(newSelectedIds, items) {
22
- this._selectedRowIds = newSelectedIds;
23
- this.emitter.emit('rowSelectionChange', {
24
- selectedRowIds: Array.from(newSelectedIds),
25
- selectedItems: items.filter((item) => newSelectedIds.has(this._getRowId(item))),
26
- });
27
- }
28
- handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey, items) {
29
- if (this._rowSelection === 'single') {
30
- this.updateSelection(checked ? new Set([rowId]) : new Set(), items);
31
- this._lastClickedRow = rowIndex;
32
- return;
33
- }
34
- let next;
35
- if (shiftKey && this._lastClickedRow >= 0 && this._lastClickedRow !== rowIndex) {
36
- next = applyRangeRowSelection(this._lastClickedRow, rowIndex, checked, items, this._getRowId, this._selectedRowIds);
37
- }
38
- else {
39
- next = new Set(this._selectedRowIds);
40
- if (checked)
41
- next.add(rowId);
42
- else
43
- next.delete(rowId);
44
- }
45
- this._lastClickedRow = rowIndex;
46
- this.updateSelection(next, items);
47
- }
48
- handleSelectAll(checked, items) {
49
- if (checked) {
50
- this.updateSelection(new Set(items.map((item) => this._getRowId(item))), items);
51
- }
52
- else {
53
- this.updateSelection(new Set(), items);
54
- }
55
- }
56
- isAllSelected(items) {
57
- return computeRowSelectionState(this._selectedRowIds, items, this._getRowId).allSelected;
58
- }
59
- isSomeSelected(items) {
60
- return computeRowSelectionState(this._selectedRowIds, items, this._getRowId).someSelected;
61
- }
62
- getSelectedRows(items) {
63
- return items.filter((item) => this._selectedRowIds.has(this._getRowId(item)));
64
- }
65
- onRowSelectionChange(handler) {
66
- this.emitter.on('rowSelectionChange', handler);
67
- return () => this.emitter.off('rowSelectionChange', handler);
68
- }
69
- destroy() {
70
- this.emitter.removeAllListeners();
71
- }
72
- }
@@ -1,109 +0,0 @@
1
- import { rangesEqual } from '@alaarab/ogrid-core';
2
- import { EventEmitter } from './EventEmitter';
3
- export class SelectionState {
4
- constructor() {
5
- this.emitter = new EventEmitter();
6
- this._activeCell = null;
7
- this._selectionRange = null;
8
- this._selectedRowIds = new Set();
9
- this._isDragging = false;
10
- this.dragStartCell = null;
11
- this.rafHandle = null;
12
- this.pendingRange = null;
13
- }
14
- get activeCell() {
15
- return this._activeCell;
16
- }
17
- get dragAnchor() {
18
- return this.dragStartCell;
19
- }
20
- get selectionRange() {
21
- return this._selectionRange;
22
- }
23
- get selectedRowIds() {
24
- return this._selectedRowIds;
25
- }
26
- get isDragging() {
27
- return this._isDragging;
28
- }
29
- /** Get the current drag range (used during drag for DOM attribute updates). */
30
- getDragRange() {
31
- return this.pendingRange;
32
- }
33
- setActiveCell(cell) {
34
- this._activeCell = cell;
35
- this._selectionRange = cell != null
36
- ? { startRow: cell.rowIndex, startCol: cell.columnIndex, endRow: cell.rowIndex, endCol: cell.columnIndex }
37
- : null;
38
- this.emitter.emit('selectionChange', { activeCell: cell, selectionRange: this._selectionRange });
39
- }
40
- setSelectionRange(range) {
41
- this._selectionRange = range;
42
- this.emitter.emit('selectionChange', { activeCell: this._activeCell, selectionRange: range });
43
- }
44
- clearSelection() {
45
- this._activeCell = null;
46
- this._selectionRange = null;
47
- this.emitter.emit('selectionChange', { activeCell: null, selectionRange: null });
48
- }
49
- startDrag(rowIndex, colIndex) {
50
- this._isDragging = true;
51
- this.dragStartCell = { rowIndex, columnIndex: colIndex };
52
- this._activeCell = { rowIndex, columnIndex: colIndex };
53
- this._selectionRange = { startRow: rowIndex, startCol: colIndex, endRow: rowIndex, endCol: colIndex };
54
- }
55
- updateDrag(rowIndex, colIndex, applyFn) {
56
- if (!this._isDragging || !this.dragStartCell)
57
- return;
58
- const newRange = {
59
- startRow: this.dragStartCell.rowIndex,
60
- startCol: this.dragStartCell.columnIndex,
61
- endRow: rowIndex,
62
- endCol: colIndex,
63
- };
64
- // Skip RAF if range hasn't changed (deduplication optimization)
65
- if (rangesEqual(this.pendingRange, newRange))
66
- return;
67
- this.pendingRange = newRange;
68
- if (this.rafHandle === null) {
69
- this.rafHandle = requestAnimationFrame(() => {
70
- if (this.pendingRange) {
71
- applyFn(this.pendingRange);
72
- }
73
- this.rafHandle = null;
74
- });
75
- }
76
- }
77
- endDrag() {
78
- if (this.rafHandle !== null) {
79
- // Flush pending RAF synchronously before ending drag (critical for jsdom tests)
80
- cancelAnimationFrame(this.rafHandle);
81
- this.rafHandle = null;
82
- }
83
- if (this.pendingRange) {
84
- this._selectionRange = this.pendingRange;
85
- this.pendingRange = null;
86
- this.emitter.emit('selectionChange', { activeCell: this._activeCell, selectionRange: this._selectionRange });
87
- }
88
- this._isDragging = false;
89
- this.dragStartCell = null;
90
- }
91
- setSelectedRowIds(ids) {
92
- this._selectedRowIds = ids;
93
- this.emitter.emit('rowSelectionChange', { selectedRowIds: ids });
94
- }
95
- onSelectionChange(handler) {
96
- this.emitter.on('selectionChange', handler);
97
- return () => this.emitter.off('selectionChange', handler);
98
- }
99
- onRowSelectionChange(handler) {
100
- this.emitter.on('rowSelectionChange', handler);
101
- return () => this.emitter.off('rowSelectionChange', handler);
102
- }
103
- destroy() {
104
- if (this.rafHandle !== null) {
105
- cancelAnimationFrame(this.rafHandle);
106
- }
107
- this.emitter.removeAllListeners();
108
- }
109
- }
@@ -1,41 +0,0 @@
1
- import { EventEmitter } from './EventEmitter';
2
- const DEFAULT_PANELS = ['columns', 'filters'];
3
- export class SideBarState {
4
- constructor(config) {
5
- this.emitter = new EventEmitter();
6
- this._isEnabled = config != null && config !== false;
7
- if (!this._isEnabled || config === true) {
8
- this._panels = DEFAULT_PANELS;
9
- this._position = 'right';
10
- this._activePanel = null;
11
- }
12
- else {
13
- const def = config;
14
- this._panels = def.panels ?? DEFAULT_PANELS;
15
- this._position = def.position ?? 'right';
16
- this._activePanel = def.defaultPanel ?? null;
17
- }
18
- }
19
- get isEnabled() { return this._isEnabled; }
20
- get panels() { return this._panels; }
21
- get position() { return this._position; }
22
- get activePanel() { return this._activePanel; }
23
- get isOpen() { return this._activePanel !== null; }
24
- setActivePanel(panel) {
25
- this._activePanel = panel;
26
- this.emitter.emit('change');
27
- }
28
- toggle(panel) {
29
- this.setActivePanel(this._activePanel === panel ? null : panel);
30
- }
31
- close() {
32
- this.setActivePanel(null);
33
- }
34
- onChange(handler) {
35
- this.emitter.on('change', handler);
36
- return () => this.emitter.off('change', handler);
37
- }
38
- destroy() {
39
- this.emitter.removeAllListeners();
40
- }
41
- }
@@ -1,97 +0,0 @@
1
- import { DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, CHECKBOX_COLUMN_WIDTH } from '@alaarab/ogrid-core';
2
- import { EventEmitter } from './EventEmitter';
3
- export class TableLayoutState {
4
- constructor() {
5
- this.emitter = new EventEmitter();
6
- this._containerWidth = 0;
7
- this._columnSizingOverrides = {};
8
- this._ro = null;
9
- }
10
- /** Start observing a container element for resize. */
11
- observeContainer(el) {
12
- this.disconnectObserver();
13
- // ResizeObserver may not be available in jsdom test environments
14
- if (typeof ResizeObserver !== 'undefined') {
15
- this._ro = new ResizeObserver((entries) => {
16
- for (const entry of entries) {
17
- const rect = entry.contentRect;
18
- this._containerWidth = rect.width;
19
- this.emitter.emit('layoutChange', { type: 'containerResize' });
20
- }
21
- });
22
- this._ro.observe(el);
23
- }
24
- // Measure initial size
25
- this._containerWidth = el.clientWidth;
26
- }
27
- disconnectObserver() {
28
- if (this._ro) {
29
- this._ro.disconnect();
30
- this._ro = null;
31
- }
32
- }
33
- get containerWidth() {
34
- return this._containerWidth;
35
- }
36
- get columnSizingOverrides() {
37
- return this._columnSizingOverrides;
38
- }
39
- /** Set a column width override (from resize drag). */
40
- setColumnOverride(columnId, widthPx) {
41
- this._columnSizingOverrides[columnId] = widthPx;
42
- this.emitter.emit('layoutChange', { type: 'columnOverride' });
43
- }
44
- /** Compute minimum table width from visible columns. */
45
- computeMinTableWidth(visibleColumnCount, hasCheckboxColumn) {
46
- const checkboxWidth = hasCheckboxColumn ? CHECKBOX_COLUMN_WIDTH : 0;
47
- return checkboxWidth + visibleColumnCount * (DEFAULT_MIN_COLUMN_WIDTH + CELL_PADDING);
48
- }
49
- /** Compute desired table width respecting overrides. */
50
- computeDesiredTableWidth(visibleColumns, hasCheckboxColumn) {
51
- const checkboxWidth = hasCheckboxColumn ? CHECKBOX_COLUMN_WIDTH : 0;
52
- let total = checkboxWidth;
53
- for (const col of visibleColumns) {
54
- const override = this._columnSizingOverrides[col.columnId];
55
- if (override) {
56
- total += override + CELL_PADDING;
57
- }
58
- else {
59
- total += (col.width ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH) + CELL_PADDING;
60
- }
61
- }
62
- return total;
63
- }
64
- /** Get all column widths (overrides only — non-overridden columns use CSS defaults). */
65
- getAllColumnWidths() {
66
- return { ...this._columnSizingOverrides };
67
- }
68
- /** Remove overrides for columns that no longer exist. */
69
- cleanupOverrides(validColumnIds) {
70
- const next = {};
71
- let changed = false;
72
- for (const [key, value] of Object.entries(this._columnSizingOverrides)) {
73
- if (validColumnIds.has(key)) {
74
- next[key] = value;
75
- }
76
- else {
77
- changed = true;
78
- }
79
- }
80
- if (changed) {
81
- this._columnSizingOverrides = next;
82
- this.emitter.emit('layoutChange', { type: 'columnOverride' });
83
- }
84
- }
85
- /** Apply initial column widths from options. */
86
- applyInitialWidths(initialWidths) {
87
- this._columnSizingOverrides = { ...initialWidths };
88
- }
89
- onLayoutChange(handler) {
90
- this.emitter.on('layoutChange', handler);
91
- return () => this.emitter.off('layoutChange', handler);
92
- }
93
- destroy() {
94
- this.disconnectObserver();
95
- this.emitter.removeAllListeners();
96
- }
97
- }
@@ -1,71 +0,0 @@
1
- import { UndoRedoStack } from '@alaarab/ogrid-core';
2
- import { EventEmitter } from './EventEmitter';
3
- export class UndoRedoState {
4
- constructor(onCellValueChanged, maxUndoDepth = 100) {
5
- this.onCellValueChanged = onCellValueChanged;
6
- this.emitter = new EventEmitter();
7
- this.stack = new UndoRedoStack(maxUndoDepth);
8
- if (onCellValueChanged) {
9
- this.wrappedCallback = (event) => {
10
- this.stack.record(event);
11
- if (!this.stack.isBatching) {
12
- this.emitStackChange();
13
- }
14
- onCellValueChanged(event);
15
- };
16
- }
17
- }
18
- get canUndo() {
19
- return this.stack.canUndo;
20
- }
21
- get canRedo() {
22
- return this.stack.canRedo;
23
- }
24
- getWrappedCallback() {
25
- return this.wrappedCallback;
26
- }
27
- beginBatch() {
28
- this.stack.beginBatch();
29
- }
30
- endBatch() {
31
- this.stack.endBatch();
32
- this.emitStackChange();
33
- }
34
- undo() {
35
- if (!this.onCellValueChanged)
36
- return;
37
- const lastBatch = this.stack.undo();
38
- if (!lastBatch)
39
- return;
40
- this.emitStackChange();
41
- for (let i = lastBatch.length - 1; i >= 0; i--) {
42
- const ev = lastBatch[i];
43
- this.onCellValueChanged({
44
- ...ev,
45
- oldValue: ev.newValue,
46
- newValue: ev.oldValue,
47
- });
48
- }
49
- }
50
- redo() {
51
- if (!this.onCellValueChanged)
52
- return;
53
- const nextBatch = this.stack.redo();
54
- if (!nextBatch)
55
- return;
56
- this.emitStackChange();
57
- for (const ev of nextBatch) {
58
- this.onCellValueChanged(ev);
59
- }
60
- }
61
- emitStackChange() {
62
- this.emitter.emit('stackChange', { canUndo: this.canUndo, canRedo: this.canRedo });
63
- }
64
- onStackChange(handler) {
65
- this.emitter.on('stackChange', handler);
66
- return () => this.emitter.off('stackChange', handler);
67
- }
68
- destroy() {
69
- this.emitter.removeAllListeners();
70
- }
71
- }