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.
Files changed (108) hide show
  1. package/.github/workflows/ci.yml +69 -0
  2. package/.github/workflows/pages.yml +6 -12
  3. package/.storybook/main.ts +20 -0
  4. package/.storybook/preview.ts +18 -0
  5. package/.storybook/tsconfig.json +24 -0
  6. package/AGENTS.md +2 -2
  7. package/README.md +51 -34
  8. package/angular.json +66 -0
  9. package/biome.json +66 -0
  10. package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
  11. package/docs/AG-GRID-COMPARISON.md +725 -0
  12. package/docs/CELL-RENDERER-GUIDE.md +241 -0
  13. package/docs/CONTEXT-MENU-GUIDE.md +371 -0
  14. package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
  15. package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
  16. package/docs/PERFORMANCE-REVIEW.md +571 -0
  17. package/docs/RESEARCH-STATUS.md +234 -0
  18. package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
  19. package/docs/STORYBOOK-REFACTOR.md +215 -0
  20. package/docs/STORYBOOK-STATUS.md +156 -0
  21. package/docs/TEST-COVERAGE-REPORT.md +276 -0
  22. package/docs/THEME-API-GUIDE.md +445 -0
  23. package/docs/THEME-API-PLAN.md +364 -0
  24. package/e2e/advanced.spec.ts +109 -0
  25. package/e2e/argentgrid.spec.ts +65 -0
  26. package/e2e/benchmark.spec.ts +52 -0
  27. package/e2e/screenshots.spec.ts +52 -0
  28. package/e2e/theming.spec.ts +35 -0
  29. package/e2e/visual.spec.ts +91 -0
  30. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  31. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  32. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  33. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  34. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  35. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  36. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  37. package/package.json +20 -6
  38. package/plan.md +50 -18
  39. package/playwright.config.ts +38 -0
  40. package/setup-vitest.ts +10 -13
  41. package/src/lib/argent-grid.module.ts +10 -12
  42. package/src/lib/components/argent-grid.component.css +327 -76
  43. package/src/lib/components/argent-grid.component.html +186 -64
  44. package/src/lib/components/argent-grid.component.spec.ts +120 -160
  45. package/src/lib/components/argent-grid.component.ts +642 -189
  46. package/src/lib/components/argent-grid.selection.spec.ts +132 -0
  47. package/src/lib/components/set-filter/set-filter.component.ts +302 -0
  48. package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
  49. package/src/lib/directives/click-outside.directive.ts +19 -0
  50. package/src/lib/rendering/canvas-renderer.spec.ts +366 -0
  51. package/src/lib/rendering/canvas-renderer.ts +418 -305
  52. package/src/lib/rendering/live-data-handler.ts +110 -0
  53. package/src/lib/rendering/live-data-optimizations.ts +133 -0
  54. package/src/lib/rendering/render/blit.spec.ts +16 -27
  55. package/src/lib/rendering/render/blit.ts +48 -36
  56. package/src/lib/rendering/render/cells.spec.ts +132 -0
  57. package/src/lib/rendering/render/cells.ts +46 -24
  58. package/src/lib/rendering/render/column-utils.ts +73 -0
  59. package/src/lib/rendering/render/hit-test.ts +55 -0
  60. package/src/lib/rendering/render/index.ts +79 -76
  61. package/src/lib/rendering/render/lines.ts +43 -43
  62. package/src/lib/rendering/render/primitives.ts +161 -0
  63. package/src/lib/rendering/render/theme.spec.ts +8 -12
  64. package/src/lib/rendering/render/theme.ts +7 -10
  65. package/src/lib/rendering/render/types.ts +2 -2
  66. package/src/lib/rendering/render/walk.spec.ts +35 -38
  67. package/src/lib/rendering/render/walk.ts +60 -50
  68. package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
  69. package/src/lib/rendering/utils/damage-tracker.ts +6 -18
  70. package/src/lib/rendering/utils/index.ts +1 -1
  71. package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
  72. package/src/lib/services/grid.service.spec.ts +1165 -201
  73. package/src/lib/services/grid.service.ts +819 -187
  74. package/src/lib/themes/parts/color-schemes.ts +132 -0
  75. package/src/lib/themes/parts/icon-sets.ts +258 -0
  76. package/src/lib/themes/theme-builder.ts +347 -0
  77. package/src/lib/themes/theme-quartz.ts +72 -0
  78. package/src/lib/themes/types.ts +238 -0
  79. package/src/lib/types/ag-grid-types.ts +73 -14
  80. package/src/public-api.ts +39 -9
  81. package/src/stories/Advanced.stories.ts +188 -0
  82. package/src/stories/ArgentGrid.stories.ts +277 -0
  83. package/src/stories/Benchmark.stories.ts +74 -0
  84. package/src/stories/CellRenderers.stories.ts +221 -0
  85. package/src/stories/Filtering.stories.ts +252 -0
  86. package/src/stories/Grouping.stories.ts +217 -0
  87. package/src/stories/Theming.stories.ts +124 -0
  88. package/src/stories/benchmark-wrapper.component.ts +315 -0
  89. package/tsconfig.storybook.json +10 -0
  90. package/vitest.config.ts +9 -9
  91. package/demo-app/README.md +0 -70
  92. package/demo-app/angular.json +0 -78
  93. package/demo-app/e2e/benchmark.spec.ts +0 -53
  94. package/demo-app/e2e/demo-page.spec.ts +0 -77
  95. package/demo-app/e2e/grid-features.spec.ts +0 -269
  96. package/demo-app/package-lock.json +0 -14023
  97. package/demo-app/package.json +0 -36
  98. package/demo-app/playwright-test-menu.js +0 -19
  99. package/demo-app/playwright.config.ts +0 -23
  100. package/demo-app/src/app/app.component.ts +0 -10
  101. package/demo-app/src/app/app.config.ts +0 -13
  102. package/demo-app/src/app/app.routes.ts +0 -7
  103. package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
  104. package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
  105. package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
  106. package/demo-app/src/index.html +0 -19
  107. package/demo-app/src/main.ts +0 -6
  108. 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?: number;
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
+ }
@@ -4,4 +4,4 @@
4
4
  * Exports all utility modules.
5
5
  */
6
6
 
7
- export * from './damage-tracker';
7
+ export * from './damage-tracker';
@@ -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
+ });