argent-grid 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) 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 +70 -27
  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/cell-renderers.spec.ts +152 -0
  28. package/e2e/debug-streaming.spec.ts +31 -0
  29. package/e2e/dnd.spec.ts +73 -0
  30. package/e2e/screenshots.spec.ts +52 -0
  31. package/e2e/theming.spec.ts +35 -0
  32. package/e2e/visual.spec.ts +112 -0
  33. package/e2e/visual.spec.ts-snapshots/checkbox-renderer-mixed.png +0 -0
  34. package/e2e/visual.spec.ts-snapshots/debug.png +0 -0
  35. package/e2e/visual.spec.ts-snapshots/grid-column-group-headers.png +0 -0
  36. package/e2e/visual.spec.ts-snapshots/grid-default.png +0 -0
  37. package/e2e/visual.spec.ts-snapshots/grid-empty-state.png +0 -0
  38. package/e2e/visual.spec.ts-snapshots/grid-filter-popup.png +0 -0
  39. package/e2e/visual.spec.ts-snapshots/grid-scroll-borders.png +0 -0
  40. package/e2e/visual.spec.ts-snapshots/grid-sidebar-buttons.png +0 -0
  41. package/e2e/visual.spec.ts-snapshots/grid-text-filter.png +0 -0
  42. package/e2e/visual.spec.ts-snapshots/grid-with-selection.png +0 -0
  43. package/e2e/visual.spec.ts-snapshots/rating-renderer-varied.png +0 -0
  44. package/package.json +21 -7
  45. package/plan.md +56 -28
  46. package/playwright.config.ts +38 -0
  47. package/setup-vitest.ts +10 -13
  48. package/src/lib/argent-grid.module.ts +10 -12
  49. package/src/lib/components/argent-grid.component.css +281 -321
  50. package/src/lib/components/argent-grid.component.html +295 -207
  51. package/src/lib/components/argent-grid.component.spec.ts +120 -160
  52. package/src/lib/components/argent-grid.component.ts +1193 -290
  53. package/src/lib/components/argent-grid.regressions.spec.ts +301 -0
  54. package/src/lib/components/argent-grid.selection.spec.ts +132 -0
  55. package/src/lib/components/set-filter/set-filter.component.spec.ts +191 -0
  56. package/src/lib/components/set-filter/set-filter.component.ts +307 -0
  57. package/src/lib/directives/ag-grid-compatibility.directive.ts +16 -26
  58. package/src/lib/directives/click-outside.directive.ts +19 -0
  59. package/src/lib/rendering/canvas-renderer.spec.ts +513 -0
  60. package/src/lib/rendering/canvas-renderer.ts +456 -452
  61. package/src/lib/rendering/live-data-handler.ts +110 -0
  62. package/src/lib/rendering/live-data-optimizations.ts +133 -0
  63. package/src/lib/rendering/render/blit.spec.ts +16 -27
  64. package/src/lib/rendering/render/blit.ts +48 -36
  65. package/src/lib/rendering/render/cells.spec.ts +132 -0
  66. package/src/lib/rendering/render/cells.ts +167 -28
  67. package/src/lib/rendering/render/column-utils.ts +95 -0
  68. package/src/lib/rendering/render/hit-test.ts +50 -0
  69. package/src/lib/rendering/render/index.ts +88 -76
  70. package/src/lib/rendering/render/lines.ts +53 -47
  71. package/src/lib/rendering/render/primitives.ts +423 -0
  72. package/src/lib/rendering/render/theme.spec.ts +8 -12
  73. package/src/lib/rendering/render/theme.ts +7 -10
  74. package/src/lib/rendering/render/types.ts +3 -2
  75. package/src/lib/rendering/render/walk.spec.ts +35 -38
  76. package/src/lib/rendering/render/walk.ts +94 -64
  77. package/src/lib/rendering/utils/damage-tracker.spec.ts +8 -7
  78. package/src/lib/rendering/utils/damage-tracker.ts +6 -18
  79. package/src/lib/rendering/utils/index.ts +1 -1
  80. package/src/lib/services/grid.service.set-filter.spec.ts +219 -0
  81. package/src/lib/services/grid.service.spec.ts +1241 -201
  82. package/src/lib/services/grid.service.ts +1204 -235
  83. package/src/lib/themes/parts/color-schemes.ts +132 -0
  84. package/src/lib/themes/parts/icon-sets.ts +258 -0
  85. package/src/lib/themes/theme-builder.ts +347 -0
  86. package/src/lib/themes/theme-quartz.ts +72 -0
  87. package/src/lib/themes/types.ts +238 -0
  88. package/src/lib/types/ag-grid-types.ts +573 -14
  89. package/src/public-api.ts +39 -9
  90. package/src/stories/Advanced.stories.ts +249 -0
  91. package/src/stories/ArgentGrid.stories.ts +301 -0
  92. package/src/stories/Benchmark.stories.ts +76 -0
  93. package/src/stories/CellRenderers.stories.ts +395 -0
  94. package/src/stories/Filtering.stories.ts +292 -0
  95. package/src/stories/Grouping.stories.ts +290 -0
  96. package/src/stories/Streaming.stories.ts +57 -0
  97. package/src/stories/Theming.stories.ts +137 -0
  98. package/src/stories/Tooltips.stories.ts +381 -0
  99. package/src/stories/benchmark-wrapper.component.ts +355 -0
  100. package/src/stories/story-utils.ts +88 -0
  101. package/src/stories/streaming-wrapper.component.ts +441 -0
  102. package/tsconfig.json +1 -0
  103. package/tsconfig.storybook.json +10 -0
  104. package/vitest.config.ts +9 -9
  105. package/demo-app/README.md +0 -70
  106. package/demo-app/angular.json +0 -78
  107. package/demo-app/e2e/benchmark.spec.ts +0 -53
  108. package/demo-app/e2e/demo-page.spec.ts +0 -77
  109. package/demo-app/e2e/grid-features.spec.ts +0 -269
  110. package/demo-app/package-lock.json +0 -14023
  111. package/demo-app/package.json +0 -36
  112. package/demo-app/playwright-test-menu.js +0 -19
  113. package/demo-app/playwright.config.ts +0 -23
  114. package/demo-app/src/app/app.component.ts +0 -10
  115. package/demo-app/src/app/app.config.ts +0 -13
  116. package/demo-app/src/app/app.routes.ts +0 -7
  117. package/demo-app/src/app/demo-page/demo-page.component.css +0 -313
  118. package/demo-app/src/app/demo-page/demo-page.component.html +0 -124
  119. package/demo-app/src/app/demo-page/demo-page.component.ts +0 -366
  120. package/demo-app/src/index.html +0 -19
  121. package/demo-app/src/main.ts +0 -6
  122. package/demo-app/tsconfig.json +0 -31
