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
@@ -1,313 +1,285 @@
1
1
  <div class="argent-grid-container" [style.height]="height" [style.width]="width" (click)="onContainerClick($event)">
2
- <div class="argent-grid-main-layout">
2
+ <div class="argent-grid-main-layout" cdkDropListGroup>
3
3
  <div class="argent-grid-content-area">
4
+ <!-- Row Group Panel -->
5
+ <div class="argent-grid-row-group-panel"
6
+ *ngIf="isRowGroupPanelVisible()"
7
+ cdkDropList
8
+ id="row-group-panel"
9
+ cdkDropListOrientation="horizontal"
10
+ (cdkDropListDropped)="onRowGroupDropped($event)">
11
+ <div class="row-group-placeholder" *ngIf="getRowGroupColumns().length === 0">
12
+ Drag columns here to group
13
+ </div>
14
+ <div *ngFor="let col of getRowGroupColumns(); trackBy: trackByRowGroup" class="row-group-pill">
15
+ <span class="pill-text">{{ getHeaderName(col) }}</span>
16
+ <span class="pill-close" (click)="removeRowGroup(col)">✕</span>
17
+ </div>
18
+ </div>
19
+
4
20
  <!-- Header Layer (DOM-based for accessibility) -->
5
21
  <div class="argent-grid-header">
6
- <!-- Main Header Row -->
7
- <div class="argent-grid-header-row">
8
-
9
22
 
10
- <!-- Left Pinned Columns -->
23
+ <div class="argent-grid-header-main">
24
+ <!-- Left Pinned Header -->
11
25
  <div class="argent-grid-header-pinned-left-container"
26
+ *ngIf="getLeftPinnedColumns().length > 0"
27
+ [style.display]="'grid'"
28
+ [style.grid-template-rows]="'repeat(' + gridApi.getHeaderDepth() + ', ' + effectiveHeaderHeight + 'px)'"
29
+ [style.grid-template-columns]="getGridTemplateColumns('left')"
12
30
  cdkDropList
13
- id="left-pinned"
14
- [cdkDropListConnectedTo]="['scrollable', 'right-pinned']"
31
+ id="left-pinned-list"
15
32
  cdkDropListOrientation="horizontal"
16
33
  (cdkDropListDropped)="onColumnDropped($event, 'left')">
17
- <div
18
- *ngFor="let col of getLeftPinnedColumns(); trackBy: trackByColumn"
19
- class="argent-grid-header-cell argent-grid-header-cell-pinned-left"
20
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
21
- [style.width.px]="getColumnWidth(col)"
22
- [class.sortable]="isSortable(col)"
23
- (click)="onHeaderClick(col)"
24
- cdkDrag
25
- [cdkDragDisabled]="col.colId === 'ag-Grid-SelectionColumn'"
26
- [cdkDragData]="col">
27
- <div class="argent-grid-header-content" cdkDragHandle>
28
- <div *ngIf="hasHeaderCheckbox(col)" class="argent-grid-header-checkbox">
29
- <input type="checkbox"
30
- [checked]="isAllSelected"
31
- [indeterminate]="isIndeterminateSelection"
32
- (click)="$event.stopPropagation()"
33
- (change)="onSelectionHeaderChange($event)" />
34
- </div>
35
- <span class="header-text">{{ getHeaderName(col) }}</span>
36
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
37
- </div>
38
- <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, col)" *ngIf="hasHeaderMenu(col)">
39
- &#8942;
40
- </div>
41
- <div class="argent-grid-header-resize-handle"
42
- *ngIf="isResizable(col)"
43
- [class.resizing]="isResizing && resizeColumn === col"
44
- (mousedown)="onResizeMouseDown($event, col)">
45
- </div>
46
- </div>
47
- </div>
48
-
49
- <!-- Scrollable Columns -->
50
- <div class="argent-grid-header-scrollable"
51
- #headerScrollable
52
- cdkDropList
53
- id="scrollable"
54
- [cdkDropListConnectedTo]="['left-pinned', 'right-pinned']"
55
- cdkDropListOrientation="horizontal"
56
- (cdkDropListDropped)="onColumnDropped($event, 'none')">
57
- <div class="argent-grid-header-row">
34
+
35
+ <ng-container *ngFor="let entry of getSectionHeaderItems('left'); trackBy: trackByHeaderItem">
58
36
  <div
