argent-grid 0.1.0 → 0.3.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 +70 -27
- 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/cell-renderers.spec.ts +152 -0
- package/e2e/debug-streaming.spec.ts +31 -0
- package/e2e/dnd.spec.ts +73 -0
- package/e2e/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +112 -0
- package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
- package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -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/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
- package/package.json +21 -7
- package/plan.md +56 -28
- 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 +281 -321
- package/src/lib/components/argent-grid.component.html +295 -207
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +1193 -290
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +307 -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 +513 -0
- package/src/lib/rendering/canvas-renderer.ts +456 -452
- 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 +167 -28
- package/src/lib/rendering/render/column-utils.ts +95 -0
- package/src/lib/rendering/render/hit-test.ts +50 -0
- package/src/lib/rendering/render/index.ts +88 -76
- package/src/lib/rendering/render/lines.ts +53 -47
- package/src/lib/rendering/render/primitives.ts +423 -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 +3 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +94 -64
- 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 +1241 -201
- package/src/lib/services/grid.service.ts +1204 -235
- 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 +573 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +249 -0
- package/src/stories/ArgentGrid.stories.ts +301 -0
- package/src/stories/Benchmark.stories.ts +76 -0
- package/src/stories/CellRenderers.stories.ts +395 -0
- package/src/stories/Filtering.stories.ts +292 -0
- package/src/stories/Grouping.stories.ts +290 -0
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +137 -0
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +355 -0
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- package/tsconfig.json +1 -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,59 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
|
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 { isColumnVisible } from '../rendering/render/column-utils';
|
|
22
|
+
import { GridService } from '../services/grid.service';
|
|
23
|
+
import { applyThemeCSSVariables, convertThemeToGridTheme } from '../themes/theme-builder';
|
|
24
|
+
import {
|
|
25
|
+
CellRange,
|
|
26
|
+
ColDef,
|
|
27
|
+
ColGroupDef,
|
|
28
|
+
Column,
|
|
29
|
+
ColumnGroup,
|
|
30
|
+
DefaultMenuItem,
|
|
31
|
+
GetContextMenuItemsParams,
|
|
32
|
+
GridApi,
|
|
33
|
+
GridOptions,
|
|
34
|
+
IRowNode,
|
|
35
|
+
MenuItemDef,
|
|
36
|
+
RowSelectionOptions,
|
|
37
|
+
} from '../types/ag-grid-types';
|
|
8
38
|
|
|
9
39
|
@Component({
|
|
10
40
|
selector: 'argent-grid',
|
|
11
41
|
templateUrl: './argent-grid.component.html',
|
|
12
42
|
styleUrls: ['./argent-grid.component.css'],
|
|
13
|
-
changeDetection: ChangeDetectionStrategy.OnPush
|
|
43
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14
44
|
})
|
|
15
|
-
export class ArgentGridComponent<TData = any>
|
|
45
|
+
export class ArgentGridComponent<TData = any>
|
|
46
|
+
implements OnInit, OnDestroy, AfterViewInit, OnChanges
|
|
47
|
+
{
|
|
16
48
|
@Input() columnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null = null;
|
|
17
49
|
@Input() rowData: TData[] | null = null;
|
|
18
50
|
@Input() gridOptions: GridOptions<TData> | null = null;
|
|
51
|
+
@Input() theme: any;
|
|
19
52
|
@Input() height = '500px';
|
|
20
53
|
@Input() width = '100%';
|
|
21
|
-
@Input() rowHeight
|
|
22
|
-
|
|
54
|
+
@Input() rowHeight?: number;
|
|
55
|
+
@Input() rowSelection: RowSelectionOptions | 'single' | 'multiple' | undefined;
|
|
56
|
+
|
|
23
57
|
@Output() gridReady = new EventEmitter<GridApi<TData>>();
|
|
24
58
|
@Output() rowClicked = new EventEmitter<{ data: TData; node: IRowNode<TData> }>();
|
|
25
59
|
@Output() selectionChanged = new EventEmitter<IRowNode<TData>[]>();
|
|
@@ -34,16 +68,31 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
34
68
|
showOverlay = false;
|
|
35
69
|
private viewportHeight = 500;
|
|
36
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Returns the current effective row height, prioritizing grid options, then the input property, and defaulting to 32.
|
|
73
|
+
*/
|
|
74
|
+
get effectiveRowHeight(): number {
|
|
75
|
+
return this.gridApi?.getGridOption('rowHeight') || this.rowHeight || 32;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns the current effective header height, prioritizing grid options, then defaulting to effectiveRowHeight.
|
|
80
|
+
*/
|
|
81
|
+
get effectiveHeaderHeight(): number {
|
|
82
|
+
return this.gridApi?.getGridOption('headerHeight') || this.effectiveRowHeight;
|
|
83
|
+
}
|
|
84
|
+
|
|
37
85
|
get totalHeight(): number {
|
|
38
86
|
if (this.gridApi) return this.gridApi.getTotalHeight();
|
|
39
|
-
return (this.rowData?.length || 0) * this.rowHeight;
|
|
87
|
+
return (this.rowData?.length || 0) * (this.rowHeight || 32);
|
|
40
88
|
}
|
|
41
89
|
|
|
42
90
|
get totalWidth(): number {
|
|
43
91
|
if (!this.gridApi) return 0;
|
|
44
|
-
return this.gridApi
|
|
45
|
-
.
|
|
46
|
-
.
|
|
92
|
+
return this.gridApi
|
|
93
|
+
.getAllColumns()
|
|
94
|
+
.filter((col) => isColumnVisible(col))
|
|
95
|
+
.reduce((sum, col) => sum + Math.floor(col.width || 150), 0);
|
|
47
96
|
}
|
|
48
97
|
|
|
49
98
|
// Selection state
|
|
@@ -52,6 +101,14 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
52
101
|
isAllSelected = false;
|
|
53
102
|
isIndeterminateSelection = false;
|
|
54
103
|
|
|
104
|
+
hasCheckboxSelection(col: Column): boolean {
|
|
105
|
+
return col.colId === 'ag-Grid-SelectionColumn';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
hasHeaderCheckbox(col: Column): boolean {
|
|
109
|
+
return !!col.headerCheckboxSelection;
|
|
110
|
+
}
|
|
111
|
+
|
|
55
112
|
trackByColumn(index: number, col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
56
113
|
return (col as any).colId || (col as any).field?.toString() || index.toString();
|
|
57
114
|
}
|
|
@@ -70,32 +127,57 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
70
127
|
// Resizing state
|
|
71
128
|
isResizing = false;
|
|
72
129
|
resizeColumn: Column | null = null;
|
|
130
|
+
resizeItem: Column | ColumnGroup | null = null;
|
|
73
131
|
private resizeStartX = 0;
|
|
74
132
|
private resizeStartWidth = 0;
|
|
75
133
|
|
|
76
134
|
// Range Selection state
|
|
77
135
|
isRangeSelecting = false;
|
|
78
|
-
private rangeStartCell: { rowIndex: number
|
|
136
|
+
private rangeStartCell: { rowIndex: number; colId: string } | null = null;
|
|
79
137
|
|
|
80
138
|
// Side Bar state
|
|
81
139
|
sideBarVisible = false;
|
|
82
140
|
activeToolPanel: 'columns' | 'filters' | null = null;
|
|
83
141
|
|
|
142
|
+
// Row Group Panel state
|
|
143
|
+
rowGroupPanelShow: 'always' | 'onlyWhenGrouping' | 'never' = 'never';
|
|
144
|
+
rowGroupColumns: Column[] = [];
|
|
145
|
+
|
|
84
146
|
// Context Menu state
|
|
85
147
|
activeContextMenu = false;
|
|
86
148
|
contextMenuPosition = { x: 0, y: 0 };
|
|
87
149
|
contextMenuItems: MenuItemDef[] = [];
|
|
88
|
-
private contextMenuCell: { rowNode: IRowNode<TData
|
|
150
|
+
private contextMenuCell: { rowNode: IRowNode<TData>; column: Column } | null = null;
|
|
151
|
+
|
|
152
|
+
// Tooltip state
|
|
153
|
+
tooltipVisible = false;
|
|
154
|
+
tooltipText = '';
|
|
155
|
+
tooltipPosition = { x: 0, y: 0 };
|
|
156
|
+
private _tooltipTimer: any = null;
|
|
157
|
+
|
|
158
|
+
// Set Filter
|
|
159
|
+
activeSetFilter = false;
|
|
160
|
+
setFilterPosition = { x: 0, y: 0 };
|
|
161
|
+
setFilterValues: any[] = [];
|
|
162
|
+
setFilterSelectedValues: any[] | null = null;
|
|
163
|
+
setFilterValueFormatter?: (value: any) => string;
|
|
164
|
+
private activeSetFilterColumn: Column | null = null;
|
|
89
165
|
private initialColumnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null = null;
|
|
90
166
|
|
|
91
|
-
|
|
167
|
+
public gridApi!: GridApi<TData>;
|
|
168
|
+
public isColumnVisible = isColumnVisible;
|
|
169
|
+
public Math = Math;
|
|
170
|
+
public scrollbarWidth = 0;
|
|
92
171
|
private canvasRenderer!: CanvasRenderer;
|
|
93
172
|
private destroy$ = new Subject<void>();
|
|
94
173
|
private gridService = new GridService<TData>();
|
|
95
174
|
private horizontalScrollListener?: (e: Event) => void;
|
|
96
175
|
private resizeObserver?: ResizeObserver;
|
|
97
176
|
|
|
98
|
-
constructor(
|
|
177
|
+
constructor(
|
|
178
|
+
@Inject(ChangeDetectorRef) private _cdr: ChangeDetectorRef,
|
|
179
|
+
private _elementRef: ElementRef<HTMLElement>
|
|
180
|
+
) {}
|
|
99
181
|
|
|
100
182
|
ngOnInit(): void {
|
|
101
183
|
this.initialColumnDefs = this.columnDefs ? JSON.parse(JSON.stringify(this.columnDefs)) : null;
|
|
@@ -104,30 +186,71 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
104
186
|
|
|
105
187
|
ngOnChanges(changes: SimpleChanges): void {
|
|
106
188
|
// Handle rowData changes after initialization
|
|
107
|
-
if (changes
|
|
108
|
-
this.onRowDataChanged(changes
|
|
189
|
+
if (changes.rowData && !changes.rowData.firstChange) {
|
|
190
|
+
this.onRowDataChanged(changes.rowData.currentValue);
|
|
109
191
|
}
|
|
110
192
|
|
|
111
193
|
// Handle columnDefs changes
|
|
112
|
-
if (changes
|
|
113
|
-
this.onColumnDefsChanged(changes
|
|
194
|
+
if (changes.columnDefs && !changes.columnDefs.firstChange) {
|
|
195
|
+
this.onColumnDefsChanged(changes.columnDefs.currentValue);
|
|
114
196
|
}
|
|
115
197
|
|
|
116
198
|
// Handle gridOptions changes
|
|
117
|
-
if (changes
|
|
118
|
-
this.onGridOptionsChanged(changes
|
|
199
|
+
if (changes.gridOptions && !changes.gridOptions.firstChange) {
|
|
200
|
+
this.onGridOptionsChanged(changes.gridOptions.currentValue);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Handle rowSelection changes
|
|
204
|
+
if (changes.rowSelection && !changes.rowSelection.firstChange) {
|
|
205
|
+
if (this.gridApi) {
|
|
206
|
+
this.gridApi.setGridOption('rowSelection', changes.rowSelection.currentValue);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle theme changes
|
|
211
|
+
if (changes.theme && !changes.theme.firstChange) {
|
|
212
|
+
// Apply theme CSS variables to the grid container
|
|
213
|
+
if (changes.theme.currentValue) {
|
|
214
|
+
applyThemeCSSVariables(changes.theme.currentValue, this._elementRef.nativeElement);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update canvas renderer theme if it's initialized
|
|
218
|
+
if (this.canvasRenderer) {
|
|
219
|
+
const convertedTheme = changes.theme.currentValue
|
|
220
|
+
? convertThemeToGridTheme(changes.theme.currentValue)
|
|
221
|
+
: undefined;
|
|
222
|
+
this.canvasRenderer.setTheme(convertedTheme);
|
|
223
|
+
|
|
224
|
+
// Sync rowHeight and headerHeight to GridApi if provided by theme and NOT explicitly overridden by user
|
|
225
|
+
if (this.gridApi) {
|
|
226
|
+
if (convertedTheme?.rowHeight && this.rowHeight === undefined) {
|
|
227
|
+
this.gridApi.setGridOption('rowHeight', convertedTheme.rowHeight);
|
|
228
|
+
}
|
|
229
|
+
if (convertedTheme?.headerHeight) {
|
|
230
|
+
this.gridApi.setGridOption('headerHeight', convertedTheme.headerHeight);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
119
234
|
}
|
|
120
235
|
}
|
|
121
236
|
|
|
122
237
|
ngAfterViewInit(): void {
|
|
123
238
|
// Setup canvas renderer after view is initialized
|
|
124
239
|
if (this.canvasRef && !this.canvasRenderer) {
|
|
240
|
+
// Convert theme from ThemeBuilder format to internal GridTheme format
|
|
241
|
+
const convertedTheme = this.theme ? convertThemeToGridTheme(this.theme) : undefined;
|
|
242
|
+
|
|
243
|
+
// Apply theme CSS variables to the grid container
|
|
244
|
+
if (this.theme) {
|
|
245
|
+
applyThemeCSSVariables(this.theme, this._elementRef.nativeElement);
|
|
246
|
+
}
|
|
247
|
+
|
|
125
248
|
this.canvasRenderer = new CanvasRenderer(
|
|
126
249
|
this.canvasRef.nativeElement,
|
|
127
250
|
this.gridApi,
|
|
128
|
-
this.
|
|
251
|
+
this.effectiveRowHeight,
|
|
252
|
+
convertedTheme
|
|
129
253
|
);
|
|
130
|
-
|
|
131
254
|
// Wire up cell editing callback
|
|
132
255
|
this.canvasRenderer.onCellDoubleClick = (rowIndex, colId) => {
|
|
133
256
|
this.startEditing(rowIndex, colId);
|
|
@@ -141,28 +264,28 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
141
264
|
// Range Selection Logic
|
|
142
265
|
this.canvasRenderer.onMouseDown = (event, rowIndex, colId) => {
|
|
143
266
|
if (event.button !== 0 || !colId || rowIndex === -1) return;
|
|
144
|
-
|
|
267
|
+
|
|
145
268
|
const rangeSelectionEnabled = this.gridApi?.getGridOption('enableRangeSelection');
|
|
146
269
|
if (!rangeSelectionEnabled) return;
|
|
147
270
|
|
|
148
271
|
this.isRangeSelecting = true;
|
|
149
272
|
this.rangeStartCell = { rowIndex, colId };
|
|
150
|
-
|
|
273
|
+
|
|
151
274
|
// Clear previous selection if not holding Shift/Ctrl
|
|
152
275
|
if (!event.shiftKey && !event.ctrlKey && !event.metaKey) {
|
|
153
276
|
this.gridApi?.clearRangeSelection();
|
|
154
277
|
}
|
|
155
278
|
};
|
|
156
279
|
|
|
157
|
-
this.canvasRenderer.onMouseMove = (
|
|
280
|
+
this.canvasRenderer.onMouseMove = (_event, rowIndex, colId) => {
|
|
158
281
|
if (!this.isRangeSelecting || !this.rangeStartCell || !colId || rowIndex === -1) return;
|
|
159
282
|
|
|
160
283
|
const start = this.rangeStartCell;
|
|
161
284
|
const end = { rowIndex, colId };
|
|
162
285
|
|
|
163
286
|
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);
|
|
287
|
+
const startColIdx = columns.findIndex((c) => c.colId === start.colId);
|
|
288
|
+
const endColIdx = columns.findIndex((c) => c.colId === end.colId);
|
|
166
289
|
|
|
167
290
|
if (startColIdx === -1 || endColIdx === -1) return;
|
|
168
291
|
|
|
@@ -171,7 +294,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
171
294
|
endRow: Math.max(start.rowIndex, end.rowIndex),
|
|
172
295
|
startColumn: columns[Math.min(startColIdx, endColIdx)].colId,
|
|
173
296
|
endColumn: columns[Math.max(startColIdx, endColIdx)].colId,
|
|
174
|
-
columns: columns.slice(
|
|
297
|
+
columns: columns.slice(
|
|
298
|
+
Math.min(startColIdx, endColIdx),
|
|
299
|
+
Math.max(startColIdx, endColIdx) + 1
|
|
300
|
+
),
|
|
175
301
|
};
|
|
176
302
|
|
|
177
303
|
this.gridApi?.addCellRange(range);
|
|
@@ -186,34 +312,72 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
186
312
|
if (this.viewportRef) {
|
|
187
313
|
const rect = this.viewportRef.nativeElement.getBoundingClientRect();
|
|
188
314
|
this.viewportHeight = rect.height || 500;
|
|
189
|
-
this.canvasRenderer?.setViewportDimensions(
|
|
190
|
-
|
|
315
|
+
this.canvasRenderer?.setViewportDimensions(
|
|
316
|
+
rect.width,
|
|
317
|
+
this.viewportHeight,
|
|
318
|
+
this.scrollbarWidth
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const updateScrollbar = () => {
|
|
322
|
+
const viewport = this.viewportRef?.nativeElement;
|
|
323
|
+
if (!viewport) return;
|
|
324
|
+
const newWidth = viewport.offsetWidth - viewport.clientWidth;
|
|
325
|
+
if (this.scrollbarWidth !== newWidth) {
|
|
326
|
+
this.scrollbarWidth = newWidth;
|
|
327
|
+
this._cdr.detectChanges();
|
|
328
|
+
}
|
|
329
|
+
};
|
|
191
330
|
|
|
192
331
|
// Synchronize horizontal scroll with DOM header
|
|
193
332
|
this.horizontalScrollListener = () => {
|
|
333
|
+
const viewport = this.viewportRef?.nativeElement;
|
|
334
|
+
if (!viewport) return;
|
|
335
|
+
|
|
336
|
+
updateScrollbar();
|
|
337
|
+
|
|
194
338
|
if (this.headerScrollableRef) {
|
|
195
|
-
this.headerScrollableRef.nativeElement.scrollLeft =
|
|
339
|
+
this.headerScrollableRef.nativeElement.scrollLeft = viewport.scrollLeft;
|
|
196
340
|
}
|
|
197
341
|
if (this.headerScrollableFilterRef) {
|
|
198
|
-
this.headerScrollableFilterRef.nativeElement.scrollLeft =
|
|
342
|
+
this.headerScrollableFilterRef.nativeElement.scrollLeft = viewport.scrollLeft;
|
|
199
343
|
}
|
|
200
344
|
};
|
|
201
|
-
|
|
202
|
-
this.viewportRef.nativeElement.addEventListener('scroll', this.horizontalScrollListener, {
|
|
345
|
+
|
|
346
|
+
this.viewportRef.nativeElement.addEventListener('scroll', this.horizontalScrollListener, {
|
|
347
|
+
passive: true,
|
|
348
|
+
});
|
|
203
349
|
|
|
204
350
|
// Add ResizeObserver to handle sidebar toggling and other size changes
|
|
205
351
|
if (typeof ResizeObserver !== 'undefined') {
|
|
206
|
-
|
|
352
|
+
let lastWidth = 0;
|
|
353
|
+
let lastHeight = 0;
|
|
354
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
207
355
|
for (const entry of entries) {
|
|
208
356
|
const { width, height } = entry.contentRect;
|
|
357
|
+
if (Math.abs(width - lastWidth) < 1 && Math.abs(height - lastHeight) < 1) continue;
|
|
358
|
+
|
|
359
|
+
lastWidth = width;
|
|
360
|
+
lastHeight = height;
|
|
209
361
|
this.viewportHeight = height;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
362
|
+
|
|
363
|
+
// Only update scrollbar if dimensions actually changed
|
|
364
|
+
updateScrollbar();
|
|
365
|
+
|
|
366
|
+
this.canvasRenderer?.setViewportDimensions(width, height, this.scrollbarWidth);
|
|
367
|
+
// setViewportDimensions → updateCanvasSize already schedules a render.
|
|
368
|
+
this._cdr.detectChanges();
|
|
213
369
|
}
|
|
214
370
|
});
|
|
215
371
|
this.resizeObserver.observe(this.viewportRef.nativeElement);
|
|
216
372
|
}
|
|
373
|
+
|
|
374
|
+
// Initial calculation
|
|
375
|
+
setTimeout(() => {
|
|
376
|
+
updateScrollbar();
|
|
377
|
+
if (this.canvasRenderer) {
|
|
378
|
+
this.canvasRenderer.render();
|
|
379
|
+
}
|
|
380
|
+
});
|
|
217
381
|
}
|
|
218
382
|
}
|
|
219
383
|
|
|
@@ -232,27 +396,78 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
232
396
|
|
|
233
397
|
this.gridApi?.destroy();
|
|
234
398
|
this.canvasRenderer?.destroy();
|
|
399
|
+
this.onCanvasMouseLeave();
|
|
235
400
|
}
|
|
236
401
|
|
|
237
402
|
private initializeGrid(): void {
|
|
403
|
+
// Merge individual inputs into grid options if provided
|
|
404
|
+
const options = { ...this.gridOptions };
|
|
405
|
+
if (this.rowSelection) {
|
|
406
|
+
options.rowSelection = this.rowSelection;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Prioritize explicit rowHeight input if provided
|
|
410
|
+
if (this.rowHeight !== undefined) {
|
|
411
|
+
options.rowHeight = this.rowHeight;
|
|
412
|
+
} else if (this.theme) {
|
|
413
|
+
// If no explicit rowHeight, but theme is provided, use theme's rowHeight
|
|
414
|
+
const convertedTheme = convertThemeToGridTheme(this.theme);
|
|
415
|
+
if (convertedTheme.rowHeight) {
|
|
416
|
+
options.rowHeight = convertedTheme.rowHeight;
|
|
417
|
+
}
|
|
418
|
+
if (convertedTheme.headerHeight && options.headerHeight === undefined) {
|
|
419
|
+
options.headerHeight = convertedTheme.headerHeight;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
238
423
|
// Initialize grid API
|
|
239
|
-
this.gridApi = this.gridService.createApi(this.columnDefs, this.rowData,
|
|
424
|
+
this.gridApi = this.gridService.createApi(this.columnDefs, this.rowData, options);
|
|
425
|
+
|
|
426
|
+
// Initial state sync
|
|
427
|
+
this.rowGroupPanelShow = this.gridApi.getGridOption('rowGroupPanelShow') || 'never';
|
|
428
|
+
this.updateRowGroupColumns();
|
|
240
429
|
|
|
241
430
|
// Listen for grid state changes from API (filters, sorts, options)
|
|
242
|
-
this.gridService.gridStateChanged$
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
431
|
+
this.gridService.gridStateChanged$.pipe(takeUntil(this.destroy$)).subscribe((event) => {
|
|
432
|
+
if (event.type === 'optionChanged' && event.key === 'sideBar') {
|
|
433
|
+
this.sideBarVisible = !!event.value;
|
|
434
|
+
}
|
|
435
|
+
if (event.type === 'optionChanged' && event.key === 'rowGroupPanelShow') {
|
|
436
|
+
this.rowGroupPanelShow = event.value || 'never';
|
|
437
|
+
}
|
|
438
|
+
if (event.type === 'selectionChanged') {
|
|
439
|
+
this.updateSelectionState();
|
|
440
|
+
|
|
441
|
+
// Mark all rows as potentially dirty for selection change to ensure canvas redraws
|
|
442
|
+
// In a more optimized version, we'd only mark specific rows.
|
|
443
|
+
if (this.canvasRenderer) {
|
|
444
|
+
this.canvasRenderer.render(); // This calls markAllDirty and schedules render
|
|
247
445
|
}
|
|
446
|
+
} else if (event.type === 'columnsChanged' || event.type === 'columnGroupExpanded') {
|
|
447
|
+
this.updateRowGroupColumns();
|
|
248
448
|
this.canvasRenderer?.render();
|
|
249
|
-
|
|
250
|
-
|
|
449
|
+
} else if (event.type === 'transactionApplied') {
|
|
450
|
+
// Efficient rendering: only mark changed rows as dirty instead of full redraw.
|
|
451
|
+
// Callers are responsible for throttling applyTransaction frequency (e.g. via RxJS).
|
|
452
|
+
const changedIndices = (event as any).changedRowIndices as number[] | undefined;
|
|
453
|
+
if (changedIndices && changedIndices.length > 0 && this.canvasRenderer) {
|
|
454
|
+
for (const rowIndex of changedIndices) {
|
|
455
|
+
this.canvasRenderer.invalidateRow(rowIndex);
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
this.canvasRenderer?.render();
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
// All other state changes (sort, filter, rangeSelection, etc.) go through the
|
|
462
|
+
// rAF-coalesced scheduler. Multiple rapid events (e.g. rangeSelectionChanged
|
|
463
|
+
// on every mousemove) collapse into at most one pending frame.
|
|
464
|
+
this.canvasRenderer?.render();
|
|
465
|
+
}
|
|
466
|
+
this._cdr.detectChanges();
|
|
467
|
+
});
|
|
251
468
|
|
|
252
|
-
//
|
|
253
|
-
this.showSelectionColumn =
|
|
254
|
-
!('children' in col) && col.checkboxSelection
|
|
255
|
-
) || false;
|
|
469
|
+
// Selection column is now handled within the data columns
|
|
470
|
+
this.showSelectionColumn = false;
|
|
256
471
|
|
|
257
472
|
// Canvas renderer will be initialized in ngAfterViewInit
|
|
258
473
|
|
|
@@ -277,15 +492,16 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
277
492
|
|
|
278
493
|
if (this.gridApi) {
|
|
279
494
|
this.gridApi.setRowData(newData || []);
|
|
280
|
-
this.canvasRenderer
|
|
281
|
-
|
|
495
|
+
if (this.canvasRenderer) {
|
|
496
|
+
this.canvasRenderer.render();
|
|
497
|
+
}
|
|
282
498
|
}
|
|
283
499
|
|
|
284
500
|
this.showOverlay = !newData || newData.length === 0;
|
|
285
501
|
this.updateSelectionState();
|
|
286
502
|
|
|
287
503
|
// Trigger change detection with OnPush
|
|
288
|
-
this.
|
|
504
|
+
this._cdr.detectChanges();
|
|
289
505
|
}
|
|
290
506
|
|
|
291
507
|
private onColumnDefsChanged(newColumnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null): void {
|
|
@@ -295,52 +511,239 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
295
511
|
this.gridApi.setColumnDefs(newColumnDefs);
|
|
296
512
|
this.canvasRenderer?.render();
|
|
297
513
|
}
|
|
298
|
-
|
|
299
|
-
this.
|
|
514
|
+
|
|
515
|
+
this._cdr.detectChanges();
|
|
300
516
|
}
|
|
301
517
|
|
|
302
518
|
private onGridOptionsChanged(newOptions: GridOptions<TData> | null): void {
|
|
303
519
|
this.gridOptions = newOptions;
|
|
304
520
|
if (this.gridApi && newOptions) {
|
|
305
521
|
// Update all options in the API
|
|
306
|
-
Object.keys(newOptions).forEach(key => {
|
|
522
|
+
Object.keys(newOptions).forEach((key) => {
|
|
307
523
|
this.gridApi.setGridOption(key as any, (newOptions as any)[key]);
|
|
308
524
|
});
|
|
525
|
+
|
|
526
|
+
if (newOptions.rowGroupPanelShow) {
|
|
527
|
+
this.rowGroupPanelShow = newOptions.rowGroupPanelShow;
|
|
528
|
+
}
|
|
529
|
+
|
|
309
530
|
this.canvasRenderer?.render();
|
|
310
531
|
}
|
|
311
|
-
this.
|
|
532
|
+
this._cdr.detectChanges();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
getHeaderRows(): (Column | ColumnGroup)[][] {
|
|
536
|
+
if (!this.gridApi) return [];
|
|
537
|
+
return this.gridApi.getHeaderRows();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
getPinnedLeftItems(row: (Column | ColumnGroup)[]): (Column | ColumnGroup)[] {
|
|
541
|
+
return row.filter((item) => item.pinned === 'left' && isColumnVisible(item));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
getPinnedRightItems(row: (Column | ColumnGroup)[]): (Column | ColumnGroup)[] {
|
|
545
|
+
return row.filter((item) => item.pinned === 'right' && isColumnVisible(item));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
getNonPinnedItems(row: (Column | ColumnGroup)[]): (Column | ColumnGroup)[] {
|
|
549
|
+
return row.filter((item) => !item.pinned && isColumnVisible(item));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
getItemWidth(item: Column | ColumnGroup): number {
|
|
553
|
+
if ('children' in item) {
|
|
554
|
+
return item.children.reduce((sum, child) => {
|
|
555
|
+
if (isColumnVisible(child)) {
|
|
556
|
+
return sum + this.getItemWidth(child);
|
|
557
|
+
}
|
|
558
|
+
return sum;
|
|
559
|
+
}, 0);
|
|
560
|
+
}
|
|
561
|
+
return Math.floor(item.width || 150);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
getItemRowSpan(item: Column | ColumnGroup, rowIndex: number): number {
|
|
565
|
+
if ('children' in item) {
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
// Leaf node spans until the bottom
|
|
569
|
+
const totalRows = this.gridApi.getHeaderDepth();
|
|
570
|
+
return totalRows - rowIndex;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
isColumnGroup(item: Column | ColumnGroup): item is ColumnGroup {
|
|
574
|
+
return 'children' in item;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
trackByHeaderItem(
|
|
578
|
+
index: number,
|
|
579
|
+
entry: { item: Column | ColumnGroup; rowIndex: number }
|
|
580
|
+
): string {
|
|
581
|
+
const item = entry.item;
|
|
582
|
+
return 'groupId' in item ? item.groupId : item.colId || index.toString();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
getItemColSpan(item: Column | ColumnGroup): number {
|
|
586
|
+
if ('children' in item) {
|
|
587
|
+
return item.children.reduce((sum, child) => {
|
|
588
|
+
return sum + this.getItemColSpan(child);
|
|
589
|
+
}, 0);
|
|
590
|
+
}
|
|
591
|
+
return 1;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
getScrollableHeaderWidth(): number {
|
|
595
|
+
return this.getNonPinnedColumns().reduce((sum, col) => sum + Math.floor(col.width || 150), 0);
|
|
312
596
|
}
|
|
313
|
-
|
|
597
|
+
|
|
598
|
+
getGridTemplateColumns(section: 'left' | 'right' | 'none'): string {
|
|
599
|
+
if (!this.gridApi) return '';
|
|
600
|
+
const allCols = this.gridApi.getAllColumns();
|
|
601
|
+
const sectionCols = allCols.filter((c) => {
|
|
602
|
+
if (section === 'left') return c.pinned === 'left';
|
|
603
|
+
if (section === 'right') return c.pinned === 'right';
|
|
604
|
+
return !c.pinned;
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (sectionCols.length === 0) return '';
|
|
608
|
+
|
|
609
|
+
const indices = sectionCols.map((c) => c.colIndex || 0);
|
|
610
|
+
const minIndex = Math.min(...indices);
|
|
611
|
+
const maxIndex = Math.max(...indices);
|
|
612
|
+
|
|
613
|
+
const widths = new Array(maxIndex - minIndex + 1).fill('0px');
|
|
614
|
+
sectionCols.forEach((c) => {
|
|
615
|
+
if (isColumnVisible(c)) {
|
|
616
|
+
widths[(c.colIndex || 0) - minIndex] = `${Math.floor(c.width || 150)}px`;
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
return widths.join(' ');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
getColGridIndex(item: Column | ColumnGroup, section: 'left' | 'right' | 'none'): number {
|
|
624
|
+
if (!this.gridApi) return 1;
|
|
625
|
+
const allCols = this.gridApi.getAllColumns();
|
|
626
|
+
const sectionCols = allCols.filter((c) => {
|
|
627
|
+
if (section === 'left') return c.pinned === 'left';
|
|
628
|
+
if (section === 'right') return c.pinned === 'right';
|
|
629
|
+
return !c.pinned;
|
|
630
|
+
});
|
|
631
|
+
if (sectionCols.length === 0) return 1;
|
|
632
|
+
const minIndex = Math.min(...sectionCols.map((c) => c.colIndex || 0));
|
|
633
|
+
|
|
634
|
+
return (item.colIndex || 0) - minIndex + 1;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
getScrollableColIndex(item: Column | ColumnGroup): number {
|
|
638
|
+
return this.getColGridIndex(item, 'none');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
getRightPinnedColIndex(item: Column | ColumnGroup): number {
|
|
642
|
+
return this.getColGridIndex(item, 'right');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
getLeftPinnedColIndex(item: Column | ColumnGroup): number {
|
|
646
|
+
return this.getColGridIndex(item, 'left');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
getSectionHeaderItems(
|
|
650
|
+
section: 'left' | 'right' | 'none'
|
|
651
|
+
): { item: Column | ColumnGroup; rowIndex: number }[] {
|
|
652
|
+
const items: { item: Column | ColumnGroup; rowIndex: number }[] = [];
|
|
653
|
+
const rows = this.getHeaderRows();
|
|
654
|
+
rows.forEach((row, i) => {
|
|
655
|
+
let rowItems: (Column | ColumnGroup)[] = [];
|
|
656
|
+
if (section === 'left') rowItems = this.getPinnedLeftItems(row);
|
|
657
|
+
else if (section === 'right') rowItems = this.getPinnedRightItems(row);
|
|
658
|
+
else rowItems = this.getNonPinnedItems(row);
|
|
659
|
+
|
|
660
|
+
rowItems.forEach((item) => items.push({ item, rowIndex: i }));
|
|
661
|
+
});
|
|
662
|
+
return items;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
hasExpansionToggle(item: ColumnGroup): boolean {
|
|
666
|
+
return item.children.some(
|
|
667
|
+
(child) => child.columnGroupShow === 'open' || child.columnGroupShow === 'closed'
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
isRowGroupPanelVisible(): boolean {
|
|
672
|
+
const show = this.rowGroupPanelShow;
|
|
673
|
+
if (show === 'always') return true;
|
|
674
|
+
if (show === 'onlyWhenGrouping') {
|
|
675
|
+
return this.rowGroupColumns.length > 0;
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
trackByRowGroup(index: number, col: Column): string {
|
|
681
|
+
return col.colId || index.toString();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private updateRowGroupColumns(): void {
|
|
685
|
+
if (!this.gridApi) {
|
|
686
|
+
this.rowGroupColumns = [];
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const groupColIds = this.gridApi.getRowGroupColumns();
|
|
690
|
+
this.rowGroupColumns = this.gridApi
|
|
691
|
+
.getAllColumns()
|
|
692
|
+
.filter((col) => groupColIds.includes(col.colId));
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
getRowGroupColumns(): Column[] {
|
|
696
|
+
return this.rowGroupColumns;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
onRowGroupDropped(event: CdkDragDrop<any[]>): void {
|
|
700
|
+
const col = event.item.data as Column;
|
|
701
|
+
if (col?.colId && col.colId !== 'ag-Grid-SelectionColumn') {
|
|
702
|
+
this.gridApi.addRowGroupColumn(col.colId);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
removeRowGroup(col: Column): void {
|
|
706
|
+
this.gridApi.removeRowGroupColumn(col.colId);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
toggleGroup(item: ColumnGroup, event: MouseEvent): void {
|
|
710
|
+
event.stopPropagation();
|
|
711
|
+
this.gridApi.toggleColumnGroup(item.groupId, !item.expanded);
|
|
712
|
+
}
|
|
713
|
+
|
|
314
714
|
getColumnWidth(col: Column | ColDef<TData> | ColGroupDef<TData>): number {
|
|
315
715
|
if ('children' in col) {
|
|
316
716
|
// Column group - sum children widths
|
|
317
717
|
return col.children.reduce((sum, child) => sum + this.getColumnWidth(child), 0);
|
|
318
718
|
}
|
|
319
|
-
return col.width || 150;
|
|
719
|
+
return Math.floor(col.width || 150);
|
|
320
720
|
}
|
|
321
721
|
|
|
322
722
|
getLeftPinnedColumns(): Column[] {
|
|
323
723
|
if (!this.gridApi) return [];
|
|
324
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
325
|
-
return col
|
|
724
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
725
|
+
return isColumnVisible(col) && col.pinned === 'left';
|
|
326
726
|
});
|
|
327
727
|
}
|
|
328
728
|
|
|
329
729
|
getRightPinnedColumns(): Column[] {
|
|
330
730
|
if (!this.gridApi) return [];
|
|
331
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
332
|
-
return col
|
|
731
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
732
|
+
return isColumnVisible(col) && col.pinned === 'right';
|
|
333
733
|
});
|
|
334
734
|
}
|
|
335
735
|
|
|
336
736
|
getNonPinnedColumns(): Column[] {
|
|
337
737
|
if (!this.gridApi) return [];
|
|
338
|
-
return this.gridApi.getAllColumns().filter(col => {
|
|
339
|
-
return col
|
|
738
|
+
return this.gridApi.getAllColumns().filter((col) => {
|
|
739
|
+
return isColumnVisible(col) && !col.pinned;
|
|
340
740
|
});
|
|
341
741
|
}
|
|
342
|
-
|
|
742
|
+
|
|
343
743
|
isSortable(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
744
|
+
const colId = (col as any).colId || (col as any).field?.toString();
|
|
745
|
+
if (colId === 'ag-Grid-SelectionColumn') return false;
|
|
746
|
+
|
|
344
747
|
// If it has children, it's a group and cannot be sorted directly
|
|
345
748
|
if ('children' in col) return false;
|
|
346
749
|
|
|
@@ -351,34 +754,44 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
351
754
|
|
|
352
755
|
// It's likely a Column object, look up its ColDef
|
|
353
756
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
354
|
-
return colDef
|
|
757
|
+
return colDef && this.isColDef(colDef) ? colDef.sortable !== false : true;
|
|
355
758
|
}
|
|
356
|
-
|
|
759
|
+
|
|
357
760
|
getHeaderName(col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
358
761
|
if ('children' in col) {
|
|
359
762
|
return col.headerName || '';
|
|
360
763
|
}
|
|
764
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') {
|
|
765
|
+
return '';
|
|
766
|
+
}
|
|
361
767
|
return col.headerName || (col as any).field?.toString() || '';
|
|
362
768
|
}
|
|
363
|
-
|
|
769
|
+
|
|
364
770
|
getSortIndicator(col: Column | ColDef<TData> | ColGroupDef<TData>): string {
|
|
365
771
|
if ('children' in col || !col.sort) {
|
|
366
772
|
return '';
|
|
367
773
|
}
|
|
368
774
|
return col.sort === 'asc' ? '▲' : '▼';
|
|
369
775
|
}
|
|
370
|
-
|
|
776
|
+
|
|
371
777
|
onHeaderClick(col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
778
|
+
if (this.isResizing) return;
|
|
779
|
+
|
|
780
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') {
|
|
781
|
+
// Selection is now handled by the checkbox directly to avoid resizing interference
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
372
785
|
if (!this.isSortable(col) || 'children' in col) {
|
|
373
786
|
return;
|
|
374
787
|
}
|
|
375
|
-
|
|
788
|
+
|
|
376
789
|
// Toggle sort
|
|
377
790
|
const currentSort = col.sort;
|
|
378
791
|
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
379
|
-
|
|
792
|
+
|
|
380
793
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
381
|
-
|
|
794
|
+
|
|
382
795
|
// Update the column directly if it's a Column object
|
|
383
796
|
if ('colId' in col && !(col as any).children) {
|
|
384
797
|
(col as any).sort = newSort;
|
|
@@ -390,44 +803,198 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
390
803
|
|
|
391
804
|
// --- Header Menu Logic ---
|
|
392
805
|
|
|
806
|
+
headerMenuItems: MenuItemDef[] = [];
|
|
807
|
+
|
|
393
808
|
hasHeaderMenu(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
809
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') return false;
|
|
394
810
|
if ('children' in col) return false;
|
|
395
811
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
396
|
-
return colDef ? colDef.suppressHeaderMenuButton !== true : true;
|
|
812
|
+
return colDef && this.isColDef(colDef) ? colDef.suppressHeaderMenuButton !== true : true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
hasHeaderFilterButton(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
816
|
+
if ((col as any).colId === 'ag-Grid-SelectionColumn') return false;
|
|
817
|
+
if ('children' in col) return false;
|
|
818
|
+
const colDef = this.getColumnDefForColumn(col as any);
|
|
819
|
+
if (!colDef || !this.isColDef(colDef)) return false;
|
|
820
|
+
if (!colDef.filter || colDef.suppressHeaderFilterButton === true) return false;
|
|
821
|
+
// Don't show when floating filters are active — they already provide quick access
|
|
822
|
+
if (colDef.floatingFilter) return false;
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
isColumnFiltered(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
827
|
+
if (!this.gridApi) return false;
|
|
828
|
+
const column = col as Column;
|
|
829
|
+
const field = column.field || column.colId;
|
|
830
|
+
if (!field) return false;
|
|
831
|
+
const filterModel = this.gridApi.getFilterModel();
|
|
832
|
+
return !!(filterModel as any)[field];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
onHeaderFilterClick(event: MouseEvent, col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
836
|
+
event.stopPropagation();
|
|
837
|
+
const column = col as Column;
|
|
838
|
+
const target = event.target as HTMLElement;
|
|
839
|
+
// Use closest ancestor that has a bounding rect useful for positioning
|
|
840
|
+
const iconEl = (target.closest('.argent-grid-header-filter-icon') as HTMLElement) ?? target;
|
|
841
|
+
const rect = iconEl.getBoundingClientRect();
|
|
842
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
843
|
+
const position = {
|
|
844
|
+
x: rect.left - containerRect.left,
|
|
845
|
+
y: rect.bottom - containerRect.top + 4,
|
|
846
|
+
};
|
|
847
|
+
const colDef = this.getColumnDefForColumn(column);
|
|
848
|
+
if (colDef && this.isColDef(colDef) && colDef.filter === 'set') {
|
|
849
|
+
this.openSetFilter(null, column, position);
|
|
850
|
+
} else {
|
|
851
|
+
this.openFilterPopup(null, column, position);
|
|
852
|
+
}
|
|
397
853
|
}
|
|
398
854
|
|
|
399
855
|
onHeaderMenuClick(event: MouseEvent, col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
400
856
|
event.stopPropagation();
|
|
401
|
-
|
|
857
|
+
|
|
402
858
|
if (this.activeHeaderMenu === col) {
|
|
403
859
|
this.closeHeaderMenu();
|
|
404
860
|
return;
|
|
405
861
|
}
|
|
406
862
|
|
|
407
863
|
this.activeHeaderMenu = col;
|
|
408
|
-
|
|
409
|
-
|
|
864
|
+
this.headerMenuItems = this.getHeaderMenuItems(col as Column);
|
|
865
|
+
|
|
866
|
+
// Position menu below the icon using coordinates relative to the grid container
|
|
410
867
|
const target = event.target as HTMLElement;
|
|
411
868
|
const rect = target.getBoundingClientRect();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
869
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
870
|
+
|
|
871
|
+
// Align left edge of menu with left edge of icon
|
|
872
|
+
let x = rect.left - containerRect.left;
|
|
873
|
+
let y = rect.bottom - containerRect.top + 4;
|
|
874
|
+
|
|
875
|
+
// Prevent menu from going off-container bounds
|
|
876
|
+
const containerWidth = this._elementRef.nativeElement.offsetWidth;
|
|
877
|
+
const containerHeight = this._elementRef.nativeElement.offsetHeight;
|
|
415
878
|
|
|
416
|
-
//
|
|
879
|
+
// Assuming menu width is up to 200px for boundary checks
|
|
880
|
+
if (x + 200 > containerWidth) {
|
|
881
|
+
x = rect.right - containerRect.left - 200; // Flip to right-aligned if it overflows
|
|
882
|
+
}
|
|
417
883
|
if (x < 0) x = 0;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
884
|
+
|
|
885
|
+
// Check if menu would overflow bottom
|
|
886
|
+
const estimatedHeight = this.headerMenuItems.length * 30 + 20;
|
|
887
|
+
if (y + estimatedHeight > containerHeight) {
|
|
888
|
+
y = Math.max(0, rect.top - containerRect.top - estimatedHeight);
|
|
421
889
|
}
|
|
422
|
-
|
|
890
|
+
|
|
423
891
|
this.headerMenuPosition = { x, y };
|
|
424
|
-
|
|
425
|
-
this.
|
|
892
|
+
|
|
893
|
+
this._cdr.detectChanges();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
private getHeaderMenuItems(col: Column): MenuItemDef[] {
|
|
897
|
+
const items: MenuItemDef[] = [];
|
|
898
|
+
|
|
899
|
+
// 1. Sort items
|
|
900
|
+
items.push({
|
|
901
|
+
name: 'Sort Ascending',
|
|
902
|
+
icon: '↑',
|
|
903
|
+
action: () => this.sortColumnMenu('asc'),
|
|
904
|
+
});
|
|
905
|
+
items.push({
|
|
906
|
+
name: 'Sort Descending',
|
|
907
|
+
icon: '↓',
|
|
908
|
+
action: () => this.sortColumnMenu('desc'),
|
|
909
|
+
});
|
|
910
|
+
items.push({
|
|
911
|
+
name: 'Clear Sort',
|
|
912
|
+
icon: '✕',
|
|
913
|
+
action: () => this.sortColumnMenu(null),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
917
|
+
|
|
918
|
+
// 2. Filter items
|
|
919
|
+
const colDef = this.getColumnDefForColumn(col);
|
|
920
|
+
if (colDef && this.isColDef(colDef) && colDef.filter !== false) {
|
|
921
|
+
const filterType = colDef.filter || 'text';
|
|
922
|
+
|
|
923
|
+
if (filterType === 'set') {
|
|
924
|
+
items.push({
|
|
925
|
+
name: 'Filter...',
|
|
926
|
+
icon: 'Y',
|
|
927
|
+
action: () => {
|
|
928
|
+
this.openSetFilter(null, col, { ...this.headerMenuPosition });
|
|
929
|
+
this.closeHeaderMenu();
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
} else {
|
|
933
|
+
items.push({
|
|
934
|
+
name: 'Filter...',
|
|
935
|
+
icon: 'Y',
|
|
936
|
+
action: () => {
|
|
937
|
+
this.openFilterPopup(null, col, { ...this.headerMenuPosition });
|
|
938
|
+
this.closeHeaderMenu();
|
|
939
|
+
},
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
945
|
+
|
|
946
|
+
// 3. Pinning items
|
|
947
|
+
items.push({
|
|
948
|
+
name: 'Pin Left',
|
|
949
|
+
icon: '«',
|
|
950
|
+
action: () => this.pinColumnMenu('left'),
|
|
951
|
+
});
|
|
952
|
+
items.push({
|
|
953
|
+
name: 'Pin Right',
|
|
954
|
+
icon: '»',
|
|
955
|
+
action: () => this.pinColumnMenu('right'),
|
|
956
|
+
});
|
|
957
|
+
items.push({
|
|
958
|
+
name: 'Unpin',
|
|
959
|
+
icon: '↺',
|
|
960
|
+
action: () => this.pinColumnMenu(null),
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
964
|
+
|
|
965
|
+
// 4. Hide item
|
|
966
|
+
items.push({
|
|
967
|
+
name: 'Hide Column',
|
|
968
|
+
icon: 'ø',
|
|
969
|
+
action: () => this.hideColumnMenu(),
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
items.push({ name: '', action: () => {}, separator: true });
|
|
973
|
+
|
|
974
|
+
// 5. Columns panel (open sidebar)
|
|
975
|
+
items.push({
|
|
976
|
+
name: 'Columns',
|
|
977
|
+
icon: '☰',
|
|
978
|
+
action: () => this.openColumnsPanel(),
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
return items;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
public clearColumnFilter(col: Column): void {
|
|
985
|
+
const field = col.field;
|
|
986
|
+
if (!field || !this.gridApi) return;
|
|
987
|
+
|
|
988
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
989
|
+
delete (currentModel as any)[field];
|
|
990
|
+
this.gridApi.setFilterModel(currentModel);
|
|
991
|
+
this.closeHeaderMenu();
|
|
992
|
+
this.closeFilterPopup();
|
|
426
993
|
}
|
|
427
994
|
|
|
428
995
|
closeHeaderMenu(): void {
|
|
429
996
|
this.activeHeaderMenu = null;
|
|
430
|
-
this.
|
|
997
|
+
this._cdr.detectChanges();
|
|
431
998
|
}
|
|
432
999
|
|
|
433
1000
|
onContainerClick(event: MouseEvent): void {
|
|
@@ -448,19 +1015,19 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
448
1015
|
|
|
449
1016
|
onCanvasContextMenu(event: MouseEvent): void {
|
|
450
1017
|
event.preventDefault();
|
|
451
|
-
|
|
1018
|
+
|
|
452
1019
|
// Get hit test from canvas renderer to know which cell was clicked
|
|
453
1020
|
const hitTest = this.canvasRenderer.getHitTestResult(event);
|
|
454
1021
|
if (!hitTest || hitTest.rowIndex === -1) return;
|
|
455
|
-
|
|
1022
|
+
|
|
456
1023
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(hitTest.rowIndex);
|
|
457
|
-
const columns = this.gridApi.getAllColumns().filter(col => col.visible);
|
|
1024
|
+
const columns = this.gridApi.getAllColumns().filter((col) => col.visible);
|
|
458
1025
|
const column = columns[hitTest.columnIndex];
|
|
459
|
-
|
|
1026
|
+
|
|
460
1027
|
if (!rowNode || !column) return;
|
|
461
|
-
|
|
1028
|
+
|
|
462
1029
|
this.contextMenuCell = { rowNode, column };
|
|
463
|
-
|
|
1030
|
+
|
|
464
1031
|
// Resolve menu items via API if provided
|
|
465
1032
|
const getContextMenuItems = this.gridApi.getGridOption('getContextMenuItems');
|
|
466
1033
|
if (getContextMenuItems) {
|
|
@@ -469,44 +1036,51 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
469
1036
|
column: column,
|
|
470
1037
|
api: this.gridApi,
|
|
471
1038
|
type: 'cell',
|
|
472
|
-
event: event
|
|
1039
|
+
event: event,
|
|
473
1040
|
};
|
|
474
1041
|
this.contextMenuItems = this.resolveContextMenuItems(getContextMenuItems(params));
|
|
475
1042
|
} else {
|
|
476
1043
|
// Fallback to defaults if no callback provided
|
|
477
1044
|
this.contextMenuItems = this.resolveContextMenuItems([
|
|
478
|
-
'copy',
|
|
1045
|
+
'copy',
|
|
1046
|
+
'copyWithHeaders',
|
|
1047
|
+
'separator',
|
|
1048
|
+
'export',
|
|
479
1049
|
]);
|
|
480
1050
|
}
|
|
481
1051
|
|
|
482
1052
|
if (this.contextMenuItems.length === 0) return;
|
|
483
1053
|
|
|
484
1054
|
this.activeContextMenu = true;
|
|
485
|
-
|
|
486
|
-
// Position menu at mouse coordinates (fixed/viewport)
|
|
487
|
-
let x = event.clientX;
|
|
488
|
-
let y = event.clientY;
|
|
489
1055
|
|
|
490
|
-
//
|
|
491
|
-
|
|
492
|
-
|
|
1056
|
+
// Position menu at mouse coordinates relative to container
|
|
1057
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
1058
|
+
let x = event.clientX - containerRect.left;
|
|
1059
|
+
let y = event.clientY - containerRect.top;
|
|
1060
|
+
|
|
1061
|
+
// Prevent menu from going off-container bounds
|
|
1062
|
+
const containerWidth = this._elementRef.nativeElement.offsetWidth;
|
|
1063
|
+
const containerHeight = this._elementRef.nativeElement.offsetHeight;
|
|
1064
|
+
|
|
1065
|
+
if (x + 200 > containerWidth) x = containerWidth - 200;
|
|
1066
|
+
if (y + 200 > containerHeight) y = containerHeight - 200;
|
|
493
1067
|
|
|
494
1068
|
this.contextMenuPosition = { x, y };
|
|
495
|
-
|
|
1069
|
+
|
|
496
1070
|
// Select the row
|
|
497
1071
|
this.gridApi.deselectAll();
|
|
498
1072
|
rowNode.selected = true;
|
|
499
1073
|
this.updateSelectionState();
|
|
500
1074
|
this.canvasRenderer?.render();
|
|
501
1075
|
this.selectionChanged.emit(this.gridApi.getSelectedRows());
|
|
502
|
-
|
|
503
|
-
this.
|
|
1076
|
+
|
|
1077
|
+
this._cdr.detectChanges();
|
|
504
1078
|
}
|
|
505
1079
|
|
|
506
1080
|
private resolveContextMenuItems(items: (DefaultMenuItem | MenuItemDef)[]): MenuItemDef[] {
|
|
507
1081
|
const resolved: MenuItemDef[] = [];
|
|
508
|
-
|
|
509
|
-
items.forEach(item => {
|
|
1082
|
+
|
|
1083
|
+
items.forEach((item) => {
|
|
510
1084
|
if (typeof item === 'string') {
|
|
511
1085
|
const defaultItem = this.getDefaultMenuItem(item);
|
|
512
1086
|
if (defaultItem) resolved.push(defaultItem);
|
|
@@ -514,7 +1088,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
514
1088
|
resolved.push(item);
|
|
515
1089
|
}
|
|
516
1090
|
});
|
|
517
|
-
|
|
1091
|
+
|
|
518
1092
|
return resolved;
|
|
519
1093
|
}
|
|
520
1094
|
|
|
@@ -523,17 +1097,18 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
523
1097
|
case 'copy':
|
|
524
1098
|
return { name: 'Copy Cell', action: () => this.copyContextMenuCell(), icon: '📋' };
|
|
525
1099
|
case 'copyWithHeaders':
|
|
526
|
-
return this.hasRangeSelection()
|
|
527
|
-
{ name: 'Copy with Headers', action: () => this.copyRangeWithHeaders(), icon: '📋' }
|
|
1100
|
+
return this.hasRangeSelection()
|
|
1101
|
+
? { name: 'Copy with Headers', action: () => this.copyRangeWithHeaders(), icon: '📋' }
|
|
1102
|
+
: null;
|
|
528
1103
|
case 'export':
|
|
529
|
-
return {
|
|
530
|
-
name: 'Export',
|
|
531
|
-
action: () => {},
|
|
1104
|
+
return {
|
|
1105
|
+
name: 'Export',
|
|
1106
|
+
action: () => {},
|
|
532
1107
|
icon: '⤓',
|
|
533
1108
|
subMenu: [
|
|
534
1109
|
{ name: 'Export to CSV', action: () => this.exportCSV() },
|
|
535
|
-
{ name: 'Export to Excel (.xlsx)', action: () => this.exportExcel() }
|
|
536
|
-
]
|
|
1110
|
+
{ name: 'Export to Excel (.xlsx)', action: () => this.exportExcel() },
|
|
1111
|
+
],
|
|
537
1112
|
};
|
|
538
1113
|
case 'resetColumns':
|
|
539
1114
|
return { name: 'Reset Columns', action: () => this.resetColumns(), icon: '⟲' };
|
|
@@ -544,10 +1119,322 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
544
1119
|
}
|
|
545
1120
|
}
|
|
546
1121
|
|
|
1122
|
+
// ============================================================================
|
|
1123
|
+
// TOOLTIP
|
|
1124
|
+
// ============================================================================
|
|
1125
|
+
|
|
1126
|
+
onCanvasMouseMove(event: MouseEvent): void {
|
|
1127
|
+
// Cancel any pending show and hide the current tooltip on every move
|
|
1128
|
+
if (this._tooltipTimer) {
|
|
1129
|
+
clearTimeout(this._tooltipTimer);
|
|
1130
|
+
this._tooltipTimer = null;
|
|
1131
|
+
}
|
|
1132
|
+
this.tooltipVisible = false;
|
|
1133
|
+
|
|
1134
|
+
if (!this.canvasRenderer) return;
|
|
1135
|
+
|
|
1136
|
+
const hit = this.canvasRenderer.getHitTestResult(event);
|
|
1137
|
+
const { rowIndex, columnIndex } = hit;
|
|
1138
|
+
if (rowIndex < 0 || columnIndex < 0) return;
|
|
1139
|
+
|
|
1140
|
+
const columns = this.canvasRenderer.getAllColumns();
|
|
1141
|
+
const column = columns[columnIndex];
|
|
1142
|
+
if (!column) return;
|
|
1143
|
+
|
|
1144
|
+
const text = this.computeTooltipText(rowIndex, column);
|
|
1145
|
+
if (!text) return;
|
|
1146
|
+
|
|
1147
|
+
this._tooltipTimer = setTimeout(() => {
|
|
1148
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
1149
|
+
let tx = event.clientX - containerRect.left + 14;
|
|
1150
|
+
let ty = event.clientY - containerRect.top + 14;
|
|
1151
|
+
// Keep within container bounds
|
|
1152
|
+
const cw = this._elementRef.nativeElement.offsetWidth;
|
|
1153
|
+
const ch = this._elementRef.nativeElement.offsetHeight;
|
|
1154
|
+
if (tx + 220 > cw) tx = Math.max(0, tx - 234);
|
|
1155
|
+
if (ty + 56 > ch) ty = Math.max(0, ty - 70);
|
|
1156
|
+
this.tooltipText = text;
|
|
1157
|
+
this.tooltipPosition = { x: tx, y: ty };
|
|
1158
|
+
this.tooltipVisible = true;
|
|
1159
|
+
this._cdr.detectChanges();
|
|
1160
|
+
}, 500);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
onCanvasMouseLeave(): void {
|
|
1164
|
+
if (this._tooltipTimer) {
|
|
1165
|
+
clearTimeout(this._tooltipTimer);
|
|
1166
|
+
this._tooltipTimer = null;
|
|
1167
|
+
}
|
|
1168
|
+
if (this.tooltipVisible) {
|
|
1169
|
+
this.tooltipVisible = false;
|
|
1170
|
+
this._cdr.detectChanges();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
private computeTooltipText(rowIndex: number, column: Column): string | null {
|
|
1175
|
+
const colDef = this.getColumnDefForColumn(column) as ColDef<TData> | null;
|
|
1176
|
+
if (!colDef || !this.isColDef(colDef)) return null;
|
|
1177
|
+
|
|
1178
|
+
const rowNode = this.gridApi?.getDisplayedRowAtIndex(rowIndex);
|
|
1179
|
+
if (!rowNode?.data) return null;
|
|
1180
|
+
|
|
1181
|
+
// tooltipValueGetter takes priority (AG Grid parity)
|
|
1182
|
+
if (typeof colDef.tooltipValueGetter === 'function') {
|
|
1183
|
+
const val = colDef.field ? (rowNode.data as any)[colDef.field as string] : undefined;
|
|
1184
|
+
return (
|
|
1185
|
+
colDef.tooltipValueGetter({
|
|
1186
|
+
value: val,
|
|
1187
|
+
data: rowNode.data as TData,
|
|
1188
|
+
node: rowNode,
|
|
1189
|
+
column,
|
|
1190
|
+
}) ?? null
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// tooltipField — show the value of that field
|
|
1195
|
+
if (colDef.tooltipField) {
|
|
1196
|
+
const val = (rowNode.data as any)[colDef.tooltipField as string];
|
|
1197
|
+
return val != null ? String(val) : null;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
547
1203
|
closeContextMenu(): void {
|
|
548
1204
|
this.activeContextMenu = false;
|
|
549
1205
|
this.contextMenuCell = null;
|
|
550
|
-
this.
|
|
1206
|
+
this._cdr.detectChanges();
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Set Filter Methods
|
|
1210
|
+
isSetFilter(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
1211
|
+
if ('children' in col) return false;
|
|
1212
|
+
const colDef = col as ColDef<TData>;
|
|
1213
|
+
return colDef.filter === 'set';
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
openSetFilter(
|
|
1217
|
+
event: MouseEvent | null,
|
|
1218
|
+
col: Column | ColDef<TData>,
|
|
1219
|
+
position?: { x: number; y: number }
|
|
1220
|
+
): void {
|
|
1221
|
+
if (event) {
|
|
1222
|
+
event.stopPropagation();
|
|
1223
|
+
event.preventDefault();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
this.activeSetFilterColumn = col as Column;
|
|
1227
|
+
|
|
1228
|
+
const field = col.field;
|
|
1229
|
+
if (!field || !this.gridApi) return;
|
|
1230
|
+
|
|
1231
|
+
this.setFilterValues = this.gridService.getUniqueValues(field as string);
|
|
1232
|
+
|
|
1233
|
+
// Restore previously selected values from the current filter model
|
|
1234
|
+
const existingFilter = this.gridApi.getFilterModel()[field as string] as any;
|
|
1235
|
+
this.setFilterSelectedValues =
|
|
1236
|
+
existingFilter?.filterType === 'set' && Array.isArray(existingFilter.values)
|
|
1237
|
+
? existingFilter.values
|
|
1238
|
+
: null;
|
|
1239
|
+
|
|
1240
|
+
const colDef = 'field' in col ? (col as ColDef<TData>) : null;
|
|
1241
|
+
this.setFilterValueFormatter = colDef?.valueFormatter
|
|
1242
|
+
? (colDef.valueFormatter as any)
|
|
1243
|
+
: undefined;
|
|
1244
|
+
|
|
1245
|
+
if (position) {
|
|
1246
|
+
this.setFilterPosition = position;
|
|
1247
|
+
} else if (event) {
|
|
1248
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
1249
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
1250
|
+
this.setFilterPosition = {
|
|
1251
|
+
x: rect.left - containerRect.left,
|
|
1252
|
+
y: rect.bottom - containerRect.top + 5,
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
setTimeout(() => {
|
|
1257
|
+
this.activeSetFilter = true;
|
|
1258
|
+
this._cdr.detectChanges();
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
closeSetFilter(): void {
|
|
1263
|
+
this.activeSetFilter = false;
|
|
1264
|
+
this.activeSetFilterColumn = null;
|
|
1265
|
+
this._cdr.detectChanges();
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Filter Popup state
|
|
1269
|
+
activeFilterPopup = false;
|
|
1270
|
+
activeFilterPopupColumn: Column | null = null;
|
|
1271
|
+
activeFilterPopupType: 'text' | 'number' | 'date' | 'boolean' | 'set' | 'multiFilter' = 'text';
|
|
1272
|
+
activeFilterOperator: string = 'contains';
|
|
1273
|
+
filterPopupPosition = { x: 0, y: 0 };
|
|
1274
|
+
filterValue1: string = '';
|
|
1275
|
+
filterValue2: string = '';
|
|
1276
|
+
|
|
1277
|
+
readonly textFilterOperators = [
|
|
1278
|
+
{ value: 'contains', label: 'Contains' },
|
|
1279
|
+
{ value: 'notContains', label: 'Not contains' },
|
|
1280
|
+
{ value: 'equals', label: 'Equals' },
|
|
1281
|
+
{ value: 'notEquals', label: 'Not equals' },
|
|
1282
|
+
{ value: 'startsWith', label: 'Starts with' },
|
|
1283
|
+
{ value: 'endsWith', label: 'Ends with' },
|
|
1284
|
+
{ value: 'blank', label: 'Blank' },
|
|
1285
|
+
{ value: 'notBlank', label: 'Not blank' },
|
|
1286
|
+
];
|
|
1287
|
+
|
|
1288
|
+
readonly numberFilterOperators = [
|
|
1289
|
+
{ value: 'equals', label: 'Equals' },
|
|
1290
|
+
{ value: 'notEquals', label: 'Not equals' },
|
|
1291
|
+
{ value: 'greaterThan', label: 'Greater than' },
|
|
1292
|
+
{ value: 'greaterThanOrEqual', label: 'Greater than or equals' },
|
|
1293
|
+
{ value: 'lessThan', label: 'Less than' },
|
|
1294
|
+
{ value: 'lessThanOrEqual', label: 'Less than or equals' },
|
|
1295
|
+
{ value: 'inRange', label: 'In range' },
|
|
1296
|
+
{ value: 'blank', label: 'Blank' },
|
|
1297
|
+
{ value: 'notBlank', label: 'Not blank' },
|
|
1298
|
+
];
|
|
1299
|
+
|
|
1300
|
+
onFilterPopupOperatorChange(operator: string): void {
|
|
1301
|
+
this.activeFilterOperator = operator;
|
|
1302
|
+
this.applyPopupFilter();
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
onFilterPopupInput(event: Event, isSecondValue: boolean = false): void {
|
|
1306
|
+
const value = (event.target as HTMLInputElement).value;
|
|
1307
|
+
if (isSecondValue) {
|
|
1308
|
+
this.filterValue2 = value;
|
|
1309
|
+
} else {
|
|
1310
|
+
this.filterValue1 = value;
|
|
1311
|
+
}
|
|
1312
|
+
this.applyPopupFilter();
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
private applyPopupFilter(): void {
|
|
1316
|
+
if (!this.activeFilterPopupColumn || !this.gridApi) return;
|
|
1317
|
+
|
|
1318
|
+
const col = this.activeFilterPopupColumn;
|
|
1319
|
+
const field = col.field;
|
|
1320
|
+
if (!field) return;
|
|
1321
|
+
|
|
1322
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
1323
|
+
|
|
1324
|
+
if (this.activeFilterOperator === 'blank' || this.activeFilterOperator === 'notBlank') {
|
|
1325
|
+
currentModel[col.colId] = {
|
|
1326
|
+
filterType: this.activeFilterPopupType,
|
|
1327
|
+
type: this.activeFilterOperator,
|
|
1328
|
+
};
|
|
1329
|
+
} else {
|
|
1330
|
+
const value = this.filterValue1;
|
|
1331
|
+
if (!value && this.activeFilterOperator !== 'inRange') {
|
|
1332
|
+
delete currentModel[col.colId];
|
|
1333
|
+
} else {
|
|
1334
|
+
const filterModel: any = {
|
|
1335
|
+
filterType: this.activeFilterPopupType,
|
|
1336
|
+
type: this.activeFilterOperator,
|
|
1337
|
+
filter: value,
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
if (this.activeFilterOperator === 'inRange') {
|
|
1341
|
+
filterModel.filterTo = this.filterValue2;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
currentModel[col.colId] = filterModel;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
this.gridApi.setFilterModel(currentModel);
|
|
1349
|
+
this.canvasRenderer?.render();
|
|
1350
|
+
this._cdr.detectChanges();
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
openFilterPopup(
|
|
1354
|
+
event: MouseEvent | null,
|
|
1355
|
+
col: Column,
|
|
1356
|
+
position?: { x: number; y: number }
|
|
1357
|
+
): void {
|
|
1358
|
+
this.activeFilterPopupColumn = col;
|
|
1359
|
+
const colDef = this.getColumnDefForColumn(col);
|
|
1360
|
+
this.activeFilterPopupType =
|
|
1361
|
+
colDef && this.isColDef(colDef) && colDef.filter === 'number' ? 'number' : 'text';
|
|
1362
|
+
|
|
1363
|
+
// Initialize operator and values from current model or default
|
|
1364
|
+
const model = this.gridApi?.getFilterModel()[col.colId] as any;
|
|
1365
|
+
this.activeFilterOperator =
|
|
1366
|
+
model?.type || (this.activeFilterPopupType === 'number' ? 'equals' : 'contains');
|
|
1367
|
+
this.filterValue1 = model?.filter || '';
|
|
1368
|
+
this.filterValue2 = model?.filterTo || '';
|
|
1369
|
+
|
|
1370
|
+
if (position) {
|
|
1371
|
+
this.filterPopupPosition = position;
|
|
1372
|
+
} else if (event) {
|
|
1373
|
+
const rect = (event.target as HTMLElement).getBoundingClientRect();
|
|
1374
|
+
const containerRect = this._elementRef.nativeElement.getBoundingClientRect();
|
|
1375
|
+
this.filterPopupPosition = {
|
|
1376
|
+
x: rect.left - containerRect.left,
|
|
1377
|
+
y: rect.bottom - containerRect.top + 5,
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
setTimeout(() => {
|
|
1382
|
+
this.activeFilterPopup = true;
|
|
1383
|
+
this._cdr.detectChanges();
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
closeFilterPopup(): void {
|
|
1388
|
+
this.activeFilterPopup = false;
|
|
1389
|
+
this.activeFilterPopupColumn = null;
|
|
1390
|
+
this._cdr.detectChanges();
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
onSetFilterChanged(values: any[]): void {
|
|
1394
|
+
if (!this.activeSetFilterColumn || !this.gridApi) return;
|
|
1395
|
+
|
|
1396
|
+
const field = this.activeSetFilterColumn.field;
|
|
1397
|
+
if (!field) return;
|
|
1398
|
+
|
|
1399
|
+
if (values.length === 0) {
|
|
1400
|
+
const currentModel = this.gridApi.getFilterModel();
|
|
1401
|
+
delete (currentModel as any)[field];
|
|
1402
|
+
this.gridApi.setFilterModel(currentModel);
|
|
1403
|
+
} else {
|
|
1404
|
+
this.gridApi.setFilterModel({
|
|
1405
|
+
...this.gridApi.getFilterModel(),
|
|
1406
|
+
[field]: {
|
|
1407
|
+
filterType: 'set',
|
|
1408
|
+
values: values,
|
|
1409
|
+
},
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
this.closeSetFilter();
|
|
1414
|
+
this.canvasRenderer?.render();
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
hasSetFilterValue(col: Column | ColDef<TData>): boolean {
|
|
1418
|
+
if (!this.gridApi) return false;
|
|
1419
|
+
const field = 'field' in col ? col.field : null;
|
|
1420
|
+
if (!field) return false;
|
|
1421
|
+
|
|
1422
|
+
const model = this.gridApi.getFilterModel();
|
|
1423
|
+
const filter = (model as any)[field];
|
|
1424
|
+
return filter && filter.filterType === 'set' && filter.values && filter.values.length > 0;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
getSetFilterCount(col: Column | ColDef<TData>): number {
|
|
1428
|
+
if (!this.gridApi) return 0;
|
|
1429
|
+
const field = 'field' in col ? col.field : null;
|
|
1430
|
+
if (!field) return 0;
|
|
1431
|
+
|
|
1432
|
+
const model = this.gridApi.getFilterModel();
|
|
1433
|
+
const filter = (model as any)[field];
|
|
1434
|
+
if (filter && filter.filterType === 'set' && Array.isArray(filter.values)) {
|
|
1435
|
+
return filter.values.length;
|
|
1436
|
+
}
|
|
1437
|
+
return 0;
|
|
551
1438
|
}
|
|
552
1439
|
|
|
553
1440
|
// Side Bar Methods
|
|
@@ -557,16 +1444,23 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
557
1444
|
} else {
|
|
558
1445
|
this.activeToolPanel = panel;
|
|
559
1446
|
}
|
|
560
|
-
this.
|
|
1447
|
+
this._cdr.detectChanges();
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
openColumnsPanel(): void {
|
|
1451
|
+
this.sideBarVisible = true;
|
|
1452
|
+
this.activeToolPanel = 'columns';
|
|
1453
|
+
this.closeHeaderMenu();
|
|
1454
|
+
this._cdr.detectChanges();
|
|
561
1455
|
}
|
|
562
1456
|
|
|
563
1457
|
toggleColumnVisibility(col: Column): void {
|
|
564
1458
|
const colDef = this.getColumnDefForColumn(col);
|
|
565
|
-
if (colDef) {
|
|
1459
|
+
if (colDef && this.isColDef(colDef)) {
|
|
566
1460
|
colDef.hide = col.visible; // Toggle
|
|
567
1461
|
this.initializeGrid(); // Re-initialize to handle visibility changes correctly
|
|
568
1462
|
this.canvasRenderer?.render();
|
|
569
|
-
this.
|
|
1463
|
+
this._cdr.detectChanges();
|
|
570
1464
|
}
|
|
571
1465
|
}
|
|
572
1466
|
|
|
@@ -582,7 +1476,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
582
1476
|
|
|
583
1477
|
// Map back to ColDefs
|
|
584
1478
|
const newDefs: (ColDef<TData> | ColGroupDef<TData>)[] = [];
|
|
585
|
-
columns.forEach(col => {
|
|
1479
|
+
columns.forEach((col) => {
|
|
586
1480
|
const def = this.getColumnDefForColumn(col);
|
|
587
1481
|
if (def) newDefs.push(def);
|
|
588
1482
|
});
|
|
@@ -592,10 +1486,10 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
592
1486
|
|
|
593
1487
|
copyContextMenuCell(): void {
|
|
594
1488
|
if (!this.contextMenuCell || !this.contextMenuCell.column.field) return;
|
|
595
|
-
|
|
1489
|
+
|
|
596
1490
|
const val = (this.contextMenuCell.rowNode.data as any)[this.contextMenuCell.column.field];
|
|
597
1491
|
if (val !== undefined && val !== null) {
|
|
598
|
-
navigator.clipboard.writeText(String(val)).catch(err => {
|
|
1492
|
+
navigator.clipboard.writeText(String(val)).catch((err) => {
|
|
599
1493
|
console.error('Failed to copy text: ', err);
|
|
600
1494
|
});
|
|
601
1495
|
}
|
|
@@ -612,20 +1506,22 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
612
1506
|
|
|
613
1507
|
const range = ranges[0];
|
|
614
1508
|
const columns = range.columns;
|
|
615
|
-
|
|
616
|
-
let text = columns.map(c => this.getHeaderName(c)).join('\t')
|
|
1509
|
+
|
|
1510
|
+
let text = `${columns.map((c) => this.getHeaderName(c)).join('\t')}\n`;
|
|
617
1511
|
|
|
618
1512
|
for (let i = range.startRow; i <= range.endRow; i++) {
|
|
619
1513
|
const node = this.gridApi.getDisplayedRowAtIndex(i);
|
|
620
1514
|
if (node) {
|
|
621
|
-
text += columns
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1515
|
+
text += `${columns
|
|
1516
|
+
.map((c) => {
|
|
1517
|
+
const val = (node.data as any)[c.field || ''];
|
|
1518
|
+
return val !== null && val !== undefined ? String(val) : '';
|
|
1519
|
+
})
|
|
1520
|
+
.join('\t')}\n`;
|
|
625
1521
|
}
|
|
626
1522
|
}
|
|
627
1523
|
|
|
628
|
-
navigator.clipboard.writeText(text).catch(err => {
|
|
1524
|
+
navigator.clipboard.writeText(text).catch((err) => {
|
|
629
1525
|
console.error('Failed to copy range: ', err);
|
|
630
1526
|
});
|
|
631
1527
|
this.closeContextMenu();
|
|
@@ -646,7 +1542,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
646
1542
|
// Deep copy back the original defs
|
|
647
1543
|
const restored = JSON.parse(JSON.stringify(this.initialColumnDefs));
|
|
648
1544
|
this.onColumnDefsChanged(restored);
|
|
649
|
-
|
|
1545
|
+
|
|
650
1546
|
// Also clear sort model
|
|
651
1547
|
this.gridApi.setSortModel([]);
|
|
652
1548
|
}
|
|
@@ -655,128 +1551,96 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
655
1551
|
|
|
656
1552
|
sortColumnMenu(direction: 'asc' | 'desc' | null): void {
|
|
657
1553
|
if (!this.activeHeaderMenu) return;
|
|
658
|
-
|
|
1554
|
+
|
|
659
1555
|
const col = this.activeHeaderMenu as any;
|
|
660
1556
|
const colId = col.colId || col.field?.toString() || '';
|
|
661
|
-
|
|
1557
|
+
|
|
662
1558
|
// Update original ColDef to ensure persistence
|
|
663
1559
|
const colDef = this.getColumnDefForColumn(col);
|
|
664
|
-
if (colDef) {
|
|
1560
|
+
if (colDef && this.isColDef(colDef)) {
|
|
665
1561
|
colDef.sort = direction;
|
|
666
1562
|
}
|
|
667
1563
|
|
|
668
1564
|
this.gridApi.setSortModel(direction ? [{ colId, sort: direction }] : []);
|
|
669
1565
|
this.canvasRenderer?.render();
|
|
670
|
-
|
|
1566
|
+
|
|
671
1567
|
this.closeHeaderMenu();
|
|
672
1568
|
}
|
|
673
1569
|
|
|
674
1570
|
hideColumnMenu(): void {
|
|
675
1571
|
if (!this.activeHeaderMenu) return;
|
|
676
|
-
|
|
1572
|
+
|
|
677
1573
|
const col = this.activeHeaderMenu as any;
|
|
678
|
-
|
|
1574
|
+
|
|
679
1575
|
// Update the original column definition
|
|
680
1576
|
const colDef = this.getColumnDefForColumn(col);
|
|
681
|
-
if (colDef) {
|
|
1577
|
+
if (colDef && this.isColDef(colDef)) {
|
|
682
1578
|
colDef.hide = true;
|
|
683
1579
|
}
|
|
684
|
-
|
|
1580
|
+
|
|
685
1581
|
// Create new array to trigger change detection and API update
|
|
686
1582
|
if (this.columnDefs) {
|
|
687
1583
|
this.onColumnDefsChanged([...this.columnDefs]);
|
|
688
1584
|
}
|
|
689
|
-
|
|
1585
|
+
|
|
690
1586
|
this.closeHeaderMenu();
|
|
691
1587
|
}
|
|
692
1588
|
|
|
693
1589
|
pinColumnMenu(pin: 'left' | 'right' | null): void {
|
|
694
1590
|
if (!this.activeHeaderMenu) return;
|
|
695
|
-
|
|
1591
|
+
|
|
696
1592
|
const col = this.activeHeaderMenu as any;
|
|
697
|
-
|
|
1593
|
+
|
|
698
1594
|
// Update the original column definition
|
|
699
1595
|
const colDef = this.getColumnDefForColumn(col);
|
|
700
|
-
if (colDef) {
|
|
1596
|
+
if (colDef && this.isColDef(colDef)) {
|
|
701
1597
|
colDef.pinned = pin as any;
|
|
702
1598
|
}
|
|
703
|
-
|
|
1599
|
+
|
|
704
1600
|
if (this.columnDefs) {
|
|
705
1601
|
this.onColumnDefsChanged([...this.columnDefs]);
|
|
706
1602
|
}
|
|
707
|
-
|
|
1603
|
+
|
|
708
1604
|
this.closeHeaderMenu();
|
|
709
1605
|
}
|
|
710
1606
|
|
|
711
|
-
onColumnDropped(event: CdkDragDrop<any
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
// Get current groups (using internal Columns)
|
|
715
|
-
const left = [...this.getLeftPinnedColumns()];
|
|
716
|
-
const center = [...this.getNonPinnedColumns()];
|
|
717
|
-
const right = [...this.getRightPinnedColumns()];
|
|
718
|
-
|
|
719
|
-
const containerMap: { [key: string]: any[] } = {
|
|
720
|
-
'left-pinned': left,
|
|
721
|
-
'scrollable': center,
|
|
722
|
-
'right-pinned': right
|
|
723
|
-
};
|
|
724
|
-
|
|
725
|
-
const previousContainerData = containerMap[event.previousContainer.id];
|
|
726
|
-
const currentContainerData = containerMap[event.container.id];
|
|
1607
|
+
onColumnDropped(event: CdkDragDrop<any>, pinned: 'left' | 'right' | 'none'): void {
|
|
1608
|
+
const col = event.item.data as Column;
|
|
1609
|
+
if (!col) return;
|
|
727
1610
|
|
|
728
|
-
|
|
729
|
-
moveItemInArray(currentContainerData, event.previousIndex, event.currentIndex);
|
|
730
|
-
} else {
|
|
731
|
-
transferArrayItem(
|
|
732
|
-
previousContainerData,
|
|
733
|
-
currentContainerData,
|
|
734
|
-
event.previousIndex,
|
|
735
|
-
event.currentIndex
|
|
736
|
-
);
|
|
1611
|
+
const targetPinned = pinned === 'none' ? false : pinned;
|
|
737
1612
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const colDef = this.getColumnDefForColumn(movedCol);
|
|
741
|
-
if (colDef) {
|
|
742
|
-
colDef.pinned = pinType === 'none' ? null : pinType as any;
|
|
743
|
-
}
|
|
1613
|
+
if (col.pinned !== targetPinned) {
|
|
1614
|
+
this.gridApi.setColumnPinned(col, targetPinned);
|
|
744
1615
|
}
|
|
745
1616
|
|
|
746
|
-
|
|
747
|
-
const orderedVisibleColDefs: (ColDef<TData> | ColGroupDef<TData>)[] = [];
|
|
748
|
-
[...left, ...center, ...right].forEach(col => {
|
|
749
|
-
const def = this.getColumnDefForColumn(col);
|
|
750
|
-
if (def) orderedVisibleColDefs.push(def);
|
|
751
|
-
});
|
|
1617
|
+
this.gridApi.moveColumn(col, event.currentIndex);
|
|
752
1618
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if ('children' in c) return false;
|
|
756
|
-
return (c as ColDef).hide;
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
const newDefs = [...orderedVisibleColDefs, ...hidden];
|
|
760
|
-
|
|
761
|
-
this.onColumnDefsChanged(newDefs);
|
|
1619
|
+
this.canvasRenderer?.render();
|
|
1620
|
+
this._cdr.detectChanges();
|
|
762
1621
|
}
|
|
763
1622
|
|
|
764
1623
|
// --- Column Resizing Logic ---
|
|
765
1624
|
|
|
766
|
-
isResizable(
|
|
767
|
-
if ('children' in
|
|
768
|
-
|
|
769
|
-
|
|
1625
|
+
isResizable(item: Column | ColumnGroup | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
1626
|
+
if ('children' in item) {
|
|
1627
|
+
return (item as any).children.some((child: any) => this.isResizable(child));
|
|
1628
|
+
}
|
|
1629
|
+
const colId = (item as any).colId || (item as any).field?.toString();
|
|
1630
|
+
if (colId === 'ag-Grid-SelectionColumn') return true;
|
|
1631
|
+
|
|
1632
|
+
const colDef = this.getColumnDefForColumn(item as any);
|
|
1633
|
+
return colDef && this.isColDef(colDef) ? colDef.resizable !== false : true;
|
|
770
1634
|
}
|
|
771
1635
|
|
|
772
|
-
onResizeMouseDown(event: MouseEvent,
|
|
1636
|
+
onResizeMouseDown(event: MouseEvent, item: Column | ColumnGroup): void {
|
|
773
1637
|
event.stopPropagation();
|
|
774
1638
|
event.preventDefault();
|
|
775
1639
|
|
|
776
1640
|
this.isResizing = true;
|
|
777
|
-
this.
|
|
1641
|
+
this.resizeItem = item;
|
|
778
1642
|
this.resizeStartX = event.clientX;
|
|
779
|
-
this.resizeStartWidth =
|
|
1643
|
+
this.resizeStartWidth = this.getItemWidth(item);
|
|
780
1644
|
|
|
781
1645
|
const mouseMoveHandler = (e: MouseEvent) => this.onResizeMouseMove(e);
|
|
782
1646
|
const mouseUpHandler = () => {
|
|
@@ -790,52 +1654,71 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
790
1654
|
}
|
|
791
1655
|
|
|
792
1656
|
private onResizeMouseMove(event: MouseEvent): void {
|
|
793
|
-
if (!this.isResizing || !this.
|
|
1657
|
+
if (!this.isResizing || !this.resizeItem) return;
|
|
794
1658
|
|
|
795
1659
|
const deltaX = event.clientX - this.resizeStartX;
|
|
796
1660
|
const newWidth = Math.max(20, this.resizeStartWidth + deltaX);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
this.resizeColumn.width = newWidth;
|
|
800
|
-
|
|
801
|
-
// Update original ColDef
|
|
802
|
-
const colDef = this.getColumnDefForColumn(this.resizeColumn);
|
|
803
|
-
if (colDef) {
|
|
804
|
-
colDef.width = newWidth;
|
|
805
|
-
}
|
|
1661
|
+
|
|
1662
|
+
this.applyResize(this.resizeItem!, newWidth);
|
|
806
1663
|
|
|
807
1664
|
// Force re-render
|
|
808
1665
|
this.canvasRenderer?.render();
|
|
809
|
-
this.
|
|
1666
|
+
this._cdr.detectChanges();
|
|
810
1667
|
}
|
|
811
1668
|
|
|
812
1669
|
private onResizeMouseUp(): void {
|
|
813
1670
|
this.isResizing = false;
|
|
814
|
-
this.
|
|
1671
|
+
this.resizeItem = null;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
private applyResize(item: Column | ColumnGroup, newWidth: number): void {
|
|
1675
|
+
if ('children' in item) {
|
|
1676
|
+
const currentWidth = this.getItemWidth(item);
|
|
1677
|
+
if (currentWidth === 0) return;
|
|
1678
|
+
|
|
1679
|
+
const ratio = newWidth / currentWidth;
|
|
1680
|
+
item.children.forEach((child) => {
|
|
1681
|
+
if (isColumnVisible(child)) {
|
|
1682
|
+
const childWidth = this.getItemWidth(child);
|
|
1683
|
+
this.applyResize(child, childWidth * ratio);
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
} else {
|
|
1687
|
+
const finalWidth = Math.floor(newWidth);
|
|
1688
|
+
(item as Column).width = finalWidth;
|
|
1689
|
+
const colDef = this.getColumnDefForColumn(item as Column);
|
|
1690
|
+
if (colDef && this.isColDef(colDef)) {
|
|
1691
|
+
colDef.width = finalWidth;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
815
1694
|
}
|
|
816
1695
|
|
|
817
1696
|
// --- Floating Filter Logic ---
|
|
818
1697
|
|
|
819
1698
|
hasFloatingFilters(): boolean {
|
|
820
1699
|
if (this.gridApi?.getGridOption('floatingFilter')) return true;
|
|
821
|
-
|
|
1700
|
+
if (this.gridOptions?.defaultColDef?.floatingFilter) return true;
|
|
1701
|
+
|
|
822
1702
|
if (!this.columnDefs) return false;
|
|
823
|
-
|
|
1703
|
+
const hasAny = this.columnDefs.some((col) => {
|
|
824
1704
|
if ('children' in col) {
|
|
825
|
-
return col.children.some(child =>
|
|
1705
|
+
return col.children.some((child) => (child as any).floatingFilter);
|
|
826
1706
|
}
|
|
827
|
-
return col.floatingFilter;
|
|
1707
|
+
return (col as any).floatingFilter;
|
|
828
1708
|
});
|
|
1709
|
+
return hasAny;
|
|
829
1710
|
}
|
|
830
1711
|
|
|
831
1712
|
isFloatingFilterEnabled(col: Column | ColDef<TData> | ColGroupDef<TData>): boolean {
|
|
832
1713
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
833
1714
|
if (!colDef || 'children' in colDef) return false;
|
|
834
|
-
|
|
835
|
-
|
|
1715
|
+
|
|
1716
|
+
const filter = colDef.filter;
|
|
1717
|
+
if (!filter) return false;
|
|
1718
|
+
|
|
836
1719
|
if (colDef.floatingFilter === true) return true;
|
|
837
1720
|
if (colDef.floatingFilter === false) return false;
|
|
838
|
-
|
|
1721
|
+
|
|
839
1722
|
return !!this.gridApi?.getGridOption('floatingFilter');
|
|
840
1723
|
}
|
|
841
1724
|
|
|
@@ -857,26 +1740,28 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
857
1740
|
private filterTimeout: any;
|
|
858
1741
|
onFloatingFilterInput(event: Event, col: Column | ColDef<TData> | ColGroupDef<TData>): void {
|
|
859
1742
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
860
|
-
if (!colDef ||
|
|
861
|
-
|
|
1743
|
+
if (!colDef || !this.isColDef(colDef)) return;
|
|
1744
|
+
|
|
862
1745
|
const input = event.target as HTMLInputElement;
|
|
863
1746
|
const value = input.value;
|
|
864
1747
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
865
1748
|
|
|
866
|
-
this.
|
|
1749
|
+
this._cdr.detectChanges(); // Update clear button visibility immediately
|
|
867
1750
|
|
|
868
1751
|
clearTimeout(this.filterTimeout);
|
|
869
1752
|
this.filterTimeout = setTimeout(() => {
|
|
870
1753
|
const currentModel = this.gridApi.getFilterModel();
|
|
871
|
-
|
|
872
|
-
|
|
1754
|
+
const existingFilter = (currentModel[colId] || {}) as any;
|
|
1755
|
+
|
|
1756
|
+
if (!value && existingFilter.type !== 'blank' && existingFilter.type !== 'notBlank') {
|
|
873
1757
|
delete currentModel[colId];
|
|
874
1758
|
} else {
|
|
875
1759
|
const filterType = this.getFilterTypeFromCol(colDef);
|
|
876
1760
|
currentModel[colId] = {
|
|
1761
|
+
...existingFilter,
|
|
877
1762
|
filterType: filterType as any,
|
|
878
|
-
type: filterType === 'text' ? 'contains' : 'equals',
|
|
879
|
-
filter: value
|
|
1763
|
+
type: existingFilter.type || (filterType === 'text' ? 'contains' : 'equals'),
|
|
1764
|
+
filter: value,
|
|
880
1765
|
};
|
|
881
1766
|
}
|
|
882
1767
|
|
|
@@ -900,25 +1785,31 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
900
1785
|
return model[colId]?.filter || '';
|
|
901
1786
|
}
|
|
902
1787
|
|
|
903
|
-
hasFilterValue(
|
|
1788
|
+
hasFilterValue(
|
|
1789
|
+
col: Column | ColDef<TData> | ColGroupDef<TData>,
|
|
1790
|
+
_input: HTMLInputElement
|
|
1791
|
+
): boolean {
|
|
904
1792
|
return !!this.getFloatingFilterValue(col);
|
|
905
1793
|
}
|
|
906
1794
|
|
|
907
|
-
clearFloatingFilter(
|
|
1795
|
+
clearFloatingFilter(
|
|
1796
|
+
col: Column | ColDef<TData> | ColGroupDef<TData>,
|
|
1797
|
+
input: HTMLInputElement
|
|
1798
|
+
): void {
|
|
908
1799
|
const colDef = this.getColumnDefForColumn(col as any);
|
|
909
|
-
if (!colDef ||
|
|
910
|
-
|
|
1800
|
+
if (!colDef || !this.isColDef(colDef)) return;
|
|
1801
|
+
|
|
911
1802
|
input.value = '';
|
|
912
1803
|
const colId = (col as any).colId || (col as any).field?.toString() || '';
|
|
913
|
-
|
|
1804
|
+
|
|
914
1805
|
const currentModel = this.gridApi.getFilterModel();
|
|
915
1806
|
delete currentModel[colId];
|
|
916
|
-
|
|
1807
|
+
|
|
917
1808
|
this.gridApi.setFilterModel(currentModel);
|
|
918
1809
|
this.canvasRenderer?.render();
|
|
919
|
-
this.
|
|
1810
|
+
this._cdr.detectChanges();
|
|
920
1811
|
}
|
|
921
|
-
|
|
1812
|
+
|
|
922
1813
|
// Public API methods
|
|
923
1814
|
getApi(): GridApi<TData> {
|
|
924
1815
|
return this.gridApi;
|
|
@@ -936,42 +1827,42 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
936
1827
|
startEditing(rowIndex: number, colId: string): void {
|
|
937
1828
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(rowIndex);
|
|
938
1829
|
const column = this.gridApi.getColumn(colId);
|
|
939
|
-
|
|
1830
|
+
|
|
940
1831
|
// Prevent editing on group rows or missing data/column
|
|
941
1832
|
if (!rowNode || rowNode.group || !column || !column.field) return;
|
|
942
|
-
|
|
1833
|
+
|
|
943
1834
|
// Check if cell is editable
|
|
944
1835
|
const colDef = this.getColumnDefForColumn(column);
|
|
945
|
-
if (colDef && colDef.editable === false) return;
|
|
946
|
-
|
|
1836
|
+
if (colDef && this.isColDef(colDef) && colDef.editable === false) return;
|
|
1837
|
+
|
|
947
1838
|
// If already editing another cell, stop it first
|
|
948
1839
|
if (this.isEditing) {
|
|
949
1840
|
this.stopEditing(true);
|
|
950
1841
|
}
|
|
951
1842
|
|
|
952
1843
|
const value = (rowNode.data as any)[column.field];
|
|
953
|
-
|
|
1844
|
+
|
|
954
1845
|
this.editingRowNode = rowNode;
|
|
955
1846
|
this.editingColDef = colDef;
|
|
956
1847
|
this.editingValue = value !== null && value !== undefined ? String(value) : '';
|
|
957
|
-
|
|
1848
|
+
|
|
958
1849
|
// Calculate editor position based on row and column
|
|
959
|
-
const columns = this.gridApi.getAllColumns().filter(c => c
|
|
1850
|
+
const columns = this.gridApi.getAllColumns().filter((c) => isColumnVisible(c));
|
|
960
1851
|
let x = 0;
|
|
961
1852
|
for (const col of columns) {
|
|
962
1853
|
if (col.colId === colId) break;
|
|
963
|
-
x += col.width;
|
|
1854
|
+
x += Math.floor(col.width || 150);
|
|
964
1855
|
}
|
|
965
|
-
|
|
1856
|
+
|
|
966
1857
|
this.editorPosition = {
|
|
967
1858
|
x: x - this.canvasRenderer.currentScrollLeft,
|
|
968
|
-
y:
|
|
969
|
-
width: column.width,
|
|
970
|
-
height: this.
|
|
1859
|
+
y: rowIndex * this.effectiveRowHeight - this.canvasRenderer.currentScrollTop,
|
|
1860
|
+
width: Math.floor(column.width),
|
|
1861
|
+
height: this.effectiveRowHeight,
|
|
971
1862
|
};
|
|
972
|
-
|
|
1863
|
+
|
|
973
1864
|
this.isEditing = true;
|
|
974
|
-
|
|
1865
|
+
|
|
975
1866
|
// Focus input after view update
|
|
976
1867
|
setTimeout(() => {
|
|
977
1868
|
if (this.editorInputRef) {
|
|
@@ -984,7 +1875,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
984
1875
|
|
|
985
1876
|
stopEditing(save: boolean = true): void {
|
|
986
1877
|
if (!this.isEditing) return;
|
|
987
|
-
|
|
1878
|
+
|
|
988
1879
|
const rowNode = this.editingRowNode;
|
|
989
1880
|
const colDef = this.editingColDef;
|
|
990
1881
|
|
|
@@ -1007,7 +1898,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1007
1898
|
data: rowNode.data,
|
|
1008
1899
|
node: rowNode,
|
|
1009
1900
|
colDef,
|
|
1010
|
-
api: this.gridApi
|
|
1901
|
+
api: this.gridApi,
|
|
1011
1902
|
});
|
|
1012
1903
|
}
|
|
1013
1904
|
|
|
@@ -1019,7 +1910,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1019
1910
|
data: rowNode.data,
|
|
1020
1911
|
node: rowNode,
|
|
1021
1912
|
colDef,
|
|
1022
|
-
api: this.gridApi
|
|
1913
|
+
api: this.gridApi,
|
|
1023
1914
|
});
|
|
1024
1915
|
} else if (field) {
|
|
1025
1916
|
// Default: update data directly
|
|
@@ -1028,9 +1919,9 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1028
1919
|
|
|
1029
1920
|
// Update via transaction
|
|
1030
1921
|
this.gridApi.applyTransaction({
|
|
1031
|
-
update: [rowNode.data]
|
|
1922
|
+
update: [rowNode.data],
|
|
1032
1923
|
});
|
|
1033
|
-
|
|
1924
|
+
|
|
1034
1925
|
// Trigger callback
|
|
1035
1926
|
if (colDef.onCellValueChanged) {
|
|
1036
1927
|
const column = this.gridApi.getColumn(colDef.colId || field || '');
|
|
@@ -1040,18 +1931,18 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1040
1931
|
oldValue,
|
|
1041
1932
|
data: rowNode.data,
|
|
1042
1933
|
node: rowNode,
|
|
1043
|
-
column
|
|
1934
|
+
column,
|
|
1044
1935
|
});
|
|
1045
1936
|
}
|
|
1046
1937
|
}
|
|
1047
|
-
|
|
1938
|
+
|
|
1048
1939
|
this.canvasRenderer?.render();
|
|
1049
1940
|
}
|
|
1050
|
-
|
|
1941
|
+
|
|
1051
1942
|
this.isEditing = false;
|
|
1052
1943
|
this.editingRowNode = null;
|
|
1053
1944
|
this.editingColDef = null;
|
|
1054
|
-
this.
|
|
1945
|
+
this._cdr.detectChanges();
|
|
1055
1946
|
}
|
|
1056
1947
|
|
|
1057
1948
|
onEditorInput(event: Event): void {
|
|
@@ -1070,7 +1961,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1070
1961
|
event.preventDefault();
|
|
1071
1962
|
const currentRowIndex = this.editingRowNode?.displayedRowIndex ?? -1;
|
|
1072
1963
|
const currentColId = this.editingColDef?.colId || this.editingColDef?.field?.toString() || '';
|
|
1073
|
-
|
|
1964
|
+
|
|
1074
1965
|
this.stopEditing(true);
|
|
1075
1966
|
|
|
1076
1967
|
// Standard AG Grid Tab behavior: move to next cell
|
|
@@ -1081,9 +1972,9 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1081
1972
|
}
|
|
1082
1973
|
|
|
1083
1974
|
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
|
-
|
|
1975
|
+
const columns = this.gridApi.getAllColumns().filter((c) => c.visible);
|
|
1976
|
+
const colIndex = columns.findIndex((c) => c.colId === colId);
|
|
1977
|
+
|
|
1087
1978
|
if (colIndex === -1) return;
|
|
1088
1979
|
|
|
1089
1980
|
let nextColIndex = backwards ? colIndex - 1 : colIndex + 1;
|
|
@@ -1109,48 +2000,60 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1109
2000
|
this.stopEditing(true);
|
|
1110
2001
|
}
|
|
1111
2002
|
}
|
|
1112
|
-
private getColumnDefForColumn(
|
|
2003
|
+
private getColumnDefForColumn(
|
|
2004
|
+
column: Column | ColumnGroup | ColDef<TData> | ColGroupDef<TData>
|
|
2005
|
+
): ColDef<TData> | ColGroupDef<TData> | null {
|
|
1113
2006
|
if (!this.columnDefs) return null;
|
|
1114
|
-
|
|
1115
|
-
const colId =
|
|
2007
|
+
|
|
2008
|
+
const colId =
|
|
2009
|
+
(column as any).colId || (column as any).field?.toString() || (column as any).groupId;
|
|
1116
2010
|
if (!colId) return null;
|
|
1117
2011
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
2012
|
+
const defaultColDef = this.gridOptions?.defaultColDef || {};
|
|
2013
|
+
|
|
2014
|
+
const findDef = (
|
|
2015
|
+
defs: (ColDef<TData> | ColGroupDef<TData>)[]
|
|
2016
|
+
): ColDef<TData> | ColGroupDef<TData> | null => {
|
|
2017
|
+
for (const def of defs) {
|
|
2018
|
+
const defId = (def as any).colId || (def as any).field?.toString() || (def as any).groupId;
|
|
2019
|
+
if (defId === colId) {
|
|
2020
|
+
return 'children' in def ? def : ({ ...defaultColDef, ...def } as ColDef<TData>);
|
|
2021
|
+
}
|
|
2022
|
+
if ('children' in def) {
|
|
2023
|
+
const found = findDef(def.children);
|
|
2024
|
+
if (found) return found;
|
|
1129
2025
|
}
|
|
1130
2026
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
2027
|
+
return null;
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
return findDef(this.columnDefs);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
private isColDef(def: any): def is ColDef<TData> {
|
|
2034
|
+
return def && !('children' in def);
|
|
1133
2035
|
}
|
|
1134
2036
|
|
|
1135
|
-
// Selection Methods
|
|
1136
2037
|
onRowClick(rowIndex: number, event: MouseEvent): void {
|
|
1137
2038
|
const rowNode = this.gridApi.getDisplayedRowAtIndex(rowIndex);
|
|
1138
2039
|
if (!rowNode) return;
|
|
1139
2040
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
rowNode.selected
|
|
2041
|
+
const selectionMode = this.gridApi.getGridOption('rowSelection') || 'single';
|
|
2042
|
+
const isMultiSelect =
|
|
2043
|
+
(selectionMode as any) === 'multiple' || (selectionMode as any) === 'multiRow';
|
|
2044
|
+
|
|
2045
|
+
if (isMultiSelect && (event.ctrlKey || event.metaKey)) {
|
|
2046
|
+
rowNode.setSelected(!rowNode.selected);
|
|
2047
|
+
} else if (isMultiSelect && event.shiftKey) {
|
|
2048
|
+
rowNode.setSelected(true);
|
|
1146
2049
|
} else {
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
2050
|
+
if (rowNode.selected) {
|
|
2051
|
+
rowNode.setSelected(false);
|
|
2052
|
+
} else {
|
|
2053
|
+
rowNode.setSelected(true, true);
|
|
2054
|
+
}
|
|
1150
2055
|
}
|
|
1151
2056
|
|
|
1152
|
-
this.updateSelectionState();
|
|
1153
|
-
this.canvasRenderer?.render();
|
|
1154
2057
|
this.selectionChanged.emit(this.gridApi.getSelectedRows());
|
|
1155
2058
|
}
|
|
1156
2059
|
|
|
@@ -1181,7 +2084,7 @@ export class ArgentGridComponent<TData = any> implements OnInit, OnDestroy, Afte
|
|
|
1181
2084
|
updateSelectionState(): void {
|
|
1182
2085
|
const selectedCount = this.gridApi.getSelectedRows().length;
|
|
1183
2086
|
const totalCount = this.gridApi.getDisplayedRowCount();
|
|
1184
|
-
|
|
2087
|
+
|
|
1185
2088
|
this.isAllSelected = selectedCount === totalCount && totalCount > 0;
|
|
1186
2089
|
this.isIndeterminateSelection = selectedCount > 0 && selectedCount < totalCount;
|
|
1187
2090
|
}
|