argent-grid 0.1.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 (53) hide show
  1. package/.github/workflows/pages.yml +68 -0
  2. package/AGENTS.md +179 -0
  3. package/README.md +222 -0
  4. package/demo-app/README.md +70 -0
  5. package/demo-app/angular.json +78 -0
  6. package/demo-app/e2e/benchmark.spec.ts +53 -0
  7. package/demo-app/e2e/demo-page.spec.ts +77 -0
  8. package/demo-app/e2e/grid-features.spec.ts +269 -0
  9. package/demo-app/package-lock.json +14023 -0
  10. package/demo-app/package.json +36 -0
  11. package/demo-app/playwright-test-menu.js +19 -0
  12. package/demo-app/playwright.config.ts +23 -0
  13. package/demo-app/src/app/app.component.ts +10 -0
  14. package/demo-app/src/app/app.config.ts +13 -0
  15. package/demo-app/src/app/app.routes.ts +7 -0
  16. package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
  17. package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
  18. package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
  19. package/demo-app/src/index.html +19 -0
  20. package/demo-app/src/main.ts +6 -0
  21. package/demo-app/tsconfig.json +31 -0
  22. package/ng-package.json +8 -0
  23. package/package.json +60 -0
  24. package/plan.md +131 -0
  25. package/setup-vitest.ts +18 -0
  26. package/src/lib/argent-grid.module.ts +21 -0
  27. package/src/lib/components/argent-grid.component.css +483 -0
  28. package/src/lib/components/argent-grid.component.html +320 -0
  29. package/src/lib/components/argent-grid.component.spec.ts +189 -0
  30. package/src/lib/components/argent-grid.component.ts +1188 -0
  31. package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
  32. package/src/lib/rendering/canvas-renderer.ts +962 -0
  33. package/src/lib/rendering/render/blit.spec.ts +453 -0
  34. package/src/lib/rendering/render/blit.ts +393 -0
  35. package/src/lib/rendering/render/cells.ts +369 -0
  36. package/src/lib/rendering/render/index.ts +105 -0
  37. package/src/lib/rendering/render/lines.ts +363 -0
  38. package/src/lib/rendering/render/theme.spec.ts +282 -0
  39. package/src/lib/rendering/render/theme.ts +201 -0
  40. package/src/lib/rendering/render/types.ts +279 -0
  41. package/src/lib/rendering/render/walk.spec.ts +360 -0
  42. package/src/lib/rendering/render/walk.ts +360 -0
  43. package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
  44. package/src/lib/rendering/utils/damage-tracker.ts +423 -0
  45. package/src/lib/rendering/utils/index.ts +7 -0
  46. package/src/lib/services/grid.service.spec.ts +1039 -0
  47. package/src/lib/services/grid.service.ts +1284 -0
  48. package/src/lib/types/ag-grid-types.ts +970 -0
  49. package/src/public-api.ts +22 -0
  50. package/tsconfig.json +32 -0
  51. package/tsconfig.lib.json +11 -0
  52. package/tsconfig.spec.json +8 -0
  53. package/vitest.config.ts +55 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Walker Functions for Canvas Renderer