59
- *ngFor="let col of getNonPinnedColumns(); trackBy: trackByColumn"
60
- class="argent-grid-header-cell"
61
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
62
- [style.width.px]="getColumnWidth(col)"
63
- [class.sortable]="isSortable(col)"
64
- (click)="onHeaderClick(col)"
37
+ *ngIf="isColumnVisible(entry.item)"
38
+ class="argent-grid-header-cell argent-grid-header-cell-pinned-left"
39
+ [class.argent-grid-header-group-cell]="isColumnGroup(entry.item)"
40
+ [class.center-content]="!isColumnGroup(entry.item) && entry.item.colId === 'ag-Grid-SelectionColumn'"
41
+ [style.grid-column]="getLeftPinnedColIndex(entry.item) + ' / span ' + getItemColSpan(entry.item)"
42
+ [style.grid-row]="(entry.rowIndex + 1) + ' / span ' + getItemRowSpan(entry.item, entry.rowIndex)"
43
+ [style.width.px]="getItemWidth(entry.item)"
44
+ [style.height.px]="getItemRowSpan(entry.item, entry.rowIndex) * effectiveHeaderHeight"
45
+ [class.sortable]="!isColumnGroup(entry.item) && isSortable(entry.item)"
46
+ (click)="!isColumnGroup(entry.item) && onHeaderClick(entry.item)"
65
47
  cdkDrag
66
- [cdkDragDisabled]="col.colId === 'ag-Grid-SelectionColumn'"
67
- [cdkDragData]="col">
48
+ [cdkDragDisabled]="isColumnGroup(entry.item) || entry.item.colId === 'ag-Grid-SelectionColumn'"
49
+ [cdkDragData]="entry.item">
68
50
  <div class="argent-grid-header-content" cdkDragHandle>
69
- <div *ngIf="hasHeaderCheckbox(col)" class="argent-grid-header-checkbox">
51
+ <div *ngIf="!isColumnGroup(entry.item) && hasHeaderCheckbox(entry.item)" class="argent-grid-header-checkbox">
70
52
  <input type="checkbox"
71
53
  [checked]="isAllSelected"
72
54
  [indeterminate]="isIndeterminateSelection"
73
55
  (click)="$event.stopPropagation()"
74
56
  (change)="onSelectionHeaderChange($event)" />
75
57
  </div>
76
- <span class="header-text">{{ getHeaderName(col) }}</span>
77
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
58
+ <span class="header-text">{{ getHeaderName(entry.item) }}</span>
59
+ <span class="group-toggle" *ngIf="isColumnGroup(entry.item) && hasExpansionToggle(entry.item)" (click)="toggleGroup(entry.item, $event)">
60
+ {{ entry.item.expanded ? '−' : '+' }}
61
+ </span>
62
+ <span class="sort-indicator" *ngIf="!isColumnGroup(entry.item) && getSortIndicator(entry.item)">{{ getSortIndicator(entry.item) }}</span>
63
+ </div>
64
+ <div class="argent-grid-header-filter-icon"
65
+ [class.active]="isColumnFiltered(entry.item)"
66
+ (click)="onHeaderFilterClick($event, entry.item)"
67
+ *ngIf="!isColumnGroup(entry.item) && hasHeaderFilterButton(entry.item)"
68
+ title="Filter">
69
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M0.5 1.5h9L6 6V9H4V6Z"/></svg>
78
70
  </div>
79
- <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, col)" *ngIf="hasHeaderMenu(col)">
71
+ <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, entry.item)" *ngIf="!isColumnGroup(entry.item) && hasHeaderMenu(entry.item)">
80
72
  &#8942;
81
73
  </div>
82
74
  <div class="argent-grid-header-resize-handle"
83
- *ngIf="isResizable(col)"
84
- [class.resizing]="isResizing && resizeColumn === col"
85
- (mousedown)="onResizeMouseDown($event, col)">
75
+ *ngIf="isResizable(entry.item)"
76
+ [class.resizing]="isResizing && resizeItem === entry.item"
77
+ (mousedown)="onResizeMouseDown($event, entry.item)">
86
78
  </div>
87
79
  </div>