@@ -1,148 +1,255 @@
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
- <!-- Selection Column Header -->
9
- <div
10
- *ngIf="showSelectionColumn"
11
- class="argent-grid-header-cell argent-grid-selection-header"
12
- [style.width.px]="selectionColumnWidth"
13
- (click)="onSelectionHeaderClick()">
14
- <input type="checkbox"
15
- [checked]="isAllSelected"
16
- [indeterminate]="isIndeterminateSelection"
17
- (change)="onSelectionHeaderChange($event)" />
18
- </div>
19
22
 
20
- <!-- Left Pinned Columns -->
23
+ <div class="argent-grid-header-main">
24
+ <!-- Left Pinned Header -->
21
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')"
22
30
  cdkDropList
23
- id="left-pinned"
24
- [cdkDropListConnectedTo]="['scrollable', 'right-pinned']"
31
+ id="left-pinned-list"
25
32
  cdkDropListOrientation="horizontal"
26
33
  (cdkDropListDropped)="onColumnDropped($event, 'left')">
27
- <div
28
- *ngFor="let col of getLeftPinnedColumns(); trackBy: trackByColumn"
29
- class="argent-grid-header-cell argent-grid-header-cell-pinned-left"
30
- [style.width.px]="getColumnWidth(col)"
31
- [class.sortable]="isSortable(col)"
32
- (click)="onHeaderClick(col)"
33
- cdkDrag
34
- [cdkDragData]="col">
35
- <div class="argent-grid-header-content" cdkDragHandle>
36
- <span class="header-text">{{ getHeaderName(col) }}</span>
37
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
38
- </div>
39
- <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, col)" *ngIf="hasHeaderMenu(col)">
40
- &#8942;
41
- </div>
42
- <div class="argent-grid-header-resize-handle"
43
- *ngIf="isResizable(col)"
44
- [class.resizing]="isResizing && resizeColumn === col"
45
- (mousedown)="onResizeMouseDown($event, col)">
46
- </div>
47
- </div>
48
- </div>
49
-
50
- <!-- Scrollable Columns -->
51
- <div class="argent-grid-header-scrollable"
52
- #headerScrollable
53
- cdkDropList
54
- id="scrollable"
55
- [cdkDropListConnectedTo]="['left-pinned', 'right-pinned']"
56
- cdkDropListOrientation="horizontal"
57
- (cdkDropListDropped)="onColumnDropped($event, 'none')">
58
- <div class="argent-grid-header-row">
34
+
35
+ <ng-container *ngFor="let entry of getSectionHeaderItems('left'); trackBy: trackByHeaderItem">
59
36
  <div
