argent-grid 0.1.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 (122) 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 +70 -27
  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/cell-renderers.spec.ts +152 -0
  28. package/e2e/debug-streaming.spec.ts +31 -0
  29. package/e2e/dnd.spec.ts +73 -0
  30. package/e2e/screenshots.spec.ts +52 -0
  31. package/e2e/theming.spec.ts +35 -0
  32. package/e2e/visual.spec.ts +112 -0
  33. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  34. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  35. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  36. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  37. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  38. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  39. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  40. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  41. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  42. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  43. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  44. package/package.json +21 -7
  45. package/plan.md +56 -28
  46. package/playwright.config.ts +38 -0
  47. package/setup-vitest.ts +10 -13
  48. package/src/lib/argent-grid.module.ts +10 -12
  49. package/src/lib/components/argent-grid.component.css +281 -321
  50. package/src/lib/components/argent-grid.component.html +295 -207
  51. package/src/lib/components/argent-grid.component.spec.ts +120 -160
  52. package/src/lib/components/argent-grid.component.ts +1193 -290
  53. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  54. package/src/lib/components/argent-grid.selection.spec.ts +132 -0
  55. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  56. package/src/lib/components/set-filter/set-filter.component.ts +307 -0
  57. package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
  58. package/src/lib/directives/click-outside.directive.ts +19 -0
  59. package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
  60. package/src/lib/rendering/canvas-renderer.ts +456 -452
  61. package/src/lib/rendering/live-data-handler.ts +110 -0
  62. package/src/lib/rendering/live-data-optimizations.ts +133 -0
  63. package/src/lib/rendering/render/blit.spec.ts +16 -27
  64. package/src/lib/rendering/render/blit.ts +48 -36
  65. package/src/lib/rendering/render/cells.spec.ts +132 -0
  66. package/src/lib/rendering/render/cells.ts +167 -28
  67. package/src/lib/rendering/render/column-utils.ts +95 -0
  68. package/src/lib/rendering/render/hit-test.ts +50 -0
  69. package/src/lib/rendering/render/index.ts +88 -76
  70. package/src/lib/rendering/render/lines.ts +53 -47
  71. package/src/lib/rendering/render/primitives.ts +423 -0
  72. package/src/lib/rendering/render/theme.spec.ts +8 -12
  73. package/src/lib/rendering/render/theme.ts +7 -10
  74. package/src/lib/rendering/render/types.ts +3 -2
  75. package/src/lib/rendering/render/walk.spec.ts +35 -38
  76. package/src/lib/rendering/render/walk.ts +94 -64
  77. package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
  78. package/src/lib/rendering/utils/damage-tracker.ts +6 -18
  79. package/src/lib/rendering/utils/index.ts +1 -1
  80. package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
  81. package/src/lib/services/grid.service.spec.ts +1241 -201
  82. package/src/lib/services/grid.service.ts +1204 -235
  83. package/src/lib/themes/parts/color-schemes.ts +132 -0
  84. package/src/lib/themes/parts/icon-sets.ts +258 -0
  85. package/src/lib/themes/theme-builder.ts +347 -0
  86. package/src/lib/themes/theme-quartz.ts +72 -0
  87. package/src/lib/themes/types.ts +238 -0
  88. package/src/lib/types/ag-grid-types.ts +573 -14
  89. package/src/public-api.ts +39 -9
  90. package/src/stories/Advanced.stories.ts +249 -0
  91. package/src/stories/ArgentGrid.stories.ts +301 -0
  92. package/src/stories/Benchmark.stories.ts +76 -0
  93. package/src/stories/CellRenderers.stories.ts +395 -0
  94. package/src/stories/Filtering.stories.ts +292 -0
  95. package/src/stories/Grouping.stories.ts +290 -0
  96. package/src/stories/Streaming.stories.ts +57 -0
  97. package/src/stories/Theming.stories.ts +137 -0
  98. package/src/stories/Tooltips.stories.ts +381 -0
  99. package/src/stories/benchmark-wrapper.component.ts +355 -0
  100. package/src/stories/story-utils.ts +88 -0
  101. package/src/stories/streaming-wrapper.component.ts +441 -0
  102. package/tsconfig.json +1 -0
  103. package/tsconfig.storybook.json +10 -0
  104. package/vitest.config.ts +9 -9
  105. package/demo-app/README.md +0 -70
  106. package/demo-app/angular.json +0 -78
  107. package/demo-app/e2e/benchmark.spec.ts +0 -53
  108. package/demo-app/e2e/demo-page.spec.ts +0 -77
  109. package/demo-app/e2e/grid-features.spec.ts +0 -269
  110. package/demo-app/package-lock.json +0 -14023
  111. package/demo-app/package.json +0 -36
  112. package/demo-app/playwright-test-menu.js +0 -19
  113. package/demo-app/playwright.config.ts +0 -23
  114. package/demo-app/src/app/app.component.ts +0 -10
  115. package/demo-app/src/app/app.config.ts +0 -13
  116. package/demo-app/src/app/app.routes.ts +0 -7
  117. package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
  118. package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
  119. package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
  120. package/demo-app/src/index.html +0 -19
  121. package/demo-app/src/main.ts +0 -6
  122. package/demo-app/tsconfig.json +0 -31
@@ -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
+ };