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.
- package/.github/workflows/pages.yml +68 -0
- package/AGENTS.md +179 -0
- package/README.md +222 -0
- package/demo-app/README.md +70 -0
- package/demo-app/angular.json +78 -0
- package/demo-app/e2e/benchmark.spec.ts +53 -0
- package/demo-app/e2e/demo-page.spec.ts +77 -0
- package/demo-app/e2e/grid-features.spec.ts +269 -0
- package/demo-app/package-lock.json +14023 -0
- package/demo-app/package.json +36 -0
- package/demo-app/playwright-test-menu.js +19 -0
- package/demo-app/playwright.config.ts +23 -0
- package/demo-app/src/app/app.component.ts +10 -0
- package/demo-app/src/app/app.config.ts +13 -0
- package/demo-app/src/app/app.routes.ts +7 -0
- package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
- package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
- package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
- package/demo-app/src/index.html +19 -0
- package/demo-app/src/main.ts +6 -0
- package/demo-app/tsconfig.json +31 -0
- package/ng-package.json +8 -0
- package/package.json +60 -0
- package/plan.md +131 -0
- package/setup-vitest.ts +18 -0
- package/src/lib/argent-grid.module.ts +21 -0
- package/src/lib/components/argent-grid.component.css +483 -0
- package/src/lib/components/argent-grid.component.html +320 -0
- package/src/lib/components/argent-grid.component.spec.ts +189 -0
- package/src/lib/components/argent-grid.component.ts +1188 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
- package/src/lib/rendering/canvas-renderer.ts +962 -0
- package/src/lib/rendering/render/blit.spec.ts +453 -0
- package/src/lib/rendering/render/blit.ts +393 -0
- package/src/lib/rendering/render/cells.ts +369 -0
- package/src/lib/rendering/render/index.ts +105 -0
- package/src/lib/rendering/render/lines.ts +363 -0
- package/src/lib/rendering/render/theme.spec.ts +282 -0
- package/src/lib/rendering/render/theme.ts +201 -0
- package/src/lib/rendering/render/types.ts +279 -0
- package/src/lib/rendering/render/walk.spec.ts +360 -0
- package/src/lib/rendering/render/walk.ts +360 -0
- package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
- package/src/lib/rendering/utils/damage-tracker.ts +423 -0
- package/src/lib/rendering/utils/index.ts +7 -0
- package/src/lib/services/grid.service.spec.ts +1039 -0
- package/src/lib/services/grid.service.ts +1284 -0
- package/src/lib/types/ag-grid-types.ts +970 -0
- package/src/public-api.ts +22 -0
- package/tsconfig.json +32 -0
- package/tsconfig.lib.json +11 -0
- package/tsconfig.spec.json +8 -0
- 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
|
+
}
|