argent-grid 0.1.0 → 0.2.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/.github/workflows/ci.yml +69 -0
- package/.github/workflows/pages.yml +6 -12
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/tsconfig.json +24 -0
- package/AGENTS.md +2 -2
- package/README.md +51 -34
- package/angular.json +66 -0
- package/biome.json +66 -0
- package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
- package/docs/AG-GRID-COMPARISON.md +725 -0
- package/docs/CELL-RENDERER-GUIDE.md +241 -0
- package/docs/CONTEXT-MENU-GUIDE.md +371 -0
- package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
- package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
- package/docs/PERFORMANCE-REVIEW.md +571 -0
- package/docs/RESEARCH-STATUS.md +234 -0
- package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
- package/docs/STORYBOOK-REFACTOR.md +215 -0
- package/docs/STORYBOOK-STATUS.md +156 -0
- package/docs/TEST-COVERAGE-REPORT.md +276 -0
- package/docs/THEME-API-GUIDE.md +445 -0
- package/docs/THEME-API-PLAN.md +364 -0
- package/e2e/advanced.spec.ts +109 -0
- package/e2e/argentgrid.spec.ts +65 -0
- package/e2e/benchmark.spec.ts +52 -0
- package/e2e/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +91 -0
- package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
- package/package.json +20 -6
- package/plan.md +50 -18
- package/playwright.config.ts +38 -0
- package/setup-vitest.ts +10 -13
- package/src/lib/argent-grid.module.ts +10 -12
- package/src/lib/components/argent-grid.component.css +327 -76
- package/src/lib/components/argent-grid.component.html +186 -64
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +642 -189
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.ts +302 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
- package/src/lib/directives/click-outside.directive.ts +19 -0
- package/src/lib/rendering/canvas-renderer.spec.ts +366 -0
- package/src/lib/rendering/canvas-renderer.ts +418 -305
- package/src/lib/rendering/live-data-handler.ts +110 -0
- package/src/lib/rendering/live-data-optimizations.ts +133 -0
- package/src/lib/rendering/render/blit.spec.ts +16 -27
- package/src/lib/rendering/render/blit.ts +48 -36
- package/src/lib/rendering/render/cells.spec.ts +132 -0
- package/src/lib/rendering/render/cells.ts +46 -24
- package/src/lib/rendering/render/column-utils.ts +73 -0
- package/src/lib/rendering/render/hit-test.ts +55 -0
- package/src/lib/rendering/render/index.ts +79 -76
- package/src/lib/rendering/render/lines.ts +43 -43
- package/src/lib/rendering/render/primitives.ts +161 -0
- package/src/lib/rendering/render/theme.spec.ts +8 -12
- package/src/lib/rendering/render/theme.ts +7 -10
- package/src/lib/rendering/render/types.ts +2 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +60 -50
- package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
- package/src/lib/rendering/utils/damage-tracker.ts +6 -18
- package/src/lib/rendering/utils/index.ts +1 -1
- package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
- package/src/lib/services/grid.service.spec.ts +1165 -201
- package/src/lib/services/grid.service.ts +819 -187
- package/src/lib/themes/parts/color-schemes.ts +132 -0
- package/src/lib/themes/parts/icon-sets.ts +258 -0
- package/src/lib/themes/theme-builder.ts +347 -0
- package/src/lib/themes/theme-quartz.ts +72 -0
- package/src/lib/themes/types.ts +238 -0
- package/src/lib/types/ag-grid-types.ts +73 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +188 -0
- package/src/stories/ArgentGrid.stories.ts +277 -0
- package/src/stories/Benchmark.stories.ts +74 -0
- package/src/stories/CellRenderers.stories.ts +221 -0
- package/src/stories/Filtering.stories.ts +252 -0
- package/src/stories/Grouping.stories.ts +217 -0
- package/src/stories/Theming.stories.ts +124 -0
- package/src/stories/benchmark-wrapper.component.ts +315 -0
- package/tsconfig.storybook.json +10 -0
- package/vitest.config.ts +9 -9
- package/demo-app/README.md +0 -70
- package/demo-app/angular.json +0 -78
- package/demo-app/e2e/benchmark.spec.ts +0 -53
- package/demo-app/e2e/demo-page.spec.ts +0 -77
- package/demo-app/e2e/grid-features.spec.ts +0 -269
- package/demo-app/package-lock.json +0 -14023
- package/demo-app/package.json +0 -36
- package/demo-app/playwright-test-menu.js +0 -19
- package/demo-app/playwright.config.ts +0 -23
- package/demo-app/src/app/app.component.ts +0 -10
- package/demo-app/src/app/app.config.ts +0 -13
- package/demo-app/src/app/app.routes.ts +0 -7
- package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
- package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
- package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
- package/demo-app/src/index.html +0 -19
- package/demo-app/src/main.ts +0 -6
- package/demo-app/tsconfig.json +0 -31
|
@@ -1,25 +1,57 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ElementRef, ViewChild, ChangeDetectionStrategy, AfterViewInit, OnChanges, SimpleChanges, ChangeDetectorRef, Inject } from '@angular/core';
|
|
2
1
|
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AfterViewInit,
|
|
4
|
+
ChangeDetectionStrategy,
|
|
5
|
+
ChangeDetectorRef,
|
|
6
|
+
Component,
|
|
7
|
+
ElementRef,
|
|
8
|
+
EventEmitter,
|
|
9
|
+
Inject,
|
|
10
|
+
Input,
|
|
11
|
+
OnChanges,
|
|
12
|
+
OnDestroy,
|
|
13
|
+
OnInit,
|
|
14
|
+
Output,
|
|
15
|
+
SimpleChanges,
|
|
16
|
+
ViewChild,
|
|
17
|
+
} from '@angular/core';
|
|
4
18
|
import { Subject } from 'rxjs';
|
|
5
19
|
import { takeUntil } from 'rxjs/operators';
|
|
6
|
-
import { GridService } from '../services/grid.service';
|
|
7
20
|
import { CanvasRenderer } from '../rendering/canvas-renderer';
|
|
21
|
+
import { GridService } from '../services/grid.service';
|
|
22
|
+
import { applyThemeCSSVariables, convertThemeToGridTheme } from '../themes/theme-builder';
|
|
23
|
+
import {
|
|
24
|
+
CellRange,
|
|
25
|
+
ColDef,
|
|
26
|
+
ColGroupDef,
|
|
27
|
+
Column,
|
|
28
|
+
DefaultMenuItem,
|
|
29
|
+
GetContextMenuItemsParams,
|
|
30
|
+
GridApi,
|
|
31
|
+
GridOptions,
|
|
32
|
+
IRowNode,
|
|
33
|
+
MenuItemDef,
|
|
34
|
+
RowSelectionOptions,
|
|
35
|
+
} from '../types/ag-grid-types';
|
|
8
36
|
|
|
9
37
|
@Component({
|
|
10
38
|
selector: 'argent-grid',
|
|
11
39
|
templateUrl: './argent-grid.component.html',
|
|
12
40
|
styleUrls: ['./argent-grid.component.css'],
|
|
13
|
-
changeDetection: ChangeDetectionStrategy.OnPush
|
|
41
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14
42
|
})
|
|
15
|
-
export class ArgentGridComponent<TData = any>
|
|
43
|
+
export class ArgentGridComponent<TData = any>
|
|
44
|
+
implements OnInit, OnDestroy, AfterViewInit, OnChanges
|
|
45
|
+
{
|
|
16
46
|
@Input() columnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null = null;
|
|
17
47
|
@Input() rowData: TData[] | null = null;
|
|
18
48
|
@Input() gridOptions: GridOptions<TData> | null = null;
|
|
49
|
+
@Input() theme: any;
|
|
19
50
|
@Input() height = '500px';
|
|
20
51
|
@Input() width = '100%';
|
|
21
52
|
@Input() rowHeight = 32;
|
|
22
|
-
|
|
53
|
+
@Input() rowSelection: RowSelectionOptions | 'single' | 'multiple' | undefined;
|
|
54
|
+
|
|
23
55
|
@Output() gridReady = new EventEmitter<GridApi<TData>>();
|
|
24
56
|
@Output() rowClicked = new EventEmitter<{ data: TData; node: IRowNode<TData> }>();
|
|
25
57
|
@Output() selectionChanged = new EventEmitter<IRowNode<TData>[]>();
|
|
@@ -41,8 +73,9 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
41
73
|
|
|
42
74
|
get totalWidth(): number {
|
|
43
75
|
if (!this.gridApi) return 0;
|
|
44
|
-
return this.gridApi
|
|
45
|
-
.
|
|
76
|
+
return this.gridApi
|
|
77
|
+
.getAllColumns()
|
|
78
|
+
.filter((col) => col.visible)
|
|
46
79
|
.reduce((sum, col) => sum + col.width, 0);
|
|
47
80
|
}
|
|
48
81
|
|
|
@@ -52,6 +85,14 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
52
85
|
isAllSelected = false;
|
|
53
86
|
isIndeterminateSelection = false;
|
|
54
87
|
|
|
88
|
+
hasCheckboxSelection(col: Column): boolean {
|
|
89
|
+
return col.colId === 'ag-Grid-SelectionColumn';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
hasHeaderCheckbox(col: Column): boolean {
|
|
93
|
+
return col.colId === 'ag-Grid-SelectionColumn';
|
|
94
|
+
}
|
|
95
|
+
|
|
55
96
|
trackByColumn(index: number, col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
56
97
|
return (col as any).colId || (col as any).field?.toString() || index.toString();
|
|
57
98
|
}
|
|
@@ -75,7 +116,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
75
116
|
|
|
76
117
|
// Range Selection state
|
|
77
118
|
isRangeSelecting = false;
|
|
78
|
-
private rangeStartCell: { rowIndex: number
|
|
119
|
+
private rangeStartCell: { rowIndex: number; colId: string } | null = null;
|
|
79
120
|
|
|
80
121
|
// Side Bar state
|
|
81
122
|
sideBarVisible = false;
|
|
@@ -85,7 +126,14 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
85
126
|
activeContextMenu = false;
|
|
86
127
|
contextMenuPosition = { x: 0, y: 0 };
|
|
87
128
|
contextMenuItems: MenuItemDef[] = [];
|
|
88
|
-
private contextMenuCell: { rowNode: IRowNode<TData
|
|
129
|
+
private contextMenuCell: { rowNode: IRowNode<TData>; column: Column } | null = null;
|
|
130
|
+
|
|
131
|
+
// Set Filter
|
|
132
|
+
activeSetFilter = false;
|
|
133
|
+
setFilterPosition = { x: 0, y: 0 };
|
|
134
|
+
setFilterValues: any[] = [];
|
|
135
|
+
setFilterValueFormatter?: (value: any) => string;
|
|
136
|
+
private activeSetFilterColumn: Column | null = null;
|
|
89
137
|
private initialColumnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null = null;
|
|
90
138
|
|
|
91
139
|
private gridApi!: GridApi<TData>;
|
|
@@ -95,7 +143,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
95
143
|
private horizontalScrollListener?: (e: Event) => void;
|
|
96
144
|
private resizeObserver?: ResizeObserver;
|
|
97
145
|
|
|
98
|
-
constructor(
|
|
146
|
+
constructor(
|
|
147
|
+
@Inject(ChangeDetectorRef) private _cdr: ChangeDetectorRef,
|
|
148
|
+
private _elementRef: ElementRef<HTMLElement>
|
|
149
|
+
) {}
|
|
99
150
|
|
|
100
151
|
ngOnInit(): void {
|
|
101
152
|
this.initialColumnDefs = this.columnDefs ? JSON.parse(JSON.stringify(this.columnDefs)) : null;
|
|
@@ -104,28 +155,60 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
104
155
|
|
|
105
156
|
ngOnChanges(changes: SimpleChanges): void {
|
|
106
157
|
// Handle rowData changes after initialization
|
|
107
|
-
if (changes
|
|
108
|
-
this.onRowDataChanged(changes
|
|
158
|
+
if (changes.rowData && !changes.rowData.firstChange) {
|
|
159
|
+
this.onRowDataChanged(changes.rowData.currentValue);
|
|
109
160
|
}
|
|
110
161
|
|
|
111
162
|
// Handle columnDefs changes
|
|
112
|
-
if (changes
|
|
113
|
-
this.onColumnDefsChanged(changes
|
|
163
|
+
if (changes.columnDefs && !changes.columnDefs.firstChange) {
|
|
164
|
+
this.onColumnDefsChanged(changes.columnDefs.currentValue);
|
|
114
165
|
}
|
|
115
166
|
|
|
116
167
|
// Handle gridOptions changes
|
|
117
|
-
if (changes
|
|
118
|
-
this.onGridOptionsChanged(changes
|
|
168
|
+
if (changes.gridOptions && !changes.gridOptions.firstChange) {
|
|
169
|
+
this.onGridOptionsChanged(changes.gridOptions.currentValue);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Handle rowSelection changes
|
|
173
|
+
if (changes.rowSelection && !changes.rowSelection.firstChange) {
|
|
174
|
+
if (this.gridApi) {
|
|
175
|
+
this.gridApi.setGridOption('rowSelection', changes.rowSelection.currentValue);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle theme changes
|
|
180
|
+
if (changes.theme && !changes.theme.firstChange) {
|
|
181
|
+
// Apply theme CSS variables to the grid container
|
|
182
|
+
if (changes.theme.currentValue) {
|
|
183
|
+
applyThemeCSSVariables(changes.theme.currentValue, this._elementRef.nativeElement);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Update canvas renderer theme if it's initialized
|
|
187
|
+
if (this.canvasRenderer) {
|
|
188
|
+
const convertedTheme = changes.theme.currentValue
|
|
189
|
+
? convertThemeToGridTheme(changes.theme.currentValue)
|
|
190
|
+
: undefined;
|
|
191
|
+
this.canvasRenderer.setTheme(convertedTheme);
|
|
192
|
+
}
|
|
119
193
|
}
|
|
120
194
|
}
|
|
121
195
|
|
|
122
196
|
ngAfterViewInit(): void {
|
|
123
197
|
// Setup canvas renderer after view is initialized
|
|
124
198
|
if (this.canvasRef && !this.canvasRenderer) {
|
|
199
|
+
// Convert theme from ThemeBuilder format to internal GridTheme format
|
|
200
|
+
const convertedTheme = this.theme ? convertThemeToGridTheme(this.theme) : undefined;
|
|
201
|
+
|
|
202
|
+
// Apply theme CSS variables to the grid container
|
|
203
|
+
if (this.theme) {
|
|
204
|
+
applyThemeCSSVariables(this.theme, this._elementRef.nativeElement);
|
|
205
|
+
}
|
|
206
|
+
|
|
125
207
|
this.canvasRenderer = new CanvasRenderer(
|
|
126
208
|
this.canvasRef.nativeElement,
|
|
127
209
|
this.gridApi,
|
|
128
|
-
this.rowHeight
|
|
210
|
+
this.rowHeight,
|
|
211
|
+
convertedTheme
|
|
129
212
|
);
|
|
130
213
|
|
|
131
214
|
// Wire up cell editing callback
|
|
@@ -141,28 +224,28 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
141
224
|
// Range Selection Logic
|
|
142
225
|
this.canvasRenderer.onMouseDown = (event, rowIndex, colId) => {
|
|
143
226
|
if (event.button !== 0 || !colId || rowIndex === -1) return;
|
|
144
|
-
|
|
227
|
+
|
|
145
228
|
const rangeSelectionEnabled = this.gridApi?.getGridOption('enableRangeSelection');
|
|
146
229
|
if (!rangeSelectionEnabled) return;
|
|
147
230
|
|
|
148
231
|
this.isRangeSelecting = true;
|
|
149
232
|
this.rangeStartCell = { rowIndex, colId };
|
|
150
|
-
|
|
233
|
+
|
|
151
234
|
// Clear previous selection if not holding Shift/Ctrl
|
|
152
235
|
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
|
|
153
236
|
this.gridApi?.clearRangeSelection();
|
|
154
237
|
}
|
|
155
238
|
};
|
|
156
239
|
|
|
157
|
-
this.canvasRenderer.onMouseMove = (
|
|
240
|
+
this.canvasRenderer.onMouseMove = (_event, rowIndex, colId) => {
|
|
158
241
|
if (!this.isRangeSelecting || !this.rangeStartCell || !colId || rowIndex === -1) return;
|
|
159
242
|
|
|
160
243
|
const start = this.rangeStartCell;
|
|
161
244
|
const end = { rowIndex, colId };
|
|
162
245
|
|
|
163
246
|
const columns = this.canvasRenderer.getAllColumns();
|
|
164
|
-
const startColIdx = columns.findIndex(c => c.colId === start.colId);
|
|
165
|
-
const endColIdx = columns.findIndex(c => c.colId === end.colId);
|
|
247
|
+
const startColIdx = columns.findIndex((c) => c.colId === start.colId);
|
|
248
|
+
const endColIdx = columns.findIndex((c) => c.colId === end.colId);
|
|
166
249
|
|
|
167
250
|
if (startColIdx === -1 || endColIdx === -1) return;
|
|
168
251
|
|
|
@@ -171,7 +254,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
171
254
|
endRow: Math.max(start.rowIndex, end.rowIndex),
|
|
172
255
|
startColumn: columns[Math.min(startColIdx, endColIdx)].colId,
|
|
173
256
|
endColumn: columns[Math.max(startColIdx, endColIdx)].colId,
|
|
174
|
-
columns: columns.slice(
|
|
257
|
+
columns: columns.slice(
|
|
258
|
+
Math.min(startColIdx, endColIdx),
|
|
259
|
+
Math.max(startColIdx, endColIdx) + 1
|
|
260
|
+
),
|
|
175
261
|
};
|
|
176
262
|
|
|
177
263
|
this.gridApi?.addCellRange(range);
|
|
@@ -187,29 +273,32 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
187
273
|
const rect = this.viewportRef.nativeElement.getBoundingClientRect();
|
|
188
274
|
this.viewportHeight = rect.height || 500;
|
|
189
275
|
this.canvasRenderer?.setViewportDimensions(rect.width, this.viewportHeight);
|
|
190
|
-
this.canvasRenderer?.setTotalRowCount(this.rowData?.length || 0);
|
|
191
276
|
|
|
192
277
|
// Synchronize horizontal scroll with DOM header
|
|
193
278
|
this.horizontalScrollListener = () => {
|
|
194
279
|
if (this.headerScrollableRef) {
|
|
195
|
-
this.headerScrollableRef.nativeElement.scrollLeft =
|
|
280
|
+
this.headerScrollableRef.nativeElement.scrollLeft =
|
|
281
|
+
this.viewportRef.nativeElement.scrollLeft;
|
|
196
282
|
}
|
|
197
283
|
if (this.headerScrollableFilterRef) {
|
|
198
|
-
this.headerScrollableFilterRef.nativeElement.scrollLeft =
|
|
284
|
+
this.headerScrollableFilterRef.nativeElement.scrollLeft =
|
|
285
|
+
this.viewportRef.nativeElement.scrollLeft;
|
|
199
286
|
}
|
|
200
287
|
};
|
|
201
|
-
|
|
202
|
-
this.viewportRef.nativeElement.addEventListener('scroll', this.horizontalScrollListener, {
|
|
288
|
+
|
|
289
|
+
this.viewportRef.nativeElement.addEventListener('scroll', this.horizontalScrollListener, {
|
|
290
|
+
passive: true,
|
|
291
|
+
});
|
|
203
292
|
|
|
204
293
|
// Add ResizeObserver to handle sidebar toggling and other size changes
|
|
205
294
|
if (typeof ResizeObserver !== 'undefined') {
|
|
206
|
-
this.resizeObserver = new ResizeObserver(entries => {
|
|
295
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
207
296
|
for (const entry of entries) {
|
|
208
297
|
const { width, height } = entry.contentRect;
|
|
209
298
|
this.viewportHeight = height;
|
|
210
299
|
this.canvasRenderer?.setViewportDimensions(width, height);
|
|
211
300
|
this.canvasRenderer?.render();
|
|
212
|
-
this.
|
|
301
|
+
this._cdr.detectChanges();
|
|
213
302
|
}
|
|
214
303
|
});
|
|
215
304
|
this.resizeObserver.observe(this.viewportRef.nativeElement);
|
|
@@ -235,24 +324,36 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
235
324
|
}
|
|
236
325
|
|
|
237
326
|
private initializeGrid(): void {
|
|
327
|
+
// Merge individual inputs into grid options if provided
|
|
328
|
+
const options = { ...this.gridOptions };
|
|
329
|
+
if (this.rowSelection) {
|
|
330
|
+
options.rowSelection = this.rowSelection;
|
|
331
|
+
}
|
|
332
|
+
|
|
238
333
|
// Initialize grid API
|
|
239
|
-
this.gridApi = this.gridService.createApi(this.columnDefs, this.rowData,
|
|
334
|
+
this.gridApi = this.gridService.createApi(this.columnDefs, this.rowData, options);
|
|
240
335
|
|
|
241
336
|
// Listen for grid state changes from API (filters, sorts, options)
|
|
242
|
-
this.gridService.gridStateChanged$
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
337
|
+
this.gridService.gridStateChanged$.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
|
338
|
+
if (event.type === 'optionChanged' && event.key === 'sideBar') {
|
|
339
|
+
this.sideBarVisible = !!event.value;
|
|
340
|
+
}
|
|
341
|
+
if (event.type === 'selectionChanged') {
|
|
342
|
+
this.updateSelectionState();
|
|
343
|
+
|
|
344
|
+
// Mark all rows as potentially dirty for selection change to ensure canvas redraws
|
|
345
|
+
// In a more optimized version, we'd only mark specific rows.
|
|
346
|
+
if (this.canvasRenderer) {
|
|
347
|
+
this.canvasRenderer.render(); // This calls markAllDirty and schedules render
|
|
247
348
|
}
|
|
349
|
+
} else {
|
|
248
350
|
this.canvasRenderer?.render();
|
|
249
|
-
|
|
250
|
-
|
|
351
|
+
}
|
|
352
|
+
this._cdr.detectChanges();
|
|
353
|
+
});
|
|
251
354
|
|
|
252
|
-
//
|
|
253
|
-
this.showSelectionColumn =
|
|
254
|
-
!('children' in col) && col.checkboxSelection
|
|
255
|
-
) || false;
|
|
355
|
+
// Selection column is now handled within the data columns
|
|
356
|
+
this.showSelectionColumn = false;
|
|
256
357
|
|
|
257
358
|
// Canvas renderer will be initialized in ngAfterViewInit
|
|
258
359
|
|
|
@@ -277,7 +378,6 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
277
378
|
|
|
278
379
|
if (this.gridApi) {
|
|
279
380
|
this.gridApi.setRowData(newData || []);
|
|
280
|
-
this.canvasRenderer?.setTotalRowCount(newData?.length || 0);
|
|
281
381
|
this.canvasRenderer?.render();
|
|
282
382
|
}
|
|
283
383
|
|
|
@@ -285,7 +385,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
285
385
|
this.updateSelectionState();
|
|
286
386
|
|
|
287
387
|
// Trigger change detection with OnPush
|
|
288
|
-
this.
|
|
388
|
+
this._cdr.detectChanges();
|
|
289
389
|
}
|
|
290
390
|
|
|
291
391
|
private onColumnDefsChanged(newColumnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null): void {
|
|
@@ -295,22 +395,22 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
295
395
|
this.gridApi.setColumnDefs(newColumnDefs);
|
|
296
396
|
this.canvasRenderer?.render();
|
|
297
397
|
}
|
|
298
|
-
|
|
299
|
-
this.
|
|
398
|
+
|
|
399
|
+
this._cdr.detectChanges();
|
|
300
400
|
}
|
|
301
401
|
|
|
302
402
|
private onGridOptionsChanged(newOptions: GridOptions<TData> | null): void {
|
|
303
403
|
this.gridOptions = newOptions;
|
|
304
404
|
if (this.gridApi && newOptions) {
|
|
305
405
|
// Update all options in the API
|
|
306
|
-
Object.keys(newOptions).forEach(key => {
|
|
406
|
+
Object.keys(newOptions).forEach((key) => {
|
|
307
407
|
this.gridApi.setGridOption(key as any, (newOptions as any)[key]);
|
|
308
408
|
});
|
|
309
409
|
this.canvasRenderer?.render();
|
|
310
410
|
}
|
|
311
|
-
this.
|
|
411
|
+
this._cdr.detectChanges();
|
|
312
412
|
}
|
|
313
|
-
|
|
413
|
+
|
|
314
414
|
getColumnWidth(col: Column | ColDef<TData> | ColGroupDef<TData>): number {
|
|
315
415
|
if ('children' in col) {
|
|
316
416
|
// Column group - sum children widths
|
|
@@ -321,26 +421,29 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
321
421
|
|
|
322
422
|
getLeftPinnedColumns(): Column[] {
|
|
323
423
|
if (!this.gridApi) return [];
|
|
324
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
424
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
325
425
|
return col.visible && col.pinned === 'left';
|
|
326
426
|
});
|
|
327
427
|
}
|
|
328
428
|
|
|
329
429
|
getRightPinnedColumns(): Column[] {
|
|
330
430
|
if (!this.gridApi) return [];
|
|
331
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
431
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
332
432
|
return col.visible && col.pinned === 'right';
|
|
333
433
|
});
|
|
334
434
|
}
|
|
335
435
|
|
|
336
436
|
getNonPinnedColumns(): Column[] {
|
|
337
437
|
if (!this.gridApi) return [];
|
|
338
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
438
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
339
439
|
return col.visible && !col.pinned;
|
|
340
440
|
});
|
|
341
441
|
}
|
|
342
|
-
|
|
442
|
+
|
|
343
443
|
isSortable(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
444
|
+
const colId = (col as any).colId || (col as any).field?.toString();
|
|
445
|
+
if (colId === 'ag-Grid-SelectionColumn') return false;
|
|
446
|
+
|
|
344
447
|
// If it has children, it's a group and cannot be sorted directly
|
|
345
448
|
if ('children' in col) return false;
|
|
346
449
|
|
|
@@ -351,34 +454,44 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
351
454
|
|
|
352
455
|
// It's likely a Column object, look up its ColDef
|
|
353
456
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
354
|
-
return colDef ?
|
|
457
|
+
return colDef ? colDef.sortable !== false : true;
|
|
355
458
|
}
|
|
356
|
-
|
|
459
|
+
|
|
357
460
|
getHeaderName(col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
358
461
|
if ('children' in col) {
|
|
359
462
|
return col.headerName || '';
|
|
360
463
|
}
|
|
464
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') {
|
|
465
|
+
return '';
|
|
466
|
+
}
|
|
361
467
|
return col.headerName || (col as any).field?.toString() || '';
|
|
362
468
|
}
|
|
363
|
-
|
|
469
|
+
|
|
364
470
|
getSortIndicator(col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
365
471
|
if ('children' in col || !col.sort) {
|
|
366
472
|
return '';
|
|
367
473
|
}
|
|
368
474
|
return col.sort === 'asc' ? '▲' : '▼';
|
|
369
475
|
}
|
|
370
|
-
|
|
476
|
+
|
|
371
477
|
onHeaderClick(col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
478
|
+
if (this.isResizing) return;
|
|
479
|
+
|
|
480
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') {
|
|
481
|
+
// Selection is now handled by the checkbox directly to avoid resizing interference
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
372
485
|
if (!this.isSortable(col) || 'children' in col) {
|
|
373
486
|
return;
|
|
374
487
|
}
|
|
375
|
-
|
|
488
|
+
|
|
376
489
|
// Toggle sort
|
|
377
490
|
const currentSort = col.sort;
|
|
378
491
|
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
379
|
-
|
|
492
|
+
|
|
380
493
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
381
|
-
|
|
494
|
+
|
|
382
495
|
// Update the column directly if it's a Column object
|
|
383
496
|
if ('colId' in col && !(col as any).children) {
|
|
384
497
|
(col as any).sort = newSort;
|
|
@@ -390,7 +503,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
390
503
|
|
|
391
504
|
// --- Header Menu Logic ---
|
|
392
505
|
|
|
506
|
+
headerMenuItems: MenuItemDef[] = [];
|
|
507
|
+
|
|
393
508
|
hasHeaderMenu(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
509
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') return false;
|
|
394
510
|
if ('children' in col) return false;
|
|
395
511
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
396
512
|
return colDef ? colDef.suppressHeaderMenuButton !== true : true;
|
|
@@ -398,36 +514,130 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
398
514
|
|
|
399
515
|
onHeaderMenuClick(event: MouseEvent, col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
400
516
|
event.stopPropagation();
|
|
401
|
-
|
|
517
|
+
|
|
402
518
|
if (this.activeHeaderMenu === col) {
|
|
403
519
|
this.closeHeaderMenu();
|
|
404
520
|
return;
|
|
405
521
|
}
|
|
406
522
|
|
|
407
523
|
this.activeHeaderMenu = col;
|
|
408
|
-
|
|
524
|
+
this.headerMenuItems = this.getHeaderMenuItems(col as Column);
|
|
525
|
+
|
|
409
526
|
// Position menu below the icon using fixed (viewport) coordinates
|
|
410
527
|
const target = event.target as HTMLElement;
|
|
411
528
|
const rect = target.getBoundingClientRect();
|
|
412
|
-
|
|
529
|
+
|
|
413
530
|
let x = rect.right - 200; // Align right, assuming menu width ~200px
|
|
414
531
|
let y = rect.bottom + 4;
|
|
415
532
|
|
|
416
533
|
// Prevent menu from going off-screen
|
|
417
534
|
if (x < 0) x = 0;
|
|
418
535
|
if (x + 200 > window.innerWidth) x = window.innerWidth - 200;
|
|
419
|
-
|
|
420
|
-
|
|
536
|
+
|
|
537
|
+
// Check if menu would overflow bottom
|
|
538
|
+
const estimatedHeight = this.headerMenuItems.length * 30 + 20;
|
|
539
|
+
if (y + estimatedHeight > window.innerHeight) {
|
|
540
|
+
y = Math.max(0, rect.top - estimatedHeight);
|
|
421
541
|
}
|
|
422
|
-
|
|
542
|
+
|
|
423
543
|
this.headerMenuPosition = { x, y };
|
|
424
|
-
|
|
425
|
-
this.
|
|
544
|
+
|
|
545
|
+
this._cdr.detectChanges();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private getHeaderMenuItems(col: Column): MenuItemDef[] {
|
|
549
|
+
const items: MenuItemDef[] = [];
|
|
550
|
+
|
|
551
|
+
// 1. Sort items
|
|
552
|
+
items.push({
|
|
553
|
+
name: 'Sort Ascending',
|
|
554
|
+
icon: '↑',
|
|
555
|
+
action: () => this.sortColumnMenu('asc'),
|
|
556
|
+
});
|
|
557
|
+
items.push({
|
|
558
|
+
name: 'Sort Descending',
|
|
559
|
+
icon: '↓',
|
|
560
|
+
action: () => this.sortColumnMenu('desc'),
|
|
561
|
+
});
|
|
562
|
+
items.push({
|
|
563
|
+
name: 'Clear Sort',
|
|
564
|
+
icon: '✕',
|
|
565
|
+
action: () => this.sortColumnMenu(null),
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
569
|
+
|
|
570
|
+
// 2. Filter items
|
|
571
|
+
const colDef = this.getColumnDefForColumn(col);
|
|
572
|
+
if (colDef && colDef.filter !== false) {
|
|
573
|
+
const filterType = colDef.filter || 'text';
|
|
574
|
+
|
|
575
|
+
if (filterType === 'set') {
|
|
576
|
+
items.push({
|
|
577
|
+
name: 'Filter...',
|
|
578
|
+
icon: 'Y',
|
|
579
|
+
action: () => {
|
|
580
|
+
this.openSetFilter(null, col, { ...this.headerMenuPosition });
|
|
581
|
+
this.closeHeaderMenu();
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
} else {
|
|
585
|
+
items.push({
|
|
586
|
+
name: 'Filter...',
|
|
587
|
+
icon: 'Y',
|
|
588
|
+
action: () => {
|
|
589
|
+
this.openFilterPopup(null, col, { ...this.headerMenuPosition });
|
|
590
|
+
this.closeHeaderMenu();
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
597
|
+
|
|
598
|
+
// 3. Pinning items
|
|
599
|
+
items.push({
|
|
600
|
+
name: 'Pin Left',
|
|
601
|
+
icon: '«',
|
|
602
|
+
action: () => this.pinColumnMenu('left'),
|
|
603
|
+
});
|
|
604
|
+
items.push({
|
|
605
|
+
name: 'Pin Right',
|
|
606
|
+
icon: '»',
|
|
607
|
+
action: () => this.pinColumnMenu('right'),
|
|
608
|
+
});
|
|
609
|
+
items.push({
|
|
610
|
+
name: 'Unpin',
|
|
611
|
+
icon: '↺',
|
|
612
|
+
action: () => this.pinColumnMenu(null),
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
616
|
+
|
|
617
|
+
// 4. Hide item
|
|
618
|
+
items.push({
|
|
619
|
+
name: 'Hide Column',
|
|
620
|
+
icon: 'ø',
|
|
621
|
+
action: () => this.hideColumnMenu(),
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
return items;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
public clearColumnFilter(col: Column): void {
|
|
628
|
+
const field = col.field;
|
|
629
|
+
if (!field || !this.gridApi) return;
|
|
630
|
+
|
|
631
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
632
|
+
delete (currentModel as any)[field];
|
|
633
|
+
this.gridApi.setFilterModel(currentModel);
|
|
634
|
+
this.closeHeaderMenu();
|
|
635
|
+
this.closeFilterPopup();
|
|
426
636
|
}
|
|
427
637
|
|
|
428
638
|
closeHeaderMenu(): void {
|
|
429
639
|
this.activeHeaderMenu = null;
|
|
430
|
-
this.
|
|
640
|
+
this._cdr.detectChanges();
|
|
431
641
|
}
|
|
432
642
|
|
|
433
643
|
onContainerClick(event: MouseEvent): void {
|
|
@@ -448,19 +658,19 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
448
658
|
|
|
449
659
|
onCanvasContextMenu(event: MouseEvent): void {
|
|
450
660
|
event.preventDefault();
|
|
451
|
-
|
|
661
|
+
|
|
452
662
|
// Get hit test from canvas renderer to know which cell was clicked
|
|
453
663
|
const hitTest = this.canvasRenderer.getHitTestResult(event);
|
|
454
664
|
if (!hitTest || hitTest.rowIndex === -1) return;
|
|
455
|
-
|
|
665
|
+
|
|
456
666
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(hitTest.rowIndex);
|
|
457
|
-
const columns = this.gridApi.getAllColumns().filter(col => col.visible);
|
|
667
|
+
const columns = this.gridApi.getAllColumns().filter((col) => col.visible);
|
|
458
668
|
const column = columns[hitTest.columnIndex];
|
|
459
|
-
|
|
669
|
+
|
|
460
670
|
if (!rowNode || !column) return;
|
|
461
|
-
|
|
671
|
+
|
|
462
672
|
this.contextMenuCell = { rowNode, column };
|
|
463
|
-
|
|
673
|
+
|
|
464
674
|
// Resolve menu items via API if provided
|
|
465
675
|
const getContextMenuItems = this.gridApi.getGridOption('getContextMenuItems');
|
|
466
676
|
if (getContextMenuItems) {
|
|
@@ -469,20 +679,25 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
469
679
|
column: column,
|
|
470
680
|
api: this.gridApi,
|
|
471
681
|
type: 'cell',
|
|
472
|
-
event: event
|
|
682
|
+
event: event,
|
|
473
683
|
};
|
|
474
684
|
this.contextMenuItems = this.resolveContextMenuItems(getContextMenuItems(params));
|
|
475
685
|
} else {
|
|
476
686
|
// Fallback to defaults if no callback provided
|
|
477
687
|
this.contextMenuItems = this.resolveContextMenuItems([
|
|
478
|
-
'copy',
|
|
688
|
+
'copy',
|
|
689
|
+
'copyWithHeaders',
|
|
690
|
+
'separator',
|
|
691
|
+
'export',
|
|
692
|
+
'separator',
|
|
693
|
+
'resetColumns',
|
|
479
694
|
]);
|
|
480
695
|
}
|
|
481
696
|
|
|
482
697
|
if (this.contextMenuItems.length === 0) return;
|
|
483
698
|
|
|
484
699
|
this.activeContextMenu = true;
|
|
485
|
-
|
|
700
|
+
|
|
486
701
|
// Position menu at mouse coordinates (fixed/viewport)
|
|
487
702
|
let x = event.clientX;
|
|
488
703
|
let y = event.clientY;
|
|
@@ -492,21 +707,21 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
492
707
|
if (y + 200 > window.innerHeight) y = window.innerHeight - 200;
|
|
493
708
|
|
|
494
709
|
this.contextMenuPosition = { x, y };
|
|
495
|
-
|
|
710
|
+
|
|
496
711
|
// Select the row
|
|
497
712
|
this.gridApi.deselectAll();
|
|
498
713
|
rowNode.selected = true;
|
|
499
714
|
this.updateSelectionState();
|
|
500
715
|
this.canvasRenderer?.render();
|
|
501
716
|
this.selectionChanged.emit(this.gridApi.getSelectedRows());
|
|
502
|
-
|
|
503
|
-
this.
|
|
717
|
+
|
|
718
|
+
this._cdr.detectChanges();
|
|
504
719
|
}
|
|
505
720
|
|
|
506
721
|
private resolveContextMenuItems(items: (DefaultMenuItem | MenuItemDef)[]): MenuItemDef[] {
|
|
507
722
|
const resolved: MenuItemDef[] = [];
|
|
508
|
-
|
|
509
|
-
items.forEach(item => {
|
|
723
|
+
|
|
724
|
+
items.forEach((item) => {
|
|
510
725
|
if (typeof item === 'string') {
|
|
511
726
|
const defaultItem = this.getDefaultMenuItem(item);
|
|
512
727
|
if (defaultItem) resolved.push(defaultItem);
|
|
@@ -514,7 +729,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
514
729
|
resolved.push(item);
|
|
515
730
|
}
|
|
516
731
|
});
|
|
517
|
-
|
|
732
|
+
|
|
518
733
|
return resolved;
|
|
519
734
|
}
|
|
520
735
|
|
|
@@ -523,17 +738,18 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
523
738
|
case 'copy':
|
|
524
739
|
return { name: 'Copy Cell', action: () => this.copyContextMenuCell(), icon: '📋' };
|
|
525
740
|
case 'copyWithHeaders':
|
|
526
|
-
return this.hasRangeSelection()
|
|
527
|
-
{ name: 'Copy with Headers', action: () => this.copyRangeWithHeaders(), icon: '📋' }
|
|
741
|
+
return this.hasRangeSelection()
|
|
742
|
+
? { name: 'Copy with Headers', action: () => this.copyRangeWithHeaders(), icon: '📋' }
|
|
743
|
+
: null;
|
|
528
744
|
case 'export':
|
|
529
|
-
return {
|
|
530
|
-
name: 'Export',
|
|
531
|
-
action: () => {},
|
|
745
|
+
return {
|
|
746
|
+
name: 'Export',
|
|
747
|
+
action: () => {},
|
|
532
748
|
icon: '⤓',
|
|
533
749
|
subMenu: [
|
|
534
750
|
{ name: 'Export to CSV', action: () => this.exportCSV() },
|
|
535
|
-
{ name: 'Export to Excel (.xlsx)', action: () => this.exportExcel() }
|
|
536
|
-
]
|
|
751
|
+
{ name: 'Export to Excel (.xlsx)', action: () => this.exportExcel() },
|
|
752
|
+
],
|
|
537
753
|
};
|
|
538
754
|
case 'resetColumns':
|
|
539
755
|
return { name: 'Reset Columns', action: () => this.resetColumns(), icon: '⟲' };
|
|
@@ -547,7 +763,224 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
547
763
|
closeContextMenu(): void {
|
|
548
764
|
this.activeContextMenu = false;
|
|
549
765
|
this.contextMenuCell = null;
|
|
550
|
-
this.
|
|
766
|
+
this._cdr.detectChanges();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Set Filter Methods
|
|
770
|
+
isSetFilter(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
771
|
+
if ('children' in col) return false;
|
|
772
|
+
const colDef = col as ColDef<TData>;
|
|
773
|
+
return colDef.filter === 'set';
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
openSetFilter(
|
|
777
|
+
event: MouseEvent | null,
|
|
778
|
+
col: Column | ColDef<TData>,
|
|
779
|
+
position?: { x: number; y: number }
|
|
780
|
+
): void {
|
|
781
|
+
if (event) {
|
|
782
|
+
event.stopPropagation();
|
|
783
|
+
event.preventDefault();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
this.activeSetFilterColumn = col as Column;
|
|
787
|
+
|
|
788
|
+
const field = col.field;
|
|
789
|
+
if (!field || !this.gridApi) return;
|
|
790
|
+
|
|
791
|
+
this.setFilterValues = this.gridService.getUniqueValues(field as string);
|
|
792
|
+
const colDef = 'field' in col ? (col as ColDef<TData>) : null;
|
|
793
|
+
this.setFilterValueFormatter = colDef?.valueFormatter
|
|
794
|
+
? (colDef.valueFormatter as any)
|
|
795
|
+
: undefined;
|
|
796
|
+
|
|
797
|
+
if (position) {
|
|
798
|
+
this.setFilterPosition = position;
|
|
799
|
+
} else if (event) {
|
|
800
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
801
|
+
this.setFilterPosition = {
|
|
802
|
+
x: rect.left,
|
|
803
|
+
y: rect.bottom + 5,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
setTimeout(() => {
|
|
808
|
+
this.activeSetFilter = true;
|
|
809
|
+
this._cdr.detectChanges();
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
closeSetFilter(): void {
|
|
814
|
+
this.activeSetFilter = false;
|
|
815
|
+
this.activeSetFilterColumn = null;
|
|
816
|
+
this._cdr.detectChanges();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Filter Popup state
|
|
820
|
+
activeFilterPopup = false;
|
|
821
|
+
activeFilterPopupColumn: Column | null = null;
|
|
822
|
+
activeFilterPopupType: 'text' | 'number' | 'date' | 'boolean' | 'set' | 'multiFilter' = 'text';
|
|
823
|
+
activeFilterOperator: string = 'contains';
|
|
824
|
+
filterPopupPosition = { x: 0, y: 0 };
|
|
825
|
+
filterValue1: string = '';
|
|
826
|
+
filterValue2: string = '';
|
|
827
|
+
|
|
828
|
+
readonly textFilterOperators = [
|
|
829
|
+
{ value: 'contains', label: 'Contains' },
|
|
830
|
+
{ value: 'notContains', label: 'Not contains' },
|
|
831
|
+
{ value: 'equals', label: 'Equals' },
|
|
832
|
+
{ value: 'notEquals', label: 'Not equals' },
|
|
833
|
+
{ value: 'startsWith', label: 'Starts with' },
|
|
834
|
+
{ value: 'endsWith', label: 'Ends with' },
|
|
835
|
+
{ value: 'blank', label: 'Blank' },
|
|
836
|
+
{ value: 'notBlank', label: 'Not blank' },
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
readonly numberFilterOperators = [
|
|
840
|
+
{ value: 'equals', label: 'Equals' },
|
|
841
|
+
{ value: 'notEquals', label: 'Not equals' },
|
|
842
|
+
{ value: 'greaterThan', label: 'Greater than' },
|
|
843
|
+
{ value: 'greaterThanOrEqual', label: 'Greater than or equals' },
|
|
844
|
+
{ value: 'lessThan', label: 'Less than' },
|
|
845
|
+
{ value: 'lessThanOrEqual', label: 'Less than or equals' },
|
|
846
|
+
{ value: 'inRange', label: 'In range' },
|
|
847
|
+
{ value: 'blank', label: 'Blank' },
|
|
848
|
+
{ value: 'notBlank', label: 'Not blank' },
|
|
849
|
+
];
|
|
850
|
+
|
|
851
|
+
onFilterPopupOperatorChange(operator: string): void {
|
|
852
|
+
this.activeFilterOperator = operator;
|
|
853
|
+
this.applyPopupFilter();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
onFilterPopupInput(event: Event, isSecondValue: boolean = false): void {
|
|
857
|
+
const value = (event.target as HTMLInputElement).value;
|
|
858
|
+
if (isSecondValue) {
|
|
859
|
+
this.filterValue2 = value;
|
|
860
|
+
} else {
|
|
861
|
+
this.filterValue1 = value;
|
|
862
|
+
}
|
|
863
|
+
this.applyPopupFilter();
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
private applyPopupFilter(): void {
|
|
867
|
+
if (!this.activeFilterPopupColumn || !this.gridApi) return;
|
|
868
|
+
|
|
869
|
+
const col = this.activeFilterPopupColumn;
|
|
870
|
+
const field = col.field;
|
|
871
|
+
if (!field) return;
|
|
872
|
+
|
|
873
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
874
|
+
|
|
875
|
+
if (this.activeFilterOperator === 'blank' || this.activeFilterOperator === 'notBlank') {
|
|
876
|
+
currentModel[col.colId] = {
|
|
877
|
+
filterType: this.activeFilterPopupType,
|
|
878
|
+
type: this.activeFilterOperator,
|
|
879
|
+
};
|
|
880
|
+
} else {
|
|
881
|
+
const value = this.filterValue1;
|
|
882
|
+
if (!value && this.activeFilterOperator !== 'inRange') {
|
|
883
|
+
delete currentModel[col.colId];
|
|
884
|
+
} else {
|
|
885
|
+
const filterModel: any = {
|
|
886
|
+
filterType: this.activeFilterPopupType,
|
|
887
|
+
type: this.activeFilterOperator,
|
|
888
|
+
filter: value,
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
if (this.activeFilterOperator === 'inRange') {
|
|
892
|
+
filterModel.filterTo = this.filterValue2;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
currentModel[col.colId] = filterModel;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
this.gridApi.setFilterModel(currentModel);
|
|
900
|
+
this.canvasRenderer?.render();
|
|
901
|
+
this._cdr.detectChanges();
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
openFilterPopup(
|
|
905
|
+
event: MouseEvent | null,
|
|
906
|
+
col: Column,
|
|
907
|
+
position?: { x: number; y: number }
|
|
908
|
+
): void {
|
|
909
|
+
this.activeFilterPopupColumn = col;
|
|
910
|
+
const colDef = this.getColumnDefForColumn(col);
|
|
911
|
+
this.activeFilterPopupType = colDef?.filter === 'number' ? 'number' : 'text';
|
|
912
|
+
|
|
913
|
+
// Initialize operator and values from current model or default
|
|
914
|
+
const model = this.gridApi?.getFilterModel()[col.colId] as any;
|
|
915
|
+
this.activeFilterOperator =
|
|
916
|
+
model?.type || (this.activeFilterPopupType === 'number' ? 'equals' : 'contains');
|
|
917
|
+
this.filterValue1 = model?.filter || '';
|
|
918
|
+
this.filterValue2 = model?.filterTo || '';
|
|
919
|
+
|
|
920
|
+
if (position) {
|
|
921
|
+
this.filterPopupPosition = position;
|
|
922
|
+
} else if (event) {
|
|
923
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
924
|
+
this.filterPopupPosition = { x: rect.left, y: rect.bottom + 5 };
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
setTimeout(() => {
|
|
928
|
+
this.activeFilterPopup = true;
|
|
929
|
+
this._cdr.detectChanges();
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
closeFilterPopup(): void {
|
|
934
|
+
this.activeFilterPopup = false;
|
|
935
|
+
this.activeFilterPopupColumn = null;
|
|
936
|
+
this._cdr.detectChanges();
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
onSetFilterChanged(values: any[]): void {
|
|
940
|
+
if (!this.activeSetFilterColumn || !this.gridApi) return;
|
|
941
|
+
|
|
942
|
+
const field = this.activeSetFilterColumn.field;
|
|
943
|
+
if (!field) return;
|
|
944
|
+
|
|
945
|
+
if (values.length === 0) {
|
|
946
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
947
|
+
delete (currentModel as any)[field];
|
|
948
|
+
this.gridApi.setFilterModel(currentModel);
|
|
949
|
+
} else {
|
|
950
|
+
this.gridApi.setFilterModel({
|
|
951
|
+
...this.gridApi.getFilterModel(),
|
|
952
|
+
[field]: {
|
|
953
|
+
filterType: 'set',
|
|
954
|
+
values: values,
|
|
955
|
+
},
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
this.closeSetFilter();
|
|
960
|
+
this.canvasRenderer?.render();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
hasSetFilterValue(col: Column | ColDef<TData>): boolean {
|
|
964
|
+
if (!this.gridApi) return false;
|
|
965
|
+
const field = 'field' in col ? col.field : null;
|
|
966
|
+
if (!field) return false;
|
|
967
|
+
|
|
968
|
+
const model = this.gridApi.getFilterModel();
|
|
969
|
+
const filter = (model as any)[field];
|
|
970
|
+
return filter && filter.filterType === 'set' && filter.values && filter.values.length > 0;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
getSetFilterCount(col: Column | ColDef<TData>): number {
|
|
974
|
+
if (!this.gridApi) return 0;
|
|
975
|
+
const field = 'field' in col ? col.field : null;
|
|
976
|
+
if (!field) return 0;
|
|
977
|
+
|
|
978
|
+
const model = this.gridApi.getFilterModel();
|
|
979
|
+
const filter = (model as any)[field];
|
|
980
|
+
if (filter && filter.filterType === 'set' && Array.isArray(filter.values)) {
|
|
981
|
+
return filter.values.length;
|
|
982
|
+
}
|
|
983
|
+
return 0;
|
|
551
984
|
}
|
|
552
985
|
|
|
553
986
|
// Side Bar Methods
|
|
@@ -557,7 +990,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
557
990
|
} else {
|
|
558
991
|
this.activeToolPanel = panel;
|
|
559
992
|
}
|
|
560
|
-
this.
|
|
993
|
+
this._cdr.detectChanges();
|
|
561
994
|
}
|
|
562
995
|
|
|
563
996
|
toggleColumnVisibility(col: Column): void {
|
|
@@ -566,7 +999,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
566
999
|
colDef.hide = col.visible; // Toggle
|
|
567
1000
|
this.initializeGrid(); // Re-initialize to handle visibility changes correctly
|
|
568
1001
|
this.canvasRenderer?.render();
|
|
569
|
-
this.
|
|
1002
|
+
this._cdr.detectChanges();
|
|
570
1003
|
}
|
|
571
1004
|
}
|
|
572
1005
|
|
|
@@ -582,7 +1015,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
582
1015
|
|
|
583
1016
|
// Map back to ColDefs
|
|
584
1017
|
const newDefs: (ColDef<TData> | ColGroupDef<TData>)[] = [];
|
|
585
|
-
columns.forEach(col => {
|
|
1018
|
+
columns.forEach((col) => {
|
|
586
1019
|
const def = this.getColumnDefForColumn(col);
|
|
587
1020
|
if (def) newDefs.push(def);
|
|
588
1021
|
});
|
|
@@ -592,10 +1025,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
592
1025
|
|
|
593
1026
|
copyContextMenuCell(): void {
|
|
594
1027
|
if (!this.contextMenuCell || !this.contextMenuCell.column.field) return;
|
|
595
|
-
|
|
1028
|
+
|
|
596
1029
|
const val = (this.contextMenuCell.rowNode.data as any)[this.contextMenuCell.column.field];
|
|
597
1030
|
if (val !== undefined && val !== null) {
|
|
598
|
-
navigator.clipboard.writeText(String(val)).catch(err => {
|
|
1031
|
+
navigator.clipboard.writeText(String(val)).catch((err) => {
|
|
599
1032
|
console.error('Failed to copy text: ', err);
|
|
600
1033
|
});
|
|
601
1034
|
}
|
|
@@ -612,20 +1045,22 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
612
1045
|
|
|
613
1046
|
const range = ranges[0];
|
|
614
1047
|
const columns = range.columns;
|
|
615
|
-
|
|
616
|
-
let text = columns.map(c => this.getHeaderName(c)).join('\t')
|
|
1048
|
+
|
|
1049
|
+
let text = `${columns.map((c) => this.getHeaderName(c)).join('\t')}\n`;
|
|
617
1050
|
|
|
618
1051
|
for (let i = range.startRow; i <= range.endRow; i++) {
|
|
619
1052
|
const node = this.gridApi.getDisplayedRowAtIndex(i);
|
|
620
1053
|
if (node) {
|
|
621
|
-
text += columns
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1054
|
+
text += `${columns
|
|
1055
|
+
.map((c) => {
|
|
1056
|
+
const val = (node.data as any)[c.field || ''];
|
|
1057
|
+
return val !== null && val !== undefined ? String(val) : '';
|
|
1058
|
+
})
|
|
1059
|
+
.join('\t')}\n`;
|
|
625
1060
|
}
|
|
626
1061
|
}
|
|
627
1062
|
|
|
628
|
-
navigator.clipboard.writeText(text).catch(err => {
|
|
1063
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
629
1064
|
console.error('Failed to copy range: ', err);
|
|
630
1065
|
});
|
|
631
1066
|
this.closeContextMenu();
|
|
@@ -646,7 +1081,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
646
1081
|
// Deep copy back the original defs
|
|
647
1082
|
const restored = JSON.parse(JSON.stringify(this.initialColumnDefs));
|
|
648
1083
|
this.onColumnDefsChanged(restored);
|
|
649
|
-
|
|
1084
|
+
|
|
650
1085
|
// Also clear sort model
|
|
651
1086
|
this.gridApi.setSortModel([]);
|
|
652
1087
|
}
|
|
@@ -655,10 +1090,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
655
1090
|
|
|
656
1091
|
sortColumnMenu(direction: 'asc' | 'desc' | null): void {
|
|
657
1092
|
if (!this.activeHeaderMenu) return;
|
|
658
|
-
|
|
1093
|
+
|
|
659
1094
|
const col = this.activeHeaderMenu as any;
|
|
660
1095
|
const colId = col.colId || col.field?.toString() || '';
|
|
661
|
-
|
|
1096
|
+
|
|
662
1097
|
// Update original ColDef to ensure persistence
|
|
663
1098
|
const colDef = this.getColumnDefForColumn(col);
|
|
664
1099
|
if (colDef) {
|
|
@@ -667,44 +1102,44 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
667
1102
|
|
|
668
1103
|
this.gridApi.setSortModel(direction ? [{ colId, sort: direction }] : []);
|
|
669
1104
|
this.canvasRenderer?.render();
|
|
670
|
-
|
|
1105
|
+
|
|
671
1106
|
this.closeHeaderMenu();
|
|
672
1107
|
}
|
|
673
1108
|
|
|
674
1109
|
hideColumnMenu(): void {
|
|
675
1110
|
if (!this.activeHeaderMenu) return;
|
|
676
|
-
|
|
1111
|
+
|
|
677
1112
|
const col = this.activeHeaderMenu as any;
|
|
678
|
-
|
|
1113
|
+
|
|
679
1114
|
// Update the original column definition
|
|
680
1115
|
const colDef = this.getColumnDefForColumn(col);
|
|
681
1116
|
if (colDef) {
|
|
682
1117
|
colDef.hide = true;
|
|
683
1118
|
}
|
|
684
|
-
|
|
1119
|
+
|
|
685
1120
|
// Create new array to trigger change detection and API update
|
|
686
1121
|
if (this.columnDefs) {
|
|
687
1122
|
this.onColumnDefsChanged([...this.columnDefs]);
|
|
688
1123
|
}
|
|
689
|
-
|
|
1124
|
+
|
|
690
1125
|
this.closeHeaderMenu();
|
|
691
1126
|
}
|
|
692
1127
|
|
|
693
1128
|
pinColumnMenu(pin: 'left' | 'right' | null): void {
|
|
694
1129
|
if (!this.activeHeaderMenu) return;
|
|
695
|
-
|
|
1130
|
+
|
|
696
1131
|
const col = this.activeHeaderMenu as any;
|
|
697
|
-
|
|
1132
|
+
|
|
698
1133
|
// Update the original column definition
|
|
699
1134
|
const colDef = this.getColumnDefForColumn(col);
|
|
700
1135
|
if (colDef) {
|
|
701
1136
|
colDef.pinned = pin as any;
|
|
702
1137
|
}
|
|
703
|
-
|
|
1138
|
+
|
|
704
1139
|
if (this.columnDefs) {
|
|
705
1140
|
this.onColumnDefsChanged([...this.columnDefs]);
|
|
706
1141
|
}
|
|
707
|
-
|
|
1142
|
+
|
|
708
1143
|
this.closeHeaderMenu();
|
|
709
1144
|
}
|
|
710
1145
|
|
|
@@ -718,8 +1153,8 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
718
1153
|
|
|
719
1154
|
const containerMap: { [key: string]: any[] } = {
|
|
720
1155
|
'left-pinned': left,
|
|
721
|
-
|
|
722
|
-
'right-pinned': right
|
|
1156
|
+
scrollable: center,
|
|
1157
|
+
'right-pinned': right,
|
|
723
1158
|
};
|
|
724
1159
|
|
|
725
1160
|
const previousContainerData = containerMap[event.previousContainer.id];
|
|
@@ -739,31 +1174,32 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
739
1174
|
const movedCol = currentContainerData[event.currentIndex] as Column;
|
|
740
1175
|
const colDef = this.getColumnDefForColumn(movedCol);
|
|
741
1176
|
if (colDef) {
|
|
742
|
-
colDef.pinned = pinType === 'none' ? null : pinType as any;
|
|
1177
|
+
colDef.pinned = pinType === 'none' ? null : (pinType as any);
|
|
743
1178
|
}
|
|
744
1179
|
}
|
|
745
1180
|
|
|
746
1181
|
// Map internal Columns back to their original ColDefs in the new order
|
|
747
1182
|
const orderedVisibleColDefs: (ColDef<TData> | ColGroupDef<TData>)[] = [];
|
|
748
|
-
[...left, ...center, ...right].forEach(col => {
|
|
1183
|
+
[...left, ...center, ...right].forEach((col) => {
|
|
749
1184
|
const def = this.getColumnDefForColumn(col);
|
|
750
1185
|
if (def) orderedVisibleColDefs.push(def);
|
|
751
1186
|
});
|
|
752
1187
|
|
|
753
1188
|
// Reconstruct full columnDefs array, maintaining hidden columns
|
|
754
|
-
const hidden = this.columnDefs.filter(c => {
|
|
1189
|
+
const hidden = this.columnDefs.filter((c) => {
|
|
755
1190
|
if ('children' in c) return false;
|
|
756
1191
|
return (c as ColDef).hide;
|
|
757
1192
|
});
|
|
758
|
-
|
|
1193
|
+
|
|
759
1194
|
const newDefs = [...orderedVisibleColDefs, ...hidden];
|
|
760
|
-
|
|
1195
|
+
|
|
761
1196
|
this.onColumnDefsChanged(newDefs);
|
|
762
1197
|
}
|
|
763
1198
|
|
|
764
1199
|
// --- Column Resizing Logic ---
|
|
765
1200
|
|
|
766
1201
|
isResizable(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
1202
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') return true;
|
|
767
1203
|
if ('children' in col) return false;
|
|
768
1204
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
769
1205
|
return colDef ? colDef.resizable !== false : true;
|
|
@@ -794,10 +1230,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
794
1230
|
|
|
795
1231
|
const deltaX = event.clientX - this.resizeStartX;
|
|
796
1232
|
const newWidth = Math.max(20, this.resizeStartWidth + deltaX);
|
|
797
|
-
|
|
1233
|
+
|
|
798
1234
|
// Update internal column width
|
|
799
1235
|
this.resizeColumn.width = newWidth;
|
|
800
|
-
|
|
1236
|
+
|
|
801
1237
|
// Update original ColDef
|
|
802
1238
|
const colDef = this.getColumnDefForColumn(this.resizeColumn);
|
|
803
1239
|
if (colDef) {
|
|
@@ -806,7 +1242,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
806
1242
|
|
|
807
1243
|
// Force re-render
|
|
808
1244
|
this.canvasRenderer?.render();
|
|
809
|
-
this.
|
|
1245
|
+
this._cdr.detectChanges();
|
|
810
1246
|
}
|
|
811
1247
|
|
|
812
1248
|
private onResizeMouseUp(): void {
|
|
@@ -818,24 +1254,28 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
818
1254
|
|
|
819
1255
|
hasFloatingFilters(): boolean {
|
|
820
1256
|
if (this.gridApi?.getGridOption('floatingFilter')) return true;
|
|
821
|
-
|
|
1257
|
+
if (this.gridOptions?.defaultColDef?.floatingFilter) return true;
|
|
1258
|
+
|
|
822
1259
|
if (!this.columnDefs) return false;
|
|
823
|
-
|
|
1260
|
+
const hasAny = this.columnDefs.some((col) => {
|
|
824
1261
|
if ('children' in col) {
|
|
825
|
-
return col.children.some(child =>
|
|
1262
|
+
return col.children.some((child) => (child as any).floatingFilter);
|
|
826
1263
|
}
|
|
827
|
-
return col.floatingFilter;
|
|
1264
|
+
return (col as any).floatingFilter;
|
|
828
1265
|
});
|
|
1266
|
+
return hasAny;
|
|
829
1267
|
}
|
|
830
1268
|
|
|
831
1269
|
isFloatingFilterEnabled(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
832
1270
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
833
1271
|
if (!colDef || 'children' in colDef) return false;
|
|
834
|
-
|
|
835
|
-
|
|
1272
|
+
|
|
1273
|
+
const filter = colDef.filter;
|
|
1274
|
+
if (!filter) return false;
|
|
1275
|
+
|
|
836
1276
|
if (colDef.floatingFilter === true) return true;
|
|
837
1277
|
if (colDef.floatingFilter === false) return false;
|
|
838
|
-
|
|
1278
|
+
|
|
839
1279
|
return !!this.gridApi?.getGridOption('floatingFilter');
|
|
840
1280
|
}
|
|
841
1281
|
|
|
@@ -858,25 +1298,27 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
858
1298
|
onFloatingFilterInput(event: Event, col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
859
1299
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
860
1300
|
if (!colDef || 'children' in colDef) return;
|
|
861
|
-
|
|
1301
|
+
|
|
862
1302
|
const input = event.target as HTMLInputElement;
|
|
863
1303
|
const value = input.value;
|
|
864
1304
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
865
1305
|
|
|
866
|
-
this.
|
|
1306
|
+
this._cdr.detectChanges(); // Update clear button visibility immediately
|
|
867
1307
|
|
|
868
1308
|
clearTimeout(this.filterTimeout);
|
|
869
1309
|
this.filterTimeout = setTimeout(() => {
|
|
870
1310
|
const currentModel = this.gridApi.getFilterModel();
|
|
871
|
-
|
|
872
|
-
|
|
1311
|
+
const existingFilter = (currentModel[colId] || {}) as any;
|
|
1312
|
+
|
|
1313
|
+
if (!value && existingFilter.type !== 'blank' && existingFilter.type !== 'notBlank') {
|
|
873
1314
|
delete currentModel[colId];
|
|
874
1315
|
} else {
|
|
875
1316
|
const filterType = this.getFilterTypeFromCol(colDef);
|
|
876
1317
|
currentModel[colId] = {
|
|
1318
|
+
...existingFilter,
|
|
877
1319
|
filterType: filterType as any,
|
|
878
|
-
type: filterType === 'text' ? 'contains' : 'equals',
|
|
879
|
-
filter: value
|
|
1320
|
+
type: existingFilter.type || (filterType === 'text' ? 'contains' : 'equals'),
|
|
1321
|
+
filter: value,
|
|
880
1322
|
};
|
|
881
1323
|
}
|
|
882
1324
|
|
|
@@ -900,25 +1342,31 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
900
1342
|
return model[colId]?.filter || '';
|
|
901
1343
|
}
|
|
902
1344
|
|
|
903
|
-
hasFilterValue(
|
|
1345
|
+
hasFilterValue(
|
|
1346
|
+
col: Column | ColDef<TData> | ColGroupDef<TData>,
|
|
1347
|
+
_input: HTMLInputElement
|
|
1348
|
+
): boolean {
|
|
904
1349
|
return !!this.getFloatingFilterValue(col);
|
|
905
1350
|
}
|
|
906
1351
|
|
|
907
|
-
clearFloatingFilter(
|
|
1352
|
+
clearFloatingFilter(
|
|
1353
|
+
col: Column | ColDef<TData> | ColGroupDef<TData>,
|
|
1354
|
+
input: HTMLInputElement
|
|
1355
|
+
): void {
|
|
908
1356
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
909
1357
|
if (!colDef || 'children' in colDef) return;
|
|
910
|
-
|
|
1358
|
+
|
|
911
1359
|
input.value = '';
|
|
912
1360
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
913
|
-
|
|
1361
|
+
|
|
914
1362
|
const currentModel = this.gridApi.getFilterModel();
|
|
915
1363
|
delete currentModel[colId];
|
|
916
|
-
|
|
1364
|
+
|
|
917
1365
|
this.gridApi.setFilterModel(currentModel);
|
|
918
1366
|
this.canvasRenderer?.render();
|
|
919
|
-
this.
|
|
1367
|
+
this._cdr.detectChanges();
|
|
920
1368
|
}
|
|
921
|
-
|
|
1369
|
+
|
|
922
1370
|
// Public API methods
|
|
923
1371
|
getApi(): GridApi<TData> {
|
|
924
1372
|
return this.gridApi;
|
|
@@ -936,42 +1384,42 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
936
1384
|
startEditing(rowIndex: number, colId: string): void {
|
|
937
1385
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(rowIndex);
|
|
938
1386
|
const column = this.gridApi.getColumn(colId);
|
|
939
|
-
|
|
1387
|
+
|
|
940
1388
|
// Prevent editing on group rows or missing data/column
|
|
941
1389
|
if (!rowNode || rowNode.group || !column || !column.field) return;
|
|
942
|
-
|
|
1390
|
+
|
|
943
1391
|
// Check if cell is editable
|
|
944
1392
|
const colDef = this.getColumnDefForColumn(column);
|
|
945
1393
|
if (colDef && colDef.editable === false) return;
|
|
946
|
-
|
|
1394
|
+
|
|
947
1395
|
// If already editing another cell, stop it first
|
|
948
1396
|
if (this.isEditing) {
|
|
949
1397
|
this.stopEditing(true);
|
|
950
1398
|
}
|
|
951
1399
|
|
|
952
1400
|
const value = (rowNode.data as any)[column.field];
|
|
953
|
-
|
|
1401
|
+
|
|
954
1402
|
this.editingRowNode = rowNode;
|
|
955
1403
|
this.editingColDef = colDef;
|
|
956
1404
|
this.editingValue = value !== null && value !== undefined ? String(value) : '';
|
|
957
|
-
|
|
1405
|
+
|
|
958
1406
|
// Calculate editor position based on row and column
|
|
959
|
-
const columns = this.gridApi.getAllColumns().filter(c => c.visible);
|
|
1407
|
+
const columns = this.gridApi.getAllColumns().filter((c) => c.visible);
|
|
960
1408
|
let x = 0;
|
|
961
1409
|
for (const col of columns) {
|
|
962
1410
|
if (col.colId === colId) break;
|
|
963
1411
|
x += col.width;
|
|
964
1412
|
}
|
|
965
|
-
|
|
1413
|
+
|
|
966
1414
|
this.editorPosition = {
|
|
967
1415
|
x: x - this.canvasRenderer.currentScrollLeft,
|
|
968
|
-
y:
|
|
1416
|
+
y: rowIndex * this.rowHeight - this.canvasRenderer.currentScrollTop,
|
|
969
1417
|
width: column.width,
|
|
970
|
-
height: this.rowHeight
|
|
1418
|
+
height: this.rowHeight,
|
|
971
1419
|
};
|
|
972
|
-
|
|
1420
|
+
|
|
973
1421
|
this.isEditing = true;
|
|
974
|
-
|
|
1422
|
+
|
|
975
1423
|
// Focus input after view update
|
|
976
1424
|
setTimeout(() => {
|
|
977
1425
|
if (this.editorInputRef) {
|
|
@@ -984,7 +1432,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
984
1432
|
|
|
985
1433
|
stopEditing(save: boolean = true): void {
|
|
986
1434
|
if (!this.isEditing) return;
|
|
987
|
-
|
|
1435
|
+
|
|
988
1436
|
const rowNode = this.editingRowNode;
|
|
989
1437
|
const colDef = this.editingColDef;
|
|
990
1438
|
|
|
@@ -1007,7 +1455,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1007
1455
|
data: rowNode.data,
|
|
1008
1456
|
node: rowNode,
|
|
1009
1457
|
colDef,
|
|
1010
|
-
api: this.gridApi
|
|
1458
|
+
api: this.gridApi,
|
|
1011
1459
|
});
|
|
1012
1460
|
}
|
|
1013
1461
|
|
|
@@ -1019,7 +1467,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1019
1467
|
data: rowNode.data,
|
|
1020
1468
|
node: rowNode,
|
|
1021
1469
|
colDef,
|
|
1022
|
-
api: this.gridApi
|
|
1470
|
+
api: this.gridApi,
|
|
1023
1471
|
});
|
|
1024
1472
|
} else if (field) {
|
|
1025
1473
|
// Default: update data directly
|
|
@@ -1028,9 +1476,9 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1028
1476
|
|
|
1029
1477
|
// Update via transaction
|
|
1030
1478
|
this.gridApi.applyTransaction({
|
|
1031
|
-
update: [rowNode.data]
|
|
1479
|
+
update: [rowNode.data],
|
|
1032
1480
|
});
|
|
1033
|
-
|
|
1481
|
+
|
|
1034
1482
|
// Trigger callback
|
|
1035
1483
|
if (colDef.onCellValueChanged) {
|
|
1036
1484
|
const column = this.gridApi.getColumn(colDef.colId || field || '');
|
|
@@ -1040,18 +1488,18 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1040
1488
|
oldValue,
|
|
1041
1489
|
data: rowNode.data,
|
|
1042
1490
|
node: rowNode,
|
|
1043
|
-
column
|
|
1491
|
+
column,
|
|
1044
1492
|
});
|
|
1045
1493
|
}
|
|
1046
1494
|
}
|
|
1047
|
-
|
|
1495
|
+
|
|
1048
1496
|
this.canvasRenderer?.render();
|
|
1049
1497
|
}
|
|
1050
|
-
|
|
1498
|
+
|
|
1051
1499
|
this.isEditing = false;
|
|
1052
1500
|
this.editingRowNode = null;
|
|
1053
1501
|
this.editingColDef = null;
|
|
1054
|
-
this.
|
|
1502
|
+
this._cdr.detectChanges();
|
|
1055
1503
|
}
|
|
1056
1504
|
|
|
1057
1505
|
onEditorInput(event: Event): void {
|
|
@@ -1070,7 +1518,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1070
1518
|
event.preventDefault();
|
|
1071
1519
|
const currentRowIndex = this.editingRowNode?.displayedRowIndex ?? -1;
|
|
1072
1520
|
const currentColId = this.editingColDef?.colId || this.editingColDef?.field?.toString() || '';
|
|
1073
|
-
|
|
1521
|
+
|
|
1074
1522
|
this.stopEditing(true);
|
|
1075
1523
|
|
|
1076
1524
|
// Standard AG Grid Tab behavior: move to next cell
|
|
@@ -1081,9 +1529,9 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1081
1529
|
}
|
|
1082
1530
|
|
|
1083
1531
|
private moveToNextCell(rowIndex: number, colId: string, backwards: boolean): void {
|
|
1084
|
-
const columns = this.gridApi.getAllColumns().filter(c => c.visible);
|
|
1085
|
-
const colIndex = columns.findIndex(c => c.colId === colId);
|
|
1086
|
-
|
|
1532
|
+
const columns = this.gridApi.getAllColumns().filter((c) => c.visible);
|
|
1533
|
+
const colIndex = columns.findIndex((c) => c.colId === colId);
|
|
1534
|
+
|
|
1087
1535
|
if (colIndex === -1) return;
|
|
1088
1536
|
|
|
1089
1537
|
let nextColIndex = backwards ? colIndex - 1 : colIndex + 1;
|
|
@@ -1109,48 +1557,53 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1109
1557
|
this.stopEditing(true);
|
|
1110
1558
|
}
|
|
1111
1559
|
}
|
|
1112
|
-
private getColumnDefForColumn(
|
|
1560
|
+
private getColumnDefForColumn(
|
|
1561
|
+
column: Column | ColDef<TData> | ColGroupDef<TData>
|
|
1562
|
+
): ColDef<TData> | null {
|
|
1113
1563
|
if (!this.columnDefs) return null;
|
|
1114
|
-
|
|
1564
|
+
|
|
1115
1565
|
const colId = (column as any).colId || (column as any).field?.toString();
|
|
1116
1566
|
if (!colId) return null;
|
|
1117
1567
|
|
|
1568
|
+
const defaultColDef = this.gridOptions?.defaultColDef || {};
|
|
1569
|
+
|
|
1118
1570
|
for (const def of this.columnDefs) {
|
|
1119
1571
|
if ('children' in def) {
|
|
1120
|
-
const found = def.children.find(c => {
|
|
1572
|
+
const found = def.children.find((c) => {
|
|
1121
1573
|
const cDef = c as ColDef;
|
|
1122
1574
|
return cDef.colId === colId || cDef.field?.toString() === colId;
|
|
1123
1575
|
});
|
|
1124
|
-
if (found) return found as ColDef<TData>;
|
|
1576
|
+
if (found) return { ...defaultColDef, ...(found as ColDef<TData>) } as ColDef<TData>;
|
|
1125
1577
|
} else {
|
|
1126
1578
|
const cDef = def as ColDef;
|
|
1127
1579
|
if (cDef.colId === colId || cDef.field?.toString() === colId) {
|
|
1128
|
-
return
|
|
1580
|
+
return { ...defaultColDef, ...cDef } as ColDef<TData>;
|
|
1129
1581
|
}
|
|
1130
1582
|
}
|
|
1131
1583
|
}
|
|
1132
1584
|
return null;
|
|
1133
1585
|
}
|
|
1134
1586
|
|
|
1135
|
-
// Selection Methods
|
|
1136
1587
|
onRowClick(rowIndex: number, event: MouseEvent): void {
|
|
1137
1588
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(rowIndex);
|
|
1138
1589
|
if (!rowNode) return;
|
|
1139
1590
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
rowNode.selected
|
|
1591
|
+
const selectionMode = this.gridApi.getGridOption('rowSelection') || 'single';
|
|
1592
|
+
const isMultiSelect =
|
|
1593
|
+
(selectionMode as any) === 'multiple' || (selectionMode as any) === 'multiRow';
|
|
1594
|
+
|
|
1595
|
+
if (isMultiSelect && (event.ctrlKey || event.metaKey)) {
|
|
1596
|
+
rowNode.setSelected(!rowNode.selected);
|
|
1597
|
+
} else if (isMultiSelect && event.shiftKey) {
|
|
1598
|
+
rowNode.setSelected(true);
|
|
1146
1599
|
} else {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1600
|
+
if (rowNode.selected) {
|
|
1601
|
+
rowNode.setSelected(false);
|
|
1602
|
+
} else {
|
|
1603
|
+
rowNode.setSelected(true, true);
|
|
1604
|
+
}
|
|
1150
1605
|
}
|
|
1151
1606
|
|
|
1152
|
-
this.updateSelectionState();
|
|
1153
|
-
this.canvasRenderer?.render();
|
|
1154
1607
|
this.selectionChanged.emit(this.gridApi.getSelectedRows());
|
|
1155
1608
|
}
|
|
1156
1609
|
|
|
@@ -1181,7 +1634,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1181
1634
|
updateSelectionState(): void {
|
|
1182
1635
|
const selectedCount = this.gridApi.getSelectedRows().length;
|
|
1183
1636
|
const totalCount = this.gridApi.getDisplayedRowCount();
|
|
1184
|
-
|
|
1637
|
+
|
|
1185
1638
|
this.isAllSelected = selectedCount === totalCount && totalCount > 0;
|
|
1186
1639
|
this.isIndeterminateSelection = selectedCount > 0 && selectedCount < totalCount;
|
|
1187
1640
|
}
|