argent-grid 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +69 -0
- package/.github/workflows/pages.yml +6 -12
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/tsconfig.json +24 -0
- package/AGENTS.md +2 -2
- package/README.md +51 -34
- package/angular.json +66 -0
- package/biome.json +66 -0
- package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
- package/docs/AG-GRID-COMPARISON.md +725 -0
- package/docs/CELL-RENDERER-GUIDE.md +241 -0
- package/docs/CONTEXT-MENU-GUIDE.md +371 -0
- package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
- package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
- package/docs/PERFORMANCE-REVIEW.md +571 -0
- package/docs/RESEARCH-STATUS.md +234 -0
- package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
- package/docs/STORYBOOK-REFACTOR.md +215 -0
- package/docs/STORYBOOK-STATUS.md +156 -0
- package/docs/TEST-COVERAGE-REPORT.md +276 -0
- package/docs/THEME-API-GUIDE.md +445 -0
- package/docs/THEME-API-PLAN.md +364 -0
- package/e2e/advanced.spec.ts +109 -0
- package/e2e/argentgrid.spec.ts +65 -0
- package/e2e/benchmark.spec.ts +52 -0
- package/e2e/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +91 -0
- package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
- package/package.json +20 -6
- package/plan.md +50 -18
- package/playwright.config.ts +38 -0
- package/setup-vitest.ts +10 -13
- package/src/lib/argent-grid.module.ts +10 -12
- package/src/lib/components/argent-grid.component.css +327 -76
- package/src/lib/components/argent-grid.component.html +186 -64
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +642 -189
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.ts +302 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
- package/src/lib/directives/click-outside.directive.ts +19 -0
- package/src/lib/rendering/canvas-renderer.spec.ts +366 -0
- package/src/lib/rendering/canvas-renderer.ts +418 -305
- package/src/lib/rendering/live-data-handler.ts +110 -0
- package/src/lib/rendering/live-data-optimizations.ts +133 -0
- package/src/lib/rendering/render/blit.spec.ts +16 -27
- package/src/lib/rendering/render/blit.ts +48 -36
- package/src/lib/rendering/render/cells.spec.ts +132 -0
- package/src/lib/rendering/render/cells.ts +46 -24
- package/src/lib/rendering/render/column-utils.ts +73 -0
- package/src/lib/rendering/render/hit-test.ts +55 -0
- package/src/lib/rendering/render/index.ts +79 -76
- package/src/lib/rendering/render/lines.ts +43 -43
- package/src/lib/rendering/render/primitives.ts +161 -0
- package/src/lib/rendering/render/theme.spec.ts +8 -12
- package/src/lib/rendering/render/theme.ts +7 -10
- package/src/lib/rendering/render/types.ts +2 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +60 -50
- package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
- package/src/lib/rendering/utils/damage-tracker.ts +6 -18
- package/src/lib/rendering/utils/index.ts +1 -1
- package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
- package/src/lib/services/grid.service.spec.ts +1165 -201
- package/src/lib/services/grid.service.ts +819 -187
- package/src/lib/themes/parts/color-schemes.ts +132 -0
- package/src/lib/themes/parts/icon-sets.ts +258 -0
- package/src/lib/themes/theme-builder.ts +347 -0
- package/src/lib/themes/theme-quartz.ts +72 -0
- package/src/lib/themes/types.ts +238 -0
- package/src/lib/types/ag-grid-types.ts +73 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +188 -0
- package/src/stories/ArgentGrid.stories.ts +277 -0
- package/src/stories/Benchmark.stories.ts +74 -0
- package/src/stories/CellRenderers.stories.ts +221 -0
- package/src/stories/Filtering.stories.ts +252 -0
- package/src/stories/Grouping.stories.ts +217 -0
- package/src/stories/Theming.stories.ts +124 -0
- package/src/stories/benchmark-wrapper.component.ts +315 -0
- package/tsconfig.storybook.json +10 -0
- package/vitest.config.ts +9 -9
- package/demo-app/README.md +0 -70
- package/demo-app/angular.json +0 -78
- package/demo-app/e2e/benchmark.spec.ts +0 -53
- package/demo-app/e2e/demo-page.spec.ts +0 -77
- package/demo-app/e2e/grid-features.spec.ts +0 -269
- package/demo-app/package-lock.json +0 -14023
- package/demo-app/package.json +0 -36
- package/demo-app/playwright-test-menu.js +0 -19
- package/demo-app/playwright.config.ts +0 -23
- package/demo-app/src/app/app.component.ts +0 -10
- package/demo-app/src/app/app.config.ts +0 -13
- package/demo-app/src/app/app.routes.ts +0 -7
- package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
- package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
- package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
- package/demo-app/src/index.html +0 -19
- package/demo-app/src/main.ts +0 -6
- package/demo-app/tsconfig.json +0 -31
|
@@ -86,10 +86,7 @@ export class DamageTracker {
|
|
|
86
86
|
/**
|
|
87
87
|
* Mark dirty based on selection change
|
|
88
88
|
*/
|
|
89
|
-
markSelectionChanged(
|
|
90
|
-
oldSelection: Set<number>,
|
|
91
|
-
newSelection: Set<number>
|
|
92
|
-
): void {
|
|
89
|
+
markSelectionChanged(oldSelection: Set<number>, newSelection: Set<number>): void {
|
|
93
90
|
// Mark rows that changed selection state
|
|
94
91
|
for (const row of oldSelection) {
|
|
95
92
|
if (!newSelection.has(row)) {
|
|
@@ -186,7 +183,7 @@ export class DamageTracker {
|
|
|
186
183
|
* Get dirty cells
|
|
187
184
|
*/
|
|
188
185
|
getDirtyCells(): Array<[number, number]> {
|
|
189
|
-
return Array.from(this.damagedCells).map(key => {
|
|
186
|
+
return Array.from(this.damagedCells).map((key) => {
|
|
190
187
|
const [col, row] = key.split(',').map(Number);
|
|
191
188
|
return [col, row];
|
|
192
189
|
});
|
|
@@ -201,16 +198,8 @@ export class DamageTracker {
|
|
|
201
198
|
* - If too many individual cells, promote to row/column
|
|
202
199
|
* - If too many rows/columns, promote to full redraw
|
|
203
200
|
*/
|
|
204
|
-
optimize(thresholds: {
|
|
205
|
-
maxCells
|
|
206
|
-
maxRows?: number;
|
|
207
|
-
maxColumns?: number;
|
|
208
|
-
} = {}): void {
|
|
209
|
-
const {
|
|
210
|
-
maxCells = 50,
|
|
211
|
-
maxRows = 100,
|
|
212
|
-
maxColumns = 20,
|
|
213
|
-
} = thresholds;
|
|
201
|
+
optimize(thresholds: { maxCells?: number; maxRows?: number; maxColumns?: number } = {}): void {
|
|
202
|
+
const { maxCells = 50, maxRows = 100, maxColumns = 20 } = thresholds;
|
|
214
203
|
|
|
215
204
|
// If too many cells, check if we should promote to rows
|
|
216
205
|
if (this.damagedCells.size > maxCells) {
|
|
@@ -361,8 +350,7 @@ export function getDirtyBounds(
|
|
|
361
350
|
|
|
362
351
|
const width = columnWidths[col] || 0;
|
|
363
352
|
|
|
364
|
-
if (x + width >= 0 && x <= viewportWidth &&
|
|
365
|
-
y + rowHeight >= 0 && y <= viewportHeight) {
|
|
353
|
+
if (x + width >= 0 && x <= viewportWidth && y + rowHeight >= 0 && y <= viewportHeight) {
|
|
366
354
|
rects.push({
|
|
367
355
|
x: Math.max(0, x),
|
|
368
356
|
y: Math.max(0, y),
|
|
@@ -420,4 +408,4 @@ export function mergeRectangles(rects: Rectangle[]): Rectangle[] {
|
|
|
420
408
|
}
|
|
421
409
|
|
|
422
410
|
return merged;
|
|
423
|
-
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
|
|
2
|
+
import { TestBed } from '@angular/core/testing';
|
|
3
|
+
import { ColDef } from '../types/ag-grid-types';
|
|
4
|
+
import { GridService } from './grid.service';
|
|
5
|
+
|
|
6
|
+
describe('GridService - Set Filter', () => {
|
|
7
|
+
let service: GridService<any>;
|
|
8
|
+
|
|
9
|
+
const testColumnDefs: ColDef[] = [
|
|
10
|
+
{ field: 'id', headerName: 'ID', width: 100, filter: 'text' },
|
|
11
|
+
{ field: 'name', headerName: 'Name', width: 150, filter: 'text' },
|
|
12
|
+
{ field: 'department', headerName: 'Department', width: 150, filter: 'set' },
|
|
13
|
+
{ field: 'status', headerName: 'Status', width: 100, filter: 'set' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const testRowData = [
|
|
17
|
+
{ id: 1, name: 'John', department: 'Engineering', status: 'active' },
|
|
18
|
+
{ id: 2, name: 'Jane', department: 'Engineering', status: 'active' },
|
|
19
|
+
{ id: 3, name: 'Bob', department: 'Sales', status: 'inactive' },
|
|
20
|
+
{ id: 4, name: 'Alice', department: 'Sales', status: 'active' },
|
|
21
|
+
{ id: 5, name: 'Charlie', department: 'Marketing', status: 'inactive' },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
TestBed.configureTestingModule({
|
|
26
|
+
providers: [GridService, provideExperimentalZonelessChangeDetection()],
|
|
27
|
+
});
|
|
28
|
+
service = TestBed.inject(GridService);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('getUniqueValues', () => {
|
|
32
|
+
it('should return unique values for a field', () => {
|
|
33
|
+
service.createApi(testColumnDefs, [...testRowData]);
|
|
34
|
+
const values = service.getUniqueValues('department');
|
|
35
|
+
|
|
36
|
+
expect(values).toEqual(['Engineering', 'Marketing', 'Sales']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return sorted values', () => {
|
|
40
|
+
service.createApi(testColumnDefs, [...testRowData]);
|
|
41
|
+
const values = service.getUniqueValues('status');
|
|
42
|
+
|
|
43
|
+
expect(values).toEqual(['active', 'inactive']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should exclude null values', () => {
|
|
47
|
+
const dataWithNulls = [
|
|
48
|
+
{ id: 1, department: 'Engineering' },
|
|
49
|
+
{ id: 2, department: null },
|
|
50
|
+
{ id: 3, department: 'Sales' },
|
|
51
|
+
{ id: 4, department: undefined },
|
|
52
|
+
];
|
|
53
|
+
service.createApi(testColumnDefs, dataWithNulls);
|
|
54
|
+
const values = service.getUniqueValues('department');
|
|
55
|
+
|
|
56
|
+
expect(values).toEqual(['Engineering', 'Sales']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return empty array for non-existent field', () => {
|
|
60
|
+
service.createApi(testColumnDefs, [...testRowData]);
|
|
61
|
+
const values = service.getUniqueValues('nonExistent');
|
|
62
|
+
|
|
63
|
+
expect(values).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle numeric values', () => {
|
|
67
|
+
const numericData = [
|
|
68
|
+
{ id: 1, count: 10 },
|
|
69
|
+
{ id: 2, count: 20 },
|
|
70
|
+
{ id: 3, count: 10 },
|
|
71
|
+
{ id: 4, count: 30 },
|
|
72
|
+
];
|
|
73
|
+
service.createApi(testColumnDefs, numericData);
|
|
74
|
+
const values = service.getUniqueValues('count');
|
|
75
|
+
|
|
76
|
+
expect(values).toEqual([10, 20, 30]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('matchesSetFilter', () => {
|
|
81
|
+
it('should match value in filter array', () => {
|
|
82
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
83
|
+
|
|
84
|
+
// Apply set filter
|
|
85
|
+
api.setFilterModel({
|
|
86
|
+
department: { filterType: 'set', values: ['Engineering', 'Sales'] },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(api.getDisplayedRowCount()).toBe(4); // John, Jane, Bob, Alice
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should not match value not in filter array', () => {
|
|
93
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
94
|
+
|
|
95
|
+
// Apply set filter excluding Engineering
|
|
96
|
+
api.setFilterModel({
|
|
97
|
+
department: { filterType: 'set', values: ['Sales', 'Marketing'] },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(api.getDisplayedRowCount()).toBe(3); // Bob, Alice, Charlie
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return all rows when filter is empty array', () => {
|
|
104
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
105
|
+
|
|
106
|
+
// Empty filter = no filter applied
|
|
107
|
+
api.setFilterModel({
|
|
108
|
+
department: { filterType: 'set', values: [] },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(api.getDisplayedRowCount()).toBe(5); // All rows
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should work with numeric values', () => {
|
|
115
|
+
const numericData = [
|
|
116
|
+
{ id: 1, name: 'A', department: 'Eng', status: 10 },
|
|
117
|
+
{ id: 2, name: 'B', department: 'Eng', status: 20 },
|
|
118
|
+
{ id: 3, name: 'C', department: 'Eng', status: 30 },
|
|
119
|
+
];
|
|
120
|
+
const api = service.createApi(testColumnDefs, numericData);
|
|
121
|
+
|
|
122
|
+
api.setFilterModel({
|
|
123
|
+
status: { filterType: 'set', values: [10, 30] },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(api.getDisplayedRowCount()).toBe(2); // id 1 and 3
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle null/undefined values correctly', () => {
|
|
130
|
+
const dataWithNulls = [
|
|
131
|
+
{ id: 1, status: 'active' },
|
|
132
|
+
{ id: 2, status: null },
|
|
133
|
+
{ id: 3, status: 'inactive' },
|
|
134
|
+
{ id: 4, status: undefined },
|
|
135
|
+
];
|
|
136
|
+
const api = service.createApi(testColumnDefs, dataWithNulls);
|
|
137
|
+
|
|
138
|
+
api.setFilterModel({
|
|
139
|
+
status: { filterType: 'set', values: ['active'] },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(api.getDisplayedRowCount()).toBe(1); // Only active
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should combine with other filters', () => {
|
|
146
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
147
|
+
|
|
148
|
+
// Combine set filter with text filter
|
|
149
|
+
api.setFilterModel({
|
|
150
|
+
department: { filterType: 'set', values: ['Engineering', 'Sales'] },
|
|
151
|
+
name: { filterType: 'text', type: 'contains', filter: 'o' },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(api.getDisplayedRowCount()).toBe(2); // Bob (Sales) and John (Engineering)
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should update when filter changes', () => {
|
|
158
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
159
|
+
|
|
160
|
+
// Initial filter
|
|
161
|
+
api.setFilterModel({
|
|
162
|
+
department: { filterType: 'set', values: ['Engineering'] },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(api.getDisplayedRowCount()).toBe(2); // John, Jane
|
|
166
|
+
|
|
167
|
+
// Change filter
|
|
168
|
+
api.setFilterModel({
|
|
169
|
+
department: { filterType: 'set', values: ['Sales'] },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(api.getDisplayedRowCount()).toBe(2); // Bob, Alice
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('Set Filter API Integration', () => {
|
|
177
|
+
it('should get filter model with set filter', () => {
|
|
178
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
179
|
+
|
|
180
|
+
api.setFilterModel({
|
|
181
|
+
department: { filterType: 'set', values: ['Engineering'] },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const model = api.getFilterModel();
|
|
185
|
+
expect(model.department).toEqual({
|
|
186
|
+
filterType: 'set',
|
|
187
|
+
values: ['Engineering'],
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should clear set filter', () => {
|
|
192
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
193
|
+
|
|
194
|
+
api.setFilterModel({
|
|
195
|
+
department: { filterType: 'set', values: ['Engineering'] },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Clear filter
|
|
199
|
+
api.setFilterModel({});
|
|
200
|
+
|
|
201
|
+
const model = api.getFilterModel();
|
|
202
|
+
expect(model.department).toBeUndefined();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should work with getDisplayedRowCount', () => {
|
|
206
|
+
const api = service.createApi(testColumnDefs, [...testRowData]);
|
|
207
|
+
|
|
208
|
+
// No filter
|
|
209
|
+
expect(api.getDisplayedRowCount()).toBe(5);
|
|
210
|
+
|
|
211
|
+
// Apply set filter
|
|
212
|
+
api.setFilterModel({
|
|
213
|
+
department: { filterType: 'set', values: ['Engineering'] },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(api.getDisplayedRowCount()).toBe(2);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|