80
+ </ng-container>
81
+ </div>
82
+
83
+ <!-- Scrollable Header -->
84
+ <div class="argent-grid-header-scrollable" #headerScrollable>
85
+ <div class="argent-grid-header-scrollable-content"
86
+ [style.display]="'grid'"
87
+ [style.grid-template-rows]="'repeat(' + gridApi.getHeaderDepth() + ', ' + effectiveHeaderHeight + 'px)'"
88
+ [style.grid-template-columns]="getGridTemplateColumns('none')"
89
+ [style.width.px]="getScrollableHeaderWidth()"
90
+ cdkDropList
91
+ id="scrollable-list"
92
+ cdkDropListOrientation="horizontal"
93
+ (cdkDropListDropped)="onColumnDropped($event, 'none')">
94
+
95
+ <ng-container *ngFor="let entry of getSectionHeaderItems('none'); trackBy: trackByHeaderItem">
96
+ <div
97
+ *ngIf="isColumnVisible(entry.item)"
98
+ class="argent-grid-header-cell"
99
+ [class.argent-grid-header-group-cell]="isColumnGroup(entry.item)"
100
+ [style.grid-column]="getScrollableColIndex(entry.item) + ' / span ' + getItemColSpan(entry.item)"
101
+ [style.grid-row]="(entry.rowIndex + 1) + ' / span ' + getItemRowSpan(entry.item, entry.rowIndex)"
102
+ [style.width.px]="getItemWidth(entry.item)"
103
+ [style.height.px]="getItemRowSpan(entry.item, entry.rowIndex) * effectiveHeaderHeight"
104
+ [class.sortable]="!isColumnGroup(entry.item) && isSortable(entry.item)"
105
+ (click)="!isColumnGroup(entry.item) && onHeaderClick(entry.item)"
106
+ cdkDrag
107
+ [cdkDragDisabled]="isColumnGroup(entry.item) || entry.item.colId === 'ag-Grid-SelectionColumn'"
108
+ [cdkDragData]="entry.item">
109
+ <div class="argent-grid-header-content" cdkDragHandle>
110
+ <span class="header-text">{{ getHeaderName(entry.item) }}</span>
111
+ <span class="group-toggle" *ngIf="isColumnGroup(entry.item) && hasExpansionToggle(entry.item)" (click)="toggleGroup(entry.item, $event)">
112
+ {{ entry.item.expanded ? '−' : '+' }}
113
+ </span>
114
+ <span class="sort-indicator" *ngIf="!isColumnGroup(entry.item) && getSortIndicator(entry.item)">{{ getSortIndicator(entry.item) }}</span>
115
+ </div>
116
+ <div class="argent-grid-header-filter-icon"
117
+ [class.active]="isColumnFiltered(entry.item)"
118
+ (click)="onHeaderFilterClick($event, entry.item)"
119
+ *ngIf="!isColumnGroup(entry.item) && hasHeaderFilterButton(entry.item)"
120
+ title="Filter">
121
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M0.5 1.5h9L6 6V9H4V6Z"/></svg>
122
+ </div>
123
+ <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, entry.item)" *ngIf="!isColumnGroup(entry.item) && hasHeaderMenu(entry.item)">
124
+ &#8942;
125
+ </div>
126
+ <div class="argent-grid-header-resize-handle"
127
+ *ngIf="isResizable(entry.item)"
128
+ [class.resizing]="isResizing && resizeItem === entry.item"
129
+ (mousedown)="onResizeMouseDown($event, entry.item)">
130
+ </div>
131
+ </div>
132
+ </ng-container>
88
133
  </div>
89
134
  </div>
90
135
 
91
- <!-- Right Pinned Columns -->
136
+ <!-- Right Pinned Header -->
92
137
  <div class="argent-grid-header-pinned-right-container"
138
+ *ngIf="getRightPinnedColumns().length > 0"
139
+ [style.display]="'grid'"
140
+ [style.grid-template-rows]="'repeat(' + gridApi.getHeaderDepth() + ', ' + effectiveHeaderHeight + 'px)'"
141
+ [style.grid-template-columns]="getGridTemplateColumns('right')"
93
142
  cdkDropList
94
- id="right-pinned"
95
- [cdkDropListConnectedTo]="['left-pinned', 'scrollable']"
143
+ id="right-pinned-list"
96
144
  cdkDropListOrientation="horizontal"
97
145
  (cdkDropListDropped)="onColumnDropped($event, 'right')">
98
- <div
99
- *ngFor="let col of getRightPinnedColumns(); trackBy: trackByColumn"
100
- class="argent-grid-header-cell argent-grid-header-cell-pinned-right"
101
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
102
- [style.width.px]="getColumnWidth(col)"
103
- [class.sortable]="isSortable(col)"
104
- (click)="onHeaderClick(col)"
105
- cdkDrag
106
- [cdkDragDisabled]="col.colId === 'ag-Grid-SelectionColumn'"
107
- [cdkDragData]="col">
108
- <div class="argent-grid-header-content" cdkDragHandle>
109
- <div *ngIf="hasHeaderCheckbox(col)" class="argent-grid-header-checkbox">
110
- <input type="checkbox"
111
- [checked]="isAllSelected"
112
- [indeterminate]="isIndeterminateSelection"
113
- (click)="$event.stopPropagation()"
114
- (change)="onSelectionHeaderChange($event)" />
146
+
147
+ <ng-container *ngFor="let entry of getSectionHeaderItems('right'); trackBy: trackByHeaderItem">
148
+ <div
149
+ *ngIf="isColumnVisible(entry.item)"
150
+ class="argent-grid-header-cell argent-grid-header-cell-pinned-right"
151
+ [class.argent-grid-header-group-cell]="isColumnGroup(entry.item)"
152
+ [style.grid-column]="getRightPinnedColIndex(entry.item) + ' / span ' + getItemColSpan(entry.item)"
153
+ [style.grid-row]="(entry.rowIndex + 1) + ' / span ' + getItemRowSpan(entry.item, entry.rowIndex)"
154
+ [style.width.px]="getItemWidth(entry.item)"
155
+ [style.height.px]="getItemRowSpan(entry.item, entry.rowIndex) * effectiveHeaderHeight"
156
+ [class.sortable]="!isColumnGroup(entry.item) && isSortable(entry.item)"
157
+ (click)="!isColumnGroup(entry.item) && onHeaderClick(entry.item)"
158
+ cdkDrag
159
+ [cdkDragDisabled]="isColumnGroup(entry.item) || entry.item.colId === 'ag-Grid-SelectionColumn'"
160
+ [cdkDragData]="entry.item">
161
+ <div class="argent-grid-header-content" cdkDragHandle>
162
+ <span class="header-text">{{ getHeaderName(entry.item) }}</span>
163
+ <span class="group-toggle" *ngIf="isColumnGroup(entry.item) && hasExpansionToggle(entry.item)" (click)="toggleGroup(entry.item, $event)">
164
+ {{ entry.item.expanded ? '−' : '+' }}
165
+ </span>
166
+ <span class="sort-indicator" *ngIf="!isColumnGroup(entry.item) && getSortIndicator(entry.item)">{{ getSortIndicator(entry.item) }}</span>
167
+ </div>
168
+ <div class="argent-grid-header-filter-icon"
169
+ [class.active]="isColumnFiltered(entry.item)"
170
+ (click)="onHeaderFilterClick($event, entry.item)"
171
+ *ngIf="!isColumnGroup(entry.item) && hasHeaderFilterButton(entry.item)"
172
+ title="Filter">
173
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M0.5 1.5h9L6 6V9H4V6Z"/></svg>
174
+ </div>
175
+ <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, entry.item)" *ngIf="!isColumnGroup(entry.item) && hasHeaderMenu(entry.item)">
176
+ &#8942;
177
+ </div>
178
+ <div class="argent-grid-header-resize-handle"
179
+ *ngIf="isResizable(entry.item)"
180
+ [class.resizing]="isResizing && resizeItem === entry.item"
181
+ (mousedown)="onResizeMouseDown($event, entry.item)">
115
182
  </div>
