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,132 @@
1
+ import { provideExperimentalZonelessChangeDetection } from '@angular/core';
2
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
3
+ import { describe, it, expect, beforeEach } from 'vitest';
4
+ import { ArgentGridComponent } from './argent-grid.component';
5
+ import { ArgentGridModule } from '../argent-grid.module';
6
+ import { GridService } from '../services/grid.service';
7
+
8
+ interface TestData {
9
+ id: number;
10
+ name: string;
11
+ }
12
+
13
+ describe('ArgentGridComponent - Selection Behavior', () => {
14
+ let component: ArgentGridComponent<TestData>;
15
+ let fixture: ComponentFixture<ArgentGridComponent<TestData>>;
16
+
17
+ const testColumnDefs = [
18
+ { field: 'id', headerName: 'ID', checkboxSelection: true },
19
+ { field: 'name', headerName: 'Name' },
20
+ ];
21
+
22
+ const testRowData = [
23
+ { id: 1, name: 'Item 1' },
24
+ { id: 2, name: 'Item 2' },
25
+ { id: 3, name: 'Item 3' },
26
+ ];
27
+
28
+ beforeEach(async () => {
29
+ await TestBed.configureTestingModule({
30
+ imports: [ArgentGridModule],
31
+ providers: [GridService, provideExperimentalZonelessChangeDetection()],
32
+ }).compileComponents();
33
+
34
+ fixture = TestBed.createComponent(ArgentGridComponent);
35
+ component = fixture.componentInstance;
36
+ component.columnDefs = testColumnDefs;
37
+ component.rowData = testRowData;
38
+ component.rowSelection = 'multiple';
39
+ fixture.detectChanges();
40
+ });
41
+
42
+ it('should initialize with no rows selected', () => {
43
+ expect(component.isAllSelected).toBe(false);
44
+ expect(component.isIndeterminateSelection).toBe(false);
45
+ expect(component.getApi().getSelectedRows().length).toBe(0);
46
+ });
47
+
48
+ it('should toggle all rows when header checkbox is clicked', () => {
49
+ // Select all
50
+ const event = { target: { checked: true } } as any;
51
+ component.onSelectionHeaderChange(event);
52
+
53
+ expect(component.getApi().getSelectedRows().length).toBe(3);
54
+ expect(component.isAllSelected).toBe(true);
55
+ expect(component.isIndeterminateSelection).toBe(false);
56
+
57
+ // Deselect all
58
+ event.target.checked = false;
59
+ component.onSelectionHeaderChange(event);
60
+
61
+ expect(component.getApi().getSelectedRows().length).toBe(0);
62
+ expect(component.isAllSelected).toBe(false);
63
+ expect(component.isIndeterminateSelection).toBe(false);
64
+ });
65
+
66
+ it('should update indeterminate state when single row is selected', () => {
67
+ const api = component.getApi();
68
+ const node = api.getDisplayedRowAtIndex(0);
69
+
70
+ node?.setSelected(true);
71
+ // GridState listener in component should trigger updateSelectionState
72
+ // We might need to manually trigger or wait for microtasks if not using zone.js
73
+ component.updateSelectionState();
74
+
75
+ expect(component.getApi().getSelectedRows().length).toBe(1);
76
+ expect(component.isAllSelected).toBe(false);
77
+ expect(component.isIndeterminateSelection).toBe(true);
78
+ });
79
+
80
+ it('should toggle selection when a row is clicked', () => {
81
+ const api = component.getApi();
82
+ const mouseEvent = new MouseEvent('click');
83
+
84
+ // Select row
85
+ component.onRowClick(0, mouseEvent);
86
+ expect(api.getDisplayedRowAtIndex(0)?.selected).toBe(true);
87
+
88
+ // Unselect row by clicking again (toggle behavior)
89
+ component.onRowClick(0, mouseEvent);
90
+ expect(api.getDisplayedRowAtIndex(0)?.selected).toBe(false);
91
+ });
92
+
93
+ it('should clear others when clicking a row without modifiers', () => {
94
+ const api = component.getApi();
95
+ const mouseEvent = new MouseEvent('click');
96
+
97
+ // Select first row
98
+ component.onRowClick(0, mouseEvent);
99
+ expect(api.getDisplayedRowAtIndex(0)?.selected).toBe(true);
100
+
101
+ // Click second row
102
+ component.onRowClick(1, mouseEvent);
103
+ expect(api.getDisplayedRowAtIndex(0)?.selected).toBe(false);
104
+ expect(api.getDisplayedRowAtIndex(1)?.selected).toBe(true);
105
+ });
106
+
107
+ it('should keep multiple selection when using Ctrl key', () => {
108
+ const api = component.getApi();
109
+ const ctrlEvent = new MouseEvent('click', { ctrlKey: true });
110
+
111
+ component.onRowClick(0, ctrlEvent);
112
+ component.onRowClick(1, ctrlEvent);
113
+
114
+ expect(api.getDisplayedRowAtIndex(0)?.selected).toBe(true);
115
+ expect(api.getDisplayedRowAtIndex(1)?.selected).toBe(true);
116
+ expect(api.getSelectedRows().length).toBe(2);
117
+ });
118
+
119
+ it('should disable sorting and menu for dedicated selection column', () => {
120
+ const selectionCol = component.getApi().getAllColumns()[0];
121
+ expect(selectionCol.colId).toBe('ag-Grid-SelectionColumn');
122
+
123
+ expect(component.isSortable(selectionCol)).toBe(false);
124
+ expect(component.hasHeaderMenu(selectionCol)).toBe(false);
125
+ expect(component.getHeaderName(selectionCol)).toBe('');
126
+ });
127
+
128
+ it('should allow resizing for dedicated selection column', () => {
129
+ const selectionCol = component.getApi().getAllColumns()[0];
130
+ expect(component.isResizable(selectionCol)).toBe(true);
131
+ });
132
+ });
@@ -0,0 +1,302 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ EventEmitter,
5
+ Input,
6
+ OnInit,
7
+ Output,
8
+ } from '@angular/core';
9
+
10
+ @Component({
11
+ selector: 'argent-set-filter',
12
+ standalone: true,
13
+ imports: [],
14
+ changeDetection: ChangeDetectionStrategy.OnPush,
15
+ template: `
16
+ <div class="set-filter">
17
+ <!-- Search Box -->
18
+ <div class="set-filter-search">
19
+ <input
20
+ type="text"
21
+ [value]="searchText"
22
+ (input)="onSearchInput($event)"
23
+ placeholder="Search..."
24
+ class="set-filter-input"
25
+ />
26
+ </div>
27
+
28
+ <!-- Select All / Clear All -->
29
+ <div class="set-filter-actions">
30
+ <button
31
+ type="button"
32
+ (click)="selectAll()"
33
+ class="set-filter-btn"
34
+ >
35
+ Select All
36
+ </button>
37
+ <button
38
+ type="button"
39
+ (click)="clearAll()"
40
+ class="set-filter-btn"
41
+ >
42
+ Clear All
43
+ </button>
44
+ </div>
45
+
46
+ <!-- Checkbox List -->
47
+ <div class="set-filter-list">
48
+ @for (item of filteredValues; track item.value) {
49
+ <label class="set-filter-item">
50
+ <input
51
+ type="checkbox"
52
+ [checked]="item.selected"
53
+ (change)="onValueToggled(item.value, $event)"
54
+ />
55
+ <span class="set-filter-label">{{ item.displayValue }}</span>
56
+ <span class="set-filter-count">({{ item.count }})</span>
57
+ </label>
58
+ } @empty {
59
+ <div class="set-filter-empty">
60
+ No values found
61
+ </div>
62
+ }
63
+ </div>
64
+
65
+ <!-- Action Buttons -->
66
+ <div class="set-filter-footer">
67
+ <button
68
+ type="button"
69
+ (click)="applyFilter()"
70
+ class="set-filter-apply"
71
+ >
72
+ Apply
73
+ </button>
74
+ <button
75
+ type="button"
76
+ (click)="resetFilter()"
77
+ class="set-filter-reset"
78
+ >
79
+ Reset
80
+ </button>
81
+ </div>
82
+ </div>
83
+ `,
84
+ styles: [
85
+ `
86
+ .set-filter {
87
+ display: flex;
88
+ flex-direction: column;
89
+ width: 250px;
90
+ max-height: 400px;
91
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
92
+ font-size: 13px;
93
+ }
94
+
95
+ .set-filter-search {
96
+ padding: 8px;
97
+ border-bottom: 1px solid #e0e0e0;
98
+ }
99
+
100
+ .set-filter-input {
101
+ width: 100%;
102
+ padding: 6px 10px;
103
+ border: 1px solid #d0d0d0;
104
+ border-radius: 4px;
105
+ font-size: 13px;
106
+ box-sizing: border-box;
107
+ }
108
+
109
+ .set-filter-input:focus {
110
+ outline: none;
111
+ border-color: #4f46e5;
112
+ box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2);
113
+ }
114
+
115
+ .set-filter-actions {
116
+ display: flex;
117
+ gap: 8px;
118
+ padding: 8px;
119
+ border-bottom: 1px solid #e0e0e0;
120
+ }
121
+
122
+ .set-filter-btn {
123
+ flex: 1;
124
+ padding: 4px 8px;
125
+ border: 1px solid #d0d0d0;
126
+ border-radius: 4px;
127
+ background: #fff;
128
+ cursor: pointer;
129
+ font-size: 12px;
130
+ }
131
+
132
+ .set-filter-btn:hover {
133
+ background: #f5f5f5;
134
+ border-color: #4f46e5;
135
+ }
136
+
137
+ .set-filter-list {
138
+ flex: 1;
139
+ overflow-y: auto;
140
+ padding: 8px 0;
141
+ max-height: 250px;
142
+ }
143
+
144
+ .set-filter-item {
145
+ display: flex;
146
+ align-items: center;
147
+ padding: 4px 12px;
148
+ cursor: pointer;
149
+ gap: 8px;
150
+ }
151
+
152
+ .set-filter-item:hover {
153
+ background: #f5f5f5;
154
+ }
155
+
156
+ .set-filter-item input[type="checkbox"] {
157
+ margin: 0;
158
+ cursor: pointer;
159
+ }
160
+
161
+ .set-filter-label {
162
+ flex: 1;
163
+ overflow: hidden;
164
+ text-overflow: ellipsis;
165
+ white-space: nowrap;
166
+ }
167
+
168
+ .set-filter-count {
169
+ color: #999;
170
+ font-size: 11px;
171
+ margin-left: 4px;
172
+ }
173
+
174
+ .set-filter-empty {
175
+ padding: 16px;
176
+ text-align: center;
177
+ color: #999;
178
+ font-style: italic;
179
+ }
180
+
181
+ .set-filter-footer {
182
+ display: flex;
183
+ gap: 8px;
184
+ padding: 8px;
185
+ border-top: 1px solid #e0e0e0;
186
+ }
187
+
188
+ .set-filter-apply,
189
+ .set-filter-reset {
190
+ flex: 1;
191
+ padding: 6px 12px;
192
+ border: none;
193
+ border-radius: 4px;
194
+ cursor: pointer;
195
+ font-size: 13px;
196
+ }
197
+
198
+ .set-filter-apply {
199
+ background: #4f46e5;
200
+ color: #fff;
201
+ }
202
+
203
+ .set-filter-apply:hover {
204
+ background: #4338ca;
205
+ }
206
+
207
+ .set-filter-reset {
208
+ background: #f5f5f5;
209
+ color: #333;
210
+ }
211
+
212
+ .set-filter-reset:hover {
213
+ background: #e5e5e5;
214
+ }
215
+ `,
216
+ ],
217
+ })
218
+ export class SetFilterComponent<T = any> implements OnInit {
219
+ @Input() values: T[] = [];
220
+ @Input() valueFormatter?: (value: T) => string;
221
+ @Output() filterChanged = new EventEmitter<T[]>();
222
+
223
+ searchText = '';
224
+ selectedValues: T[] = [];
225
+ allValues: Array<{ value: T; displayValue: string; count: number; selected: boolean }> = [];
226
+
227
+ get filteredValues() {
228
+ if (!this.searchText) {
229
+ return this.allValues;
230
+ }
231
+ const search = this.searchText.toLowerCase();
232
+ return this.allValues.filter((item) => item.displayValue.toLowerCase().includes(search));
233
+ }
234
+
235
+ ngOnInit(): void {
236
+ this.initializeValues();
237
+ }
238
+
239
+ onSearchInput(event: Event): void {
240
+ this.searchText = (event.target as HTMLInputElement).value;
241
+ }
242
+
243
+ private initializeValues(): void {
244
+ // Count occurrences of each value
245
+ const valueCounts = new Map<T, number>();
246
+ this.values.forEach((value) => {
247
+ valueCounts.set(value, (valueCounts.get(value) || 0) + 1);
248
+ });
249
+
250
+ // Build value list with counts
251
+ this.allValues = Array.from(valueCounts.entries()).map(([value, count]) => ({
252
+ value,
253
+ displayValue: this.valueFormatter ? this.valueFormatter(value) : String(value),
254
+ count,
255
+ selected: true, // All selected by default
256
+ }));
257
+
258
+ this.selectedValues = this.values.filter((v, i, arr) => arr.indexOf(v) === i);
259
+ }
260
+
261
+ onSearchChanged(): void {
262
+ // Trigger change detection through filteredValues getter
263
+ }
264
+
265
+ onValueToggled(value: T, event: Event): void {
266
+ const checkbox = event.target as HTMLInputElement;
267
+ const item = this.allValues.find((v) => v.value === value);
268
+ if (item) {
269
+ item.selected = checkbox.checked;
270
+ }
271
+
272
+ this.updateSelectedValues();
273
+ }
274
+
275
+ selectAll(): void {
276
+ this.allValues.forEach((item) => {
277
+ item.selected = true;
278
+ });
279
+ this.updateSelectedValues();
280
+ }
281
+
282
+ clearAll(): void {
283
+ this.allValues.forEach((item) => {
284
+ item.selected = false;
285
+ });
286
+ this.updateSelectedValues();
287
+ }
288
+
289
+ private updateSelectedValues(): void {
290
+ this.selectedValues = this.allValues.filter((item) => item.selected).map((item) => item.value);
291
+ }
292
+
293
+ applyFilter(): void {
294
+ this.filterChanged.emit(this.selectedValues);
295
+ }
296
+
297
+ resetFilter(): void {
298
+ this.searchText = '';
299
+ this.initializeValues();
300
+ this.filterChanged.emit(this.selectedValues);
301
+ }
302
+ }
@@ -1,24 +1,16 @@
1
- import {
2
- Directive,
3
- Input,
4
- OnInit,
5
- OnDestroy,
6
- ComponentRef,
7
- ViewContainerRef,
8
- ComponentFactoryResolver
9
- } from '@angular/core';
10
- import { GridOptions, ColDef, ColGroupDef } from '../types/ag-grid-types';
1
+ import { ComponentRef, Directive, Input, OnDestroy, OnInit, ViewContainerRef } from '@angular/core';
11
2
  import { ArgentGridComponent } from '../components/argent-grid.component';
