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,423 @@
1
+ /**
2
+ * Damage Tracking for Canvas Renderer
3
+ *
4
+ * Tracks which regions of the grid need to be redrawn.
5
+ * Enables efficient partial redraws for better performance.
6
+ */
7
+
8
+ import { DirtyRegions, Rectangle } from '../render/types';
9
+
10
+ // ============================================================================
11
+ // DAMAGE TRACKER CLASS
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Tracks dirty regions for partial redraws
16
+ */
17
+ export class DamageTracker {
18
+ private damagedCells: Set<string> = new Set();
19
+ private damagedRows: Set<number> = new Set();
20
+ private damagedColumns: Set<number> = new Set();
21
+ private fullRedrawNeeded: boolean = false;
22
+ private lastRenderTime: number = 0;
23
+
24
+ // ============================================================================
25
+ // MARK DIRTY METHODS
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Mark a single cell as dirty
30
+ */
31
+ markCellDirty(col: number, row: number): void {
32
+ this.damagedCells.add(`${col},${row}`);
33
+ }
34
+
35
+ /**
36
+ * Mark multiple cells as dirty
37
+ */
38
+ markCellsDirty(cells: Array<[number, number]>): void {
39
+ for (const [col, row] of cells) {
40
+ this.damagedCells.add(`${col},${row}`);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Mark an entire row as dirty
46
+ */
47
+ markRowDirty(row: number): void {
48
+ this.damagedRows.add(row);
49
+ }
50
+
51
+ /**
52
+ * Mark multiple rows as dirty
53
+ */
54
+ markRowsDirty(rows: number[]): void {
55
+ for (const row of rows) {
56
+ this.damagedRows.add(row);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Mark an entire column as dirty
62
+ */
63
+ markColumnDirty(col: number): void {
64
+ this.damagedColumns.add(col);
65
+ }
66
+
67
+ /**
68
+ * Mark multiple columns as dirty
69
+ */
70
+ markColumnsDirty(cols: number[]): void {
71
+ for (const col of cols) {
72
+ this.damagedColumns.add(col);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Mark entire grid as dirty (requires full redraw)
78
+ */
79
+ markAllDirty(): void {
80
+ this.fullRedrawNeeded = true;
81
+ this.damagedCells.clear();
82
+ this.damagedRows.clear();
83
+ this.damagedColumns.clear();
84
+ }
85
+
86
+ /**
87
+ * Mark dirty based on selection change
88
+ */
89
+ markSelectionChanged(
90
+ oldSelection: Set<number>,
91
+ newSelection: Set<number>
92
+ ): void {
93
+ // Mark rows that changed selection state
94
+ for (const row of oldSelection) {
95
+ if (!newSelection.has(row)) {
96
+ this.markRowDirty(row);
97
+ }
98
+ }
99
+ for (const row of newSelection) {
100
+ if (!oldSelection.has(row)) {
101
+ this.markRowDirty(row);
102
+ }
103
+ }
104
+ }
105
+
106
+ // ============================================================================
107
+ // QUERY METHODS
108
+ // ============================================================================
109
+
110
+ /**
111
+ * Check if a full redraw is needed
112
+ */
113
+ needsFullRedraw(): boolean {
114
+ return this.fullRedrawNeeded;
115
+ }
116
+
117
+ /**
118
+ * Check if any damage exists
119
+ */
120
+ hasDamage(): boolean {
121
+ return (
122
+ this.fullRedrawNeeded ||
123
+ this.damagedCells.size > 0 ||
124
+ this.damagedRows.size > 0 ||
125
+ this.damagedColumns.size > 0
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Check if a specific cell is dirty
131
+ */
132
+ isCellDirty(col: number, row: number): boolean {
133
+ if (this.fullRedrawNeeded) return true;
134
+ if (this.damagedRows.has(row)) return true;
135
+ if (this.damagedColumns.has(col)) return true;
136
+ return this.damagedCells.has(`${col},${row}`);
137
+ }
138
+
139
+ /**
140
+ * Check if a row is dirty
141
+ */
142
+ isRowDirty(row: number): boolean {
143
+ if (this.fullRedrawNeeded) return true;
144
+ return this.damagedRows.has(row);
145
+ }
146
+
147
+ /**
148
+ * Check if a column is dirty
149
+ */
150
+ isColumnDirty(col: number): boolean {
151
+ if (this.fullRedrawNeeded) return true;
152
+ return this.damagedColumns.has(col);
153
+ }
154
+
155
+ // ============================================================================
156
+ // GET DIRTY REGIONS
157
+ // ============================================================================
158
+
159
+ /**
160
+ * Get all dirty regions
161
+ */
162
+ getDirtyRegions(): DirtyRegions {
163
+ return {
164
+ full: this.fullRedrawNeeded,
165
+ rows: new Set(this.damagedRows),
166
+ columns: new Set(this.damagedColumns),
167
+ cells: new Set(this.damagedCells),
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Get dirty rows
173
+ */
174
+ getDirtyRows(): number[] {
175
+ return Array.from(this.damagedRows);
176
+ }
177
+
178
+ /**
179
+ * Get dirty columns
180
+ */
181
+ getDirtyColumns(): number[] {
182
+ return Array.from(this.damagedColumns);
183
+ }
184
+
185
+ /**
186
+ * Get dirty cells
187
+ */
188
+ getDirtyCells(): Array<[number, number]> {
189
+ return Array.from(this.damagedCells).map(key => {
190
+ const [col, row] = key.split(',').map(Number);
191
+ return [col, row];
192
+ });
193
+ }
194
+
195
+ // ============================================================================
196
+ // OPTIMIZATION
197
+ // ============================================================================
198
+
199
+ /**
200
+ * Optimize dirty regions
201
+ * - If too many individual cells, promote to row/column
202
+ * - If too many rows/columns, promote to full redraw
203
+ */
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;
214
+
215
+ // If too many cells, check if we should promote to rows
216
+ if (this.damagedCells.size > maxCells) {
217
+ const rowsWithDirtyCells = new Set<number>();
218
+ for (const cell of this.damagedCells) {
219
+ const [_, row] = cell.split(',').map(Number);
220
+ rowsWithDirtyCells.add(row);
221
+ }
222
+
223
+ // If cells are concentrated in few rows, promote to those rows
224
+ if (rowsWithDirtyCells.size <= 10) {
225
+ for (const row of rowsWithDirtyCells) {
226
+ this.damagedRows.add(row);
227
+ }
228
+ this.damagedCells.clear();
229
+ } else {
230
+ // Too spread out, just do full redraw
231
+ this.markAllDirty();
232
+ return;
233
+ }
234
+ }
235
+
236
+ // If too many rows, do full redraw
237
+ if (this.damagedRows.size > maxRows) {
238
+ this.markAllDirty();
239
+ return;
240
+ }
241
+
242
+ // If too many columns, do full redraw
243
+ if (this.damagedColumns.size > maxColumns) {
244
+ this.markAllDirty();
245
+ return;
246
+ }
247
+ }
248
+
249
+ // ============================================================================
250
+ // CLEAR
251
+ // ============================================================================
252
+
253
+ /**
254
+ * Clear all damage tracking
255
+ */
256
+ clear(): void {
257
+ this.damagedCells.clear();
258
+ this.damagedRows.clear();
259
+ this.damagedColumns.clear();
260
+ this.fullRedrawNeeded = false;
261
+ this.lastRenderTime = performance.now();
262
+ }
263
+
264
+ /**
265
+ * Reset completely
266
+ */
267
+ reset(): void {
268
+ this.clear();
269
+ this.lastRenderTime = 0;
270
+ }
271
+
272
+ // ============================================================================
273
+ // RENDER TIMING
274
+ // ============================================================================
275
+
276
+ /**
277
+ * Get time since last render
278
+ */
279
+ getTimeSinceLastRender(): number {
280
+ return performance.now() - this.lastRenderTime;
281
+ }
282
+
283
+ /**
284
+ * Get last render time
285
+ */
286
+ getLastRenderTime(): number {
287
+ return this.lastRenderTime;
288
+ }
289
+ }
290
+
291
+ // ============================================================================
292
+ // HELPER FUNCTIONS
293
+ // ============================================================================
294
+
295
+ /**
296
+ * Calculate bounding rectangle for dirty regions
297
+ */
298
+ export function getDirtyBounds(
299
+ dirty: DirtyRegions,
300
+ rowHeight: number,
301
+ columnWidths: number[],
302
+ scrollTop: number,
303
+ scrollLeft: number,
304
+ viewportWidth: number,
305
+ viewportHeight: number
306
+ ): Rectangle[] {
307
+ if (dirty.full) {
308
+ return [{ x: 0, y: 0, width: viewportWidth, height: viewportHeight }];
309
+ }
310
+
311
+ const rects: Rectangle[] = [];
312
+
313
+ // Add rectangles for dirty rows
314
+ for (const row of dirty.rows) {
315
+ const y = row * rowHeight - scrollTop;
316
+ if (y + rowHeight >= 0 && y <= viewportHeight) {
317
+ rects.push({
318
+ x: 0,
319
+ y: Math.max(0, y),
320
+ width: viewportWidth,
321
+ height: rowHeight,
322
+ });
323
+ }
324
+ }
325
+
326
+ // Add rectangles for dirty columns
327
+ for (const col of dirty.columns) {
328
+ if (col >= 0 && col < columnWidths.length) {
329
+ let x = 0;
330
+ for (let i = 0; i < col; i++) {
331
+ x += columnWidths[i] || 0;
332
+ }
333
+ x -= scrollLeft;
334
+
335
+ const width = columnWidths[col] || 0;
336
+
337
+ if (x + width >= 0 && x <= viewportWidth) {
338
+ rects.push({
339
+ x: Math.max(0, x),
340
+ y: 0,
341
+ width,
342
+ height: viewportHeight,
343
+ });
344
+ }
345
+ }
346
+ }
347
+
348
+ // Add rectangles for dirty cells
349
+ for (const cell of dirty.cells) {
350
+ const [col, row] = cell.split(',').map(Number);
351
+
352
+ // Skip if row or column already covered
353
+ if (dirty.rows.has(row) || dirty.columns.has(col)) continue;
354
+
355
+ const y = row * rowHeight - scrollTop;
356
+ let x = 0;
357
+ for (let i = 0; i < col; i++) {
358
+ x += columnWidths[i] || 0;
359
+ }
360
+ x -= scrollLeft;
361
+
362
+ const width = columnWidths[col] || 0;
363
+
364
+ if (x + width >= 0 && x <= viewportWidth &&
365
+ y + rowHeight >= 0 && y <= viewportHeight) {
366
+ rects.push({
367
+ x: Math.max(0, x),
368
+ y: Math.max(0, y),
369
+ width,
370
+ height: rowHeight,
371
+ });
372
+ }
373
+ }
374
+
375
+ return rects;
376
+ }
377
+
378
+ /**
379
+ * Merge overlapping rectangles
380
+ */
381
+ export function mergeRectangles(rects: Rectangle[]): Rectangle[] {
382
+ if (rects.length <= 1) return rects;
383
+
384
+ // Simple merge - find bounding box
385
+ // For more complex merging, use a proper rectangle packing algorithm
386
+ const merged: Rectangle[] = [];
387
+ const used = new Set<number>();
388
+
389
+ for (let i = 0; i < rects.length; i++) {
390
+ if (used.has(i)) continue;
391
+
392
+ let current = rects[i];
393
+
394
+ for (let j = i + 1; j < rects.length; j++) {
395
+ if (used.has(j)) continue;
396
+
397
+ const other = rects[j];
398
+
399
+ // Check if they overlap or are adjacent
400
+ if (
401
+ current.x <= other.x + other.width &&
402
+ current.x + current.width >= other.x &&
403
+ current.y <= other.y + other.height &&
404
+ current.y + current.height >= other.y
405
+ ) {
406
+ // Merge
407
+ const newX = Math.min(current.x, other.x);
408
+ const newY = Math.min(current.y, other.y);
409
+ current = {
410
+ x: newX,
411
+ y: newY,
412
+ width: Math.max(current.x + current.width, other.x + other.width) - newX,
413
+ height: Math.max(current.y + current.height, other.y + other.height) - newY,
414
+ };
415
+ used.add(j);
416
+ }
417
+ }
418
+
419
+ merged.push(current);
420
+ }
421
+
422
+ return merged;
423
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Utilities Module Index
3
+ *
4
+ * Exports all utility modules.
5
+ */
6
+
7
+ export * from './damage-tracker';