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.
Files changed (55) hide show
  1. package/AGENTS.md +70 -27
  2. package/e2e/advanced.spec.ts +1 -1
  3. package/e2e/benchmark.spec.ts +7 -7
  4. package/e2e/cell-renderers.spec.ts +152 -0
  5. package/e2e/debug-streaming.spec.ts +31 -0
  6. package/e2e/dnd.spec.ts +73 -0
  7. package/e2e/screenshots.spec.ts +1 -1
  8. package/e2e/visual.spec.ts +30 -9
  9. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  10. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  11. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  12. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  13. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  14. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  15. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  16. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  17. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  18. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  19. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  20. package/package.json +5 -5
  21. package/plan.md +30 -34
  22. package/src/lib/components/argent-grid.component.css +258 -549
  23. package/src/lib/components/argent-grid.component.html +272 -306
  24. package/src/lib/components/argent-grid.component.ts +585 -135
  25. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  26. package/src/lib/components/argent-grid.selection.spec.ts +2 -2
  27. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  28. package/src/lib/components/set-filter/set-filter.component.ts +7 -2
  29. package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
  30. package/src/lib/rendering/canvas-renderer.ts +177 -286
  31. package/src/lib/rendering/render/cells.ts +122 -5
  32. package/src/lib/rendering/render/column-utils.ts +27 -5
  33. package/src/lib/rendering/render/hit-test.ts +6 -11
  34. package/src/lib/rendering/render/index.ts +15 -6
  35. package/src/lib/rendering/render/lines.ts +12 -6
  36. package/src/lib/rendering/render/primitives.ts +269 -7
  37. package/src/lib/rendering/render/types.ts +2 -1
  38. package/src/lib/rendering/render/walk.ts +39 -19
  39. package/src/lib/services/grid.service.spec.ts +76 -0
  40. package/src/lib/services/grid.service.ts +451 -114
  41. package/src/lib/themes/theme-quartz.ts +2 -2
  42. package/src/lib/types/ag-grid-types.ts +500 -0
  43. package/src/stories/Advanced.stories.ts +78 -17
  44. package/src/stories/ArgentGrid.stories.ts +50 -26
  45. package/src/stories/Benchmark.stories.ts +17 -15
  46. package/src/stories/CellRenderers.stories.ts +205 -31
  47. package/src/stories/Filtering.stories.ts +56 -16
  48. package/src/stories/Grouping.stories.ts +86 -13
  49. package/src/stories/Streaming.stories.ts +57 -0
  50. package/src/stories/Theming.stories.ts +23 -10
  51. package/src/stories/Tooltips.stories.ts +381 -0
  52. package/src/stories/benchmark-wrapper.component.ts +69 -29
  53. package/src/stories/story-utils.ts +88 -0
  54. package/src/stories/streaming-wrapper.component.ts +441 -0
  55. package/tsconfig.json +1 -0
@@ -2,6 +2,14 @@ import { BrowserModule } from '@angular/platform-browser';
2
2
  import type { Meta, StoryObj } from '@storybook/angular';
3
3
  import { moduleMetadata } from '@storybook/angular';
4
4
  import { ArgentGridComponent, ArgentGridModule, themeQuartz } from '../public-api';
5
+ import {
6
+ departmentValueFormatter,
7
+ locationValueFormatter,
8
+ roleValueFormatter,
9
+ STORY_DEPARTMENTS,
10
+ STORY_LOCATIONS,
11
+ STORY_ROLES,
12
+ } from './story-utils';
5
13
 