60
- *ngFor="let col of getNonPinnedColumns(); trackBy: trackByColumn"
61
- class="argent-grid-header-cell"
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
- [cdkDragData]="col">
48
+ [cdkDragDisabled]="isColumnGroup(entry.item) || entry.item.colId === 'ag-Grid-SelectionColumn'"
49
+ [cdkDragData]="entry.item">
67
50
  <div class="argent-grid-header-content" cdkDragHandle>
68
- <span class="header-text">{{ getHeaderName(col) }}</span>
69
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
51
+ <div *ngIf="!isColumnGroup(entry.item) && hasHeaderCheckbox(entry.item)" class="argent-grid-header-checkbox">
52
+ <input type="checkbox"
53
+ [checked]="isAllSelected"
54
+ [indeterminate]="isIndeterminateSelection"
55
+ (click)="$event.stopPropagation()"
56
+ (change)="onSelectionHeaderChange($event)" />
57
+ </div>
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>
70
70
  </div>
71
- <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)">
72
72
  &#8942;
73
73
  </div>
74
74
  <div class="argent-grid-header-resize-handle"
75
- *ngIf="isResizable(col)"
76
- [class.resizing]="isResizing && resizeColumn === col"
77
- (mousedown)="onResizeMouseDown($event, col)">
75
+ *ngIf="isResizable(entry.item)"
76
+ [class.resizing]="isResizing && resizeItem === entry.item"
77
+ (mousedown)="onResizeMouseDown($event, entry.item)">
78
78
  </div>
79
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>
80
133
  </div>
81
134
  </div>
82
135
 
83
- <!-- Right Pinned Columns -->
136
+ <!-- Right Pinned Header -->
84
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')"
85
142
  cdkDropList
86
- id="right-pinned"
87
- [cdkDropListConnectedTo]="['left-pinned', 'scrollable']"
143
+ id="right-pinned-list"
88
144
  cdkDropListOrientation="horizontal"
89
145
  (cdkDropListDropped)="onColumnDropped($event, 'right')">
