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.
Files changed (108) hide show
  1. package/.github/workflows/ci.yml +69 -0
  2. package/.github/workflows/pages.yml +6 -12
  3. package/.storybook/main.ts +20 -0
  4. package/.storybook/preview.ts +18 -0
  5. package/.storybook/tsconfig.json +24 -0
  6. package/AGENTS.md +2 -2
  7. package/README.md +51 -34
  8. package/angular.json +66 -0
  9. package/biome.json +66 -0
  10. package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
  11. package/docs/AG-GRID-COMPARISON.md +725 -0
  12. package/docs/CELL-RENDERER-GUIDE.md +241 -0
  13. package/docs/CONTEXT-MENU-GUIDE.md +371 -0
  14. package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
  15. package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
  16. package/docs/PERFORMANCE-REVIEW.md +571 -0
  17. package/docs/RESEARCH-STATUS.md +234 -0
  18. package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
  19. package/docs/STORYBOOK-REFACTOR.md +215 -0
  20. package/docs/STORYBOOK-STATUS.md +156 -0
  21. package/docs/TEST-COVERAGE-REPORT.md +276 -0
  22. package/docs/THEME-API-GUIDE.md +445 -0
  23. package/docs/THEME-API-PLAN.md +364 -0
  24. package/e2e/advanced.spec.ts +109 -0
  25. package/e2e/argentgrid.spec.ts +65 -0
  26. package/e2e/benchmark.spec.ts +52 -0
  27. package/e2e/screenshots.spec.ts +52 -0
  28. package/e2e/theming.spec.ts +35 -0
  29. package/e2e/visual.spec.ts +91 -0
  30. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  31. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  32. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  33. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  34. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  35. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  36. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  37. package/package.json +20 -6
  38. package/plan.md +50 -18
  39. package/playwright.config.ts +38 -0
  40. package/setup-vitest.ts +10 -13
  41. package/src/lib/argent-grid.module.ts +10 -12
  42. package/src/lib/components/argent-grid.component.css +327 -76
  43. package/src/lib/components/argent-grid.component.html +186 -64
  44. package/src/lib/components/argent-grid.component.spec.ts +120 -160
  45. package/src/lib/components/argent-grid.component.ts +642 -189
  46. package/src/lib/components/argent-grid.selection.spec.ts +132 -0
  47. package/src/lib/components/set-filter/set-filter.component.ts +302 -0
  48. package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
  49. package/src/lib/directives/click-outside.directive.ts +19 -0
  50. package/src/lib/rendering/canvas-renderer.spec.ts +366 -0
  51. package/src/lib/rendering/canvas-renderer.ts +418 -305
  52. package/src/lib/rendering/live-data-handler.ts +110 -0
  53. package/src/lib/rendering/live-data-optimizations.ts +133 -0
  54. package/src/lib/rendering/render/blit.spec.ts +16 -27
  55. package/src/lib/rendering/render/blit.ts +48 -36
  56. package/src/lib/rendering/render/cells.spec.ts +132 -0
  57. package/src/lib/rendering/render/cells.ts +46 -24
  58. package/src/lib/rendering/render/column-utils.ts +73 -0
  59. package/src/lib/rendering/render/hit-test.ts +55 -0
  60. package/src/lib/rendering/render/index.ts +79 -76
  61. package/src/lib/rendering/render/lines.ts +43 -43
  62. package/src/lib/rendering/render/primitives.ts +161 -0
  63. package/src/lib/rendering/render/theme.spec.ts +8 -12
  64. package/src/lib/rendering/render/theme.ts +7 -10
  65. package/src/lib/rendering/render/types.ts +2 -2
  66. package/src/lib/rendering/render/walk.spec.ts +35 -38
  67. package/src/lib/rendering/render/walk.ts +60 -50
  68. package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
  69. package/src/lib/rendering/utils/damage-tracker.ts +6 -18
  70. package/src/lib/rendering/utils/index.ts +1 -1
  71. package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
  72. package/src/lib/services/grid.service.spec.ts +1165 -201
  73. package/src/lib/services/grid.service.ts +819 -187
  74. package/src/lib/themes/parts/color-schemes.ts +132 -0
  75. package/src/lib/themes/parts/icon-sets.ts +258 -0
  76. package/src/lib/themes/theme-builder.ts +347 -0
  77. package/src/lib/themes/theme-quartz.ts +72 -0
  78. package/src/lib/themes/types.ts +238 -0
  79. package/src/lib/types/ag-grid-types.ts +73 -14
  80. package/src/public-api.ts +39 -9
  81. package/src/stories/Advanced.stories.ts +188 -0
  82. package/src/stories/ArgentGrid.stories.ts +277 -0
  83. package/src/stories/Benchmark.stories.ts +74 -0
  84. package/src/stories/CellRenderers.stories.ts +221 -0
  85. package/src/stories/Filtering.stories.ts +252 -0
  86. package/src/stories/Grouping.stories.ts +217 -0
  87. package/src/stories/Theming.stories.ts +124 -0
  88. package/src/stories/benchmark-wrapper.component.ts +315 -0
  89. package/tsconfig.storybook.json +10 -0
  90. package/vitest.config.ts +9 -9
  91. package/demo-app/README.md +0 -70
  92. package/demo-app/angular.json +0 -78
  93. package/demo-app/e2e/benchmark.spec.ts +0 -53
  94. package/demo-app/e2e/demo-page.spec.ts +0 -77
  95. package/demo-app/e2e/grid-features.spec.ts +0 -269
  96. package/demo-app/package-lock.json +0 -14023
  97. package/demo-app/package.json +0 -36
  98. package/demo-app/playwright-test-menu.js +0 -19
  99. package/demo-app/playwright.config.ts +0 -23
  100. package/demo-app/src/app/app.component.ts +0 -10
  101. package/demo-app/src/app/app.config.ts +0 -13
  102. package/demo-app/src/app/app.routes.ts +0 -7
  103. package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
  104. package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
  105. package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
  106. package/demo-app/src/index.html +0 -19
  107. package/demo-app/src/main.ts +0 -6
  108. 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