6
14
  interface Employee {
7
15
  id: number;
@@ -30,9 +38,9 @@ export default meta;
30
38
  type Story = StoryObj<ArgentGridComponent<Employee>>;
31
39
 
32
40
  function generateStaticData(count: number): Employee[] {
33
- const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance'];
34
- const roles = ['Engineer', 'Manager', 'Director', 'VP', 'Intern'];
35
- const locations = ['New York', 'San Francisco', 'London', 'Singapore', 'Remote'];
41
+ const departments = STORY_DEPARTMENTS;
42
+ const roles = STORY_ROLES;
43
+ const locations = STORY_LOCATIONS;
36
44
 
37
45
  return Array.from({ length: count }, (_, i) => ({
38
46
  id: i + 1,
@@ -50,14 +58,32 @@ export const SideBar: Story = {
50
58
  columnDefs: [
51
59
  { field: 'id', headerName: 'ID', width: 80, filter: true },
52
60
  { field: 'name', headerName: 'Name', width: 200, filter: true },
53
- { field: 'department', headerName: 'Department', width: 180, filter: 'set' },
54
- { field: 'role', headerName: 'Role', width: 250, filter: true },
61
+ {
62
+ field: 'department',
63
+ headerName: 'Department',
64
+ width: 180,
65
+ filter: 'set',
66
+ valueFormatter: departmentValueFormatter,
67
+ },
68
+ {
69
+ field: 'role',
70
+ headerName: 'Role',
71
+ width: 250,
72
+ filter: true,
73
+ valueFormatter: roleValueFormatter,
74
+ },
55
75
  { field: 'salary', headerName: 'Salary', width: 120, filter: 'number' },
56
- { field: 'location', headerName: 'Location', width: 150, filter: 'set' },
76
+ {
77
+ field: 'location',
78
+ headerName: 'Location',
79
+ width: 150,
80
+ filter: 'set',
81
+ valueFormatter: locationValueFormatter,
82
+ },
57
83
  { field: 'performance', headerName: 'Performance', width: 120, filter: 'number' },
58
84
  ],
59
85
  rowData: generateStaticData(50),
60
- height: '500px',
86
+ height: 'calc(100vh - 60px)',
61
87
  width: '100%',
62
88
  theme: themeQuartz,
63
89
  gridOptions: {
@@ -96,12 +122,24 @@ export const SideBarDefault: Story = {
96
122
  columnDefs: [
97
123
  { field: 'id', headerName: 'ID', width: 80, filter: true },
98
124
  { field: 'name', headerName: 'Name', width: 200, filter: true },
99
- { field: 'department', headerName: 'Department', width: 180, filter: 'set' },
100
- { field: 'role', headerName: 'Role', width: 250, filter: true },
125
+ {
126
+ field: 'department',
127
+ headerName: 'Department',
128
+ width: 180,
129
+ filter: 'set',
130
+ valueFormatter: departmentValueFormatter,
131
+ },
132
+ {
133
+ field: 'role',
134
+ headerName: 'Role',
135
+ width: 250,
136
+ filter: true,
137
+ valueFormatter: roleValueFormatter,
138
+ },
101
139
  { field: 'salary', headerName: 'Salary', width: 120, filter: 'number' },
102
140
  ],
103
141
  rowData: generateStaticData(50),
104
- height: '500px',
142
+ height: 'calc(100vh - 60px)',
105
143
  width: '100%',
106
144
  theme: themeQuartz,
107
145
  gridOptions: {
@@ -122,13 +160,23 @@ export const RangeSelection: Story = {
122
160
  columnDefs: [
123
161
  { field: 'id', headerName: 'ID', width: 80 },
124
162
  { field: 'name', headerName: 'Name', width: 200 },
125
- { field: 'department', headerName: 'Department', width: 180 },
126
- { field: 'role', headerName: 'Role', width: 250 },
163
+ {
164
+ field: 'department',
165
+ headerName: 'Department',
166
+ width: 180,
167
+ valueFormatter: departmentValueFormatter,
168
+ },
169
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
127
170
  { field: 'salary', headerName: 'Salary', width: 120 },
128
- { field: 'location', headerName: 'Location', width: 150 },
171
+ {
172
+ field: 'location',
173
+ headerName: 'Location',
174
+ width: 150,
175
+ valueFormatter: locationValueFormatter,
176
+ },
129
177
  ],
130
178
  rowData: generateStaticData(50),
131
- height: '400px',
179
+ height: 'calc(100vh - 60px)',
132
180
  width: '100%',
133
181
  theme: themeQuartz,
134
182
  gridOptions: {
@@ -157,13 +205,26 @@ export const FullFeatures: Story = {
157
205
  filter: 'set',
158
206
  sortable: true,
159
207
  rowGroup: true,
208
+ valueFormatter: departmentValueFormatter,
209
+ },
210
+ {
211
+ field: 'role',
212
+ headerName: 'Role',
213
+ width: 250,
214
+ filter: true,
215
+ valueFormatter: roleValueFormatter,
160
216
  },
161
- { field: 'role', headerName: 'Role', width: 250, filter: true },
162
217
  { field: 'salary', headerName: 'Salary', width: 120, filter: 'number', sortable: true },
163
- { field: 'location', headerName: 'Location', width: 150, filter: 'set' },
218
+ {
219
+ field: 'location',
220
+ headerName: 'Location',
221
+ width: 150,
222
+ filter: 'set',
223
+ valueFormatter: locationValueFormatter,
224
+ },
164
225
  ],
165
226
  rowData: generateStaticData(100),
166
- height: '500px',
227
+ height: 'calc(100vh - 60px)',
167
228
  width: '100%',
168
229
  theme: themeQuartz,
169
230
  gridOptions: {
@@ -1,6 +1,14 @@
1
1
  import type { Meta, StoryObj } from '@storybook/angular';
2
2
  import { moduleMetadata } from '@storybook/angular';
3
3
  import { ArgentGridComponent, ArgentGridModule, themeQuartz } from '../public-api';
4
+ import {
5
+ departmentValueFormatter,
6
+ locationValueFormatter,
7
+ roleValueFormatter,
8
+ STORY_DEPARTMENTS,
9
+ STORY_LOCATIONS,
10
+ STORY_ROLES,
11
+ } from './story-utils';
4
12
 
5
13
  interface Employee {
6
14
  id: number;
@@ -35,9 +43,9 @@ export default meta;
35
43
  type Story = StoryObj<ArgentGridComponent<Employee>>;
36
44
 
37
45
  function generateStaticData(count: number): Employee[] {
38
- const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance'];
39
- const roles = ['Engineer', 'Manager', 'Director', 'VP', 'Intern'];
40
- const locations = ['New York', 'San Francisco', 'London', 'Singapore', 'Remote'];
46
+ const departments = STORY_DEPARTMENTS;
47
+ const roles = STORY_ROLES;
48
+ const locations = STORY_LOCATIONS;
41
49
 
42
50
  return Array.from({ length: count }, (_, i) => ({
43
51
  id: i + 1,
@@ -56,15 +64,25 @@ export const Default: Story = {
56
64
  columnDefs: [
57
65
  { field: 'id', headerName: 'ID', width: 80 },
58
66
  { field: 'name', headerName: 'Name', width: 200 },
59
- { field: 'department', headerName: 'Department', width: 180 },
60
- { field: 'role', headerName: 'Role', width: 250 },
67
+ {
68
+ field: 'department',
69
+ headerName: 'Department',
70
+ width: 180,
71
+ valueFormatter: departmentValueFormatter,
72
+ },
73
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
61
74
  { field: 'salary', headerName: 'Salary', width: 120 },
62
- { field: 'location', headerName: 'Location', width: 150 },
75
+ {
76
+ field: 'location',
77
+ headerName: 'Location',
78
+ width: 180,
79
+ valueFormatter: locationValueFormatter,
80
+ },
63
81
  { field: 'startDate', headerName: 'Start Date', width: 130 },
64
82
  { field: 'performance', headerName: 'Performance', width: 120 },
65
83
  ],
66
84
  rowData: generateStaticData(100),
67
- height: '500px',
85
+ height: 'calc(100vh - 60px)',
68
86
  width: '100%',
69
87
  theme: themeQuartz,
70
88
  },
@@ -82,12 +100,17 @@ export const LargeDataset: Story = {
82
100
  columnDefs: [
83
101
  { field: 'id', headerName: 'ID', width: 80 },
84
102
  { field: 'name', headerName: 'Name', width: 200 },
85
- { field: 'department', headerName: 'Department', width: 180 },
86
- { field: 'role', headerName: 'Role', width: 250 },
103
+ {
104
+ field: 'department',
105
+ headerName: 'Department',
106
+ width: 180,
107
+ valueFormatter: departmentValueFormatter,
108
+ },
109
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
87
110
  { field: 'salary', headerName: 'Salary', width: 120 },
88
111
  ],
89
112
  rowData: generateStaticData(100000),
90
- height: '500px',
113
+ height: 'calc(100vh - 60px)',
91
114
  width: '100%',
92
115
  theme: themeQuartz,
93
116
  },
@@ -106,7 +129,7 @@ export const WithSorting: Story = {
106
129
  columnDefs: [
107
130
  {
108
131
  field: 'id',
109
- headerName: 'ID ↕️',
132
+ headerName: 'ID',
110
133
  width: 80,
111
134
  sortable: true,
112
135
  headerComponentParams: { sortIcon: '↕️' },
@@ -134,7 +157,7 @@ export const WithSorting: Story = {
134
157
  },
135
158
  ],
136
159
  rowData: generateStaticData(50),
137
- height: '400px',
160
+ height: 'calc(100vh - 60px)',
138
161
  width: '100%',
139
162
  theme: themeQuartz,
140
163
  },
@@ -151,20 +174,19 @@ export const WithSorting: Story = {
151
174
  export const WithSelection: Story = {
152
175
  args: {
153
176
  columnDefs: [
177
+ { field: 'id', headerName: 'ID', width: 80 },
178
+ { field: 'name', headerName: 'Name', width: 200 },
154
179
  {
155
- field: 'id',
156
- headerName: 'ID ☑️',
157
- width: 80,
158
- checkboxSelection: true,
159
- headerComponentParams: { selectionIcon: '☑️' },
180
+ field: 'department',
181
+ headerName: 'Department',
182
+ width: 180,
183
+ valueFormatter: departmentValueFormatter,
160
184
  },
161
- { field: 'name', headerName: 'Name', width: 200 },
162
- { field: 'department', headerName: 'Department', width: 180 },
163
- { field: 'role', headerName: 'Role', width: 250 },
185
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
164
186
  ],
165
187
  rowData: generateStaticData(50),
166
188
  rowSelection: 'multiple',
167
- height: '400px',
189
+ height: 'calc(100vh - 60px)',
168
190
  width: '100%',
169
191
  theme: themeQuartz,
170
192
  },
@@ -172,7 +194,7 @@ export const WithSelection: Story = {
172
194
  docs: {
173
195
  description: {
174
196
  story:
175
- '**Row selection with checkboxes**. The first column shows **☑️ checkboxes** in every row. **Click checkboxes** to select/deselect rows. **Header checkbox** selects/deselects all visible rows.',
197
+ '**Row selection with checkboxes**. Enabling `rowSelection` automatically adds a dedicated checkbox selection column. **Click checkboxes** to select/deselect rows. **Header checkbox** selects/deselects all visible rows.',
176
198
  },
177
199
  },
178
200
  },
@@ -183,7 +205,7 @@ export const WithFiltering: Story = {
183
205
  columnDefs: [
184
206
  {
185
207
  field: 'id',
186
- headerName: 'ID 🔢',
208
+ headerName: 'ID',
187
209
  width: 80,
188
210
  filter: 'number',
189
211
  floatingFilter: true,
@@ -204,6 +226,7 @@ export const WithFiltering: Story = {
204
226
  filter: 'set',
205
227
  floatingFilter: true,
206
228
  headerComponentParams: { filterIcon: '☑️' },
229
+ valueFormatter: departmentValueFormatter,
207
230
  },
208
231
  {
209
232
  field: 'role',
@@ -212,10 +235,11 @@ export const WithFiltering: Story = {
212
235
  filter: 'text',
213
236
  floatingFilter: true,
214
237
  headerComponentParams: { filterIcon: '🔤' },
238
+ valueFormatter: roleValueFormatter,
215
239
  },
216
240
  ],
217
241
  rowData: generateStaticData(50),
218
- height: '500px',
242
+ height: 'calc(100vh - 60px)',
219
243
  width: '100%',
220
244
  theme: themeQuartz,
221
245
  },
@@ -236,7 +260,7 @@ export const Empty: Story = {
236
260
  { field: 'name', headerName: 'Name', width: 200 },
237
261
  ],
238
262
  rowData: [],
239
- height: '300px',
263
+ height: 'calc(100vh - 60px)',
240
264
  width: '100%',
241
265
  theme: themeQuartz,
242
266
  },
@@ -258,7 +282,7 @@ export const WithCustomTheme: Story = {
258
282
  { field: 'salary', headerName: 'Salary', width: 120 },
259
283
  ],
260
284
  rowData: generateStaticData(50),
261
- height: '400px',
285
+ height: 'calc(100vh - 60px)',
262
286
  width: '100%',
263
287
  theme: themeQuartz.withParams({
264
288
  accentColor: '#ff5722', // Orange accent
@@ -18,56 +18,58 @@ const meta: Meta<BenchmarkWrapperComponent> = {
18
18
  export default meta;
19
19
  type Story = StoryObj<BenchmarkWrapperComponent>;
20
20
 
21
- export const Benchmark10K: Story = {
22
- args: {
23
- // Default 10K rows
24
- },
21
+ export const Benchmark100K: Story = {
22
+ args: {},
25
23
  render: (args) => ({
26
- props: args,
27
- template: `<app-benchmark-wrapper />`,
24
+ props: {
25
+ ...args,
26
+ rowCount: 100000,
27
+ } as any,
28
+ template: `<app-benchmark-wrapper [rowCount]="100000" />`,
28
29
  }),
29
30
  parameters: {
30
31
  docs: {
31
32
  description: {
32
33
  story:
33
- 'Benchmark with ~10,000 rows. Click "Run Benchmark" to test selection, grouping, and scroll performance.',
34
+ 'Benchmark with ~100,000 rows. Tests virtual scrolling performance with a large dataset.',
34
35
  },
35
36
  },
36
37
  },
37
38
  };
38
39
 
39
- export const Benchmark50K: Story = {
40
+ export const Benchmark500K: Story = {
40
41
  args: {},
41
42
  render: (args) => ({
42
43
  props: {
43
44
  ...args,
44
- rowCount: 50000,
45
+ rowCount: 500000,
45
46
  } as any,
46
- template: `<app-benchmark-wrapper [rowCount]="50000" />`,
47
+ template: `<app-benchmark-wrapper [rowCount]="500000" />`,
47
48
  }),
48
49
  parameters: {
49
50
  docs: {
50
51
  description: {
51
- story: 'Benchmark with ~50,000 rows. Stress tests virtual scrolling with large datasets.',
52
+ story:
53
+ 'Benchmark with ~500,000 rows. Heavy stress test for virtual scrolling and canvas rendering.',
52
54
  },
53
55
  },
54
56
  },
55
57
  };
56
58
 
57
- export const Benchmark100K: Story = {
59
+ export const Benchmark1M: Story = {
58
60
  args: {},
59
61
  render: (args) => ({
60
62
  props: {
61
63
  ...args,
62
- rowCount: 100000,
64
+ rowCount: 1000000,
63
65
  } as any,
64
- template: `<app-benchmark-wrapper [rowCount]="100000" />`,
66
+ template: `<app-benchmark-wrapper [rowCount]="1000000" />`,
65
67
  }),
66
68
  parameters: {
67
69
  docs: {
68
70
  description: {
69
71
  story:
70
- 'Benchmark with ~100,000 rows. High-performance stress test. Expect longer initial render but smooth scrolling.',
72
+ 'Benchmark with ~1,000,000 rows. Extreme stress test. Validates canvas renderer at maximum scale.',
71
73
  },
72
74
  },
73
75
  },