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.
- package/.github/workflows/ci.yml +69 -0
- package/.github/workflows/pages.yml +6 -12
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/tsconfig.json +24 -0
- package/AGENTS.md +70 -27
- package/README.md +51 -34
- package/angular.json +66 -0
- package/biome.json +66 -0
- package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
- package/docs/AG-GRID-COMPARISON.md +725 -0
- package/docs/CELL-RENDERER-GUIDE.md +241 -0
- package/docs/CONTEXT-MENU-GUIDE.md +371 -0
- package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
- package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
- package/docs/PERFORMANCE-REVIEW.md +571 -0
- package/docs/RESEARCH-STATUS.md +234 -0
- package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
- package/docs/STORYBOOK-REFACTOR.md +215 -0
- package/docs/STORYBOOK-STATUS.md +156 -0
- package/docs/TEST-COVERAGE-REPORT.md +276 -0
- package/docs/THEME-API-GUIDE.md +445 -0
- package/docs/THEME-API-PLAN.md +364 -0
- package/e2e/advanced.spec.ts +109 -0
- package/e2e/argentgrid.spec.ts +65 -0
- package/e2e/benchmark.spec.ts +52 -0
- package/e2e/cell-renderers.spec.ts +152 -0
- package/e2e/debug-streaming.spec.ts +31 -0
- package/e2e/dnd.spec.ts +73 -0
- package/e2e/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +112 -0
- package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
- package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
- package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
- package/package.json +21 -7
- package/plan.md +56 -28
- package/playwright.config.ts +38 -0
- package/setup-vitest.ts +10 -13
- package/src/lib/argent-grid.module.ts +10 -12
- package/src/lib/components/argent-grid.component.css +281 -321
- package/src/lib/components/argent-grid.component.html +295 -207
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +1193 -290
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +307 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
- package/src/lib/directives/click-outside.directive.ts +19 -0
- package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
- package/src/lib/rendering/canvas-renderer.ts +456 -452
- package/src/lib/rendering/live-data-handler.ts +110 -0
- package/src/lib/rendering/live-data-optimizations.ts +133 -0
- package/src/lib/rendering/render/blit.spec.ts +16 -27
- package/src/lib/rendering/render/blit.ts +48 -36
- package/src/lib/rendering/render/cells.spec.ts +132 -0
- package/src/lib/rendering/render/cells.ts +167 -28
- package/src/lib/rendering/render/column-utils.ts +95 -0
- package/src/lib/rendering/render/hit-test.ts +50 -0
- package/src/lib/rendering/render/index.ts +88 -76
- package/src/lib/rendering/render/lines.ts +53 -47
- package/src/lib/rendering/render/primitives.ts +423 -0
- package/src/lib/rendering/render/theme.spec.ts +8 -12
- package/src/lib/rendering/render/theme.ts +7 -10
- package/src/lib/rendering/render/types.ts +3 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +94 -64
- package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
- package/src/lib/rendering/utils/damage-tracker.ts +6 -18
- package/src/lib/rendering/utils/index.ts +1 -1
- package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
- package/src/lib/services/grid.service.spec.ts +1241 -201
- package/src/lib/services/grid.service.ts +1204 -235
- package/src/lib/themes/parts/color-schemes.ts +132 -0
- package/src/lib/themes/parts/icon-sets.ts +258 -0
- package/src/lib/themes/theme-builder.ts +347 -0
- package/src/lib/themes/theme-quartz.ts +72 -0
- package/src/lib/themes/types.ts +238 -0
- package/src/lib/types/ag-grid-types.ts +573 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +249 -0
- package/src/stories/ArgentGrid.stories.ts +301 -0
- package/src/stories/Benchmark.stories.ts +76 -0
- package/src/stories/CellRenderers.stories.ts +395 -0
- package/src/stories/Filtering.stories.ts +292 -0
- package/src/stories/Grouping.stories.ts +290 -0
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +137 -0
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +355 -0
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- package/tsconfig.json +1 -0
- package/tsconfig.storybook.json +10 -0
- package/vitest.config.ts +9 -9
- package/demo-app/README.md +0 -70
- package/demo-app/angular.json +0 -78
- package/demo-app/e2e/benchmark.spec.ts +0 -53
- package/demo-app/e2e/demo-page.spec.ts +0 -77
- package/demo-app/e2e/grid-features.spec.ts +0 -269
- package/demo-app/package-lock.json +0 -14023
- package/demo-app/package.json +0 -36
- package/demo-app/playwright-test-menu.js +0 -19
- package/demo-app/playwright.config.ts +0 -23
- package/demo-app/src/app/app.component.ts +0 -10
- package/demo-app/src/app/app.config.ts +0 -13
- package/demo-app/src/app/app.routes.ts +0 -7
- package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
- package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
- package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
- package/demo-app/src/index.html +0 -19
- package/demo-app/src/main.ts +0 -6
- package/demo-app/tsconfig.json +0 -31
|
@@ -18,7 +18,7 @@ export const DEFAULT_THEME: GridTheme = {
|
|
|
18
18
|
bgCell: '#ffffff',
|
|
19
19
|
bgCellEven: '#f8f9fa',
|
|
20
20
|
bgHeader: '#f8f9fa',
|
|
21
|
-
bgSelection: '#
|
|
21
|
+
bgSelection: '#d1e9ff', // Clearer blue for selection
|
|
22
22
|
bgHover: '#f0f2f5',
|
|
23
23
|
bgGroupRow: '#f5f5f5',
|
|
24
24
|
|
|
@@ -61,7 +61,7 @@ export const DARK_THEME: PartialTheme = {
|
|
|
61
61
|
|
|
62
62
|
textCell: '#cccccc',
|
|
63
63
|
textHeader: '#ffffff',
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
borderColor: '#3c3c3c',
|
|
66
66
|
headerBorderColor: '#3c3c3c',
|
|
67
67
|
gridLineColor: '#3c3c3c',
|
|
@@ -78,13 +78,13 @@ export function mergeTheme(base: GridTheme, ...overrides: PartialTheme[]): GridT
|
|
|
78
78
|
if (overrides.length === 0) return base;
|
|
79
79
|
|
|
80
80
|
let result = { ...base };
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
for (const override of overrides) {
|
|
83
83
|
if (override) {
|
|
84
84
|
result = { ...result, ...override };
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
return result;
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -113,7 +113,7 @@ export function getRowTheme(
|
|
|
113
113
|
if (isSelected) {
|
|
114
114
|
return { bgCell: baseTheme.bgSelection };
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
if (isHovered) {
|
|
118
118
|
return { bgCell: baseTheme.bgHover };
|
|
119
119
|
}
|
|
@@ -192,10 +192,7 @@ export function getThemePreset(name: string): PartialTheme {
|
|
|
192
192
|
/**
|
|
193
193
|
* Create a complete theme from a preset name and optional overrides
|
|
194
194
|
*/
|
|
195
|
-
export function createTheme(
|
|
196
|
-
presetName: string = 'default',
|
|
197
|
-
overrides?: PartialTheme
|
|
198
|
-
): GridTheme {
|
|
195
|
+
export function createTheme(presetName: string = 'default', overrides?: PartialTheme): GridTheme {
|
|
199
196
|
const preset = getThemePreset(presetName);
|
|
200
197
|
return mergeTheme(DEFAULT_THEME, preset, overrides || {});
|
|
201
|
-
}
|
|
198
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared type definitions used across the rendering modules.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { ColDef, Column, GridApi, IRowNode } from '../../types/ag-grid-types';
|
|
8
8
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
// CORE RENDERING TYPES
|
|
@@ -116,6 +116,7 @@ export interface CellDrawContext<TData = any> {
|
|
|
116
116
|
isSelected: boolean;
|
|
117
117
|
isHovered: boolean;
|
|
118
118
|
isEvenRow: boolean;
|
|
119
|
+
api: GridApi<TData>;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/**
|
|
@@ -276,4 +277,4 @@ export interface GridMouseEvent {
|
|
|
276
277
|
hitTest: HitTestResult;
|
|
277
278
|
canvasX: number;
|
|
278
279
|
canvasY: number;
|
|
279
|
-
}
|
|
280
|
+
}
|
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
* Tests for walkColumns, walkRows, walkCells, and related utilities.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Column } from '../../types/ag-grid-types';
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
-
getPositionedColumns,
|
|
10
|
-
getPinnedWidths,
|
|
11
|
-
walkRows,
|
|
12
|
-
getVisibleRowRange,
|
|
13
|
-
getRowY,
|
|
14
|
-
walkCells,
|
|
9
|
+
calculateVisibleRange,
|
|
15
10
|
getColumnAtX,
|
|
16
11
|
getColumnIndex,
|
|
17
|
-
|
|
12
|
+
getPinnedWidths,
|
|
13
|
+
getPositionedColumns,
|
|
18
14
|
getRowAtY,
|
|
15
|
+
getRowY,
|
|
16
|
+
getTotalColumnWidth,
|
|
17
|
+
getVisibleRowRange,
|
|
19
18
|
isRowVisible,
|
|
20
|
-
|
|
19
|
+
walkColumns,
|
|
20
|
+
walkRows,
|
|
21
21
|
} from './walk';
|
|
22
|
-
import { Column } from '../../types/ag-grid-types';
|
|
23
22
|
|
|
24
23
|
// Helper to create mock columns
|
|
25
24
|
function createMockColumn(overrides: Partial<Column> = {}): Column {
|
|
@@ -66,11 +65,11 @@ describe('Walker Functions', () => {
|
|
|
66
65
|
describe('getVisibleRowRange', () => {
|
|
67
66
|
it('should calculate visible row range with buffer', () => {
|
|
68
67
|
const result = getVisibleRowRange(
|
|
69
|
-
100,
|
|
70
|
-
400,
|
|
71
|
-
32,
|
|
68
|
+
100, // scrollTop
|
|
69
|
+
400, // viewportHeight
|
|
70
|
+
32, // rowHeight
|
|
72
71
|
1000, // totalRowCount
|
|
73
|
-
5
|
|
72
|
+
5 // buffer
|
|
74
73
|
);
|
|
75
74
|
|
|
76
75
|
// Starting row: floor(100/32) = 3, minus buffer = -2 -> clamped to 0
|
|
@@ -82,11 +81,11 @@ describe('Walker Functions', () => {
|
|
|
82
81
|
|
|
83
82
|
it('should clamp start row to 0', () => {
|
|
84
83
|
const result = getVisibleRowRange(
|
|
85
|
-
0,
|
|
86
|
-
400,
|
|
87
|
-
32,
|
|
84
|
+
0, // scrollTop
|
|
85
|
+
400, // viewportHeight
|
|
86
|
+
32, // rowHeight
|
|
88
87
|
1000, // totalRowCount
|
|
89
|
-
5
|
|
88
|
+
5 // buffer
|
|
90
89
|
);
|
|
91
90
|
|
|
92
91
|
expect(result.startRow).toBe(0);
|
|
@@ -94,11 +93,11 @@ describe('Walker Functions', () => {
|
|
|
94
93
|
|
|
95
94
|
it('should clamp end row to total row count', () => {
|
|
96
95
|
const result = getVisibleRowRange(
|
|
97
|
-
0,
|
|
98
|
-
400,
|
|
99
|
-
32,
|
|
100
|
-
10,
|
|
101
|
-
5
|
|
96
|
+
0, // scrollTop
|
|
97
|
+
400, // viewportHeight
|
|
98
|
+
32, // rowHeight
|
|
99
|
+
10, // totalRowCount (small)
|
|
100
|
+
5 // buffer
|
|
102
101
|
);
|
|
103
102
|
|
|
104
103
|
expect(result.endRow).toBeLessThanOrEqual(10);
|
|
@@ -116,7 +115,7 @@ describe('Walker Functions', () => {
|
|
|
116
115
|
const visited: string[] = [];
|
|
117
116
|
const xPositions: number[] = [];
|
|
118
117
|
|
|
119
|
-
walkColumns(columns, 0, 400, 50, 75, (col, x,
|
|
118
|
+
walkColumns(columns, 0, 400, 50, 75, (col, x, _width, _isPinned, _pinSide) => {
|
|
120
119
|
visited.push(col.colId);
|
|
121
120
|
xPositions.push(x);
|
|
122
121
|
});
|
|
@@ -141,7 +140,7 @@ describe('Walker Functions', () => {
|
|
|
141
140
|
|
|
142
141
|
// Scroll so center1 and part of center2 are hidden
|
|
143
142
|
const visited: string[] = [];
|
|
144
|
-
walkColumns(columns, 150, 400, 50, 75, (col,
|
|
143
|
+
walkColumns(columns, 150, 400, 50, 75, (col, _x, _width, _isPinned) => {
|
|
145
144
|
visited.push(col.colId);
|
|
146
145
|
});
|
|
147
146
|
|
|
@@ -156,7 +155,7 @@ describe('Walker Functions', () => {
|
|
|
156
155
|
const visited: { row: number; y: number }[] = [];
|
|
157
156
|
const getRowNode = (index: number) => ({ data: { id: index } }) as any;
|
|
158
157
|
|
|
159
|
-
walkRows(0, 5, 0, 32, getRowNode, (rowIndex, y,
|
|
158
|
+
walkRows(0, 5, 0, 32, getRowNode, (rowIndex, y, _height, _rowNode) => {
|
|
160
159
|
visited.push({ row: rowIndex, y });
|
|
161
160
|
});
|
|
162
161
|
|
|
@@ -170,7 +169,7 @@ describe('Walker Functions', () => {
|
|
|
170
169
|
const visited: { row: number; y: number }[] = [];
|
|
171
170
|
const getRowNode = (index: number) => ({ data: { id: index } }) as any;
|
|
172
171
|
|
|
173
|
-
walkRows(10, 15, 320, 32, getRowNode, (rowIndex, y,
|
|
172
|
+
walkRows(10, 15, 320, 32, getRowNode, (rowIndex, y, _height, _rowNode) => {
|
|
174
173
|
visited.push({ row: rowIndex, y });
|
|
175
174
|
});
|
|
176
175
|
|
|
@@ -255,9 +254,7 @@ describe('Walker Functions', () => {
|
|
|
255
254
|
});
|
|
256
255
|
|
|
257
256
|
it('should return null for position outside columns', () => {
|
|
258
|
-
const columns: Column[] = [
|
|
259
|
-
createMockColumn({ colId: 'col1', width: 100 }),
|
|
260
|
-
];
|
|
257
|
+
const columns: Column[] = [createMockColumn({ colId: 'col1', width: 100 })];
|
|
261
258
|
|
|
262
259
|
const result = getColumnAtX(columns, 200, 0, 400);
|
|
263
260
|
expect(result.column).toBeNull();
|
|
@@ -311,13 +308,13 @@ describe('Walker Functions', () => {
|
|
|
311
308
|
|
|
312
309
|
const range = calculateVisibleRange(
|
|
313
310
|
columns,
|
|
314
|
-
0,
|
|
315
|
-
0,
|
|
316
|
-
400,
|
|
317
|
-
400,
|
|
318
|
-
32,
|
|
319
|
-
100,
|
|
320
|
-
5
|
|
311
|
+
0, // scrollTop
|
|
312
|
+
0, // scrollLeft
|
|
313
|
+
400, // viewportWidth
|
|
314
|
+
400, // viewportHeight
|
|
315
|
+
32, // rowHeight
|
|
316
|
+
100, // totalRowCount
|
|
317
|
+
5 // buffer
|
|
321
318
|
);
|
|
322
319
|
|
|
323
320
|
expect(range.startRow).toBe(0);
|
|
@@ -357,4 +354,4 @@ describe('Walker Functions', () => {
|
|
|
357
354
|
expect(getColumnIndex(columns, 'notfound')).toBe(-1);
|
|
358
355
|
});
|
|
359
356
|
});
|
|
360
|
-
});
|
|
357
|
+
});
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Based on Glide Data Grid's walker architecture.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Column,
|
|
9
|
-
import {
|
|
10
|
-
ColumnWalkCallback,
|
|
11
|
-
RowWalkCallback,
|
|
8
|
+
import { Column, GridApi, IRowNode } from '../../types/ag-grid-types';
|
|
9
|
+
import {
|
|
12
10
|
CellWalkCallback,
|
|
11
|
+
ColumnWalkCallback,
|
|
13
12
|
PositionedColumn,
|
|
14
|
-
|
|
13
|
+
RowWalkCallback,
|
|
14
|
+
VisibleRange,
|
|
15
15
|
} from './types';
|
|
16
16
|
|
|
17
17
|
// ============================================================================
|
|
@@ -25,47 +25,52 @@ import {
|
|
|
25
25
|
export function walkColumns(
|
|
26
26
|
columns: Column[],
|
|
27
27
|
scrollX: number,
|
|
28
|
-
viewportWidth: number,
|
|
28
|
+
viewportWidth: number, // Total width of the container
|
|
29
29
|
leftPinnedWidth: number,
|
|
30
30
|
rightPinnedWidth: number,
|
|
31
|
-
callback: ColumnWalkCallback
|
|
31
|
+
callback: ColumnWalkCallback,
|
|
32
|
+
availableWidth?: number // Width excluding vertical scrollbar
|
|
32
33
|
): 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);
|
|
34
|
+
const leftPinned = columns.filter((c) => c.pinned === 'left');
|
|
35
|
+
const rightPinned = columns.filter((c) => c.pinned === 'right');
|
|
36
|
+
const centerColumns = columns.filter((c) => !c.pinned);
|
|
37
|
+
|
|
38
|
+
const effectiveWidth = availableWidth ?? viewportWidth;
|
|
36
39
|
|
|
37
40
|
// 1. Left pinned columns (no scroll offset)
|
|
38
41
|
let x = 0;
|
|
39
42
|
for (const col of leftPinned) {
|
|
40
|
-
|
|
41
|
-
x
|
|
43
|
+
const width = Math.floor(col.width);
|
|
44
|
+
callback(col, x, width, true, 'left');
|
|
45
|
+
x += width;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
// 2. Center columns (with scroll offset and clipping)
|
|
45
|
-
const centerStartX = leftPinnedWidth;
|
|
46
|
-
const centerEndX =
|
|
47
|
-
const centerWidth = centerEndX - centerStartX;
|
|
49
|
+
const centerStartX = Math.floor(leftPinnedWidth);
|
|
50
|
+
const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
|
|
48
51
|
|
|
49
|
-
x =
|
|
52
|
+
x = centerStartX - scrollX;
|
|
50
53
|
for (const col of centerColumns) {
|
|
54
|
+
const width = Math.floor(col.width);
|
|
51
55
|
// Skip columns completely outside viewport
|
|
52
|
-
if (x +
|
|
53
|
-
x +=
|
|
56
|
+
if (x + width < centerStartX) {
|
|
57
|
+
x += width;
|
|
54
58
|
continue;
|
|
55
59
|
}
|
|
56
60
|
if (x > centerEndX) {
|
|
57
61
|
break; // Rest of columns are off-screen
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
callback(col, x,
|
|
61
|
-
x +=
|
|
64
|
+
callback(col, x, width, false);
|
|
65
|
+
x += width;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// 3. Right pinned columns (no scroll offset)
|
|
65
|
-
x =
|
|
69
|
+
x = centerEndX;
|
|
66
70
|
for (const col of rightPinned) {
|
|
67
|
-
|
|
68
|
-
x
|
|
71
|
+
const width = Math.floor(col.width);
|
|
72
|
+
callback(col, x, width, true, 'right');
|
|
73
|
+
x += width;
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -77,14 +82,21 @@ export function getPositionedColumns(
|
|
|
77
82
|
scrollX: number,
|
|
78
83
|
viewportWidth: number,
|
|
79
84
|
leftPinnedWidth: number,
|
|
80
|
-
rightPinnedWidth: number
|
|
85
|
+
rightPinnedWidth: number,
|
|
86
|
+
availableWidth?: number
|
|
81
87
|
): PositionedColumn[] {
|
|
82
88
|
const result: PositionedColumn[] = [];
|
|
83
89
|
|
|
84
|
-
walkColumns(
|
|
90
|
+
walkColumns(
|
|
91
|
+
columns,
|
|
92
|
+
scrollX,
|
|
93
|
+
viewportWidth,
|
|
94
|
+
leftPinnedWidth,
|
|
95
|
+
rightPinnedWidth,
|
|
85
96
|
(column, x, width, isPinned, pinSide) => {
|
|
86
97
|
result.push({ column, x, width, isPinned, pinSide });
|
|
87
|
-
}
|
|
98
|
+
},
|
|
99
|
+
availableWidth
|
|
88
100
|
);
|
|
89
101
|
|
|
90
102
|
return result;
|
|
@@ -95,12 +107,12 @@ export function getPositionedColumns(
|
|
|
95
107
|
*/
|
|
96
108
|
export function getPinnedWidths(columns: Column[]): { left: number; right: number } {
|
|
97
109
|
const left = columns
|
|
98
|
-
.filter(c => c.pinned === 'left')
|
|
99
|
-
.reduce((sum, c) => sum + c.width, 0);
|
|
100
|
-
|
|
110
|
+
.filter((c) => c.pinned === 'left')
|
|
111
|
+
.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
112
|
+
|
|
101
113
|
const right = columns
|
|
102
|
-
.filter(c => c.pinned === 'right')
|
|
103
|
-
.reduce((sum, c) => sum + c.width, 0);
|
|
114
|
+
.filter((c) => c.pinned === 'right')
|
|
115
|
+
.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
104
116
|
|
|
105
117
|
return { left, right };
|
|
106
118
|
}
|
|
@@ -124,7 +136,7 @@ export function walkRows(
|
|
|
124
136
|
for (let rowIndex = startRow; rowIndex < endRow; rowIndex++) {
|
|
125
137
|
const y = api ? api.getRowY(rowIndex) - scrollTop : rowIndex * rowHeight - scrollTop;
|
|
126
138
|
const rowNode = getRowNode(rowIndex);
|
|
127
|
-
const height =
|
|
139
|
+
const height = api && rowNode ? rowNode.rowHeight || rowHeight : rowHeight;
|
|
128
140
|
callback(rowIndex, y, height, rowNode);
|
|
129
141
|
}
|
|
130
142
|
}
|
|
@@ -141,20 +153,20 @@ export function getVisibleRowRange(
|
|
|
141
153
|
api?: GridApi
|
|
142
154
|
): { startRow: number; endRow: number } {
|
|
143
155
|
if (api) {
|
|
144
|
-
const startRow = Math.max(0, api.getRowAtY(scrollTop) - buffer);
|
|
156
|
+
const startRow = Math.max(0, Math.min(totalRowCount - 1, api.getRowAtY(scrollTop)) - buffer);
|
|
145
157
|
const endRow = Math.min(
|
|
146
158
|
totalRowCount,
|
|
147
|
-
api.getRowAtY(scrollTop + viewportHeight) + buffer + 1
|
|
159
|
+
api.getRowAtY(Math.max(0, scrollTop + viewportHeight)) + buffer + 1
|
|
148
160
|
);
|
|
149
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
startRow: Math.max(0, startRow),
|
|
163
|
+
endRow: Math.max(0, Math.min(totalRowCount, endRow)),
|
|
164
|
+
};
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
const startRow = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
|
|
153
168
|
const visibleRowCount = Math.ceil(viewportHeight / rowHeight);
|
|
154
|
-
const endRow = Math.min(
|
|
155
|
-
totalRowCount,
|
|
156
|
-
startRow + visibleRowCount + buffer * 2
|
|
157
|
-
);
|
|
169
|
+
const endRow = Math.min(totalRowCount, startRow + visibleRowCount + buffer * 2);
|
|
158
170
|
|
|
159
171
|
return { startRow, endRow };
|
|
160
172
|
}
|
|
@@ -180,7 +192,7 @@ export function walkCells(
|
|
|
180
192
|
scrollX: number,
|
|
181
193
|
scrollTop: number,
|
|
182
194
|
viewportWidth: number,
|
|
183
|
-
|
|
195
|
+
_viewportHeight: number,
|
|
184
196
|
rowHeight: number,
|
|
185
197
|
getRowNode: (index: number) => IRowNode | null,
|
|
186
198
|
callback: CellWalkCallback
|
|
@@ -189,8 +201,13 @@ export function walkCells(
|
|
|
189
201
|
|
|
190
202
|
// Walk columns for each row
|
|
191
203
|
walkRows(startRow, endRow, scrollTop, rowHeight, getRowNode, (rowIndex, y, height, rowNode) => {
|
|
192
|
-
walkColumns(
|
|
193
|
-
|
|
204
|
+
walkColumns(
|
|
205
|
+
columns,
|
|
206
|
+
scrollX,
|
|
207
|
+
viewportWidth,
|
|
208
|
+
leftPinnedWidth,
|
|
209
|
+
rightPinnedWidth,
|
|
210
|
+
(column, x, width, _isPinned) => {
|
|
194
211
|
callback(column, rowIndex, x, y, width, height, rowNode);
|
|
195
212
|
}
|
|
196
213
|
);
|
|
@@ -208,13 +225,15 @@ export function getColumnAtX(
|
|
|
208
225
|
columns: Column[],
|
|
209
226
|
x: number,
|
|
210
227
|
scrollX: number,
|
|
211
|
-
viewportWidth: number
|
|
228
|
+
viewportWidth: number,
|
|
229
|
+
availableWidth?: number
|
|
212
230
|
): { column: Column | null; index: number; localX: number } {
|
|
213
231
|
const { left: leftPinnedWidth, right: rightPinnedWidth } = getPinnedWidths(columns);
|
|
232
|
+
const effectiveWidth = availableWidth ?? viewportWidth;
|
|
214
233
|
|
|
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);
|
|
234
|
+
const leftPinned = columns.filter((c) => c.pinned === 'left');
|
|
235
|
+
const rightPinned = columns.filter((c) => c.pinned === 'right');
|
|
236
|
+
const centerColumns = columns.filter((c) => !c.pinned);
|
|
218
237
|
|
|
219
238
|
// Check left pinned
|
|
220
239
|
if (x < leftPinnedWidth) {
|
|
@@ -229,8 +248,9 @@ export function getColumnAtX(
|
|
|
229
248
|
}
|
|
230
249
|
|
|
231
250
|
// Check right pinned
|
|
232
|
-
|
|
233
|
-
|
|
251
|
+
const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
|
|
252
|
+
if (x > centerEndX) {
|
|
253
|
+
let colX = centerEndX;
|
|
234
254
|
for (let i = 0; i < rightPinned.length; i++) {
|
|
235
255
|
const col = rightPinned[i];
|
|
236
256
|
if (x < colX + col.width) {
|
|
@@ -258,18 +278,18 @@ export function getColumnAtX(
|
|
|
258
278
|
* Get column index in visible columns array
|
|
259
279
|
*/
|
|
260
280
|
export function getColumnIndex(columns: Column[], colId: string): number {
|
|
261
|
-
return columns.findIndex(c => c.colId === colId);
|
|
281
|
+
return columns.findIndex((c) => c.colId === colId);
|
|
262
282
|
}
|
|
263
283
|
|
|
264
284
|
/**
|
|
265
285
|
* Calculate total width of columns
|
|
266
286
|
*/
|
|
267
287
|
export function getTotalColumnWidth(columns: Column[]): number {
|
|
268
|
-
return columns.reduce((sum, col) => sum + col.width, 0);
|
|
288
|
+
return columns.reduce((sum, col) => sum + Math.floor(col.width), 0);
|
|
269
289
|
}
|
|
270
290
|
|
|
271
291
|
// ============================================================================
|
|
272
|
-
// ROW UTILITIES
|
|
292
|
+
// ROW UTILITIES
|
|
273
293
|
// ============================================================================
|
|
274
294
|
|
|
275
295
|
/**
|
|
@@ -291,7 +311,7 @@ export function isRowVisible(
|
|
|
291
311
|
const y = rowIndex * rowHeight;
|
|
292
312
|
const rowBottom = y + rowHeight;
|
|
293
313
|
const viewportBottom = scrollTop + viewportHeight;
|
|
294
|
-
|
|
314
|
+
|
|
295
315
|
return y < viewportBottom && rowBottom > scrollTop;
|
|
296
316
|
}
|
|
297
317
|
|
|
@@ -310,19 +330,26 @@ export function calculateVisibleRange(
|
|
|
310
330
|
viewportHeight: number,
|
|
311
331
|
rowHeight: number,
|
|
312
332
|
totalRowCount: number,
|
|
313
|
-
rowBuffer: number = 5
|
|
333
|
+
rowBuffer: number = 5,
|
|
334
|
+
availableWidth?: number
|
|
314
335
|
): VisibleRange {
|
|
315
336
|
const { startRow, endRow } = getVisibleRowRange(
|
|
316
|
-
scrollTop,
|
|
337
|
+
scrollTop,
|
|
338
|
+
viewportHeight,
|
|
339
|
+
rowHeight,
|
|
340
|
+
totalRowCount,
|
|
341
|
+
rowBuffer
|
|
317
342
|
);
|
|
318
343
|
|
|
344
|
+
const effectiveWidth = availableWidth ?? viewportWidth;
|
|
345
|
+
|
|
319
346
|
// 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');
|
|
347
|
+
const centerColumns = columns.filter((c) => !c.pinned);
|
|
348
|
+
const leftPinned = columns.filter((c) => c.pinned === 'left');
|
|
349
|
+
const rightPinned = columns.filter((c) => c.pinned === 'right');
|
|
323
350
|
|
|
324
|
-
const leftPinnedWidth = leftPinned.reduce((sum, c) => sum + c.width, 0);
|
|
325
|
-
const rightPinnedWidth = rightPinned.reduce((sum, c) => sum + c.width, 0);
|
|
351
|
+
const leftPinnedWidth = leftPinned.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
352
|
+
const rightPinnedWidth = rightPinned.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
326
353
|
|
|
327
354
|
// Find first and last visible center column
|
|
328
355
|
let startColumnIndex = leftPinned.length;
|
|
@@ -331,21 +358,24 @@ export function calculateVisibleRange(
|
|
|
331
358
|
let x = leftPinnedWidth - scrollLeft;
|
|
332
359
|
for (let i = 0; i < centerColumns.length; i++) {
|
|
333
360
|
const col = centerColumns[i];
|
|
334
|
-
|
|
361
|
+
const width = Math.floor(col.width);
|
|
362
|
+
if (x + width > leftPinnedWidth) {
|
|
335
363
|
startColumnIndex = leftPinned.length + i;
|
|
336
364
|
break;
|
|
337
365
|
}
|
|
338
|
-
x +=
|
|
366
|
+
x += width;
|
|
339
367
|
}
|
|
340
368
|
|
|
341
369
|
x = leftPinnedWidth - scrollLeft;
|
|
370
|
+
const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
|
|
342
371
|
for (let i = 0; i < centerColumns.length; i++) {
|
|
343
372
|
const col = centerColumns[i];
|
|
344
|
-
|
|
373
|
+
const width = Math.floor(col.width);
|
|
374
|
+
if (x > centerEndX) {
|
|
345
375
|
endColumnIndex = leftPinned.length + i;
|
|
346
376
|
break;
|
|
347
377
|
}
|
|
348
|
-
x +=
|
|
378
|
+
x += width;
|
|
349
379
|
}
|
|
350
380
|
|
|
351
381
|
// Add right pinned columns
|
|
@@ -357,4 +387,4 @@ export function calculateVisibleRange(
|
|
|
357
387
|
startColumnIndex,
|
|
358
388
|
endColumnIndex,
|
|
359
389
|
};
|
|
360
|
-
}
|
|
390
|
+
}
|
|
@@ -4,12 +4,8 @@
|
|
|
4
4
|
* Tests for DamageTracker class and related utilities.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
DamageTracker,
|
|
9
|
-
getDirtyBounds,
|
|
10
|
-
mergeRectangles,
|
|
11
|
-
} from './damage-tracker';
|
|
12
7
|
import { DirtyRegions, Rectangle } from '../render/types';
|
|
8
|
+
import { DamageTracker, getDirtyBounds, mergeRectangles } from './damage-tracker';
|
|
13
9
|
|
|
14
10
|
describe('DamageTracker', () => {
|
|
15
11
|
let tracker: DamageTracker;
|
|
@@ -221,7 +217,12 @@ describe('DamageTracker', () => {
|
|
|
221
217
|
[3, 4],
|
|
222
218
|
]);
|
|
223
219
|
const cells = tracker.getDirtyCells();
|
|
224
|
-
expect(cells).toEqual(
|
|
220
|
+
expect(cells).toEqual(
|
|
221
|
+
expect.arrayContaining([
|
|
222
|
+
[1, 2],
|
|
223
|
+
[3, 4],
|
|
224
|
+
])
|
|
225
|
+
);
|
|
225
226
|
});
|
|
226
227
|
});
|
|
227
228
|
|
|
@@ -441,4 +442,4 @@ describe('mergeRectangles', () => {
|
|
|
441
442
|
it('should handle empty array', () => {
|
|
442
443
|
expect(mergeRectangles([])).toEqual([]);
|
|
443
444
|
});
|
|
444
|
-
});
|
|
445
|
+
});
|
|
@@ -86,10 +86,7 @@ export class DamageTracker {
|
|
|
86
86
|
/**
|
|
87
87
|
* Mark dirty based on selection change
|
|
88
88
|
*/
|
|
89
|
-
markSelectionChanged(
|
|
90
|
-
oldSelection: Set<number>,
|
|
91
|
-
newSelection: Set<number>
|
|
92
|
-
): void {
|
|
89
|
+
markSelectionChanged(oldSelection: Set<number>, newSelection: Set<number>): void {
|
|
93
90
|
// Mark rows that changed selection state
|
|
94
91
|
for (const row of oldSelection) {
|
|
95
92
|
if (!newSelection.has(row)) {
|
|
@@ -186,7 +183,7 @@ export class DamageTracker {
|
|
|
186
183
|
* Get dirty cells
|
|
187
184
|
*/
|
|
188
185
|
getDirtyCells(): Array<[number, number]> {
|
|
189
|
-
return Array.from(this.damagedCells).map(key => {
|
|
186
|
+
return Array.from(this.damagedCells).map((key) => {
|
|
190
187
|
const [col, row] = key.split(',').map(Number);
|
|
191
188
|
return [col, row];
|
|
192
189
|
});
|
|
@@ -201,16 +198,8 @@ export class DamageTracker {
|
|
|
201
198
|
* - If too many individual cells, promote to row/column
|
|
202
199
|
* - If too many rows/columns, promote to full redraw
|
|
203
200
|
*/
|
|
204
|
-
optimize(thresholds: {
|
|
205
|
-
maxCells
|
|
206
|
-
maxRows?: number;
|
|
207
|
-
maxColumns?: number;
|
|
208
|
-
} = {}): void {
|
|
209
|
-
const {
|
|
210
|
-
maxCells = 50,
|
|
211
|
-
maxRows = 100,
|
|
212
|
-
maxColumns = 20,
|
|
213
|
-
} = thresholds;
|
|
201
|
+
optimize(thresholds: { maxCells?: number; maxRows?: number; maxColumns?: number } = {}): void {
|
|
202
|
+
const { maxCells = 50, maxRows = 100, maxColumns = 20 } = thresholds;
|
|
214
203
|
|
|
215
204
|
// If too many cells, check if we should promote to rows
|
|
216
205
|
if (this.damagedCells.size > maxCells) {
|
|
@@ -361,8 +350,7 @@ export function getDirtyBounds(
|
|
|
361
350
|
|
|
362
351
|
const width = columnWidths[col] || 0;
|
|
363
352
|
|
|
364
|
-
if (x + width >= 0 && x <= viewportWidth &&
|
|
365
|
-
y + rowHeight >= 0 && y <= viewportHeight) {
|
|
353
|
+
if (x + width >= 0 && x <= viewportWidth && y + rowHeight >= 0 && y <= viewportHeight) {
|
|
366
354
|
rects.push({
|
|
367
355
|
x: Math.max(0, x),
|
|
368
356
|
y: Math.max(0, y),
|
|
@@ -420,4 +408,4 @@ export function mergeRectangles(rects: Rectangle[]): Rectangle[] {
|
|
|
420
408
|
}
|
|
421
409
|
|
|
422
410
|
return merged;
|
|
423
|
-
}
|
|
411
|
+
}
|