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
@@ -4,9 +4,9 @@
4
4
  * Handles drawing of individual cells with prep/draw cycle optimization.
5
5
  */
6
6
 
7
- import { Column, IRowNode, ColDef, GridApi } from '../../types/ag-grid-types';
8
- import { CellDrawContext, ColumnPrepResult, GridTheme } from './types';
7
+ import { ColDef, Column, GridApi, IRowNode } from '../../types/ag-grid-types';
9
8
  import { getFontFromTheme } from './theme';
9
+ import { CellDrawContext, ColumnPrepResult, GridTheme } from './types';
10
10
 
11
11
  /**
12
12
  * Get value from object using path (e.g. 'pivotData.NY.salary')
@@ -14,8 +14,8 @@ import { getFontFromTheme } from './theme';
14
14
  export function getValueByPath(obj: any, path: string): any {
15
15
  if (!path || !obj) return undefined;
16
16
  if (!path.includes('.')) return obj[path];
17
-
18
- return path.split('.').reduce((acc, part) => acc && acc[part], obj);
17
+
18
+ return path.split('.').reduce((acc, part) => acc?.[part], obj);
19
19
  }
20
20
 
21
21
  // ============================================================================
@@ -75,7 +75,7 @@ export function drawCell<TData = any>(
75
75
  prep: ColumnPrepResult<TData>,
76
76
  context: CellDrawContext<TData>
77
77
  ): void {
78
- const { x, y, width, height, value, formattedValue, column, rowNode } = context;
78
+ const { rowNode } = context;
79
79
 
80
80
  // Draw cell background
81
81
  drawCellBackground(ctx, context);
@@ -113,7 +113,7 @@ export function drawCellBackground<TData = any>(
113
113
  */
