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
@@ -0,0 +1,301 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { ArgentGridComponent } from './argent-grid.component';
3
+
4
+ describe('ArgentGridComponent - Regression Protection', () => {
5
+ let component: ArgentGridComponent<any>;
6
+ let mockCdr: any;
7
+ let mockElementRef: any;
8
+
9
+ beforeEach(() => {
10
+ // Mock canvas context
11
+ HTMLCanvasElement.prototype.getContext = vi.fn().mockReturnValue({
12
+ measureText: vi.fn().mockReturnValue({ width: 50 }),
13
+ } as any);
14
+
15
+ mockCdr = {
16
+ detectChanges: vi.fn(),
17
+ markForCheck: vi.fn(),
18
+ };
19
+
20
+ mockElementRef = {
21
+ nativeElement: {
22
+ getBoundingClientRect: () => ({ left: 100, top: 100, right: 1100, bottom: 900 }),
23
+ offsetWidth: 1000,
24
+ offsetHeight: 800,
25
+ style: {},
26
+ },
27
+ };
28
+
29
+ component = new ArgentGridComponent(mockCdr, mockElementRef);
30
+
31
+ // Mock canvasRenderer
32
+ (component as any).canvasRenderer = {
33
+ getHitTestResult: vi.fn().mockReturnValue({ rowIndex: 0, columnIndex: 0 }),
34
+ render: vi.fn(),
35
+ renderFrame: vi.fn(),
36
+ };
37
+
38
+ component.columnDefs = [{ field: 'id', headerName: 'ID' }];
39
+ component.rowData = [{ id: 1 }];
40
+ component.ngOnInit();
41
+ });
42
+
43
+ it('should hide row group panel by default', () => {
44
+ expect(component.isRowGroupPanelVisible()).toBe(false);
45
+ });
46
+
47
+ it('should show row group panel when always is set', () => {
48
+ component.gridOptions = { rowGroupPanelShow: 'always' };
49
+ // Simulate option change
50
+ (component as any).onGridOptionsChanged(component.gridOptions);
51
+ expect(component.isRowGroupPanelVisible()).toBe(true);
52
+ });
53
+
54
+ it('should position header menu relative to container', () => {
55
+ const mockEvent = {
56
+ stopPropagation: vi.fn(),
57
+ target: {
58
+ getBoundingClientRect: () => ({ left: 200, top: 120, right: 220, bottom: 140 }),
59
+ },
60
+ } as any;
61
+
62
+ const col = component.getApi().getAllColumns()[0];
63
+ component.onHeaderMenuClick(mockEvent, col);
64
+
65
+ expect(component.activeHeaderMenu).toBe(col);
66
+ // Container is at 100, 100. Icon is at 200, 120.
67
+ // Relative X = IconLeft - ContainerLeft = 200 - 100 = 100
68
+ // Relative Y = IconBottom - ContainerTop + 4 = 140 - 100 + 4 = 44
69
+ expect(component.headerMenuPosition.x).toBe(100);
70
+ expect(component.headerMenuPosition.y).toBe(44);
71
+ });
72
+
73
+ it('should position context menu relative to container', () => {
74
+ const mockEvent = {
75
+ preventDefault: vi.fn(),
76
+ stopPropagation: vi.fn(),
77
+ clientX: 250,
78
+ clientY: 300,
79
+ } as any;
80
+
81
+ // Mock row node finding logic
82
+ const rowNode = { id: '1', data: { id: 1 }, selected: false, setSelected: vi.fn() };
83
+ vi.spyOn(component.getApi(), 'getDisplayedRowAtIndex').mockReturnValue(rowNode as any);
84
+
85
+ component.onCanvasContextMenu(mockEvent);
86
+
87
+ // Relative X = clientX - ContainerLeft = 250 - 100 = 150
88
+ // Relative Y = clientY - ContainerTop = 300 - 100 = 200
89
+ expect(component.contextMenuPosition.x).toBe(150);
90
+ expect(component.contextMenuPosition.y).toBe(200);
91
+ });
92
+
93
+ it('should update rowGroupColumns when columns change', () => {
94
+ expect(component.rowGroupColumns.length).toBe(0);
95
+
96
+ // Add group column via API
97
+ component.getApi().addRowGroupColumn('id');
98
+
99
+ // The component listens to gridStateChanged$ which calls updateRowGroupColumns
100
+ expect(component.rowGroupColumns.length).toBe(1);
101
+ expect(component.rowGroupColumns[0].colId).toBe('id');
102
+ });
103
+ });
104
+
105
+ // ─── Header Filter Button ──────────────────────────────────────────────────
106
+
107
+ describe('ArgentGridComponent - Header Filter Button', () => {
108
+ let component: ArgentGridComponent<any>;
109
+
110
+ beforeEach(() => {
111
+ HTMLCanvasElement.prototype.getContext = vi.fn().mockReturnValue({
112
+ measureText: vi.fn().mockReturnValue({ width: 50 }),
113
+ } as any);
114
+
115
+ const mockCdr = { detectChanges: vi.fn(), markForCheck: vi.fn() };
116
+ const mockElementRef = {
117
+ nativeElement: {
118
+ getBoundingClientRect: () => ({ left: 0, top: 0 }),
119
+ offsetWidth: 1000,
120
+ offsetHeight: 800,
121
+ style: {},
122
+ },
123
+ };
124
+
125
+ component = new ArgentGridComponent(mockCdr as any, mockElementRef as any);
126
+ component.columnDefs = [
127
+ { field: 'name', filter: 'text' },
128
+ { field: 'dept', filter: 'set' },
129
+ { field: 'salary', filter: 'number', floatingFilter: true },
130
+ { field: 'id' }, // no filter
131
+ { field: 'status', filter: 'text', suppressHeaderFilterButton: true },
132
+ ];
133
+ component.rowData = [{ name: 'Alice', dept: 'Eng', salary: 100, id: 1, status: 'active' }];
134
+ component.ngOnInit();
135
+ });
136
+
137
+ describe('hasHeaderFilterButton', () => {
138
+ it('should return true for column with filter and no floatingFilter', () => {
139
+ const col = component
140
+ .getApi()
141
+ .getAllColumns()
142
+ .find((c) => c.field === 'name')!;
143
+ expect(component.hasHeaderFilterButton(col)).toBe(true);
144
+ });
145
+
146
+ it('should return true for set-filter column', () => {
147
+ const col = component
148
+ .getApi()
149
+ .getAllColumns()
150
+ .find((c) => c.field === 'dept')!;
151
+ expect(component.hasHeaderFilterButton(col)).toBe(true);
152
+ });
153
+
154
+ it('should return false when floatingFilter is enabled', () => {
155
+ const col = component
156
+ .getApi()
157
+ .getAllColumns()
158
+ .find((c) => c.field === 'salary')!;
159
+ expect(component.hasHeaderFilterButton(col)).toBe(false);
160
+ });
161
+
162
+ it('should return false when no filter configured', () => {
163
+ const col = component
164
+ .getApi()
165
+ .getAllColumns()
166
+ .find((c) => c.field === 'id')!;
167
+ expect(component.hasHeaderFilterButton(col)).toBe(false);
168
+ });
169
+
170
+ it('should return false when suppressHeaderFilterButton is true', () => {
171
+ const col = component
172
+ .getApi()
173
+ .getAllColumns()
174
+ .find((c) => c.field === 'status')!;
175
+ expect(component.hasHeaderFilterButton(col)).toBe(false);
176
+ });
177
+
178
+ it('should return false for the selection column', () => {
179
+ const fakeSelCol = { colId: 'ag-Grid-SelectionColumn' } as any;
180
+ expect(component.hasHeaderFilterButton(fakeSelCol)).toBe(false);
181
+ });
182
+ });
183
+
184
+ describe('isColumnFiltered', () => {
185
+ it('should return false when no filter is applied', () => {
186
+ const col = component
187
+ .getApi()
188
+ .getAllColumns()
189
+ .find((c) => c.field === 'name')!;
190
+ expect(component.isColumnFiltered(col)).toBe(false);
191
+ });
192
+
193
+ it('should return true when a filter is active on the column', () => {
194
+ component
195
+ .getApi()
196
+ .setFilterModel({ name: { filterType: 'text', type: 'contains', filter: 'Alice' } });
197
+ const col = component
198
+ .getApi()
199
+ .getAllColumns()
200
+ .find((c) => c.field === 'name')!;
201
+ expect(component.isColumnFiltered(col)).toBe(true);
202
+ });
203
+
204
+ it('should return false for a different column when only one filtered', () => {
205
+ component
206
+ .getApi()
207
+ .setFilterModel({ name: { filterType: 'text', type: 'contains', filter: 'Alice' } });
208
+ const col = component
209
+ .getApi()
210
+ .getAllColumns()
211
+ .find((c) => c.field === 'dept')!;
212
+ expect(component.isColumnFiltered(col)).toBe(false);
213
+ });
214
+ });
215
+
216
+ describe('openColumnsPanel', () => {
217
+ it('should make sideBarVisible true', () => {
218
+ component.sideBarVisible = false;
219
+ component.openColumnsPanel();
220
+ expect(component.sideBarVisible).toBe(true);
221
+ });
222
+
223
+ it('should set activeToolPanel to columns', () => {
224
+ component.activeToolPanel = null;
225
+ component.openColumnsPanel();
226
+ expect(component.activeToolPanel).toBe('columns');
227
+ });
228
+
229
+ it('should close any open header menu', () => {
230
+ const col = component.getApi().getAllColumns()[0];
231
+ component.activeHeaderMenu = col;
232
+ component.openColumnsPanel();
233
+ expect(component.activeHeaderMenu).toBeNull();
234
+ });
235
+ });
236
+ });
237
+
238
+ // ─── openSetFilter restores selected values ─────────────────────────────────
239
+
240
+ describe('ArgentGridComponent - openSetFilter restores filter state', () => {
241
+ let component: ArgentGridComponent<any>;
242
+
243
+ beforeEach(() => {
244
+ HTMLCanvasElement.prototype.getContext = vi.fn().mockReturnValue({
245
+ measureText: vi.fn().mockReturnValue({ width: 50 }),
246
+ } as any);
247
+
248
+ const mockCdr = { detectChanges: vi.fn(), markForCheck: vi.fn() };
249
+ const mockElementRef = {
250
+ nativeElement: {
251
+ getBoundingClientRect: () => ({ left: 0, top: 0 }),
252
+ offsetWidth: 1000,
253
+ offsetHeight: 800,
254
+ style: {},
255
+ },
256
+ };
257
+
258
+ component = new ArgentGridComponent(mockCdr as any, mockElementRef as any);
259
+ component.columnDefs = [{ field: 'dept', filter: 'set' }];
260
+ component.rowData = [{ dept: 'Engineering' }, { dept: 'Sales' }, { dept: 'Marketing' }];
261
+ component.ngOnInit();
262
+ });
263
+
264
+ it('should set setFilterSelectedValues to null when no existing filter', () => {
265
+ const col = component.getApi().getAllColumns()[0];
266
+ component.openSetFilter(null, col, { x: 0, y: 0 });
267
+ expect(component.setFilterSelectedValues).toBeNull();
268
+ });
269
+
270
+ it('should restore previously selected values from the filter model', () => {
271
+ component.getApi().setFilterModel({
272
+ dept: { filterType: 'set', values: ['Engineering', 'Sales'] },
273
+ });
274
+
275
+ const col = component.getApi().getAllColumns()[0];
276
+ component.openSetFilter(null, col, { x: 0, y: 0 });
277
+
278
+ expect(component.setFilterSelectedValues).toEqual(['Engineering', 'Sales']);
279
+ });
280
+
281
+ it('should set setFilterSelectedValues to null for non-set filter models', () => {
282
+ // Manually set a text filter (wrong type) under the dept colId
283
+ component.getApi().setFilterModel({
284
+ dept: { filterType: 'text', type: 'contains', filter: 'Eng' },
285
+ });
286
+
287
+ const col = component.getApi().getAllColumns()[0];
288
+ component.openSetFilter(null, col, { x: 0, y: 0 });
289
+
290
+ expect(component.setFilterSelectedValues).toBeNull();
291
+ });
292
+
293
+ it('should populate setFilterValues with all unique values for the column', () => {
294
+ const col = component.getApi().getAllColumns()[0];
295
+ component.openSetFilter(null, col, { x: 0, y: 0 });
296
+
297
+ expect(component.setFilterValues).toContain('Engineering');
298
+ expect(component.setFilterValues).toContain('Sales');
299
+ expect(component.setFilterValues).toContain('Marketing');
300
+ });
301
+ });
@@ -1,9 +1,9 @@
1
1
  import { provideExperimentalZonelessChangeDetection } from '@angular/core';