90
- <div
91
- *ngFor="let col of getRightPinnedColumns(); trackBy: trackByColumn"
92
- class="argent-grid-header-cell argent-grid-header-cell-pinned-right"
93
- [style.width.px]="getColumnWidth(col)"
94
- [class.sortable]="isSortable(col)"
95
- (click)="onHeaderClick(col)"
96
- cdkDrag
97
- [cdkDragData]="col">
98
- <div class="argent-grid-header-content" cdkDragHandle>
99
- <span class="header-text">{{ getHeaderName(col) }}</span>
100
- <span class="sort-indicator" *ngIf="getSortIndicator(col)">{{ getSortIndicator(col) }}</span>
101
- </div>
102
- <div class="argent-grid-header-menu-icon" (click)="onHeaderMenuClick($event, col)" *ngIf="hasHeaderMenu(col)">
103
- &#8942;
104
- </div>
105
- <div class="argent-grid-header-resize-handle"
106
- *ngIf="isResizable(col)"
107
- [class.resizing]="isResizing && resizeColumn === col"
108
- (mousedown)="onResizeMouseDown($event, col)">
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)">
182
+ </div>
109
183
  </div>
110
- </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">
111
191
  </div>
112
192
  </div>
113
193
 
114
194
  <!-- Floating Filter Row -->
115
- <div class="argent-grid-header-row floating-filter-row" *ngIf="hasFloatingFilters()">
116
- <!-- Selection Column Padding -->
117
- <div *ngIf="showSelectionColumn" class="argent-grid-header-cell" [style.width.px]="selectionColumnWidth"></div>
118
-
195
+ <div class="floating-filter-row" *ngIf="hasFloatingFilters()">
119
196
  <!-- Left Pinned Filters -->
120
- <div class="argent-grid-header-pinned-left-container">
121
- <div
122
- *ngFor="let col of getLeftPinnedColumns(); trackBy: trackByColumn"
123
- class="argent-grid-header-cell argent-grid-header-cell-pinned-left"
124
- [style.width.px]="getColumnWidth(col)">
125
- <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
126
- <input #filterInput
127
- class="floating-filter-input"
128
- [type]="getFilterInputType(col)"
129
- [value]="getFloatingFilterValue(col)"
130
- (input)="onFloatingFilterInput($event, col)"
131
- [placeholder]="'Filter...'" />
132
- <span class="floating-filter-clear"
133
- *ngIf="hasFilterValue(col, filterInput)"
134
- (click)="clearFloatingFilter(col, filterInput)">✕</span>
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)">
203
+ <input #filterInput
204
+ class="floating-filter-input"
205
+ [type]="getFilterInputType(col)"
206
+ [value]="getFloatingFilterValue(col)"
207
+ (input)="onFloatingFilterInput($event, col)"
208
+ [placeholder]="'Filter...'" />
209
+ <span class="floating-filter-clear"
210
+ *ngIf="hasFilterValue(col, filterInput)"
211
+ (click)="clearFloatingFilter(col, filterInput)">✕</span>
212
+ </div>
135
213
  </div>
136
- </div>
214
+ </ng-container>
137
215
  </div>
138
-
139
216
  <!-- Scrollable Filters -->
140
217
  <div class="argent-grid-header-scrollable" #headerScrollableFilter>
141
- <div class="argent-grid-header-row">
142
- <div
143
- *ngFor="let col of getNonPinnedColumns(); trackBy: trackByColumn"
144
- class="argent-grid-header-cell"
145
- [style.width.px]="getColumnWidth(col)">
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>
243
+ </div>
244
+ </ng-container>
245
+ </div>
246
+ </div>
247
+ <!-- Right Pinned Filters -->
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)">
146
253
  <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
147
254
  <input #filterInput
148
255
  class="floating-filter-input"
@@ -150,42 +257,29 @@
150
257
  [value]="getFloatingFilterValue(col)"
151
258
  (input)="onFloatingFilterInput($event, col)"
152
259
  [placeholder]="'Filter...'" />
153
- <span class="floating-filter-clear"
260
+ <span class="floating-filter-clear"
154
261
  *ngIf="hasFilterValue(col, filterInput)"
155
262
  (click)="clearFloatingFilter(col, filterInput)">✕</span>
156
263
  </div>
157
264
  </div>
158
- </div>
265
+ </ng-container>
159
266
  </div>
160
267
 
