argent-grid 0.1.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 (53) hide show
  1. package/.github/workflows/pages.yml +68 -0
  2. package/AGENTS.md +179 -0
  3. package/README.md +222 -0
  4. package/demo-app/README.md +70 -0
  5. package/demo-app/angular.json +78 -0
  6. package/demo-app/e2e/benchmark.spec.ts +53 -0
  7. package/demo-app/e2e/demo-page.spec.ts +77 -0
  8. package/demo-app/e2e/grid-features.spec.ts +269 -0
  9. package/demo-app/package-lock.json +14023 -0
  10. package/demo-app/package.json +36 -0
  11. package/demo-app/playwright-test-menu.js +19 -0
  12. package/demo-app/playwright.config.ts +23 -0
  13. package/demo-app/src/app/app.component.ts +10 -0
  14. package/demo-app/src/app/app.config.ts +13 -0
  15. package/demo-app/src/app/app.routes.ts +7 -0
  16. package/demo-app/src/app/demo-page/demo-page.component.css +313 -0
  17. package/demo-app/src/app/demo-page/demo-page.component.html +124 -0
  18. package/demo-app/src/app/demo-page/demo-page.component.ts +366 -0
  19. package/demo-app/src/index.html +19 -0
  20. package/demo-app/src/main.ts +6 -0
  21. package/demo-app/tsconfig.json +31 -0
  22. package/ng-package.json +8 -0
  23. package/package.json +60 -0
  24. package/plan.md +131 -0
  25. package/setup-vitest.ts +18 -0
  26. package/src/lib/argent-grid.module.ts +21 -0
  27. package/src/lib/components/argent-grid.component.css +483 -0
  28. package/src/lib/components/argent-grid.component.html +320 -0
  29. package/src/lib/components/argent-grid.component.spec.ts +189 -0
  30. package/src/lib/components/argent-grid.component.ts +1188 -0
  31. package/src/lib/directives/ag-grid-compatibility.directive.ts +92 -0
  32. package/src/lib/rendering/canvas-renderer.ts +962 -0
  33. package/src/lib/rendering/render/blit.spec.ts +453 -0
  34. package/src/lib/rendering/render/blit.ts +393 -0
  35. package/src/lib/rendering/render/cells.ts +369 -0
  36. package/src/lib/rendering/render/index.ts +105 -0
  37. package/src/lib/rendering/render/lines.ts +363 -0
  38. package/src/lib/rendering/render/theme.spec.ts +282 -0
  39. package/src/lib/rendering/render/theme.ts +201 -0
  40. package/src/lib/rendering/render/types.ts +279 -0
  41. package/src/lib/rendering/render/walk.spec.ts +360 -0
  42. package/src/lib/rendering/render/walk.ts +360 -0
  43. package/src/lib/rendering/utils/damage-tracker.spec.ts +444 -0
  44. package/src/lib/rendering/utils/damage-tracker.ts +423 -0
  45. package/src/lib/rendering/utils/index.ts +7 -0
  46. package/src/lib/services/grid.service.spec.ts +1039 -0
  47. package/src/lib/services/grid.service.ts +1284 -0
  48. package/src/lib/types/ag-grid-types.ts +970 -0
  49. package/src/public-api.ts +22 -0
  50. package/tsconfig.json +32 -0
  51. package/tsconfig.lib.json +11 -0
  52. package/tsconfig.spec.json +8 -0
  53. package/vitest.config.ts +55 -0
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "argent-grid-demo",
3
+ "version": "1.0.0",
4
+ "description": "Live demo for ArgentGrid - Canvas-based high-performance Angular grid",
5
+ "private": true,
6
+ "scripts": {
7
+ "ng": "ng",
8
+ "start": "ng serve",
9
+ "build": "ng build",
10
+ "watch": "ng build --watch --configuration development",
11
+ "build:gh-pages": "ng build --base-href /ArgentGrid/ --configuration production"
12
+ },
13
+ "dependencies": {
14
+ "@angular/animations": "^18.0.0",
15
+ "@angular/cdk": "^18.0.0",
16
+ "@angular/common": "^18.0.0",
17
+ "@angular/compiler": "^18.0.0",
18
+ "@angular/core": "^18.0.0",
19
+ "@angular/forms": "^18.0.0",
20
+ "@angular/platform-browser": "^18.0.0",
21
+ "@angular/platform-browser-dynamic": "^18.0.0",
22
+ "@angular/router": "^18.0.0",
23
+ "argent-grid": "file:../dist",
24
+ "rxjs": "~7.8.0",
25
+ "tslib": "^2.3.0",
26
+ "exceljs": "^4.4.0"
27
+ },
28
+ "devDependencies": {
29
+ "@angular-devkit/build-angular": "^18.0.0",
30
+ "@angular/cli": "^18.0.0",
31
+ "@angular/compiler-cli": "^18.0.0",
32
+ "@playwright/test": "^1.58.2",
33
+ "@types/exceljs": "^1.3.2",
34
+ "typescript": "~5.4.0"
35
+ }
36
+ }
@@ -0,0 +1,19 @@
1
+ const { chromium } = require('playwright');
2
+ (async () => {
3
+ const browser = await chromium.launch({ headless: true });
4
+ const page = await browser.newPage();
5
+ await page.goto('http://localhost:4201');
6
+
7
+ // wait for grid
8
+ await page.waitForSelector('.argent-grid-container');
9
+
10
+ // hover over first header
11
+ await page.hover('.argent-grid-header-cell:nth-child(2)');
12
+
13
+ // click menu icon
14
+ await page.click('.argent-grid-header-cell:nth-child(2) .argent-grid-header-menu-icon');
15
+
16
+ // screenshot
17
+ await page.screenshot({ path: 'demo-app/e2e/screenshots/menu-open.png' });
18
+ await browser.close();
19
+ })();
@@ -0,0 +1,23 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ export default defineConfig({
4
+ testDir: './e2e',
5
+ fullyParallel: true,
6
+ forbidOnly: !!process.env['CI'],
7
+ retries: process.env['CI'] ? 2 : 0,
8
+ workers: process.env['CI'] ? 1 : undefined,
9
+ reporter: 'list',
10
+ use: {
11
+ baseURL: 'http://localhost:4200',
12
+ trace: 'on-first-retry',
13
+ },
14
+ projects: [
15
+ {
16
+ name: 'chromium',
17
+ use: {
18
+ ...devices['Desktop Chrome'],
19
+ viewport: { width: 1920, height: 1080 }
20
+ },
21
+ },
22
+ ],
23
+ });
@@ -0,0 +1,10 @@
1
+ import { Component } from '@angular/core';
2
+ import { RouterOutlet } from '@angular/router';
3
+
4
+ @Component({
5
+ standalone: true,
6
+ imports: [RouterOutlet],
7
+ selector: 'app-root',
8
+ template: `<router-outlet />`,
9
+ })
10
+ export class AppComponent {}
@@ -0,0 +1,13 @@
1
+ import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';
2
+ import { provideRouter } from '@angular/router';
3
+ import { provideAnimations } from '@angular/platform-browser/animations';
4
+
5
+ import { routes } from './app.routes';
6
+
7
+ export const appConfig: ApplicationConfig = {
8
+ providers: [
9
+ provideExperimentalZonelessChangeDetection(),
10
+ provideRouter(routes),
11
+ provideAnimations(),
12
+ ],
13
+ };
@@ -0,0 +1,7 @@
1
+ import { Routes } from '@angular/router';
2
+ import { DemoPageComponent } from './demo-page/demo-page.component';
3
+
4
+ export const routes: Routes = [
5
+ { path: '', component: DemoPageComponent },
6
+ { path: '**', redirectTo: '' },
7
+ ];
@@ -0,0 +1,313 @@
1
+ /* WIP Banner */
2
+ .wip-banner {
3
+ position: fixed;
4
+ top: 0;
5
+ left: 0;
6
+ right: 0;
7
+ background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
8
+ color: #fff;
9
+ text-align: center;
10
+ padding: 12px 20px;
11
+ font-size: 14px;
12
+ font-weight: 600;
13
+ z-index: 9999;
14
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ gap: 10px;
19
+ }
20
+
21
+ .wip-banner .icon {
22
+ font-size: 18px;
23
+ animation: pulse 2s ease-in-out infinite;
24
+ }
25
+
26
+ @keyframes pulse {
27
+ 0%, 100% { opacity: 1; transform: scale(1); }
28
+ 50% { opacity: 0.7; transform: scale(1.1); }
29
+ }
30
+
31
+ .wip-banner a {
32
+ color: #fff;
33
+ text-decoration: underline;
34
+ margin-left: 10px;
35
+ }
36
+
37
+ /* Demo Container */
38
+ .demo-container {
39
+ max-width: 1800px;
40
+ margin: 0 auto;
41
+ background: #fff;
42
+ border-radius: 12px;
43
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
44
+ overflow: hidden;
45
+ margin-top: 20px;
46
+ height: calc(100vh - 100px);
47
+ display: flex;
48
+ flex-direction: column;
49
+ }
50
+
51
+ .demo-header {
52
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
53
+ color: #fff;
54
+ padding: 30px 40px;
55
+ display: flex;
56
+ justify-content: space-between;
57
+ align-items: center;
58
+ flex-wrap: wrap;
59
+ gap: 20px;
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .demo-header h1 {
64
+ font-size: 28px;
65
+ font-weight: 700;
66
+ margin: 0;
67
+ }
68
+
69
+ .demo-header p {
70
+ color: #a0a0a0;
71
+ margin-top: 8px;
72
+ font-size: 14px;
73
+ }
74
+
75
+ .feature-list {
76
+ display: flex;
77
+ gap: 20px;
78
+ flex-wrap: wrap;
79
+ margin-top: 20px;
80
+ }
81
+
82
+ .feature {
83
+ background: rgba(255, 255, 255, 0.05);
84
+ padding: 8px 14px;
85
+ border-radius: 6px;
86
+ font-size: 13px;
87
+ color: #a0a0a0;
88
+ }
89
+
90
+ .feature.highlight {
91
+ color: #4ade80;
92
+ background: rgba(74, 222, 128, 0.1);
93
+ }
94
+
95
+ .demo-stats {
96
+ display: flex;
97
+ gap: 12px;
98
+ flex-wrap: wrap;
99
+ align-items: center;
100
+ }
101
+
102
+ .stat-badge {
103
+ background: rgba(255, 255, 255, 0.1);
104
+ padding: 0 16px;
105
+ border-radius: 8px;
106
+ font-size: 14px;
107
+ backdrop-filter: blur(10px);
108
+ min-width: 100px;
109
+ text-align: center;
110
+ height: 42px;
111
+ display: flex;
112
+ flex-direction: column;
113
+ justify-content: center;
114
+ align-items: center;
115
+ }
116
+
117
+ .stat-badge strong {
118
+ color: #4ade80;
119
+ font-size: 16px;
120
+ display: block;
121
+ line-height: 1.2;
122
+ }
123
+
124
+ .btn-benchmark {
125
+ background: linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%);
126
+ box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3);
127
+ }
128
+
129
+ .btn-benchmark:hover:not(:disabled) {
130
+ background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%);
131
+ }
132
+
133
+ .benchmark-results {
134
+ background: #1a1a2e;
135
+ color: #fff;
136
+ padding: 20px 40px;
137
+ border-bottom: 2px solid #e11d48;
138
+ animation: slideDown 0.3s ease-out;
139
+ }
140
+
141
+ @keyframes slideDown {
142
+ from { transform: translateY(-100%); opacity: 0; }
143
+ to { transform: translateY(0); opacity: 1; }
144
+ }
145
+
146
+ .benchmark-results h3 {
147
+ margin: 0 0 15px 0;
148
+ font-size: 18px;
149
+ color: #fb7185;
150
+ }
151
+
152
+ .results-grid {
153
+ display: grid;
154
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
155
+ gap: 20px;
156
+ margin-bottom: 15px;
157
+ }
158
+
159
+ .result-item {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 5px;
163
+ }
164
+
165
+ .result-item label {
166
+ font-size: 12px;
167
+ color: #a0a0a0;
168
+ text-transform: uppercase;
169
+ letter-spacing: 1px;
170
+ }
171
+
172
+ .result-item span {
173
+ font-size: 20px;
174
+ font-weight: 700;
175
+ color: #fff;
176
+ font-family: 'Courier New', Courier, monospace;
177
+ }
178
+
179
+ .btn-small {
180
+ padding: 6px 12px;
181
+ font-size: 12px;
182
+ }
183
+
184
+ .demo-controls {
185
+ display: flex;
186
+ gap: 12px;
187
+ width: 100%;
188
+ margin-top: 20px;
189
+ padding-top: 20px;
190
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
191
+ }
192
+
193
+ .btn {
194
+ height: 42px;
195
+ padding: 0 20px;
196
+ background: #4f46e5;
197
+ color: white;
198
+ border: none;
199
+ border-radius: 8px;
200
+ cursor: pointer;
201
+ font-size: 14px;
202
+ font-weight: 600;
203
+ transition: all 0.2s;
204
+ display: inline-flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ white-space: nowrap;
208
+ }
209
+
210
+ .btn-secondary {
211
+ background: rgba(255, 255, 255, 0.1);
212
+ color: #fff;
213
+ border: 1px solid rgba(255, 255, 255, 0.2);
214
+ }
215
+
216
+ .btn-secondary:hover:not(:disabled) {
217
+ background: rgba(255, 255, 255, 0.2);
218
+ }
219
+
220
+ .btn:hover:not(:disabled) {
221
+ background: #4338ca;
222
+ transform: translateY(-1px);
223
+ }
224
+
225
+ .btn:disabled {
226
+ opacity: 0.5;
227
+ cursor: not-allowed;
228
+ }
229
+
230
+ .grid-wrapper {
231
+ flex: 1;
232
+ position: relative;
233
+ overflow: hidden;
234
+ background: #fafafa;
235
+ min-height: 0;
236
+ }
237
+
238
+ .loading {
239
+ display: flex;
240
+ flex-direction: column;
241
+ align-items: center;
242
+ justify-content: center;
243
+ height: 100%;
244
+ color: #666;
245
+ }
246
+
247
+ .loading-spinner {
248
+ width: 50px;
249
+ height: 50px;
250
+ border: 4px solid #e0e0e0;
251
+ border-top-color: #4f46e5;
252
+ border-radius: 50%;
253
+ animation: spin 1s linear infinite;
254
+ margin-bottom: 20px;
255
+ }
256
+
257
+ @keyframes spin {
258
+ to { transform: rotate(360deg); }
259
+ }
260
+
261
+ .footer {
262
+ background: #f5f5f5;
263
+ padding: 15px 40px;
264
+ border-top: 1px solid #e0e0e0;
265
+ font-size: 13px;
266
+ color: #666;
267
+ display: flex;
268
+ justify-content: space-between;
269
+ align-items: center;
270
+ flex-shrink: 0;
271
+ }
272
+
273
+ .footer a {
274
+ color: #4f46e5;
275
+ text-decoration: none;
276
+ }
277
+
278
+ .footer a:hover {
279
+ text-decoration: underline;
280
+ }
281
+
282
+ /* Responsive */
283
+ @media (max-width: 1024px) {
284
+ .demo-header {
285
+ flex-direction: column;
286
+ align-items: flex-start;
287
+ }
288
+
289
+ .demo-stats {
290
+ width: 100%;
291
+ justify-content: flex-start;
292
+ }
293
+
294
+ .feature-list {
295
+ gap: 10px;
296
+ }
297
+ }
298
+
299
+ /* Override argent-grid styles for demo */
300
+ argent-grid {
301
+ display: block;
302
+ height: 100%;
303
+ width: 100%;
304
+ }
305
+
306
+ argent-grid .argent-grid-container {
307
+ height: 100% !important;
308
+ border: none;
309
+ }
310
+
311
+ argent-grid .argent-grid-viewport {
312
+ height: calc(100% - 40px);
313
+ }
@@ -0,0 +1,124 @@
1
+ <!-- WIP Banner -->
2
+ <div class="wip-banner">
3
+ <span class="icon">🚧</span>
4
+ <span>WORK IN PROGRESS - Using ArgentGrid Component</span>
5
+ <a href="https://github.com/HainanZhao/ArgentGrid" target="_blank">View on GitHub →</a>
6
+ </div>
7
+
8
+ <div class="demo-container">
9
+ <header class="demo-header">
10
+ <div>
11
+ <h1>🎨 ArgentGrid - Canvas-Based Grid</h1>
12
+ <p>Ultra-High Performance - 1,000,000+ Rows at 60fps</p>
13
+ <div class="feature-list">
14
+ <span class="feature highlight">✓ Canvas Rendering</span>
15
+ <span class="feature highlight">✓ Zero DOM Overhead</span>
16
+ <span class="feature highlight">✓ GPU Accelerated</span>
17
+ <span class="feature">✓ AG Grid Compatible</span>
18
+ <span class="feature">✓ < 50KB Bundle</span>
19
+ </div>
20
+ </div>
21
+ <div class="demo-stats">
22
+ <div class="stat-badge">
23
+ <strong>{{ rowCount | number }}</strong> rows
24
+ </div>
25
+ <div class="stat-badge">
26
+ <strong>{{ renderTime }}</strong>ms load
27
+ </div>
28
+ <div class="stat-badge" title="Time taken by canvas to render the last frame">
29
+ <strong>{{ canvasFrameTime }}</strong>ms frame
30
+ </div>
31
+ <div class="stat-badge">
32
+ <strong>{{ fps }}</strong> FPS
33
+ </div>
34
+ <button class="btn btn-benchmark" (click)="runBenchmark()" [disabled]="isLoading || isBenchmarking">
35
+ {{ isBenchmarking ? 'Running...' : '🚀 Benchmark' }}
36
+ </button>
37
+ <button class="btn" (click)="loadData(100000)" [disabled]="isLoading || isBenchmarking">
38
+ {{ isLoading ? 'Loading...' : '100K' }}
39
+ </button>
40
+ <button class="btn" (click)="loadData(500000)" [disabled]="isLoading">
41
+ {{ isLoading ? 'Loading...' : '500K' }}
42
+ </button>
43
+ <button class="btn" (click)="loadData(1000000)" [disabled]="isLoading">
44
+ {{ isLoading ? 'Loading...' : '1M' }}
45
+ </button>
46
+ </div>
47
+ <div class="demo-controls">
48
+ <button class="btn btn-secondary" (click)="toggleGrouping()">
49
+ {{ isGrouped ? 'Ungroup' : 'Group by Dept' }}
50
+ </button>
51
+ <button class="btn btn-secondary" (click)="toggleMasterDetail()">
52
+ {{ isMasterDetail ? 'Disable Detail' : 'Master/Detail' }}
53
+ </button>
54
+ <button class="btn btn-secondary" (click)="togglePivotMode()">
55
+ {{ isPivotMode ? 'Disable Pivot' : 'Pivot Mode' }}
56
+ </button>
57
+ <button class="btn btn-secondary" (click)="toggleSideBar()">
58
+ {{ isSideBarVisible ? 'Hide Sidebar' : 'Show Sidebar' }}
59
+ </button>
60
+ <button class="btn btn-secondary" (click)="toggleFloatingFilter()">
61
+ {{ isFloatingFilterShown ? 'Hide Filters' : 'Show Filters' }}
62
+ </button>
63
+ <button class="btn btn-secondary" (click)="toggleFilter()">
64
+ {{ isFiltered ? 'Clear Filter' : 'Filter "Eng"' }}
65
+ </button>
66
+ </div>
67
+ </header>
68
+
69
+ @if (benchmarkResults) {
70
+ <div class="benchmark-results">
71
+ <h3>🚀 Benchmark Results ({{ rowCount | number }} rows)</h3>
72
+ <div class="results-grid">
73
+ <div class="result-item">
74
+ <label>Initial Render:</label>
75
+ <span>{{ benchmarkResults.initialRender.toFixed(2) }}ms</span>
76
+ </div>
77
+ <div class="result-item">
78
+ <label>Avg Scroll Frame:</label>
79
+ <span>{{ benchmarkResults.scrollFrameAverage.toFixed(2) }}ms</span>
80
+ </div>
81
+ <div class="result-item">
82
+ <label>Selection All:</label>
83
+ <span>{{ benchmarkResults.selectionUpdateTime.toFixed(2) }}ms</span>
84
+ </div>
85
+ <div class="result-item">
86
+ <label>Grouping Toggle:</label>
87
+ <span>{{ benchmarkResults.groupingUpdateTime.toFixed(2) }}ms</span>
88
+ </div>
89
+ <div class="result-item">
90
+ <label>Total Test Time:</label>
91
+ <span>{{ benchmarkResults.totalTime.toFixed(2) }}ms</span>
92
+ </div>
93
+ </div>
94
+ <button class="btn btn-small" (click)="benchmarkResults = null">Close</button>
95
+ </div>
96
+ }
97
+
98
+ <div class="grid-wrapper">
99
+ @if (isLoading) {
100
+ <div class="loading">
101
+ <div class="loading-spinner"></div>
102
+ <p>Loading {{ rowCount | number }} rows...</p>
103
+ </div>
104
+ } @else {
105
+ <argent-grid
106
+ [columnDefs]="columnDefs"
107
+ [rowData]="rowData"
108
+ [gridOptions]="gridOptions"
109
+ [rowHeight]="32"
110
+ [height]="'100%'"
111
+ [width]="'100%'"
112
+ (gridReady)="onGridReady($event)"
113
+ (rowClicked)="onRowClicked($event)"
114
+ />
115
+ }
116
+ </div>
117
+
118
+ <div class="footer">
119
+ <span>Built with Angular 18 + Canvas 2D API</span>
120
+ <span>
121
+ <a href="https://github.com/HainanZhao/ArgentGrid" target="_blank">View on GitHub →</a>
122
+ </span>
123
+ </div>
124
+ </div>