@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.
- package/dist/esm/components/base-column-chooser.component.js +77 -0
- package/dist/esm/components/base-column-header-filter.component.js +267 -0
- package/dist/esm/components/base-column-header-menu.component.js +80 -0
- package/dist/esm/components/base-datagrid-table.component.js +768 -0
- package/dist/esm/components/base-inline-cell-editor.component.js +380 -0
- package/dist/esm/components/base-ogrid.component.js +36 -0
- package/dist/esm/components/base-pagination-controls.component.js +68 -0
- package/dist/esm/components/base-popover-cell-editor.component.js +122 -0
- package/dist/esm/components/empty-state.component.js +68 -0
- package/dist/esm/components/formula-bar.component.js +99 -0
- package/dist/esm/components/formula-ref-overlay.component.js +115 -0
- package/dist/esm/components/grid-context-menu.component.js +197 -0
- package/dist/esm/components/inline-cell-editor-template.js +134 -0
- package/dist/esm/components/marching-ants-overlay.component.js +177 -0
- package/dist/esm/components/ogrid-layout.component.js +302 -0
- package/dist/esm/components/sheet-tabs.component.js +83 -0
- package/dist/esm/components/sidebar.component.js +431 -0
- package/dist/esm/components/status-bar.component.js +92 -0
- package/dist/esm/index.js +39 -819
- package/dist/esm/services/column-reorder.service.js +176 -0
- package/dist/esm/services/datagrid-editing.service.js +59 -0
- package/dist/esm/services/datagrid-interaction.service.js +744 -0
- package/dist/esm/services/datagrid-layout.service.js +157 -0
- package/dist/esm/services/datagrid-state.service.js +636 -0
- package/dist/esm/services/formula-engine.service.js +223 -0
- package/dist/esm/services/ogrid.service.js +1094 -0
- package/dist/esm/services/virtual-scroll.service.js +114 -0
- package/dist/esm/styles/ogrid-theme-vars.js +112 -0
- package/dist/esm/types/columnTypes.js +1 -0
- package/dist/esm/types/dataGridTypes.js +1 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/utils/dataGridViewModel.js +6 -0
- package/dist/esm/utils/debounce.js +68 -0
- package/dist/esm/utils/index.js +8 -0
- package/dist/esm/utils/latestRef.js +41 -0
- package/dist/types/components/base-column-chooser.component.d.ts +3 -0
- package/dist/types/components/base-column-header-filter.component.d.ts +3 -0
- package/dist/types/components/base-column-header-menu.component.d.ts +3 -0
- package/dist/types/components/base-datagrid-table.component.d.ts +3 -0
- package/dist/types/components/base-inline-cell-editor.component.d.ts +7 -0
- package/dist/types/components/base-ogrid.component.d.ts +3 -0
- package/dist/types/components/base-pagination-controls.component.d.ts +3 -0
- package/dist/types/components/base-popover-cell-editor.component.d.ts +3 -0
- package/dist/types/components/empty-state.component.d.ts +3 -0
- package/dist/types/components/formula-bar.component.d.ts +3 -8
- package/dist/types/components/formula-ref-overlay.component.d.ts +3 -6
- package/dist/types/components/grid-context-menu.component.d.ts +3 -0
- package/dist/types/components/inline-cell-editor-template.d.ts +2 -2
- package/dist/types/components/marching-ants-overlay.component.d.ts +3 -0
- package/dist/types/components/ogrid-layout.component.d.ts +3 -0
- package/dist/types/components/sheet-tabs.component.d.ts +3 -8
- package/dist/types/components/sidebar.component.d.ts +3 -0
- package/dist/types/components/status-bar.component.d.ts +3 -0
- package/dist/types/services/column-reorder.service.d.ts +3 -0
- package/dist/types/services/datagrid-interaction.service.d.ts +1 -0
- package/dist/types/services/datagrid-state.service.d.ts +5 -0
- package/dist/types/services/formula-engine.service.d.ts +3 -9
- package/dist/types/services/ogrid.service.d.ts +8 -2
- package/dist/types/services/virtual-scroll.service.d.ts +3 -0
- 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
|
+
}
|