3
+ import { ColDef, ColGroupDef, GridOptions } from '../types/ag-grid-types';
12
4
 
13
5
  /**
14
6
  * AgGridCompatibilityDirective - Drop-in replacement for AG Grid
15
- *
7
+ *
16
8
  * This directive allows users to migrate from AG Grid to ArgentGrid
17
9
  * by simply changing their import statement. It maps AG Grid's
18
10
  * [columnDefs] and [rowData] inputs to ArgentGrid's internal format.
19
11
  */
20
12
  @Directive({
21
- selector: '[argentGrid], ag-grid-angular'
13
+ selector: '[argentGrid], ag-grid-angular',
22
14
  })
23
15
  export class AgGridCompatibilityDirective<TData = any> implements OnInit, OnDestroy {
24
16
  @Input('columnDefs') set columnDefs(value: (ColDef<TData> | ColGroupDef<TData>)[] | null) {
@@ -29,7 +21,7 @@ export class AgGridCompatibilityDirective<TData = any> implements OnInit, OnDest
29
21
  return this._columnDefs;
30
22
  }
31
23
  private _columnDefs: (ColDef<TData> | ColGroupDef<TData>)[] | null = null;
32
-
24
+
33
25
  @Input('rowData') set rowData(value: TData[] | null) {
34
26
  this._rowData = value;
35
27
  this.updateGrid();
@@ -38,7 +30,7 @@ export class AgGridCompatibilityDirective<TData = any> implements OnInit, OnDest
38
30
  return this._rowData;
39
31
  }
40
32
  private _rowData: TData[] | null = null;
41
-
33
+
42
34
  @Input('gridOptions') set gridOptions(value: GridOptions<TData>) {
43
35
  this._gridOptions = value;
44
36
  this.updateGrid();
@@ -47,42 +39,40 @@ export class AgGridCompatibilityDirective<TData = any> implements OnInit, OnDest
47
39
  return this._gridOptions;
48
40
  }
49
41
  private _gridOptions: GridOptions<TData> | null = null;
50
-
42
+
51
43
  private gridComponentRef: ComponentRef<ArgentGridComponent<TData>> | null = null;
52
-
53
- constructor(
54
- private viewContainerRef: ViewContainerRef
55
- ) {}
56
-
44
+
45
+ constructor(private viewContainerRef: ViewContainerRef) {}
46
+
57
47
  ngOnInit(): void {
58
48
  this.createGridComponent();
59
49
  }
60
-
50
+
61
51
  ngOnDestroy(): void {
62
52
  if (this.gridComponentRef) {
63
53
  this.gridComponentRef.destroy();
64
54
  }
65
55
  }
66
-
56
+
67
57
  private createGridComponent(): void {
68
58
  // Create ArgentGrid component
69
59
  this.gridComponentRef = this.viewContainerRef.createComponent(ArgentGridComponent as any);
70
-
60
+
71
61
  this.updateGrid();
72
62
  }
73
-
63
+
74
64
  private updateGrid(): void {
75
65
  if (!this.gridComponentRef) {
76
66
  return;
77
67
  }
78
-
68
+
79
69
  // Map AG Grid inputs to ArgentGrid component
80
70
  this.gridComponentRef.instance.columnDefs = this.columnDefs;
81
71
  this.gridComponentRef.instance.rowData = this.rowData;
82
72
  this.gridComponentRef.instance.gridOptions = this.gridOptions || undefined;
83
73
  this.gridComponentRef.instance.refresh();
84
74
  }
85
-
75
+
86
76
  /**
87
77
  * Get the underlying GridApi for programmatic access
88
78
  */
@@ -0,0 +1,19 @@
1
+ import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[clickOutside]',
5
+ standalone: true,
6
+ })
7
+ export class ClickOutsideDirective {
8
+ @Output() clickOutside = new EventEmitter<void>();
9
+
10
+ constructor(private elementRef: ElementRef) {}
11
+
12
+ @HostListener('document:click', ['$event.target'])
13
+ onClick(target: HTMLElement): void {
14
+ const clickedInside = this.elementRef.nativeElement.contains(target);
15
+ if (!clickedInside) {
16
+ this.clickOutside.emit();
17
+ }
18
+ }
19
+ }