116
- <span class="header-text">{{ getHeaderName(col) }}</span>
117
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
118
- </div>
119
- <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, col)" *ngIf="hasHeaderMenu(col)">
120
- &#8942;
121
- </div>
122
- <div class="argent-grid-header-resize-handle"
123
- *ngIf="isResizable(col)"
124
- [class.resizing]="isResizing && resizeColumn === col"
125
- (mousedown)="onResizeMouseDown($event, col)">
126
183
  </div>
127
- </div>
184
+ </ng-container>
185
+ </div>
186
+
187
+ <!-- Vertical Scrollbar Spacer -->
188
+ <div class="argent-grid-header-scrollbar-spacer"
189
+ *ngIf="scrollbarWidth > 0"
190
+ [style.width.px]="scrollbarWidth">
128
191
  </div>
129
192
  </div>
130
193
 
131
194
  <!-- Floating Filter Row -->
132
- <div class="argent-grid-header-row floating-filter-row" *ngIf="hasFloatingFilters()">
133
-
134
-
195
+ <div class="floating-filter-row" *ngIf="hasFloatingFilters()">
135
196
  <!-- Left Pinned Filters -->
136
- <div class="argent-grid-header-pinned-left-container">
137
- <div
138
- *ngFor="let col of getLeftPinnedColumns(); trackBy: trackByColumn"
139
- class="argent-grid-header-cell argent-grid-header-cell-pinned-left argent-grid-floating-filter-cell"
140
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
141
- [style.width.px]="getColumnWidth(col)">
142
- <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
143
- <!-- Set Filter Button -->
144
- <button *ngIf="isSetFilter(col)"
145
- type="button"
146
- class="floating-filter-btn"
147
- (click)="openSetFilter($event, col)"
148
- [class.active]="hasSetFilterValue(col)">
149
- 🔽 {{ getSetFilterCount(col) }}
150
- </button>
151
- <!-- Text/Number/Date Filter Input -->
152
- <ng-container *ngIf="!isSetFilter(col)">
197
+ <div class="argent-grid-header-pinned-left-container" *ngIf="getLeftPinnedColumns().length > 0">
198
+ <ng-container *ngFor="let col of getLeftPinnedColumns()">
199
+ <div *ngIf="isColumnVisible(col)"
200
+ class="argent-grid-floating-filter-cell"
201
+ [style.width.px]="getItemWidth(col)">
202
+ <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
153
203
  <input #filterInput
154
204
  class="floating-filter-input"
155
205
  [type]="getFilterInputType(col)"
156
206
  [value]="getFloatingFilterValue(col)"
157
207
  (input)="onFloatingFilterInput($event, col)"
158
208
  [placeholder]="'Filter...'" />
159
- <span class="floating-filter-clear"
209
+ <span class="floating-filter-clear"
160
210
  *ngIf="hasFilterValue(col, filterInput)"
161
211
  (click)="clearFloatingFilter(col, filterInput)">✕</span>
162
- </ng-container>
212
+ </div>
163
213
  </div>
164
- </div>
214
+ </ng-container>
165
215
  </div>
166
-
167
216
  <!-- Scrollable Filters -->
168
217
  <div class="argent-grid-header-scrollable" #headerScrollableFilter>