114
114
  export function drawCellContent<TData = any>(
115
115
  ctx: CanvasRenderingContext2D,
116
- prep: ColumnPrepResult<TData>,
116
+ _prep: ColumnPrepResult<TData>,
117
117
  context: CellDrawContext<TData>
118
118
  ): void {
119
119
  const { x, y, width, height, formattedValue, theme } = context;
@@ -142,10 +142,10 @@ export function drawCellContent<TData = any>(
142
142
  */
143
143
  export function drawGroupIndicators<TData = any>(
144
144
  ctx: CanvasRenderingContext2D,
145
- prep: ColumnPrepResult<TData>,
145
+ _prep: ColumnPrepResult<TData>,
146
146
  context: CellDrawContext<TData>
147
147
  ): void {
148
- const { x, y, width, height, column, rowNode, theme, isEvenRow } = context;
148
+ const { x, y, height, column, rowNode, theme } = context;
149
149
 
150
150
  if (!rowNode) return;
151
151
 
@@ -174,7 +174,7 @@ export function drawGroupIndicators<TData = any>(
174
174
  const size = theme.groupIndicatorSize;
175
175
  const centerX = Math.floor(indicatorX + size / 2);
176
176
  const centerY = Math.floor(indicatorY);
177
-
177
+
178
178
  // Horizontal line
179
179
  ctx.moveTo(Math.floor(indicatorX), centerY);
180
180
  ctx.lineTo(Math.floor(indicatorX + size), centerY);
@@ -212,7 +212,7 @@ export function truncateText(
212
212
 
213
213
  while (start < end) {
214
214
  const mid = Math.floor((start + end) / 2);
215
- const truncated = text.slice(0, mid) + '...';
215
+ const truncated = `${text.slice(0, mid)}...`;
216
216
 
217
217
  if (ctx.measureText(truncated).width <= maxWidth) {
218
218
  start = mid + 1;
@@ -221,16 +221,13 @@ export function truncateText(
221
221
  }
222
222
  }
223
223
 
224
- return text.slice(0, Math.max(0, start - 1)) + '...';
224
+ return `${text.slice(0, Math.max(0, start - 1))}...`;
225
225
  }
226
226
 
227
227
  /**
228
228
  * Measure text width
229
229
  */
230
- export function measureText(
231
- ctx: CanvasRenderingContext2D,
232
- text: string
233
- ): number {
230
+ export function measureText(ctx: CanvasRenderingContext2D, text: string): number {
234
231
  return ctx.measureText(text).width;
235
232
  }
236
233
 
@@ -240,7 +237,7 @@ export function measureText(
240
237
  export function calculateColumnWidth<TData = any>(
241
238
  ctx: CanvasRenderingContext2D,
242
239
  column: Column,
243
- colDef: ColDef<TData> | null,
240
+ _colDef: ColDef<TData> | null,
244
241
  theme: GridTheme,
245
242
  sampleData: any[],
246
243
  maxRows: number = 100
@@ -278,6 +275,16 @@ export function calculateColumnWidth<TData = any>(
278
275
  /**
279
276
  * Get formatted cell value
280
277
  */
278
+ /**
279
+ * Strip HTML tags from string
280
+ * Supports basic cellRenderer that returns HTML strings
281
+ * Note: Only plain text is rendered - colors, backgrounds, etc. are NOT supported
282
+ */
283
+ export function stripHtmlTags(html: string): string {
284
+ if (!html) return '';
285
+ return html.replace(/<[^>]*>/g, '');
286
+ }
287
+
281
288
  export function getFormattedValue<TData = any>(
282
289
  value: any,
283
290
  colDef: ColDef<TData> | null,
@@ -289,6 +296,27 @@ export function getFormattedValue<TData = any>(
289
296
  return '';
290
297
  }
291
298
 
299
+ // Use custom cellRenderer if provided
300
+ if (colDef && typeof colDef.cellRenderer === 'function') {
301
+ try {
302
+ const result = colDef.cellRenderer({
303
+ value,
304
+ data,
305
+ node: rowNode,
306
+ colDef,
307
+ api,
308
+ });
309
+ // Handle both string and Promise<string> returns
310
+ if (typeof result === 'string') {
311
+ return stripHtmlTags(result);
312
+ }
313
+ // For async renderers, return value as string (will be updated on next render)
314
+ return String(value);
315
+ } catch (e) {
316
+ console.warn('Cell renderer error:', e);
317
+ }
318
+ }
319
+
292
320
  // Use custom formatter if provided
293
321
  if (colDef && typeof colDef.valueFormatter === 'function') {
294
322
  try {
@@ -338,13 +366,7 @@ export function renderRow<TData = any>(
338
366
 
339
367
  const x = getCellX(column);
340
368
  const value = column.field ? getValueByPath(rowNode.data, column.field) : undefined;
341
- const formattedValue = getFormattedValue(
342
- value,
343
- prep.colDef,
344
- rowNode.data,
345
- rowNode,
346
- api
347
- );
369
+ const formattedValue = getFormattedValue(value, prep.colDef, rowNode.data, rowNode, api);
348
370
 
349
371
  const context: CellDrawContext<TData> = {
350
372
  ctx,
@@ -366,4 +388,4 @@ export function renderRow<TData = any>(
366
388
 
367
389
  drawCell(ctx, prep, context);
368
390
  }
369
- }
391
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Column Utilities for Canvas Renderer
3
+ *
4
+ * Helper functions for column management and definition lookup.
5
+ */
6
+
7
+ import { ColDef, Column, GridApi } from '../../types/ag-grid-types';
8
+
9
+ /**
10
+ * Find the Column Definition for a given Column
11
+ */
12
+ export function getColumnDef<TData = any>(
13
+ column: Column,
14
+ gridApi: GridApi<TData>
15
+ ): ColDef<TData> | null {
16
+ const allDefs = gridApi.getColumnDefs();
17
+ if (!allDefs) return null;
18
+
19
+ const targetId = column.colId;
20
+ const targetField = column.field?.toString();
21
+
22
+ for (const def of allDefs) {
23
+ if ('children' in def) {
24
+ const found = def.children.find((c) => {
25
+ const cDef = c as ColDef;
26
+ return (
27
+ cDef.colId === targetId ||
28
+ cDef.field?.toString() === targetId ||
29
+ (targetField && cDef.field?.toString() === targetField)
30
+ );
31
+ });
32
+ if (found) return found as ColDef<TData>;
33
+ } else {
34
+ const cDef = def as ColDef;
35
+ if (
36
+ cDef.colId === targetId ||
37
+ cDef.field?.toString() === targetId ||
38
+ (targetField && cDef.field?.toString() === targetField)
39
+ ) {
40
+ return def as ColDef<TData>;
41
+ }
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Get X position for a column
49
+ */
50
+ export function getColumnX(
51
+ targetCol: Column,
52
+ columnPositions: Map<string, number>,
53
+ scrollLeft: number,
54
+ leftPinnedWidth: number,
55
+ rightPinnedWidth: number,
56
+ viewportWidth: number
57
+ ): number {
58
+ // Use cached column position (O(1) lookup)
59
+ const baseX = columnPositions.get(targetCol.colId) || 0;
60
+
61
+ // Adjust for pinned columns and scroll position
62
+ if (targetCol.pinned === 'left') {
63
+ return baseX;
64
+ } else if (targetCol.pinned === 'right') {
65
+ // When right-pinned, we need to know the offset from the right edge
66
+ // Our positions are accumulated from left to right.
67
+ // We need to find where the right-pinned section starts.
68
+ const rightPinnedStartX = viewportWidth - rightPinnedWidth;
69
+ return rightPinnedStartX + (baseX - (viewportWidth - rightPinnedWidth));
70
+ } else {
71
+ return leftPinnedWidth - scrollLeft + (baseX - leftPinnedWidth);
72
+ }
73
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Hit Testing for Canvas Renderer
3
+ *
4
+ * Utilities for detecting which grid element is under a given coordinate.
5
+ */
6
+
7
+ import { Column } from '../../types/ag-grid-types';
8
+ import { getColumnAtX, getRowAtY } from './walk';
9
+
10
+ /**
11
+ * Result of a grid hit test
12
+ */
13
+ export interface HitTestResult {
14
+ rowIndex: number;
15
+ columnIndex: number;
16
+ column: Column | null;
17
+ }
18
+
19
+ /**
20
+ * Perform a hit test on the grid
21
+ */
22
+ export function performHitTest(
23
+ canvasX: number,
24
+ canvasY: number,
25
+ rowHeight: number,
26
+ scrollTop: number,
27
+ scrollLeft: number,
28
+ viewportWidth: number,
29
+ columns: Column[]
30
+ ): HitTestResult {
31
+ // Use walker utility for row detection
32
+ const rowIndex = getRowAtY(canvasY, rowHeight, scrollTop);
33
+
34
+ // Use walker utility for column detection
35
+ const result = getColumnAtX(columns, canvasX, scrollLeft, viewportWidth);
36
+
37
+ return {
38
+ rowIndex,
39
+ columnIndex: result.index,
40
+ column: result.column,
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Get the horizontal offset of a center column
46
+ */
47
+ export function getCenterColumnOffset(targetCol: Column, columns: Column[]): number {
48
+ const centerColumns = columns.filter((c) => !c.pinned);
49
+ let offset = 0;
50
+ for (const col of centerColumns) {
51
+ if (col === targetCol) return offset;
52
+ offset += col.width;
53
+ }
54
+ return offset;
55
+ }
@@ -4,102 +4,105 @@
4
4
  * Exports all rendering-related modules.
5
5
  */
6
6
 
7
- // Types (base definitions)
8
- export {
9
- Rectangle,
10
- Point,
11
- Size,
12
- ScrollPosition,
13
- PositionedColumn,
14
- ColumnWalkCallback,
15
- RowWalkCallback,
16
- CellWalkCallback,
17
- CellDrawContext,
18
- ColumnPrepResult,
19
- BlitResult,
20
- BufferPair,
21
- DamageType,
22
- DirtyRegions,
23
- GridTheme,
24
- PartialTheme,
25
- RenderState,
26
- VisibleRange,
27
- HitTestResult,
28
- GridMouseEvent,
29
- } from './types';
30
-
31
- // Theme (re-export the DEFAULT_THEME and utilities)
32
- export {
33
- DEFAULT_THEME,
34
- DARK_THEME,
35
- THEME_PRESETS,
36
- mergeTheme,
37
- getFontFromTheme,
38
- getRowTheme,
39
- getCellBackgroundColor,
40
- getThemePreset,
41
- createTheme,
42
- } from './theme';
43
-
44
- // Walker functions
45
- export {
46
- walkColumns,
47
- getPositionedColumns,
48
- getPinnedWidths,
49
- walkRows,
50
- getVisibleRowRange,
51
- getRowY,
52
- walkCells,
53
- getColumnAtX,
54
- getColumnIndex,
55
- getTotalColumnWidth,
56
- getRowAtY,
57
- isRowVisible,
58
- calculateVisibleRange,
59
- } from './walk';
60
-
7
+ // Live data optimizations
8
+ export { LiveDataOptimizations } from '../live-data-optimizations';
9
+ // Rendering primitives
10
+ export { drawCheckbox, drawGroupIndicator, drawSparkline } from './primitives';
11
+ // Hit testing
12
+ export * from './hit-test';
13
+ // Column utilities
14
+ export * from './column-utils';
61
15
  // Blitting optimization
62
16
  export {
63
- MIN_BLIT_DELTA,
64
- MAX_BLIT_DELTA_RATIO,
65
- shouldBlit,
66
- calculateBlit,
17
+ BlitState,
67
18
  blitLastFrame,
19
+ calculateBlit,
68
20
  createBufferPair,
69
- swapBuffers,
70
21
  displayBuffer,
22
+ MAX_BLIT_DELTA_RATIO,
23
+ MIN_BLIT_DELTA,
71
24
  resizeBufferPair,
72
- BlitState,
25
+ shouldBlit,
26
+ swapBuffers,
73
27
  } from './blit';
74
-
75
28
  // Cell rendering (explicit exports to avoid conflicts)
76
29
  export {
77
- prepColumn,
78
- prepColumns,
30
+ calculateColumnWidth,
79
31
  drawCell,
80
32
  drawCellBackground,
81
33
  drawCellContent,
82
34
  drawGroupIndicators,
83
- truncateText,
84
- measureText,
85
- calculateColumnWidth,
86
35
  getFormattedValue,
87
36
  getValueByPath,
37
+ measureText,
38
+ prepColumn,
39
+ prepColumns,
88
40
  renderRow,
41
+ truncateText,
89
42
  } from './cells';
90
-
91
43
  // Grid lines
92
44
  export {
93
- drawCrispLine,
94
- drawHorizontalLine,
95
- drawVerticalLine,
96
- drawRowLines,
97
- drawColumnLines,
98
- getColumnBorderPositions,
99
- drawGridLines,
100
45
  drawBorder,
101
46
  drawCellSelectionBorder,
102
- drawRangeSelectionBorder,
47
+ drawColumnLines,
48
+ drawCrispLine,
49
+ drawGridLines,
50
+ drawHorizontalLine,
103
51
  drawPinnedRegionBorders,
104
52
  drawPinnedRegionShadows,
105
- } from './lines';
53
+ drawRangeSelectionBorder,
54
+ drawRowLines,
55
+ drawVerticalLine,
56
+ getColumnBorderPositions,
57
+ } from './lines';
58
+ // Theme (re-export the DEFAULT_THEME and utilities)
59
+ export {
60
+ createTheme,
61
+ DARK_THEME,
62
+ DEFAULT_THEME,
63
+ getCellBackgroundColor,
64
+ getFontFromTheme,
65
+ getRowTheme,
66
+ getThemePreset,
67
+ mergeTheme,
68
+ THEME_PRESETS,
69
+ } from './theme';
70
+ // Types (base definitions)
71
+ export {
72
+ BlitResult,
73
+ BufferPair,
74
+ CellDrawContext,
75
+ CellWalkCallback,
76
+ ColumnPrepResult,
77
+ ColumnWalkCallback,
78
+ DamageType,
79
+ DirtyRegions,
80
+ GridMouseEvent,
81
+ GridTheme,
82
+ HitTestResult,
83
+ PartialTheme,
84
+ Point,
85
+ PositionedColumn,
86
+ Rectangle,
87
+ RenderState,
88
+ RowWalkCallback,
89
+ ScrollPosition,
90
+ Size,
91
+ VisibleRange,
92
+ } from './types';
93
+ // Walker functions
94
+ export {
95
+ calculateVisibleRange,
96
+ getColumnAtX,
97
+ getColumnIndex,
98
+ getPinnedWidths,
99
+ getPositionedColumns,
100
+ getRowAtY,
101
+ getRowY,
102
+ getTotalColumnWidth,
103
+ getVisibleRowRange,
104
+ isRowVisible,
105
+ walkCells,
106
+ walkColumns,
107
+ walkRows,
108
+ } from './walk';
@@ -4,7 +4,7 @@
4
4
  * Draws grid lines (borders) efficiently.
5
5
  */
6
6
 
7
- import { Column, IRowNode } from '../../types/ag-grid-types';
7
+ import { Column, GridApi } from '../../types/ag-grid-types';
8
8
  import { GridTheme, Rectangle } from './types';
9
9
 
10
10
  // ============================================================================
@@ -22,9 +22,14 @@ export function drawCrispLine(
22
22
  y2: number
23
23
  ): void {
24
24
  ctx.beginPath();
25
- // Add 0.5 to pixel coordinates for crisp 1px lines
26
- ctx.moveTo(Math.floor(x1) + 0.5, Math.floor(y1) + 0.5);
27
- ctx.lineTo(Math.floor(x2) + 0.5, Math.floor(y2) + 0.5);
25
+ // For 1px lines, we want the center of the line to be at X.5
26
+ const snapX1 = Math.floor(x1) + 0.5;
27
+ const snapY1 = Math.floor(y1) + 0.5;
28
+ const snapX2 = Math.floor(x2) + 0.5;
29
+ const snapY2 = Math.floor(y2) + 0.5;
30
+
31
+ ctx.moveTo(snapX1, snapY1);
32
+ ctx.lineTo(snapX2, snapY2);
28
33
  ctx.stroke();
29
34
  }
30
35
 
@@ -66,7 +71,8 @@ export function drawRowLines(
66
71
  rowHeight: number,
67
72
  scrollTop: number,
68
73
  viewportWidth: number,
69
- theme: GridTheme
74
+ theme: GridTheme,
75
+ api?: GridApi
70
76
  ): void {
71
77
  ctx.strokeStyle = theme.borderColor || theme.gridLineColor;
72
78
  ctx.lineWidth = 1;
@@ -74,9 +80,12 @@ export function drawRowLines(
74
80
  ctx.beginPath();
75
81
 
76
82
  for (let row = startRow; row <= endRow; row++) {
77
- const y = Math.floor(row * rowHeight - scrollTop) + 0.5;
78
- ctx.moveTo(0, y);
79
- ctx.lineTo(viewportWidth, y);
83
+ const y = Math.floor(api ? api.getRowY(row) - scrollTop : row * rowHeight - scrollTop);
84
+ // Draw border at the bottom of the row (y-0.5) to match DOM border-bottom
85
+ const borderY = y - 0.5;
86
+ if (borderY < 0) continue; // Skip top border if it's outside
87
+ ctx.moveTo(0, borderY);
88
+ ctx.lineTo(viewportWidth, borderY);
80
89
  }
81
90
 
82
91
  ctx.stroke();
@@ -97,7 +106,8 @@ export function drawColumnLines(
97
106
  theme: GridTheme,
98
107
  startRow: number = 0,
99
108
  endRow: number = 0,
100
- rowHeight: number = 32
109
+ rowHeight: number = 32,
110
+ api?: GridApi
101
111
  ): void {
102
112
  ctx.strokeStyle = theme.borderColor || theme.gridLineColor;
103
113
  ctx.lineWidth = 1;
@@ -111,13 +121,15 @@ export function drawColumnLines(
111
121
  );
112
122
 
113
123
  // Calculate Y range for drawing
114
- const drawY1 = Math.max(0, Math.floor(startRow * rowHeight - scrollTop));
115
- const drawY2 = Math.min(viewportHeight, Math.floor(endRow * rowHeight - scrollTop));
124
+ const drawY1 = Math.floor(
125
+ api ? api.getRowY(startRow) - scrollTop : startRow * rowHeight - scrollTop
126
+ );
127
+ const drawY2 = Math.floor(api ? api.getRowY(endRow) - scrollTop : endRow * rowHeight - scrollTop);
116
128
 
117
129
  ctx.beginPath();
118
130
 
119
131
  for (const x of columnPositions) {
120
- const borderX = Math.floor(x) + 0.5;
132
+ const borderX = Math.floor(x) - 0.5;
121
133
  ctx.moveTo(borderX, drawY1);
122
134
  ctx.lineTo(borderX, drawY2);
123
135
  }
@@ -137,21 +149,21 @@ export function getColumnBorderPositions(
137
149
  ): number[] {
138
150
  const positions: number[] = [];
139
151
 
140
- const leftPinned = columns.filter(c => c.pinned === 'left');
141
- const rightPinned = columns.filter(c => c.pinned === 'right');
142
- const centerColumns = columns.filter(c => !c.pinned);
152
+ const leftPinned = columns.filter((c) => c.pinned === 'left');
153
+ const rightPinned = columns.filter((c) => c.pinned === 'right');
154
+ const centerColumns = columns.filter((c) => !c.pinned);
143
155
 
144
156
  // Left pinned column borders
145
157
  let x = 0;
146
158
  for (const col of leftPinned) {
147
- x += col.width;
159
+ x += Math.floor(col.width);
148
160
  positions.push(x);
149
161
  }
150
162
 
151
163
  // Center column borders
152
- x = leftPinnedWidth - scrollX;
164
+ x = Math.floor(leftPinnedWidth) - scrollX;
153
165
  for (const col of centerColumns) {
154
- x += col.width;
166
+ x += Math.floor(col.width);
155
167
  // Only include if visible
156
168
  if (x > leftPinnedWidth && x < viewportWidth - rightPinnedWidth) {
157
169
  positions.push(x);
@@ -159,9 +171,9 @@ export function getColumnBorderPositions(
159
171
  }
160
172
 
161
173
  // Right pinned column borders
162
- x = viewportWidth - rightPinnedWidth;
174
+ x = Math.floor(viewportWidth - rightPinnedWidth);
163
175
  for (const col of rightPinned) {
164
- x += col.width;
176
+ x += Math.floor(col.width);
165
177
  positions.push(x);
166
178
  }
167
179
 
@@ -183,10 +195,11 @@ export function drawGridLines(
183
195
  viewportHeight: number,
184
196
  leftPinnedWidth: number,
185
197
  rightPinnedWidth: number,
186
- theme: GridTheme
198
+ theme: GridTheme,
199
+ api?: GridApi
187
200
  ): void {
188
201
  // Draw horizontal lines
189
- drawRowLines(ctx, startRow, endRow, rowHeight, scrollTop, viewportWidth, theme);
202
+ drawRowLines(ctx, startRow, endRow, rowHeight, scrollTop, viewportWidth, theme, api);
190
203
 
191
204
  // Draw vertical lines
192
205
  drawColumnLines(
@@ -201,7 +214,8 @@ export function drawGridLines(
201
214
  theme,
202
215
  startRow,
203
216
  endRow,
204
- rowHeight
217
+ rowHeight,
218
+ api
205
219
  );
206
220
  }
207
221
 
@@ -261,11 +275,7 @@ export function drawRangeSelectionBorder(
261
275
  lineWidth?: number;
262
276
  } = {}
263
277
  ): void {
264
- const {
265
- color = '#1976d2',
266
- fillColor = 'rgba(25, 118, 210, 0.1)',
267
- lineWidth = 1
268
- } = options;
278
+ const { color = '#1976d2', fillColor = 'rgba(25, 118, 210, 0.1)', lineWidth = 1 } = options;
269
279
 
270
280
  // Draw fill
271
281
  if (fillColor) {
@@ -304,7 +314,7 @@ export function drawPinnedRegionBorders(
304
314
  // Left pinned border
305
315
  if (leftPinnedWidth > 0) {
306
316
  ctx.beginPath();
307
- const x = Math.floor(leftPinnedWidth) + 0.5;
317
+ const x = Math.floor(leftPinnedWidth) - 0.5;
308
318
  ctx.moveTo(x, 0);
309
319
  ctx.lineTo(x, viewportHeight);
310
320
  ctx.stroke();
@@ -313,7 +323,7 @@ export function drawPinnedRegionBorders(
313
323
  // Right pinned border
314
324
  if (rightPinnedWidth > 0) {
315
325
  ctx.beginPath();
316
- const x = Math.floor(viewportWidth - rightPinnedWidth) + 0.5;
326
+ const x = Math.floor(viewportWidth - rightPinnedWidth) - 0.5;
317
327
  ctx.moveTo(x, 0);
318
328
  ctx.lineTo(x, viewportHeight);
319
329
  ctx.stroke();
@@ -332,12 +342,7 @@ export function drawPinnedRegionShadows(
332
342
  ): void {
333
343
  // Left shadow (on the right edge of left pinned)
334
344
  if (leftPinnedWidth > 0) {
335
- const gradient = ctx.createLinearGradient(
336
- leftPinnedWidth,
337
- 0,
338
- leftPinnedWidth + 4,
339
- 0
340
- );
345
+ const gradient = ctx.createLinearGradient(leftPinnedWidth, 0, leftPinnedWidth + 4, 0);
341
346
  gradient.addColorStop(0, 'rgba(0, 0, 0, 0.1)');
342
347
  gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
343
348
 
@@ -348,16 +353,11 @@ export function drawPinnedRegionShadows(
348
353
  // Right shadow (on the left edge of right pinned)
349
354
  if (rightPinnedWidth > 0) {
350
355
  const shadowX = viewportWidth - rightPinnedWidth;
351
- const gradient = ctx.createLinearGradient(
352
- shadowX - 4,
353
- 0,
354
- shadowX,
355
- 0
356
- );
356
+ const gradient = ctx.createLinearGradient(shadowX - 4, 0, shadowX, 0);
357
357
  gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
358
358
  gradient.addColorStop(1, 'rgba(0, 0, 0, 0.1)');
359
359
 
360
360
  ctx.fillStyle = gradient;
361
361
  ctx.fillRect(shadowX - 4, 0, 4, viewportHeight);
362
362
  }
363
- }
363
+ }