2
2
  import { ComponentFixture, TestBed } from '@angular/core/testing';
3
- import { describe, it, expect, beforeEach } from 'vitest';
4
- import { ArgentGridComponent } from './argent-grid.component';
3
+ import { beforeEach, describe, expect, it } from 'vitest';
5
4
  import { ArgentGridModule } from '../argent-grid.module';
6
5
  import { GridService } from '../services/grid.service';
6
+ import { ArgentGridComponent } from './argent-grid.component';
7
7
 
8
8
  interface TestData {
9
9
  id: number;
@@ -0,0 +1,191 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { SetFilterComponent } from './set-filter.component';
3
+
4
+ describe('SetFilterComponent', () => {
5
+ let component: SetFilterComponent<string>;
6
+
7
+ const values = ['Engineering', 'Sales', 'Marketing', 'Engineering', 'Sales'];
8
+
9
+ beforeEach(() => {
10
+ component = new SetFilterComponent();
11
+ });
12
+
13
+ // ── initialSelectedValues ──────────────────────────────────────────────────
14
+
15
+ describe('initialSelectedValues input', () => {
16
+ it('should check all items when initialSelectedValues is null (default)', () => {
17
+ component.values = values;
18
+ component.initialSelectedValues = null;
19
+ component.ngOnInit();
20
+
21
+ expect(component.allValues.every((v) => v.selected)).toBe(true);
22
+ });
23
+
24
+ it('should pre-check only the provided values when initialSelectedValues is set', () => {
25
+ component.values = values;
26
+ component.initialSelectedValues = ['Engineering'];
27
+ component.ngOnInit();
28
+
29
+ const eng = component.allValues.find((v) => v.value === 'Engineering');
30
+ const sales = component.allValues.find((v) => v.value === 'Sales');
31
+ const mkt = component.allValues.find((v) => v.value === 'Marketing');
32
+
33
+ expect(eng?.selected).toBe(true);
34
+ expect(sales?.selected).toBe(false);
35
+ expect(mkt?.selected).toBe(false);
36
+ });
37
+
38
+ it('should populate selectedValues matching initialSelectedValues', () => {
39
+ component.values = values;
40
+ component.initialSelectedValues = ['Sales', 'Marketing'];
41
+ component.ngOnInit();
42
+
43
+ expect(component.selectedValues).toEqual(expect.arrayContaining(['Sales', 'Marketing']));
44
+ expect(component.selectedValues).not.toContain('Engineering');
45
+ });
46
+
47
+ it('should handle empty initialSelectedValues (none checked)', () => {
48
+ component.values = values;
49
+ component.initialSelectedValues = [];
50
+ component.ngOnInit();
51
+
52
+ expect(component.allValues.every((v) => !v.selected)).toBe(true);
53
+ expect(component.selectedValues).toEqual([]);
54
+ });
55
+
56
+ it('should handle initialSelectedValues with values not present in list gracefully', () => {
57
+ component.values = values;
58
+ component.initialSelectedValues = ['NonExistent'];
59
+ component.ngOnInit();
60
+
61
+ expect(component.allValues.every((v) => !v.selected)).toBe(true);
62
+ });
63
+ });
64
+
65
+ // ── resetFilter ────────────────────────────────────────────────────────────
66
+
67
+ describe('resetFilter', () => {
68
+ it('should re-select all items after reset, regardless of initialSelectedValues', () => {
69
+ component.values = values;
70
+ component.initialSelectedValues = ['Engineering'];
71
+ component.ngOnInit();
72
+
73
+ // Confirm only Engineering is selected before reset
74
+ expect(component.allValues.filter((v) => v.selected).length).toBe(1);
75
+
76
+ const emit = vi.fn();
77
+ component.filterChanged.emit = emit;
78
+ component.resetFilter();
79
+
80
+ // After reset, all items should be selected
81
+ expect(component.allValues.every((v) => v.selected)).toBe(true);
82
+ expect(emit).toHaveBeenCalledWith(
83
+ expect.arrayContaining(['Engineering', 'Sales', 'Marketing'])
84
+ );
85
+ });
86
+
87
+ it('should clear search text on reset', () => {
88
+ component.values = values;
89
+ component.ngOnInit();
90
+ component.searchText = 'Eng';
91
+
92
+ component.resetFilter();
93
+
94
+ expect(component.searchText).toBe('');
95
+ });
96
+ });
97
+
98
+ // ── selectAll / clearAll ───────────────────────────────────────────────────
99
+
100
+ describe('selectAll', () => {
101
+ it('should mark all items selected', () => {
102
+ component.values = values;
103
+ component.initialSelectedValues = ['Engineering'];
104
+ component.ngOnInit();
105
+
106
+ component.selectAll();
107
+
108
+ expect(component.allValues.every((v) => v.selected)).toBe(true);
109
+ });
110
+ });
111
+
112
+ describe('clearAll', () => {
113
+ it('should uncheck all items', () => {
114
+ component.values = values;
115
+ component.ngOnInit();
116
+
117
+ component.clearAll();
118
+
119
+ expect(component.allValues.every((v) => !v.selected)).toBe(true);
120
+ expect(component.selectedValues).toEqual([]);
121
+ });
122
+ });
123
+
124
+ // ── applyFilter ────────────────────────────────────────────────────────────
125
+
126
+ describe('applyFilter', () => {
127
+ it('should emit only selected values', () => {
128
+ component.values = values;
129
+ component.initialSelectedValues = ['Engineering'];
130
+ component.ngOnInit();
131
+
132
+ const emit = vi.fn();
133
+ component.filterChanged.emit = emit;
134
+ component.applyFilter();
135
+
136
+ expect(emit).toHaveBeenCalledWith(['Engineering']);
137
+ });
138
+ });
139
+
140
+ // ── filteredValues (search) ────────────────────────────────────────────────
141
+
142
+ describe('filteredValues', () => {
143
+ beforeEach(() => {
144
+ component.values = values;
145
+ component.ngOnInit();
146
+ });
147
+
148
+ it('should return all values when search is empty', () => {
149
+ expect(component.filteredValues.length).toBe(3); // unique: Eng, Sales, Mkt
150
+ });
151
+
152
+ it('should filter by search text (case-insensitive)', () => {
153
+ component.searchText = 'eng';
154
+ expect(component.filteredValues.length).toBe(1);
155
+ expect(component.filteredValues[0].value).toBe('Engineering');
156
+ });
157
+
158
+ it('should return empty when search matches nothing', () => {
159
+ component.searchText = 'zzz';
160
+ expect(component.filteredValues.length).toBe(0);
161
+ });
162
+ });
163
+
164
+ // ── value counts ──────────────────────────────────────────────────────────
165
+
166
+ describe('value counts', () => {
167
+ it('should count occurrences correctly', () => {
168
+ component.values = values; // Eng x2, Sales x2, Mkt x1
169
+ component.ngOnInit();
170
+
171
+ const eng = component.allValues.find((v) => v.value === 'Engineering');
172
+ const mkt = component.allValues.find((v) => v.value === 'Marketing');
173
+
174
+ expect(eng?.count).toBe(2);
175
+ expect(mkt?.count).toBe(1);
176
+ });
177
+ });
178
+
179
+ // ── valueFormatter ────────────────────────────────────────────────────────
180
+
181
+ describe('valueFormatter', () => {
182
+ it('should apply formatter to display values', () => {
183
+ component.values = ['eng', 'sales'];
184
+ component.valueFormatter = (v) => v.toUpperCase();
185
+ component.ngOnInit();
186
+
187
+ expect(component.allValues[0].displayValue).toBe('ENG');
188
+ expect(component.allValues[1].displayValue).toBe('SALES');
189
+ });
190
+ });
191
+ });
@@ -218,6 +218,8 @@ import {
218
218
  export class SetFilterComponent<T = any> implements OnInit {
219
219
  @Input() values: T[] = [];
220
220
  @Input() valueFormatter?: (value: T) => string;
221
+ /** When provided, only these values will be pre-checked (restores existing filter state) */
222
+ @Input() initialSelectedValues: T[] | null = null;
221
223
  @Output() filterChanged = new EventEmitter<T[]>();
222
224
 
223
225
  searchText = '';
@@ -248,14 +250,16 @@ export class SetFilterComponent<T = any> implements OnInit {
248
250
  });
249
251
 
250
252
  // Build value list with counts
253
+ // If initialSelectedValues provided, pre-check only those; otherwise check all
254
+ const preSelected = this.initialSelectedValues;
251
255
  this.allValues = Array.from(valueCounts.entries()).map(([value, count]) => ({
252
256
  value,
253
257
  displayValue: this.valueFormatter ? this.valueFormatter(value) : String(value),
254
258
  count,
255
- selected: true, // All selected by default
259
+ selected: preSelected ? preSelected.includes(value) : true,
256
260
  }));
257
261
 
258
- this.selectedValues = this.values.filter((v, i, arr) => arr.indexOf(v) === i);
262
+ this.selectedValues = this.allValues.filter((v) => v.selected).map((v) => v.value);
259
263
  }
260
264
 
261
265
  onSearchChanged(): void {
@@ -296,6 +300,7 @@ export class SetFilterComponent<T = any> implements OnInit {
296
300
 
297
301
  resetFilter(): void {
298
302
  this.searchText = '';
303
+ this.initialSelectedValues = null; // Reset to all-selected state
299
304
  this.initializeValues();
300
305
  this.filterChanged.emit(this.selectedValues);
301
306
  }