169
- <div class="argent-grid-header-row">
170
- <div
171
- *ngFor="let col of getNonPinnedColumns(); trackBy: trackByColumn"
172
- class="argent-grid-header-cell argent-grid-floating-filter-cell"
173
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
174
- [style.width.px]="getColumnWidth(col)">
175
- <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
176
- <!-- Set Filter Button -->
177
- <button *ngIf="isSetFilter(col)"
178
- type="button"
179
- class="floating-filter-btn"
180
- (click)="openSetFilter($event, col)"
181
- [class.active]="hasSetFilterValue(col)">
182
- 🔽 {{ getSetFilterCount(col) }}
183
- </button>
184
- <!-- Text/Number/Date Filter Input -->
185
- <ng-container *ngIf="!isSetFilter(col)">
186
- <input #filterInput
187
- class="floating-filter-input"
188
- [type]="getFilterInputType(col)"
189
- [value]="getFloatingFilterValue(col)"
190
- (input)="onFloatingFilterInput($event, col)"
191
- [placeholder]="'Filter...'" />
192
- <span class="floating-filter-clear"
193
- *ngIf="hasFilterValue(col, filterInput)"
194
- (click)="clearFloatingFilter(col, filterInput)">✕</span>
195
- </ng-container>
218
+ <div class="argent-grid-header-row" [style.width.px]="getScrollableHeaderWidth()">
219
+ <ng-container *ngFor="let col of getNonPinnedColumns()">
220
+ <div *ngIf="isColumnVisible(col)"
221
+ class="argent-grid-floating-filter-cell"
222
+ [style.width.px]="getItemWidth(col)">
223
+ <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
224
+ <button class="floating-filter-btn"
225
+ *ngIf="isSetFilter(col)"
226
+ [class.active]="hasSetFilterValue(col)"
227
+ (click)="openSetFilter($event, col)">
228
+ <span class="filter-label">{{ getFloatingFilterValue(col) || 'All' }}</span>
229
+ <span class="filter-count" *ngIf="hasSetFilterValue(col)">🔽 {{ getSetFilterCount(col) }}</span>
230
+ </button>
231
+ <ng-container *ngIf="!isSetFilter(col)">
232
+ <input #filterInput
233
+ class="floating-filter-input"
234
+ [type]="getFilterInputType(col)"
235
+ [value]="getFloatingFilterValue(col)"
236
+ (input)="onFloatingFilterInput($event, col)"
237
+ [placeholder]="'Filter...'" />
238
+ <span class="floating-filter-clear"
239
+ *ngIf="hasFilterValue(col, filterInput)"
240
+ (click)="clearFloatingFilter(col, filterInput)">✕</span>
241
+ </ng-container>
242
+ </div>
196
243
  </div>
197
- </div>
244
+ </ng-container>
198
245
  </div>
199
246
  </div>
200
-
201
247
  <!-- Right Pinned Filters -->
202
- <div class="argent-grid-header-pinned-right-container">
203
- <div
204
- *ngFor="let col of getRightPinnedColumns(); trackBy: trackByColumn"
205
- class="argent-grid-header-cell argent-grid-header-cell-pinned-right argent-grid-floating-filter-cell"
206
- [class.center-content]="col.colId === 'ag-Grid-SelectionColumn'"
207
- [style.width.px]="getColumnWidth(col)">
208
- <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
209
- <!-- Set Filter Button -->
210
- <button *ngIf="isSetFilter(col)"
211
- type="button"
212
- class="floating-filter-btn"
213
- (click)="openSetFilter($event, col)"
214
- [class.active]="hasSetFilterValue(col)">
215
- 🔽 {{ getSetFilterCount(col) }}
216
- </button>
217
- <!-- Text/Number/Date Filter Input -->
218
- <ng-container *ngIf="!isSetFilter(col)">
248
+ <div class="argent-grid-header-pinned-right-container" *ngIf="getRightPinnedColumns().length > 0">
249
+ <ng-container *ngFor="let col of getRightPinnedColumns()">
250
+ <div *ngIf="isColumnVisible(col)"
251
+ class="argent-grid-floating-filter-cell"
252
+ [style.width.px]="getItemWidth(col)">
253
+ <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
219
254
  <input #filterInput
220
255
  class="floating-filter-input"
221
256
  [type]="getFilterInputType(col)"
222
257
  [value]="getFloatingFilterValue(col)"
223
258
  (input)="onFloatingFilterInput($event, col)"
224
259
  [placeholder]="'Filter...'" />
225
- <span class="floating-filter-clear"
260
+ <span class="floating-filter-clear"
226
261
  *ngIf="hasFilterValue(col, filterInput)"
227
262
  (click)="clearFloatingFilter(col, filterInput)">✕</span>
228
- </ng-container>
263
+ </div>
229
264
  </div>
