argent-grid 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +69 -0
- package/.github/workflows/pages.yml +6 -12
- package/.storybook/main.ts +20 -0
- package/.storybook/preview.ts +18 -0
- package/.storybook/tsconfig.json +24 -0
- package/AGENTS.md +70 -27
- package/README.md +51 -34
- package/angular.json +66 -0
- package/biome.json +66 -0
- package/demo-app/e2e/selection-screenshot.spec.ts +20 -0
- package/docs/AG-GRID-COMPARISON.md +725 -0
- package/docs/CELL-RENDERER-GUIDE.md +241 -0
- package/docs/CONTEXT-MENU-GUIDE.md +371 -0
- package/docs/LIVE-DATA-OPTIMIZATIONS.md +497 -0
- package/docs/PERFORMANCE-OPTIMIZATIONS-PHASE1.md +162 -0
- package/docs/PERFORMANCE-REVIEW.md +571 -0
- package/docs/RESEARCH-STATUS.md +234 -0
- package/docs/STATE-PERSISTENCE-GUIDE.md +370 -0
- package/docs/STORYBOOK-REFACTOR.md +215 -0
- package/docs/STORYBOOK-STATUS.md +156 -0
- package/docs/TEST-COVERAGE-REPORT.md +276 -0
- package/docs/THEME-API-GUIDE.md +445 -0
- package/docs/THEME-API-PLAN.md +364 -0
- package/e2e/advanced.spec.ts +109 -0
- package/e2e/argentgrid.spec.ts +65 -0
- package/e2e/benchmark.spec.ts +52 -0
- package/e2e/cell-renderers.spec.ts +152 -0
- package/e2e/debug-streaming.spec.ts +31 -0
- package/e2e/dnd.spec.ts +73 -0
- package/e2e/screenshots.spec.ts +52 -0
- package/e2e/theming.spec.ts +35 -0
- package/e2e/visual.spec.ts +112 -0
- package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
- package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
- package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
- package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
- package/package.json +21 -7
- package/plan.md +56 -28
- package/playwright.config.ts +38 -0
- package/setup-vitest.ts +10 -13
- package/src/lib/argent-grid.module.ts +10 -12
- package/src/lib/components/argent-grid.component.css +281 -321
- package/src/lib/components/argent-grid.component.html +295 -207
- package/src/lib/components/argent-grid.component.spec.ts +120 -160
- package/src/lib/components/argent-grid.component.ts +1193 -290
- package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
- package/src/lib/components/argent-grid.selection.spec.ts +132 -0
- package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
- package/src/lib/components/set-filter/set-filter.component.ts +307 -0
- package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
- package/src/lib/directives/click-outside.directive.ts +19 -0
- package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
- package/src/lib/rendering/canvas-renderer.ts +456 -452
- package/src/lib/rendering/live-data-handler.ts +110 -0
- package/src/lib/rendering/live-data-optimizations.ts +133 -0
- package/src/lib/rendering/render/blit.spec.ts +16 -27
- package/src/lib/rendering/render/blit.ts +48 -36
- package/src/lib/rendering/render/cells.spec.ts +132 -0
- package/src/lib/rendering/render/cells.ts +167 -28
- package/src/lib/rendering/render/column-utils.ts +95 -0
- package/src/lib/rendering/render/hit-test.ts +50 -0
- package/src/lib/rendering/render/index.ts +88 -76
- package/src/lib/rendering/render/lines.ts +53 -47
- package/src/lib/rendering/render/primitives.ts +423 -0
- package/src/lib/rendering/render/theme.spec.ts +8 -12
- package/src/lib/rendering/render/theme.ts +7 -10
- package/src/lib/rendering/render/types.ts +3 -2
- package/src/lib/rendering/render/walk.spec.ts +35 -38
- package/src/lib/rendering/render/walk.ts +94 -64
- package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
- package/src/lib/rendering/utils/damage-tracker.ts +6 -18
- package/src/lib/rendering/utils/index.ts +1 -1
- package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
- package/src/lib/services/grid.service.spec.ts +1241 -201
- package/src/lib/services/grid.service.ts +1204 -235
- package/src/lib/themes/parts/color-schemes.ts +132 -0
- package/src/lib/themes/parts/icon-sets.ts +258 -0
- package/src/lib/themes/theme-builder.ts +347 -0
- package/src/lib/themes/theme-quartz.ts +72 -0
- package/src/lib/themes/types.ts +238 -0
- package/src/lib/types/ag-grid-types.ts +573 -14
- package/src/public-api.ts +39 -9
- package/src/stories/Advanced.stories.ts +249 -0
- package/src/stories/ArgentGrid.stories.ts +301 -0
- package/src/stories/Benchmark.stories.ts +76 -0
- package/src/stories/CellRenderers.stories.ts +395 -0
- package/src/stories/Filtering.stories.ts +292 -0
- package/src/stories/Grouping.stories.ts +290 -0
- package/src/stories/Streaming.stories.ts +57 -0
- package/src/stories/Theming.stories.ts +137 -0
- package/src/stories/Tooltips.stories.ts +381 -0
- package/src/stories/benchmark-wrapper.component.ts +355 -0
- package/src/stories/story-utils.ts +88 -0
- package/src/stories/streaming-wrapper.component.ts +441 -0
- package/tsconfig.json +1 -0
- package/tsconfig.storybook.json +10 -0
- package/vitest.config.ts +9 -9
- package/demo-app/README.md +0 -70
- package/demo-app/angular.json +0 -78
- package/demo-app/e2e/benchmark.spec.ts +0 -53
- package/demo-app/e2e/demo-page.spec.ts +0 -77
- package/demo-app/e2e/grid-features.spec.ts +0 -269
- package/demo-app/package-lock.json +0 -14023
- package/demo-app/package.json +0 -36
- package/demo-app/playwright-test-menu.js +0 -19
- package/demo-app/playwright.config.ts +0 -23
- package/demo-app/src/app/app.component.ts +0 -10
- package/demo-app/src/app/app.config.ts +0 -13
- package/demo-app/src/app/app.routes.ts +0 -7
- package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
- package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
- package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
- package/demo-app/src/index.html +0 -19
- package/demo-app/src/main.ts +0 -6
- package/demo-app/tsconfig.json +0 -31
|
@@ -1,189 +1,149 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ChangeDetectorRef, provideExperimentalZonelessChangeDetection } from '@angular/core';
|
|
3
|
-
import { CommonModule } from '@angular/common';
|
|
4
|
-
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
5
|
-
import { ArgentGridComponent } from './argent-grid.component';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
2
|
import { GridService } from '../services/grid.service';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
// Mock
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
beginPath: vi.fn(),
|
|
14
|
-
moveTo: vi.fn(),
|
|
15
|
-
lineTo: vi.fn(),
|
|
16
|
-
stroke: vi.fn(),
|
|
17
|
-
fillText: vi.fn(),
|
|
18
|
-
measureText: vi.fn(() => ({ width: 100 })),
|
|
19
|
-
scale: vi.fn(),
|
|
20
|
-
setTransform: vi.fn(),
|
|
21
|
-
font: '13px sans-serif',
|
|
22
|
-
textBaseline: 'middle',
|
|
23
|
-
fillStyle: '#000',
|
|
24
|
-
strokeStyle: '#000'
|
|
3
|
+
import { ArgentGridComponent } from './argent-grid.component';
|
|
4
|
+
|
|
5
|
+
// Mock ChangeDetectorRef
|
|
6
|
+
const mockCdr = {
|
|
7
|
+
detectChanges: vi.fn(),
|
|
8
|
+
markForCheck: vi.fn(),
|
|
25
9
|
};
|
|
26
10
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
height: 600,
|
|
31
|
-
style: {},
|
|
32
|
-
addEventListener: vi.fn(),
|
|
33
|
-
getBoundingClientRect: vi.fn(() => ({ width: 800, height: 600 }))
|
|
34
|
-
} as unknown as HTMLCanvasElement;
|
|
35
|
-
|
|
36
|
-
interface TestData {
|
|
37
|
-
id: number;
|
|
38
|
-
name: string;
|
|
39
|
-
value: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe('ArgentGridComponent', () => {
|
|
43
|
-
let component: ArgentGridComponent<TestData>;
|
|
44
|
-
let fixture: ComponentFixture<ArgentGridComponent<TestData>>;
|
|
45
|
-
|
|
46
|
-
// Mock getContext globally for this test suite
|
|
47
|
-
beforeAll(() => {
|
|
48
|
-
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockImplementation((contextId) => {
|
|
49
|
-
if (contextId === '2d') {
|
|
50
|
-
return mockCanvasContext as any;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
vi.spyOn(HTMLCanvasElement.prototype, 'getBoundingClientRect').mockReturnValue({
|
|
56
|
-
width: 800,
|
|
57
|
-
height: 600,
|
|
58
|
-
top: 0,
|
|
59
|
-
left: 0,
|
|
60
|
-
bottom: 600,
|
|
61
|
-
right: 800,
|
|
62
|
-
x: 0,
|
|
63
|
-
y: 0,
|
|
64
|
-
toJSON: () => {}
|
|
65
|
-
} as DOMRect);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const testColumnDefs: (ColDef<TestData>)[] = [
|
|
69
|
-
{ colId: 'id', field: 'id', headerName: 'ID', width: 100 },
|
|
70
|
-
{ colId: 'name', field: 'name', headerName: 'Name', width: 150 },
|
|
71
|
-
{ colId: 'value', field: 'value', headerName: 'Value', width: 100 }
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
const testRowData: TestData[] = [
|
|
75
|
-
{ id: 1, name: 'Item 1', value: 100 },
|
|
76
|
-
{ id: 2, name: 'Item 2', value: 200 }
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
beforeEach(async () => {
|
|
80
|
-
await TestBed.configureTestingModule({
|
|
81
|
-
declarations: [ArgentGridComponent],
|
|
82
|
-
imports: [
|
|
83
|
-
CommonModule,
|
|
84
|
-
DragDropModule
|
|
85
|
-
],
|
|
86
|
-
providers: [
|
|
87
|
-
provideExperimentalZonelessChangeDetection()
|
|
88
|
-
]
|
|
89
|
-
}).compileComponents();
|
|
90
|
-
});
|
|
11
|
+
describe('ArgentGridComponent - Context Menu', () => {
|
|
12
|
+
let component: ArgentGridComponent;
|
|
13
|
+
let _gridService: GridService;
|
|
91
14
|
|
|
92
15
|
beforeEach(() => {
|
|
93
|
-
|
|
94
|
-
component =
|
|
95
|
-
component.columnDefs = testColumnDefs;
|
|
96
|
-
component.rowData = testRowData;
|
|
97
|
-
|
|
98
|
-
fixture.detectChanges();
|
|
16
|
+
_gridService = new GridService();
|
|
17
|
+
component = new ArgentGridComponent(mockCdr as any);
|
|
99
18
|
});
|
|
100
19
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
20
|
+
describe('resolveContextMenuItems', () => {
|
|
21
|
+
it('should resolve string menu items to defaults', () => {
|
|
22
|
+
const items = component.resolveContextMenuItems(['copy', 'separator']);
|
|
23
|
+
expect(items.length).toBeGreaterThan(0);
|
|
24
|
+
expect(items[0].name).toBe('Copy Cell');
|
|
25
|
+
});
|
|
104
26
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
27
|
+
it('should handle custom MenuItemDef objects', () => {
|
|
28
|
+
const customItem = {
|
|
29
|
+
name: 'Custom Action',
|
|
30
|
+
action: vi.fn(),
|
|
31
|
+
icon: '⭐',
|
|
32
|
+
};
|
|
33
|
+
const items = component.resolveContextMenuItems([customItem]);
|
|
34
|
+
expect(items.length).toBe(1);
|
|
35
|
+
expect(items[0].name).toBe('Custom Action');
|
|
36
|
+
});
|
|
108
37
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
38
|
+
it('should mix default and custom items', () => {
|
|
39
|
+
const items = component.resolveContextMenuItems([
|
|
40
|
+
'copy',
|
|
41
|
+
{ name: 'Custom', action: vi.fn() },
|
|
42
|
+
'separator',
|
|
43
|
+
]);
|
|
44
|
+
expect(items.length).toBeGreaterThan(1);
|
|
45
|
+
});
|
|
112
46
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
expect(
|
|
116
|
-
expect(api.getColumnDefs()).toEqual(testColumnDefs);
|
|
47
|
+
it('should filter out null items', () => {
|
|
48
|
+
const items = component.resolveContextMenuItems(['copy', null as any]);
|
|
49
|
+
expect(items.length).toBeGreaterThan(0);
|
|
117
50
|
});
|
|
118
51
|
});
|
|
119
52
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
53
|
+
describe('getDefaultMenuItem', () => {
|
|
54
|
+
it('should return copy cell item', () => {
|
|
55
|
+
const item = component.getDefaultMenuItem('copy');
|
|
56
|
+
expect(item?.name).toBe('Copy Cell');
|
|
57
|
+
expect(item?.icon).toBe('📋');
|
|
58
|
+
});
|
|
123
59
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
60
|
+
it('should return copy with headers item (when range exists)', () => {
|
|
61
|
+
// Note: copyWithHeaders only returns item when range selection exists
|
|
62
|
+
// For testing, we just verify it doesn't throw
|
|
63
|
+
expect(() => {
|
|
64
|
+
component.getDefaultMenuItem('copyWithHeaders');
|
|
65
|
+
}).not.toThrow();
|
|
66
|
+
});
|
|
128
67
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
68
|
+
it('should return export submenu', () => {
|
|
69
|
+
const item = component.getDefaultMenuItem('export');
|
|
70
|
+
expect(item?.name).toBe('Export');
|
|
71
|
+
expect(item?.subMenu).toBeDefined();
|
|
72
|
+
});
|
|
133
73
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
74
|
+
it('should return reset columns item', () => {
|
|
75
|
+
const item = component.getDefaultMenuItem('resetColumns');
|
|
76
|
+
expect(item?.name).toBe('Reset Columns');
|
|
77
|
+
expect(item?.icon).toBe('⟲');
|
|
78
|
+
});
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
80
|
+
it('should return separator', () => {
|
|
81
|
+
const item = component.getDefaultMenuItem('separator');
|
|
82
|
+
expect(item?.separator).toBe(true);
|
|
83
|
+
});
|
|
142
84
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
85
|
+
it('should return null for unknown key', () => {
|
|
86
|
+
const item = component.getDefaultMenuItem('unknown' as any);
|
|
87
|
+
expect(item).toBe(null);
|
|
88
|
+
});
|
|
146
89
|
});
|
|
147
90
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Click again to toggle
|
|
154
|
-
component.onHeaderClick(col);
|
|
155
|
-
expect(col.sort).toBe('desc');
|
|
156
|
-
|
|
157
|
-
// Click third time to clear
|
|
158
|
-
component.onHeaderClick(col);
|
|
159
|
-
expect(col.sort).toBeNull();
|
|
160
|
-
});
|
|
91
|
+
describe('closeContextMenu', () => {
|
|
92
|
+
it('should reset context menu state', () => {
|
|
93
|
+
component.contextMenuItems = [{ name: 'Test', action: vi.fn() }];
|
|
94
|
+
component.activeContextMenu = true;
|
|
95
|
+
component.contextMenuCell = { rowNode: {} as any, column: {} as any };
|
|
161
96
|
|
|
162
|
-
|
|
163
|
-
const col: ColDef<TestData> = { colId: 'test', sortable: false };
|
|
164
|
-
component.onHeaderClick(col);
|
|
165
|
-
expect(col.sort).toBeUndefined();
|
|
166
|
-
});
|
|
97
|
+
component.closeContextMenu();
|
|
167
98
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
99
|
+
expect(component.activeContextMenu).toBe(false);
|
|
100
|
+
expect(component.contextMenuCell).toBe(null);
|
|
101
|
+
expect(mockCdr.detectChanges).toHaveBeenCalled();
|
|
102
|
+
});
|
|
171
103
|
});
|
|
172
104
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
105
|
+
describe('copyContextMenuCell', () => {
|
|
106
|
+
it('should handle null cell gracefully', () => {
|
|
107
|
+
component.contextMenuCell = null;
|
|
108
|
+
expect(() => component.copyContextMenuCell()).not.toThrow();
|
|
109
|
+
});
|
|
177
110
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
111
|
+
it('should handle missing field gracefully', () => {
|
|
112
|
+
component.contextMenuCell = {
|
|
113
|
+
rowNode: { data: { name: 'John' } } as any,
|
|
114
|
+
column: { field: null } as any,
|
|
115
|
+
};
|
|
116
|
+
expect(() => component.copyContextMenuCell()).not.toThrow();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should close context menu after copy', () => {
|
|
120
|
+
const mockClipboard = { writeText: vi.fn().mockResolvedValue(undefined) };
|
|
121
|
+
Object.defineProperty(navigator, 'clipboard', { value: mockClipboard, writable: true });
|
|
122
|
+
|
|
123
|
+
component.contextMenuCell = {
|
|
124
|
+
rowNode: { data: { name: 'John' } } as any,
|
|
125
|
+
column: { field: 'name' } as any,
|
|
126
|
+
};
|
|
127
|
+
component.activeContextMenu = true;
|
|
128
|
+
|
|
129
|
+
component.copyContextMenuCell();
|
|
130
|
+
|
|
131
|
+
expect(component.activeContextMenu).toBe(false);
|
|
132
|
+
expect(component.contextMenuCell).toBe(null);
|
|
133
|
+
});
|
|
183
134
|
});
|
|
184
135
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
136
|
+
describe('hasRangeSelection', () => {
|
|
137
|
+
it('should return false when no range', () => {
|
|
138
|
+
const mockApi = { getCellRanges: vi.fn(() => []) };
|
|
139
|
+
component.gridApi = mockApi as any;
|
|
140
|
+
expect(component.hasRangeSelection()).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return true when range exists', () => {
|
|
144
|
+
const mockApi = { getCellRanges: vi.fn(() => [{}]) };
|
|
145
|
+
component.gridApi = mockApi as any;
|
|
146
|
+
expect(component.hasRangeSelection()).toBe(true);
|
|
147
|
+
});
|
|
188
148
|
});
|
|
189
149
|
});
|