argent-grid 0.2.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.
Files changed (55) hide show
  1. package/AGENTS.md +70 -27
  2. package/e2e/advanced.spec.ts +1 -1
  3. package/e2e/benchmark.spec.ts +7 -7
  4. package/e2e/cell-renderers.spec.ts +152 -0
  5. package/e2e/debug-streaming.spec.ts +31 -0
  6. package/e2e/dnd.spec.ts +73 -0
  7. package/e2e/screenshots.spec.ts +1 -1
  8. package/e2e/visual.spec.ts +30 -9
  9. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  10. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  11. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  12. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  13. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  14. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  15. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  16. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  17. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  18. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  19. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  20. package/package.json +5 -5
  21. package/plan.md +30 -34
  22. package/src/lib/components/argent-grid.component.css +258 -549
  23. package/src/lib/components/argent-grid.component.html +272 -306
  24. package/src/lib/components/argent-grid.component.ts +585 -135
  25. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  26. package/src/lib/components/argent-grid.selection.spec.ts +2 -2
  27. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  28. package/src/lib/components/set-filter/set-filter.component.ts +7 -2
  29. package/src/lib/rendering/canvas-renderer.spec.ts +148 -1
  30. package/src/lib/rendering/canvas-renderer.ts +177 -286
  31. package/src/lib/rendering/render/cells.ts +122 -5
  32. package/src/lib/rendering/render/column-utils.ts +27 -5
  33. package/src/lib/rendering/render/hit-test.ts +6 -11
  34. package/src/lib/rendering/render/index.ts +15 -6
  35. package/src/lib/rendering/render/lines.ts +12 -6
  36. package/src/lib/rendering/render/primitives.ts +269 -7
  37. package/src/lib/rendering/render/types.ts +2 -1
  38. package/src/lib/rendering/render/walk.ts +39 -19
  39. package/src/lib/services/grid.service.spec.ts +76 -0
  40. package/src/lib/services/grid.service.ts +451 -114
  41. package/src/lib/themes/theme-quartz.ts +2 -2
  42. package/src/lib/types/ag-grid-types.ts +500 -0
  43. package/src/stories/Advanced.stories.ts +78 -17
  44. package/src/stories/ArgentGrid.stories.ts +50 -26
  45. package/src/stories/Benchmark.stories.ts +17 -15
  46. package/src/stories/CellRenderers.stories.ts +205 -31
  47. package/src/stories/Filtering.stories.ts +56 -16
  48. package/src/stories/Grouping.stories.ts +86 -13
  49. package/src/stories/Streaming.stories.ts +57 -0
  50. package/src/stories/Theming.stories.ts +23 -10
  51. package/src/stories/Tooltips.stories.ts +381 -0
  52. package/src/stories/benchmark-wrapper.component.ts +69 -29
  53. package/src/stories/story-utils.ts +88 -0
  54. package/src/stories/streaming-wrapper.component.ts +441 -0
  55. package/tsconfig.json +1 -0
@@ -25,15 +25,18 @@ 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
34
  const leftPinned = columns.filter((c) => c.pinned === 'left');
34
35
  const rightPinned = columns.filter((c) => c.pinned === 'right');
35
36
  const centerColumns = columns.filter((c) => !c.pinned);
36
37
 
38
+ const effectiveWidth = availableWidth ?? viewportWidth;
39
+
37
40
  // 1. Left pinned columns (no scroll offset)
38
41
  let x = 0;