230
- </div>
231
- </div>
232
- </div>
233
- </div>
234
-
235
- <!-- Set Filter Popup -->
236
- <div *ngIf="activeSetFilter"
237
- class="set-filter-popup"
238
- [style.left.px]="setFilterPosition.x"
239
- [style.top.px]="setFilterPosition.y"
240
- (clickOutside)="closeSetFilter()"
241
- clickOutside>
242
- <argent-set-filter
243
- [values]="setFilterValues"
244
- [valueFormatter]="setFilterValueFormatter"
245
- (filterChanged)="onSetFilterChanged($event)">
246
- </argent-set-filter>
247
- </div>
248
-
249
- <!-- Text/Number Filter Popup -->
250
- <div *ngIf="activeFilterPopup"
251
- class="filter-popup"
252
- [style.left.px]="filterPopupPosition.x"
253
- [style.top.px]="filterPopupPosition.y"
254
- (clickOutside)="closeFilterPopup()"
255
- clickOutside>
256
- <div class="filter-popup-header">
257
- <span>Filter {{ getHeaderName(activeFilterPopupColumn!) }}</span>
258
- <span class="filter-popup-close" (click)="closeFilterPopup()">✕</span>
259
- </div>
260
- <div class="filter-popup-body">
261
- <!-- Operator Select -->
262
- <div class="filter-popup-row">
263
- <select class="filter-popup-select"
264
- [value]="activeFilterOperator"
265
- (change)="onFilterPopupOperatorChange($any($event.target).value)">
266
- <ng-container *ngIf="activeFilterPopupType === 'text'">
267
- <option *ngFor="let op of textFilterOperators" [value]="op.value">{{ op.label }}</option>
268
- </ng-container>
269
- <ng-container *ngIf="activeFilterPopupType === 'number'">
270
- <option *ngFor="let op of numberFilterOperators" [value]="op.value">{{ op.label }}</option>
271
- </ng-container>
272
- </select>
265
+ </ng-container>
273
266
  </div>
274
267
 
275
- <!-- Value Input(s) -->
276
- <ng-container *ngIf="activeFilterOperator !== 'blank' && activeFilterOperator !== 'notBlank'">
277
- <div class="filter-popup-row">
278
- <input #filterPopupInput
279
- [type]="activeFilterPopupType === 'number' ? 'number' : 'text'"
280
- class="filter-popup-input"
281
- [value]="filterValue1"
282
- (input)="onFilterPopupInput($event)"
283
- (keydown.enter)="closeFilterPopup()"
284
- [placeholder]="activeFilterOperator === 'inRange' ? 'From...' : 'Filter value...'"
285
- autofocus />
286
- </div>
287
-
288
- <div class="filter-popup-row" *ngIf="activeFilterOperator === 'inRange'">
289
- <input type="number"
290
- class="filter-popup-input"
291
- [value]="filterValue2"
292
- (input)="onFilterPopupInput($event, true)"
293
- (keydown.enter)="closeFilterPopup()"
294
- [placeholder]="'To...'" />
295
- </div>
296
- </ng-container>
297
-
298
- <div class="filter-popup-footer">
299
- <button class="filter-popup-btn clear-btn" (click)="clearColumnFilter(activeFilterPopupColumn!)">Clear</button>
300
- <button class="filter-popup-btn apply-btn" (click)="closeFilterPopup()">Apply</button>
268
+ <!-- Vertical Scrollbar Spacer -->
269
+ <div class="argent-grid-header-scrollbar-spacer"
270
+ *ngIf="scrollbarWidth > 0"
271
+ [style.width.px]="scrollbarWidth">
301
272
  </div>
302
273
  </div>
303
274
  </div>
304
275
 
305
- <!-- Canvas Layer for Data Viewport with virtual scrolling -->
276
+ <!-- Canvas Layer for Data Viewport -->
306
277
  <div class="argent-grid-viewport" #viewport>
307
- <!-- Spacer to create scrollbars for virtual scrolling -->
308
278
  <div class="argent-grid-scroll-spacer" [style.height.px]="totalHeight" [style.width.px]="totalWidth"></div>
309
-
310
- <canvas #gridCanvas class="argent-grid-canvas" (contextmenu)="onCanvasContextMenu($event)"></canvas>
279
+ <canvas #gridCanvas class="argent-grid-canvas"
280
+ (contextmenu)="onCanvasContextMenu($event)"
281
+ (mousemove)="onCanvasMouseMove($event)"
282
+ (mouseleave)="onCanvasMouseLeave()"></canvas>
311
283
 
312
284
  <!-- Cell Editor Overlay -->
313
285
  <div class="argent-grid-cell-editor"
@@ -329,48 +301,29 @@
329
301
  </div>
330
302
  </div>
331
303
 
332
- <!-- Side Bar / Tool Panels -->
304
+ <!-- Side Bar -->
333
305
  <div class="argent-grid-side-bar" *ngIf="sideBarVisible" [class.has-active-panel]="!!activeToolPanel">
334
306
  <div class="side-bar-buttons">
335
307
  <div class="side-bar-button" [class.active]="activeToolPanel === 'columns'" (click)="toggleToolPanel('columns')">