- DEFAULT_THEME,
8
+ createTheme,
9
9
  DARK_THEME,
10
- THEME_PRESETS,
11
- mergeTheme,
10
+ DEFAULT_THEME,
11
+ getCellBackgroundColor,
12
12
  getFontFromTheme,
13
13
  getRowTheme,
14
- getCellBackgroundColor,
15
14
  getThemePreset,
16
- createTheme,
15
+ mergeTheme,
16
+ THEME_PRESETS,
17
17
  } from './theme';
18
- import { GridTheme, PartialTheme } from './types';
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: '#e3f2fd',
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, ColDef, GridApi } from '../../types/ag-grid-types';
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
- walkColumns,
9
- getPositionedColumns,
10
- getPinnedWidths,
11
- walkRows,
12
- getVisibleRowRange,
13
- getRowY,
14
- walkCells,
9
+ calculateVisibleRange,
15
10
  getColumnAtX,
16
11
  getColumnIndex,
17
- getTotalColumnWidth,
12
+ getPinnedWidths,
13
+ getPositionedColumns,
18
14
  getRowAtY,
15
+ getRowY,
16
+ getTotalColumnWidth,
17
+ getVisibleRowRange,
19
18
  isRowVisible,
20
- calculateVisibleRange,
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, // scrollTop
70
- 400, // viewportHeight
71
- 32, // rowHeight
68
+ 100, // scrollTop
69
+ 400, // viewportHeight
70
+ 32, // rowHeight
72
71
  1000, // totalRowCount
73
- 5 // buffer
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, // scrollTop
86
- 400, // viewportHeight
87
- 32, // rowHeight
84
+ 0, // scrollTop
85
+ 400, // viewportHeight
86
+ 32, // rowHeight
88
87
  1000, // totalRowCount
89
- 5 // buffer
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, // scrollTop
98
- 400, // viewportHeight
99
- 32, // rowHeight
100
- 10, // totalRowCount (small)
101
- 5 // buffer
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, width, isPinned, pinSide) => {
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, x, width, isPinned) => {
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, height, rowNode) => {
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, height, rowNode) => {
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, // scrollTop
315
- 0, // scrollLeft
316
- 400, // viewportWidth
317
- 400, // viewportHeight
318
- 32, // rowHeight
319
- 100, // totalRowCount
320
- 5 // buffer
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, IRowNode, GridApi } from '../../types/ag-grid-types';
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
- VisibleRange
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
- callback(col, x, col.width, true, 'left');
41
- x += col.width;
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 = leftPinnedWidth - scrollX;
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 + col.width < centerStartX) {
53
- x += col.width;
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, col.width, false);
61
- x += col.width;
61
+ callback(col, x, width, false);
62
+ x += width;
62
63
  }
63
64
 
64
65
  // 3. Right pinned columns (no scroll offset)
65
- x = viewportWidth - rightPinnedWidth;
66
+ x = centerEndX;
66
67
  for (const col of rightPinned) {
67
- callback(col, x, col.width, true, 'right');
68
- x += col.width;
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(columns, scrollX, viewportWidth, leftPinnedWidth, rightPinnedWidth,
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 = (api && rowNode) ? (rowNode.rowHeight || rowHeight) : rowHeight;
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
- viewportHeight: number,
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(columns, scrollX, viewportWidth, leftPinnedWidth, rightPinnedWidth,
193
- (column, x, width, isPinned) => {
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, viewportHeight, rowHeight, totalRowCount, rowBuffer
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(expect.arrayContaining([[1, 2], [3, 4]]));
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
+ });