161
- <!-- Right Pinned Filters -->
162
- <div class="argent-grid-header-pinned-right-container">
163
- <div
164
- *ngFor="let col of getRightPinnedColumns(); trackBy: trackByColumn"
165
- class="argent-grid-header-cell argent-grid-header-cell-pinned-right"
166
- [style.width.px]="getColumnWidth(col)">
167
- <div class="floating-filter-container" *ngIf="isFloatingFilterEnabled(col)">
168
- <input #filterInput
169
- class="floating-filter-input"
170
- [type]="getFilterInputType(col)"
171
- [value]="getFloatingFilterValue(col)"
172
- (input)="onFloatingFilterInput($event, col)"
173
- [placeholder]="'Filter...'" />
174
- <span class="floating-filter-clear"
175
- *ngIf="hasFilterValue(col, filterInput)"
176
- (click)="clearFloatingFilter(col, filterInput)">✕</span>
177
- </div>
178
- </div>
268
+ <!-- Vertical Scrollbar Spacer -->
269
+ <div class="argent-grid-header-scrollbar-spacer"
270
+ *ngIf="scrollbarWidth > 0"
271
+ [style.width.px]="scrollbarWidth">
179
272
  </div>
180
273
  </div>
181
274
  </div>
182
275
 
183
- <!-- Canvas Layer for Data Viewport with virtual scrolling -->
276
+ <!-- Canvas Layer for Data Viewport -->
184
277
  <div class="argent-grid-viewport" #viewport>
185
- <!-- Spacer to create scrollbars for virtual scrolling -->
186
278
  <div class="argent-grid-scroll-spacer" [style.height.px]="totalHeight" [style.width.px]="totalWidth"></div>
187
-
188
- <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>
189
283
 
190
284
  <!-- Cell Editor Overlay -->
191
285
  <div class="argent-grid-cell-editor"
@@ -207,48 +301,29 @@
207
301
  </div>
208
302
  </div>
209
303
 
210
- <!-- Side Bar / Tool Panels -->
304
+ <!-- Side Bar -->
211
305
  <div class="argent-grid-side-bar" *ngIf="sideBarVisible" [class.has-active-panel]="!!activeToolPanel">
212
306
  <div class="side-bar-buttons">
213
307
  <div class="side-bar-button" [class.active]="activeToolPanel === 'columns'" (click)="toggleToolPanel('columns')">
214
308
  Columns
215
309
  </div>
216
- <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')">
217
312
  Filters
218
- </div>
313
+ </div> -->
219
314
  </div>
220
315
 
221
316
  <div class="tool-panel-content" *ngIf="activeToolPanel">
222
- <!-- Columns Tool Panel -->
223
317
  <div class="columns-tool-panel" *ngIf="activeToolPanel === 'columns'">
224
318
  <h3>Columns</h3>
225
- <div class="column-list"
226
- cdkDropList
227
- (cdkDropListDropped)="onSidebarColumnDropped($event)">
228
- <div *ngFor="let col of getAllColumns()"
229
- class="column-item"
230
- cdkDrag
231
- [cdkDragData]="col">
232
- <div *cdkDragPreview class="sidebar-drag-preview">
233
- <span class="column-drag-handle">⠿</span>
234
- <span>{{ getHeaderName(col) }}</span>
235
- </div>
236
- <div *cdkDragPlaceholder class="sidebar-drag-placeholder"></div>
237
-
319
+ <div class="column-list" cdkDropList (cdkDropListDropped)="onSidebarColumnDropped($event)">
320
+ <div *ngFor="let col of getAllColumns()" class="column-item" cdkDrag [cdkDragData]="col">
238
321
  <span class="column-drag-handle" cdkDragHandle>⠿</span>
239
322
  <input type="checkbox" [checked]="col.visible" (change)="toggleColumnVisibility(col)" />
240
323
  <span class="column-label">{{ getHeaderName(col) }}</span>
241
324
  </div>
242
325
  </div>
243
326
  </div>
244
-
245
- <!-- Filters Tool Panel -->
246
- <div class="filters-tool-panel" *ngIf="activeToolPanel === 'filters'">
247
- <h3>Filters</h3>
248
- <div class="filter-placeholder">
249
- Filters coming soon...
250
- </div>
251
- </div>
252
327
  </div>