336
308
  Columns
337
309
  </div>
338
- <div class="side-bar-button" [class.active]="activeToolPanel === 'filters'" (click)="toggleToolPanel('filters')">
310
+ <!-- Hide Filters tool panel for now as it is not functional yet -->
311
+ <!-- <div class="side-bar-button" [class.active]="activeToolPanel === 'filters'" (click)="toggleToolPanel('filters')">
339
312
  Filters
340
- </div>
313
+ </div> -->
341
314
  </div>
342
315
 
343
316
  <div class="tool-panel-content" *ngIf="activeToolPanel">
344
- <!-- Columns Tool Panel -->
345
317
  <div class="columns-tool-panel" *ngIf="activeToolPanel === 'columns'">
346
318
  <h3>Columns</h3>
347
- <div class="column-list"
348
- cdkDropList
349
- (cdkDropListDropped)="onSidebarColumnDropped($event)">
350
- <div *ngFor="let col of getAllColumns()"
351
- class="column-item"
352
- cdkDrag
353
- [cdkDragData]="col">
354
- <div *cdkDragPreview class="sidebar-drag-preview">
355
- <span class="column-drag-handle">⠿</span>
356
- <span>{{ getHeaderName(col) }}</span>
357
- </div>
358
- <div *cdkDragPlaceholder class="sidebar-drag-placeholder"></div>
359
-
319
+ <div class="column-list" cdkDropList (cdkDropListDropped)="onSidebarColumnDropped($event)">
320
+ <div *ngFor="let col of getAllColumns()" class="column-item" cdkDrag [cdkDragData]="col">
360
321
  <span class="column-drag-handle" cdkDragHandle>⠿</span>
361
322
  <input type="checkbox" [checked]="col.visible" (change)="toggleColumnVisibility(col)" />
362
323
  <span class="column-label">{{ getHeaderName(col) }}</span>
363
324
  </div>
364
325
  </div>
365
326
  </div>
366
-
367
- <!-- Filters Tool Panel -->
368
- <div class="filters-tool-panel" *ngIf="activeToolPanel === 'filters'">
369
- <h3>Filters</h3>
370
- <div class="filter-placeholder">
371
- Filters coming soon...
372
- </div>
373
- </div>
374
327
  </div>
375
328
  </div>
376
329
  </div>
@@ -380,63 +333,76 @@
380
333
  <ng-content select="[overlay]"></ng-content>
381
334
  </div>
382
335
 
383
- <!-- Header Menu Overlay -->
384
- <div class="argent-grid-header-menu"
385
- *ngIf="activeHeaderMenu"
386
- [style.top.px]="headerMenuPosition.y"
387
- [style.left.px]="headerMenuPosition.x"
388
- (click)="$event.stopPropagation()">
336
+ <!-- Menus and Popups -->
337
+ <div class="argent-grid-header-menu" *ngIf="activeHeaderMenu" [style.top.px]="headerMenuPosition.y" [style.left.px]="headerMenuPosition.x" (click)="$event.stopPropagation()">
389
338
  <ng-container *ngFor="let item of headerMenuItems">
390
339
  <div *ngIf="item.separator" class="menu-divider"></div>
391
- <div *ngIf="!item.separator"
392
- class="menu-item"
393
- [class.disabled]="item.disabled"
394
- [class.has-submenu]="item.subMenu && item.subMenu.length > 0"
395
- (click)="!item.disabled && item.action(); !item.subMenu && closeHeaderMenu()">
340
+ <div *ngIf="!item.separator" class="menu-item" (click)="!item.disabled && item.action(); !item.subMenu && closeHeaderMenu()">
396
341
  <span class="menu-icon" *ngIf="item.icon">{{ item.icon }}</span>
397
342
  <span class="menu-text">{{ item.name }}</span>
398
- <span class="menu-arrow" *ngIf="item.subMenu && item.subMenu.length > 0">▶</span>
399
-
400
- <!-- Sub-menu -->
401
- <div class="argent-grid-header-menu sub-menu" *ngIf="item.subMenu && item.subMenu.length > 0">
402
- <div *ngFor="let subItem of item.subMenu"
403
- class="menu-item"
404
- (click)="subItem.action(); closeHeaderMenu(); $event.stopPropagation()">
405
- <span class="menu-icon" *ngIf="subItem.icon">{{ subItem.icon }}</span>
406
- <span class="menu-text">{{ subItem.name }}</span>
407
- </div>
408
- </div>
409
343
  </div>
410
344
  </ng-container>
411
345
  </div>
412
346
 
