@alaarab/ogrid-angular 2.6.0 → 2.7.0
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 +777 -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 +119 -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 +623 -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 +10 -19
- 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 +8 -3
- 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/index.d.ts +0 -2
- 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 -11
- package/dist/types/services/virtual-scroll.service.d.ts +3 -0
- package/dist/types/types/dataGridTypes.d.ts +1 -4
- package/package.json +4 -3
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import { Directive, signal, computed, effect, inject } from '@angular/core';
|
|
2
|
+
import { DataGridStateService } from '../services/datagrid-state.service';
|
|
3
|
+
import { ColumnReorderService } from '../services/column-reorder.service';
|
|
4
|
+
import { VirtualScrollService } from '../services/virtual-scroll.service';
|
|
5
|
+
import { buildHeaderRows, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, ROW_NUMBER_COLUMN_ID, ROW_NUMBER_COLUMN_MIN_WIDTH, measureColumnContentWidth, estimateHeaderMinWidth, partitionColumnsForVirtualization, handleBooleanCellPointerDown, } from '@alaarab/ogrid-core';
|
|
6
|
+
import { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildPopoverEditorProps, } from '../utils';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
/**
|
|
9
|
+
* Abstract base class containing all shared TypeScript logic for DataGridTable components.
|
|
10
|
+
* Framework-specific UI packages extend this with their templates and style overrides.
|
|
11
|
+
*
|
|
12
|
+
* Subclasses must:
|
|
13
|
+
* 1. Provide a @Component decorator with template and styles
|
|
14
|
+
* 2. Call `initBase()` in the constructor (effects require injection context)
|
|
15
|
+
* 3. Implement abstract accessors for propsInput, wrapperRef, and tableContainerRef
|
|
16
|
+
*/
|
|
17
|
+
export class BaseDataGridTableComponent {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.stateService = inject(DataGridStateService);
|
|
20
|
+
this.columnReorderService = inject(ColumnReorderService);
|
|
21
|
+
this.virtualScrollService = inject(VirtualScrollService);
|
|
22
|
+
this.lastMouseShift = false;
|
|
23
|
+
this.columnSizingVersion = signal(0, ...(ngDevMode ? [{ debugName: "columnSizingVersion" }] : []));
|
|
24
|
+
/** Dirty flag - set when column layout changes, cleared after measurement. */
|
|
25
|
+
this.measureDirty = signal(true, ...(ngDevMode ? [{ debugName: "measureDirty" }] : []));
|
|
26
|
+
/** DOM-measured column widths from the last layout pass.
|
|
27
|
+
* Used as a minWidth floor to prevent columns from shrinking
|
|
28
|
+
* when new data loads (e.g. server-side pagination). */
|
|
29
|
+
this.measuredColumnWidths = signal({}, ...(ngDevMode ? [{ debugName: "measuredColumnWidths" }] : []));
|
|
30
|
+
// Signal-backed view child elements - set from ngAfterViewInit.
|
|
31
|
+
// @ViewChild is a plain property (not a signal), so effects/computed that read it
|
|
32
|
+
// only evaluate once during construction when the ref is still undefined.
|
|
33
|
+
this.wrapperElSignal = signal(null, ...(ngDevMode ? [{ debugName: "wrapperElSignal" }] : []));
|
|
34
|
+
this.tableContainerElSignal = signal(null, ...(ngDevMode ? [{ debugName: "tableContainerElSignal" }] : []));
|
|
35
|
+
// --- Delegated state ---
|
|
36
|
+
this.state = computed(() => this.stateService.getState(), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
37
|
+
// Intermediate computed signals - narrow slices of state() so leaf computeds
|
|
38
|
+
// only recompute when their specific sub-state changes.
|
|
39
|
+
this.layoutState = computed(() => this.state().layout, ...(ngDevMode ? [{ debugName: "layoutState" }] : []));
|
|
40
|
+
this.rowSelectionState = computed(() => this.state().rowSelection, ...(ngDevMode ? [{ debugName: "rowSelectionState" }] : []));
|
|
41
|
+
this.editingState = computed(() => this.state().editing, ...(ngDevMode ? [{ debugName: "editingState" }] : []));
|
|
42
|
+
this.interactionState = computed(() => this.state().interaction, ...(ngDevMode ? [{ debugName: "interactionState" }] : []));
|
|
43
|
+
this.contextMenuState = computed(() => this.state().contextMenu, ...(ngDevMode ? [{ debugName: "contextMenuState" }] : []));
|
|
44
|
+
this.viewModelsState = computed(() => this.state().viewModels, ...(ngDevMode ? [{ debugName: "viewModelsState" }] : []));
|
|
45
|
+
this.pinningState = computed(() => this.state().pinning, ...(ngDevMode ? [{ debugName: "pinningState" }] : []));
|
|
46
|
+
this.tableContainerEl = computed(() => this.tableContainerElSignal(), ...(ngDevMode ? [{ debugName: "tableContainerEl" }] : []));
|
|
47
|
+
this.items = computed(() => this.getProps()?.items ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
48
|
+
this.getRowId = computed(() => this.getProps()?.getRowId ?? ((item) => item['id']), ...(ngDevMode ? [{ debugName: "getRowId" }] : []));
|
|
49
|
+
this.isLoading = computed(() => this.getProps()?.isLoading ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
50
|
+
this.loadingMessage = computed(() => 'Loading\u2026', ...(ngDevMode ? [{ debugName: "loadingMessage" }] : []));
|
|
51
|
+
this.layoutModeFit = computed(() => (this.getProps()?.layoutMode ?? 'fill') === 'content', ...(ngDevMode ? [{ debugName: "layoutModeFit" }] : []));
|
|
52
|
+
this.rowHeightCssVar = computed(() => {
|
|
53
|
+
const rh = this.getProps()?.rowHeight;
|
|
54
|
+
return rh ? `${rh}px` : null;
|
|
55
|
+
}, ...(ngDevMode ? [{ debugName: "rowHeightCssVar" }] : []));
|
|
56
|
+
this.ariaLabel = computed(() => this.getProps()?.['aria-label'] ?? 'Data grid', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
57
|
+
this.ariaLabelledBy = computed(() => this.getProps()?.['aria-labelledby'], ...(ngDevMode ? [{ debugName: "ariaLabelledBy" }] : []));
|
|
58
|
+
this.stickyHeader = computed(() => this.getProps()?.stickyHeader ?? true, ...(ngDevMode ? [{ debugName: "stickyHeader" }] : []));
|
|
59
|
+
this.emptyState = computed(() => this.getProps()?.emptyState, ...(ngDevMode ? [{ debugName: "emptyState" }] : []));
|
|
60
|
+
this.currentPage = computed(() => this.getProps()?.currentPage ?? 1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
61
|
+
this.pageSize = computed(() => this.getProps()?.pageSize ?? 25, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
62
|
+
this.rowNumberOffset = computed(() => this.hasRowNumbersCol() ? (this.currentPage() - 1) * this.pageSize() : 0, ...(ngDevMode ? [{ debugName: "rowNumberOffset" }] : []));
|
|
63
|
+
this.propsVisibleColumns = computed(() => this.getProps()?.visibleColumns, ...(ngDevMode ? [{ debugName: "propsVisibleColumns" }] : []));
|
|
64
|
+
this.propsColumnOrder = computed(() => this.getProps()?.columnOrder, ...(ngDevMode ? [{ debugName: "propsColumnOrder" }] : []));
|
|
65
|
+
// State service outputs - read from narrow intermediate signals
|
|
66
|
+
this.visibleCols = computed(() => this.layoutState().visibleCols, ...(ngDevMode ? [{ debugName: "visibleCols" }] : []));
|
|
67
|
+
this.hasCheckboxCol = computed(() => this.layoutState().hasCheckboxCol, ...(ngDevMode ? [{ debugName: "hasCheckboxCol" }] : []));
|
|
68
|
+
this.hasRowNumbersCol = computed(() => this.layoutState().hasRowNumbersCol, ...(ngDevMode ? [{ debugName: "hasRowNumbersCol" }] : []));
|
|
69
|
+
this.colOffset = computed(() => this.layoutState().colOffset, ...(ngDevMode ? [{ debugName: "colOffset" }] : []));
|
|
70
|
+
this.containerWidth = computed(() => this.layoutState().containerWidth, ...(ngDevMode ? [{ debugName: "containerWidth" }] : []));
|
|
71
|
+
this.minTableWidth = computed(() => this.layoutState().minTableWidth, ...(ngDevMode ? [{ debugName: "minTableWidth" }] : []));
|
|
72
|
+
this.desiredTableWidth = computed(() => this.layoutState().desiredTableWidth, ...(ngDevMode ? [{ debugName: "desiredTableWidth" }] : []));
|
|
73
|
+
this.columnSizingOverrides = computed(() => this.layoutState().columnSizingOverrides, ...(ngDevMode ? [{ debugName: "columnSizingOverrides" }] : []));
|
|
74
|
+
this.selectedRowIds = computed(() => this.rowSelectionState().selectedRowIds, ...(ngDevMode ? [{ debugName: "selectedRowIds" }] : []));
|
|
75
|
+
this.allSelected = computed(() => this.rowSelectionState().allSelected, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
|
|
76
|
+
this.someSelected = computed(() => this.rowSelectionState().someSelected, ...(ngDevMode ? [{ debugName: "someSelected" }] : []));
|
|
77
|
+
this.editingCell = computed(() => this.editingState().editingCell, ...(ngDevMode ? [{ debugName: "editingCell" }] : []));
|
|
78
|
+
this.pendingEditorValue = computed(() => this.editingState().pendingEditorValue, ...(ngDevMode ? [{ debugName: "pendingEditorValue" }] : []));
|
|
79
|
+
this.activeCell = computed(() => this.interactionState().activeCell, ...(ngDevMode ? [{ debugName: "activeCell" }] : []));
|
|
80
|
+
this.selectionRange = computed(() => this.interactionState().selectionRange, ...(ngDevMode ? [{ debugName: "selectionRange" }] : []));
|
|
81
|
+
this.hasCellSelection = computed(() => this.interactionState().hasCellSelection, ...(ngDevMode ? [{ debugName: "hasCellSelection" }] : []));
|
|
82
|
+
this.cutRange = computed(() => this.interactionState().cutRange, ...(ngDevMode ? [{ debugName: "cutRange" }] : []));
|
|
83
|
+
this.copyRange = computed(() => this.interactionState().copyRange, ...(ngDevMode ? [{ debugName: "copyRange" }] : []));
|
|
84
|
+
this.canUndo = computed(() => this.interactionState().canUndo, ...(ngDevMode ? [{ debugName: "canUndo" }] : []));
|
|
85
|
+
this.canRedo = computed(() => this.interactionState().canRedo, ...(ngDevMode ? [{ debugName: "canRedo" }] : []));
|
|
86
|
+
this.isDragging = computed(() => this.interactionState().isDragging, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
87
|
+
this.menuPosition = computed(() => this.contextMenuState().menuPosition, ...(ngDevMode ? [{ debugName: "menuPosition" }] : []));
|
|
88
|
+
this.statusBarConfig = computed(() => this.viewModelsState().statusBarConfig, ...(ngDevMode ? [{ debugName: "statusBarConfig" }] : []));
|
|
89
|
+
this.showEmptyInGrid = computed(() => this.viewModelsState().showEmptyInGrid, ...(ngDevMode ? [{ debugName: "showEmptyInGrid" }] : []));
|
|
90
|
+
this.headerFilterInput = computed(() => this.viewModelsState().headerFilterInput, ...(ngDevMode ? [{ debugName: "headerFilterInput" }] : []));
|
|
91
|
+
this.cellDescriptorInput = computed(() => this.viewModelsState().cellDescriptorInput, ...(ngDevMode ? [{ debugName: "cellDescriptorInput" }] : []));
|
|
92
|
+
// Pinning state
|
|
93
|
+
this.pinnedColumnsMap = computed(() => this.pinningState().pinnedColumns, ...(ngDevMode ? [{ debugName: "pinnedColumnsMap" }] : []));
|
|
94
|
+
// Virtual scrolling
|
|
95
|
+
this.vsEnabled = computed(() => this.virtualScrollService.isActive(), ...(ngDevMode ? [{ debugName: "vsEnabled" }] : []));
|
|
96
|
+
this.vsVisibleRange = computed(() => this.virtualScrollService.visibleRange(), ...(ngDevMode ? [{ debugName: "vsVisibleRange" }] : []));
|
|
97
|
+
this.vsTopSpacerHeight = computed(() => {
|
|
98
|
+
if (!this.vsEnabled())
|
|
99
|
+
return 0;
|
|
100
|
+
return this.vsVisibleRange().offsetTop;
|
|
101
|
+
}, ...(ngDevMode ? [{ debugName: "vsTopSpacerHeight" }] : []));
|
|
102
|
+
this.vsBottomSpacerHeight = computed(() => {
|
|
103
|
+
if (!this.vsEnabled())
|
|
104
|
+
return 0;
|
|
105
|
+
return this.vsVisibleRange().offsetBottom;
|
|
106
|
+
}, ...(ngDevMode ? [{ debugName: "vsBottomSpacerHeight" }] : []));
|
|
107
|
+
this.vsVisibleItems = computed(() => {
|
|
108
|
+
const items = this.items();
|
|
109
|
+
if (!this.vsEnabled())
|
|
110
|
+
return items;
|
|
111
|
+
const range = this.vsVisibleRange();
|
|
112
|
+
return items.slice(range.startIndex, Math.min(range.endIndex + 1, items.length));
|
|
113
|
+
}, ...(ngDevMode ? [{ debugName: "vsVisibleItems" }] : []));
|
|
114
|
+
this.vsStartIndex = computed(() => {
|
|
115
|
+
if (!this.vsEnabled())
|
|
116
|
+
return 0;
|
|
117
|
+
return this.vsVisibleRange().startIndex;
|
|
118
|
+
}, ...(ngDevMode ? [{ debugName: "vsStartIndex" }] : []));
|
|
119
|
+
// Column virtualization
|
|
120
|
+
this.vsColumnsEnabled = computed(() => this.virtualScrollService.columnsEnabled(), ...(ngDevMode ? [{ debugName: "vsColumnsEnabled" }] : []));
|
|
121
|
+
this.vsColumnRange = computed(() => this.virtualScrollService.columnRange(), ...(ngDevMode ? [{ debugName: "vsColumnRange" }] : []));
|
|
122
|
+
/** Partitioned columns for column virtualization: pinned-left, virtualized-unpinned, pinned-right, with spacer widths. */
|
|
123
|
+
this.vsColumnPartition = computed(() => {
|
|
124
|
+
if (!this.vsColumnsEnabled())
|
|
125
|
+
return null;
|
|
126
|
+
const cols = this.visibleCols();
|
|
127
|
+
const range = this.vsColumnRange();
|
|
128
|
+
const props = this.getProps();
|
|
129
|
+
const pinnedCols = props?.pinnedColumns;
|
|
130
|
+
return partitionColumnsForVirtualization(cols, range, pinnedCols);
|
|
131
|
+
}, ...(ngDevMode ? [{ debugName: "vsColumnPartition" }] : []));
|
|
132
|
+
/** Column layouts filtered by column virtualization range (or all if column virt is off). */
|
|
133
|
+
this.vsColumnLayouts = computed(() => {
|
|
134
|
+
const allLayouts = this.columnLayouts();
|
|
135
|
+
const partition = this.vsColumnPartition();
|
|
136
|
+
if (!partition)
|
|
137
|
+
return allLayouts;
|
|
138
|
+
// Build a set of visible column IDs from the partition
|
|
139
|
+
const visibleIds = new Set();
|
|
140
|
+
for (const col of partition.pinnedLeft)
|
|
141
|
+
visibleIds.add(col.columnId);
|
|
142
|
+
for (const col of partition.virtualizedUnpinned)
|
|
143
|
+
visibleIds.add(col.columnId);
|
|
144
|
+
for (const col of partition.pinnedRight)
|
|
145
|
+
visibleIds.add(col.columnId);
|
|
146
|
+
return allLayouts.filter(layout => visibleIds.has(layout.col.columnId));
|
|
147
|
+
}, ...(ngDevMode ? [{ debugName: "vsColumnLayouts" }] : []));
|
|
148
|
+
/** Visible columns filtered by column virtualization range (or all if column virt is off). */
|
|
149
|
+
this.vsVisibleCols = computed(() => {
|
|
150
|
+
const allCols = this.visibleCols();
|
|
151
|
+
const partition = this.vsColumnPartition();
|
|
152
|
+
if (!partition)
|
|
153
|
+
return allCols;
|
|
154
|
+
const visibleIds = new Set();
|
|
155
|
+
for (const col of partition.pinnedLeft)
|
|
156
|
+
visibleIds.add(col.columnId);
|
|
157
|
+
for (const col of partition.virtualizedUnpinned)
|
|
158
|
+
visibleIds.add(col.columnId);
|
|
159
|
+
for (const col of partition.pinnedRight)
|
|
160
|
+
visibleIds.add(col.columnId);
|
|
161
|
+
return allCols.filter(col => visibleIds.has(col.columnId));
|
|
162
|
+
}, ...(ngDevMode ? [{ debugName: "vsVisibleCols" }] : []));
|
|
163
|
+
/** Left spacer width for column virtualization (in pixels). */
|
|
164
|
+
this.vsLeftSpacerWidth = computed(() => this.vsColumnPartition()?.leftSpacerWidth ?? 0, ...(ngDevMode ? [{ debugName: "vsLeftSpacerWidth" }] : []));
|
|
165
|
+
/** Right spacer width for column virtualization (in pixels). */
|
|
166
|
+
this.vsRightSpacerWidth = computed(() => this.vsColumnPartition()?.rightSpacerWidth ?? 0, ...(ngDevMode ? [{ debugName: "vsRightSpacerWidth" }] : []));
|
|
167
|
+
/** Map from columnId to its global index in visibleCols. Used to maintain correct colIdx for cell descriptors during column virtualization. */
|
|
168
|
+
this.globalColIndexMap = computed(() => {
|
|
169
|
+
const cols = this.visibleCols();
|
|
170
|
+
const map = new Map();
|
|
171
|
+
for (let i = 0; i < cols.length; i++) {
|
|
172
|
+
map.set(cols[i].columnId, i);
|
|
173
|
+
}
|
|
174
|
+
return map;
|
|
175
|
+
}, ...(ngDevMode ? [{ debugName: "globalColIndexMap" }] : []));
|
|
176
|
+
// Popover editing
|
|
177
|
+
this.popoverAnchorEl = computed(() => this.editingState().popoverAnchorEl, ...(ngDevMode ? [{ debugName: "popoverAnchorEl" }] : []));
|
|
178
|
+
this.pendingEditorValueForPopover = computed(() => this.editingState().pendingEditorValue, ...(ngDevMode ? [{ debugName: "pendingEditorValueForPopover" }] : []));
|
|
179
|
+
this.allowOverflowX = computed(() => {
|
|
180
|
+
const p = this.getProps();
|
|
181
|
+
if (p?.suppressHorizontalScroll)
|
|
182
|
+
return false;
|
|
183
|
+
const cw = this.containerWidth();
|
|
184
|
+
const mtw = this.minTableWidth();
|
|
185
|
+
const dtw = this.desiredTableWidth();
|
|
186
|
+
return cw > 0 && (mtw > cw || dtw > cw);
|
|
187
|
+
}, ...(ngDevMode ? [{ debugName: "allowOverflowX" }] : []));
|
|
188
|
+
this.selectionCellCount = computed(() => {
|
|
189
|
+
const sr = this.selectionRange();
|
|
190
|
+
if (!sr)
|
|
191
|
+
return undefined;
|
|
192
|
+
return (Math.abs(sr.endRow - sr.startRow) + 1) * (Math.abs(sr.endCol - sr.startCol) + 1);
|
|
193
|
+
}, ...(ngDevMode ? [{ debugName: "selectionCellCount" }] : []));
|
|
194
|
+
// Header rows from column definition
|
|
195
|
+
this.headerRows = computed(() => {
|
|
196
|
+
const p = this.getProps();
|
|
197
|
+
if (!p)
|
|
198
|
+
return [];
|
|
199
|
+
return buildHeaderRows(p.columns, p.visibleColumns);
|
|
200
|
+
}, ...(ngDevMode ? [{ debugName: "headerRows" }] : []));
|
|
201
|
+
// Pre-computed column layouts
|
|
202
|
+
this.columnLayouts = computed(() => {
|
|
203
|
+
const cols = this.visibleCols();
|
|
204
|
+
const props = this.getProps();
|
|
205
|
+
const pinnedCols = props?.pinnedColumns ?? {};
|
|
206
|
+
const measuredWidths = this.measuredColumnWidths();
|
|
207
|
+
const sizingOverrides = this.columnSizingOverrides();
|
|
208
|
+
return cols.map((col) => {
|
|
209
|
+
const runtimePinned = pinnedCols[col.columnId];
|
|
210
|
+
const pinnedLeft = runtimePinned === 'left' || col.pinned === 'left';
|
|
211
|
+
const pinnedRight = runtimePinned === 'right' || col.pinned === 'right';
|
|
212
|
+
const w = this.getColumnWidth(col);
|
|
213
|
+
// Use previously-measured DOM width as a minWidth floor to prevent columns
|
|
214
|
+
// from shrinking when new data loads (e.g. server-side pagination).
|
|
215
|
+
const hasResizeOverride = !!sizingOverrides[col.columnId];
|
|
216
|
+
const measuredW = measuredWidths[col.columnId];
|
|
217
|
+
const baseMinWidth = col.minWidth ?? estimateHeaderMinWidth(col.name);
|
|
218
|
+
const effectiveMinWidth = hasResizeOverride ? w : Math.max(baseMinWidth, measuredW ?? 0);
|
|
219
|
+
return {
|
|
220
|
+
col,
|
|
221
|
+
pinnedLeft,
|
|
222
|
+
pinnedRight,
|
|
223
|
+
minWidth: effectiveMinWidth,
|
|
224
|
+
width: w,
|
|
225
|
+
cssWidth: col.width,
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}, ...(ngDevMode ? [{ debugName: "columnLayouts" }] : []));
|
|
229
|
+
// Compute sticky offsets for pinned columns (single pass from both ends)
|
|
230
|
+
this.pinningOffsets = computed(() => {
|
|
231
|
+
const layouts = this.columnLayouts();
|
|
232
|
+
const leftOffsets = {};
|
|
233
|
+
const rightOffsets = {};
|
|
234
|
+
let leftAcc = 0;
|
|
235
|
+
if (this.hasCheckboxCol())
|
|
236
|
+
leftAcc += CHECKBOX_COLUMN_WIDTH;
|
|
237
|
+
if (this.hasRowNumbersCol())
|
|
238
|
+
leftAcc += this.getRowNumberWidth();
|
|
239
|
+
let rightAcc = 0;
|
|
240
|
+
const len = layouts.length;
|
|
241
|
+
for (let i = 0; i < len; i++) {
|
|
242
|
+
// Left-pinned: walk forward
|
|
243
|
+
const leftLayout = layouts[i];
|
|
244
|
+
if (leftLayout.pinnedLeft) {
|
|
245
|
+
leftOffsets[leftLayout.col.columnId] = leftAcc;
|
|
246
|
+
leftAcc += leftLayout.width + CELL_PADDING;
|
|
247
|
+
}
|
|
248
|
+
// Right-pinned: walk backward
|
|
249
|
+
const ri = len - 1 - i;
|
|
250
|
+
const rightLayout = layouts[ri];
|
|
251
|
+
if (rightLayout.pinnedRight) {
|
|
252
|
+
rightOffsets[rightLayout.col.columnId] = rightAcc;
|
|
253
|
+
rightAcc += rightLayout.width + CELL_PADDING;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { leftOffsets, rightOffsets };
|
|
257
|
+
}, ...(ngDevMode ? [{ debugName: "pinningOffsets" }] : []));
|
|
258
|
+
/** Memoized column menu handlers - avoids recreating objects on every CD cycle */
|
|
259
|
+
this.columnMenuHandlersMap = computed(() => {
|
|
260
|
+
const cols = this.visibleCols();
|
|
261
|
+
const map = new Map();
|
|
262
|
+
for (const col of cols) {
|
|
263
|
+
map.set(col.columnId, this.buildColumnMenuHandlers(col.columnId));
|
|
264
|
+
}
|
|
265
|
+
return map;
|
|
266
|
+
}, ...(ngDevMode ? [{ debugName: "columnMenuHandlersMap" }] : []));
|
|
267
|
+
}
|
|
268
|
+
/** Lifecycle hook - populate element signals from @ViewChild refs */
|
|
269
|
+
ngAfterViewInit() {
|
|
270
|
+
const wrapper = this.getWrapperRef()?.nativeElement ?? null;
|
|
271
|
+
const tableContainer = this.getTableContainerRef()?.nativeElement ?? null;
|
|
272
|
+
if (wrapper)
|
|
273
|
+
this.wrapperElSignal.set(wrapper);
|
|
274
|
+
if (tableContainer)
|
|
275
|
+
this.tableContainerElSignal.set(tableContainer);
|
|
276
|
+
this.measureColumnWidths();
|
|
277
|
+
}
|
|
278
|
+
/** Lifecycle hook - re-measure column widths only when layout changed */
|
|
279
|
+
ngAfterViewChecked() {
|
|
280
|
+
if (this.measureDirty()) {
|
|
281
|
+
this.measureDirty.set(false);
|
|
282
|
+
this.measureColumnWidths();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** Measure actual th widths from the DOM and update the measuredColumnWidths signal.
|
|
286
|
+
* Only updates the signal when values actually change, to avoid render loops. */
|
|
287
|
+
measureColumnWidths() {
|
|
288
|
+
const wrapper = this.getWrapperRef()?.nativeElement;
|
|
289
|
+
if (!wrapper)
|
|
290
|
+
return;
|
|
291
|
+
const headerCells = wrapper.querySelectorAll('th[data-column-id]');
|
|
292
|
+
if (headerCells.length === 0)
|
|
293
|
+
return;
|
|
294
|
+
const measured = {};
|
|
295
|
+
headerCells.forEach((cell) => {
|
|
296
|
+
const colId = cell.getAttribute('data-column-id');
|
|
297
|
+
if (colId)
|
|
298
|
+
measured[colId] = cell.offsetWidth;
|
|
299
|
+
});
|
|
300
|
+
// Only update signal if values changed to avoid triggering computed re-evaluations unnecessarily
|
|
301
|
+
const prev = this.measuredColumnWidths();
|
|
302
|
+
let changed = Object.keys(measured).length !== Object.keys(prev).length;
|
|
303
|
+
if (!changed) {
|
|
304
|
+
for (const key in measured) {
|
|
305
|
+
if (prev[key] !== measured[key]) {
|
|
306
|
+
changed = true;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (changed) {
|
|
312
|
+
this.measuredColumnWidths.set(measured);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Get global column index for a column (correct even during column virtualization). */
|
|
316
|
+
getGlobalColIndex(col) {
|
|
317
|
+
return this.globalColIndexMap().get(col.columnId) ?? 0;
|
|
318
|
+
}
|
|
319
|
+
/** Returns the effective row number column width (from overrides or default). */
|
|
320
|
+
getRowNumberWidth() {
|
|
321
|
+
const overrides = this.columnSizingOverrides();
|
|
322
|
+
const override = overrides[ROW_NUMBER_COLUMN_ID];
|
|
323
|
+
return override ? override.widthPx : ROW_NUMBER_COLUMN_WIDTH;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Initialize base wiring effects. Must be called from subclass constructor.
|
|
327
|
+
*
|
|
328
|
+
* **Timing:** Angular requires `effect()` to be created inside an injection
|
|
329
|
+
* context (constructor or field initializer). On the first run, signals like
|
|
330
|
+
* `wrapperElSignal()` return `null` because the DOM hasn't been created yet.
|
|
331
|
+
* After `ngAfterViewInit` sets these signals, Angular's signal graph
|
|
332
|
+
* automatically re-runs each effect. The null guards inside each effect body
|
|
333
|
+
* ensure the first (null) run is a safe no-op.
|
|
334
|
+
*
|
|
335
|
+
* Sequence:
|
|
336
|
+
* 1. Constructor to `initBase()` to effects created, first run (signals null to no-ops)
|
|
337
|
+
* 2. `ngAfterViewInit` to `wrapperElSignal.set(el)` to effects re-run with real values
|
|
338
|
+
*/
|
|
339
|
+
initBase() {
|
|
340
|
+
// Wire props to state service
|
|
341
|
+
effect(() => {
|
|
342
|
+
const p = this.getProps();
|
|
343
|
+
if (p)
|
|
344
|
+
this.stateService.props.set(p);
|
|
345
|
+
});
|
|
346
|
+
// Wire wrapper element (reads from signal populated by ngAfterViewInit)
|
|
347
|
+
effect(() => {
|
|
348
|
+
const el = this.wrapperElSignal();
|
|
349
|
+
if (el) {
|
|
350
|
+
this.stateService.wrapperEl.set(el);
|
|
351
|
+
this.columnReorderService.wrapperEl.set(el);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
// Wire column reorder service inputs
|
|
355
|
+
effect(() => {
|
|
356
|
+
const p = this.getProps();
|
|
357
|
+
if (p) {
|
|
358
|
+
const cols = this.visibleCols();
|
|
359
|
+
this.columnReorderService.columns.set(cols);
|
|
360
|
+
this.columnReorderService.columnOrder.set(p.columnOrder);
|
|
361
|
+
this.columnReorderService.onColumnOrderChange.set(p.onColumnOrderChange);
|
|
362
|
+
this.columnReorderService.enabled.set(p.columnReorder === true);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
// Mark measurement dirty when column layout changes
|
|
366
|
+
effect(() => {
|
|
367
|
+
// Track signals that affect column layout
|
|
368
|
+
this.visibleCols();
|
|
369
|
+
this.columnSizingOverrides();
|
|
370
|
+
this.columnSizingVersion();
|
|
371
|
+
this.measureDirty.set(true);
|
|
372
|
+
});
|
|
373
|
+
// Wire virtual scroll service inputs
|
|
374
|
+
effect(() => {
|
|
375
|
+
const p = this.getProps();
|
|
376
|
+
if (p) {
|
|
377
|
+
this.virtualScrollService.totalRows.set(p.items.length);
|
|
378
|
+
if (p.virtualScroll) {
|
|
379
|
+
this.virtualScrollService.updateConfig({
|
|
380
|
+
enabled: p.virtualScroll.enabled,
|
|
381
|
+
rowHeight: p.virtualScroll.rowHeight,
|
|
382
|
+
overscan: p.virtualScroll.overscan,
|
|
383
|
+
columns: p.virtualScroll.columns,
|
|
384
|
+
columnOverscan: p.virtualScroll.columnOverscan,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
// Wire column widths to virtual scroll service for column virtualization
|
|
390
|
+
effect(() => {
|
|
391
|
+
const layouts = this.columnLayouts();
|
|
392
|
+
const props = this.getProps();
|
|
393
|
+
const pinnedCols = props?.pinnedColumns ?? {};
|
|
394
|
+
// Only provide widths for unpinned columns (pinned are always rendered)
|
|
395
|
+
const unpinnedWidths = layouts
|
|
396
|
+
.filter(l => !l.pinnedLeft && !l.pinnedRight && !pinnedCols[l.col.columnId])
|
|
397
|
+
.map(l => l.width);
|
|
398
|
+
this.virtualScrollService.columnWidths.set(unpinnedWidths);
|
|
399
|
+
});
|
|
400
|
+
// Wire wrapper element to virtual scroll for scroll events + container height
|
|
401
|
+
effect(() => {
|
|
402
|
+
const el = this.wrapperElSignal();
|
|
403
|
+
if (el) {
|
|
404
|
+
this.virtualScrollService.setContainer(el);
|
|
405
|
+
this.virtualScrollService.containerHeight.set(el.clientHeight);
|
|
406
|
+
this.virtualScrollService.containerWidth.set(el.clientWidth);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
// --- Helper methods ---
|
|
411
|
+
/** Lookup effective min-width for a column (includes measured width floor) */
|
|
412
|
+
getEffectiveMinWidth(col) {
|
|
413
|
+
const layout = this.columnLayouts().find((l) => l.col.columnId === col.columnId);
|
|
414
|
+
return layout?.minWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Returns derived cell interaction metadata (non-event attributes) for use in templates.
|
|
418
|
+
* Mirrors React's getCellInteractionProps for the Angular view layer.
|
|
419
|
+
* Event handlers (pointerdown, click, dblclick, contextmenu) are still bound inline in templates.
|
|
420
|
+
*/
|
|
421
|
+
getCellInteractionProps(descriptor) {
|
|
422
|
+
return {
|
|
423
|
+
tabIndex: descriptor.isActive ? 0 : -1,
|
|
424
|
+
dataRowIndex: descriptor.rowIndex,
|
|
425
|
+
dataColIndex: descriptor.globalColIndex,
|
|
426
|
+
dataInRange: descriptor.isInRange ? 'true' : null,
|
|
427
|
+
role: descriptor.canEditAny ? 'button' : null,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
asColumnDef(colDef) {
|
|
431
|
+
return colDef;
|
|
432
|
+
}
|
|
433
|
+
visibleColIndex(col) {
|
|
434
|
+
return this.visibleCols().indexOf(col);
|
|
435
|
+
}
|
|
436
|
+
getColumnWidth(col) {
|
|
437
|
+
const overrides = this.columnSizingOverrides();
|
|
438
|
+
const override = overrides[col.columnId];
|
|
439
|
+
if (override)
|
|
440
|
+
return override.widthPx;
|
|
441
|
+
return col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
442
|
+
}
|
|
443
|
+
getFilterConfig(col) {
|
|
444
|
+
return getHeaderFilterConfig(col, this.headerFilterInput());
|
|
445
|
+
}
|
|
446
|
+
/** Build column menu handler object for a single column */
|
|
447
|
+
buildColumnMenuHandlers(columnId) {
|
|
448
|
+
return {
|
|
449
|
+
onPinLeft: () => this.onPinColumn(columnId, 'left'),
|
|
450
|
+
onPinRight: () => this.onPinColumn(columnId, 'right'),
|
|
451
|
+
onUnpin: () => this.onUnpinColumn(columnId),
|
|
452
|
+
onSortAsc: () => this.onSortAsc(columnId),
|
|
453
|
+
onSortDesc: () => this.onSortDesc(columnId),
|
|
454
|
+
onClearSort: () => this.onClearSort(columnId),
|
|
455
|
+
onAutosizeThis: () => this.onAutosizeColumn(columnId),
|
|
456
|
+
onAutosizeAll: () => this.onAutosizeAllColumns(),
|
|
457
|
+
onClose: () => { }
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
/** Get memoized handlers for a column */
|
|
461
|
+
getColumnMenuHandlersMemoized(columnId) {
|
|
462
|
+
return this.columnMenuHandlersMap().get(columnId) ?? this.buildColumnMenuHandlers(columnId);
|
|
463
|
+
}
|
|
464
|
+
getCellDescriptor(item, col, rowIndex, colIdx) {
|
|
465
|
+
return getCellRenderDescriptor(item, col, rowIndex, colIdx, this.cellDescriptorInput());
|
|
466
|
+
}
|
|
467
|
+
resolveCellContent(col, item, displayValue) {
|
|
468
|
+
try {
|
|
469
|
+
return resolveCellDisplayContent(col, item, displayValue);
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
const onCellError = this.getProps()?.onCellError;
|
|
473
|
+
if (onCellError) {
|
|
474
|
+
onCellError(err instanceof Error ? err : new Error(String(err)), undefined);
|
|
475
|
+
}
|
|
476
|
+
return '';
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
resolveCellStyleFn(col, item, displayValue) {
|
|
480
|
+
return resolveCellStyle(col, item, displayValue);
|
|
481
|
+
}
|
|
482
|
+
buildPopoverEditorProps(item, col, descriptor) {
|
|
483
|
+
return buildPopoverEditorProps(item, col, descriptor, this.pendingEditorValue(), {
|
|
484
|
+
setPendingEditorValue: (value) => this.setPendingEditorValue(value),
|
|
485
|
+
commitCellEdit: (item, columnId, oldValue, newValue, rowIndex, globalColIndex) => this.commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex),
|
|
486
|
+
cancelPopoverEdit: () => this.cancelPopoverEdit(),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
/** Check if a specific cell is the active cell (PrimeNG inline template helper). */
|
|
490
|
+
isActiveCell(rowIndex, colIdx) {
|
|
491
|
+
const ac = this.activeCell();
|
|
492
|
+
if (!ac)
|
|
493
|
+
return false;
|
|
494
|
+
return ac.rowIndex === rowIndex && ac.columnIndex === colIdx + this.colOffset();
|
|
495
|
+
}
|
|
496
|
+
/** Check if a cell is within the current selection range (PrimeNG inline template helper). */
|
|
497
|
+
isInSelectionRange(rowIndex, colIdx) {
|
|
498
|
+
const range = this.selectionRange();
|
|
499
|
+
if (!range)
|
|
500
|
+
return false;
|
|
501
|
+
const minR = Math.min(range.startRow, range.endRow);
|
|
502
|
+
const maxR = Math.max(range.startRow, range.endRow);
|
|
503
|
+
const minC = Math.min(range.startCol, range.endCol);
|
|
504
|
+
const maxC = Math.max(range.startCol, range.endCol);
|
|
505
|
+
return rowIndex >= minR && rowIndex <= maxR && colIdx >= minC && colIdx <= maxC;
|
|
506
|
+
}
|
|
507
|
+
/** Check if a cell is the selection end cell for fill handle display. */
|
|
508
|
+
isSelectionEndCell(rowIndex, colIdx) {
|
|
509
|
+
const range = this.selectionRange();
|
|
510
|
+
if (!range || this.isDragging() || this.copyRange() || this.cutRange())
|
|
511
|
+
return false;
|
|
512
|
+
return rowIndex === range.endRow && colIdx === range.endCol;
|
|
513
|
+
}
|
|
514
|
+
/** Get cell background color based on selection state. */
|
|
515
|
+
getCellBackground(rowIndex, colIdx) {
|
|
516
|
+
if (this.isInSelectionRange(rowIndex, colIdx))
|
|
517
|
+
return 'var(--ogrid-range-bg, rgba(33, 115, 70, 0.08))';
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
/** Resolve editor type from column definition. */
|
|
521
|
+
getEditorType(col, _item) {
|
|
522
|
+
if (col.cellEditor === 'text' || col.cellEditor === 'select' || col.cellEditor === 'checkbox' || col.cellEditor === 'date' || col.cellEditor === 'richSelect') {
|
|
523
|
+
return col.cellEditor;
|
|
524
|
+
}
|
|
525
|
+
if (col.type === 'date')
|
|
526
|
+
return 'date';
|
|
527
|
+
if (col.type === 'boolean')
|
|
528
|
+
return 'checkbox';
|
|
529
|
+
return 'text';
|
|
530
|
+
}
|
|
531
|
+
getSelectValues(col) {
|
|
532
|
+
const params = col.cellEditorParams;
|
|
533
|
+
if (params && typeof params === 'object' && 'values' in params) {
|
|
534
|
+
return params.values.map(String);
|
|
535
|
+
}
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
formatDateForInput(value) {
|
|
539
|
+
if (!value)
|
|
540
|
+
return '';
|
|
541
|
+
const d = new Date(String(value));
|
|
542
|
+
if (Number.isNaN(d.getTime()))
|
|
543
|
+
return '';
|
|
544
|
+
return d.toISOString().split('T')[0];
|
|
545
|
+
}
|
|
546
|
+
getPinnedLeftOffset(columnId) {
|
|
547
|
+
const offsets = this.pinningOffsets();
|
|
548
|
+
return offsets.leftOffsets[columnId] ?? null;
|
|
549
|
+
}
|
|
550
|
+
getPinnedRightOffset(columnId) {
|
|
551
|
+
const offsets = this.pinningOffsets();
|
|
552
|
+
return offsets.rightOffsets[columnId] ?? null;
|
|
553
|
+
}
|
|
554
|
+
// --- Virtual scroll event handler ---
|
|
555
|
+
onWrapperScroll(event) {
|
|
556
|
+
this.virtualScrollService.onScroll(event);
|
|
557
|
+
}
|
|
558
|
+
// --- Popover editor helpers ---
|
|
559
|
+
setPopoverAnchorEl(el) {
|
|
560
|
+
this.state().editing.setPopoverAnchorEl(el);
|
|
561
|
+
}
|
|
562
|
+
setPendingEditorValue(value) {
|
|
563
|
+
this.state().editing.setPendingEditorValue(value);
|
|
564
|
+
}
|
|
565
|
+
cancelPopoverEdit() {
|
|
566
|
+
this.state().editing.cancelPopoverEdit();
|
|
567
|
+
}
|
|
568
|
+
commitPopoverEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
|
|
569
|
+
this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
|
|
570
|
+
}
|
|
571
|
+
// --- Event handlers ---
|
|
572
|
+
onWrapperMouseDown(event) {
|
|
573
|
+
this.lastMouseShift = event.shiftKey;
|
|
574
|
+
}
|
|
575
|
+
onGridKeyDown(event) {
|
|
576
|
+
this.state().interaction.handleGridKeyDown(event);
|
|
577
|
+
}
|
|
578
|
+
onCellMouseDown(event, rowIndex, globalColIndex) {
|
|
579
|
+
this.state().editing.setEditingCell(null);
|
|
580
|
+
this.state().interaction.handleCellMouseDown(event, rowIndex, globalColIndex);
|
|
581
|
+
}
|
|
582
|
+
onNonDataSurfacePointerDown(event) {
|
|
583
|
+
if (typeof event.button === 'number' && event.button !== 0)
|
|
584
|
+
return;
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
}
|
|
587
|
+
onNonDataSurfaceSelectStart(event) {
|
|
588
|
+
event.preventDefault();
|
|
589
|
+
}
|
|
590
|
+
onCellClick(rowIndex, globalColIndex) {
|
|
591
|
+
this.state().interaction.setActiveCell?.({ rowIndex, columnIndex: globalColIndex });
|
|
592
|
+
}
|
|
593
|
+
onCellContextMenu(event) {
|
|
594
|
+
this.state().contextMenu.handleCellContextMenu(event);
|
|
595
|
+
}
|
|
596
|
+
onCellDblClick(rowId, columnId) {
|
|
597
|
+
this.state().editing.setEditingCell({ rowId, columnId });
|
|
598
|
+
}
|
|
599
|
+
onFillHandleMouseDown(event) {
|
|
600
|
+
this.state().interaction.handleFillHandleMouseDown?.(event);
|
|
601
|
+
}
|
|
602
|
+
onResizeStart(event, col) {
|
|
603
|
+
event.preventDefault();
|
|
604
|
+
// Clear cell selection before resize (like React) so selection outlines don't persist during drag
|
|
605
|
+
this.state().interaction.setActiveCell?.(null);
|
|
606
|
+
this.state().interaction.setSelectionRange?.(null);
|
|
607
|
+
this.getWrapperRef()?.nativeElement.focus({ preventScroll: true });
|
|
608
|
+
const startX = event.clientX;
|
|
609
|
+
const columnId = col.columnId;
|
|
610
|
+
const startWidth = columnId === ROW_NUMBER_COLUMN_ID ? this.getRowNumberWidth() : this.getColumnWidth(col);
|
|
611
|
+
const minWidth = columnId === ROW_NUMBER_COLUMN_ID ? ROW_NUMBER_COLUMN_MIN_WIDTH : (col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
|
|
612
|
+
const onMove = (e) => {
|
|
613
|
+
const delta = e.clientX - startX;
|
|
614
|
+
const newWidth = Math.max(minWidth, startWidth + delta);
|
|
615
|
+
const overrides = { ...this.columnSizingOverrides(), [col.columnId]: { widthPx: newWidth } };
|
|
616
|
+
this.state().layout.setColumnSizingOverrides(overrides);
|
|
617
|
+
this.columnSizingVersion.update(v => v + 1);
|
|
618
|
+
};
|
|
619
|
+
const onUp = () => {
|
|
620
|
+
window.removeEventListener('pointermove', onMove);
|
|
621
|
+
window.removeEventListener('pointerup', onUp);
|
|
622
|
+
const finalWidth = this.getColumnWidth(col);
|
|
623
|
+
this.state().layout.onColumnResized?.(col.columnId, finalWidth);
|
|
624
|
+
};
|
|
625
|
+
window.addEventListener('pointermove', onMove);
|
|
626
|
+
window.addEventListener('pointerup', onUp);
|
|
627
|
+
}
|
|
628
|
+
onResizeDoubleClick(event, col) {
|
|
629
|
+
event.preventDefault();
|
|
630
|
+
event.stopPropagation();
|
|
631
|
+
const columnId = col.columnId;
|
|
632
|
+
const thEl = event.currentTarget.closest('th') ?? event.currentTarget.parentElement;
|
|
633
|
+
const container = thEl?.closest('table')?.parentElement ?? undefined;
|
|
634
|
+
const minWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
635
|
+
const idealWidth = measureColumnContentWidth(columnId, minWidth, container);
|
|
636
|
+
const overrides = { ...this.columnSizingOverrides(), [columnId]: { widthPx: idealWidth } };
|
|
637
|
+
this.state().layout.setColumnSizingOverrides(overrides);
|
|
638
|
+
this.columnSizingVersion.update(v => v + 1);
|
|
639
|
+
this.state().layout.onColumnResized?.(columnId, idealWidth);
|
|
640
|
+
}
|
|
641
|
+
onSelectAllChange(event) {
|
|
642
|
+
const checked = event.target.checked;
|
|
643
|
+
this.state().rowSelection.handleSelectAll(!!checked);
|
|
644
|
+
}
|
|
645
|
+
onRowClick(event, rowId) {
|
|
646
|
+
const p = this.getProps();
|
|
647
|
+
if (p?.rowSelection !== 'single')
|
|
648
|
+
return;
|
|
649
|
+
const ids = this.selectedRowIds();
|
|
650
|
+
this.state().rowSelection.updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
|
|
651
|
+
}
|
|
652
|
+
onRowCheckboxChange(rowId, event, rowIndex) {
|
|
653
|
+
const checked = event.target.checked;
|
|
654
|
+
this.state().rowSelection.handleRowCheckboxChange(rowId, checked, rowIndex, this.lastMouseShift);
|
|
655
|
+
}
|
|
656
|
+
commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
|
|
657
|
+
this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
|
|
658
|
+
}
|
|
659
|
+
commitBooleanEdit(item, columnId, oldValue, rowIndex, globalColIndex) {
|
|
660
|
+
this.state().editing.commitCellEdit(item, columnId, oldValue, !oldValue, rowIndex, globalColIndex, { skipAdvance: true });
|
|
661
|
+
}
|
|
662
|
+
onBooleanCheckboxPointerDown(event, rowIndex, globalColIndex) {
|
|
663
|
+
handleBooleanCellPointerDown(event, rowIndex, globalColIndex, this.colOffset(), {
|
|
664
|
+
setActiveCell: (c) => this.state().interaction.setActiveCell?.(c),
|
|
665
|
+
setSelectionRange: (r) => this.state().interaction.setSelectionRange?.(r),
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
cancelEdit() {
|
|
669
|
+
this.state().editing.setEditingCell(null);
|
|
670
|
+
}
|
|
671
|
+
onEditorKeydown(event, item, columnId, oldValue, rowIndex, globalColIndex) {
|
|
672
|
+
if (event.key === 'Enter') {
|
|
673
|
+
event.preventDefault();
|
|
674
|
+
const newValue = event.target.value;
|
|
675
|
+
this.commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
|
|
676
|
+
}
|
|
677
|
+
else if (event.key === 'Escape') {
|
|
678
|
+
event.preventDefault();
|
|
679
|
+
this.cancelEdit();
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
closeContextMenu() {
|
|
683
|
+
this.state().contextMenu.closeContextMenu();
|
|
684
|
+
}
|
|
685
|
+
handleCopy() {
|
|
686
|
+
this.state().interaction.handleCopy();
|
|
687
|
+
}
|
|
688
|
+
handleCut() {
|
|
689
|
+
this.state().interaction.handleCut();
|
|
690
|
+
}
|
|
691
|
+
handlePaste() {
|
|
692
|
+
void this.state().interaction.handlePaste();
|
|
693
|
+
}
|
|
694
|
+
handleSelectAllCells() {
|
|
695
|
+
this.state().interaction.handleSelectAllCells();
|
|
696
|
+
}
|
|
697
|
+
onUndo() {
|
|
698
|
+
this.state().interaction.onUndo?.();
|
|
699
|
+
}
|
|
700
|
+
onRedo() {
|
|
701
|
+
this.state().interaction.onRedo?.();
|
|
702
|
+
}
|
|
703
|
+
onHeaderMouseDown(columnId, event) {
|
|
704
|
+
this.columnReorderService.handleHeaderMouseDown(columnId, event);
|
|
705
|
+
}
|
|
706
|
+
// --- Column pinning methods ---
|
|
707
|
+
onPinColumn(columnId, side) {
|
|
708
|
+
this.state().pinning.pinColumn(columnId, side);
|
|
709
|
+
}
|
|
710
|
+
onUnpinColumn(columnId) {
|
|
711
|
+
this.state().pinning.unpinColumn(columnId);
|
|
712
|
+
}
|
|
713
|
+
isPinned(columnId) {
|
|
714
|
+
return this.state().pinning.isPinned(columnId);
|
|
715
|
+
}
|
|
716
|
+
getPinState(columnId) {
|
|
717
|
+
const pinned = this.isPinned(columnId);
|
|
718
|
+
return {
|
|
719
|
+
canPinLeft: pinned !== 'left',
|
|
720
|
+
canPinRight: pinned !== 'right',
|
|
721
|
+
canUnpin: !!pinned,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
// --- Column sorting methods ---
|
|
725
|
+
onSortAsc(columnId) {
|
|
726
|
+
const props = this.getProps();
|
|
727
|
+
props?.onColumnSort?.(columnId, 'asc');
|
|
728
|
+
}
|
|
729
|
+
onSortDesc(columnId) {
|
|
730
|
+
const props = this.getProps();
|
|
731
|
+
props?.onColumnSort?.(columnId, 'desc');
|
|
732
|
+
}
|
|
733
|
+
onClearSort(columnId) {
|
|
734
|
+
const props = this.getProps();
|
|
735
|
+
const col = columnId ?? props?.sortBy;
|
|
736
|
+
if (col) {
|
|
737
|
+
props?.onColumnSort?.(col, null);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
getSortState(columnId) {
|
|
741
|
+
const props = this.getProps();
|
|
742
|
+
if (props?.sortBy === columnId) {
|
|
743
|
+
return props.sortDirection ?? 'asc';
|
|
744
|
+
}
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
// --- Column autosize methods ---
|
|
748
|
+
onAutosizeColumn(columnId) {
|
|
749
|
+
const col = this.visibleCols().find((c) => c.columnId === columnId);
|
|
750
|
+
if (!col)
|
|
751
|
+
return;
|
|
752
|
+
const width = measureColumnContentWidth(columnId, col.minWidth, this.tableContainerEl() ?? undefined);
|
|
753
|
+
this.state().layout.setColumnSizingOverrides({
|
|
754
|
+
...this.columnSizingOverrides(),
|
|
755
|
+
[columnId]: { widthPx: width },
|
|
756
|
+
});
|
|
757
|
+
(this.state().layout.onAutosizeColumn ?? this.state().layout.onColumnResized)?.(columnId, width);
|
|
758
|
+
}
|
|
759
|
+
onAutosizeAllColumns() {
|
|
760
|
+
const tableEl = this.tableContainerEl() ?? undefined;
|
|
761
|
+
const overrides = {};
|
|
762
|
+
for (const col of this.visibleCols()) {
|
|
763
|
+
const width = measureColumnContentWidth(col.columnId, col.minWidth, tableEl);
|
|
764
|
+
overrides[col.columnId] = { widthPx: width };
|
|
765
|
+
(this.state().layout.onAutosizeColumn ?? this.state().layout.onColumnResized)?.(col.columnId, width);
|
|
766
|
+
}
|
|
767
|
+
this.state().layout.setColumnSizingOverrides({
|
|
768
|
+
...this.columnSizingOverrides(),
|
|
769
|
+
...overrides,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
static { this.ɵfac = function BaseDataGridTableComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BaseDataGridTableComponent)(); }; }
|
|
773
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseDataGridTableComponent }); }
|
|
774
|
+
}
|
|
775
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseDataGridTableComponent, [{
|
|
776
|
+
type: Directive
|
|
777
|
+
}], null, null); })();
|