253
328
  </div>
254
329
  </div>
@@ -258,63 +333,76 @@
258
333
  <ng-content select="[overlay]"></ng-content>
259
334
  </div>
260
335
 
261
- <!-- Header Menu Overlay -->
262
- <div class="argent-grid-header-menu"
263
- *ngIf="activeHeaderMenu"
264
- [style.top.px]="headerMenuPosition.y"
265
- [style.left.px]="headerMenuPosition.x"
266
- (click)="$event.stopPropagation()">
267
- <div class="menu-item" (click)="sortColumnMenu('asc')">
268
- <span class="menu-icon">↑</span> Sort Ascending
269
- </div>
270
- <div class="menu-item" (click)="sortColumnMenu('desc')">
271
- <span class="menu-icon">↓</span> Sort Descending
272
- </div>
273
- <div class="menu-item" (click)="sortColumnMenu(null)">
274
- <span class="menu-icon">✕</span> Clear Sort
275
- </div>
276
- <div class="menu-divider"></div>
277
- <div class="menu-item" (click)="hideColumnMenu()">
278
- <span class="menu-icon">ø</span> Hide Column
279
- </div>
280
- <div class="menu-item" (click)="pinColumnMenu('left')">
281
- <span class="menu-icon">«</span> Pin Left
282
- </div>
283
- <div class="menu-item" (click)="pinColumnMenu('right')">
284
- <span class="menu-icon">»</span> Pin Right
285
- </div>
286
- <div class="menu-item" (click)="pinColumnMenu(null)">
287
- <span class="menu-icon">↺</span> Unpin
288
- </div>
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()">
338
+ <ng-container *ngFor="let item of headerMenuItems">
339
+ <div *ngIf="item.separator" class="menu-divider"></div>
340
+ <div *ngIf="!item.separator" class="menu-item" (click)="!item.disabled && item.action(); !item.subMenu && closeHeaderMenu()">
341
+ <span class="menu-icon" *ngIf="item.icon">{{ item.icon }}</span>
342
+ <span class="menu-text">{{ item.name }}</span>
343
+ </div>
344
+ </ng-container>
345
+ </div>
346
+
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>
289
350
  </div>
290
351
 
291
- <!-- Context Menu Overlay -->
292
- <div class="argent-grid-context-menu"
293
- *ngIf="activeContextMenu"
294
- [style.top.px]="contextMenuPosition.y"
295
- [style.left.px]="contextMenuPosition.x"
296
- (click)="$event.stopPropagation()">
352
+ <!-- Context Menu -->
353
+ <div class="context-menu-popup" *ngIf="activeContextMenu" [style.top.px]="contextMenuPosition.y" [style.left.px]="contextMenuPosition.x" (click)="$event.stopPropagation()">
297
354
  <ng-container *ngFor="let item of contextMenuItems">
298
355
  <div *ngIf="item.separator" class="menu-divider"></div>
299
- <div *ngIf="!item.separator"
300
- class="menu-item"
301
- [class.disabled]="item.disabled"
302
- [class.has-submenu]="item.subMenu && item.subMenu.length > 0"
303
- (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()">
304
358
  <span class="menu-icon" *ngIf="item.icon">{{ item.icon }}</span>
305
359
  <span class="menu-text">{{ item.name }}</span>
306
- <span class="menu-arrow" *ngIf="item.subMenu && item.subMenu.length > 0">▶</span>
307
-
308
- <!-- Sub-menu -->
309
- <div class="argent-grid-context-menu sub-menu" *ngIf="item.subMenu && item.subMenu.length > 0">
310
- <div *ngFor="let subItem of item.subMenu"
311
- class="menu-item"
312
- (click)="subItem.action(); closeContextMenu(); $event.stopPropagation()">
313
- <span class="menu-icon" *ngIf="subItem.icon">{{ subItem.icon }}</span>
314
- <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>
315
366
  </div>
316
367
  </div>
317
368
  </div>
318
369
  </ng-container>
319
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>
320
408
  </div>