@alaarab/ogrid-angular 2.5.9 → 2.6.1

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 (60) hide show
  1. package/dist/esm/components/base-column-chooser.component.js +77 -0
  2. package/dist/esm/components/base-column-header-filter.component.js +267 -0
  3. package/dist/esm/components/base-column-header-menu.component.js +80 -0
  4. package/dist/esm/components/base-datagrid-table.component.js +768 -0
  5. package/dist/esm/components/base-inline-cell-editor.component.js +380 -0
  6. package/dist/esm/components/base-ogrid.component.js +36 -0
  7. package/dist/esm/components/base-pagination-controls.component.js +68 -0
  8. package/dist/esm/components/base-popover-cell-editor.component.js +122 -0
  9. package/dist/esm/components/empty-state.component.js +68 -0
  10. package/dist/esm/components/formula-bar.component.js +99 -0
  11. package/dist/esm/components/formula-ref-overlay.component.js +115 -0
  12. package/dist/esm/components/grid-context-menu.component.js +197 -0
  13. package/dist/esm/components/inline-cell-editor-template.js +134 -0
  14. package/dist/esm/components/marching-ants-overlay.component.js +177 -0
  15. package/dist/esm/components/ogrid-layout.component.js +302 -0
  16. package/dist/esm/components/sheet-tabs.component.js +83 -0
  17. package/dist/esm/components/sidebar.component.js +431 -0
  18. package/dist/esm/components/status-bar.component.js +92 -0
  19. package/dist/esm/index.js +39 -819
  20. package/dist/esm/services/column-reorder.service.js +176 -0
  21. package/dist/esm/services/datagrid-editing.service.js +59 -0
  22. package/dist/esm/services/datagrid-interaction.service.js +744 -0
  23. package/dist/esm/services/datagrid-layout.service.js +157 -0
  24. package/dist/esm/services/datagrid-state.service.js +636 -0
  25. package/dist/esm/services/formula-engine.service.js +223 -0
  26. package/dist/esm/services/ogrid.service.js +1094 -0
  27. package/dist/esm/services/virtual-scroll.service.js +114 -0
  28. package/dist/esm/styles/ogrid-theme-vars.js +112 -0
  29. package/dist/esm/types/columnTypes.js +1 -0
  30. package/dist/esm/types/dataGridTypes.js +1 -0
  31. package/dist/esm/types/index.js +1 -0
  32. package/dist/esm/utils/dataGridViewModel.js +6 -0
  33. package/dist/esm/utils/debounce.js +68 -0
  34. package/dist/esm/utils/index.js +8 -0
  35. package/dist/esm/utils/latestRef.js +41 -0
  36. package/dist/types/components/base-column-chooser.component.d.ts +3 -0
  37. package/dist/types/components/base-column-header-filter.component.d.ts +3 -0
  38. package/dist/types/components/base-column-header-menu.component.d.ts +3 -0
  39. package/dist/types/components/base-datagrid-table.component.d.ts +3 -0
  40. package/dist/types/components/base-inline-cell-editor.component.d.ts +7 -0
  41. package/dist/types/components/base-ogrid.component.d.ts +3 -0
  42. package/dist/types/components/base-pagination-controls.component.d.ts +3 -0
  43. package/dist/types/components/base-popover-cell-editor.component.d.ts +3 -0
  44. package/dist/types/components/empty-state.component.d.ts +3 -0
  45. package/dist/types/components/formula-bar.component.d.ts +3 -8
  46. package/dist/types/components/formula-ref-overlay.component.d.ts +3 -6
  47. package/dist/types/components/grid-context-menu.component.d.ts +3 -0
  48. package/dist/types/components/inline-cell-editor-template.d.ts +2 -2
  49. package/dist/types/components/marching-ants-overlay.component.d.ts +3 -0
  50. package/dist/types/components/ogrid-layout.component.d.ts +3 -0
  51. package/dist/types/components/sheet-tabs.component.d.ts +3 -8
  52. package/dist/types/components/sidebar.component.d.ts +3 -0
  53. package/dist/types/components/status-bar.component.d.ts +3 -0
  54. package/dist/types/services/column-reorder.service.d.ts +3 -0
  55. package/dist/types/services/datagrid-interaction.service.d.ts +1 -0
  56. package/dist/types/services/datagrid-state.service.d.ts +5 -0
  57. package/dist/types/services/formula-engine.service.d.ts +3 -9
  58. package/dist/types/services/ogrid.service.d.ts +8 -2
  59. package/dist/types/services/virtual-scroll.service.d.ts +3 -0
  60. package/package.json +4 -3
