argent-grid 0.2.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/AGENTS.md +70 -27
- package/e2e/advanced.spec.ts +1 -1
- package/e2e/benchmark.spec.ts +7 -7
- 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 +1 -1
- package/e2e/visual.spec.ts +30 -9
- 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 +5 -5
- package/plan.md +30 -34
- package/src/lib/components/argent-grid.component.css +258 -549
- package/src/lib/components/argent-grid.component.html +272 -306
- package/src/lib/components/argent-grid.component.ts +585 -135
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +2 -2
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +7 -2
- package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
- package/src/lib/rendering/canvas-renderer.ts +177 -286
- package/src/lib/rendering/render/cells.ts +122 -5
- package/src/lib/rendering/render/column-utils.ts +27 -5
- package/src/lib/rendering/render/hit-test.ts +6 -11
- package/src/lib/rendering/render/index.ts +15 -6
- package/src/lib/rendering/render/lines.ts +12 -6
- package/src/lib/rendering/render/primitives.ts +269 -7
- package/src/lib/rendering/render/types.ts +2 -1
- package/src/lib/rendering/render/walk.ts +39 -19
- package/src/lib/services/grid.service.spec.ts +76 -0
- package/src/lib/services/grid.service.ts +451 -114
- package/src/lib/themes/theme-quartz.ts +2 -2
- package/src/lib/types/ag-grid-types.ts +500 -0
- package/src/stories/Advanced.stories.ts +78 -17
- package/src/stories/ArgentGrid.stories.ts +50 -26
- package/src/stories/Benchmark.stories.ts +17 -15
- package/src/stories/CellRenderers.stories.ts +205 -31
- package/src/stories/Filtering.stories.ts +56 -16
- package/src/stories/Grouping.stories.ts +86 -13
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +23 -10
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +69 -29
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- package/tsconfig.json +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { moveItemInArray } from '@angular/cdk/drag-drop';
|
|
1
2
|
import { Injectable } from '@angular/core';
|
|
2
3
|
import { Workbook } from 'exceljs';
|
|
3
4
|
import { Subject } from 'rxjs';
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
ColDef,
|
|
7
8
|
ColGroupDef,
|
|
8
9
|
Column,
|
|
10
|
+
ColumnGroup,
|
|
9
11
|
CsvExportParams,
|
|
10
12
|
ExcelExportParams,
|
|
11
13
|
FilterModel,
|
|
@@ -17,12 +19,15 @@ import {
|
|
|
17
19
|
IRowNode,
|
|
18
20
|
RowDataTransaction,
|
|
19
21
|
RowDataTransactionResult,
|
|
22
|
+
SortDirection,
|
|
20
23
|
SortModelItem,
|
|
21
24
|
} from '../types/ag-grid-types';
|
|
22
25
|
|
|
23
26
|
@Injectable()
|
|
24
27
|
export class GridService<TData = any> {
|
|
25
28
|
private columns: Map<string, Column> = new Map();
|
|
29
|
+
private columnGroups: ColumnGroup[] = [];
|
|
30
|
+
private headerDepth = 1;
|
|
26
31
|
private rowData: TData[] = [];
|
|
27
32
|
private rowNodes: Map<string, IRowNode<TData>> = new Map();
|
|
28
33
|
private displayedRowNodes: IRowNode<TData>[] = [];
|
|
@@ -35,7 +40,12 @@ export class GridService<TData = any> {
|
|
|
35
40
|
private cellRanges: CellRange[] = [];
|
|
36
41
|
private gridId: string = '';
|
|
37
42
|
private gridOptions: GridOptions<TData> | null = null;
|
|
38
|
-
public gridStateChanged$ = new Subject<{
|
|
43
|
+
public gridStateChanged$ = new Subject<{
|
|
44
|
+
type: string;
|
|
45
|
+
key?: string;
|
|
46
|
+
value?: any;
|
|
47
|
+
changedRowIndices?: number[];
|
|
48
|
+
}>();
|
|
39
49
|
|
|
40
50
|
// Row height cache
|
|
41
51
|
private cumulativeRowHeights: number[] = [];
|
|
@@ -62,7 +72,7 @@ export class GridService<TData = any> {
|
|
|
62
72
|
this.gridOptions = gridOptions ? { ...gridOptions } : {};
|
|
63
73
|
this.isPivotMode = !!this.gridOptions.pivotMode;
|
|
64
74
|
|
|
65
|
-
this.initializeColumns();
|
|
75
|
+
this.initializeColumns(true);
|
|
66
76
|
|
|
67
77
|
// Trigger initial pipeline run
|
|
68
78
|
this.applySorting();
|
|
@@ -76,6 +86,12 @@ export class GridService<TData = any> {
|
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
private hasCheckboxSelection(): boolean {
|
|
89
|
+
const rowSelection = this.gridOptions?.rowSelection;
|
|
90
|
+
const isMultiMode =
|
|
91
|
+
rowSelection === 'multiple' ||
|
|
92
|
+
(typeof rowSelection === 'object' && rowSelection.mode === 'multiRow');
|
|
93
|
+
|
|
94
|
+
if (isMultiMode) return true;
|
|
79
95
|
if (this.gridOptions?.selectionColumnDef?.checkboxes) return true;
|
|
80
96
|
if (!this.columnDefs) return false;
|
|
81
97
|
|
|
@@ -89,17 +105,22 @@ export class GridService<TData = any> {
|
|
|
89
105
|
return check(this.columnDefs);
|
|
90
106
|
}
|
|
91
107
|
|
|
92
|
-
private initializeColumns(): void {
|
|
108
|
+
private initializeColumns(clearColumns: boolean = true): void {
|
|
93
109
|
if (!this.columnDefs) {
|
|
94
110
|
return;
|
|
95
111
|
}
|
|
96
112
|
|
|
113
|
+
const existingColumns = clearColumns ? [] : Array.from(this.columns.values());
|
|
97
114
|
this.columns.clear();
|
|
115
|
+
this.columnGroups = [];
|
|
116
|
+
this.headerDepth = 1;
|
|
98
117
|
|
|
99
118
|
const groupColumns = this.getGroupColumns();
|
|
100
119
|
const isGrouping = groupColumns.length > 0;
|
|
101
120
|
const groupDisplayType = this.gridOptions?.groupDisplayType || 'singleColumn';
|
|
102
121
|
|
|
122
|
+
const topLevelColumns: (Column | ColumnGroup)[] = [];
|
|
123
|
+
|
|
103
124
|
// 1. Handle Selection Column
|
|
104
125
|
if (this.hasCheckboxSelection()) {
|
|
105
126
|
const selectionCol: Column = {
|
|
@@ -112,8 +133,10 @@ export class GridService<TData = any> {
|
|
|
112
133
|
sort: null,
|
|
113
134
|
checkboxSelection: true,
|
|
114
135
|
headerCheckboxSelection: true,
|
|
136
|
+
colIndex: 0,
|
|
115
137
|
};
|
|
116
138
|
this.columns.set(selectionCol.colId, selectionCol);
|
|
139
|
+
topLevelColumns.push(selectionCol);
|
|
117
140
|
}
|
|
118
141
|
|
|
119
142
|
// 2. Handle Auto Group Column (for singleColumn display)
|
|
@@ -132,89 +155,384 @@ export class GridService<TData = any> {
|
|
|
132
155
|
pinned: this.normalizePinned(autoGroupDef.pinned || 'left'),
|
|
133
156
|
visible: true,
|
|
134
157
|
sort: null,
|
|
158
|
+
colIndex: this.columns.size,
|
|
135
159
|
};
|
|
136
160
|
this.columns.set(autoGroupCol.colId, autoGroupCol);
|
|
161
|
+
topLevelColumns.push(autoGroupCol);
|
|
137
162
|
}
|
|
138
163
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
// 3. Process columns
|
|
165
|
+
let currentLeafIndex = topLevelColumns.length;
|
|
166
|
+
|
|
167
|
+
const processDefs = (
|
|
168
|
+
defs: (ColDef<TData> | ColGroupDef<TData>)[],
|
|
169
|
+
level: number,
|
|
170
|
+
parent?: ColumnGroup
|
|
171
|
+
): (Column | ColumnGroup)[] => {
|
|
172
|
+
return defs.map((def, index) => {
|
|
173
|
+
if ('children' in def) {
|
|
174
|
+
const groupId = def.groupId || `group-${index}-${level}`;
|
|
175
|
+
const group: ColumnGroup = {
|
|
176
|
+
groupId,
|
|
177
|
+
headerName: def.headerName,
|
|
178
|
+
children: [],
|
|
179
|
+
displayedChildren: [],
|
|
180
|
+
visible: true,
|
|
181
|
+
expanded: !!def.openByDefault,
|
|
182
|
+
parent,
|
|
183
|
+
level,
|
|
184
|
+
pinned: false,
|
|
185
|
+
columnGroupShow: def.columnGroupShow,
|
|
186
|
+
marryChildren: !!def.marryChildren,
|
|
187
|
+
colIndex: 0,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const startIndex = currentLeafIndex;
|
|
191
|
+
group.children = processDefs(def.children, level + 1, group);
|
|
192
|
+
group.colIndex = startIndex;
|
|
193
|
+
|
|
194
|
+
const firstPinned = group.children.find((c) => 'pinned' in c && c.pinned)?.pinned;
|
|
195
|
+
group.pinned = firstPinned || false;
|
|
196
|
+
|
|
197
|
+
this.columnGroups.push(group);
|
|
198
|
+
return group;
|
|
199
|
+
} else {
|
|
200
|
+
const mergedDef = { ...this.gridOptions?.defaultColDef, ...def };
|
|
201
|
+
const colId = mergedDef.colId || mergedDef.field?.toString() || `col-${index}-${level}`;
|
|
202
|
+
|
|
203
|
+
// Preserve existing column if it exists to maintain width/order etc.
|
|
204
|
+
let column = existingColumns.find((c) => c.colId === colId);
|
|
205
|
+
|
|
206
|
+
let visible = !mergedDef.hide;
|
|
207
|
+
if (
|
|
208
|
+
isGrouping &&
|
|
209
|
+
mergedDef.rowGroup &&
|
|
210
|
+
visible &&
|
|
211
|
+
this.gridOptions?.groupHideOpenParents !== false
|
|
212
|
+
) {
|
|
213
|
+
visible = false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (this.isPivotMode && mergedDef.pivot && visible) {
|
|
217
|
+
visible = false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (column) {
|
|
221
|
+
column.visible = visible;
|
|
222
|
+
column.parent = parent;
|
|
223
|
+
column.colIndex = currentLeafIndex++;
|
|
224
|
+
return column;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
column = {
|
|
228
|
+
colId,
|
|
229
|
+
field: mergedDef.field?.toString(),
|
|
230
|
+
headerName: mergedDef.headerName,
|
|
231
|
+
width: mergedDef.width || 150,
|
|
232
|
+
minWidth: mergedDef.minWidth,
|
|
233
|
+
maxWidth: mergedDef.maxWidth,
|
|
234
|
+
pinned: this.normalizePinned(mergedDef.pinned),
|
|
235
|
+
visible: visible,
|
|
236
|
+
sort:
|
|
237
|
+
typeof mergedDef.sort === 'object' && mergedDef.sort !== null
|
|
238
|
+
? (mergedDef.sort as any).sort
|
|
239
|
+
: mergedDef.sort || null,
|
|
240
|
+
sortIndex: mergedDef.sortIndex ?? undefined,
|
|
241
|
+
aggFunc: typeof mergedDef.aggFunc === 'string' ? mergedDef.aggFunc : null,
|
|
242
|
+
checkboxSelection: !!mergedDef.checkboxSelection,
|
|
243
|
+
headerCheckboxSelection: !!mergedDef.headerCheckboxSelection,
|
|
244
|
+
filter: mergedDef.filter,
|
|
245
|
+
parent,
|
|
246
|
+
columnGroupShow: mergedDef.columnGroupShow,
|
|
247
|
+
colIndex: currentLeafIndex++,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
this.columns.set(colId, column);
|
|
251
|
+
return column;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const defsToProcess =
|
|
257
|
+
this.isPivotMode && this.pivotColumnDefs ? this.pivotColumnDefs : this.columnDefs || [];
|
|
258
|
+
|
|
259
|
+
topLevelColumns.push(...processDefs(defsToProcess, 0));
|
|
260
|
+
|
|
261
|
+
// 4. Add back any columns that were in existingColumns but not in defs
|
|
262
|
+
// (Only for normal columns, not internal Selection or AutoGroup columns
|
|
263
|
+
// which are handled in steps 1 and 2 above)
|
|
264
|
+
existingColumns.forEach((c) => {
|
|
265
|
+
if (
|
|
266
|
+
c.colId !== 'ag-Grid-SelectionColumn' &&
|
|
267
|
+
c.colId !== 'ag-Grid-AutoColumn' &&
|
|
268
|
+
!this.columns.has(c.colId)
|
|
269
|
+
) {
|
|
270
|
+
this.columns.set(c.colId, c);
|
|
156
271
|
}
|
|
157
272
|
});
|
|
158
|
-
}
|
|
159
273
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
): 'left' | 'right' | false {
|
|
163
|
-
if (pinned === 'left' || pinned === true) return 'left';
|
|
164
|
-
if (pinned === 'right') return 'right';
|
|
165
|
-
return false;
|
|
274
|
+
// 5. Calculate header depth
|
|
275
|
+
this.headerDepth = this.calculateHeaderDepth(topLevelColumns);
|
|
166
276
|
}
|
|
167
277
|
|
|
168
|
-
private
|
|
169
|
-
|
|
278
|
+
private calculateHeaderDepth(columns: (Column | ColumnGroup)[]): number {
|
|
279
|
+
let maxDepth = 0;
|
|
280
|
+
const walk = (items: (Column | ColumnGroup)[], depth: number) => {
|
|
281
|
+
maxDepth = Math.max(maxDepth, depth);
|
|
282
|
+
items.forEach((item) => {
|
|
283
|
+
if ('children' in item) {
|
|
284
|
+
walk(item.children, depth + 1);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
walk(columns, 1);
|
|
289
|
+
return maxDepth;
|
|
290
|
+
}
|
|
170
291
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
292
|
+
public getHeaderRows(): (Column | ColumnGroup)[][] {
|
|
293
|
+
const rows: (Column | ColumnGroup)[][] = [];
|
|
294
|
+
for (let i = 0; i < this.headerDepth; i++) {
|
|
295
|
+
rows[i] = [];
|
|
175
296
|
}
|
|
176
297
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
298
|
+
const walk = (items: (Column | ColumnGroup)[], level: number) => {
|
|
299
|
+
items.forEach((item) => {
|
|
300
|
+
rows[level].push(item);
|
|
301
|
+
if ('children' in item) {
|
|
302
|
+
walk(item.children, level + 1);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const topItems: (Column | ColumnGroup)[] = [];
|
|
181
308
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
visible = false;
|
|
309
|
+
if (this.hasCheckboxSelection()) {
|
|
310
|
+
topItems.push(this.columns.get('ag-Grid-SelectionColumn')!);
|
|
185
311
|
}
|
|
186
312
|
|
|
187
|
-
|
|
313
|
+
const isGrouping = this.getGroupColumns().length > 0;
|
|
314
|
+
const groupDisplayType = this.gridOptions?.groupDisplayType || 'singleColumn';
|
|
188
315
|
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
!def.rowGroup &&
|
|
192
|
-
!colId.startsWith('pivot_') &&
|
|
193
|
-
colId !== 'ag-Grid-AutoColumn'
|
|
316
|
+
isGrouping &&
|
|
317
|
+
(groupDisplayType === 'singleColumn' || !this.gridOptions?.groupDisplayType)
|
|
194
318
|
) {
|
|
195
|
-
|
|
319
|
+
topItems.push(this.columns.get('ag-Grid-AutoColumn')!);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const defsToProcess =
|
|
323
|
+
this.isPivotMode && this.pivotColumnDefs ? this.pivotColumnDefs : this.columnDefs || [];
|
|
324
|
+
|
|
325
|
+
const processTop = (
|
|
326
|
+
defs: (ColDef<TData> | ColGroupDef<TData>)[],
|
|
327
|
+
level: number
|
|
328
|
+
): (Column | ColumnGroup)[] => {
|
|
329
|
+
return defs.map((def, index) => {
|
|
330
|
+
if ('children' in def) {
|
|
331
|
+
const groupId = def.groupId || `group-${index}-${level}`;
|
|
332
|
+
return this.columnGroups.find((g) => g.groupId === groupId)!;
|
|
333
|
+
} else {
|
|
334
|
+
const colId = def.colId || def.field?.toString() || `col-${index}-${level}`;
|
|
335
|
+
return this.columns.get(colId)!;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
topItems.push(...processTop(defsToProcess, 0));
|
|
341
|
+
|
|
342
|
+
walk(topItems, 0);
|
|
343
|
+
return rows;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
public toggleColumnGroup(groupId: string, expanded: boolean): void {
|
|
347
|
+
const group = this.columnGroups.find((g) => g.groupId === groupId);
|
|
348
|
+
if (group) {
|
|
349
|
+
group.expanded = expanded;
|
|
350
|
+
this.gridStateChanged$.next({ type: 'columnGroupExpanded', key: groupId, value: expanded });
|
|
196
351
|
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public addRowGroupColumn(colId: string): void {
|
|
355
|
+
if (!this.columnDefs) return;
|
|
356
|
+
|
|
357
|
+
const updateDef = (defs: (ColDef<TData> | ColGroupDef<TData>)[]) => {
|
|
358
|
+
defs.forEach((def) => {
|
|
359
|
+
if ('children' in def) {
|
|
360
|
+
updateDef(def.children);
|
|
361
|
+
} else if (def.colId === colId || def.field === colId) {
|
|
362
|
+
def.rowGroup = true;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
this.expandedGroups.clear();
|
|
368
|
+
updateDef(this.columnDefs);
|
|
369
|
+
this.initializeColumns(false);
|
|
370
|
+
this.applyFiltering();
|
|
371
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
public removeRowGroupColumn(colId: string): void {
|
|
375
|
+
if (!this.columnDefs) return;
|
|
197
376
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
377
|
+
const updateDef = (defs: (ColDef<TData> | ColGroupDef<TData>)[]) => {
|
|
378
|
+
defs.forEach((def) => {
|
|
379
|
+
if ('children' in def) {
|
|
380
|
+
updateDef(def.children);
|
|
381
|
+
} else if (def.colId === colId || def.field === colId) {
|
|
382
|
+
def.rowGroup = false;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
this.expandedGroups.clear();
|
|
388
|
+
updateDef(this.columnDefs);
|
|
389
|
+
this.initializeColumns(false);
|
|
390
|
+
this.applyFiltering();
|
|
391
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
public setRowGroupColumns(colIds: string[]): void {
|
|
395
|
+
if (!this.columnDefs) return;
|
|
396
|
+
|
|
397
|
+
const updateDef = (defs: (ColDef<TData> | ColGroupDef<TData>)[]) => {
|
|
398
|
+
defs.forEach((def) => {
|
|
399
|
+
if ('children' in def) {
|
|
400
|
+
updateDef(def.children);
|
|
401
|
+
} else {
|
|
402
|
+
def.rowGroup = colIds.includes(def.colId || def.field?.toString() || '');
|
|
403
|
+
}
|
|
404
|
+
});
|
|
216
405
|
};
|
|
217
|
-
|
|
406
|
+
|
|
407
|
+
this.expandedGroups.clear();
|
|
408
|
+
updateDef(this.columnDefs);
|
|
409
|
+
this.initializeColumns(false);
|
|
410
|
+
this.applyFiltering();
|
|
411
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
public setColumnPinned(col: string | Column, pinned: 'left' | 'right' | boolean): void {
|
|
415
|
+
const colId = typeof col === 'string' ? col : col.colId;
|
|
416
|
+
const column = this.columns.get(colId);
|
|
417
|
+
if (column) {
|
|
418
|
+
column.pinned = this.normalizePinned(pinned);
|
|
419
|
+
|
|
420
|
+
// Also update ColDef
|
|
421
|
+
if (this.columnDefs) {
|
|
422
|
+
const updateDef = (defs: (ColDef<TData> | ColGroupDef<TData>)[]) => {
|
|
423
|
+
defs.forEach((def) => {
|
|
424
|
+
if ('children' in def) updateDef(def.children);
|
|
425
|
+
else if (def.colId === colId || def.field === colId) {
|
|
426
|
+
def.pinned = column.pinned === false ? null : column.pinned;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
updateDef(this.columnDefs);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.initializeColumns(false);
|
|
434
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
public moveColumn(col: string | Column, toIndex: number): void {
|
|
439
|
+
const colId = typeof col === 'string' ? col : col.colId;
|
|
440
|
+
const column = this.columns.get(colId);
|
|
441
|
+
|
|
442
|
+
if (column) {
|
|
443
|
+
const allCols = Array.from(this.columns.values());
|
|
444
|
+
const fromIdx = allCols.findIndex((c) => c.colId === colId);
|
|
445
|
+
|
|
446
|
+
// We want to find the target position relative to the section
|
|
447
|
+
const sectionCols = allCols.filter((c) => c.pinned === column.pinned);
|
|
448
|
+
const targetCol = sectionCols[toIndex];
|
|
449
|
+
|
|
450
|
+
let toIdx = -1;
|
|
451
|
+
if (targetCol) {
|
|
452
|
+
toIdx = allCols.findIndex((c) => c.colId === targetCol.colId);
|
|
453
|
+
} else {
|
|
454
|
+
// Drop at end of section
|
|
455
|
+
if (toIndex >= sectionCols.length) {
|
|
456
|
+
const lastInSection = sectionCols[sectionCols.length - 1];
|
|
457
|
+
if (lastInSection) {
|
|
458
|
+
toIdx = allCols.findIndex((c) => c.colId === lastInSection.colId);
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
const firstInSection = sectionCols[0];
|
|
462
|
+
if (firstInSection) {
|
|
463
|
+
toIdx = allCols.findIndex((c) => c.colId === firstInSection.colId);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (toIdx !== -1 && fromIdx !== toIdx) {
|
|
469
|
+
moveItemInArray(allCols, fromIdx, toIdx);
|
|
470
|
+
|
|
471
|
+
// Also move in columnDefs if present
|
|
472
|
+
if (this.columnDefs) {
|
|
473
|
+
const fromDefIdx = this.columnDefs.findIndex(
|
|
474
|
+
(d) => !('children' in d) && (d.colId === colId || d.field?.toString() === colId)
|
|
475
|
+
);
|
|
476
|
+
if (fromDefIdx !== -1 && targetCol) {
|
|
477
|
+
const toDefIdx = this.columnDefs.findIndex(
|
|
478
|
+
(d) =>
|
|
479
|
+
!('children' in d) &&
|
|
480
|
+
(d.colId === targetCol.colId || d.field?.toString() === targetCol.colId)
|
|
481
|
+
);
|
|
482
|
+
if (toDefIdx !== -1) {
|
|
483
|
+
moveItemInArray(this.columnDefs, fromDefIdx, toDefIdx);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
this.columns.clear();
|
|
489
|
+
allCols.forEach((c) => this.columns.set(c.colId, c));
|
|
490
|
+
|
|
491
|
+
this.initializeColumns(false);
|
|
492
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
public setColumnVisible(col: string | Column, visible: boolean): void {
|
|
498
|
+
const colId = typeof col === 'string' ? col : col.colId;
|
|
499
|
+
const column = this.columns.get(colId);
|
|
500
|
+
if (column) {
|
|
501
|
+
column.visible = visible;
|
|
502
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
public setColumnWidth(col: string | Column, width: number): void {
|
|
507
|
+
const colId = typeof col === 'string' ? col : col.colId;
|
|
508
|
+
const column = this.columns.get(colId);
|
|
509
|
+
if (column) {
|
|
510
|
+
column.width = Math.floor(width);
|
|
511
|
+
this.gridStateChanged$.next({ type: 'columnsChanged' });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
public setColumnSort(col: string | Column, sort: SortDirection, multiSort?: boolean): void {
|
|
516
|
+
const colId = typeof col === 'string' ? col : col.colId;
|
|
517
|
+
const column = this.columns.get(colId);
|
|
518
|
+
if (column) {
|
|
519
|
+
column.sort = sort;
|
|
520
|
+
if (!multiSort) {
|
|
521
|
+
this.columns.forEach((c) => {
|
|
522
|
+
if (c.colId !== colId) c.sort = null;
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
this.applySorting();
|
|
526
|
+
this.gridStateChanged$.next({ type: 'sortChanged' });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private normalizePinned(
|
|
531
|
+
pinned: boolean | 'left' | 'right' | null | undefined
|
|
532
|
+
): 'left' | 'right' | false {
|
|
533
|
+
if (pinned === 'left' || pinned === true) return 'left';
|
|
534
|
+
if (pinned === 'right') return 'right';
|
|
535
|
+
return false;
|
|
218
536
|
}
|
|
219
537
|
|
|
220
538
|
private getRowId(data: TData, index: number): string {
|
|
@@ -237,7 +555,7 @@ export class GridService<TData = any> {
|
|
|
237
555
|
setColumnDefs: (colDefs) => {
|
|
238
556
|
this.columnDefs = colDefs;
|
|
239
557
|
this.groupingDirty = true;
|
|
240
|
-
this.initializeColumns();
|
|
558
|
+
this.initializeColumns(true);
|
|
241
559
|
},
|
|
242
560
|
getColumn: (key) => {
|
|
243
561
|
const colId = typeof key === 'string' ? key : key.colId;
|
|
@@ -247,6 +565,12 @@ export class GridService<TData = any> {
|
|
|
247
565
|
getDisplayedRowAtIndex: (index) => {
|
|
248
566
|
return this.displayedRowNodes[index] || null;
|
|
249
567
|
},
|
|
568
|
+
getHeaderRows: () => this.getHeaderRows(),
|
|
569
|
+
getHeaderDepth: () => this.headerDepth,
|
|
570
|
+
getHeaderHeight: () => {
|
|
571
|
+
const headerHeight = this.gridOptions?.headerHeight || this.gridOptions?.rowHeight || 32;
|
|
572
|
+
return this.headerDepth * headerHeight;
|
|
573
|
+
},
|
|
250
574
|
|
|
251
575
|
// Row Data API
|
|
252
576
|
getRowData: () => [...this.filteredRowData],
|
|
@@ -301,11 +625,7 @@ export class GridService<TData = any> {
|
|
|
301
625
|
this.gridStateChanged$.next({ type: 'sortChanged' });
|
|
302
626
|
},
|
|
303
627
|
getSortModel: () => [...this.sortModel],
|
|
304
|
-
onSortChanged
|
|
305
|
-
this.applySorting();
|
|
306
|
-
this.applyFiltering(); // Re-filter and re-group after sort
|
|
307
|
-
this.gridStateChanged$.next({ type: 'sortChanged' });
|
|
308
|
-
},
|
|
628
|
+
// onSortChanged handled below
|
|
309
629
|
|
|
310
630
|
// Pagination API
|
|
311
631
|
paginationGetPageSize: () => 100,
|
|
@@ -392,7 +712,13 @@ export class GridService<TData = any> {
|
|
|
392
712
|
if (!this.gridOptions) {
|
|
393
713
|
this.gridOptions = {} as GridOptions<TData>;
|
|
394
714
|
}
|
|
715
|
+
if (this.gridOptions[key] === value) return;
|
|
395
716
|
this.gridOptions[key] = value;
|
|
717
|
+
|
|
718
|
+
if (key === 'rowHeight' || key === 'detailRowHeight') {
|
|
719
|
+
this.updateRowHeightCache();
|
|
720
|
+
}
|
|
721
|
+
|
|
396
722
|
this.gridStateChanged$.next({ type: 'optionChanged', key: key as string, value });
|
|
397
723
|
},
|
|
398
724
|
|
|
@@ -447,40 +773,6 @@ export class GridService<TData = any> {
|
|
|
447
773
|
},
|
|
448
774
|
|
|
449
775
|
// Column Operations
|
|
450
|
-
moveColumn: (column, toIndex) => {
|
|
451
|
-
// Basic implementation - reorder columns array
|
|
452
|
-
const cols = Array.from(this.columns.values());
|
|
453
|
-
const idx = cols.findIndex((c) => c.colId === column.colId);
|
|
454
|
-
if (idx !== -1) {
|
|
455
|
-
cols.splice(idx, 1);
|
|
456
|
-
cols.splice(toIndex, 0, column);
|
|
457
|
-
this.columns.clear();
|
|
458
|
-
cols.forEach((c) => {
|
|
459
|
-
this.columns.set(c.colId, c);
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
setColumnWidth: (column, width) => {
|
|
464
|
-
if (column) {
|
|
465
|
-
column.width = width;
|
|
466
|
-
}
|
|
467
|
-
},
|
|
468
|
-
setColumnPinned: (column, pinned) => {
|
|
469
|
-
if (column) {
|
|
470
|
-
column.pinned = pinned === true ? 'left' : pinned;
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
setColumnVisible: (column, visible) => {
|
|
474
|
-
if (column) {
|
|
475
|
-
column.visible = visible;
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
setColumnSort: (column, sort, multiSort) => {
|
|
479
|
-
if (column) {
|
|
480
|
-
column.sort = sort;
|
|
481
|
-
column.sortIndex = multiSort ? 0 : undefined;
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
776
|
autoSizeColumns: (colKeys) => {
|
|
485
777
|
// Basic implementation - set reasonable widths
|
|
486
778
|
colKeys.forEach((key) => {
|
|
@@ -566,12 +858,20 @@ export class GridService<TData = any> {
|
|
|
566
858
|
getValueColumns: () => {
|
|
567
859
|
return [];
|
|
568
860
|
},
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
861
|
+
toggleColumnGroup: (groupId, expanded) => this.toggleColumnGroup(groupId, expanded),
|
|
862
|
+
addRowGroupColumn: (colId) => this.addRowGroupColumn(colId),
|
|
863
|
+
removeRowGroupColumn: (colId) => this.removeRowGroupColumn(colId),
|
|
864
|
+
setRowGroupColumns: (colIds) => this.setRowGroupColumns(colIds),
|
|
865
|
+
getRowGroupColumns: () => this.getGroupColumns(),
|
|
572
866
|
getGroupDisplayType: () => {
|
|
573
867
|
return this.gridOptions?.groupDisplayType || 'singleColumn';
|
|
574
868
|
},
|
|
869
|
+
setColumnPinned: (col, pinned) => this.setColumnPinned(col, pinned),
|
|
870
|
+
moveColumn: (col, toIndex) => this.moveColumn(col, toIndex),
|
|
871
|
+
setColumnVisible: (col, visible) => this.setColumnVisible(col, visible),
|
|
872
|
+
setColumnWidth: (col, width) => this.setColumnWidth(col, width),
|
|
873
|
+
setColumnSort: (col, sort, multiSort) => this.setColumnSort(col, sort, multiSort),
|
|
874
|
+
onSortChanged: () => this.applySorting(),
|
|
575
875
|
|
|
576
876
|
// Tool Panels
|
|
577
877
|
setSideBarVisible: (_visible) => {
|
|
@@ -760,12 +1060,16 @@ export class GridService<TData = any> {
|
|
|
760
1060
|
};
|
|
761
1061
|
|
|
762
1062
|
let dataChanged = false;
|
|
1063
|
+
// Track changed row indices for efficient rendering
|
|
1064
|
+
const changedRowIndices: number[] = [];
|
|
763
1065
|
|
|
764
1066
|
if (transaction.add) {
|
|
1067
|
+
const startIndex = this.rowData.length;
|
|
765
1068
|
transaction.add.forEach((data, index) => {
|
|
766
1069
|
const _id = this.getRowId(data, this.rowData.length + index);
|
|
767
1070
|
this.rowData.push(data);
|
|
768
1071
|
dataChanged = true;
|
|
1072
|
+
changedRowIndices.push(startIndex + index);
|
|
769
1073
|
|
|
770
1074
|
// We'll create the actual node during the pipeline re-run
|
|
771
1075
|
// but we can return a placeholder result for now as AG Grid does
|
|
@@ -779,6 +1083,7 @@ export class GridService<TData = any> {
|
|
|
779
1083
|
if (index !== -1) {
|
|
780
1084
|
this.rowData[index] = data;
|
|
781
1085
|
dataChanged = true;
|
|
1086
|
+
changedRowIndices.push(index);
|
|
782
1087
|
|
|
783
1088
|
const existingNode = this.rowNodes.get(id);
|
|
784
1089
|
if (existingNode) {
|
|
@@ -799,6 +1104,8 @@ export class GridService<TData = any> {
|
|
|
799
1104
|
if (index !== -1) {
|
|
800
1105
|
const _removedData = this.rowData.splice(index, 1)[0];
|
|
801
1106
|
dataChanged = true;
|
|
1107
|
+
// For removes, we mark the row as changed but it's being deleted
|
|
1108
|
+
// The component will handle this appropriately
|
|
802
1109
|
|
|
803
1110
|
const node = this.rowNodes.get(id);
|
|
804
1111
|
if (node) {
|
|
@@ -823,7 +1130,11 @@ export class GridService<TData = any> {
|
|
|
823
1130
|
});
|
|
824
1131
|
}
|
|
825
1132
|
|
|
826
|
-
|
|
1133
|
+
// Emit transactionApplied with changed row indices for efficient rendering
|
|
1134
|
+
this.gridStateChanged$.next({
|
|
1135
|
+
type: 'transactionApplied',
|
|
1136
|
+
changedRowIndices,
|
|
1137
|
+
});
|
|
827
1138
|
}
|
|
828
1139
|
|
|
829
1140
|
return result;
|
|
@@ -1048,18 +1359,44 @@ export class GridService<TData = any> {
|
|
|
1048
1359
|
}
|
|
1049
1360
|
|
|
1050
1361
|
this.groupingDirty = false;
|
|
1362
|
+
|
|
1363
|
+
// Apply default expansion only on structural/data changes
|
|
1364
|
+
const defaultExpanded = this.gridOptions?.groupDefaultExpanded ?? 0;
|
|
1365
|
+
if (defaultExpanded !== 0) {
|
|
1366
|
+
this.applyDefaultExpansion(this.cachedGroupedData, 0, defaultExpanded);
|
|
1367
|
+
}
|
|
1051
1368
|
}
|
|
1052
1369
|
|
|
1053
|
-
// Re-initialize from cache (respects current expansion state)
|
|
1054
|
-
this.
|
|
1370
|
+
// Re-initialize from cache (respects current expansion state in this.expandedGroups)
|
|
1371
|
+
this.syncExpansionStateFromSet(this.cachedGroupedData);
|
|
1055
1372
|
this.initializeRowNodesFromGroupedData();
|
|
1056
1373
|
}
|
|
1057
1374
|
|
|
1058
|
-
private
|
|
1375
|
+
private applyDefaultExpansion(
|
|
1376
|
+
groupedData: (TData | GroupRowNode<TData>)[],
|
|
1377
|
+
level: number,
|
|
1378
|
+
defaultExpanded: number
|
|
1379
|
+
): void {
|
|
1380
|
+
const isDefaultExpanded = defaultExpanded === -1 || level < defaultExpanded;
|
|
1381
|
+
if (!isDefaultExpanded) return;
|
|
1382
|
+
|
|
1383
|
+
for (const item of groupedData) {
|
|
1384
|
+
if (this.isGroupRowNode(item)) {
|
|
1385
|
+
this.expandedGroups.add(item.id);
|
|
1386
|
+
if (item.children) {
|
|
1387
|
+
this.applyDefaultExpansion(item.children, level + 1, defaultExpanded);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
private syncExpansionStateFromSet(groupedData: (TData | GroupRowNode<TData>)[]): void {
|
|
1059
1394
|
for (const item of groupedData) {
|
|
1060
1395
|
if (this.isGroupRowNode(item)) {
|
|
1061
1396
|
item.expanded = this.expandedGroups.has(item.id);
|
|
1062
|
-
|
|
1397
|
+
if (item.children) {
|
|
1398
|
+
this.syncExpansionStateFromSet(item.children);
|
|
1399
|
+
}
|
|
1063
1400
|
}
|
|
1064
1401
|
}
|
|
1065
1402
|
}
|