@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,176 @@
1
+ import { Injectable, signal, DestroyRef, inject } from '@angular/core';
2
+ import { reorderColumnArray } from '@alaarab/ogrid-core';
3
+ import * as i0 from "@angular/core";
4
+ /** Width of the resize handle zone on the right edge of each header cell. */
5
+ const RESIZE_HANDLE_ZONE = 8;
6
+ /**
7
+ * Manages column reorder drag interactions with RAF-throttled updates.
8
+ * Angular signals-based port of React's useColumnReorder hook.
9
+ */
10
+ export class ColumnReorderService {
11
+ constructor() {
12
+ this.destroyRef = inject(DestroyRef);
13
+ // --- Input signals (set by consuming component) ---
14
+ this.columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : []));
15
+ this.columnOrder = signal(undefined, ...(ngDevMode ? [{ debugName: "columnOrder" }] : []));
16
+ this.onColumnOrderChange = signal(undefined, ...(ngDevMode ? [{ debugName: "onColumnOrderChange" }] : []));
17
+ this.enabled = signal(true, ...(ngDevMode ? [{ debugName: "enabled" }] : []));
18
+ this.wrapperEl = signal(null, ...(ngDevMode ? [{ debugName: "wrapperEl" }] : []));
19
+ // --- Internal state ---
20
+ this.isDragging = signal(false, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
21
+ this.dropIndicatorX = signal(null, ...(ngDevMode ? [{ debugName: "dropIndicatorX" }] : []));
22
+ // Imperative drag tracking (not reactive)
23
+ this.rafId = 0;
24
+ this.cleanupFn = null;
25
+ // Refs for latest values (captured in closure)
26
+ this.latestDropTargetIndex = null;
27
+ this.destroyRef.onDestroy(() => {
28
+ if (this.cleanupFn) {
29
+ this.cleanupFn();
30
+ this.cleanupFn = null;
31
+ }
32
+ if (this.rafId) {
33
+ cancelAnimationFrame(this.rafId);
34
+ this.rafId = 0;
35
+ }
36
+ });
37
+ }
38
+ /**
39
+ * Call this from the header cell's mousedown handler.
40
+ * @param columnId - The column being dragged
41
+ * @param event - The native MouseEvent
42
+ */
43
+ handleHeaderMouseDown(columnId, event) {
44
+ if (!this.enabled())
45
+ return;
46
+ if (!this.onColumnOrderChange())
47
+ return;
48
+ // Gate on left-click only
49
+ if (event.button !== 0)
50
+ return;
51
+ // Skip if in resize handle zone (right 8px of the header cell)
52
+ const target = event.currentTarget;
53
+ const rect = target.getBoundingClientRect();
54
+ if (event.clientX > rect.right - RESIZE_HANDLE_ZONE)
55
+ return;
56
+ // Skip column groups - only reorder leaf columns
57
+ const cols = this.columns();
58
+ const colIndex = cols.findIndex((c) => c.columnId === columnId);
59
+ if (colIndex === -1)
60
+ return;
61
+ event.preventDefault();
62
+ const startX = event.clientX;
63
+ let hasMoved = false;
64
+ this.latestDropTargetIndex = null;
65
+ // Lock text selection during drag
66
+ const prevUserSelect = document.body.style.userSelect;
67
+ document.body.style.userSelect = 'none';
68
+ const onMove = (moveEvent) => {
69
+ // Require a small minimum drag distance before activating
70
+ if (!hasMoved && Math.abs(moveEvent.clientX - startX) < 5)
71
+ return;
72
+ if (!hasMoved) {
73
+ hasMoved = true;
74
+ this.isDragging.set(true);
75
+ }
76
+ if (this.rafId)
77
+ cancelAnimationFrame(this.rafId);
78
+ this.rafId = requestAnimationFrame(() => {
79
+ this.rafId = 0;
80
+ const wrapper = this.wrapperEl();
81
+ if (!wrapper)
82
+ return;
83
+ const headerCells = wrapper.querySelectorAll('th[data-column-id]');
84
+ const rects = [];
85
+ for (let i = 0; i < headerCells.length; i++) {
86
+ const th = headerCells[i];
87
+ const id = th.getAttribute('data-column-id');
88
+ if (!id)
89
+ continue;
90
+ const thRect = th.getBoundingClientRect();
91
+ rects.push({
92
+ columnId: id,
93
+ left: thRect.left,
94
+ right: thRect.right,
95
+ centerX: thRect.left + thRect.width / 2,
96
+ });
97
+ }
98
+ const result = this.calculateDrop(columnId, moveEvent.clientX, rects);
99
+ this.latestDropTargetIndex = result.dropIndex;
100
+ this.dropIndicatorX.set(result.indicatorX);
101
+ });
102
+ };
103
+ const cleanup = () => {
104
+ window.removeEventListener('pointermove', onMove, true);
105
+ window.removeEventListener('pointerup', onUp, true);
106
+ this.cleanupFn = null;
107
+ // Restore user-select
108
+ document.body.style.userSelect = prevUserSelect;
109
+ // Cancel pending RAF
110
+ if (this.rafId) {
111
+ cancelAnimationFrame(this.rafId);
112
+ this.rafId = 0;
113
+ }
114
+ };
115
+ const onUp = () => {
116
+ cleanup();
117
+ if (hasMoved && this.latestDropTargetIndex != null) {
118
+ const currentOrder = this.columnOrder() ?? this.columns().map((c) => c.columnId);
119
+ const newOrder = reorderColumnArray(currentOrder, columnId, this.latestDropTargetIndex);
120
+ this.onColumnOrderChange()?.(newOrder);
121
+ }
122
+ this.isDragging.set(false);
123
+ this.dropIndicatorX.set(null);
124
+ };
125
+ window.addEventListener('pointermove', onMove, true);
126
+ window.addEventListener('pointerup', onUp, true);
127
+ this.cleanupFn = cleanup;
128
+ }
129
+ /**
130
+ * Calculate drop target from mouse position and header cell rects.
131
+ * Same logic as React's useColumnReorder inline calculation.
132
+ */
133
+ calculateDrop(draggedColumnId, mouseX, rects) {
134
+ if (rects.length === 0) {
135
+ return { dropIndex: null, indicatorX: null };
136
+ }
137
+ const order = this.columnOrder() ?? this.columns().map((c) => c.columnId);
138
+ const currentIndex = order.indexOf(draggedColumnId);
139
+ // Find which column the mouse is closest to
140
+ let bestIndex = 0;
141
+ let indicatorX = null;
142
+ if (mouseX <= rects[0].centerX) {
143
+ // Before the first column
144
+ bestIndex = 0;
145
+ indicatorX = rects[0].left;
146
+ }
147
+ else if (mouseX >= rects[rects.length - 1].centerX) {
148
+ // After the last column
149
+ bestIndex = rects.length;
150
+ indicatorX = rects[rects.length - 1].right;
151
+ }
152
+ else {
153
+ for (let i = 0; i < rects.length - 1; i++) {
154
+ if (mouseX >= rects[i].centerX && mouseX < rects[i + 1].centerX) {
155
+ bestIndex = i + 1;
156
+ indicatorX = rects[i].right;
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ // Map visual index back to order array index
162
+ const targetOrderIndex = bestIndex < rects.length
163
+ ? order.indexOf(rects[bestIndex]?.columnId ?? '')
164
+ : order.length;
165
+ // Check if this is a no-op (dropping at same position)
166
+ if (currentIndex === targetOrderIndex || currentIndex + 1 === targetOrderIndex) {
167
+ return { dropIndex: targetOrderIndex, indicatorX: null };
168
+ }
169
+ return { dropIndex: targetOrderIndex, indicatorX };
170
+ }
171
+ static { this.ɵfac = function ColumnReorderService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ColumnReorderService)(); }; }
172
+ static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ColumnReorderService, factory: ColumnReorderService.ɵfac }); }
173
+ }
174
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ColumnReorderService, [{
175
+ type: Injectable
176
+ }], () => [], null); })();
@@ -0,0 +1,59 @@
1
+ import { signal } from '@angular/core';
2
+ import { parseValue, } from '@alaarab/ogrid-core';
3
+ /**
4
+ * Manages cell editing state, inline/popover editor, and commit/cancel logic.
5
+ * Extracted from DataGridStateService for modularity.
6
+ *
7
+ * Not @Injectable - instantiated and owned by DataGridStateService.
8
+ */
9
+ export class DataGridEditingHelper {
10
+ constructor(getVisibleCols, getItems, getWrappedOnCellValueChanged, setActiveCellFn, setSelectionRangeFn, getColOffset) {
11
+ this.editingCellSig = signal(null, ...(ngDevMode ? [{ debugName: "editingCellSig" }] : []));
12
+ this.pendingEditorValueSig = signal(undefined, ...(ngDevMode ? [{ debugName: "pendingEditorValueSig" }] : []));
13
+ this.popoverAnchorElSig = signal(null, ...(ngDevMode ? [{ debugName: "popoverAnchorElSig" }] : []));
14
+ this.getVisibleCols = getVisibleCols;
15
+ this.getItems = getItems;
16
+ this.getWrappedOnCellValueChanged = getWrappedOnCellValueChanged;
17
+ this.setActiveCellFn = setActiveCellFn;
18
+ this.setSelectionRangeFn = setSelectionRangeFn;
19
+ this.getColOffset = getColOffset;
20
+ }
21
+ setEditingCell(cell) {
22
+ this.editingCellSig.set(cell);
23
+ }
24
+ setPendingEditorValue(value) {
25
+ this.pendingEditorValueSig.set(value);
26
+ }
27
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex, options) {
28
+ const col = this.getVisibleCols().find((c) => c.columnId === columnId);
29
+ if (col) {
30
+ const result = parseValue(newValue, oldValue, item, col);
31
+ if (!result.valid) {
32
+ this.editingCellSig.set(null);
33
+ this.popoverAnchorElSig.set(null);
34
+ this.pendingEditorValueSig.set(undefined);
35
+ return;
36
+ }
37
+ newValue = result.value;
38
+ }
39
+ this.editingCellSig.set(null);
40
+ this.popoverAnchorElSig.set(null);
41
+ this.pendingEditorValueSig.set(undefined);
42
+ if (!Object.is(newValue, oldValue)) {
43
+ const onCellValueChanged = this.getWrappedOnCellValueChanged();
44
+ onCellValueChanged?.({ item, columnId, oldValue, newValue, rowIndex });
45
+ }
46
+ const items = this.getItems();
47
+ if (!options?.skipAdvance && rowIndex < items.length - 1) {
48
+ const newRow = rowIndex + 1;
49
+ const localCol = globalColIndex - this.getColOffset();
50
+ this.setActiveCellFn({ rowIndex: newRow, columnIndex: globalColIndex });
51
+ this.setSelectionRangeFn({ startRow: newRow, startCol: localCol, endRow: newRow, endCol: localCol });
52
+ }
53
+ }
54
+ cancelPopoverEdit() {
55
+ this.editingCellSig.set(null);
56
+ this.popoverAnchorElSig.set(null);
57
+ this.pendingEditorValueSig.set(undefined);
58
+ }
59
+ }