@@ -0,0 +1,636 @@
1
+ import { Injectable, signal, computed, effect, DestroyRef, inject, NgZone } from '@angular/core';
2
+ import { getDataGridStatusBarConfig, parseValue, computeAggregations, getCellValue, normalizeSelectionRange, } from '@alaarab/ogrid-core';
3
+ import { DataGridLayoutHelper } from './datagrid-layout.service';
4
+ import { DataGridEditingHelper } from './datagrid-editing.service';
5
+ import { DataGridInteractionHelper } from './datagrid-interaction.service';
6
+ import * as i0 from "@angular/core";
7
+ // Stable no-op functions to avoid allocating new closures on every getState() call
8
+ const NOOP = () => { };
9
+ const NOOP_ASYNC = async () => { };
10
+ const NOOP_MOUSE = (_e, _r, _c) => { };
11
+ const NOOP_KEY = (_e) => { };
12
+ const NOOP_CTX = (_e) => { };
13
+ /**
14
+ * Single orchestration service for DataGridTable. Takes grid props,
15
+ * returns all derived state and handlers so Angular UI packages can be thin view layers.
16
+ *
17
+ * Port of React's useDataGridState hook.
18
+ */
19
+ export class DataGridStateService {
20
+ constructor() {
21
+ this.destroyRef = inject(DestroyRef);
22
+ this.ngZone = inject(NgZone);
23
+ this.mutationTick = signal(0, ...(ngDevMode ? [{ debugName: "mutationTick" }] : []));
24
+ // --- Input signals ---
25
+ this.props = signal(null, ...(ngDevMode ? [{ debugName: "props" }] : []));
26
+ this.wrapperEl = signal(null, ...(ngDevMode ? [{ debugName: "wrapperEl" }] : []));
27
+ // --- Internal state (still owned by main service for backward compat) ---
28
+ this.internalSelectedRows = signal(new Set(), ...(ngDevMode ? [{ debugName: "internalSelectedRows" }] : []));
29
+ // Row selection
30
+ this.lastClickedRow = -1;
31
+ // Header menu state (for column pinning UI)
32
+ this.headerMenuIsOpenSig = signal(false, ...(ngDevMode ? [{ debugName: "headerMenuIsOpenSig" }] : []));
33
+ this.headerMenuOpenForColumnSig = signal(null, ...(ngDevMode ? [{ debugName: "headerMenuOpenForColumnSig" }] : []));
34
+ this.headerMenuAnchorElementSig = signal(null, ...(ngDevMode ? [{ debugName: "headerMenuAnchorElementSig" }] : []));
35
+ // --- Derived computed ---
36
+ this.propsResolved = computed(() => {
37
+ const p = this.props();
38
+ if (!p)
39
+ throw new Error('DataGridStateService: props must be set before use');
40
+ return p;
41
+ }, ...(ngDevMode ? [{ debugName: "propsResolved" }] : []));
42
+ this.cellSelection = computed(() => {
43
+ const p = this.props();
44
+ return p ? p.cellSelection !== false : true;
45
+ }, ...(ngDevMode ? [{ debugName: "cellSelection" }] : []));
46
+ // Narrow signal extractors - prevent full props() dependency in effects/computed
47
+ this.originalOnCellValueChanged = computed(() => this.props()?.onCellValueChanged, ...(ngDevMode ? [{ debugName: "originalOnCellValueChanged" }] : []));
48
+ // Undo/redo wrapped callback - only recomputes when the actual callback reference changes
49
+ this.wrappedOnCellValueChanged = computed(() => {
50
+ const original = this.originalOnCellValueChanged();
51
+ if (!original)
52
+ return undefined;
53
+ return (event) => {
54
+ this.interactionHelper.undoRedoStack.record(event);
55
+ if (!this.interactionHelper.undoRedoStack.isBatching) {
56
+ this.interactionHelper.undoLengthSig.set(this.interactionHelper.undoRedoStack.historyLength);
57
+ this.interactionHelper.redoLengthSig.set(this.interactionHelper.undoRedoStack.redoLength);
58
+ }
59
+ original(event);
60
+ };
61
+ }, ...(ngDevMode ? [{ debugName: "wrappedOnCellValueChanged" }] : []));
62
+ // --- Delegated computed signals from layoutHelper ---
63
+ this.flatColumnsRaw = computed(() => this.layoutHelper.flatColumnsRaw(), ...(ngDevMode ? [{ debugName: "flatColumnsRaw" }] : []));
64
+ this.flatColumns = computed(() => this.layoutHelper.flatColumns(), ...(ngDevMode ? [{ debugName: "flatColumns" }] : []));
65
+ this.visibleCols = computed(() => this.layoutHelper.visibleCols(), ...(ngDevMode ? [{ debugName: "visibleCols" }] : []));
66
+ this.visibleColumnCount = computed(() => this.layoutHelper.visibleColumnCount(), ...(ngDevMode ? [{ debugName: "visibleColumnCount" }] : []));
67
+ this.hasCheckboxCol = computed(() => this.layoutHelper.hasCheckboxCol(), ...(ngDevMode ? [{ debugName: "hasCheckboxCol" }] : []));
68
+ this.hasRowNumbersCol = computed(() => this.layoutHelper.hasRowNumbersCol(), ...(ngDevMode ? [{ debugName: "hasRowNumbersCol" }] : []));
69
+ this.specialColsCount = computed(() => this.layoutHelper.specialColsCount(), ...(ngDevMode ? [{ debugName: "specialColsCount" }] : []));
70
+ this.totalColCount = computed(() => this.layoutHelper.totalColCount(), ...(ngDevMode ? [{ debugName: "totalColCount" }] : []));
71
+ this.colOffset = computed(() => this.layoutHelper.colOffset(), ...(ngDevMode ? [{ debugName: "colOffset" }] : []));
72
+ this.rowIndexByRowId = computed(() => this.layoutHelper.rowIndexByRowId(), ...(ngDevMode ? [{ debugName: "rowIndexByRowId" }] : []));
73
+ this.selectedRowIds = computed(() => {
74
+ const p = this.props();
75
+ if (!p)
76
+ return new Set();
77
+ const controlled = p.selectedRows;
78
+ if (controlled != null) {
79
+ return controlled instanceof Set ? controlled : new Set(controlled);
80
+ }
81
+ return this.internalSelectedRows();
82
+ }, ...(ngDevMode ? [{ debugName: "selectedRowIds" }] : []));
83
+ this.allSelected = computed(() => {
84
+ const p = this.props();
85
+ if (!p || p.items.length === 0)
86
+ return false;
87
+ const selected = this.selectedRowIds();
88
+ // Fast path: if counts don't match, can't be all selected (avoids O(n) .every())
89
+ if (selected.size !== p.items.length)
90
+ return false;
91
+ return p.items.every((item) => selected.has(p.getRowId(item)));
92
+ }, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
93
+ this.someSelected = computed(() => {
94
+ const p = this.props();
95
+ if (!p)
96
+ return false;
97
+ const selected = this.selectedRowIds();
98
+ return !this.allSelected() && p.items.some((item) => selected.has(p.getRowId(item)));
99
+ }, ...(ngDevMode ? [{ debugName: "someSelected" }] : []));
100
+ this.hasCellSelection = computed(() => this.interactionHelper.hasCellSelection(), ...(ngDevMode ? [{ debugName: "hasCellSelection" }] : []));
101
+ this.canUndo = computed(() => this.interactionHelper.canUndo(), ...(ngDevMode ? [{ debugName: "canUndo" }] : []));
102
+ this.canRedo = computed(() => this.interactionHelper.canRedo(), ...(ngDevMode ? [{ debugName: "canRedo" }] : []));
103
+ // Table layout (delegated to layoutHelper)
104
+ this.minTableWidth = computed(() => this.layoutHelper.minTableWidth(), ...(ngDevMode ? [{ debugName: "minTableWidth" }] : []));
105
+ this.desiredTableWidth = computed(() => this.layoutHelper.desiredTableWidth(), ...(ngDevMode ? [{ debugName: "desiredTableWidth" }] : []));
106
+ this.aggregation = computed(() => {
107
+ const p = this.props();
108
+ if (!p)
109
+ return null;
110
+ return computeAggregations(p.items, this.visibleCols(), this.cellSelection() ? this.interactionHelper.selectionRangeSig() : null);
111
+ }, ...(ngDevMode ? [{ debugName: "aggregation" }] : []));
112
+ this.statusBarConfig = computed(() => {
113
+ const p = this.props();
114
+ if (!p)
115
+ return null;
116
+ const base = getDataGridStatusBarConfig(p.statusBar, p.items.length, this.selectedRowIds().size);
117
+ if (!base)
118
+ return null;
119
+ return { ...base, aggregation: this.aggregation() ?? undefined };
120
+ }, ...(ngDevMode ? [{ debugName: "statusBarConfig" }] : []));
121
+ this.showEmptyInGrid = computed(() => {
122
+ const p = this.props();
123
+ if (!p)
124
+ return false;
125
+ return p.items.length === 0 && !!p.emptyState && !p.isLoading;
126
+ }, ...(ngDevMode ? [{ debugName: "showEmptyInGrid" }] : []));
127
+ // --- Stable handler closures (defined once, reused in getState()) ---
128
+ // These arrow properties ensure Angular change detection receives the same
129
+ // function reference on every getState() call, avoiding unnecessary re-renders.
130
+ this._setColumnSizingOverrides = (overrides) => this.layoutHelper.columnSizingOverridesSig.set(overrides);
131
+ this._updateSelection = (ids) => this.updateSelection(ids);
132
+ this._handleRowCheckboxChange = (rowId, checked, rowIndex, shiftKey) => this.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey);
133
+ this._handleSelectAll = (checked) => this.handleSelectAll(checked);
134
+ this._setEditingCell = (cell) => this.setEditingCell(cell);
135
+ this._setPendingEditorValue = (v) => this.setPendingEditorValue(v);
136
+ this._commitCellEdit = (item, colId, oldVal, newVal, rowIdx, globalColIdx, options) => this.commitCellEdit(item, colId, oldVal, newVal, rowIdx, globalColIdx, options);
137
+ this._cancelPopoverEdit = () => this.cancelPopoverEdit();
138
+ this._setPopoverAnchorEl = (el) => this.editingHelper.popoverAnchorElSig.set(el);
139
+ this._setActiveCell = (cell) => this.setActiveCell(cell);
140
+ this._setSelectionRange = (range) => this.setSelectionRange(range);
141
+ this._handleCellMouseDown = (e, r, c) => this.handleCellMouseDown(e, r, c);
142
+ this._handleSelectAllCells = () => this.handleSelectAllCells();
143
+ this._handleGridKeyDown = (e) => this.handleGridKeyDown(e);
144
+ this._handleFillHandleMouseDown = (e) => this.handleFillHandleMouseDown(e);
145
+ this._handleCopy = () => this.handleCopy();
146
+ this._handleCut = () => this.handleCut();
147
+ this._handlePaste = () => this.handlePaste();
148
+ this._clearClipboardRanges = () => this.clearClipboardRanges();
149
+ this._onUndo = () => this.undo();
150
+ this._onRedo = () => this.redo();
151
+ this._setContextMenuPosition = (pos) => this.setContextMenuPosition(pos);
152
+ this._handleCellContextMenu = (e) => this.handleCellContextMenu(e);
153
+ this._closeContextMenu = () => this.closeContextMenu();
154
+ this._headerFilterOnColumnSort = (columnKey, direction) => this.props()?.onColumnSort(columnKey, direction);
155
+ this._headerFilterOnFilterChange = (key, value) => this.props()?.onFilterChange(key, value);
156
+ this._pinColumn = (columnId, side) => this.pinColumn(columnId, side);
157
+ this._unpinColumn = (columnId) => this.unpinColumn(columnId);
158
+ this._isPinned = (columnId) => this.isPinned(columnId);
159
+ this._openHeaderMenu = (columnId, anchorEl) => this.openHeaderMenu(columnId, anchorEl);
160
+ this._closeHeaderMenu = () => this.closeHeaderMenu();
161
+ this._headerMenuPinLeft = () => this.headerMenuPinLeft();
162
+ this._headerMenuPinRight = () => this.headerMenuPinRight();
163
+ this._headerMenuUnpin = () => this.headerMenuUnpin();
164
+ // --- Instantiate sub-helpers ---
165
+ this.layoutHelper = new DataGridLayoutHelper(this.props, this.wrapperEl, this.ngZone);
166
+ this.interactionHelper = new DataGridInteractionHelper();
167
+ this.editingHelper = new DataGridEditingHelper(() => this.visibleCols(), () => this.props()?.items ?? [], () => this.wrappedOnCellValueChanged(), (cell) => this.setActiveCell(cell), (range) => this.setSelectionRange(range), () => this.colOffset());
168
+ // Setup window event listeners for cell selection drag
169
+ // Run outside NgZone to avoid 60Hz change detection during drag
170
+ effect((onCleanup) => {
171
+ const onMove = (e) => this.onWindowMouseMove(e);
172
+ const onUp = () => this.onWindowMouseUp();
173
+ this.ngZone.runOutsideAngular(() => {
174
+ window.addEventListener('pointermove', onMove, true);
175
+ window.addEventListener('pointerup', onUp, true);
176
+ });
177
+ onCleanup(() => {
178
+ window.removeEventListener('pointermove', onMove, true);
179
+ window.removeEventListener('pointerup', onUp, true);
180
+ });
181
+ });
182
+ // Cleanup on destroy - cancel pending work and release references
183
+ this.destroyRef.onDestroy(() => {
184
+ this.interactionHelper.destroy();
185
+ this.layoutHelper.destroy();
186
+ });
187
+ }
188
+ // --- Row selection methods ---
189
+ updateSelection(newSelectedIds) {
190
+ const p = this.props();
191
+ if (!p)
192
+ return;
193
+ if (p.selectedRows === undefined) {
194
+ this.internalSelectedRows.set(newSelectedIds);
195
+ }
196
+ p.onSelectionChange?.({
197
+ selectedRowIds: Array.from(newSelectedIds),
198
+ selectedItems: p.items.filter((item) => newSelectedIds.has(p.getRowId(item))),
199
+ });
200
+ }
201
+ handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey) {
202
+ const p = this.props();
203
+ if (!p)
204
+ return;
205
+ const rowSelection = p.rowSelection ?? 'none';
206
+ if (rowSelection === 'single') {
207
+ this.updateSelection(checked ? new Set([rowId]) : new Set());
208
+ this.lastClickedRow = rowIndex;
209
+ return;
210
+ }
211
+ const next = new Set(this.selectedRowIds());
212
+ if (shiftKey && this.lastClickedRow >= 0 && this.lastClickedRow !== rowIndex) {
213
+ const start = Math.min(this.lastClickedRow, rowIndex);
214
+ const end = Math.max(this.lastClickedRow, rowIndex);
215
+ for (let i = start; i <= end; i++) {
216
+ if (i < p.items.length) {
217
+ const id = p.getRowId(p.items[i]);
218
+ if (checked)
219
+ next.add(id);
220
+ else
221
+ next.delete(id);
222
+ }
223
+ }
224
+ }
225
+ else {
226
+ if (checked)
227
+ next.add(rowId);
228
+ else
229
+ next.delete(rowId);
230
+ }
231
+ this.lastClickedRow = rowIndex;
232
+ this.updateSelection(next);
233
+ }
234
+ handleSelectAll(checked) {
235
+ const p = this.props();
236
+ if (!p)
237
+ return;
238
+ if (checked) {
239
+ this.updateSelection(new Set(p.items.map((item) => p.getRowId(item))));
240
+ }
241
+ else {
242
+ this.updateSelection(new Set());
243
+ }
244
+ }
245
+ // --- Cell editing (delegated to editingHelper) ---
246
+ setEditingCell(cell) {
247
+ this.editingHelper.setEditingCell(cell);
248
+ }
249
+ setPendingEditorValue(value) {
250
+ this.editingHelper.setPendingEditorValue(value);
251
+ }
252
+ setActiveCell(cell) {
253
+ this.interactionHelper.setActiveCell(cell);
254
+ }
255
+ setSelectionRange(range) {
256
+ this.interactionHelper.setSelectionRange(range);
257
+ }
258
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex, options) {
259
+ this.editingHelper.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex, options);
260
+ }
261
+ cancelPopoverEdit() {
262
+ this.editingHelper.cancelPopoverEdit();
263
+ }
264
+ // --- Cell selection / mouse handling (delegated to interactionHelper) ---
265
+ handleCellMouseDown(e, rowIndex, globalColIndex) {
266
+ this.interactionHelper.handleCellMouseDown(e, rowIndex, globalColIndex, this.colOffset(), this.wrapperEl());
267
+ }
268
+ handleSelectAllCells() {
269
+ const p = this.props();
270
+ if (!p)
271
+ return;
272
+ this.interactionHelper.handleSelectAllCells(p.items.length, this.visibleColumnCount(), this.colOffset());
273
+ }
274
+ // --- Context menu (delegated to interactionHelper) ---
275
+ setContextMenuPosition(pos) {
276
+ this.interactionHelper.setContextMenuPosition(pos);
277
+ }
278
+ handleCellContextMenu(e) {
279
+ this.interactionHelper.handleCellContextMenu(e);
280
+ }
281
+ closeContextMenu() {
282
+ this.interactionHelper.closeContextMenu();
283
+ }
284
+ // --- Clipboard (delegated to interactionHelper) ---
285
+ handleCopy() {
286
+ const p = this.props();
287
+ if (!p)
288
+ return;
289
+ this.interactionHelper.handleCopy(p.items, this.visibleCols(), this.colOffset());
290
+ }
291
+ handleCut() {
292
+ const p = this.props();
293
+ if (!p)
294
+ return;
295
+ this.interactionHelper.handleCut(p.items, this.visibleCols(), this.colOffset(), p.editable, this.wrappedOnCellValueChanged());
296
+ }
297
+ async handlePaste() {
298
+ const p = this.props();
299
+ if (!p)
300
+ return;
301
+ await this.interactionHelper.handlePaste(p.items, this.visibleCols(), this.colOffset(), p.editable, this.wrappedOnCellValueChanged());
302
+ this.mutationTick.update((v) => v + 1);
303
+ }
304
+ clearClipboardRanges() {
305
+ this.interactionHelper.clearClipboardRanges();
306
+ }
307
+ // --- Undo/Redo (delegated to interactionHelper) ---
308
+ beginBatch() {
309
+ this.interactionHelper.beginBatch();
310
+ }
311
+ endBatch() {
312
+ this.interactionHelper.endBatch();
313
+ }
314
+ undo() {
315
+ const p = this.props();
316
+ this.interactionHelper.undo(p?.onCellValueChanged);
317
+ this.mutationTick.update((v) => v + 1);
318
+ }
319
+ redo() {
320
+ const p = this.props();
321
+ this.interactionHelper.redo(p?.onCellValueChanged);
322
+ this.mutationTick.update((v) => v + 1);
323
+ }
324
+ // --- Keyboard navigation (delegated to interactionHelper) ---
325
+ handleGridKeyDown(e) {
326
+ const p = this.props();
327
+ if (!p)
328
+ return;
329
+ this.interactionHelper.handleGridKeyDown(e, p.items, p.getRowId, this.visibleCols(), this.colOffset(), this.hasCheckboxCol(), this.visibleColumnCount(), p.editable, this.wrappedOnCellValueChanged(), p.onCellValueChanged, p.rowSelection ?? 'none', this.selectedRowIds(), this.wrapperEl(), (rowId, checked, rowIndex, shiftKey) => this.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey), this.editingHelper.editingCellSig(), (cell) => this.setEditingCell(cell), p.onKeyDown);
330
+ }
331
+ // --- Fill handle (delegated to interactionHelper + setupFillHandleDrag) ---
332
+ handleFillHandleMouseDown(e) {
333
+ this.interactionHelper.handleFillHandleMouseDown(e);
334
+ this.setupFillHandleDrag();
335
+ }
336
+ // --- Column pinning ---
337
+ pinColumn(columnId, side) {
338
+ const props = this.props();
339
+ props?.onColumnPinned?.(columnId, side);
340
+ }
341
+ unpinColumn(columnId) {
342
+ const props = this.props();
343
+ props?.onColumnPinned?.(columnId, null);
344
+ }
345
+ isPinned(columnId) {
346
+ const props = this.props();
347
+ return props?.pinnedColumns?.[columnId];
348
+ }
349
+ getPinState(columnId) {
350
+ const pinned = this.isPinned(columnId);
351
+ return {
352
+ canPinLeft: pinned !== 'left',
353
+ canPinRight: pinned !== 'right',
354
+ canUnpin: !!pinned,
355
+ };
356
+ }
357
+ // --- Header menu ---
358
+ openHeaderMenu(columnId, anchorEl) {
359
+ this.headerMenuOpenForColumnSig.set(columnId);
360
+ this.headerMenuAnchorElementSig.set(anchorEl);
361
+ this.headerMenuIsOpenSig.set(true);
362
+ }
363
+ closeHeaderMenu() {
364
+ this.headerMenuIsOpenSig.set(false);
365
+ this.headerMenuOpenForColumnSig.set(null);
366
+ this.headerMenuAnchorElementSig.set(null);
367
+ }
368
+ headerMenuPinLeft() {
369
+ const col = this.headerMenuOpenForColumnSig();
370
+ if (col && this.isPinned(col) !== 'left') {
371
+ this.pinColumn(col, 'left');
372
+ this.closeHeaderMenu();
373
+ }
374
+ }
375
+ headerMenuPinRight() {
376
+ const col = this.headerMenuOpenForColumnSig();
377
+ if (col && this.isPinned(col) !== 'right') {
378
+ this.pinColumn(col, 'right');
379
+ this.closeHeaderMenu();
380
+ }
381
+ }
382
+ headerMenuUnpin() {
383
+ const col = this.headerMenuOpenForColumnSig();
384
+ if (col && this.isPinned(col)) {
385
+ this.unpinColumn(col);
386
+ this.closeHeaderMenu();
387
+ }
388
+ }
389
+ // --- Get state result ---
390
+ getState() {
391
+ this.mutationTick();
392
+ const p = this.props();
393
+ const cellSel = this.cellSelection();
394
+ const layout = {
395
+ flatColumns: this.flatColumns(),
396
+ visibleCols: this.visibleCols(),
397
+ visibleColumnCount: this.visibleColumnCount(),
398
+ totalColCount: this.totalColCount(),
399
+ colOffset: this.colOffset(),
400
+ hasCheckboxCol: this.hasCheckboxCol(),
401
+ hasRowNumbersCol: this.hasRowNumbersCol(),
402
+ rowIndexByRowId: this.rowIndexByRowId(),
403
+ containerWidth: this.layoutHelper.containerWidthSig(),
404
+ minTableWidth: this.minTableWidth(),
405
+ desiredTableWidth: this.desiredTableWidth(),
406
+ columnSizingOverrides: this.layoutHelper.columnSizingOverridesSig(),
407
+ setColumnSizingOverrides: this._setColumnSizingOverrides,
408
+ onColumnResized: p?.onColumnResized,
409
+ onAutosizeColumn: p?.onAutosizeColumn,
410
+ };
411
+ const rowSelection = {
412
+ selectedRowIds: this.selectedRowIds(),
413
+ updateSelection: this._updateSelection,
414
+ handleRowCheckboxChange: this._handleRowCheckboxChange,
415
+ handleSelectAll: this._handleSelectAll,
416
+ allSelected: this.allSelected(),
417
+ someSelected: this.someSelected(),
418
+ };
419
+ const editing = {
420
+ editingCell: this.editingHelper.editingCellSig(),
421
+ setEditingCell: this._setEditingCell,
422
+ pendingEditorValue: this.editingHelper.pendingEditorValueSig(),
423
+ setPendingEditorValue: this._setPendingEditorValue,
424
+ commitCellEdit: this._commitCellEdit,
425
+ cancelPopoverEdit: this._cancelPopoverEdit,
426
+ popoverAnchorEl: this.editingHelper.popoverAnchorElSig(),
427
+ setPopoverAnchorEl: this._setPopoverAnchorEl,
428
+ };
429
+ const interaction = {
430
+ activeCell: cellSel ? this.interactionHelper.activeCellSig() : null,
431
+ setActiveCell: cellSel ? this._setActiveCell : undefined,
432
+ selectionRange: cellSel ? this.interactionHelper.selectionRangeSig() : null,
433
+ setSelectionRange: cellSel ? this._setSelectionRange : undefined,
434
+ handleCellMouseDown: cellSel ? this._handleCellMouseDown : NOOP_MOUSE,
435
+ handleSelectAllCells: cellSel ? this._handleSelectAllCells : NOOP,
436
+ hasCellSelection: cellSel ? this.hasCellSelection() : false,
437
+ handleGridKeyDown: cellSel ? this._handleGridKeyDown : NOOP_KEY,
438
+ handleFillHandleMouseDown: cellSel ? this._handleFillHandleMouseDown : undefined,
439
+ handleCopy: cellSel ? this._handleCopy : NOOP,
440
+ handleCut: cellSel ? this._handleCut : NOOP,
441
+ handlePaste: cellSel ? this._handlePaste : NOOP_ASYNC,
442
+ cutRange: cellSel ? this.interactionHelper.cutRangeSig() : null,
443
+ copyRange: cellSel ? this.interactionHelper.copyRangeSig() : null,
444
+ clearClipboardRanges: cellSel ? this._clearClipboardRanges : NOOP,
445
+ canUndo: this.canUndo(),
446
+ canRedo: this.canRedo(),
447
+ onUndo: this._onUndo,
448
+ onRedo: this._onRedo,
449
+ isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false,
450
+ };
451
+ const contextMenu = {
452
+ menuPosition: cellSel ? this.interactionHelper.contextMenuPositionSig() : null,
453
+ setMenuPosition: cellSel ? this._setContextMenuPosition : undefined,
454
+ handleCellContextMenu: cellSel ? this._handleCellContextMenu : NOOP_CTX,
455
+ closeContextMenu: cellSel ? this._closeContextMenu : NOOP,
456
+ };
457
+ const viewModels = {
458
+ headerFilterInput: {
459
+ sortBy: p?.sortBy,
460
+ sortDirection: p?.sortDirection ?? 'asc',
461
+ onColumnSort: this._headerFilterOnColumnSort,
462
+ filters: p?.filters ?? {},
463
+ onFilterChange: this._headerFilterOnFilterChange,
464
+ filterOptions: p?.filterOptions ?? {},
465
+ loadingFilterOptions: p?.loadingFilterOptions ?? {},
466
+ peopleSearch: p?.peopleSearch,
467
+ },
468
+ cellDescriptorInput: {
469
+ editingCell: this.editingHelper.editingCellSig(),
470
+ activeCell: cellSel ? this.interactionHelper.activeCellSig() : null,
471
+ selectionRange: cellSel ? this.interactionHelper.selectionRangeSig() : null,
472
+ cutRange: cellSel ? this.interactionHelper.cutRangeSig() : null,
473
+ copyRange: cellSel ? this.interactionHelper.copyRangeSig() : null,
474
+ colOffset: this.colOffset(),
475
+ itemsLength: p?.items.length ?? 0,
476
+ getRowId: p?.getRowId ?? ((item) => item['id']),
477
+ editable: p?.editable,
478
+ onCellValueChanged: this.wrappedOnCellValueChanged(),
479
+ isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false,
480
+ getFormulaValue: p?.getFormulaValue,
481
+ hasFormula: p?.hasFormula,
482
+ getFormula: p?.getFormula,
483
+ formulaVersion: p?.formulaVersion,
484
+ },
485
+ statusBarConfig: this.statusBarConfig(),
486
+ showEmptyInGrid: this.showEmptyInGrid(),
487
+ onCellError: p?.onCellError,
488
+ };
489
+ // --- Pinning ---
490
+ const openForColumn = this.headerMenuOpenForColumnSig();
491
+ const currentPinState = openForColumn ? (p?.pinnedColumns?.[openForColumn]) : undefined;
492
+ const pinning = {
493
+ pinnedColumns: p?.pinnedColumns ?? {},
494
+ pinColumn: this._pinColumn,
495
+ unpinColumn: this._unpinColumn,
496
+ isPinned: this._isPinned,
497
+ headerMenu: {
498
+ isOpen: this.headerMenuIsOpenSig(),
499
+ openForColumn,
500
+ anchorElement: this.headerMenuAnchorElementSig(),
501
+ open: this._openHeaderMenu,
502
+ close: this._closeHeaderMenu,
503
+ handlePinLeft: this._headerMenuPinLeft,
504
+ handlePinRight: this._headerMenuPinRight,
505
+ handleUnpin: this._headerMenuUnpin,
506
+ canPinLeft: currentPinState !== 'left',
507
+ canPinRight: currentPinState !== 'right',
508
+ canUnpin: !!currentPinState,
509
+ },
510
+ };
511
+ return { layout, rowSelection, editing, interaction, contextMenu, viewModels, pinning };
512
+ }
513
+ // --- Private helpers (drag selection delegated to interactionHelper) ---
514
+ onWindowMouseMove(e) {
515
+ this.interactionHelper.onWindowMouseMove(e, this.colOffset(), this.wrapperEl());
516
+ }
517
+ onWindowMouseUp() {
518
+ this.interactionHelper.onWindowMouseUp(this.colOffset(), this.wrapperEl());
519
+ }
520
+ setupFillHandleDrag() {
521
+ const p = this.props();
522
+ const fillDragStart = this.interactionHelper.fillDragStart;
523
+ if (!fillDragStart || p?.editable === false || !this.wrappedOnCellValueChanged())
524
+ return;
525
+ const colOff = this.colOffset();
526
+ const fillStart = fillDragStart;
527
+ let fillDragEnd = { endRow: fillStart.startRow, endCol: fillStart.startCol };
528
+ let liveFillRange = null;
529
+ let lastFillMousePos = null;
530
+ const resolveRange = (cx, cy) => {
531
+ const target = document.elementFromPoint(cx, cy);
532
+ const cell = target?.closest?.('[data-row-index][data-col-index]');
533
+ const wrapper = this.wrapperEl();
534
+ if (!cell || !wrapper?.contains(cell))
535
+ return null;
536
+ const r = parseInt(cell.getAttribute('data-row-index') ?? '', 10);
537
+ const c = parseInt(cell.getAttribute('data-col-index') ?? '', 10);
538
+ if (Number.isNaN(r) || Number.isNaN(c) || c < colOff)
539
+ return null;
540
+ const dataCol = c - colOff;
541
+ return normalizeSelectionRange({
542
+ startRow: fillStart.startRow, startCol: fillStart.startCol,
543
+ endRow: r, endCol: dataCol,
544
+ });
545
+ };
546
+ const onMove = (e) => {
547
+ lastFillMousePos = { cx: e.clientX, cy: e.clientY };
548
+ if (this.interactionHelper.fillRafId)
549
+ cancelAnimationFrame(this.interactionHelper.fillRafId);
550
+ this.interactionHelper.fillRafId = requestAnimationFrame(() => {
551
+ this.interactionHelper.fillRafId = 0;
552
+ if (!lastFillMousePos)
553
+ return;
554
+ const newRange = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
555
+ if (!newRange)
556
+ return;
557
+ if (liveFillRange && liveFillRange.startRow === newRange.startRow &&
558
+ liveFillRange.startCol === newRange.startCol &&
559
+ liveFillRange.endRow === newRange.endRow &&
560
+ liveFillRange.endCol === newRange.endCol)
561
+ return;
562
+ liveFillRange = newRange;
563
+ fillDragEnd = { endRow: newRange.endRow, endCol: newRange.endCol };
564
+ this.interactionHelper.applyDragAttrs(newRange, colOff, this.wrapperEl());
565
+ });
566
+ };
567
+ const onUp = () => {
568
+ window.removeEventListener('pointermove', onMove, true);
569
+ window.removeEventListener('pointerup', onUp, true);
570
+ this.interactionHelper.fillMoveHandler = null;
571
+ this.interactionHelper.fillUpHandler = null;
572
+ if (this.interactionHelper.fillRafId) {
573
+ cancelAnimationFrame(this.interactionHelper.fillRafId);
574
+ this.interactionHelper.fillRafId = 0;
575
+ }
576
+ if (lastFillMousePos) {
577
+ const flushed = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
578
+ if (flushed) {
579
+ liveFillRange = flushed;
580
+ fillDragEnd = { endRow: flushed.endRow, endCol: flushed.endCol };
581
+ }
582
+ }
583
+ this.interactionHelper.clearDragAttrs(this.wrapperEl());
584
+ const norm = normalizeSelectionRange({
585
+ startRow: fillStart.startRow, startCol: fillStart.startCol,
586
+ endRow: fillDragEnd.endRow, endCol: fillDragEnd.endCol,
587
+ });
588
+ this.setSelectionRange(norm);
589
+ this.setActiveCell({ rowIndex: fillStart.startRow, columnIndex: fillStart.startCol + colOff });
590
+ // Apply fill values
591
+ if (!p)
592
+ return;
593
+ const items = p.items;
594
+ const visibleCols = this.visibleCols();
595
+ const startItem = items[norm.startRow];
596
+ const startColDef = visibleCols[norm.startCol];
597
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
598
+ if (startItem && startColDef && onCellValueChanged) {
599
+ const startValue = getCellValue(startItem, startColDef);
600
+ this.beginBatch();
601
+ for (let row = norm.startRow; row <= norm.endRow; row++) {
602
+ for (let col = norm.startCol; col <= norm.endCol; col++) {
603
+ if (row === fillStart.startRow && col === fillStart.startCol)
604
+ continue;
605
+ if (row >= items.length || col >= visibleCols.length)
606
+ continue;
607
+ const item = items[row];
608
+ const colDef = visibleCols[col];
609
+ const colEditable = colDef.editable === true || (typeof colDef.editable === 'function' && colDef.editable(item));
610
+ if (!colEditable)
611
+ continue;
612
+ const oldValue = getCellValue(item, colDef);
613
+ const result = parseValue(startValue, oldValue, item, colDef);
614
+ if (!result.valid)
615
+ continue;
616
+ onCellValueChanged({ item, columnId: colDef.columnId, oldValue, newValue: result.value, rowIndex: row });
617
+ }
618
+ }
619
+ this.endBatch();
620
+ }
621
+ this.interactionHelper.fillDragStart = null;
622
+ };
623
+ // Track handlers for cleanup on destroy
624
+ this.interactionHelper.fillMoveHandler = onMove;
625
+ this.interactionHelper.fillUpHandler = onUp;
626
+ this.ngZone.runOutsideAngular(() => {
627
+ window.addEventListener('pointermove', onMove, true);
628
+ window.addEventListener('pointerup', onUp, true);
629
+ });
630
+ }
631
+ static { this.ɵfac = function DataGridStateService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DataGridStateService)(); }; }
632
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: DataGridStateService, factory: DataGridStateService.ɵfac }); }
633
+ }
634
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataGridStateService, [{
635
+ type: Injectable
636
+ }], () => [], null); })();