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
@@ -0,0 +1,57 @@
1
+ import type { Meta, StoryObj } from '@storybook/angular';
2
+ import { moduleMetadata } from '@storybook/angular';
3
+ import { StreamingWrapperComponent } from './streaming-wrapper.component';
4
+
5
+ const meta: Meta<StreamingWrapperComponent> = {
6
+ title: 'Features/Streaming',
7
+ component: StreamingWrapperComponent,
8
+ decorators: [
9
+ moduleMetadata({
10
+ imports: [StreamingWrapperComponent],
11
+ }),
12
+ ],
13
+ parameters: {
14
+ layout: 'fullscreen',
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<StreamingWrapperComponent>;
20
+
21
+ export const LiveStockFeed: Story = {
22
+ args: {
23
+ updateFrequency: 100,
24
+ batchSize: 5,
25
+ },
26
+ render: (args) => ({
27
+ props: args,
28
+ template: `<app-streaming-wrapper [updateFrequency]="updateFrequency" [batchSize]="batchSize" />`,
29
+ }),
30
+ parameters: {
31
+ docs: {
32
+ description: {
33
+ story:
34
+ 'Simulates a live stock market feed. Updates are buffered and applied via applyTransaction at most twice per second (500ms throttle) for efficient rendering. Demonstrates high-performance row updates with the canvas engine.',
35
+ },
36
+ },
37
+ },
38
+ };
39
+
40
+ export const HighFrequencyStream: Story = {
41
+ args: {
42
+ updateFrequency: 50,
43
+ batchSize: 10,
44
+ },
45
+ render: (args) => ({
46
+ props: args,
47
+ template: `<app-streaming-wrapper [updateFrequency]="updateFrequency" [batchSize]="batchSize" />`,
48
+ }),
49
+ parameters: {
50
+ docs: {
51
+ description: {
52
+ story:
53
+ 'Stresses the grid with 20 updates per second (10 rows each), totaling 200 row updates per second. All updates are buffered and applied in batched transactions every 500ms. Shows the efficiency of the transaction API and canvas renderer.',
54
+ },
55
+ },
56
+ },
57
+ };
@@ -3,6 +3,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
3
3
  import type { Meta, StoryObj } from '@storybook/angular';
4
4
  import { moduleMetadata } from '@storybook/angular';
5
5
  import { ArgentGridComponent, ArgentGridModule, colorSchemeDark, themeQuartz } from '../public-api';
6
+ import {
7
+ departmentValueFormatter,
8
+ locationValueFormatter,
9
+ roleValueFormatter,
10
+ STORY_DEPARTMENTS,
11
+ STORY_LOCATIONS,
12
+ STORY_ROLES,
13
+ } from './story-utils';
6
14
 
7
15
  interface Employee {
8
16
  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,
@@ -47,17 +55,22 @@ function generateStaticData(count: number): Employee[] {
47
55
  const columnDefs = [
48
56
  { field: 'id', headerName: 'ID', width: 80 },
49
57
  { field: 'name', headerName: 'Name', width: 200 },
50
- { field: 'department', headerName: 'Department', width: 180 },
51
- { field: 'role', headerName: 'Role', width: 250 },
58
+ {
59
+ field: 'department',
60
+ headerName: 'Department',
61
+ width: 180,
62
+ valueFormatter: departmentValueFormatter,
63
+ },
64
+ { field: 'role', headerName: 'Role', width: 250, valueFormatter: roleValueFormatter },
52
65
  { field: 'salary', headerName: 'Salary', width: 120 },
53
- { field: 'location', headerName: 'Location', width: 150 },
66
+ { field: 'location', headerName: 'Location', width: 150, valueFormatter: locationValueFormatter },
54
67
  ];
55
68
 
56
69
  export const LightMode: Story = {
57
70
  args: {
58
71
  columnDefs,
59
72
  rowData: generateStaticData(50),
60
- height: '400px',
73
+ height: 'calc(100vh - 60px)',
61
74
  width: '100%',
62
75
  theme: themeQuartz,
63
76
  },
@@ -74,7 +87,7 @@ export const DarkMode: Story = {
74
87
  args: {
75
88
  columnDefs,
76
89
  rowData: generateStaticData(50),
77
- height: '400px',
90
+ height: 'calc(100vh - 60px)',
78
91
  width: '100%',
79
92
  theme: themeQuartz.withPart(colorSchemeDark),
80
93
  },
@@ -91,7 +104,7 @@ export const CompactMode: Story = {
91
104
  args: {
92
105
  columnDefs,
93
106
  rowData: generateStaticData(50),
94
- height: '400px',
107
+ height: 'calc(100vh - 60px)',
95
108
  width: '100%',
96
109
  theme: themeQuartz.withParams({ rowHeight: 32, fontSize: 12, spacing: 4 }),
97
110
  },
@@ -108,7 +121,7 @@ export const CompactDarkMode: Story = {
108
121
  args: {
109
122
  columnDefs,
110
123
  rowData: generateStaticData(50),
111
- height: '400px',
124
+ height: 'calc(100vh - 60px)',
112
125
  width: '100%',
113
126
  theme: themeQuartz
114
127
  .withParams({ rowHeight: 32, fontSize: 12, spacing: 4 })
@@ -0,0 +1,381 @@
1
+ import { BrowserModule } from '@angular/platform-browser';
2
+ import type { Meta, StoryObj } from '@storybook/angular';
3
+ import { moduleMetadata } from '@storybook/angular';
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';
13
+
14
+ interface Employee {
15
+ id: number;
16
+ name: string;
17
+ department: string;
18
+ role: string;
19
+ salary: number;
20
+ location: string;
21
+ email: string;
22
+ phone: string;
23
+ startDate: string;
24
+ performance: number;
25
+ skills: string;
26
+ bio: string;
27
+ manager: string;
28
+ projects: number;
29
+ vacationDays: number;
30
+ }
31
+
32
+ const meta: Meta<ArgentGridComponent<Employee>> = {
33
+ title: 'Features/Tooltips',
34
+ component: ArgentGridComponent,
35
+ decorators: [
36
+ moduleMetadata({
37
+ imports: [ArgentGridModule, BrowserModule],
38
+ }),
39
+ ],
40
+ parameters: {
41
+ layout: 'fullscreen',
42
+ docs: {
43
+ description: {
44
+ component:
45
+ '**Cell Tooltips** — AG Grid-compatible `tooltipField` and `tooltipValueGetter` APIs. ' +
46
+ 'Hover over a cell for 500ms to reveal the tooltip overlay.',
47
+ },
48
+ },
49
+ },
50
+ };
51
+
52
+ export default meta;
53
+ type Story = StoryObj<ArgentGridComponent<Employee>>;
54
+
55
+ const departments = STORY_DEPARTMENTS;
56
+ const roles = STORY_ROLES;
57
+ const locations = STORY_LOCATIONS;
58
+ const skills: Record<string, string> = {
59
+ Engineering: 'TypeScript, Angular, RxJS, Canvas API, WebGL',
60
+ Sales: 'CRM, Negotiation, Salesforce, Pipeline Management',
61
+ Marketing: 'SEO, Content Strategy, Google Analytics, A/B Testing',
62
+ HR: 'Recruiting, HRIS, Onboarding, Conflict Resolution',
63
+ Finance: 'Excel, SQL, Financial Modeling, SAP',
64
+ Design: 'Figma, Sketch, CSS, User Research, Prototyping',
65
+ };
66
+ const managers: Record<string, string> = {
67
+ Engineering: 'Alice Chen',
68
+ Sales: 'Bob Martinez',
69
+ Marketing: 'Carol White',
70
+ HR: 'David Park',
71
+ Finance: 'Eve Johnson',
72
+ Design: 'Frank Lee',
73
+ };
74
+
75
+ function generateData(count: number): Employee[] {
76
+ return Array.from({ length: count }, (_, i) => {
77
+ const dept = departments[i % departments.length];
78
+ const role = roles[i % roles.length];
79
+ const location = locations[i % locations.length];
80
+ return {
81
+ id: i + 1,
82
+ name: `Employee ${i + 1}`,
83
+ department: dept,
84
+ role,
85
+ salary: 50000 + (i % 10) * 8000 + departments.indexOf(dept) * 5000,
86
+ location,
87
+ email: `employee${i + 1}@company.com`,
88
+ phone: `+1-555-${String(1000 + i).slice(1)}-${String(5000 + i).slice(1)}`,
89
+ startDate: `202${i % 5}-${String((i % 12) + 1).padStart(2, '0')}-${String((i % 28) + 1).padStart(2, '0')}`,
90
+ performance: 60 + (i % 41),
91
+ skills: skills[dept],
92
+ bio: `${role} in ${dept} based in ${location}. ${i % 2 === 0 ? 'Team lead for Q4 initiative.' : 'Contributing to cross-functional projects.'}`,
93
+ manager: managers[dept],
94
+ projects: 1 + (i % 8),
95
+ vacationDays: 10 + (i % 16),
96
+ };
97
+ });
98
+ }
99
+
100
+ const sampleData = generateData(50);
101
+
102
+ // ─── Story 1: tooltipField ────────────────────────────────────────────────────
103
+
104
+ export const TooltipField: Story = {
105
+ args: {
106
+ columnDefs: [
107
+ { field: 'id', headerName: 'ID', width: 70 },
108
+ {
109
+ field: 'name',
110
+ headerName: 'Name',
111
+ width: 180,
112
+ tooltipField: 'bio',
113
+ },
114
+ {
115
+ field: 'department',
116
+ headerName: 'Department',
117
+ width: 160,
118
+ tooltipField: 'skills',
119
+ valueFormatter: departmentValueFormatter,
120
+ },
121
+ {
122
+ field: 'role',
123
+ headerName: 'Role',
124
+ width: 140,
125
+ tooltipField: 'manager',
126
+ valueFormatter: roleValueFormatter,
127
+ },
128
+ {
129
+ field: 'salary',
130
+ headerName: 'Salary',
131
+ width: 110,
132
+ tooltipField: 'salary',
133
+ },
134
+ {
135
+ field: 'location',
136
+ headerName: 'Location',
137
+ width: 150,
138
+ tooltipField: 'email',
139
+ valueFormatter: locationValueFormatter,
140
+ },
141
+ ],
142
+ rowData: sampleData,
143
+ height: 'calc(100vh - 60px)',
144
+ width: '100%',
145
+ theme: themeQuartz,
146
+ },
147
+ parameters: {
148
+ docs: {
149
+ description: {
150
+ story:
151
+ '**`tooltipField`** — the simplest tooltip API. Specify any field name and ' +
152
+ 'its value will appear as the tooltip when hovering over cells in that column. ' +
153
+ 'Hover over **Name** to see the employee bio, **Department** to see their skill set, ' +
154
+ "or **Role** to see their manager's name.",
155
+ },
156
+ },
157
+ },
158
+ };
159
+
160
+ // ─── Story 2: tooltipValueGetter ─────────────────────────────────────────────
161
+
162
+ export const TooltipValueGetter: Story = {
163
+ args: {
164
+ columnDefs: [
165
+ { field: 'id', headerName: 'ID', width: 70 },
166
+ {
167
+ field: 'name',
168
+ headerName: 'Name',
169
+ width: 180,
170
+ tooltipValueGetter: ({ data }: { data: Employee }) =>
171
+ [
172
+ `👤 ${data.name}`,
173
+ `📧 ${data.email}`,
174
+ `📞 ${data.phone}`,
175
+ `🏢 Reports to: ${data.manager}`,
176
+ ].join('\n'),
177
+ },
178
+ {
179
+ field: 'department',
180
+ headerName: 'Department',
181
+ width: 160,
182
+ tooltipValueGetter: ({ data }: { data: Employee }) =>
183
+ `🏷 Department: ${data.department}\n🔧 Skills: ${data.skills}`,
184
+ valueFormatter: departmentValueFormatter,
185
+ },
186
+ {
187
+ field: 'salary',
188
+ headerName: 'Salary',
189
+ width: 110,
190
+ tooltipValueGetter: ({ value, data }: { value: number; data: Employee }) => {
191
+ const annual = value;
192
+ const monthly = Math.round(annual / 12).toLocaleString();
193
+ const daily = Math.round(annual / 260).toLocaleString();
194
+ return `💰 Annual: $${annual.toLocaleString()}\n📆 Monthly: $${monthly}\n📅 Daily: $${daily}`;
195
+ },
196
+ },
197
+ {
198
+ field: 'performance',
199
+ headerName: 'Performance',
200
+ width: 130,
201
+ tooltipValueGetter: ({ value, data }: { value: number; data: Employee }) => {
202
+ const stars = '★'.repeat(Math.round(value / 20)) + '☆'.repeat(5 - Math.round(value / 20));
203
+ const label =
204
+ value >= 90
205
+ ? 'Exceptional'
206
+ : value >= 75
207
+ ? 'Strong'
208
+ : value >= 60
209
+ ? 'Meets Expectations'
210
+ : 'Needs Improvement';
211
+ return `${stars}\n${value}/100 — ${label}\nProjects active: ${data.projects}`;
212
+ },
213
+ },
214
+ {
215
+ field: 'startDate',
216
+ headerName: 'Start Date',
217
+ width: 130,
218
+ tooltipValueGetter: ({ value, data }: { value: string; data: Employee }) => {
219
+ const start = new Date(value);
220
+ const now = new Date();
221
+ const months =
222
+ (now.getFullYear() - start.getFullYear()) * 12 + (now.getMonth() - start.getMonth());
223
+ const years = Math.floor(months / 12);
224
+ const rem = months % 12;
225
+ const tenure = years > 0 ? `${years}y ${rem}m` : `${rem} months`;
226
+ return `📅 Joined: ${value}\n⏱ Tenure: ${tenure}\n🏖 Vacation days: ${data.vacationDays}`;
227
+ },
228
+ },
229
+ ],
230
+ rowData: sampleData,
231
+ height: 'calc(100vh - 60px)',
232
+ width: '100%',
233
+ theme: themeQuartz,
234
+ },
235
+ parameters: {
236
+ docs: {
237
+ description: {
238
+ story:
239
+ '**`tooltipValueGetter`** — full control over tooltip content via a function. ' +
240
+ 'Receives `{ value, data, node, column }` and returns a string (newlines supported). ' +
241
+ 'Hover over **Name** for contact details, **Salary** for a pay breakdown, ' +
242
+ '**Performance** for a star-rating, or **Start Date** for tenure info.',
243
+ },
244
+ },
245
+ },
246
+ };
247
+
248
+ // ─── Story 3: Mixed (both APIs together) ─────────────────────────────────────
249
+
250
+ export const MixedTooltips: Story = {
251
+ args: {
252
+ columnDefs: [
253
+ { field: 'id', headerName: 'ID', width: 70 },
254
+ {
255
+ field: 'name',
256
+ headerName: 'Name',
257
+ width: 180,
258
+ // tooltipValueGetter takes priority over tooltipField
259
+ tooltipField: 'bio',
260
+ tooltipValueGetter: ({ data }: { data: Employee }) =>
261
+ `👤 ${data.name}\n📧 ${data.email}\n📞 ${data.phone}`,
262
+ },
263
+ {
264
+ field: 'department',
265
+ headerName: 'Department',
266
+ width: 160,
267
+ // plain tooltipField
268
+ tooltipField: 'skills',
269
+ valueFormatter: departmentValueFormatter,
270
+ },
271
+ {
272
+ field: 'salary',
273
+ headerName: 'Salary',
274
+ width: 110,
275
+ tooltipValueGetter: ({ value }: { value: number }) =>
276
+ `Monthly: $${Math.round(value / 12).toLocaleString()}`,
277
+ },
278
+ {
279
+ field: 'performance',
280
+ headerName: 'Performance',
281
+ width: 130,
282
+ // no tooltip — hovering shows nothing
283
+ },
284
+ {
285
+ field: 'location',
286
+ headerName: 'Location',
287
+ width: 150,
288
+ tooltipField: 'email',
289
+ valueFormatter: locationValueFormatter,
290
+ },
291
+ {
292
+ field: 'startDate',
293
+ headerName: 'Start Date',
294
+ width: 130,
295
+ tooltipField: 'manager',
296
+ },
297
+ ],
298
+ rowData: sampleData,
299
+ height: 'calc(100vh - 60px)',
300
+ width: '100%',
301
+ theme: themeQuartz,
302
+ },
303
+ parameters: {
304
+ docs: {
305
+ description: {
306
+ story:
307
+ '**Mixed usage** — some columns use `tooltipField`, some use `tooltipValueGetter`, ' +
308
+ 'and some have no tooltip at all. When both are set on the same column, ' +
309
+ '`tooltipValueGetter` takes priority (see **Name** column). ' +
310
+ '**Performance** intentionally has no tooltip.',
311
+ },
312
+ },
313
+ },
314
+ };
315
+
316
+ // ─── Story 4: Long / multi-line content ──────────────────────────────────────
317
+
318
+ export const LongTooltips: Story = {
319
+ args: {
320
+ columnDefs: [
321
+ { field: 'id', headerName: 'ID', width: 70 },
322
+ {
323
+ field: 'name',
324
+ headerName: 'Name',
325
+ width: 180,
326
+ tooltipValueGetter: ({ data }: { data: Employee }) =>
327
+ [
328
+ `Employee Profile`,
329
+ `─────────────────────`,
330
+ `Name: ${data.name}`,
331
+ `Role: ${data.role}`,
332
+ `Dept: ${data.department}`,
333
+ `Location: ${data.location}`,
334
+ `Manager: ${data.manager}`,
335
+ `Email: ${data.email}`,
336
+ `Phone: ${data.phone}`,
337
+ `Start Date: ${data.startDate}`,
338
+ `Projects: ${data.projects}`,
339
+ `Salary: $${data.salary.toLocaleString()}`,
340
+ `Performance:${data.performance}/100`,
341
+ `─────────────────────`,
342
+ data.bio,
343
+ ].join('\n'),
344
+ },
345
+ {
346
+ field: 'department',
347
+ headerName: 'Department',
348
+ width: 160,
349
+ tooltipValueGetter: ({ data }: { data: Employee }) =>
350
+ `Skills required for ${data.department}:\n\n${data.skills
351
+ .split(', ')
352
+ .map((s) => ` • ${s}`)
353
+ .join('\n')}`,
354
+ valueFormatter: departmentValueFormatter,
355
+ },
356
+ { field: 'role', headerName: 'Role', width: 140, valueFormatter: roleValueFormatter },
357
+ { field: 'salary', headerName: 'Salary', width: 110 },
358
+ { field: 'performance', headerName: 'Perf', width: 80 },
359
+ {
360
+ field: 'location',
361
+ headerName: 'Location',
362
+ width: 150,
363
+ valueFormatter: locationValueFormatter,
364
+ },
365
+ ],
366
+ rowData: sampleData,
367
+ height: 'calc(100vh - 60px)',
368
+ width: '100%',
369
+ theme: themeQuartz,
370
+ },
371
+ parameters: {
372
+ docs: {
373
+ description: {
374
+ story:
375
+ '**Multi-line / long tooltips** — hover over **Name** for a full employee profile card, ' +
376
+ 'or **Department** for a bulleted skills list. The tooltip supports `\\n` newlines and ' +
377
+ 'is capped at `max-width: 300px` with `white-space: pre-wrap`.',
378
+ },
379
+ },
380
+ },
381
+ };
@@ -1,6 +1,14 @@
1
1
  import { CommonModule } from '@angular/common';
2
2
  import { AfterViewInit, Component, Input, OnDestroy, ViewChild } from '@angular/core';
3
3
  import { ArgentGridComponent, ArgentGridModule, ColDef, GridApi, 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;
@@ -25,24 +33,35 @@ interface Employee {
25
33
  <span class="row-count">{{ rowCount | number }} rows</span>
26
34
  </div>
27
35
 
36
+ <div class="benchmark-info">
37
+ <strong>What does this benchmark measure?</strong>
38
+ <ul>
39
+ <li><b>Initial Render</b> — time to paint the first visible frame of the grid</li>
40
+ <li><b>Selection Update</b> — time to select and deselect all {{ rowCount | number }} rows</li>
41
+ <li><b>Grouping Toggle</b> — time to enable then revert row grouping on the Department column</li>
42
+ <li><b>Avg Scroll Frame</b> — average canvas render time across 30 scroll frames (100 px each)</li>
43
+ <li><b>Total</b> — total wall-clock time for the entire benchmark run</li>
44
+ </ul>
45
+ </div>
46
+
28
47
  <div class="results" *ngIf="results">
29
- <div class="result-item">
48
+ <div class="result-item" title="Time to paint the first visible frame of the grid">
30
49
  <span class="label">Initial Render:</span>
31
50
  <span class="value">{{ results.initialRender }}ms</span>
32
51
  </div>
33
- <div class="result-item">
52
+ <div class="result-item" title="Time to select and deselect all rows">
34
53
  <span class="label">Selection Update:</span>
35
54
  <span class="value">{{ results.selectionUpdateTime }}ms</span>
36
55
  </div>
37
- <div class="result-item">
56
+ <div class="result-item" title="Time to toggle row grouping on the Department column">
38
57
  <span class="label">Grouping Toggle:</span>
39
58
  <span class="value">{{ results.groupingUpdateTime }}ms</span>
40
59
  </div>
41
- <div class="result-item">
60
+ <div class="result-item" title="Average canvas render time per scroll frame (30 frames, 100px each)">
42
61
  <span class="label">Avg Scroll Frame:</span>
43
62
  <span class="value">{{ results.scrollFrameAverage }}ms</span>
44
63
  </div>
45
- <div class="result-item total">
64
+ <div class="result-item total" title="Total wall-clock time for the full benchmark run">
46
65
  <span class="label">Total:</span>
47
66
  <span class="value">{{ results.totalTime }}ms</span>
48
67
  </div>
@@ -118,25 +137,62 @@ interface Employee {
118
137
  .result-item.total .value {
119
138
  color: #059669;
120
139
  }
140
+ .benchmark-info {
141
+ padding: 10px 14px;
142
+ background: #eff6ff;
143
+ border: 1px solid #bfdbfe;
144
+ border-radius: 4px;
145
+ font-size: 13px;
146
+ color: #1e40af;
147
+ }
148
+ .benchmark-info strong {
149
+ display: block;
150
+ margin-bottom: 6px;
151
+ }
152
+ .benchmark-info ul {
153
+ margin: 0;
154
+ padding-left: 18px;
155
+ }
156
+ .benchmark-info li {
157
+ margin-bottom: 3px;
158
+ }
121
159
  `,
122
160
  ],
123
161
  })
124
162
  export class BenchmarkWrapperComponent implements AfterViewInit, OnDestroy {
125
163
  @ViewChild('grid') gridComponent!: ArgentGridComponent;
126
164
 
127
- @Input() rowCount = 10000;
165
+ @Input() rowCount = 100000;
128
166
 
129
167
  columnDefs: ColDef<Employee>[] = [
130
168
  { field: 'id', headerName: 'ID', width: 80, sortable: true },
131
169
  { field: 'name', headerName: 'Name', width: 200, sortable: true },
132
- { field: 'department', headerName: 'Department', width: 180, sortable: true },
133
- { field: 'role', headerName: 'Role', width: 250, filter: true },
170
+ {
171
+ field: 'department',
172
+ headerName: 'Department',
173
+ width: 180,
174
+ sortable: true,
175
+ valueFormatter: departmentValueFormatter,
176
+ },
177
+ {
178
+ field: 'role',
179
+ headerName: 'Role',
180
+ width: 250,
181
+ filter: true,
182
+ valueFormatter: roleValueFormatter,
183
+ },
134
184
  { field: 'salary', headerName: 'Salary', width: 120, sortable: true, filter: 'number' },
135
- { field: 'location', headerName: 'Location', width: 150, filter: true },
185
+ {
186
+ field: 'location',
187
+ headerName: 'Location',
188
+ width: 150,
189
+ filter: true,
190
+ valueFormatter: locationValueFormatter,
191
+ },
136
192
  ];
137
193
 
138
194
  rowData: Employee[] = [];
139
- height = '500px';
195
+ height = 'calc(100vh - 60px)';
140
196
  width = '100%';
141
197
  theme = themeQuartz;
142
198
 
@@ -167,25 +223,9 @@ export class BenchmarkWrapperComponent implements AfterViewInit, OnDestroy {
167
223
  }
168
224
 
169
225
  generateData(count: number): Employee[] {
170
- const departments = [
171
- 'Engineering',
172
- 'Sales',
173
- 'Marketing',
174
- 'HR',
175
- 'Finance',
176
- 'Operations',
177
- 'Support',
178
- ];
179
- const roles = ['Software Engineer', 'Manager', 'Director', 'VP', 'Intern', 'Analyst', 'Lead'];
180
- const locations = [
181
- 'New York',
182
- 'San Francisco',
183
- 'London',
184
- 'Singapore',
185
- 'Remote',
186
- 'Berlin',
187
- 'Tokyo',
188
- ];
226
+ const departments = STORY_DEPARTMENTS;
227
+ const roles = STORY_ROLES;
228
+ const locations = STORY_LOCATIONS;
189
229
 
190
230
  return Array.from({ length: count }, (_, i) => ({
191
231
  id: i + 1,