argent-grid 0.1.0 → 0.2.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 +2 -2
- 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/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +91 -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/package.json +20 -6
- package/plan.md +50 -18
- 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 +327 -76
- package/src/lib/components/argent-grid.component.html +186 -64
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +642 -189
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.ts +302 -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 +366 -0
- package/src/lib/rendering/canvas-renderer.ts +418 -305
- 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 +46 -24
- package/src/lib/rendering/render/column-utils.ts +73 -0
- package/src/lib/rendering/render/hit-test.ts +55 -0
- package/src/lib/rendering/render/index.ts +79 -76
- package/src/lib/rendering/render/lines.ts +43 -43
- package/src/lib/rendering/render/primitives.ts +161 -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 +2 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +60 -50
- 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 +1165 -201
- package/src/lib/services/grid.service.ts +819 -187
- 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 +73 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +188 -0
- package/src/stories/ArgentGrid.stories.ts +277 -0
- package/src/stories/Benchmark.stories.ts +74 -0
- package/src/stories/CellRenderers.stories.ts +221 -0
- package/src/stories/Filtering.stories.ts +252 -0
- package/src/stories/Grouping.stories.ts +217 -0
- package/src/stories/Theming.stories.ts +124 -0
- package/src/stories/benchmark-wrapper.component.ts +315 -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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering Primitives for Canvas Renderer
|
|
3
|
+
*
|
|
4
|
+
* Provides specialized drawing functions for grid UI elements:
|
|
5
|
+
* - Checkboxes
|
|
6
|
+
* - Group indicators (expand/collapse)
|
|
7
|
+
* - Sparklines
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { SparklineOptions } from '../../types/ag-grid-types';
|
|
11
|
+
import { GridTheme } from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Draw a grid checkbox
|
|
15
|
+
*/
|
|
16
|
+
export function drawCheckbox(
|
|
17
|
+
ctx: CanvasRenderingContext2D,
|
|
18
|
+
x: number,
|
|
19
|
+
y: number,
|
|
20
|
+
size: number,
|
|
21
|
+
checked: boolean,
|
|
22
|
+
theme: GridTheme
|
|
23
|
+
): void {
|
|
24
|
+
// Draw checkbox border
|
|
25
|
+
ctx.strokeStyle = theme.borderColor;
|
|
26
|
+
ctx.lineWidth = 1.2;
|
|
27
|
+
ctx.strokeRect(Math.floor(x) + 0.5, Math.floor(y) + 0.5, size, size);
|
|
28
|
+
|
|
29
|
+
// Draw checkmark if checked
|
|
30
|
+
if (checked) {
|
|
31
|
+
ctx.strokeStyle = theme.textCell;
|
|
32
|
+
ctx.lineWidth = 1.2;
|
|
33
|
+
ctx.beginPath();
|
|
34
|
+
const padding = 3;
|
|
35
|
+
const checkX = x + padding;
|
|
36
|
+
const checkY = y + size / 2;
|
|
37
|
+
const checkWidth = size - padding * 2;
|
|
38
|
+
|
|
39
|
+
// Draw checkmark
|
|
40
|
+
ctx.moveTo(checkX, checkY);
|
|
41
|
+
ctx.lineTo(checkX + checkWidth / 3, checkY + checkWidth / 3);
|
|
42
|
+
ctx.lineTo(checkX + checkWidth, checkY - checkWidth / 3);
|
|
43
|
+
ctx.stroke();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Draw a group expand/collapse indicator
|
|
49
|
+
*/
|
|
50
|
+
export function drawGroupIndicator(
|
|
51
|
+
ctx: CanvasRenderingContext2D,
|
|
52
|
+
x: number,
|
|
53
|
+
y: number,
|
|
54
|
+
rowHeight: number,
|
|
55
|
+
expanded: boolean,
|
|
56
|
+
theme: GridTheme
|
|
57
|
+
): void {
|
|
58
|
+
ctx.beginPath();
|
|
59
|
+
ctx.strokeStyle = theme.textCell;
|
|
60
|
+
ctx.lineWidth = 1;
|
|
61
|
+
const centerY = Math.floor(y + rowHeight / 2);
|
|
62
|
+
const size = theme.groupIndicatorSize;
|
|
63
|
+
|
|
64
|
+
if (expanded) {
|
|
65
|
+
// Expanded: horizontal line
|
|
66
|
+
ctx.moveTo(Math.floor(x), centerY);
|
|
67
|
+
ctx.lineTo(Math.floor(x + size), centerY);
|
|
68
|
+
} else {
|
|
69
|
+
// Collapsed: plus sign
|
|
70
|
+
const halfSize = size / 2;
|
|
71
|
+
ctx.moveTo(Math.floor(x), centerY);
|
|
72
|
+
ctx.lineTo(Math.floor(x + size), centerY);
|
|
73
|
+
ctx.moveTo(Math.floor(x + halfSize), centerY - halfSize);
|
|
74
|
+
ctx.lineTo(Math.floor(x + halfSize), centerY + halfSize);
|
|
75
|
+
}
|
|
76
|
+
ctx.stroke();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Draw a sparkline within a cell
|
|
81
|
+
*/
|
|
82
|
+
export function drawSparkline(
|
|
83
|
+
ctx: CanvasRenderingContext2D,
|
|
84
|
+
data: any[],
|
|
85
|
+
x: number,
|
|
86
|
+
y: number,
|
|
87
|
+
width: number,
|
|
88
|
+
height: number,
|
|
89
|
+
options: SparklineOptions
|
|
90
|
+
): void {
|
|
91
|
+
if (!Array.isArray(data) || data.length === 0) return;
|
|
92
|
+
|
|
93
|
+
const padding = options.padding || { top: 4, bottom: 4, left: 4, right: 4 };
|
|
94
|
+
const drawX = x + (padding.left || 0);
|
|
95
|
+
const drawY = y + (padding.top || 0);
|
|
96
|
+
const drawWidth = width - (padding.left || 0) - (padding.right || 0);
|
|
97
|
+
const drawHeight = height - (padding.top || 0) - (padding.bottom || 0);
|
|
98
|
+
|
|
99
|
+
if (drawWidth <= 0 || drawHeight <= 0) return;
|
|
100
|
+
|
|
101
|
+
const min = Math.min(...data);
|
|
102
|
+
const max = Math.max(...data);
|
|
103
|
+
const range = max - min || 1;
|
|
104
|
+
|
|
105
|
+
const type = options.type || 'line';
|
|
106
|
+
|
|
107
|
+
ctx.save();
|
|
108
|
+
|
|
109
|
+
if (type === 'line' || type === 'area') {
|
|
110
|
+
ctx.beginPath();
|
|
111
|
+
for (let i = 0; i < data.length; i++) {
|
|
112
|
+
const px = drawX + (i / (data.length - 1)) * drawWidth;
|
|
113
|
+
const py = drawY + drawHeight - ((data[i] - min) / range) * drawHeight;
|
|
114
|
+
|
|
115
|
+
if (i === 0) ctx.moveTo(px, py);
|
|
116
|
+
else ctx.lineTo(px, py);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (type === 'area') {
|
|
120
|
+
const areaOptions = options.area || {};
|
|
121
|
+
ctx.lineTo(drawX + drawWidth, drawY + drawHeight);
|
|
122
|
+
ctx.lineTo(drawX, drawY + drawHeight);
|
|
123
|
+
ctx.closePath();
|
|
124
|
+
ctx.fillStyle = areaOptions.fill || 'rgba(33, 150, 243, 0.3)';
|
|
125
|
+
ctx.fill();
|
|
126
|
+
|
|
127
|
+
// Stroke the top line
|
|
128
|
+
ctx.beginPath();
|
|
129
|
+
for (let i = 0; i < data.length; i++) {
|
|
130
|
+
const px = drawX + (i / (data.length - 1)) * drawWidth;
|
|
131
|
+
const py = drawY + drawHeight - ((data[i] - min) / range) * drawHeight;
|
|
132
|
+
if (i === 0) ctx.moveTo(px, py);
|
|
133
|
+
else ctx.lineTo(px, py);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const lineOptions = (type === 'area' ? options.area : options.line) || {};
|
|
138
|
+
ctx.strokeStyle = lineOptions.stroke || '#2196f3';
|
|
139
|
+
ctx.lineWidth = lineOptions.strokeWidth || 1.5;
|
|
140
|
+
ctx.lineJoin = 'round';
|
|
141
|
+
ctx.lineCap = 'round';
|
|
142
|
+
ctx.stroke();
|
|
143
|
+
} else if (type === 'column' || type === 'bar') {
|
|
144
|
+
const colOptions = options.column || {};
|
|
145
|
+
const colPadding = colOptions.padding || 0.1;
|
|
146
|
+
const colWidth = drawWidth / data.length;
|
|
147
|
+
const barWidth = colWidth * (1 - colPadding);
|
|
148
|
+
|
|
149
|
+
ctx.fillStyle = colOptions.fill || '#2196f3';
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < data.length; i++) {
|
|
152
|
+
const px = drawX + i * colWidth + (colWidth * colPadding) / 2;
|
|
153
|
+
const valHeight = ((data[i] - min) / range) * drawHeight;
|
|
154
|
+
const py = drawY + drawHeight - valHeight;
|
|
155
|
+
|
|
156
|
+
ctx.fillRect(Math.floor(px), Math.floor(py), Math.floor(barWidth), Math.ceil(valHeight));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ctx.restore();
|
|
161
|
+
}
|
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
createTheme,
|
|
9
9
|
DARK_THEME,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
DEFAULT_THEME,
|
|
11
|
+
getCellBackgroundColor,
|
|
12
12
|
getFontFromTheme,
|
|
13
13
|
getRowTheme,
|
|
14
|
-
getCellBackgroundColor,
|
|
15
14
|
getThemePreset,
|
|
16
|
-
|
|
15
|
+
mergeTheme,
|
|
16
|
+
THEME_PRESETS,
|
|
17
17
|
} from './theme';
|
|
18
|
-
import { GridTheme
|
|
18
|
+
import { GridTheme } from './types';
|
|
19
19
|
|
|
20
20
|
describe('Theme System', () => {
|
|
21
21
|
describe('DEFAULT_THEME', () => {
|
|
@@ -86,11 +86,7 @@ describe('Theme System', () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('should apply multiple overrides in order', () => {
|
|
89
|
-
const result = mergeTheme(
|
|
90
|
-
DEFAULT_THEME,
|
|
91
|
-
{ bgCell: '#ff0000' },
|
|
92
|
-
{ bgCell: '#00ff00' }
|
|
93
|
-
);
|
|
89
|
+
const result = mergeTheme(DEFAULT_THEME, { bgCell: '#ff0000' }, { bgCell: '#00ff00' });
|
|
94
90
|
|
|
95
91
|
expect(result.bgCell).toBe('#00ff00');
|
|
96
92
|
});
|
|
@@ -279,4 +275,4 @@ describe('Theme System', () => {
|
|
|
279
275
|
expect(comfortableFull.fontSize).toBeGreaterThan(DEFAULT_THEME.fontSize);
|
|
280
276
|
});
|
|
281
277
|
});
|
|
282
|
-
});
|
|
278
|
+
});
|
|
@@ -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 { Column, IRowNode
|
|
7
|
+
import { ColDef, Column, IRowNode } from '../../types/ag-grid-types';
|
|
8
8
|
|
|
9
9
|
// ============================================================================
|
|
10
10
|
// CORE RENDERING TYPES
|
|
@@ -276,4 +276,4 @@ export interface GridMouseEvent {
|
|
|
276
276
|
hitTest: HitTestResult;
|
|
277
277
|
canvasX: number;
|
|
278
278
|
canvasY: number;
|
|
279
|
-
}
|
|
279
|
+
}
|
|
@@ -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
|
// ============================================================================
|
|
@@ -30,42 +30,44 @@ export function walkColumns(
|
|
|
30
30
|
rightPinnedWidth: number,
|
|
31
31
|
callback: ColumnWalkCallback
|
|
32
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);
|
|
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
36
|
|
|
37
37
|
// 1. Left pinned columns (no scroll offset)
|
|
38
38
|
let x = 0;
|
|
39
39
|
for (const col of leftPinned) {
|
|
40
|
-
|
|
41
|
-
x
|
|
40
|
+
const width = Math.floor(col.width);
|
|
41
|
+
callback(col, x, width, true, 'left');
|
|
42
|
+
x += width;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// 2. Center columns (with scroll offset and clipping)
|
|
45
|
-
const centerStartX = leftPinnedWidth;
|
|
46
|
-
const centerEndX = viewportWidth - rightPinnedWidth;
|
|
47
|
-
const centerWidth = centerEndX - centerStartX;
|
|
46
|
+
const centerStartX = Math.floor(leftPinnedWidth);
|
|
47
|
+
const centerEndX = Math.floor(viewportWidth - rightPinnedWidth);
|
|
48
48
|
|
|
49
|
-
x =
|
|
49
|
+
x = centerStartX - scrollX;
|
|
50
50
|
for (const col of centerColumns) {
|
|
51
|
+
const width = Math.floor(col.width);
|
|
51
52
|
// Skip columns completely outside viewport
|
|
52
|
-
if (x +
|
|
53
|
-
x +=
|
|
53
|
+
if (x + width < centerStartX) {
|
|
54
|
+
x += width;
|
|
54
55
|
continue;
|
|
55
56
|
}
|
|
56
57
|
if (x > centerEndX) {
|
|
57
58
|
break; // Rest of columns are off-screen
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
callback(col, x,
|
|
61
|
-
x +=
|
|
61
|
+
callback(col, x, width, false);
|
|
62
|
+
x += width;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
// 3. Right pinned columns (no scroll offset)
|
|
65
|
-
x =
|
|
66
|
+
x = centerEndX;
|
|
66
67
|
for (const col of rightPinned) {
|
|
67
|
-
|
|
68
|
-
x
|
|
68
|
+
const width = Math.floor(col.width);
|
|
69
|
+
callback(col, x, width, true, 'right');
|
|
70
|
+
x += width;
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -81,7 +83,12 @@ export function getPositionedColumns(
|
|
|
81
83
|
): PositionedColumn[] {
|
|
82
84
|
const result: PositionedColumn[] = [];
|
|
83
85
|
|
|
84
|
-
walkColumns(
|
|
86
|
+
walkColumns(
|
|
87
|
+
columns,
|
|
88
|
+
scrollX,
|
|
89
|
+
viewportWidth,
|
|
90
|
+
leftPinnedWidth,
|
|
91
|
+
rightPinnedWidth,
|
|
85
92
|
(column, x, width, isPinned, pinSide) => {
|
|
86
93
|
result.push({ column, x, width, isPinned, pinSide });
|
|
87
94
|
}
|
|
@@ -95,12 +102,12 @@ export function getPositionedColumns(
|
|
|
95
102
|
*/
|
|
96
103
|
export function getPinnedWidths(columns: Column[]): { left: number; right: number } {
|
|
97
104
|
const left = columns
|
|
98
|
-
.filter(c => c.pinned === 'left')
|
|
99
|
-
.reduce((sum, c) => sum + c.width, 0);
|
|
100
|
-
|
|
105
|
+
.filter((c) => c.pinned === 'left')
|
|
106
|
+
.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
107
|
+
|
|
101
108
|
const right = columns
|
|
102
|
-
.filter(c => c.pinned === 'right')
|
|
103
|
-
.reduce((sum, c) => sum + c.width, 0);
|
|
109
|
+
.filter((c) => c.pinned === 'right')
|
|
110
|
+
.reduce((sum, c) => sum + Math.floor(c.width), 0);
|
|
104
111
|
|
|
105
112
|
return { left, right };
|
|
106
113
|
}
|
|
@@ -124,7 +131,7 @@ export function walkRows(
|
|
|
124
131
|
for (let rowIndex = startRow; rowIndex < endRow; rowIndex++) {
|
|
125
132
|
const y = api ? api.getRowY(rowIndex) - scrollTop : rowIndex * rowHeight - scrollTop;
|
|
126
133
|
const rowNode = getRowNode(rowIndex);
|
|
127
|
-
const height =
|
|
134
|
+
const height = api && rowNode ? rowNode.rowHeight || rowHeight : rowHeight;
|
|
128
135
|
callback(rowIndex, y, height, rowNode);
|
|
129
136
|
}
|
|
130
137
|
}
|
|
@@ -142,19 +149,13 @@ export function getVisibleRowRange(
|
|
|
142
149
|
): { startRow: number; endRow: number } {
|
|
143
150
|
if (api) {
|
|
144
151
|
const startRow = Math.max(0, api.getRowAtY(scrollTop) - buffer);
|
|
145
|
-
const endRow = Math.min(
|
|
146
|
-
totalRowCount,
|
|
147
|
-
api.getRowAtY(scrollTop + viewportHeight) + buffer + 1
|
|
148
|
-
);
|
|
152
|
+
const endRow = Math.min(totalRowCount, api.getRowAtY(scrollTop + viewportHeight) + buffer + 1);
|
|
149
153
|
return { startRow, endRow };
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
const startRow = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
|
|
153
157
|
const visibleRowCount = Math.ceil(viewportHeight / rowHeight);
|
|
154
|
-
const endRow = Math.min(
|
|
155
|
-
totalRowCount,
|
|
156
|
-
startRow + visibleRowCount + buffer * 2
|
|
157
|
-
);
|
|
158
|
+
const endRow = Math.min(totalRowCount, startRow + visibleRowCount + buffer * 2);
|
|
158
159
|
|
|
159
160
|
return { startRow, endRow };
|
|
160
161
|
}
|
|
@@ -180,7 +181,7 @@ export function walkCells(
|
|
|
180
181
|
scrollX: number,
|
|
181
182
|
scrollTop: number,
|
|
182
183
|
viewportWidth: number,
|
|
183
|
-
|
|
184
|
+
_viewportHeight: number,
|
|
184
185
|
rowHeight: number,
|
|
185
186
|
getRowNode: (index: number) => IRowNode | null,
|
|
186
187
|
callback: CellWalkCallback
|
|
@@ -189,8 +190,13 @@ export function walkCells(
|
|
|
189
190
|
|
|
190
191
|
// Walk columns for each row
|
|
191
192
|
walkRows(startRow, endRow, scrollTop, rowHeight, getRowNode, (rowIndex, y, height, rowNode) => {
|
|
192
|
-
walkColumns(
|
|
193
|
-
|
|
193
|
+
walkColumns(
|
|
194
|
+
columns,
|
|
195
|
+
scrollX,
|
|
196
|
+
viewportWidth,
|
|
197
|
+
leftPinnedWidth,
|
|
198
|
+
rightPinnedWidth,
|
|
199
|
+
(column, x, width, _isPinned) => {
|
|
194
200
|
callback(column, rowIndex, x, y, width, height, rowNode);
|
|
195
201
|
}
|
|
196
202
|
);
|
|
@@ -212,9 +218,9 @@ export function getColumnAtX(
|
|
|
212
218
|
): { column: Column | null; index: number; localX: number } {
|
|
213
219
|
const { left: leftPinnedWidth, right: rightPinnedWidth } = getPinnedWidths(columns);
|
|
214
220
|
|
|
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);
|
|
221
|
+
const leftPinned = columns.filter((c) => c.pinned === 'left');
|
|
222
|
+
const rightPinned = columns.filter((c) => c.pinned === 'right');
|
|
223
|
+
const centerColumns = columns.filter((c) => !c.pinned);
|
|
218
224
|
|
|
219
225
|
// Check left pinned
|
|
220
226
|
if (x < leftPinnedWidth) {
|
|
@@ -258,7 +264,7 @@ export function getColumnAtX(
|
|
|
258
264
|
* Get column index in visible columns array
|
|
259
265
|
*/
|
|
260
266
|
export function getColumnIndex(columns: Column[], colId: string): number {
|
|
261
|
-
return columns.findIndex(c => c.colId === colId);
|
|
267
|
+
return columns.findIndex((c) => c.colId === colId);
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
/**
|
|
@@ -269,7 +275,7 @@ export function getTotalColumnWidth(columns: Column[]): number {
|
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
// ============================================================================
|
|
272
|
-
// ROW UTILITIES
|
|
278
|
+
// ROW UTILITIES
|
|
273
279
|
// ============================================================================
|
|
274
280
|
|
|
275
281
|
/**
|
|
@@ -291,7 +297,7 @@ export function isRowVisible(
|
|
|
291
297
|
const y = rowIndex * rowHeight;
|
|
292
298
|
const rowBottom = y + rowHeight;
|
|
293
299
|
const viewportBottom = scrollTop + viewportHeight;
|
|
294
|
-
|
|
300
|
+
|
|
295
301
|
return y < viewportBottom && rowBottom > scrollTop;
|
|
296
302
|
}
|
|
297
303
|
|
|
@@ -313,13 +319,17 @@ export function calculateVisibleRange(
|
|
|
313
319
|
rowBuffer: number = 5
|
|
314
320
|
): VisibleRange {
|
|
315
321
|
const { startRow, endRow } = getVisibleRowRange(
|
|
316
|
-
scrollTop,
|
|
322
|
+
scrollTop,
|
|
323
|
+
viewportHeight,
|
|
324
|
+
rowHeight,
|
|
325
|
+
totalRowCount,
|
|
326
|
+
rowBuffer
|
|
317
327
|
);
|
|
318
328
|
|
|
319
329
|
// 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');
|
|
330
|
+
const centerColumns = columns.filter((c) => !c.pinned);
|
|
331
|
+
const leftPinned = columns.filter((c) => c.pinned === 'left');
|
|
332
|
+
const rightPinned = columns.filter((c) => c.pinned === 'right');
|
|
323
333
|
|
|
324
334
|
const leftPinnedWidth = leftPinned.reduce((sum, c) => sum + c.width, 0);
|
|
325
335
|
const rightPinnedWidth = rightPinned.reduce((sum, c) => sum + c.width, 0);
|
|
@@ -357,4 +367,4 @@ export function calculateVisibleRange(
|
|
|
357
367
|
startColumnIndex,
|
|
358
368
|
endColumnIndex,
|
|
359
369
|
};
|
|
360
|
-
}
|
|
370
|
+
}
|
|
@@ -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
|
+
});
|