413
- <!-- Context Menu Overlay -->
414
- <div class="argent-grid-context-menu"
415
- *ngIf="activeContextMenu"
416
- [style.top.px]="contextMenuPosition.y"
417
- [style.left.px]="contextMenuPosition.x"
418
- (click)="$event.stopPropagation()">
347
+ <!-- Set Filter Popup -->
348
+ <div *ngIf="activeSetFilter" class="set-filter-popup" [style.left.px]="setFilterPosition.x" [style.top.px]="setFilterPosition.y" (clickOutside)="closeSetFilter()" clickOutside>
349
+ <argent-set-filter [values]="setFilterValues" [initialSelectedValues]="setFilterSelectedValues" [valueFormatter]="setFilterValueFormatter" (filterChanged)="onSetFilterChanged($event)"></argent-set-filter>
350
+ </div>
351
+
352
+ <!-- Context Menu -->
353
+ <div class="context-menu-popup" *ngIf="activeContextMenu" [style.top.px]="contextMenuPosition.y" [style.left.px]="contextMenuPosition.x" (click)="$event.stopPropagation()">
419
354
  <ng-container *ngFor="let item of contextMenuItems">
420
355
  <div *ngIf="item.separator" class="menu-divider"></div>
421
- <div *ngIf="!item.separator"
422
- class="menu-item"
423
- [class.disabled]="item.disabled"
424
- [class.has-submenu]="item.subMenu && item.subMenu.length > 0"
425
- (click)="!item.disabled && item.action(); !item.subMenu && closeContextMenu()">
356
+ <div *ngIf="!item.separator" class="menu-item" [class.disabled]="item.disabled" [class.has-submenu]="item.subMenu"
357
+ (click)="!item.disabled && !item.subMenu && item.action(); !item.subMenu && closeContextMenu()">
426
358
  <span class="menu-icon" *ngIf="item.icon">{{ item.icon }}</span>
427
359
  <span class="menu-text">{{ item.name }}</span>
428
- <span class="menu-arrow" *ngIf="item.subMenu && item.subMenu.length > 0">▶</span>
429
-
430
- <!-- Sub-menu -->
431
- <div class="argent-grid-context-menu sub-menu" *ngIf="item.subMenu && item.subMenu.length > 0">
432
- <div *ngFor="let subItem of item.subMenu"
433
- class="menu-item"
434
- (click)="subItem.action(); closeContextMenu(); $event.stopPropagation()">
435
- <span class="menu-icon" *ngIf="subItem.icon">{{ subItem.icon }}</span>
436
- <span class="menu-text">{{ subItem.name }}</span>
360
+ <span class="menu-chevron" *ngIf="item.subMenu">&#9654;</span>
361
+ <div class="sub-menu" *ngIf="item.subMenu">
362
+ <div *ngFor="let sub of item.subMenu" class="menu-item"
363
+ (click)="!sub.disabled && sub.action(); closeContextMenu(); $event.stopPropagation()">
364
+ <span class="menu-icon" *ngIf="sub.icon">{{ sub.icon }}</span>
365
+ <span class="menu-text">{{ sub.name }}</span>
437
366
  </div>
438
367
  </div>
439
368
  </div>
440
369
  </ng-container>
441
370
  </div>
371
+
372
+ <!-- Tooltip -->
373
+ <div class="ag-tooltip" *ngIf="tooltipVisible"
374
+ [style.left.px]="tooltipPosition.x"
375
+ [style.top.px]="tooltipPosition.y">{{ tooltipText }}</div>
376
+
377
+ <!-- Filter Popup -->
378
+ <div *ngIf="activeFilterPopup" class="filter-popup" [style.left.px]="filterPopupPosition.x" [style.top.px]="filterPopupPosition.y" (clickOutside)="closeFilterPopup()" clickOutside (click)="$event.stopPropagation()">
379
+ <div class="filter-popup-content">
380
+ <div class="filter-popup-header">
381
+ Filter {{ getHeaderName(activeFilterPopupColumn!) }}
382
+ </div>
383
+ <select class="filter-operator-select" [value]="activeFilterOperator" (change)="onFilterPopupOperatorChange($any($event.target).value)">
384
+ <option *ngFor="let op of (activeFilterPopupType === 'number' ? numberFilterOperators : textFilterOperators)" [value]="op.value">{{ op.label }}</option>
385
+ </select>
386
+
387
+ <div class="filter-input-container">
388
+ <input class="filter-input"
389
+ [type]="activeFilterPopupType === 'number' ? 'number' : 'text'"
390
+ [value]="filterValue1"
391
+ (input)="onFilterPopupInput($event, false)"
392
+ placeholder="Filter..." />
393
+ </div>
394
+
395
+ <div class="filter-input-container" *ngIf="activeFilterOperator === 'inRange'">
396
+ <input class="filter-input"
397
+ [type]="activeFilterPopupType === 'number' ? 'number' : 'text'"
398
+ [value]="filterValue2"
399
+ (input)="onFilterPopupInput($event, true)"
400
+ placeholder="to..." />
401
+ </div>
402
+
403
+ <div class="filter-popup-footer">
404
+ <button class="filter-clear-btn" (click)="clearColumnFilter(activeFilterPopupColumn!)">Clear Filter</button>
405
+ </div>
406
+ </div>
407
+ </div>
442
408
  </div>