3
+ *
4
+ * Composable iteration patterns for columns and rows.
5
+ * Based on Glide Data Grid's walker architecture.
6
+ */
7
+
8
+ import { Column, IRowNode, GridApi } from '../../types/ag-grid-types';
9
+ import {
10
+ ColumnWalkCallback,
11
+ RowWalkCallback,
12
+ CellWalkCallback,
13
+ PositionedColumn,
14
+ VisibleRange
15
+ } from './types';
16
+
17
+ // ============================================================================
18
+ // COLUMN WALKERS
19
+ // ============================================================================
20
+
21
+ /**
22
+ * Walk through visible columns in render order
23
+ * Handles pinned columns (left, center, right) correctly
24
+ */
25
+ export function walkColumns(
26
+ columns: Column[],
27
+ scrollX: number,
28
+ viewportWidth: number,
29
+ leftPinnedWidth: number,
30
+ rightPinnedWidth: number,
31
+ callback: ColumnWalkCallback
32
+ ): void {
33
+ const leftPinned = columns.filter(c => c.pinned === 'left');
34
+ const rightPinned = columns.filter(c => c.pinned === 'right');
35
+ const centerColumns = columns.filter(c => !c.pinned);
36
+
37
+ // 1. Left pinned columns (no scroll offset)
38
+ let x = 0;
39
+ for (const col of leftPinned) {
40
+ callback(col, x, col.width, true, 'left');
41
+ x += col.width;
42
+ }
43
+
44
+ // 2. Center columns (with scroll offset and clipping)
45
+ const centerStartX = leftPinnedWidth;
46
+ const centerEndX = viewportWidth - rightPinnedWidth;
47
+ const centerWidth = centerEndX - centerStartX;
48
+
49
+ x = leftPinnedWidth - scrollX;
50
+ for (const col of centerColumns) {
51
+ // Skip columns completely outside viewport
52
+ if (x + col.width < centerStartX) {
53
+ x += col.width;
54
+ continue;
55
+ }
56
+ if (x > centerEndX) {
57
+ break; // Rest of columns are off-screen
58
+ }
59
+
60
+ callback(col, x, col.width, false);
61
+ x += col.width;
62
+ }
63
+
64
+ // 3. Right pinned columns (no scroll offset)
65
+ x = viewportWidth - rightPinnedWidth;
66
+ for (const col of rightPinned) {
67
+ callback(col, x, col.width, true, 'right');
68
+ x += col.width;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get positioned columns for rendering
74
+ */
75
+ export function getPositionedColumns(
76
+ columns: Column[],
77
+ scrollX: number,
78
+ viewportWidth: number,
79
+ leftPinnedWidth: number,
80
+ rightPinnedWidth: number
81
+ ): PositionedColumn[] {
82
+ const result: PositionedColumn[] = [];
83
+
84
+ walkColumns(columns, scrollX, viewportWidth, leftPinnedWidth, rightPinnedWidth,
85
+ (column, x, width, isPinned, pinSide) => {
86
+ result.push({ column, x, width, isPinned, pinSide });
87
+ }
88
+ );
89
+
90
+ return result;
91
+ }
92
+
93
+ /**
94
+ * Get pinned column widths
95
+ */
96
+ export function getPinnedWidths(columns: Column[]): { left: number; right: number } {
97
+ const left = columns
98
+ .filter(c => c.pinned === 'left')
99
+ .reduce((sum, c) => sum + c.width, 0);
100
+
101
+ const right = columns
102
+ .filter(c => c.pinned === 'right')
103
+ .reduce((sum, c) => sum + c.width, 0);
104
+
105
+ return { left, right };
106
+ }
107
+
108
+ // ============================================================================
109
+ // ROW WALKERS
110
+ // ============================================================================
111
+
112
+ /**
113
+ * Walk through visible rows
114
+ */
115
+ export function walkRows(
116
+ startRow: number,
117
+ endRow: number,
118
+ scrollTop: number,
119
+ rowHeight: number,
120
+ getRowNode: (index: number) => IRowNode | null,
121
+ callback: RowWalkCallback,
122
+ api?: GridApi
123
+ ): void {
124
+ for (let rowIndex = startRow; rowIndex < endRow; rowIndex++) {
125
+ const y = api ? api.getRowY(rowIndex) - scrollTop : rowIndex * rowHeight - scrollTop;
126
+ const rowNode = getRowNode(rowIndex);
127
+ const height = (api && rowNode) ? (rowNode.rowHeight || rowHeight) : rowHeight;
128
+ callback(rowIndex, y, height, rowNode);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Calculate visible row range with buffer
134
+ */
135
+ export function getVisibleRowRange(
136
+ scrollTop: number,
137
+ viewportHeight: number,
138
+ rowHeight: number,
139
+ totalRowCount: number,
140
+ buffer: number = 5,
141
+ api?: GridApi
142
+ ): { startRow: number; endRow: number } {
143
+ if (api) {
144
+ const startRow = Math.max(0, api.getRowAtY(scrollTop) - buffer);
145
+ const endRow = Math.min(
146
+ totalRowCount,
147
+ api.getRowAtY(scrollTop + viewportHeight) + buffer + 1
148
+ );
149
+ return { startRow, endRow };
150
+ }
151
+
152
+ const startRow = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
153
+ const visibleRowCount = Math.ceil(viewportHeight / rowHeight);
154
+ const endRow = Math.min(
155
+ totalRowCount,
156
+ startRow + visibleRowCount + buffer * 2
157
+ );
158
+
159
+ return { startRow, endRow };
160
+ }
161
+
162
+ /**
163
+ * Get row Y position
164
+ */
165
+ export function getRowY(rowIndex: number, rowHeight: number, scrollTop: number): number {
166
+ return rowIndex * rowHeight - scrollTop;
167
+ }
168
+
169
+ // ============================================================================
170
+ // CELL WALKERS
171
+ // ============================================================================
172
+
173
+ /**
174
+ * Walk through all visible cells
175
+ */
176
+ export function walkCells(
177
+ columns: Column[],
178
+ startRow: number,
179
+ endRow: number,
180
+ scrollX: number,
181
+ scrollTop: number,
182
+ viewportWidth: number,
183
+ viewportHeight: number,
184
+ rowHeight: number,
185
+ getRowNode: (index: number) => IRowNode | null,
186
+ callback: CellWalkCallback
187
+ ): void {
188
+ const { left: leftPinnedWidth, right: rightPinnedWidth } = getPinnedWidths(columns);
189
+
190
+ // Walk columns for each row
191
+ walkRows(startRow, endRow, scrollTop, rowHeight, getRowNode, (rowIndex, y, height, rowNode) => {
192
+ walkColumns(columns, scrollX, viewportWidth, leftPinnedWidth, rightPinnedWidth,
193
+ (column, x, width, isPinned) => {
194
+ callback(column, rowIndex, x, y, width, height, rowNode);
195
+ }
196
+ );
197
+ });
198
+ }
199
+
200
+ // ============================================================================
201
+ // COLUMN UTILITIES
202
+ // ============================================================================
203
+
204
+ /**
205
+ * Find column at X position
206
+ */
207
+ export function getColumnAtX(
208
+ columns: Column[],
209
+ x: number,
210
+ scrollX: number,
211
+ viewportWidth: number
212
+ ): { column: Column | null; index: number; localX: number } {
213
+ const { left: leftPinnedWidth, right: rightPinnedWidth } = getPinnedWidths(columns);
214
+
215
+ const leftPinned = columns.filter(c => c.pinned === 'left');
216
+ const rightPinned = columns.filter(c => c.pinned === 'right');
217
+ const centerColumns = columns.filter(c => !c.pinned);
218
+
219
+ // Check left pinned
220
+ if (x < leftPinnedWidth) {
221
+ let colX = 0;
222
+ for (let i = 0; i < leftPinned.length; i++) {
223
+ const col = leftPinned[i];
224
+ if (x < colX + col.width) {
225
+ return { column: col, index: columns.indexOf(col), localX: x - colX };
226
+ }
227
+ colX += col.width;
228
+ }
229
+ }
230
+
231
+ // Check right pinned
232
+ if (x > viewportWidth - rightPinnedWidth) {
233
+ let colX = viewportWidth - rightPinnedWidth;
234
+ for (let i = 0; i < rightPinned.length; i++) {
235
+ const col = rightPinned[i];
236
+ if (x < colX + col.width) {
237
+ return { column: col, index: columns.indexOf(col), localX: x - colX };
238
+ }
239
+ colX += col.width;
240
+ }
241
+ }
242
+
243
+ // Check center columns
244
+ const scrolledX = x - leftPinnedWidth + scrollX;
245
+ let colX = 0;
246
+ for (let i = 0; i < centerColumns.length; i++) {
247
+ const col = centerColumns[i];
248
+ if (scrolledX < colX + col.width) {
249
+ return { column: col, index: columns.indexOf(col), localX: scrolledX - colX };
250
+ }
251
+ colX += col.width;
252
+ }
253
+
254
+ return { column: null, index: -1, localX: 0 };
255
+ }
256
+
257
+ /**
258
+ * Get column index in visible columns array
259
+ */
260
+ export function getColumnIndex(columns: Column[], colId: string): number {
261
+ return columns.findIndex(c => c.colId === colId);
262
+ }
263
+
264
+ /**
265
+ * Calculate total width of columns
266
+ */
267
+ export function getTotalColumnWidth(columns: Column[]): number {
268
+ return columns.reduce((sum, col) => sum + col.width, 0);
269
+ }
270
+
271
+ // ============================================================================
272
+ // ROW UTILITIES
273
+ // ============================================================================
274
+
275
+ /**
276
+ * Find row at Y position
277
+ */
278
+ export function getRowAtY(y: number, rowHeight: number, scrollTop: number): number {
279
+ return Math.floor((y + scrollTop) / rowHeight);
280
+ }
281
+
282
+ /**
283
+ * Check if row is visible in viewport
284
+ */
285
+ export function isRowVisible(
286
+ rowIndex: number,
287
+ scrollTop: number,
288
+ viewportHeight: number,
289
+ rowHeight: number
290
+ ): boolean {
291
+ const y = rowIndex * rowHeight;
292
+ const rowBottom = y + rowHeight;
293
+ const viewportBottom = scrollTop + viewportHeight;
294
+
295
+ return y < viewportBottom && rowBottom > scrollTop;
296
+ }
297
+
298
+ // ============================================================================
299
+ // VISIBLE RANGE CALCULATION
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Calculate complete visible range for rendering
304
+ */
305
+ export function calculateVisibleRange(
306
+ columns: Column[],
307
+ scrollTop: number,
308
+ scrollLeft: number,
309
+ viewportWidth: number,
310
+ viewportHeight: number,
311
+ rowHeight: number,
312
+ totalRowCount: number,
313
+ rowBuffer: number = 5
314
+ ): VisibleRange {
315
+ const { startRow, endRow } = getVisibleRowRange(
316
+ scrollTop, viewportHeight, rowHeight, totalRowCount, rowBuffer
317
+ );
318
+
319
+ // For columns, we just track indices
320
+ const centerColumns = columns.filter(c => !c.pinned);
321
+ const leftPinned = columns.filter(c => c.pinned === 'left');
322
+ const rightPinned = columns.filter(c => c.pinned === 'right');
323
+
324
+ const leftPinnedWidth = leftPinned.reduce((sum, c) => sum + c.width, 0);
325
+ const rightPinnedWidth = rightPinned.reduce((sum, c) => sum + c.width, 0);
326
+
327
+ // Find first and last visible center column
328
+ let startColumnIndex = leftPinned.length;
329
+ let endColumnIndex = startColumnIndex + centerColumns.length;
330
+
331
+ let x = leftPinnedWidth - scrollLeft;
332
+ for (let i = 0; i < centerColumns.length; i++) {
333
+ const col = centerColumns[i];
334
+ if (x + col.width > leftPinnedWidth) {
335
+ startColumnIndex = leftPinned.length + i;
336
+ break;
337
+ }
338
+ x += col.width;
339
+ }
340
+
341
+ x = leftPinnedWidth - scrollLeft;
342
+ for (let i = 0; i < centerColumns.length; i++) {
343
+ const col = centerColumns[i];
344
+ if (x > viewportWidth - rightPinnedWidth) {
345
+ endColumnIndex = leftPinned.length + i;
346
+ break;
347
+ }
348
+ x += col.width;
349
+ }
350
+
351
+ // Add right pinned columns
352
+ endColumnIndex += rightPinned.length;
353
+
354
+ return {
355
+ startRow,
356
+ endRow,
357
+ startColumnIndex,
358
+ endColumnIndex,
359
+ };
360
+ }