39
42
  for (const col of leftPinned) {
@@ -44,7 +47,7 @@ export function walkColumns(
44
47
 
45
48
  // 2. Center columns (with scroll offset and clipping)
46
49
  const centerStartX = Math.floor(leftPinnedWidth);
47
- const centerEndX = Math.floor(viewportWidth - rightPinnedWidth);
50
+ const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
48
51
 
49
52
  x = centerStartX - scrollX;
50
53
  for (const col of centerColumns) {
@@ -79,7 +82,8 @@ export function getPositionedColumns(
79
82
  scrollX: number,
80
83
  viewportWidth: number,
81
84
  leftPinnedWidth: number,
82
- rightPinnedWidth: number
85
+ rightPinnedWidth: number,
86
+ availableWidth?: number
83
87
  ): PositionedColumn[] {
84
88
  const result: PositionedColumn[] = [];
85
89
 
@@ -91,7 +95,8 @@ export function getPositionedColumns(
91
95
  rightPinnedWidth,
92
96
  (column, x, width, isPinned, pinSide) => {
93
97
  result.push({ column, x, width, isPinned, pinSide });
94
- }
98
+ },
99
+ availableWidth
95
100
  );
96
101
 
97
102
  return result;
@@ -148,9 +153,15 @@ export function getVisibleRowRange(
148
153
  api?: GridApi
149
154
  ): { startRow: number; endRow: number } {
150
155
  if (api) {
151
- const startRow = Math.max(0, api.getRowAtY(scrollTop) - buffer);
152
- const endRow = Math.min(totalRowCount, api.getRowAtY(scrollTop + viewportHeight) + buffer + 1);
153
- return { startRow, endRow };
156
+ const startRow = Math.max(0, Math.min(totalRowCount - 1, api.getRowAtY(scrollTop)) - buffer);
157
+ const endRow = Math.min(
158
+ totalRowCount,
159
+ api.getRowAtY(Math.max(0, scrollTop + viewportHeight)) + buffer + 1
160
+ );
161
+ return {
162
+ startRow: Math.max(0, startRow),
163
+ endRow: Math.max(0, Math.min(totalRowCount, endRow)),
164
+ };
154
165
  }
155
166
 
156
167
  const startRow = Math.max(0, Math.floor(scrollTop / rowHeight) - buffer);
@@ -214,9 +225,11 @@ export function getColumnAtX(
214
225
  columns: Column[],
215
226
  x: number,
216
227
  scrollX: number,
217
- viewportWidth: number
228
+ viewportWidth: number,
229
+ availableWidth?: number
218
230
  ): { column: Column | null; index: number; localX: number } {
219
231
  const { left: leftPinnedWidth, right: rightPinnedWidth } = getPinnedWidths(columns);
232
+ const effectiveWidth = availableWidth ?? viewportWidth;
220
233
 
221
234
  const leftPinned = columns.filter((c) => c.pinned === 'left');
222
235
  const rightPinned = columns.filter((c) => c.pinned === 'right');
@@ -235,8 +248,9 @@ export function getColumnAtX(
235
248
  }
236
249
 
237
250
  // Check right pinned
238
- if (x > viewportWidth - rightPinnedWidth) {
239
- let colX = viewportWidth - rightPinnedWidth;
251
+ const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
252
+ if (x > centerEndX) {
253
+ let colX = centerEndX;
240
254
  for (let i = 0; i < rightPinned.length; i++) {
241
255
  const col = rightPinned[i];
242
256
  if (x < colX + col.width) {
@@ -271,7 +285,7 @@ export function getColumnIndex(columns: Column[], colId: string): number {
271
285
  * Calculate total width of columns
272
286
  */
273
287
  export function getTotalColumnWidth(columns: Column[]): number {
274
- return columns.reduce((sum, col) => sum + col.width, 0);
288
+ return columns.reduce((sum, col) => sum + Math.floor(col.width), 0);
275
289
  }
276
290
 
277
291
  // ============================================================================
@@ -316,7 +330,8 @@ export function calculateVisibleRange(
316
330
  viewportHeight: number,
317
331
  rowHeight: number,
318
332
  totalRowCount: number,
319
- rowBuffer: number = 5
333
+ rowBuffer: number = 5,
334
+ availableWidth?: number
320
335
  ): VisibleRange {
321
336
  const { startRow, endRow } = getVisibleRowRange(
322
337
  scrollTop,
@@ -326,13 +341,15 @@ export function calculateVisibleRange(
326
341
  rowBuffer
327
342
  );
328
343
 
344
+ const effectiveWidth = availableWidth ?? viewportWidth;
345
+
329
346
  // For columns, we just track indices
330
347
  const centerColumns = columns.filter((c) => !c.pinned);
331
348
  const leftPinned = columns.filter((c) => c.pinned === 'left');
332
349
  const rightPinned = columns.filter((c) => c.pinned === 'right');
333
350
 
334
- const leftPinnedWidth = leftPinned.reduce((sum, c) => sum + c.width, 0);
335
- 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);
336
353
 
337
354
  // Find first and last visible center column
338
355
  let startColumnIndex = leftPinned.length;
@@ -341,21 +358,24 @@ export function calculateVisibleRange(
341
358
  let x = leftPinnedWidth - scrollLeft;
342
359
  for (let i = 0; i < centerColumns.length; i++) {
343
360
  const col = centerColumns[i];
344
- if (x + col.width > leftPinnedWidth) {
361
+ const width = Math.floor(col.width);
362
+ if (x + width > leftPinnedWidth) {
345
363
  startColumnIndex = leftPinned.length + i;
346
364
  break;
347
365
  }
348
- x += col.width;
366
+ x += width;
349
367
  }
350
368
 
351
369
  x = leftPinnedWidth - scrollLeft;
370
+ const centerEndX = Math.floor(effectiveWidth - rightPinnedWidth);
352
371
  for (let i = 0; i < centerColumns.length; i++) {
353
372
  const col = centerColumns[i];
354
- if (x > viewportWidth - rightPinnedWidth) {
373
+ const width = Math.floor(col.width);
374
+ if (x > centerEndX) {
355
375
  endColumnIndex = leftPinned.length + i;
356
376
  break;
357
377
  }
358
- x += col.width;
378
+ x += width;
359
379
  }
360
380
 
361
381
  // Add right pinned columns
@@ -2000,4 +2000,80 @@ describe('GridService', () => {
2000
2000
  });
2001
2001
  });
2002
2002
  });
2003
+
2004
+ describe('Bug Fixes & Regression Protection', () => {
2005
+ it('should only emit optionChanged if the value actually changed', () => {
2006
+ const stateChangeSpy = vi.fn();
2007
+ api.setGridOption('rowHeight', 40); // Initial set
2008
+ service.gridStateChanged$.subscribe(stateChangeSpy);
2009
+
2010
+ api.setGridOption('rowHeight', 40); // Same value
2011
+ expect(stateChangeSpy).not.toHaveBeenCalled();
2012
+
2013
+ api.setGridOption('rowHeight', 50); // Different value
2014
+ expect(stateChangeSpy).toHaveBeenCalledWith(
2015
+ expect.objectContaining({
2016
+ type: 'optionChanged',
2017
+ key: 'rowHeight',
2018
+ value: 50,
2019
+ })
2020
+ );
2021
+ });
2022
+
2023
+ it('should respect groupDefaultExpanded option', () => {
2024
+ const groupingApi = service.createApi(
2025
+ [{ field: 'name', rowGroup: true }, { field: 'age' }],
2026
+ testRowData,
2027
+ { groupDefaultExpanded: 1 }
2028
+ );
2029
+
2030
+ const rowCount = groupingApi.getDisplayedRowCount();
2031
+ // 2 groups (John, Jane, Bob are unique in testRowData name field) + 3 rows = 6 rows total if all expanded
2032
+ // Wait, testRowData has 3 unique names. So 3 groups + 3 rows = 6.
2033
+ expect(rowCount).toBe(6);
2034
+
2035
+ const firstNode = groupingApi.getDisplayedRowAtIndex(0);
2036
+ expect(firstNode?.group).toBe(true);
2037
+ expect(firstNode?.expanded).toBe(true);
2038
+ });
2039
+
2040
+ it('should allow collapsing a group even when groupDefaultExpanded is set', () => {
2041
+ const groupingApi = service.createApi(
2042
+ [{ field: 'name', rowGroup: true }, { field: 'age' }],
2043
+ testRowData,
2044
+ { groupDefaultExpanded: 1 }
2045
+ );
2046
+
2047
+ expect(groupingApi.getDisplayedRowCount()).toBe(6);
2048
+
2049
+ const firstGroup = groupingApi.getDisplayedRowAtIndex(0);
2050
+ expect(firstGroup?.group).toBe(true);
2051
+ expect(firstGroup?.expanded).toBe(true);
2052
+
2053
+ // Collapse it
2054
+ groupingApi.setRowNodeExpanded(firstGroup!, false);
2055
+
2056
+ expect(firstGroup?.expanded).toBe(false);
2057
+ // 3 groups + 3 rows - 1 child of first group = 5
2058
+ expect(groupingApi.getDisplayedRowCount()).toBe(5);
2059
+ });
2060
+
2061
+ it('should remove ag-Grid-AutoColumn when grouping is disabled', () => {
2062
+ api.addRowGroupColumn('name');
2063
+ expect(api.getAllColumns().find((c) => c.colId === 'ag-Grid-AutoColumn')).toBeDefined();
2064
+
2065
+ api.removeRowGroupColumn('name');
2066
+ expect(api.getAllColumns().find((c) => c.colId === 'ag-Grid-AutoColumn')).toBeUndefined();
2067
+ });
2068
+
2069
+ it('should remove ag-Grid-SelectionColumn when checkbox selection is disabled', () => {
2070
+ api.setColumnDefs([{ field: 'id', checkboxSelection: true }, { field: 'name' }]);
2071
+ expect(api.getAllColumns().find((c) => c.colId === 'ag-Grid-SelectionColumn')).toBeDefined();
2072
+
2073
+ api.setColumnDefs([{ field: 'id', checkboxSelection: false }, { field: 'name' }]);
2074
+ expect(
2075
+ api.getAllColumns().find((c) => c.colId === 'ag-Grid-SelectionColumn')
2076
+ ).toBeUndefined();
2077
+ });
2078
+ });
2003
2079
  });