bways-grid 0.0.5

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 (37) hide show
  1. package/README.md +76 -0
  2. package/fesm2022/bways-grid.mjs +3440 -0
  3. package/fesm2022/bways-grid.mjs.map +1 -0
  4. package/index.d.ts +5 -0
  5. package/lib/bways-grid.module.d.ts +13 -0
  6. package/lib/components/cell/cell.component.d.ts +20 -0
  7. package/lib/components/choose-columns/choose-columns.component.d.ts +17 -0
  8. package/lib/components/column-tool-panel/column-tool-panel.component.d.ts +36 -0
  9. package/lib/components/filter-tool-panel/filter-tool-panel.component.d.ts +63 -0
  10. package/lib/components/header/header.component.d.ts +42 -0
  11. package/lib/components/header-filter/header-filter.component.d.ts +31 -0
  12. package/lib/components/header-menu/header-menu.component.d.ts +41 -0
  13. package/lib/components/pagination/pagination.component.d.ts +22 -0
  14. package/lib/components/row/row.component.d.ts +19 -0
  15. package/lib/components/side-bar/side-bar.component.d.ts +42 -0
  16. package/lib/components/ultra-grid/ultra-grid.component.d.ts +155 -0
  17. package/lib/core/grid-engine.service.d.ts +14 -0
  18. package/lib/core/grid-flattener.service.d.ts +8 -0
  19. package/lib/core/row-cache.d.ts +10 -0
  20. package/lib/core/viewport-manager.d.ts +21 -0
  21. package/lib/datasources/infinite-scroll.datasource.d.ts +17 -0
  22. package/lib/datasources/server-datasource.interface.d.ts +14 -0
  23. package/lib/directives/column-resize.directive.d.ts +18 -0
  24. package/lib/models/column.model.d.ts +21 -0
  25. package/lib/models/csv-export.model.d.ts +12 -0
  26. package/lib/models/filter.model.d.ts +18 -0
  27. package/lib/models/grid-config.model.d.ts +11 -0
  28. package/lib/models/row.model.d.ts +26 -0
  29. package/lib/services/csv-export.service.d.ts +11 -0
  30. package/lib/workers/generated/export.worker.code.d.ts +1 -0
  31. package/lib/workers/generated/sorting.worker.code.d.ts +1 -0
  32. package/package.json +23 -0
  33. package/public-api.d.ts +19 -0
  34. package/src/lib/workers/export.worker.ts +110 -0
  35. package/src/lib/workers/generated/export.worker.code.ts +7 -0
  36. package/src/lib/workers/generated/sorting.worker.code.ts +4 -0
  37. package/src/lib/workers/sorting.worker.ts +423 -0
@@ -0,0 +1,3440 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, HostListener, Output, Input, Directive, ViewEncapsulation, ChangeDetectionStrategy, Component, HostBinding, Injectable, PLATFORM_ID, Inject, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
5
+ import * as i3$1 from '@angular/cdk/scrolling';
6
+ import { ScrollingModule } from '@angular/cdk/scrolling';
7
+ import { BehaviorSubject, Subscription, Subject } from 'rxjs';
8
+ import { map } from 'rxjs/operators';
9
+ import { DataSource } from '@angular/cdk/collections';
10
+ import * as i3 from '@angular/cdk/drag-drop';
11
+ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
12
+ import * as i2 from '@angular/forms';
13
+ import { FormsModule } from '@angular/forms';
14
+
15
+ // AUTO-GENERATED FILE. DO NOT EDIT.
16
+ // Built from src/lib/workers/sorting.worker.ts
17
+ const SORTING_WORKER_CODE = `"use strict";(()=>{var G=[],N=[];addEventListener("message",r=>{let{action:a,rows:n,sortModel:o,filterModel:i,conditionFilters:e,groupModel:t,aggregations:d,groupStateMap:l}=r.data;if(a==="INIT"){G=(n||[]).map(u=>({...u,type:"leaf",level:0})),postMessage({action:"INIT_DONE"});return}if(a==="FLATTEN_ONLY"){let s=m(N,l||{},r.data.groupIncludeFooter);postMessage({flatList:s});return}if(a==="EXECUTE"){let s=G;if(i&&Object.keys(i).length>0){let c={};for(let p of Object.keys(i))c[p]=new Set(i[p]);s=s.filter(p=>{for(let y of Object.keys(c)){let F=c[y],k=p.data?p.data[y]:null;if(!F.has(k))return!1}return!0})}e&&Object.keys(e).length>0&&(s=O(s,e));let u=s,f=d||{};if(t&&t.length>0?u=E(s,t,f):s.forEach(c=>c.level=0),o&&o.length>0&&L(u,o),N=u,t&&t.length>0&&r.data.groupIncludeFooter&&Object.keys(f).length>0){let p={id:"grand-total-footer",type:"group-footer",level:0,groupField:"Grand Total",groupKey:"Grand Total",data:v(s,f)};N.push(p)}let g=m(N,l||{},r.data.groupIncludeFooter),h=U(N);postMessage({flatList:g,resultTree:h})}});function U(r){return r.map(a=>{if(a.type==="leaf")return{id:a.id,type:"leaf",level:a.level};if(a.type==="group"){let n=a;return{...n,children:n.children?U(n.children):void 0}}return a})}function m(r,a,n=!1){let o=[],i=[];for(let e=r.length-1;e>=0;e--)i.push(r[e]);for(;i.length>0;){let e=i.pop();if(e.type==="group"){let t=e;if(o.push({id:t.id,type:"group",level:t.level,groupField:t.groupField,groupKey:t.groupKey,data:t.data,childCount:t.children?t.children.length:0}),!!a[t.id]&&(n&&i.push({id:\`\${t.id}-footer\`,type:"group-footer",level:t.level,groupField:t.groupField,groupKey:t.groupKey,data:{...t.data}}),t.children))for(let l=t.children.length-1;l>=0;l--)i.push(t.children[l])}else e.type==="group-footer"?o.push({id:e.id,type:"group-footer",level:e.level,groupField:e.groupField,groupKey:e.groupKey,data:e.data}):o.push({id:e.id,type:"leaf",level:e.level})}return o}function E(r,a,n){return T(r,a,0,n,"root")}function T(r,a,n,o,i){let e=a[n],t=n===a.length-1,d=new Map;for(let s of r){let u=s.data?s.data[e]:null;d.has(u)||d.set(u,[]),d.get(u).push(s)}let l=[];for(let[s,u]of d.entries()){let f=\`\${i}-\${e}-\${s}\`,g={id:f,type:"group",level:n,groupField:e,groupKey:s,data:{[e]:s},children:[]},h=v(u,o);Object.assign(g.data,h),t?(u.forEach(c=>c.level=n+1),g.children=u):g.children=T(u,a,n+1,o,f),l.push(g)}return l}function v(r,a){let n={};for(let o of Object.keys(a)){let i=a[o],e=0;if(i==="count")e=r.length;else{let t=!0,d=0,l=0;for(let s of r){let u=s.data?s.data[o]:0,f=0;typeof u=="number"?f=u:typeof u=="string"&&(f=Number(u.replace(/[^0-9.-]+/g,""))),isNaN(f)&&(f=0),i==="sum"?e+=f:i==="min"?t?e=f:e=Math.min(e,f):i==="max"?t?e=f:e=Math.max(e,f):i==="avg"&&(d+=f,l++),t=!1}i==="avg"&&(e=l>0?d/l:0)}n[o]=e}return n}function L(r,a){r.sort((n,o)=>{for(let i of a){let{field:e,direction:t}=i,d=n.data?n.data[e]:null,l=o.data?o.data[e]:null;if(d===l)continue;let s=d>l?1:-1;return t==="asc"?s:-s}return 0});for(let n of r)n.type==="group"&&n.children&&L(n.children,a)}function O(r,a){let n=Object.keys(a);return n.length===0?r:r.filter(o=>{for(let i of n){let e=a[i];if(!e||!e.condition1)continue;let t=o.data?o.data[i]:null,d=b(t,e.condition1);if(e.condition2&&e.condition2.type){let l=b(t,e.condition2);if(!(e.operator==="OR"?d||l:d&&l))return!1}else if(!d)return!1}return!0})}function b(r,a){let{type:n,value:o,valueTo:i}=a;if(n==="blank")return r==null||r==="";if(n==="notBlank")return r!=null&&r!=="";if(o==null||o==="")return!0;let e=String(r??"").toLowerCase(),t=String(o).toLowerCase();switch(n){case"contains":return e.includes(t);case"notContains":return!e.includes(t);case"equals":return!isNaN(Number(r))&&!isNaN(Number(o))?Number(r)===Number(o):e===t;case"notEqual":return!isNaN(Number(r))&&!isNaN(Number(o))?Number(r)!==Number(o):e!==t;case"startsWith":return e.startsWith(t);case"endsWith":return e.endsWith(t);case"greaterThan":return Number(r)>Number(o);case"greaterThanOrEqual":return Number(r)>=Number(o);case"lessThan":return Number(r)<Number(o);case"lessThanOrEqual":return Number(r)<=Number(o);case"inRange":if(i==null||i==="")return!0;let d=Number(r);return d>=Number(o)&&d<=Number(i);default:return!0}}})();
18
+ `;
19
+
20
+ class InfiniteScrollDataSource extends DataSource {
21
+ _serverDataSource;
22
+ _bufferSize;
23
+ _cachedData = [];
24
+ _dataStream = new BehaviorSubject([]);
25
+ _subscription = new Subscription();
26
+ _fetchedPages = new Set();
27
+ _pageSize = 100;
28
+ constructor(_serverDataSource, _bufferSize = 200) {
29
+ super();
30
+ this._serverDataSource = _serverDataSource;
31
+ this._bufferSize = _bufferSize;
32
+ this._pageSize = this._bufferSize;
33
+ }
34
+ connect(collectionViewer) {
35
+ this._subscription.add(collectionViewer.viewChange.subscribe(range => {
36
+ const startPage = this._getPageForIndex(range.start);
37
+ const endPage = this._getPageForIndex(range.end - 1);
38
+ for (let i = startPage; i <= endPage; i++) {
39
+ this._fetchPage(i);
40
+ }
41
+ }));
42
+ return this._dataStream;
43
+ }
44
+ disconnect() {
45
+ this._subscription.unsubscribe();
46
+ }
47
+ _getPageForIndex(index) {
48
+ return Math.floor(index / this._pageSize);
49
+ }
50
+ _fetchPage(page) {
51
+ if (this._fetchedPages.has(page)) {
52
+ return;
53
+ }
54
+ this._fetchedPages.add(page);
55
+ const startRow = page * this._pageSize;
56
+ const endRow = startRow + this._pageSize;
57
+ this._serverDataSource.getRows({ startRow, endRow }).subscribe(res => {
58
+ // Initialize array to totalCount if not yet done
59
+ if (this._cachedData.length !== res.totalCount) {
60
+ this._cachedData = Array.from({ length: res.totalCount }).fill(undefined);
61
+ }
62
+ this._cachedData.splice(startRow, res.rows.length, ...res.rows);
63
+ this._dataStream.next([...this._cachedData]);
64
+ });
65
+ }
66
+ }
67
+
68
+ class ColumnResizeDirective {
69
+ el;
70
+ renderer;
71
+ columnField;
72
+ resizeEnd = new EventEmitter();
73
+ startX;
74
+ startWidth;
75
+ minWidth = 50;
76
+ constructor(el, renderer) {
77
+ this.el = el;
78
+ this.renderer = renderer;
79
+ }
80
+ onMouseDown(event) {
81
+ event.preventDefault();
82
+ event.stopPropagation();
83
+ this.startX = event.pageX;
84
+ this.startWidth = this.el.nativeElement.parentElement.offsetWidth;
85
+ const mouseMoveListener = this.renderer.listen('document', 'mousemove', (e) => {
86
+ const width = Math.max(this.minWidth, this.startWidth + (e.pageX - this.startX));
87
+ this.renderer.setStyle(this.el.nativeElement.parentElement, 'width', `${width}px`);
88
+ });
89
+ const mouseUpListener = this.renderer.listen('document', 'mouseup', (e) => {
90
+ mouseMoveListener(); // Unregister
91
+ mouseUpListener(); // Unregister
92
+ const finalWidth = Math.max(this.minWidth, this.startWidth + (e.pageX - this.startX));
93
+ this.resizeEnd.emit({ field: this.columnField, width: finalWidth });
94
+ });
95
+ }
96
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ColumnResizeDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
97
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.18", type: ColumnResizeDirective, isStandalone: true, selector: "[ugColumnResize]", inputs: { columnField: ["ugColumnResize", "columnField"] }, outputs: { resizeEnd: "resizeEnd" }, host: { listeners: { "mousedown": "onMouseDown($event)" } }, ngImport: i0 });
98
+ }
99
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ColumnResizeDirective, decorators: [{
100
+ type: Directive,
101
+ args: [{
102
+ selector: '[ugColumnResize]',
103
+ standalone: true
104
+ }]
105
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { columnField: [{
106
+ type: Input,
107
+ args: ['ugColumnResize']
108
+ }], resizeEnd: [{
109
+ type: Output
110
+ }], onMouseDown: [{
111
+ type: HostListener,
112
+ args: ['mousedown', ['$event']]
113
+ }] } });
114
+
115
+ class HeaderComponent {
116
+ columns = [];
117
+ sortModel = [];
118
+ isAllSelected = false;
119
+ sortChanged = new EventEmitter();
120
+ columnsReordered = new EventEmitter();
121
+ columnResized = new EventEmitter();
122
+ headerCheckboxClicked = new EventEmitter();
123
+ menuClicked = new EventEmitter();
124
+ filterClicked = new EventEmitter();
125
+ getSortDirection(field) {
126
+ const model = this.sortModel.find(m => m.field === field);
127
+ return model ? model.direction : null;
128
+ }
129
+ onSort(col) {
130
+ const current = this.getSortDirection(col.field);
131
+ let next = 'asc';
132
+ if (current === 'asc')
133
+ next = 'desc';
134
+ else if (current === 'desc')
135
+ next = null;
136
+ this.sortChanged.emit({ field: col.field, direction: next });
137
+ }
138
+ onColumnDrop(event) {
139
+ const reordered = [...this.columns];
140
+ moveItemInArray(reordered, event.previousIndex, event.currentIndex);
141
+ this.columnsReordered.emit(reordered);
142
+ }
143
+ onResize(event) {
144
+ this.columnResized.emit(event);
145
+ }
146
+ onHeaderCheckboxClick(event) {
147
+ event.stopPropagation();
148
+ this.headerCheckboxClicked.emit(!this.isAllSelected);
149
+ }
150
+ onMenuClick(column, event) {
151
+ event.stopPropagation();
152
+ this.menuClicked.emit({ column, event });
153
+ }
154
+ onFilterClick(column, event) {
155
+ event.stopPropagation();
156
+ this.filterClicked.emit({ column, event });
157
+ }
158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: HeaderComponent, isStandalone: true, selector: "ug-header", inputs: { columns: "columns", sortModel: "sortModel", isAllSelected: "isAllSelected" }, outputs: { sortChanged: "sortChanged", columnsReordered: "columnsReordered", columnResized: "columnResized", headerCheckboxClicked: "headerCheckboxClicked", menuClicked: "menuClicked", filterClicked: "filterClicked" }, ngImport: i0, template: `
160
+ <div class="ug-header-row" cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="onColumnDrop($event)">
161
+ <div
162
+ class="ug-header-cell"
163
+ *ngFor="let col of columns; let isLast = last"
164
+ cdkDrag
165
+ [style.width.px]="col.width || 200"
166
+ [style.min-width.px]="col.minWidth"
167
+ [style.max-width.px]="col.maxWidth"
168
+ [class.ug-cell-hide]="col.hide"
169
+ [class.ug-pinned-left]="col.pinned === 'left'"
170
+ [class.ug-pinned-right]="col.pinned === 'right'"
171
+ [style.left.px]="col._leftOffset"
172
+ [style.right.px]="col._rightOffset"
173
+ >
174
+ <div class="ug-header-checkbox" *ngIf="col.headerCheckboxSelection" (click)="onHeaderCheckboxClick($event)">
175
+ <input type="checkbox" [checked]="isAllSelected" />
176
+ </div>
177
+
178
+ <div class="ug-header-cell-label" (click)="col.sortable !== false ? onSort(col) : null" [class.sortable]="col.sortable !== false">
179
+ <span>{{ col.headerName || col.field }}</span>
180
+
181
+ <div class="ug-sort-icon" *ngIf="col.sortable !== false && getSortDirection(col.field)">
182
+ <svg *ngIf="getSortDirection(col.field) === 'desc'" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
183
+ <svg *ngIf="getSortDirection(col.field) === 'asc'" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>
184
+ </div>
185
+ </div>
186
+
187
+ <div class="ug-header-filter-icon" (click)="onFilterClick(col, $event)">
188
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
189
+ </div>
190
+
191
+ <div class="ug-header-menu-icon" (click)="onMenuClick(col, $event)">
192
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
193
+ </div>
194
+
195
+ <div class="ug-header-divider" *ngIf="!isLast"></div>
196
+
197
+ <div
198
+ *ngIf="col.resizable !== false"
199
+ class="ug-resize-handle"
200
+ [class.ug-hide-resizer]="isLast"
201
+ [ugColumnResize]="col.field"
202
+ (resizeEnd)="onResize($event)"
203
+ (click)="$event.stopPropagation()">
204
+ </div>
205
+ </div>
206
+ </div>
207
+ `, isInline: true, styles: ["ug-header{display:block;background-color:var(--ug-header-bg);border-bottom:1px solid var(--ug-border-color);position:sticky;top:0;z-index:10;color:var(--ug-header-color);font-weight:var(--ug-header-font-weight)}.ug-header-row{display:inline-flex;min-width:100%;height:48px}.ug-header-cell{display:flex;align-items:center;padding:0 0 0 16px;box-sizing:border-box;position:relative;-webkit-user-select:none;user-select:none;background-color:var(--ug-header-bg);transition:background-color .2s;flex-shrink:0;flex-grow:1}.ug-header-cell:hover{background-color:var(--ug-header-hover-bg)}.ug-pinned-left{position:sticky;z-index:2;border-right:1px solid var(--ug-border-color)}.ug-pinned-right{position:sticky;z-index:2;border-left:1px solid var(--ug-border-color)}.ug-header-checkbox{margin-right:12px;display:flex;align-items:center}.ug-header-checkbox input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color)}.ug-header-cell-label{flex:1;display:flex;align-items:center;overflow:hidden;white-space:nowrap;height:100%}.ug-header-cell-label.sortable{cursor:pointer}.ug-header-cell-label span{overflow:hidden;text-overflow:ellipsis}.ug-sort-icon{margin-left:6px;display:flex;align-items:center;color:var(--ug-icon-color)}.ug-header-menu-icon,.ug-header-filter-icon{padding:0 4px;cursor:pointer;color:var(--ug-icon-color);display:flex;align-items:center;opacity:0;transition:opacity .2s}.ug-header-menu-icon{padding-right:8px}.ug-header-cell:hover .ug-header-menu-icon,.ug-header-cell:hover .ug-header-filter-icon{opacity:1}.ug-header-divider{width:1px;height:50%;background-color:var(--ug-border-color);margin-right:2px}.ug-resize-handle{position:absolute;right:0;top:0;bottom:0;width:5px;cursor:col-resize;background-color:transparent;z-index:1}.ug-resize-handle:hover,.ug-resize-handle:active{background-color:var(--ug-primary-color)}.ug-hide-resizer{display:none!important}.cdk-drag-preview{box-sizing:border-box;background-color:var(--ug-header-bg);border:1px solid var(--ug-border-color);box-shadow:0 4px 6px -1px #0000001a;display:flex;align-items:center;padding:0 16px;font-weight:var(--ug-header-font-weight);color:var(--ug-header-color);font-size:var(--ug-font-size);font-family:var(--ug-font-family)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: ColumnResizeDirective, selector: "[ugColumnResize]", inputs: ["ugColumnResize"], outputs: ["resizeEnd"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
208
+ }
209
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderComponent, decorators: [{
210
+ type: Component,
211
+ args: [{ selector: 'ug-header', standalone: true, imports: [CommonModule, DragDropModule, ColumnResizeDirective], template: `
212
+ <div class="ug-header-row" cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="onColumnDrop($event)">
213
+ <div
214
+ class="ug-header-cell"
215
+ *ngFor="let col of columns; let isLast = last"
216
+ cdkDrag
217
+ [style.width.px]="col.width || 200"
218
+ [style.min-width.px]="col.minWidth"
219
+ [style.max-width.px]="col.maxWidth"
220
+ [class.ug-cell-hide]="col.hide"
221
+ [class.ug-pinned-left]="col.pinned === 'left'"
222
+ [class.ug-pinned-right]="col.pinned === 'right'"
223
+ [style.left.px]="col._leftOffset"
224
+ [style.right.px]="col._rightOffset"
225
+ >
226
+ <div class="ug-header-checkbox" *ngIf="col.headerCheckboxSelection" (click)="onHeaderCheckboxClick($event)">
227
+ <input type="checkbox" [checked]="isAllSelected" />
228
+ </div>
229
+
230
+ <div class="ug-header-cell-label" (click)="col.sortable !== false ? onSort(col) : null" [class.sortable]="col.sortable !== false">
231
+ <span>{{ col.headerName || col.field }}</span>
232
+
233
+ <div class="ug-sort-icon" *ngIf="col.sortable !== false && getSortDirection(col.field)">
234
+ <svg *ngIf="getSortDirection(col.field) === 'desc'" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>
235
+ <svg *ngIf="getSortDirection(col.field) === 'asc'" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>
236
+ </div>
237
+ </div>
238
+
239
+ <div class="ug-header-filter-icon" (click)="onFilterClick(col, $event)">
240
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
241
+ </div>
242
+
243
+ <div class="ug-header-menu-icon" (click)="onMenuClick(col, $event)">
244
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>
245
+ </div>
246
+
247
+ <div class="ug-header-divider" *ngIf="!isLast"></div>
248
+
249
+ <div
250
+ *ngIf="col.resizable !== false"
251
+ class="ug-resize-handle"
252
+ [class.ug-hide-resizer]="isLast"
253
+ [ugColumnResize]="col.field"
254
+ (resizeEnd)="onResize($event)"
255
+ (click)="$event.stopPropagation()">
256
+ </div>
257
+ </div>
258
+ </div>
259
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["ug-header{display:block;background-color:var(--ug-header-bg);border-bottom:1px solid var(--ug-border-color);position:sticky;top:0;z-index:10;color:var(--ug-header-color);font-weight:var(--ug-header-font-weight)}.ug-header-row{display:inline-flex;min-width:100%;height:48px}.ug-header-cell{display:flex;align-items:center;padding:0 0 0 16px;box-sizing:border-box;position:relative;-webkit-user-select:none;user-select:none;background-color:var(--ug-header-bg);transition:background-color .2s;flex-shrink:0;flex-grow:1}.ug-header-cell:hover{background-color:var(--ug-header-hover-bg)}.ug-pinned-left{position:sticky;z-index:2;border-right:1px solid var(--ug-border-color)}.ug-pinned-right{position:sticky;z-index:2;border-left:1px solid var(--ug-border-color)}.ug-header-checkbox{margin-right:12px;display:flex;align-items:center}.ug-header-checkbox input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color)}.ug-header-cell-label{flex:1;display:flex;align-items:center;overflow:hidden;white-space:nowrap;height:100%}.ug-header-cell-label.sortable{cursor:pointer}.ug-header-cell-label span{overflow:hidden;text-overflow:ellipsis}.ug-sort-icon{margin-left:6px;display:flex;align-items:center;color:var(--ug-icon-color)}.ug-header-menu-icon,.ug-header-filter-icon{padding:0 4px;cursor:pointer;color:var(--ug-icon-color);display:flex;align-items:center;opacity:0;transition:opacity .2s}.ug-header-menu-icon{padding-right:8px}.ug-header-cell:hover .ug-header-menu-icon,.ug-header-cell:hover .ug-header-filter-icon{opacity:1}.ug-header-divider{width:1px;height:50%;background-color:var(--ug-border-color);margin-right:2px}.ug-resize-handle{position:absolute;right:0;top:0;bottom:0;width:5px;cursor:col-resize;background-color:transparent;z-index:1}.ug-resize-handle:hover,.ug-resize-handle:active{background-color:var(--ug-primary-color)}.ug-hide-resizer{display:none!important}.cdk-drag-preview{box-sizing:border-box;background-color:var(--ug-header-bg);border:1px solid var(--ug-border-color);box-shadow:0 4px 6px -1px #0000001a;display:flex;align-items:center;padding:0 16px;font-weight:var(--ug-header-font-weight);color:var(--ug-header-color);font-size:var(--ug-font-size);font-family:var(--ug-font-family)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}\n"] }]
260
+ }], propDecorators: { columns: [{
261
+ type: Input
262
+ }], sortModel: [{
263
+ type: Input
264
+ }], isAllSelected: [{
265
+ type: Input
266
+ }], sortChanged: [{
267
+ type: Output
268
+ }], columnsReordered: [{
269
+ type: Output
270
+ }], columnResized: [{
271
+ type: Output
272
+ }], headerCheckboxClicked: [{
273
+ type: Output
274
+ }], menuClicked: [{
275
+ type: Output
276
+ }], filterClicked: [{
277
+ type: Output
278
+ }] } });
279
+
280
+ class CellComponent {
281
+ column;
282
+ row;
283
+ isSelected = false;
284
+ isFirstColumn = false;
285
+ groupExpanded = false;
286
+ groupToggleClicked = new EventEmitter();
287
+ get isGroup() {
288
+ return !!this.row && this.row.type === 'group';
289
+ }
290
+ get isGroupFooter() {
291
+ return !!this.row && this.row.type === 'group-footer';
292
+ }
293
+ get value() {
294
+ return this.row && this.row.data ? this.row.data[this.column.field] : null;
295
+ }
296
+ get formattedFooterValue() {
297
+ const v = this.value;
298
+ if (v == null)
299
+ return '';
300
+ if (typeof v === 'number' && isFinite(v)) {
301
+ return v.toLocaleString();
302
+ }
303
+ // Try parsing string numbers
304
+ const num = Number(v);
305
+ if (!isNaN(num) && isFinite(num)) {
306
+ return num.toLocaleString();
307
+ }
308
+ return v;
309
+ }
310
+ onCheckboxClick(event) {
311
+ // Handled up the pipe by the row selection
312
+ }
313
+ onGroupToggleClick(event) {
314
+ event.stopPropagation();
315
+ this.groupToggleClicked.emit();
316
+ }
317
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: CellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
318
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: CellComponent, isStandalone: true, selector: "ug-cell", inputs: { column: "column", row: "row", isSelected: "isSelected", isFirstColumn: "isFirstColumn", groupExpanded: "groupExpanded" }, outputs: { groupToggleClicked: "groupToggleClicked" }, ngImport: i0, template: `
319
+ <div class="ug-cell-wrapper" [style.padding-left.px]="isFirstColumn && row.level ? row.level * 20 : 0">
320
+
321
+ <!-- Group Expand/Collapse Caret -->
322
+ <div
323
+ class="ug-group-caret"
324
+ *ngIf="isFirstColumn && isGroup"
325
+ (click)="onGroupToggleClick($event)">
326
+ <svg *ngIf="groupExpanded" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
327
+ <svg *ngIf="!groupExpanded" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
328
+ </div>
329
+
330
+ <div class="ug-cell-checkbox" *ngIf="column.checkboxSelection && row.type !== 'group-footer'" (click)="onCheckboxClick($event)">
331
+ <input type="checkbox" [checked]="isSelected" />
332
+ </div>
333
+ <ng-container *ngIf="column.cellTemplate; else defaultCell">
334
+ <ng-container *ngTemplateOutlet="column.cellTemplate; context: { $implicit: row?.data, row: row, column: column }"></ng-container>
335
+ </ng-container>
336
+ <ng-template #defaultCell>
337
+ <div class="ug-cell-content" [title]="value">
338
+ <ng-container *ngIf="isFirstColumn && isGroup">
339
+ <b>{{ value }}</b>
340
+ <span class="ug-group-count" *ngIf="row?.type === 'group'">({{ $any(row).childCount || 0 }})</span>
341
+ </ng-container>
342
+ <ng-container *ngIf="isGroupFooter">
343
+ <b *ngIf="isFirstColumn">
344
+ {{ $any(row).groupField === 'Grand Total' ? 'Grand Total' : 'Total' }}
345
+ </b>
346
+ <span *ngIf="!isFirstColumn">{{ formattedFooterValue }}</span>
347
+ </ng-container>
348
+ <ng-container *ngIf="!(isFirstColumn && isGroup) && !isGroupFooter">
349
+ {{ value }}
350
+ </ng-container>
351
+ </div>
352
+ </ng-template>
353
+ </div>
354
+ `, isInline: true, styles: ["ug-cell{display:flex;align-items:center;padding:0 16px;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;flex-grow:1;white-space:nowrap;box-sizing:border-box;height:100%}.ug-cell-wrapper{display:flex;align-items:center;width:100%;height:100%;overflow:hidden}.ug-cell-checkbox{margin-right:12px;display:flex;align-items:center}.ug-cell-checkbox input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color)}.ug-cell-content{overflow:hidden;text-overflow:ellipsis;width:100%;display:flex;align-items:center;gap:6px}.ug-group-caret{cursor:pointer;display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:4px;color:var(--ug-text-color);opacity:.7;transition:opacity .2s}.ug-group-caret:hover{opacity:1}.ug-group-count{font-size:.85em;opacity:.6}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
355
+ }
356
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: CellComponent, decorators: [{
357
+ type: Component,
358
+ args: [{ selector: 'ug-cell', standalone: true, imports: [CommonModule], template: `
359
+ <div class="ug-cell-wrapper" [style.padding-left.px]="isFirstColumn && row.level ? row.level * 20 : 0">
360
+
361
+ <!-- Group Expand/Collapse Caret -->
362
+ <div
363
+ class="ug-group-caret"
364
+ *ngIf="isFirstColumn && isGroup"
365
+ (click)="onGroupToggleClick($event)">
366
+ <svg *ngIf="groupExpanded" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
367
+ <svg *ngIf="!groupExpanded" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
368
+ </div>
369
+
370
+ <div class="ug-cell-checkbox" *ngIf="column.checkboxSelection && row.type !== 'group-footer'" (click)="onCheckboxClick($event)">
371
+ <input type="checkbox" [checked]="isSelected" />
372
+ </div>
373
+ <ng-container *ngIf="column.cellTemplate; else defaultCell">
374
+ <ng-container *ngTemplateOutlet="column.cellTemplate; context: { $implicit: row?.data, row: row, column: column }"></ng-container>
375
+ </ng-container>
376
+ <ng-template #defaultCell>
377
+ <div class="ug-cell-content" [title]="value">
378
+ <ng-container *ngIf="isFirstColumn && isGroup">
379
+ <b>{{ value }}</b>
380
+ <span class="ug-group-count" *ngIf="row?.type === 'group'">({{ $any(row).childCount || 0 }})</span>
381
+ </ng-container>
382
+ <ng-container *ngIf="isGroupFooter">
383
+ <b *ngIf="isFirstColumn">
384
+ {{ $any(row).groupField === 'Grand Total' ? 'Grand Total' : 'Total' }}
385
+ </b>
386
+ <span *ngIf="!isFirstColumn">{{ formattedFooterValue }}</span>
387
+ </ng-container>
388
+ <ng-container *ngIf="!(isFirstColumn && isGroup) && !isGroupFooter">
389
+ {{ value }}
390
+ </ng-container>
391
+ </div>
392
+ </ng-template>
393
+ </div>
394
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["ug-cell{display:flex;align-items:center;padding:0 16px;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;flex-grow:1;white-space:nowrap;box-sizing:border-box;height:100%}.ug-cell-wrapper{display:flex;align-items:center;width:100%;height:100%;overflow:hidden}.ug-cell-checkbox{margin-right:12px;display:flex;align-items:center}.ug-cell-checkbox input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color)}.ug-cell-content{overflow:hidden;text-overflow:ellipsis;width:100%;display:flex;align-items:center;gap:6px}.ug-group-caret{cursor:pointer;display:flex;align-items:center;justify-content:center;width:20px;height:20px;margin-right:4px;color:var(--ug-text-color);opacity:.7;transition:opacity .2s}.ug-group-caret:hover{opacity:1}.ug-group-count{font-size:.85em;opacity:.6}\n"] }]
395
+ }], propDecorators: { column: [{
396
+ type: Input
397
+ }], row: [{
398
+ type: Input
399
+ }], isSelected: [{
400
+ type: Input
401
+ }], isFirstColumn: [{
402
+ type: Input
403
+ }], groupExpanded: [{
404
+ type: Input
405
+ }], groupToggleClicked: [{
406
+ type: Output
407
+ }] } });
408
+
409
+ class RowComponent {
410
+ columns = [];
411
+ row;
412
+ rowHeight = 42;
413
+ isExpanded = false;
414
+ selectionVersion = 0;
415
+ groupToggled = new EventEmitter();
416
+ get isSelected() {
417
+ return this.row?.selected;
418
+ }
419
+ get isGroupFooter() {
420
+ return this.row?.type === 'group-footer';
421
+ }
422
+ get height() {
423
+ return this.rowHeight;
424
+ }
425
+ trackByField(index, col) {
426
+ return col.field;
427
+ }
428
+ onGroupToggle() {
429
+ this.groupToggled.emit();
430
+ }
431
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: RowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
432
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: RowComponent, isStandalone: true, selector: "ug-row", inputs: { columns: "columns", row: "row", rowHeight: "rowHeight", isExpanded: "isExpanded", selectionVersion: "selectionVersion" }, outputs: { groupToggled: "groupToggled" }, host: { properties: { "class.ug-row-selected": "this.isSelected", "class.ug-row-group-footer": "this.isGroupFooter", "style.height.px": "this.height" } }, ngImport: i0, template: `
433
+ <div class="ug-row-cells">
434
+ <ug-cell
435
+ *ngFor="let col of columns; let isFirst = first; trackBy: trackByField"
436
+ [column]="col"
437
+ [row]="row"
438
+ [isSelected]="$any(row)?.selected"
439
+ [isFirstColumn]="isFirst"
440
+ [groupExpanded]="isExpanded"
441
+ (groupToggleClicked)="onGroupToggle()"
442
+ [style.width.px]="col.width || 200"
443
+ [style.min-width.px]="col.minWidth"
444
+ [style.max-width.px]="col.maxWidth"
445
+ [class.ug-cell-hide]="col.hide"
446
+ [class.ug-pinned-left]="col.pinned === 'left'"
447
+ [class.ug-pinned-right]="col.pinned === 'right'"
448
+ [style.left.px]="col._leftOffset"
449
+ [style.right.px]="col._rightOffset">
450
+ </ug-cell>
451
+ </div>
452
+ `, isInline: true, styles: ["ug-row{display:inline-flex;min-width:100%;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-row-bg);box-sizing:border-box;transition:background-color .2s}ug-row:hover{background-color:var(--ug-row-hover-bg)}ug-row.ug-row-selected{background-color:var(--ug-row-selected-bg)}ug-row.ug-row-group-footer{background-color:var(--ug-group-footer-bg, #f8f9fa);font-weight:600;border-top:1px solid var(--ug-border-color);border-bottom:2px solid var(--ug-border-color)}ug-row.ug-row-group-footer:hover{background-color:var(--ug-group-footer-bg, #f8f9fa)}.ug-row-cells{display:flex;width:100%;height:100%}.ug-cell-hide{display:none!important}ug-cell.ug-pinned-left{position:sticky;z-index:1;border-right:1px solid var(--ug-border-color);background-color:inherit}ug-cell.ug-pinned-right{position:sticky;z-index:1;border-left:1px solid var(--ug-border-color);background-color:inherit}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: CellComponent, selector: "ug-cell", inputs: ["column", "row", "isSelected", "isFirstColumn", "groupExpanded"], outputs: ["groupToggleClicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
453
+ }
454
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: RowComponent, decorators: [{
455
+ type: Component,
456
+ args: [{ selector: 'ug-row', standalone: true, imports: [CommonModule, CellComponent], template: `
457
+ <div class="ug-row-cells">
458
+ <ug-cell
459
+ *ngFor="let col of columns; let isFirst = first; trackBy: trackByField"
460
+ [column]="col"
461
+ [row]="row"
462
+ [isSelected]="$any(row)?.selected"
463
+ [isFirstColumn]="isFirst"
464
+ [groupExpanded]="isExpanded"
465
+ (groupToggleClicked)="onGroupToggle()"
466
+ [style.width.px]="col.width || 200"
467
+ [style.min-width.px]="col.minWidth"
468
+ [style.max-width.px]="col.maxWidth"
469
+ [class.ug-cell-hide]="col.hide"
470
+ [class.ug-pinned-left]="col.pinned === 'left'"
471
+ [class.ug-pinned-right]="col.pinned === 'right'"
472
+ [style.left.px]="col._leftOffset"
473
+ [style.right.px]="col._rightOffset">
474
+ </ug-cell>
475
+ </div>
476
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["ug-row{display:inline-flex;min-width:100%;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-row-bg);box-sizing:border-box;transition:background-color .2s}ug-row:hover{background-color:var(--ug-row-hover-bg)}ug-row.ug-row-selected{background-color:var(--ug-row-selected-bg)}ug-row.ug-row-group-footer{background-color:var(--ug-group-footer-bg, #f8f9fa);font-weight:600;border-top:1px solid var(--ug-border-color);border-bottom:2px solid var(--ug-border-color)}ug-row.ug-row-group-footer:hover{background-color:var(--ug-group-footer-bg, #f8f9fa)}.ug-row-cells{display:flex;width:100%;height:100%}.ug-cell-hide{display:none!important}ug-cell.ug-pinned-left{position:sticky;z-index:1;border-right:1px solid var(--ug-border-color);background-color:inherit}ug-cell.ug-pinned-right{position:sticky;z-index:1;border-left:1px solid var(--ug-border-color);background-color:inherit}\n"] }]
477
+ }], propDecorators: { columns: [{
478
+ type: Input
479
+ }], row: [{
480
+ type: Input
481
+ }], rowHeight: [{
482
+ type: Input
483
+ }], isExpanded: [{
484
+ type: Input
485
+ }], selectionVersion: [{
486
+ type: Input
487
+ }], groupToggled: [{
488
+ type: Output
489
+ }], isSelected: [{
490
+ type: HostBinding,
491
+ args: ['class.ug-row-selected']
492
+ }], isGroupFooter: [{
493
+ type: HostBinding,
494
+ args: ['class.ug-row-group-footer']
495
+ }], height: [{
496
+ type: HostBinding,
497
+ args: ['style.height.px']
498
+ }] } });
499
+
500
+ class PaginationComponent {
501
+ totalCount = 0;
502
+ pageSize = 100;
503
+ currentPage = 1;
504
+ pageChanged = new EventEmitter();
505
+ pageSizeChanged = new EventEmitter(); // Added new output
506
+ get showControls() { return true; }
507
+ get totalPages() { return Math.max(1, Math.ceil(this.totalCount / this.pageSize)); }
508
+ get isFirstPage() { return this.currentPage <= 1; }
509
+ // Provide for infinite scrolling where total count might not be known immediately
510
+ get isLastPage() { return this.totalCount ? this.currentPage >= this.totalPages : false; }
511
+ get startRow() { return this.totalCount === 0 && this.currentPage === 1 ? 0 : (this.currentPage - 1) * this.pageSize + 1; }
512
+ get endRow() { return this.totalCount ? Math.min(this.currentPage * this.pageSize, this.totalCount) : this.currentPage * this.pageSize; }
513
+ onPrev() {
514
+ if (!this.isFirstPage) {
515
+ this.pageChanged.emit(this.currentPage - 1);
516
+ }
517
+ }
518
+ onNext() {
519
+ if (!this.isLastPage) {
520
+ this.pageChanged.emit(this.currentPage + 1);
521
+ }
522
+ }
523
+ // Added new method
524
+ onPage(page) {
525
+ if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
526
+ this.pageChanged.emit(page);
527
+ }
528
+ }
529
+ get isAllSelectedSize() {
530
+ return this.pageSize === this.totalCount && this.totalCount > 0;
531
+ }
532
+ // Added new method
533
+ onPageSizeChange(size) {
534
+ if (size === 'All') {
535
+ this.pageSizeChanged.emit(this.totalCount);
536
+ }
537
+ else {
538
+ this.pageSizeChanged.emit(Number(size));
539
+ }
540
+ }
541
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PaginationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
542
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: PaginationComponent, isStandalone: true, selector: "ug-pagination", inputs: { totalCount: "totalCount", pageSize: "pageSize", currentPage: "currentPage" }, outputs: { pageChanged: "pageChanged", pageSizeChanged: "pageSizeChanged" }, ngImport: i0, template: `
543
+ <div class="ug-pagination-container">
544
+ <div class="ug-pagination-left">
545
+ <!-- Optional space for additional footer info -->
546
+ </div>
547
+ <div class="ug-pagination-right" *ngIf="showControls">
548
+ <div class="ug-page-size">
549
+ <span>Page Size:</span>
550
+ <select [ngModel]="isAllSelectedSize ? 'All' : pageSize" (ngModelChange)="onPageSizeChange($event)">
551
+ <option [ngValue]="10">10</option>
552
+ <option [ngValue]="25">25</option>
553
+ <option [ngValue]="50">50</option>
554
+ <option [ngValue]="100">100</option>
555
+ <option [ngValue]="500">500</option>
556
+ <option [ngValue]="'All'">All</option>
557
+ </select>
558
+ </div>
559
+ <div class="ug-pagination-info">
560
+ {{ startRow }} to {{ endRow }} of {{ totalCount || 'many' }}
561
+ </div>
562
+ <div class="ug-pagination-controls">
563
+ <button [disabled]="isFirstPage" (click)="onPage(1)" title="First Page">
564
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 17l-5-5 5-5M18 17l-5-5 5-5M6 17V7"></path></svg>
565
+ </button>
566
+ <button [disabled]="isFirstPage" (click)="onPrev()" title="Previous Page">
567
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"></path></svg>
568
+ </button>
569
+ <span class="ug-page-info">Page {{ currentPage }} of {{ totalPages }}</span>
570
+ <button [disabled]="isLastPage" (click)="onNext()" title="Next Page">
571
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"></path></svg>
572
+ </button>
573
+ <button [disabled]="isLastPage" (click)="onPage(totalPages)" title="Last Page">
574
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 17l5-5-5-5M6 17l5-5-5-5M18 17V7"></path></svg>
575
+ </button>
576
+ </div>
577
+ </div>
578
+ </div>
579
+ `, isInline: true, styles: ["ug-pagination{display:block;border-top:1px solid var(--ug-border-color);background-color:var(--ug-footer-bg);color:var(--ug-footer-color);font-size:var(--ug-font-size)}.ug-pagination-container{display:flex;justify-content:space-between;align-items:center;height:48px;padding:0 16px}.ug-pagination-right{display:flex;align-items:center;gap:24px}.ug-page-size{display:flex;align-items:center;gap:8px}.ug-page-size select{border:1px solid var(--ug-border-color);border-radius:4px;padding:4px 8px;background-color:var(--ug-bg-color);color:var(--ug-footer-color);cursor:pointer;font-family:var(--ug-font-family)}.ug-pagination-info{font-weight:var(--ug-header-font-weight)}.ug-pagination-controls{display:flex;align-items:center;gap:8px}.ug-pagination-controls button{background:none;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--ug-icon-color);padding:4px;border-radius:4px}.ug-pagination-controls button:disabled{opacity:.3;cursor:not-allowed}.ug-pagination-controls button:not(:disabled):hover{background-color:var(--ug-row-hover-bg)}.ug-page-info{margin:0 4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
580
+ }
581
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: PaginationComponent, decorators: [{
582
+ type: Component,
583
+ args: [{ selector: 'ug-pagination', standalone: true, imports: [CommonModule, FormsModule], template: `
584
+ <div class="ug-pagination-container">
585
+ <div class="ug-pagination-left">
586
+ <!-- Optional space for additional footer info -->
587
+ </div>
588
+ <div class="ug-pagination-right" *ngIf="showControls">
589
+ <div class="ug-page-size">
590
+ <span>Page Size:</span>
591
+ <select [ngModel]="isAllSelectedSize ? 'All' : pageSize" (ngModelChange)="onPageSizeChange($event)">
592
+ <option [ngValue]="10">10</option>
593
+ <option [ngValue]="25">25</option>
594
+ <option [ngValue]="50">50</option>
595
+ <option [ngValue]="100">100</option>
596
+ <option [ngValue]="500">500</option>
597
+ <option [ngValue]="'All'">All</option>
598
+ </select>
599
+ </div>
600
+ <div class="ug-pagination-info">
601
+ {{ startRow }} to {{ endRow }} of {{ totalCount || 'many' }}
602
+ </div>
603
+ <div class="ug-pagination-controls">
604
+ <button [disabled]="isFirstPage" (click)="onPage(1)" title="First Page">
605
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 17l-5-5 5-5M18 17l-5-5 5-5M6 17V7"></path></svg>
606
+ </button>
607
+ <button [disabled]="isFirstPage" (click)="onPrev()" title="Previous Page">
608
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"></path></svg>
609
+ </button>
610
+ <span class="ug-page-info">Page {{ currentPage }} of {{ totalPages }}</span>
611
+ <button [disabled]="isLastPage" (click)="onNext()" title="Next Page">
612
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"></path></svg>
613
+ </button>
614
+ <button [disabled]="isLastPage" (click)="onPage(totalPages)" title="Last Page">
615
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 17l5-5-5-5M6 17l5-5-5-5M18 17V7"></path></svg>
616
+ </button>
617
+ </div>
618
+ </div>
619
+ </div>
620
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["ug-pagination{display:block;border-top:1px solid var(--ug-border-color);background-color:var(--ug-footer-bg);color:var(--ug-footer-color);font-size:var(--ug-font-size)}.ug-pagination-container{display:flex;justify-content:space-between;align-items:center;height:48px;padding:0 16px}.ug-pagination-right{display:flex;align-items:center;gap:24px}.ug-page-size{display:flex;align-items:center;gap:8px}.ug-page-size select{border:1px solid var(--ug-border-color);border-radius:4px;padding:4px 8px;background-color:var(--ug-bg-color);color:var(--ug-footer-color);cursor:pointer;font-family:var(--ug-font-family)}.ug-pagination-info{font-weight:var(--ug-header-font-weight)}.ug-pagination-controls{display:flex;align-items:center;gap:8px}.ug-pagination-controls button{background:none;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--ug-icon-color);padding:4px;border-radius:4px}.ug-pagination-controls button:disabled{opacity:.3;cursor:not-allowed}.ug-pagination-controls button:not(:disabled):hover{background-color:var(--ug-row-hover-bg)}.ug-page-info{margin:0 4px}\n"] }]
621
+ }], propDecorators: { totalCount: [{
622
+ type: Input
623
+ }], pageSize: [{
624
+ type: Input
625
+ }], currentPage: [{
626
+ type: Input
627
+ }], pageChanged: [{
628
+ type: Output
629
+ }], pageSizeChanged: [{
630
+ type: Output
631
+ }] } });
632
+
633
+ class HeaderMenuComponent {
634
+ el;
635
+ column;
636
+ isOpen = false;
637
+ position = { x: 0, y: 0 };
638
+ groupModel = [];
639
+ closeMenu = new EventEmitter();
640
+ sort = new EventEmitter();
641
+ pin = new EventEmitter();
642
+ autosize = new EventEmitter();
643
+ group = new EventEmitter();
644
+ chooseColumns = new EventEmitter();
645
+ showPinSubmenu = false;
646
+ get isGrouped() {
647
+ return !!this.column && this.groupModel.includes(this.column.field);
648
+ }
649
+ constructor(el) {
650
+ this.el = el;
651
+ }
652
+ onClose() {
653
+ this.closeMenu.emit();
654
+ }
655
+ onSort(direction) {
656
+ if (this.column) {
657
+ this.sort.emit({ field: this.column.field, direction });
658
+ }
659
+ this.onClose();
660
+ }
661
+ onPin(pinned) {
662
+ if (this.column) {
663
+ this.pin.emit({ field: this.column.field, pinned });
664
+ }
665
+ this.onClose();
666
+ }
667
+ onAutosize(all) {
668
+ if (this.column) {
669
+ this.autosize.emit({ field: this.column.field, all });
670
+ }
671
+ this.onClose();
672
+ }
673
+ onGroup() {
674
+ if (this.column) {
675
+ this.group.emit({ field: this.column.field });
676
+ }
677
+ this.onClose();
678
+ }
679
+ onChooseColumns() {
680
+ this.chooseColumns.emit();
681
+ this.onClose();
682
+ }
683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: HeaderMenuComponent, isStandalone: true, selector: "ug-header-menu", inputs: { column: "column", isOpen: "isOpen", position: "position", groupModel: "groupModel" }, outputs: { closeMenu: "closeMenu", sort: "sort", pin: "pin", autosize: "autosize", group: "group", chooseColumns: "chooseColumns" }, ngImport: i0, template: `
685
+ <div class="ug-header-menu-backdrop" *ngIf="isOpen" (click)="onClose()"></div>
686
+ <div class="ug-header-menu-panel" *ngIf="isOpen" [style.top.px]="position.y" [style.left.px]="position.x">
687
+
688
+ <!-- Sorting -->
689
+ <div class="ug-menu-item" (click)="onSort('asc')">
690
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
691
+ <span>Sort Ascending</span>
692
+ </div>
693
+ <div class="ug-menu-item" (click)="onSort('desc')">
694
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
695
+ <span>Sort Descending</span>
696
+ </div>
697
+
698
+ <div class="ug-menu-separator"></div>
699
+
700
+ <!-- Pinning (Submenu trigger) -->
701
+ <div class="ug-menu-item ug-has-submenu" (mouseenter)="showPinSubmenu = true" (mouseleave)="showPinSubmenu = false">
702
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 10l-4 4-4-4"/></svg>
703
+ <span>Pin Column</span>
704
+ <svg class="ug-menu-arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
705
+
706
+ <div class="ug-submenu" *ngIf="showPinSubmenu">
707
+ <div class="ug-menu-item" (click)="onPin(null)">
708
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned ? 'hidden' : 'visible'"><path d="M20 6L9 17l-5-5"/></svg>
709
+ <span>No Pin</span>
710
+ </div>
711
+ <div class="ug-menu-item" (click)="onPin('left')">
712
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned === 'left' ? 'visible' : 'hidden'"><path d="M20 6L9 17l-5-5"/></svg>
713
+ <span>Pin Left</span>
714
+ </div>
715
+ <div class="ug-menu-item" (click)="onPin('right')">
716
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned === 'right' ? 'visible' : 'hidden'"><path d="M20 6L9 17l-5-5"/></svg>
717
+ <span>Pin Right</span>
718
+ </div>
719
+ </div>
720
+ </div>
721
+
722
+ <div class="ug-menu-separator"></div>
723
+
724
+ <!-- Autosize -->
725
+ <div class="ug-menu-item" (click)="onAutosize(false)">
726
+ <span class="ug-menu-text-only">Autosize This Column</span>
727
+ </div>
728
+ <div class="ug-menu-item" (click)="onAutosize(true)">
729
+ <span class="ug-menu-text-only">Autosize All Columns</span>
730
+ </div>
731
+
732
+ <div class="ug-menu-separator"></div>
733
+
734
+ <!-- Grouping -->
735
+ <div class="ug-menu-item" (click)="onGroup()">
736
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 3H3v18h18V3zM3 9h18M9 21V9"/></svg>
737
+ <span>{{ isGrouped ? 'Un-Group by' : 'Group by' }} {{ column?.headerName || column?.field }}</span>
738
+ </div>
739
+
740
+ <div class="ug-menu-separator"></div>
741
+
742
+ <!-- Choose Columns -->
743
+ <div class="ug-menu-item" (click)="onChooseColumns()">
744
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><path d="M9 3v18"/></svg>
745
+ <span>Choose Columns</span>
746
+ </div>
747
+
748
+ </div>
749
+ `, isInline: true, styles: [".ug-header-menu-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999}.ug-header-menu-panel{position:fixed;z-index:1000;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:220px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color)}.ug-menu-item{display:flex;align-items:center;padding:8px 16px;cursor:pointer;position:relative;transition:background-color .15s ease;-webkit-user-select:none;user-select:none}.ug-menu-item:hover{background-color:var(--ug-row-hover-bg)}.ug-menu-icon{margin-right:12px;color:var(--ug-icon-color)}.ug-menu-text-only{margin-left:26px}.ug-menu-separator{height:1px;background-color:var(--ug-border-color);margin:4px 0}.ug-has-submenu{display:flex;justify-content:space-between}.ug-has-submenu span{flex:1}.ug-menu-arrow{color:var(--ug-icon-color);margin-left:8px}.ug-submenu{position:absolute;top:-9px;left:100%;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:150px;z-index:1001}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], encapsulation: i0.ViewEncapsulation.None });
750
+ }
751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderMenuComponent, decorators: [{
752
+ type: Component,
753
+ args: [{ selector: 'ug-header-menu', standalone: true, imports: [CommonModule], template: `
754
+ <div class="ug-header-menu-backdrop" *ngIf="isOpen" (click)="onClose()"></div>
755
+ <div class="ug-header-menu-panel" *ngIf="isOpen" [style.top.px]="position.y" [style.left.px]="position.x">
756
+
757
+ <!-- Sorting -->
758
+ <div class="ug-menu-item" (click)="onSort('asc')">
759
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
760
+ <span>Sort Ascending</span>
761
+ </div>
762
+ <div class="ug-menu-item" (click)="onSort('desc')">
763
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12l7 7 7-7"/></svg>
764
+ <span>Sort Descending</span>
765
+ </div>
766
+
767
+ <div class="ug-menu-separator"></div>
768
+
769
+ <!-- Pinning (Submenu trigger) -->
770
+ <div class="ug-menu-item ug-has-submenu" (mouseenter)="showPinSubmenu = true" (mouseleave)="showPinSubmenu = false">
771
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 10l-4 4-4-4"/></svg>
772
+ <span>Pin Column</span>
773
+ <svg class="ug-menu-arrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg>
774
+
775
+ <div class="ug-submenu" *ngIf="showPinSubmenu">
776
+ <div class="ug-menu-item" (click)="onPin(null)">
777
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned ? 'hidden' : 'visible'"><path d="M20 6L9 17l-5-5"/></svg>
778
+ <span>No Pin</span>
779
+ </div>
780
+ <div class="ug-menu-item" (click)="onPin('left')">
781
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned === 'left' ? 'visible' : 'hidden'"><path d="M20 6L9 17l-5-5"/></svg>
782
+ <span>Pin Left</span>
783
+ </div>
784
+ <div class="ug-menu-item" (click)="onPin('right')">
785
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" [style.visibility]="column?.pinned === 'right' ? 'visible' : 'hidden'"><path d="M20 6L9 17l-5-5"/></svg>
786
+ <span>Pin Right</span>
787
+ </div>
788
+ </div>
789
+ </div>
790
+
791
+ <div class="ug-menu-separator"></div>
792
+
793
+ <!-- Autosize -->
794
+ <div class="ug-menu-item" (click)="onAutosize(false)">
795
+ <span class="ug-menu-text-only">Autosize This Column</span>
796
+ </div>
797
+ <div class="ug-menu-item" (click)="onAutosize(true)">
798
+ <span class="ug-menu-text-only">Autosize All Columns</span>
799
+ </div>
800
+
801
+ <div class="ug-menu-separator"></div>
802
+
803
+ <!-- Grouping -->
804
+ <div class="ug-menu-item" (click)="onGroup()">
805
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 3H3v18h18V3zM3 9h18M9 21V9"/></svg>
806
+ <span>{{ isGrouped ? 'Un-Group by' : 'Group by' }} {{ column?.headerName || column?.field }}</span>
807
+ </div>
808
+
809
+ <div class="ug-menu-separator"></div>
810
+
811
+ <!-- Choose Columns -->
812
+ <div class="ug-menu-item" (click)="onChooseColumns()">
813
+ <svg class="ug-menu-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><path d="M9 3v18"/></svg>
814
+ <span>Choose Columns</span>
815
+ </div>
816
+
817
+ </div>
818
+ `, encapsulation: ViewEncapsulation.None, styles: [".ug-header-menu-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999}.ug-header-menu-panel{position:fixed;z-index:1000;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:220px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color)}.ug-menu-item{display:flex;align-items:center;padding:8px 16px;cursor:pointer;position:relative;transition:background-color .15s ease;-webkit-user-select:none;user-select:none}.ug-menu-item:hover{background-color:var(--ug-row-hover-bg)}.ug-menu-icon{margin-right:12px;color:var(--ug-icon-color)}.ug-menu-text-only{margin-left:26px}.ug-menu-separator{height:1px;background-color:var(--ug-border-color);margin:4px 0}.ug-has-submenu{display:flex;justify-content:space-between}.ug-has-submenu span{flex:1}.ug-menu-arrow{color:var(--ug-icon-color);margin-left:8px}.ug-submenu{position:absolute;top:-9px;left:100%;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:150px;z-index:1001}\n"] }]
819
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { column: [{
820
+ type: Input
821
+ }], isOpen: [{
822
+ type: Input
823
+ }], position: [{
824
+ type: Input
825
+ }], groupModel: [{
826
+ type: Input
827
+ }], closeMenu: [{
828
+ type: Output
829
+ }], sort: [{
830
+ type: Output
831
+ }], pin: [{
832
+ type: Output
833
+ }], autosize: [{
834
+ type: Output
835
+ }], group: [{
836
+ type: Output
837
+ }], chooseColumns: [{
838
+ type: Output
839
+ }] } });
840
+
841
+ class ChooseColumnsComponent {
842
+ columns = [];
843
+ columnsChanged = new EventEmitter();
844
+ closePanel = new EventEmitter();
845
+ searchQuery = '';
846
+ onClose() {
847
+ this.closePanel.emit();
848
+ }
849
+ matchesSearch(col) {
850
+ if (!this.searchQuery)
851
+ return true;
852
+ const name = (col.headerName || col.field || '').toLowerCase();
853
+ return name.includes(this.searchQuery.toLowerCase());
854
+ }
855
+ toggleVisibility(col) {
856
+ col.hide = !col.hide;
857
+ this.emitChange();
858
+ }
859
+ onDrop(event) {
860
+ // Because of the *ngIf matching search, the index in the drag drop event might not match the original array
861
+ // if the user is filtering. We will only allow reordering if search is empty to be safe,
862
+ // or we have to map visual index to actual index.
863
+ if (this.searchQuery) {
864
+ // Find actual indices
865
+ const visibleCols = this.columns.filter(c => this.matchesSearch(c));
866
+ const movedItem = visibleCols[event.previousIndex];
867
+ const targetItem = visibleCols[event.currentIndex];
868
+ const actualPrevIdx = this.columns.indexOf(movedItem);
869
+ const actualCurrIdx = this.columns.indexOf(targetItem);
870
+ moveItemInArray(this.columns, actualPrevIdx, actualCurrIdx);
871
+ }
872
+ else {
873
+ moveItemInArray(this.columns, event.previousIndex, event.currentIndex);
874
+ }
875
+ this.emitChange();
876
+ }
877
+ emitChange() {
878
+ this.columnsChanged.emit([...this.columns]);
879
+ }
880
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChooseColumnsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
881
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: ChooseColumnsComponent, isStandalone: true, selector: "ug-choose-columns", inputs: { columns: "columns" }, outputs: { columnsChanged: "columnsChanged", closePanel: "closePanel" }, ngImport: i0, template: `
882
+ <div class="ug-choose-columns-panel">
883
+
884
+ <div class="ug-cc-header">
885
+ <span class="ug-cc-title">Choose Columns</span>
886
+ <button class="ug-cc-close" (click)="onClose()">
887
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
888
+ </button>
889
+ </div>
890
+
891
+ <div class="ug-cc-search">
892
+ <div class="ug-cc-search-icon">
893
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
894
+ </div>
895
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" />
896
+ </div>
897
+
898
+ <div class="ug-cc-list" cdkDropList (cdkDropListDropped)="onDrop($event)">
899
+ <ng-container *ngFor="let col of columns">
900
+ <div class="ug-cc-item" cdkDrag *ngIf="matchesSearch(col)">
901
+
902
+ <label class="ug-cc-label">
903
+ <input type="checkbox" [checked]="!col.hide" (change)="toggleVisibility(col)" />
904
+ <span class="ug-cc-drag-handle" cdkDragHandle>
905
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 6h.01M16 6h.01M8 12h.01M16 12h.01M8 18h.01M16 18h.01"/></svg>
906
+ </span>
907
+ <span class="ug-cc-name">{{ col.headerName || col.field }}</span>
908
+ </label>
909
+
910
+ </div>
911
+ </ng-container>
912
+ </div>
913
+
914
+ </div>
915
+ `, isInline: true, styles: [".ug-choose-columns-panel{display:flex;flex-direction:column;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 12px -1px #00000026,0 2px 8px -2px #0000001a;width:250px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color);overflow:hidden}.ug-cc-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg)}.ug-cc-title{font-weight:var(--ug-header-font-weight);color:var(--ug-header-color)}.ug-cc-close{background:none;border:none;cursor:pointer;color:var(--ug-icon-color);padding:4px;display:flex;align-items:center;justify-content:center;border-radius:4px}.ug-cc-close:hover{background-color:var(--ug-row-hover-bg)}.ug-cc-search{padding:8px;border-bottom:1px solid var(--ug-border-color);position:relative;display:flex;align-items:center}.ug-cc-search-icon{position:absolute;left:16px;color:var(--ug-icon-color);display:flex}.ug-cc-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:inherit;font-size:inherit;background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-cc-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-cc-list{max-height:250px;overflow-y:auto;padding:8px 0}.ug-cc-item{display:flex;align-items:center;padding:4px 16px}.ug-cc-label{display:flex;align-items:center;width:100%;cursor:pointer}.ug-cc-label input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color);margin-right:12px}.ug-cc-drag-handle{cursor:grab;color:var(--ug-icon-color);margin-right:12px;display:flex}.ug-cc-drag-handle:active{cursor:grabbing}.ug-cc-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cdk-drag-preview{box-sizing:border-box;background-color:var(--ug-bg-color);border:1px solid var(--ug-primary-color);box-shadow:0 4px 6px -1px #0000001a;display:flex;align-items:center;padding:4px 16px}.cdk-drag-placeholder{opacity:.3}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
916
+ }
917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ChooseColumnsComponent, decorators: [{
918
+ type: Component,
919
+ args: [{ selector: 'ug-choose-columns', standalone: true, imports: [CommonModule, FormsModule, DragDropModule], template: `
920
+ <div class="ug-choose-columns-panel">
921
+
922
+ <div class="ug-cc-header">
923
+ <span class="ug-cc-title">Choose Columns</span>
924
+ <button class="ug-cc-close" (click)="onClose()">
925
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>
926
+ </button>
927
+ </div>
928
+
929
+ <div class="ug-cc-search">
930
+ <div class="ug-cc-search-icon">
931
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
932
+ </div>
933
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" />
934
+ </div>
935
+
936
+ <div class="ug-cc-list" cdkDropList (cdkDropListDropped)="onDrop($event)">
937
+ <ng-container *ngFor="let col of columns">
938
+ <div class="ug-cc-item" cdkDrag *ngIf="matchesSearch(col)">
939
+
940
+ <label class="ug-cc-label">
941
+ <input type="checkbox" [checked]="!col.hide" (change)="toggleVisibility(col)" />
942
+ <span class="ug-cc-drag-handle" cdkDragHandle>
943
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 6h.01M16 6h.01M8 12h.01M16 12h.01M8 18h.01M16 18h.01"/></svg>
944
+ </span>
945
+ <span class="ug-cc-name">{{ col.headerName || col.field }}</span>
946
+ </label>
947
+
948
+ </div>
949
+ </ng-container>
950
+ </div>
951
+
952
+ </div>
953
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".ug-choose-columns-panel{display:flex;flex-direction:column;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 12px -1px #00000026,0 2px 8px -2px #0000001a;width:250px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color);overflow:hidden}.ug-cc-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg)}.ug-cc-title{font-weight:var(--ug-header-font-weight);color:var(--ug-header-color)}.ug-cc-close{background:none;border:none;cursor:pointer;color:var(--ug-icon-color);padding:4px;display:flex;align-items:center;justify-content:center;border-radius:4px}.ug-cc-close:hover{background-color:var(--ug-row-hover-bg)}.ug-cc-search{padding:8px;border-bottom:1px solid var(--ug-border-color);position:relative;display:flex;align-items:center}.ug-cc-search-icon{position:absolute;left:16px;color:var(--ug-icon-color);display:flex}.ug-cc-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:inherit;font-size:inherit;background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-cc-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-cc-list{max-height:250px;overflow-y:auto;padding:8px 0}.ug-cc-item{display:flex;align-items:center;padding:4px 16px}.ug-cc-label{display:flex;align-items:center;width:100%;cursor:pointer}.ug-cc-label input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color);margin-right:12px}.ug-cc-drag-handle{cursor:grab;color:var(--ug-icon-color);margin-right:12px;display:flex}.ug-cc-drag-handle:active{cursor:grabbing}.ug-cc-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cdk-drag-preview{box-sizing:border-box;background-color:var(--ug-bg-color);border:1px solid var(--ug-primary-color);box-shadow:0 4px 6px -1px #0000001a;display:flex;align-items:center;padding:4px 16px}.cdk-drag-placeholder{opacity:.3}\n"] }]
954
+ }], propDecorators: { columns: [{
955
+ type: Input
956
+ }], columnsChanged: [{
957
+ type: Output
958
+ }], closePanel: [{
959
+ type: Output
960
+ }] } });
961
+
962
+ class HeaderFilterComponent {
963
+ column;
964
+ isOpen = false;
965
+ position = { x: 0, y: 0 };
966
+ // All unique values extracted from the dataset
967
+ uniqueValues = [];
968
+ // The currently applied active filter Set for this column
969
+ activeFilterSet = new Set();
970
+ closeFilter = new EventEmitter();
971
+ filterApplied = new EventEmitter();
972
+ searchQuery = '';
973
+ displayValues = [];
974
+ // Local working copy of selected values while the menu is open
975
+ selectedValues = new Set();
976
+ ngOnChanges(changes) {
977
+ if (changes['isOpen'] && this.isOpen) {
978
+ // Reset query and clone the active filters
979
+ this.searchQuery = '';
980
+ this.selectedValues = new Set(this.activeFilterSet);
981
+ // If the active filter was totally empty (meaning NO filter was ever applied),
982
+ // Excel defaults to having EVERYTHING checked initially.
983
+ if (this.activeFilterSet.size === 0) {
984
+ this.uniqueValues.forEach(v => this.selectedValues.add(v));
985
+ }
986
+ this.applySearch();
987
+ }
988
+ if (changes['uniqueValues']) {
989
+ this.applySearch();
990
+ }
991
+ }
992
+ onClose() {
993
+ this.closeFilter.emit();
994
+ }
995
+ onSearchChanged() {
996
+ this.applySearch();
997
+ }
998
+ applySearch() {
999
+ if (!this.searchQuery) {
1000
+ this.displayValues = [...this.uniqueValues];
1001
+ }
1002
+ else {
1003
+ const lowerQ = this.searchQuery.toLowerCase();
1004
+ this.displayValues = this.uniqueValues.filter(val => String(val).toLowerCase().includes(lowerQ));
1005
+ }
1006
+ }
1007
+ get isAllSelected() {
1008
+ if (this.displayValues.length === 0)
1009
+ return false;
1010
+ return this.displayValues.every(val => this.selectedValues.has(val));
1011
+ }
1012
+ toggleAll() {
1013
+ const targetState = !this.isAllSelected;
1014
+ if (targetState) {
1015
+ this.displayValues.forEach(val => this.selectedValues.add(val));
1016
+ }
1017
+ else {
1018
+ this.displayValues.forEach(val => this.selectedValues.delete(val));
1019
+ }
1020
+ this.emitFilter();
1021
+ }
1022
+ toggleValue(val) {
1023
+ if (this.selectedValues.has(val)) {
1024
+ this.selectedValues.delete(val);
1025
+ }
1026
+ else {
1027
+ this.selectedValues.add(val);
1028
+ }
1029
+ this.emitFilter();
1030
+ }
1031
+ emitFilter() {
1032
+ if (!this.column)
1033
+ return;
1034
+ // If "Select All" of all unique values is checked, we conceptually have NO filter.
1035
+ // E.g., if we select everything, we just clear the filter for performance.
1036
+ let finalSet = new Set(this.selectedValues);
1037
+ let isDefaultAllSelected = true;
1038
+ for (const val of this.uniqueValues) {
1039
+ if (!this.selectedValues.has(val)) {
1040
+ isDefaultAllSelected = false;
1041
+ break;
1042
+ }
1043
+ }
1044
+ if (isDefaultAllSelected) {
1045
+ finalSet = new Set(); // Empty means no filter
1046
+ }
1047
+ this.filterApplied.emit({ field: this.column.field, selected: finalSet });
1048
+ }
1049
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1050
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: HeaderFilterComponent, isStandalone: true, selector: "ug-header-filter", inputs: { column: "column", isOpen: "isOpen", position: "position", uniqueValues: "uniqueValues", activeFilterSet: "activeFilterSet" }, outputs: { closeFilter: "closeFilter", filterApplied: "filterApplied" }, usesOnChanges: true, ngImport: i0, template: `
1051
+ <div class="ug-header-filter-backdrop" *ngIf="isOpen" (click)="onClose()"></div>
1052
+ <div class="ug-header-filter-panel" *ngIf="isOpen" [style.top.px]="position.y" [style.left.px]="position.x">
1053
+
1054
+ <div class="ug-filter-search">
1055
+ <div class="ug-filter-search-icon">
1056
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1057
+ </div>
1058
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChanged()" />
1059
+ </div>
1060
+
1061
+ <div class="ug-filter-list">
1062
+ <!-- Select All Checkbox -->
1063
+ <label class="ug-filter-item ug-filter-select-all">
1064
+ <input type="checkbox" [checked]="isAllSelected" (change)="toggleAll()" />
1065
+ <span>(Select All)</span>
1066
+ </label>
1067
+
1068
+ <!-- Values Checkboxes -->
1069
+ <cdk-virtual-scroll-viewport itemSize="24" class="ug-filter-viewport">
1070
+ <label class="ug-filter-item" *cdkVirtualFor="let item of displayValues">
1071
+ <input type="checkbox" [checked]="selectedValues.has(item)" (change)="toggleValue(item)" />
1072
+ <span>{{ item === null || item === undefined || item === '' ? '(Blanks)' : item }}</span>
1073
+ </label>
1074
+ </cdk-virtual-scroll-viewport>
1075
+
1076
+ <div class="ug-filter-empty" *ngIf="displayValues.length === 0">
1077
+ No matches found
1078
+ </div>
1079
+ </div>
1080
+
1081
+ </div>
1082
+ `, isInline: true, styles: [".ug-header-filter-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999}.ug-header-filter-panel{position:fixed;z-index:1000;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:250px;max-width:300px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color);display:flex;flex-direction:column}.ug-filter-search{padding:0 12px 8px;border-bottom:1px solid var(--ug-border-color);position:relative;display:flex;align-items:center}.ug-filter-search-icon{position:absolute;left:20px;color:var(--ug-icon-color);display:flex}.ug-filter-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:inherit;font-size:inherit;background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-filter-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-filter-list{padding:8px 0}.ug-filter-viewport{height:200px;width:100%}.ug-filter-select-all{border-bottom:1px solid var(--ug-border-color);margin-bottom:4px}.ug-filter-item{display:flex;align-items:center;padding:4px 16px;cursor:pointer;-webkit-user-select:none;user-select:none}.ug-filter-item:hover{background-color:var(--ug-row-hover-bg)}.ug-filter-item input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color);margin-right:12px}.ug-filter-item span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ug-filter-empty{padding:16px;text-align:center;color:var(--ug-icon-color);font-style:italic}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3$1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3$1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3$1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1083
+ }
1084
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: HeaderFilterComponent, decorators: [{
1085
+ type: Component,
1086
+ args: [{ selector: 'ug-header-filter', standalone: true, imports: [CommonModule, FormsModule, ScrollingModule], template: `
1087
+ <div class="ug-header-filter-backdrop" *ngIf="isOpen" (click)="onClose()"></div>
1088
+ <div class="ug-header-filter-panel" *ngIf="isOpen" [style.top.px]="position.y" [style.left.px]="position.x">
1089
+
1090
+ <div class="ug-filter-search">
1091
+ <div class="ug-filter-search-icon">
1092
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1093
+ </div>
1094
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="onSearchChanged()" />
1095
+ </div>
1096
+
1097
+ <div class="ug-filter-list">
1098
+ <!-- Select All Checkbox -->
1099
+ <label class="ug-filter-item ug-filter-select-all">
1100
+ <input type="checkbox" [checked]="isAllSelected" (change)="toggleAll()" />
1101
+ <span>(Select All)</span>
1102
+ </label>
1103
+
1104
+ <!-- Values Checkboxes -->
1105
+ <cdk-virtual-scroll-viewport itemSize="24" class="ug-filter-viewport">
1106
+ <label class="ug-filter-item" *cdkVirtualFor="let item of displayValues">
1107
+ <input type="checkbox" [checked]="selectedValues.has(item)" (change)="toggleValue(item)" />
1108
+ <span>{{ item === null || item === undefined || item === '' ? '(Blanks)' : item }}</span>
1109
+ </label>
1110
+ </cdk-virtual-scroll-viewport>
1111
+
1112
+ <div class="ug-filter-empty" *ngIf="displayValues.length === 0">
1113
+ No matches found
1114
+ </div>
1115
+ </div>
1116
+
1117
+ </div>
1118
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".ug-header-filter-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:999}.ug-header-filter-panel{position:fixed;z-index:1000;background-color:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;padding:8px 0;min-width:250px;max-width:300px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color);display:flex;flex-direction:column}.ug-filter-search{padding:0 12px 8px;border-bottom:1px solid var(--ug-border-color);position:relative;display:flex;align-items:center}.ug-filter-search-icon{position:absolute;left:20px;color:var(--ug-icon-color);display:flex}.ug-filter-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:inherit;font-size:inherit;background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-filter-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-filter-list{padding:8px 0}.ug-filter-viewport{height:200px;width:100%}.ug-filter-select-all{border-bottom:1px solid var(--ug-border-color);margin-bottom:4px}.ug-filter-item{display:flex;align-items:center;padding:4px 16px;cursor:pointer;-webkit-user-select:none;user-select:none}.ug-filter-item:hover{background-color:var(--ug-row-hover-bg)}.ug-filter-item input[type=checkbox]{width:16px;height:16px;cursor:pointer;accent-color:var(--ug-primary-color);margin-right:12px}.ug-filter-item span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ug-filter-empty{padding:16px;text-align:center;color:var(--ug-icon-color);font-style:italic}\n"] }]
1119
+ }], propDecorators: { column: [{
1120
+ type: Input
1121
+ }], isOpen: [{
1122
+ type: Input
1123
+ }], position: [{
1124
+ type: Input
1125
+ }], uniqueValues: [{
1126
+ type: Input
1127
+ }], activeFilterSet: [{
1128
+ type: Input
1129
+ }], closeFilter: [{
1130
+ type: Output
1131
+ }], filterApplied: [{
1132
+ type: Output
1133
+ }] } });
1134
+
1135
+ class ColumnToolPanelComponent {
1136
+ columns = [];
1137
+ groupModel = [];
1138
+ valuesModel = [];
1139
+ columnsUpdated = new EventEmitter();
1140
+ groupModelChanged = new EventEmitter();
1141
+ valuesModelChanged = new EventEmitter();
1142
+ exportCsvClicked = new EventEmitter();
1143
+ displayColumns = [];
1144
+ groupColumns = [];
1145
+ valueColumns = [];
1146
+ searchQuery = '';
1147
+ pivotMode = false;
1148
+ openDropdownField = null;
1149
+ ngOnChanges(changes) {
1150
+ if (changes['columns']) {
1151
+ this.applySearch();
1152
+ }
1153
+ // Reconstruct groupColumns from persisted groupModel (survives tab switches)
1154
+ if (changes['columns'] || changes['groupModel']) {
1155
+ this.rebuildGroupColumns();
1156
+ }
1157
+ // Reconstruct valueColumns from persisted valuesModel (survives tab switches)
1158
+ if (changes['columns'] || changes['valuesModel']) {
1159
+ this.rebuildValueColumns();
1160
+ }
1161
+ }
1162
+ rebuildGroupColumns() {
1163
+ if (this.groupModel.length === 0) {
1164
+ this.groupColumns = [];
1165
+ return;
1166
+ }
1167
+ // Map field names back to column definitions, preserving order
1168
+ this.groupColumns = this.groupModel
1169
+ .map(field => this.columns.find(c => c.field === field))
1170
+ .filter((c) => !!c);
1171
+ }
1172
+ getColumnHeader(field) {
1173
+ const col = this.columns.find(c => c.field === field);
1174
+ return col ? (col.headerName || col.field) : field;
1175
+ }
1176
+ applySearch() {
1177
+ if (!this.searchQuery) {
1178
+ this.displayColumns = [...this.columns];
1179
+ }
1180
+ else {
1181
+ const q = this.searchQuery.toLowerCase();
1182
+ this.displayColumns = this.columns.filter(c => {
1183
+ const name = (c.headerName || c.field).toLowerCase();
1184
+ return name.includes(q);
1185
+ });
1186
+ }
1187
+ }
1188
+ toggleVisibility(col) {
1189
+ // Determine the original index in the main array
1190
+ const realIdx = this.columns.findIndex(c => c.field === col.field);
1191
+ if (realIdx > -1) {
1192
+ const newCols = [...this.columns];
1193
+ newCols[realIdx] = { ...newCols[realIdx], hide: !newCols[realIdx].hide };
1194
+ this.columnsUpdated.emit(newCols);
1195
+ }
1196
+ }
1197
+ onDrop(event) {
1198
+ if (this.searchQuery)
1199
+ return; // Drag/drop while searching is complex, ignoring for MVP
1200
+ if (event.previousContainer === event.container) {
1201
+ const newCols = [...this.columns];
1202
+ moveItemInArray(newCols, event.previousIndex, event.currentIndex);
1203
+ this.columnsUpdated.emit(newCols);
1204
+ }
1205
+ }
1206
+ onGroupDrop(event) {
1207
+ if (event.previousContainer === event.container) {
1208
+ const newGrp = [...this.groupColumns];
1209
+ moveItemInArray(newGrp, event.previousIndex, event.currentIndex);
1210
+ this.groupColumns = newGrp;
1211
+ }
1212
+ else {
1213
+ const col = event.previousContainer.data[event.previousIndex];
1214
+ // Prevent duplicate groupings
1215
+ if (this.groupColumns.find(c => c.field === col.field))
1216
+ return;
1217
+ const newGrp = [...this.groupColumns];
1218
+ newGrp.splice(event.currentIndex, 0, col);
1219
+ this.groupColumns = newGrp;
1220
+ }
1221
+ this.emitGroupChange();
1222
+ }
1223
+ removeGroup(col) {
1224
+ this.groupColumns = this.groupColumns.filter(c => c !== col);
1225
+ this.emitGroupChange();
1226
+ }
1227
+ emitGroupChange() {
1228
+ this.groupModelChanged.emit(this.groupColumns.map(c => c.field));
1229
+ }
1230
+ rebuildValueColumns() {
1231
+ this.valueColumns = [...this.valuesModel];
1232
+ }
1233
+ onValueDrop(event) {
1234
+ if (event.previousContainer === event.container) {
1235
+ const newVals = [...this.valueColumns];
1236
+ moveItemInArray(newVals, event.previousIndex, event.currentIndex);
1237
+ this.valueColumns = newVals;
1238
+ }
1239
+ else {
1240
+ const col = event.previousContainer.data[event.previousIndex];
1241
+ // Prevent duplicate
1242
+ if (this.valueColumns.find(c => c.field === col.field))
1243
+ return;
1244
+ const newVals = [...this.valueColumns];
1245
+ newVals.splice(event.currentIndex, 0, { field: col.field, aggFunc: 'sum' });
1246
+ this.valueColumns = newVals;
1247
+ }
1248
+ this.emitValuesChange();
1249
+ }
1250
+ removeValue(field) {
1251
+ this.valueColumns = this.valueColumns.filter(c => c.field !== field);
1252
+ this.emitValuesChange();
1253
+ }
1254
+ toggleAggDropdown(field) {
1255
+ if (this.openDropdownField === field) {
1256
+ this.openDropdownField = null;
1257
+ }
1258
+ else {
1259
+ this.openDropdownField = field;
1260
+ }
1261
+ }
1262
+ setAggFunc(vCol, func) {
1263
+ vCol.aggFunc = func;
1264
+ this.openDropdownField = null;
1265
+ this.emitValuesChange();
1266
+ }
1267
+ emitValuesChange() {
1268
+ this.valuesModelChanged.emit([...this.valueColumns]);
1269
+ }
1270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ColumnToolPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1271
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: ColumnToolPanelComponent, isStandalone: true, selector: "ug-column-tool-panel", inputs: { columns: "columns", groupModel: "groupModel", valuesModel: "valuesModel" }, outputs: { columnsUpdated: "columnsUpdated", groupModelChanged: "groupModelChanged", valuesModelChanged: "valuesModelChanged", exportCsvClicked: "exportCsvClicked" }, usesOnChanges: true, ngImport: i0, template: `
1272
+ <div class="ug-ctp-wrapper" cdkDropListGroup>
1273
+ <!-- Pivot Mode Toggle (Mock) -->
1274
+ <div class="ug-ctp-pivot-mode">
1275
+ <label class="ug-switch">
1276
+ <input type="checkbox" [(ngModel)]="pivotMode" />
1277
+ <span class="ug-slider round"></span>
1278
+ </label>
1279
+ <span>Pivot Mode</span>
1280
+ </div>
1281
+
1282
+ <!-- Column List Section -->
1283
+ <div class="ug-ctp-columns-section">
1284
+ <div class="ug-ctp-search">
1285
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1286
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="applySearch()" />
1287
+ </div>
1288
+
1289
+ <div
1290
+ class="ug-ctp-list"
1291
+ id="colList"
1292
+ cdkDropList
1293
+ [cdkDropListData]="displayColumns"
1294
+ [cdkDropListConnectedTo]="['groupList', 'valueList']"
1295
+ (cdkDropListDropped)="onDrop($event)">
1296
+ <div
1297
+ class="ug-ctp-item"
1298
+ *ngFor="let col of displayColumns"
1299
+ cdkDrag
1300
+ [cdkDragData]="col">
1301
+ <input type="checkbox" [checked]="!col.hide" (change)="toggleVisibility(col)" />
1302
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1303
+ <span class="ug-ctp-item-name">{{ col.headerName || col.field }}</span>
1304
+ </div>
1305
+ </div>
1306
+ </div>
1307
+
1308
+ <!-- Row Groups (Mock) -->
1309
+ <div class="ug-ctp-group">
1310
+ <div class="ug-ctp-group-header">
1311
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
1312
+ <span>Row Groups</span>
1313
+ </div>
1314
+ <div
1315
+ class="ug-ctp-dropzone"
1316
+ id="groupList"
1317
+ cdkDropList
1318
+ [cdkDropListData]="groupColumns"
1319
+ [cdkDropListConnectedTo]="['colList']"
1320
+ (cdkDropListDropped)="onGroupDrop($event)"
1321
+ [class.empty]="groupColumns.length === 0">
1322
+ <div *ngIf="groupColumns.length === 0">Drag here to set row groups</div>
1323
+ <div class="ug-ctp-item" *ngFor="let col of groupColumns" cdkDrag [cdkDragData]="col">
1324
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1325
+ <span class="ug-ctp-item-name">{{ col.headerName || col.field }}</span>
1326
+ <span class="ug-ctp-remove" (click)="removeGroup(col)" style="margin-left:auto;cursor:pointer;">&times;</span>
1327
+ </div>
1328
+ </div>
1329
+ </div>
1330
+
1331
+ <!-- Values -->
1332
+ <div class="ug-ctp-group">
1333
+ <div class="ug-ctp-group-header">
1334
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>
1335
+ <span>Values</span>
1336
+ </div>
1337
+ <div
1338
+ class="ug-ctp-dropzone"
1339
+ id="valueList"
1340
+ cdkDropList
1341
+ [cdkDropListData]="valueColumns"
1342
+ [cdkDropListConnectedTo]="['colList']"
1343
+ (cdkDropListDropped)="onValueDrop($event)"
1344
+ [class.empty]="valueColumns.length === 0">
1345
+ <div *ngIf="valueColumns.length === 0">Drag here to aggregate</div>
1346
+ <div class="ug-ctp-item" *ngFor="let vCol of valueColumns" cdkDrag [cdkDragData]="vCol">
1347
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1348
+ <div class="ug-ctp-agg-selector" (click)="toggleAggDropdown(vCol.field)">
1349
+ <span class="ug-ctp-item-name"><b>{{ vCol.aggFunc }}</b>({{ getColumnHeader(vCol.field) }})</span>
1350
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1351
+ </div>
1352
+ <span class="ug-ctp-remove" (click)="removeValue(vCol.field)" style="margin-left:auto;cursor:pointer;">&times;</span>
1353
+
1354
+ <!-- Dropdown Menu -->
1355
+ <div class="ug-ctp-agg-menu" *ngIf="openDropdownField === vCol.field">
1356
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'sum'" (click)="setAggFunc(vCol, 'sum')">sum</div>
1357
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'min'" (click)="setAggFunc(vCol, 'min')">min</div>
1358
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'max'" (click)="setAggFunc(vCol, 'max')">max</div>
1359
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'count'" (click)="setAggFunc(vCol, 'count')">count</div>
1360
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'avg'" (click)="setAggFunc(vCol, 'avg')">avg</div>
1361
+ </div>
1362
+ </div>
1363
+ </div>
1364
+ </div>
1365
+
1366
+ <!-- Export Section -->
1367
+ <div class="ug-ctp-group" style="padding: 15px; text-align: center; border-top: 1px solid #e2e8f0; margin-top: auto;">
1368
+ <button
1369
+ style="padding: 10px 16px; width: 100%; background: #2196f3; color: white; border: none; border-radius: 4px; font-weight: 500; cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 8px;"
1370
+ (click)="exportCsvClicked.emit()">
1371
+ <svg fill="currentColor" width="16" height="16" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
1372
+ Export CSV
1373
+ </button>
1374
+ </div>
1375
+ </div>
1376
+ `, isInline: true, styles: [".ug-ctp-wrapper{display:flex;flex-direction:column;height:100%;font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-color, #181d1f)}.ug-ctp-pivot-mode{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);gap:12px}.ug-switch{position:relative;display:inline-block;width:34px;height:20px}.ug-switch input{opacity:0;width:0;height:0}.ug-slider{position:absolute;cursor:pointer;inset:0;background-color:var(--ug-border-color);transition:.4s}.ug-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:3px;bottom:3px;background-color:#fff;transition:.4s}input:checked+.ug-slider{background-color:var(--ug-primary-color)}input:checked+.ug-slider:before{transform:translate(14px)}.ug-slider.round{border-radius:34px}.ug-slider.round:before{border-radius:50%}.ug-ctp-columns-section{flex:1;display:flex;flex-direction:column;border-bottom:1px solid var(--ug-border-color);min-height:200px}.ug-ctp-search{padding:12px;position:relative;display:flex;align-items:center}.ug-ctp-search svg{position:absolute;left:20px;color:var(--ug-icon-color)}.ug-ctp-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:var(--ug-font-family, inherit);font-size:var(--ug-font-size, inherit);background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-ctp-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ctp-list{flex:1;overflow-y:auto;padding:0 12px 12px}.ug-ctp-item{display:flex;align-items:center;padding:6px 8px;background:var(--ug-bg-color);border:1px solid transparent;border-radius:4px;margin-bottom:2px}.ug-ctp-item:hover{background:var(--ug-row-hover-bg)}.ug-ctp-item input[type=checkbox]{accent-color:var(--ug-primary-color);cursor:pointer}.ug-drag-handle{color:var(--ug-icon-color);cursor:grab;margin:0 8px}.ug-ctp-item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.cdk-drag-preview{box-sizing:border-box;background:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:flex;align-items:center;padding:6px 8px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,0,1)}.ug-ctp-group{border-bottom:1px solid var(--ug-border-color)}.ug-ctp-group-header{display:flex;align-items:center;padding:12px 16px;gap:8px;font-family:var(--ug-font-family, sans-serif);font-weight:var(--ug-header-font-weight, 500);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color);background-color:var(--ug-header-bg)}.ug-ctp-dropzone{min-height:80px;padding:12px 16px;background-color:var(--ug-bg-color);font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px)}.ug-ctp-dropzone.empty{display:flex;align-items:center;justify-content:center;color:var(--ug-icon-color);font-style:italic;border:1px dashed var(--ug-border-color);margin:12px;border-radius:4px}.ug-ctp-item{position:relative}.ug-ctp-agg-selector{display:flex;align-items:center;gap:4px;cursor:pointer;padding:2px 4px;border-radius:4px;flex:1;overflow:hidden}.ug-ctp-agg-selector:hover{background:#0000000d}.ug-ctp-agg-selector b{color:var(--ug-primary-color);margin-right:2px}.ug-ctp-agg-menu{position:absolute;top:100%;left:24px;background:#fff;border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;z-index:100;min-width:120px;padding:4px 0}.ug-ctp-agg-option{padding:6px 12px;cursor:pointer;font-size:13px}.ug-ctp-agg-option:hover{background:#f1f5f9}.ug-ctp-agg-option.selected{background:#e0f2fe;color:#0369a1;font-weight:500}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1377
+ }
1378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ColumnToolPanelComponent, decorators: [{
1379
+ type: Component,
1380
+ args: [{ selector: 'ug-column-tool-panel', standalone: true, imports: [CommonModule, FormsModule, DragDropModule], template: `
1381
+ <div class="ug-ctp-wrapper" cdkDropListGroup>
1382
+ <!-- Pivot Mode Toggle (Mock) -->
1383
+ <div class="ug-ctp-pivot-mode">
1384
+ <label class="ug-switch">
1385
+ <input type="checkbox" [(ngModel)]="pivotMode" />
1386
+ <span class="ug-slider round"></span>
1387
+ </label>
1388
+ <span>Pivot Mode</span>
1389
+ </div>
1390
+
1391
+ <!-- Column List Section -->
1392
+ <div class="ug-ctp-columns-section">
1393
+ <div class="ug-ctp-search">
1394
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1395
+ <input type="text" placeholder="Search..." [(ngModel)]="searchQuery" (ngModelChange)="applySearch()" />
1396
+ </div>
1397
+
1398
+ <div
1399
+ class="ug-ctp-list"
1400
+ id="colList"
1401
+ cdkDropList
1402
+ [cdkDropListData]="displayColumns"
1403
+ [cdkDropListConnectedTo]="['groupList', 'valueList']"
1404
+ (cdkDropListDropped)="onDrop($event)">
1405
+ <div
1406
+ class="ug-ctp-item"
1407
+ *ngFor="let col of displayColumns"
1408
+ cdkDrag
1409
+ [cdkDragData]="col">
1410
+ <input type="checkbox" [checked]="!col.hide" (change)="toggleVisibility(col)" />
1411
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1412
+ <span class="ug-ctp-item-name">{{ col.headerName || col.field }}</span>
1413
+ </div>
1414
+ </div>
1415
+ </div>
1416
+
1417
+ <!-- Row Groups (Mock) -->
1418
+ <div class="ug-ctp-group">
1419
+ <div class="ug-ctp-group-header">
1420
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line></svg>
1421
+ <span>Row Groups</span>
1422
+ </div>
1423
+ <div
1424
+ class="ug-ctp-dropzone"
1425
+ id="groupList"
1426
+ cdkDropList
1427
+ [cdkDropListData]="groupColumns"
1428
+ [cdkDropListConnectedTo]="['colList']"
1429
+ (cdkDropListDropped)="onGroupDrop($event)"
1430
+ [class.empty]="groupColumns.length === 0">
1431
+ <div *ngIf="groupColumns.length === 0">Drag here to set row groups</div>
1432
+ <div class="ug-ctp-item" *ngFor="let col of groupColumns" cdkDrag [cdkDragData]="col">
1433
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1434
+ <span class="ug-ctp-item-name">{{ col.headerName || col.field }}</span>
1435
+ <span class="ug-ctp-remove" (click)="removeGroup(col)" style="margin-left:auto;cursor:pointer;">&times;</span>
1436
+ </div>
1437
+ </div>
1438
+ </div>
1439
+
1440
+ <!-- Values -->
1441
+ <div class="ug-ctp-group">
1442
+ <div class="ug-ctp-group-header">
1443
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line></svg>
1444
+ <span>Values</span>
1445
+ </div>
1446
+ <div
1447
+ class="ug-ctp-dropzone"
1448
+ id="valueList"
1449
+ cdkDropList
1450
+ [cdkDropListData]="valueColumns"
1451
+ [cdkDropListConnectedTo]="['colList']"
1452
+ (cdkDropListDropped)="onValueDrop($event)"
1453
+ [class.empty]="valueColumns.length === 0">
1454
+ <div *ngIf="valueColumns.length === 0">Drag here to aggregate</div>
1455
+ <div class="ug-ctp-item" *ngFor="let vCol of valueColumns" cdkDrag [cdkDragData]="vCol">
1456
+ <svg class="ug-drag-handle" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" cdkDragHandle><circle cx="9" cy="12" r="1"/><circle cx="9" cy="5" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="19" r="1"/></svg>
1457
+ <div class="ug-ctp-agg-selector" (click)="toggleAggDropdown(vCol.field)">
1458
+ <span class="ug-ctp-item-name"><b>{{ vCol.aggFunc }}</b>({{ getColumnHeader(vCol.field) }})</span>
1459
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1460
+ </div>
1461
+ <span class="ug-ctp-remove" (click)="removeValue(vCol.field)" style="margin-left:auto;cursor:pointer;">&times;</span>
1462
+
1463
+ <!-- Dropdown Menu -->
1464
+ <div class="ug-ctp-agg-menu" *ngIf="openDropdownField === vCol.field">
1465
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'sum'" (click)="setAggFunc(vCol, 'sum')">sum</div>
1466
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'min'" (click)="setAggFunc(vCol, 'min')">min</div>
1467
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'max'" (click)="setAggFunc(vCol, 'max')">max</div>
1468
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'count'" (click)="setAggFunc(vCol, 'count')">count</div>
1469
+ <div class="ug-ctp-agg-option" [class.selected]="vCol.aggFunc === 'avg'" (click)="setAggFunc(vCol, 'avg')">avg</div>
1470
+ </div>
1471
+ </div>
1472
+ </div>
1473
+ </div>
1474
+
1475
+ <!-- Export Section -->
1476
+ <div class="ug-ctp-group" style="padding: 15px; text-align: center; border-top: 1px solid #e2e8f0; margin-top: auto;">
1477
+ <button
1478
+ style="padding: 10px 16px; width: 100%; background: #2196f3; color: white; border: none; border-radius: 4px; font-weight: 500; cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 8px;"
1479
+ (click)="exportCsvClicked.emit()">
1480
+ <svg fill="currentColor" width="16" height="16" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
1481
+ Export CSV
1482
+ </button>
1483
+ </div>
1484
+ </div>
1485
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".ug-ctp-wrapper{display:flex;flex-direction:column;height:100%;font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-color, #181d1f)}.ug-ctp-pivot-mode{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);gap:12px}.ug-switch{position:relative;display:inline-block;width:34px;height:20px}.ug-switch input{opacity:0;width:0;height:0}.ug-slider{position:absolute;cursor:pointer;inset:0;background-color:var(--ug-border-color);transition:.4s}.ug-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:3px;bottom:3px;background-color:#fff;transition:.4s}input:checked+.ug-slider{background-color:var(--ug-primary-color)}input:checked+.ug-slider:before{transform:translate(14px)}.ug-slider.round{border-radius:34px}.ug-slider.round:before{border-radius:50%}.ug-ctp-columns-section{flex:1;display:flex;flex-direction:column;border-bottom:1px solid var(--ug-border-color);min-height:200px}.ug-ctp-search{padding:12px;position:relative;display:flex;align-items:center}.ug-ctp-search svg{position:absolute;left:20px;color:var(--ug-icon-color)}.ug-ctp-search input{width:100%;padding:6px 8px 6px 28px;border:1px solid var(--ug-border-color);border-radius:4px;font-family:var(--ug-font-family, inherit);font-size:var(--ug-font-size, inherit);background-color:var(--ug-bg-color);color:var(--ug-color)}.ug-ctp-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ctp-list{flex:1;overflow-y:auto;padding:0 12px 12px}.ug-ctp-item{display:flex;align-items:center;padding:6px 8px;background:var(--ug-bg-color);border:1px solid transparent;border-radius:4px;margin-bottom:2px}.ug-ctp-item:hover{background:var(--ug-row-hover-bg)}.ug-ctp-item input[type=checkbox]{accent-color:var(--ug-primary-color);cursor:pointer}.ug-drag-handle{color:var(--ug-icon-color);cursor:grab;margin:0 8px}.ug-ctp-item-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.cdk-drag-preview{box-sizing:border-box;background:var(--ug-bg-color);border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;display:flex;align-items:center;padding:6px 8px;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-color)}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,0,1)}.ug-ctp-group{border-bottom:1px solid var(--ug-border-color)}.ug-ctp-group-header{display:flex;align-items:center;padding:12px 16px;gap:8px;font-family:var(--ug-font-family, sans-serif);font-weight:var(--ug-header-font-weight, 500);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color);background-color:var(--ug-header-bg)}.ug-ctp-dropzone{min-height:80px;padding:12px 16px;background-color:var(--ug-bg-color);font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px)}.ug-ctp-dropzone.empty{display:flex;align-items:center;justify-content:center;color:var(--ug-icon-color);font-style:italic;border:1px dashed var(--ug-border-color);margin:12px;border-radius:4px}.ug-ctp-item{position:relative}.ug-ctp-agg-selector{display:flex;align-items:center;gap:4px;cursor:pointer;padding:2px 4px;border-radius:4px;flex:1;overflow:hidden}.ug-ctp-agg-selector:hover{background:#0000000d}.ug-ctp-agg-selector b{color:var(--ug-primary-color);margin-right:2px}.ug-ctp-agg-menu{position:absolute;top:100%;left:24px;background:#fff;border:1px solid var(--ug-border-color);border-radius:4px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;z-index:100;min-width:120px;padding:4px 0}.ug-ctp-agg-option{padding:6px 12px;cursor:pointer;font-size:13px}.ug-ctp-agg-option:hover{background:#f1f5f9}.ug-ctp-agg-option.selected{background:#e0f2fe;color:#0369a1;font-weight:500}\n"] }]
1486
+ }], propDecorators: { columns: [{
1487
+ type: Input
1488
+ }], groupModel: [{
1489
+ type: Input
1490
+ }], valuesModel: [{
1491
+ type: Input
1492
+ }], columnsUpdated: [{
1493
+ type: Output
1494
+ }], groupModelChanged: [{
1495
+ type: Output
1496
+ }], valuesModelChanged: [{
1497
+ type: Output
1498
+ }], exportCsvClicked: [{
1499
+ type: Output
1500
+ }] } });
1501
+
1502
+ const TEXT_FILTER_OPTIONS = [
1503
+ { value: 'contains', label: 'Contains' },
1504
+ { value: 'notContains', label: 'Not Contains' },
1505
+ { value: 'equals', label: 'Equals' },
1506
+ { value: 'notEqual', label: 'Not Equal' },
1507
+ { value: 'startsWith', label: 'Starts With' },
1508
+ { value: 'endsWith', label: 'Ends With' },
1509
+ { value: 'blank', label: 'Blank' },
1510
+ { value: 'notBlank', label: 'Not Blank' }
1511
+ ];
1512
+ const NUMBER_FILTER_OPTIONS = [
1513
+ { value: 'equals', label: 'Equals' },
1514
+ { value: 'notEqual', label: 'Not Equal' },
1515
+ { value: 'greaterThan', label: 'Greater Than' },
1516
+ { value: 'greaterThanOrEqual', label: 'Greater Than or Equal' },
1517
+ { value: 'lessThan', label: 'Less Than' },
1518
+ { value: 'lessThanOrEqual', label: 'Less Than or Equal' },
1519
+ { value: 'inRange', label: 'In Range' },
1520
+ { value: 'blank', label: 'Blank' },
1521
+ { value: 'notBlank', label: 'Not Blank' }
1522
+ ];
1523
+ class FilterToolPanelComponent {
1524
+ cdr;
1525
+ columns = [];
1526
+ rowData = [];
1527
+ activeFilters = new Map();
1528
+ filterApplied = new EventEmitter();
1529
+ conditionFilterChanged = new EventEmitter();
1530
+ clearAllFilters = new EventEmitter();
1531
+ sections = [];
1532
+ _loadedFields = new Set();
1533
+ constructor(cdr) {
1534
+ this.cdr = cdr;
1535
+ }
1536
+ get hasAnyActiveFilter() {
1537
+ return this.sections.some(s => s.hasActiveFilter || s.hasConditionFilter);
1538
+ }
1539
+ ngOnChanges(changes) {
1540
+ if (changes['columns']) {
1541
+ this.buildSections();
1542
+ }
1543
+ if (changes['activeFilters']) {
1544
+ this.syncActiveFilters();
1545
+ }
1546
+ }
1547
+ buildSections() {
1548
+ const oldStateMap = new Map();
1549
+ for (const s of this.sections) {
1550
+ oldStateMap.set(s.column.field, s);
1551
+ }
1552
+ this._loadedFields.clear();
1553
+ this.sections = this.columns
1554
+ .map(col => {
1555
+ const existing = this.activeFilters.get(col.field);
1556
+ const hasActive = !!existing && existing.size > 0;
1557
+ const old = oldStateMap.get(col.field);
1558
+ // Auto-detect if column is numeric from first non-null value
1559
+ const isNumeric = this.detectNumericColumn(col.field);
1560
+ const filterOptions = isNumeric ? NUMBER_FILTER_OPTIONS : TEXT_FILTER_OPTIONS;
1561
+ const section = {
1562
+ column: col,
1563
+ expanded: old ? old.expanded : false,
1564
+ searchQuery: old ? old.searchQuery : '',
1565
+ uniqueValues: [],
1566
+ displayValues: [],
1567
+ selectedValues: new Set(),
1568
+ hasActiveFilter: hasActive,
1569
+ filterOptions: filterOptions,
1570
+ cond1Type: old ? old.cond1Type : (isNumeric ? 'equals' : 'contains'),
1571
+ cond1Value: old ? old.cond1Value : '',
1572
+ condOperator: old ? old.condOperator : 'AND',
1573
+ cond2Type: old ? old.cond2Type : (isNumeric ? 'equals' : 'contains'),
1574
+ cond2Value: old ? old.cond2Value : '',
1575
+ cond2ValueTo: old ? old.cond2ValueTo : '',
1576
+ hasConditionFilter: old ? old.hasConditionFilter : false
1577
+ };
1578
+ if (section.expanded) {
1579
+ this.ensureSectionLoaded(section);
1580
+ }
1581
+ return section;
1582
+ });
1583
+ }
1584
+ detectNumericColumn(field) {
1585
+ // Sample first 10 non-null values to detect type
1586
+ let count = 0;
1587
+ for (let i = 0; i < this.rowData.length && count < 10; i++) {
1588
+ const val = this.rowData[i][field];
1589
+ if (val !== null && val !== undefined && val !== '') {
1590
+ if (typeof val === 'number')
1591
+ return true;
1592
+ // Check if string looks like a number (but not "$..." formatted)
1593
+ const str = String(val);
1594
+ if (str.startsWith('$')) {
1595
+ // Currency — treat as number
1596
+ const numPart = str.replace(/[$,]/g, '');
1597
+ if (!isNaN(Number(numPart)))
1598
+ return true;
1599
+ }
1600
+ if (!isNaN(Number(str)))
1601
+ return true;
1602
+ count++;
1603
+ if (count >= 3)
1604
+ return false; // 3 non-numeric strings = text column
1605
+ }
1606
+ }
1607
+ return false;
1608
+ }
1609
+ ensureSectionLoaded(section) {
1610
+ if (this._loadedFields.has(section.column.field))
1611
+ return;
1612
+ this._loadedFields.add(section.column.field);
1613
+ const field = section.column.field;
1614
+ const uniqueSet = new Set();
1615
+ for (let i = 0; i < this.rowData.length; i++) {
1616
+ uniqueSet.add(this.rowData[i][field]);
1617
+ }
1618
+ section.uniqueValues = Array.from(uniqueSet).sort((a, b) => {
1619
+ if (a === null || a === undefined || a === '')
1620
+ return -1;
1621
+ if (b === null || b === undefined || b === '')
1622
+ return 1;
1623
+ return a > b ? 1 : -1;
1624
+ });
1625
+ const existing = this.activeFilters.get(field);
1626
+ if (existing && existing.size > 0) {
1627
+ section.selectedValues = new Set(existing);
1628
+ }
1629
+ else {
1630
+ section.selectedValues = new Set(section.uniqueValues);
1631
+ }
1632
+ section.displayValues = [...section.uniqueValues];
1633
+ if (section.searchQuery) {
1634
+ this.applySearchOnSection(section);
1635
+ }
1636
+ }
1637
+ syncActiveFilters() {
1638
+ for (const section of this.sections) {
1639
+ const existing = this.activeFilters.get(section.column.field);
1640
+ const hasActive = !!existing && existing.size > 0;
1641
+ section.hasActiveFilter = hasActive;
1642
+ if (this._loadedFields.has(section.column.field)) {
1643
+ if (hasActive) {
1644
+ section.selectedValues = new Set(existing);
1645
+ }
1646
+ else {
1647
+ section.selectedValues = new Set(section.uniqueValues);
1648
+ }
1649
+ }
1650
+ }
1651
+ this.cdr.markForCheck();
1652
+ }
1653
+ // --- Condition Filter Logic ---
1654
+ onConditionChanged(section) {
1655
+ const hasCond1 = section.cond1Value.trim() !== '' || section.cond1Type === 'blank' || section.cond1Type === 'notBlank';
1656
+ const hasCond2 = section.cond2Value.trim() !== '' || section.cond2Type === 'blank' || section.cond2Type === 'notBlank';
1657
+ if (!hasCond1 && !hasCond2) {
1658
+ section.hasConditionFilter = false;
1659
+ this.conditionFilterChanged.emit({ field: section.column.field, filter: null });
1660
+ return;
1661
+ }
1662
+ section.hasConditionFilter = true;
1663
+ const filter = {
1664
+ condition1: { type: section.cond1Type, value: section.cond1Value },
1665
+ operator: section.condOperator,
1666
+ condition2: hasCond2 ? { type: section.cond2Type, value: section.cond2Value, valueTo: section.cond2ValueTo } : undefined
1667
+ };
1668
+ this.conditionFilterChanged.emit({ field: section.column.field, filter });
1669
+ }
1670
+ // --- Accordion Controls ---
1671
+ toggleSection(section) {
1672
+ section.expanded = !section.expanded;
1673
+ if (section.expanded) {
1674
+ this.ensureSectionLoaded(section);
1675
+ }
1676
+ }
1677
+ expandAll() {
1678
+ this.sections.forEach(s => {
1679
+ s.expanded = true;
1680
+ this.ensureSectionLoaded(s);
1681
+ });
1682
+ }
1683
+ collapseAll() {
1684
+ this.sections.forEach(s => s.expanded = false);
1685
+ }
1686
+ // --- Search ---
1687
+ onSectionSearch(section) {
1688
+ this.applySearchOnSection(section);
1689
+ }
1690
+ applySearchOnSection(section) {
1691
+ if (!section.searchQuery) {
1692
+ section.displayValues = [...section.uniqueValues];
1693
+ }
1694
+ else {
1695
+ const q = section.searchQuery.toLowerCase();
1696
+ section.displayValues = section.uniqueValues.filter(v => String(v).toLowerCase().includes(q));
1697
+ }
1698
+ }
1699
+ // --- Checkbox Logic ---
1700
+ isSectionAllSelected(section) {
1701
+ if (section.displayValues.length === 0)
1702
+ return false;
1703
+ return section.displayValues.every(v => section.selectedValues.has(v));
1704
+ }
1705
+ toggleSectionAll(section) {
1706
+ const targetState = !this.isSectionAllSelected(section);
1707
+ if (targetState) {
1708
+ section.displayValues.forEach(v => section.selectedValues.add(v));
1709
+ }
1710
+ else {
1711
+ section.displayValues.forEach(v => section.selectedValues.delete(v));
1712
+ }
1713
+ this.emitFilter(section);
1714
+ }
1715
+ toggleValue(section, val) {
1716
+ if (section.selectedValues.has(val)) {
1717
+ section.selectedValues.delete(val);
1718
+ }
1719
+ else {
1720
+ section.selectedValues.add(val);
1721
+ }
1722
+ this.emitFilter(section);
1723
+ }
1724
+ emitFilter(section) {
1725
+ let isAllSelected = true;
1726
+ for (const v of section.uniqueValues) {
1727
+ if (!section.selectedValues.has(v)) {
1728
+ isAllSelected = false;
1729
+ break;
1730
+ }
1731
+ }
1732
+ section.hasActiveFilter = !isAllSelected;
1733
+ const finalSet = isAllSelected ? new Set() : new Set(section.selectedValues);
1734
+ this.filterApplied.emit({ field: section.column.field, selected: finalSet });
1735
+ }
1736
+ // --- Clear All ---
1737
+ onClearAll() {
1738
+ for (const section of this.sections) {
1739
+ if (this._loadedFields.has(section.column.field)) {
1740
+ section.selectedValues = new Set(section.uniqueValues);
1741
+ section.displayValues = [...section.uniqueValues];
1742
+ }
1743
+ section.hasActiveFilter = false;
1744
+ section.searchQuery = '';
1745
+ // Reset condition filters
1746
+ section.cond1Value = '';
1747
+ section.cond2Value = '';
1748
+ section.cond2ValueTo = '';
1749
+ section.hasConditionFilter = false;
1750
+ }
1751
+ this.clearAllFilters.emit();
1752
+ }
1753
+ trackByField(index, section) {
1754
+ return section.column.field;
1755
+ }
1756
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FilterToolPanelComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
1757
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: FilterToolPanelComponent, isStandalone: true, selector: "ug-filter-tool-panel", inputs: { columns: "columns", rowData: "rowData", activeFilters: "activeFilters" }, outputs: { filterApplied: "filterApplied", conditionFilterChanged: "conditionFilterChanged", clearAllFilters: "clearAllFilters" }, usesOnChanges: true, ngImport: i0, template: `
1758
+ <div class="ug-ftp-wrapper">
1759
+ <!-- Global Toolbar -->
1760
+ <div class="ug-ftp-toolbar">
1761
+ <button class="ug-ftp-btn" (click)="expandAll()" title="Expand All">
1762
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
1763
+ Expand All
1764
+ </button>
1765
+ <button class="ug-ftp-btn" (click)="collapseAll()" title="Collapse All">
1766
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
1767
+ Collapse All
1768
+ </button>
1769
+ <button class="ug-ftp-btn ug-ftp-btn-clear" (click)="onClearAll()" title="Clear All Filters" [disabled]="!hasAnyActiveFilter">
1770
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
1771
+ Clear All
1772
+ </button>
1773
+ </div>
1774
+
1775
+ <!-- Column Filter Sections -->
1776
+ <div class="ug-ftp-sections">
1777
+ <div class="ug-ftp-section" *ngFor="let section of sections; trackBy: trackByField">
1778
+ <!-- Section Header (Accordion) -->
1779
+ <div class="ug-ftp-section-header" (click)="toggleSection(section)" [class.active-filter]="section.hasActiveFilter || section.hasConditionFilter">
1780
+ <svg class="ug-ftp-caret" [class.expanded]="section.expanded" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
1781
+ <polyline points="9 18 15 12 9 6"></polyline>
1782
+ </svg>
1783
+ <span class="ug-ftp-section-title">{{ section.column.headerName || section.column.field }}</span>
1784
+ <span class="ug-ftp-active-dot" *ngIf="section.hasActiveFilter || section.hasConditionFilter" title="Filter active"></span>
1785
+ </div>
1786
+
1787
+ <!-- Section Body (Expanded) -->
1788
+ <div class="ug-ftp-section-body" *ngIf="section.expanded">
1789
+
1790
+ <!-- ======= CONDITION FILTER ======= -->
1791
+ <div class="ug-ftp-condition-block">
1792
+ <!-- Condition 1 -->
1793
+ <select class="ug-ftp-cond-select" [(ngModel)]="section.cond1Type" (ngModelChange)="onConditionChanged(section)">
1794
+ <option *ngFor="let opt of section.filterOptions" [ngValue]="opt.value">{{ opt.label }}</option>
1795
+ </select>
1796
+ <input *ngIf="section.cond1Type !== 'blank' && section.cond1Type !== 'notBlank'"
1797
+ class="ug-ftp-cond-input"
1798
+ type="text"
1799
+ placeholder="Filter value..."
1800
+ [(ngModel)]="section.cond1Value"
1801
+ (ngModelChange)="onConditionChanged(section)" />
1802
+
1803
+ <!-- AND / OR Toggle -->
1804
+ <div class="ug-ftp-operator-row">
1805
+ <label class="ug-ftp-radio">
1806
+ <input type="radio" name="op_{{section.column.field}}" value="AND" [(ngModel)]="section.condOperator" (ngModelChange)="onConditionChanged(section)" />
1807
+ AND
1808
+ </label>
1809
+ <label class="ug-ftp-radio">
1810
+ <input type="radio" name="op_{{section.column.field}}" value="OR" [(ngModel)]="section.condOperator" (ngModelChange)="onConditionChanged(section)" />
1811
+ OR
1812
+ </label>
1813
+ </div>
1814
+
1815
+ <!-- Condition 2 -->
1816
+ <select class="ug-ftp-cond-select" [(ngModel)]="section.cond2Type" (ngModelChange)="onConditionChanged(section)">
1817
+ <option *ngFor="let opt of section.filterOptions" [ngValue]="opt.value">{{ opt.label }}</option>
1818
+ </select>
1819
+ <input *ngIf="section.cond2Type !== 'blank' && section.cond2Type !== 'notBlank'"
1820
+ class="ug-ftp-cond-input"
1821
+ type="text"
1822
+ placeholder="Filter value..."
1823
+ [(ngModel)]="section.cond2Value"
1824
+ (ngModelChange)="onConditionChanged(section)" />
1825
+
1826
+ <!-- In Range second value -->
1827
+ <input *ngIf="section.cond2Type === 'inRange'"
1828
+ class="ug-ftp-cond-input"
1829
+ type="text"
1830
+ placeholder="To value..."
1831
+ [(ngModel)]="section.cond2ValueTo"
1832
+ (ngModelChange)="onConditionChanged(section)" />
1833
+ </div>
1834
+
1835
+ <!-- ======= SET FILTER (Checkboxes) ======= -->
1836
+ <div class="ug-ftp-search">
1837
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1838
+ <input type="text" placeholder="Search..." [(ngModel)]="section.searchQuery" (ngModelChange)="onSectionSearch(section)" />
1839
+ </div>
1840
+
1841
+ <label class="ug-ftp-item ug-ftp-select-all">
1842
+ <input type="checkbox" [checked]="isSectionAllSelected(section)" (change)="toggleSectionAll(section)" />
1843
+ <span>(Select All)</span>
1844
+ </label>
1845
+
1846
+ <div class="ug-ftp-values">
1847
+ <cdk-virtual-scroll-viewport itemSize="28" class="ug-ftp-viewport">
1848
+ <label class="ug-ftp-item" *cdkVirtualFor="let val of section.displayValues">
1849
+ <input type="checkbox" [checked]="section.selectedValues.has(val)" (change)="toggleValue(section, val)" />
1850
+ <span>{{ val === null || val === undefined || val === '' ? '(Blanks)' : val }}</span>
1851
+ </label>
1852
+ </cdk-virtual-scroll-viewport>
1853
+ <div class="ug-ftp-empty" *ngIf="section.displayValues.length === 0">
1854
+ No values found
1855
+ </div>
1856
+ </div>
1857
+ </div>
1858
+ </div>
1859
+ </div>
1860
+ </div>
1861
+ `, isInline: true, styles: [".ug-ftp-wrapper{display:flex;flex-direction:column;height:100%;font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color, #181d1f)}.ug-ftp-toolbar{display:flex;align-items:center;gap:4px;padding:8px 10px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg);flex-wrap:wrap}.ug-ftp-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border:1px solid var(--ug-border-color);border-radius:3px;background:var(--ug-bg-color);color:var(--ug-header-color);font-family:inherit;font-size:11px;cursor:pointer;transition:all .15s ease;white-space:nowrap}.ug-ftp-btn:hover{background:var(--ug-row-hover-bg);border-color:var(--ug-primary-color);color:var(--ug-primary-color)}.ug-ftp-btn:disabled{opacity:.4;cursor:default;pointer-events:none}.ug-ftp-btn-clear{margin-left:auto}.ug-ftp-sections{flex:1;overflow-y:auto;overflow-x:hidden}.ug-ftp-section{border-bottom:1px solid var(--ug-border-color)}.ug-ftp-section-header{display:flex;align-items:center;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none;gap:8px;background-color:var(--ug-bg-color);transition:background-color .15s}.ug-ftp-section-header:hover{background-color:var(--ug-row-hover-bg)}.ug-ftp-section-header.active-filter{font-weight:600}.ug-ftp-caret{transition:transform .2s ease;color:var(--ug-icon-color);flex-shrink:0}.ug-ftp-caret.expanded{transform:rotate(90deg)}.ug-ftp-section-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ug-ftp-active-dot{width:8px;height:8px;border-radius:50%;background-color:var(--ug-primary-color);flex-shrink:0}.ug-ftp-section-body{padding:0 12px 12px;background-color:var(--ug-bg-color)}.ug-ftp-condition-block{display:flex;flex-direction:column;gap:6px;margin-bottom:10px;padding:10px;border:1px solid var(--ug-border-color);border-radius:4px;background:var(--ug-header-bg)}.ug-ftp-cond-select{width:100%;padding:5px 8px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, sans-serif);font-size:12px;background-color:var(--ug-bg-color);color:var(--ug-header-color);cursor:pointer;box-sizing:border-box}.ug-ftp-cond-select:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-cond-input{width:100%;padding:5px 8px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, sans-serif);font-size:12px;background-color:var(--ug-bg-color);color:var(--ug-header-color);box-sizing:border-box}.ug-ftp-cond-input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-operator-row{display:flex;align-items:center;gap:16px;padding:4px 0}.ug-ftp-radio{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:500;cursor:pointer;color:var(--ug-header-color)}.ug-ftp-radio input[type=radio]{accent-color:var(--ug-primary-color);cursor:pointer}.ug-ftp-search{position:relative;display:flex;align-items:center;margin-bottom:8px}.ug-ftp-search svg{position:absolute;left:8px;color:var(--ug-icon-color);pointer-events:none}.ug-ftp-search input{width:100%;padding:5px 8px 5px 26px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, inherit);font-size:var(--ug-font-size, 12px);background-color:var(--ug-bg-color);color:var(--ug-header-color);box-sizing:border-box}.ug-ftp-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-values{border:1px solid var(--ug-border-color);border-radius:3px;background:var(--ug-bg-color)}.ug-ftp-viewport{height:200px}.ug-ftp-item{display:flex;align-items:center;gap:8px;padding:4px 8px;cursor:pointer;font-size:12px;transition:background-color .1s}.ug-ftp-item:hover{background-color:var(--ug-row-hover-bg)}.ug-ftp-item input[type=checkbox]{accent-color:var(--ug-primary-color);cursor:pointer;width:14px;height:14px;flex-shrink:0}.ug-ftp-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ug-ftp-select-all{border-bottom:1px solid var(--ug-border-color);font-weight:500;padding:6px 8px}.ug-ftp-empty{padding:16px;text-align:center;color:var(--ug-icon-color);font-style:italic;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3$1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3$1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3$1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1862
+ }
1863
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: FilterToolPanelComponent, decorators: [{
1864
+ type: Component,
1865
+ args: [{ selector: 'ug-filter-tool-panel', standalone: true, imports: [CommonModule, FormsModule, ScrollingModule], template: `
1866
+ <div class="ug-ftp-wrapper">
1867
+ <!-- Global Toolbar -->
1868
+ <div class="ug-ftp-toolbar">
1869
+ <button class="ug-ftp-btn" (click)="expandAll()" title="Expand All">
1870
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>
1871
+ Expand All
1872
+ </button>
1873
+ <button class="ug-ftp-btn" (click)="collapseAll()" title="Collapse All">
1874
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
1875
+ Collapse All
1876
+ </button>
1877
+ <button class="ug-ftp-btn ug-ftp-btn-clear" (click)="onClearAll()" title="Clear All Filters" [disabled]="!hasAnyActiveFilter">
1878
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
1879
+ Clear All
1880
+ </button>
1881
+ </div>
1882
+
1883
+ <!-- Column Filter Sections -->
1884
+ <div class="ug-ftp-sections">
1885
+ <div class="ug-ftp-section" *ngFor="let section of sections; trackBy: trackByField">
1886
+ <!-- Section Header (Accordion) -->
1887
+ <div class="ug-ftp-section-header" (click)="toggleSection(section)" [class.active-filter]="section.hasActiveFilter || section.hasConditionFilter">
1888
+ <svg class="ug-ftp-caret" [class.expanded]="section.expanded" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
1889
+ <polyline points="9 18 15 12 9 6"></polyline>
1890
+ </svg>
1891
+ <span class="ug-ftp-section-title">{{ section.column.headerName || section.column.field }}</span>
1892
+ <span class="ug-ftp-active-dot" *ngIf="section.hasActiveFilter || section.hasConditionFilter" title="Filter active"></span>
1893
+ </div>
1894
+
1895
+ <!-- Section Body (Expanded) -->
1896
+ <div class="ug-ftp-section-body" *ngIf="section.expanded">
1897
+
1898
+ <!-- ======= CONDITION FILTER ======= -->
1899
+ <div class="ug-ftp-condition-block">
1900
+ <!-- Condition 1 -->
1901
+ <select class="ug-ftp-cond-select" [(ngModel)]="section.cond1Type" (ngModelChange)="onConditionChanged(section)">
1902
+ <option *ngFor="let opt of section.filterOptions" [ngValue]="opt.value">{{ opt.label }}</option>
1903
+ </select>
1904
+ <input *ngIf="section.cond1Type !== 'blank' && section.cond1Type !== 'notBlank'"
1905
+ class="ug-ftp-cond-input"
1906
+ type="text"
1907
+ placeholder="Filter value..."
1908
+ [(ngModel)]="section.cond1Value"
1909
+ (ngModelChange)="onConditionChanged(section)" />
1910
+
1911
+ <!-- AND / OR Toggle -->
1912
+ <div class="ug-ftp-operator-row">
1913
+ <label class="ug-ftp-radio">
1914
+ <input type="radio" name="op_{{section.column.field}}" value="AND" [(ngModel)]="section.condOperator" (ngModelChange)="onConditionChanged(section)" />
1915
+ AND
1916
+ </label>
1917
+ <label class="ug-ftp-radio">
1918
+ <input type="radio" name="op_{{section.column.field}}" value="OR" [(ngModel)]="section.condOperator" (ngModelChange)="onConditionChanged(section)" />
1919
+ OR
1920
+ </label>
1921
+ </div>
1922
+
1923
+ <!-- Condition 2 -->
1924
+ <select class="ug-ftp-cond-select" [(ngModel)]="section.cond2Type" (ngModelChange)="onConditionChanged(section)">
1925
+ <option *ngFor="let opt of section.filterOptions" [ngValue]="opt.value">{{ opt.label }}</option>
1926
+ </select>
1927
+ <input *ngIf="section.cond2Type !== 'blank' && section.cond2Type !== 'notBlank'"
1928
+ class="ug-ftp-cond-input"
1929
+ type="text"
1930
+ placeholder="Filter value..."
1931
+ [(ngModel)]="section.cond2Value"
1932
+ (ngModelChange)="onConditionChanged(section)" />
1933
+
1934
+ <!-- In Range second value -->
1935
+ <input *ngIf="section.cond2Type === 'inRange'"
1936
+ class="ug-ftp-cond-input"
1937
+ type="text"
1938
+ placeholder="To value..."
1939
+ [(ngModel)]="section.cond2ValueTo"
1940
+ (ngModelChange)="onConditionChanged(section)" />
1941
+ </div>
1942
+
1943
+ <!-- ======= SET FILTER (Checkboxes) ======= -->
1944
+ <div class="ug-ftp-search">
1945
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
1946
+ <input type="text" placeholder="Search..." [(ngModel)]="section.searchQuery" (ngModelChange)="onSectionSearch(section)" />
1947
+ </div>
1948
+
1949
+ <label class="ug-ftp-item ug-ftp-select-all">
1950
+ <input type="checkbox" [checked]="isSectionAllSelected(section)" (change)="toggleSectionAll(section)" />
1951
+ <span>(Select All)</span>
1952
+ </label>
1953
+
1954
+ <div class="ug-ftp-values">
1955
+ <cdk-virtual-scroll-viewport itemSize="28" class="ug-ftp-viewport">
1956
+ <label class="ug-ftp-item" *cdkVirtualFor="let val of section.displayValues">
1957
+ <input type="checkbox" [checked]="section.selectedValues.has(val)" (change)="toggleValue(section, val)" />
1958
+ <span>{{ val === null || val === undefined || val === '' ? '(Blanks)' : val }}</span>
1959
+ </label>
1960
+ </cdk-virtual-scroll-viewport>
1961
+ <div class="ug-ftp-empty" *ngIf="section.displayValues.length === 0">
1962
+ No values found
1963
+ </div>
1964
+ </div>
1965
+ </div>
1966
+ </div>
1967
+ </div>
1968
+ </div>
1969
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".ug-ftp-wrapper{display:flex;flex-direction:column;height:100%;font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color, #181d1f)}.ug-ftp-toolbar{display:flex;align-items:center;gap:4px;padding:8px 10px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg);flex-wrap:wrap}.ug-ftp-btn{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;border:1px solid var(--ug-border-color);border-radius:3px;background:var(--ug-bg-color);color:var(--ug-header-color);font-family:inherit;font-size:11px;cursor:pointer;transition:all .15s ease;white-space:nowrap}.ug-ftp-btn:hover{background:var(--ug-row-hover-bg);border-color:var(--ug-primary-color);color:var(--ug-primary-color)}.ug-ftp-btn:disabled{opacity:.4;cursor:default;pointer-events:none}.ug-ftp-btn-clear{margin-left:auto}.ug-ftp-sections{flex:1;overflow-y:auto;overflow-x:hidden}.ug-ftp-section{border-bottom:1px solid var(--ug-border-color)}.ug-ftp-section-header{display:flex;align-items:center;padding:10px 12px;cursor:pointer;-webkit-user-select:none;user-select:none;gap:8px;background-color:var(--ug-bg-color);transition:background-color .15s}.ug-ftp-section-header:hover{background-color:var(--ug-row-hover-bg)}.ug-ftp-section-header.active-filter{font-weight:600}.ug-ftp-caret{transition:transform .2s ease;color:var(--ug-icon-color);flex-shrink:0}.ug-ftp-caret.expanded{transform:rotate(90deg)}.ug-ftp-section-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ug-ftp-active-dot{width:8px;height:8px;border-radius:50%;background-color:var(--ug-primary-color);flex-shrink:0}.ug-ftp-section-body{padding:0 12px 12px;background-color:var(--ug-bg-color)}.ug-ftp-condition-block{display:flex;flex-direction:column;gap:6px;margin-bottom:10px;padding:10px;border:1px solid var(--ug-border-color);border-radius:4px;background:var(--ug-header-bg)}.ug-ftp-cond-select{width:100%;padding:5px 8px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, sans-serif);font-size:12px;background-color:var(--ug-bg-color);color:var(--ug-header-color);cursor:pointer;box-sizing:border-box}.ug-ftp-cond-select:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-cond-input{width:100%;padding:5px 8px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, sans-serif);font-size:12px;background-color:var(--ug-bg-color);color:var(--ug-header-color);box-sizing:border-box}.ug-ftp-cond-input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-operator-row{display:flex;align-items:center;gap:16px;padding:4px 0}.ug-ftp-radio{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:500;cursor:pointer;color:var(--ug-header-color)}.ug-ftp-radio input[type=radio]{accent-color:var(--ug-primary-color);cursor:pointer}.ug-ftp-search{position:relative;display:flex;align-items:center;margin-bottom:8px}.ug-ftp-search svg{position:absolute;left:8px;color:var(--ug-icon-color);pointer-events:none}.ug-ftp-search input{width:100%;padding:5px 8px 5px 26px;border:1px solid var(--ug-border-color);border-radius:3px;font-family:var(--ug-font-family, inherit);font-size:var(--ug-font-size, 12px);background-color:var(--ug-bg-color);color:var(--ug-header-color);box-sizing:border-box}.ug-ftp-search input:focus{outline:none;border-color:var(--ug-primary-color)}.ug-ftp-values{border:1px solid var(--ug-border-color);border-radius:3px;background:var(--ug-bg-color)}.ug-ftp-viewport{height:200px}.ug-ftp-item{display:flex;align-items:center;gap:8px;padding:4px 8px;cursor:pointer;font-size:12px;transition:background-color .1s}.ug-ftp-item:hover{background-color:var(--ug-row-hover-bg)}.ug-ftp-item input[type=checkbox]{accent-color:var(--ug-primary-color);cursor:pointer;width:14px;height:14px;flex-shrink:0}.ug-ftp-item span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ug-ftp-select-all{border-bottom:1px solid var(--ug-border-color);font-weight:500;padding:6px 8px}.ug-ftp-empty{padding:16px;text-align:center;color:var(--ug-icon-color);font-style:italic;font-size:12px}\n"] }]
1970
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { columns: [{
1971
+ type: Input
1972
+ }], rowData: [{
1973
+ type: Input
1974
+ }], activeFilters: [{
1975
+ type: Input
1976
+ }], filterApplied: [{
1977
+ type: Output
1978
+ }], conditionFilterChanged: [{
1979
+ type: Output
1980
+ }], clearAllFilters: [{
1981
+ type: Output
1982
+ }] } });
1983
+
1984
+ class SideBarComponent {
1985
+ columns = [];
1986
+ rowData = [];
1987
+ activeFilters = new Map();
1988
+ groupModel = [];
1989
+ valuesModel = [];
1990
+ columnsUpdated = new EventEmitter();
1991
+ groupModelUpdated = new EventEmitter();
1992
+ valuesModelUpdated = new EventEmitter();
1993
+ exportCsvClicked = new EventEmitter();
1994
+ filterApplied = new EventEmitter();
1995
+ conditionFilterChanged = new EventEmitter();
1996
+ clearAllFilters = new EventEmitter();
1997
+ activeTab = 'columns'; // Open by default for demo parity
1998
+ toggleTab(tab) {
1999
+ if (this.activeTab === tab) {
2000
+ this.activeTab = null;
2001
+ }
2002
+ else {
2003
+ this.activeTab = tab;
2004
+ }
2005
+ }
2006
+ closePanel() {
2007
+ this.activeTab = null;
2008
+ }
2009
+ onColumnsChanged(updatedCols) {
2010
+ this.columnsUpdated.emit(updatedCols);
2011
+ }
2012
+ onGroupModelChanged(model) {
2013
+ this.groupModelUpdated.emit(model);
2014
+ }
2015
+ onExportCsvClicked() {
2016
+ this.exportCsvClicked.emit();
2017
+ }
2018
+ onFilterApplied(event) {
2019
+ this.filterApplied.emit(event);
2020
+ }
2021
+ onClearAllFilters() {
2022
+ this.clearAllFilters.emit();
2023
+ }
2024
+ onConditionFilterChanged(event) {
2025
+ this.conditionFilterChanged.emit(event);
2026
+ }
2027
+ onValuesModelChanged(model) {
2028
+ this.valuesModelUpdated.emit(model);
2029
+ }
2030
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: SideBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2031
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: SideBarComponent, isStandalone: true, selector: "ug-side-bar", inputs: { columns: "columns", rowData: "rowData", activeFilters: "activeFilters", groupModel: "groupModel", valuesModel: "valuesModel" }, outputs: { columnsUpdated: "columnsUpdated", groupModelUpdated: "groupModelUpdated", valuesModelUpdated: "valuesModelUpdated", exportCsvClicked: "exportCsvClicked", filterApplied: "filterApplied", conditionFilterChanged: "conditionFilterChanged", clearAllFilters: "clearAllFilters" }, ngImport: i0, template: `
2032
+ <div class="ug-side-bar-wrapper">
2033
+ <!-- Content Area -->
2034
+ <div class="ug-side-bar-content" *ngIf="activeTab">
2035
+ <div class="ug-side-bar-header">
2036
+ <span class="ug-side-bar-title">{{ activeTab === 'columns' ? 'Columns' : 'Filters' }}</span>
2037
+ <span class="ug-side-bar-close" (click)="closePanel()">
2038
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
2039
+ </span>
2040
+ </div>
2041
+
2042
+ <div class="ug-side-bar-body">
2043
+ <ug-column-tool-panel
2044
+ *ngIf="activeTab === 'columns'"
2045
+ [columns]="columns"
2046
+ [groupModel]="groupModel"
2047
+ [valuesModel]="valuesModel"
2048
+ (columnsUpdated)="onColumnsChanged($event)"
2049
+ (groupModelChanged)="onGroupModelChanged($event)"
2050
+ (valuesModelChanged)="onValuesModelChanged($event)"
2051
+ (exportCsvClicked)="onExportCsvClicked()">
2052
+ </ug-column-tool-panel>
2053
+
2054
+ <ug-filter-tool-panel
2055
+ *ngIf="activeTab === 'filters'"
2056
+ [columns]="columns"
2057
+ [rowData]="rowData"
2058
+ [activeFilters]="activeFilters"
2059
+ (filterApplied)="onFilterApplied($event)"
2060
+ (conditionFilterChanged)="onConditionFilterChanged($event)"
2061
+ (clearAllFilters)="onClearAllFilters()">
2062
+ </ug-filter-tool-panel>
2063
+ </div>
2064
+ </div>
2065
+
2066
+ <!-- Toolbar / Tabs -->
2067
+ <div class="ug-side-bar-toolbar">
2068
+ <div class="ug-side-bar-tab" [class.active]="activeTab === 'columns'" (click)="toggleTab('columns')" title="Columns">
2069
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2070
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2071
+ <line x1="9" y1="3" x2="9" y2="21"></line>
2072
+ </svg>
2073
+ <span>Columns</span>
2074
+ </div>
2075
+ <div class="ug-side-bar-tab" [class.active]="activeTab === 'filters'" (click)="toggleTab('filters')" title="Filters">
2076
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2077
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
2078
+ </svg>
2079
+ <span>Filters</span>
2080
+ </div>
2081
+ </div>
2082
+ </div>
2083
+ `, isInline: true, styles: [".ug-side-bar-wrapper{display:flex;flex-direction:row;height:100%;background-color:var(--ug-bg-color);border-left:1px solid var(--ug-border-color);font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color)}.ug-side-bar-content{display:flex;flex-direction:column;width:250px;height:100%;border-right:1px solid var(--ug-border-color)}.ug-side-bar-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg)}.ug-side-bar-title{font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);font-weight:var(--ug-header-font-weight, 500);color:var(--ug-header-color)}.ug-side-bar-close{cursor:pointer;color:var(--ug-icon-color);display:flex;align-items:center;justify-content:center}.ug-side-bar-close:hover{color:var(--ug-primary-color)}.ug-side-bar-body{flex:1;overflow-y:auto;overflow-x:hidden;background-color:var(--ug-bg-color)}.ug-side-bar-toolbar{display:flex;flex-direction:column;width:40px;height:100%;background-color:var(--ug-header-bg);padding-top:8px;gap:8px;align-items:center}.ug-side-bar-tab{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 4px;cursor:pointer;color:var(--ug-icon-color);border-left:2px solid transparent;width:100%;box-sizing:border-box}.ug-side-bar-tab span{writing-mode:vertical-rl;transform:rotate(180deg);margin-top:12px;font-family:var(--ug-font-family, sans-serif);font-weight:var(--ug-header-font-weight, 500);font-size:var(--ug-font-size, 13px);letter-spacing:.5px}.ug-side-bar-tab:hover{color:var(--ug-primary-color);background-color:var(--ug-row-hover-bg)}.ug-side-bar-tab.active{color:var(--ug-primary-color);background-color:var(--ug-bg-color);border-left:2px solid var(--ug-primary-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ColumnToolPanelComponent, selector: "ug-column-tool-panel", inputs: ["columns", "groupModel", "valuesModel"], outputs: ["columnsUpdated", "groupModelChanged", "valuesModelChanged", "exportCsvClicked"] }, { kind: "component", type: FilterToolPanelComponent, selector: "ug-filter-tool-panel", inputs: ["columns", "rowData", "activeFilters"], outputs: ["filterApplied", "conditionFilterChanged", "clearAllFilters"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2084
+ }
2085
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: SideBarComponent, decorators: [{
2086
+ type: Component,
2087
+ args: [{ selector: 'ug-side-bar', standalone: true, imports: [CommonModule, ColumnToolPanelComponent, FilterToolPanelComponent], template: `
2088
+ <div class="ug-side-bar-wrapper">
2089
+ <!-- Content Area -->
2090
+ <div class="ug-side-bar-content" *ngIf="activeTab">
2091
+ <div class="ug-side-bar-header">
2092
+ <span class="ug-side-bar-title">{{ activeTab === 'columns' ? 'Columns' : 'Filters' }}</span>
2093
+ <span class="ug-side-bar-close" (click)="closePanel()">
2094
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
2095
+ </span>
2096
+ </div>
2097
+
2098
+ <div class="ug-side-bar-body">
2099
+ <ug-column-tool-panel
2100
+ *ngIf="activeTab === 'columns'"
2101
+ [columns]="columns"
2102
+ [groupModel]="groupModel"
2103
+ [valuesModel]="valuesModel"
2104
+ (columnsUpdated)="onColumnsChanged($event)"
2105
+ (groupModelChanged)="onGroupModelChanged($event)"
2106
+ (valuesModelChanged)="onValuesModelChanged($event)"
2107
+ (exportCsvClicked)="onExportCsvClicked()">
2108
+ </ug-column-tool-panel>
2109
+
2110
+ <ug-filter-tool-panel
2111
+ *ngIf="activeTab === 'filters'"
2112
+ [columns]="columns"
2113
+ [rowData]="rowData"
2114
+ [activeFilters]="activeFilters"
2115
+ (filterApplied)="onFilterApplied($event)"
2116
+ (conditionFilterChanged)="onConditionFilterChanged($event)"
2117
+ (clearAllFilters)="onClearAllFilters()">
2118
+ </ug-filter-tool-panel>
2119
+ </div>
2120
+ </div>
2121
+
2122
+ <!-- Toolbar / Tabs -->
2123
+ <div class="ug-side-bar-toolbar">
2124
+ <div class="ug-side-bar-tab" [class.active]="activeTab === 'columns'" (click)="toggleTab('columns')" title="Columns">
2125
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2126
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
2127
+ <line x1="9" y1="3" x2="9" y2="21"></line>
2128
+ </svg>
2129
+ <span>Columns</span>
2130
+ </div>
2131
+ <div class="ug-side-bar-tab" [class.active]="activeTab === 'filters'" (click)="toggleTab('filters')" title="Filters">
2132
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2133
+ <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
2134
+ </svg>
2135
+ <span>Filters</span>
2136
+ </div>
2137
+ </div>
2138
+ </div>
2139
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: [".ug-side-bar-wrapper{display:flex;flex-direction:row;height:100%;background-color:var(--ug-bg-color);border-left:1px solid var(--ug-border-color);font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);color:var(--ug-header-color)}.ug-side-bar-content{display:flex;flex-direction:column;width:250px;height:100%;border-right:1px solid var(--ug-border-color)}.ug-side-bar-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--ug-border-color);background-color:var(--ug-header-bg)}.ug-side-bar-title{font-family:var(--ug-font-family, sans-serif);font-size:var(--ug-font-size, 13px);font-weight:var(--ug-header-font-weight, 500);color:var(--ug-header-color)}.ug-side-bar-close{cursor:pointer;color:var(--ug-icon-color);display:flex;align-items:center;justify-content:center}.ug-side-bar-close:hover{color:var(--ug-primary-color)}.ug-side-bar-body{flex:1;overflow-y:auto;overflow-x:hidden;background-color:var(--ug-bg-color)}.ug-side-bar-toolbar{display:flex;flex-direction:column;width:40px;height:100%;background-color:var(--ug-header-bg);padding-top:8px;gap:8px;align-items:center}.ug-side-bar-tab{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 4px;cursor:pointer;color:var(--ug-icon-color);border-left:2px solid transparent;width:100%;box-sizing:border-box}.ug-side-bar-tab span{writing-mode:vertical-rl;transform:rotate(180deg);margin-top:12px;font-family:var(--ug-font-family, sans-serif);font-weight:var(--ug-header-font-weight, 500);font-size:var(--ug-font-size, 13px);letter-spacing:.5px}.ug-side-bar-tab:hover{color:var(--ug-primary-color);background-color:var(--ug-row-hover-bg)}.ug-side-bar-tab.active{color:var(--ug-primary-color);background-color:var(--ug-bg-color);border-left:2px solid var(--ug-primary-color)}\n"] }]
2140
+ }], propDecorators: { columns: [{
2141
+ type: Input
2142
+ }], rowData: [{
2143
+ type: Input
2144
+ }], activeFilters: [{
2145
+ type: Input
2146
+ }], groupModel: [{
2147
+ type: Input
2148
+ }], valuesModel: [{
2149
+ type: Input
2150
+ }], columnsUpdated: [{
2151
+ type: Output
2152
+ }], groupModelUpdated: [{
2153
+ type: Output
2154
+ }], valuesModelUpdated: [{
2155
+ type: Output
2156
+ }], exportCsvClicked: [{
2157
+ type: Output
2158
+ }], filterApplied: [{
2159
+ type: Output
2160
+ }], conditionFilterChanged: [{
2161
+ type: Output
2162
+ }], clearAllFilters: [{
2163
+ type: Output
2164
+ }] } });
2165
+
2166
+ class GridFlattenerService {
2167
+ flatten(nodes, groupStateMap, groupIncludeFooter = false) {
2168
+ const result = [];
2169
+ this.traverse(nodes, groupStateMap, groupIncludeFooter, result);
2170
+ return result;
2171
+ }
2172
+ traverse(nodes, stateMap, groupIncludeFooter, result) {
2173
+ for (const node of nodes) {
2174
+ // Push the current node (could be a group header or a leaf)
2175
+ result.push(node);
2176
+ // If it's a group node, only push its children if it is explicitly expanded in the state map
2177
+ if (node.type === 'group') {
2178
+ const groupNode = node;
2179
+ const isExpanded = stateMap.get(groupNode.id);
2180
+ if (isExpanded) {
2181
+ if (groupNode.children && groupNode.children.length > 0) {
2182
+ this.traverse(groupNode.children, stateMap, groupIncludeFooter, result);
2183
+ }
2184
+ if (groupIncludeFooter) {
2185
+ const footerNode = {
2186
+ id: `${groupNode.id}-footer`,
2187
+ type: 'group-footer',
2188
+ level: groupNode.level,
2189
+ parent: groupNode.parent,
2190
+ groupField: groupNode.groupField,
2191
+ groupKey: groupNode.groupKey,
2192
+ data: { ...groupNode.data } // Copy aggregated data
2193
+ };
2194
+ result.push(footerNode);
2195
+ }
2196
+ }
2197
+ }
2198
+ }
2199
+ }
2200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridFlattenerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2201
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridFlattenerService, providedIn: 'root' });
2202
+ }
2203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridFlattenerService, decorators: [{
2204
+ type: Injectable,
2205
+ args: [{ providedIn: 'root' }]
2206
+ }] });
2207
+
2208
+ // AUTO-GENERATED FILE. DO NOT EDIT.
2209
+ // Built from src/lib/workers/export.worker.ts
2210
+ const EXPORT_WORKER_CODE = `"use strict";(()=>{var E=1e4;addEventListener("message",({data:a})=>{let{action:c,columns:e,data:l,options:u}=a;if(c==="START_EXPORT")try{let s=u.separator||",",g=u.includeHeaders!==!1,o=[];if(g){let t=e.map(n=>m(n.headerName||n.field,s)).join(s);o.push(t+\`
2211
+ \`)}let r="",d=0,f=(t,n)=>{let C=t.data?t.data:t,y=e.map((p,h)=>{let i=C[p.field];return h===0&&n>0&&(i=" ".repeat(n)+(i||"")),h===0&&t.type==="group"&&t.children&&(i=(i||"")+\` (\${t.children.length})\`),m(i,s)}).join(s);if(r+=y+\`
2212
+ \`,d++,d>0&&d%E===0&&(o.push(r),r=""),t.type==="group"&&t.children&&t.children.length>0)for(let p of t.children)f(p,n+1)};for(let t=0;t<l.length;t++){let n=l[t].level||0;f(l[t],n)}r&&o.push(r),postMessage({action:"COMPLETE",payload:{parts:o,type:"text/csv;charset=utf-8"}})}catch(s){postMessage({action:"ERROR",error:s.message||"CSV Export Failed"})}});function m(a,c){if(a==null)return"";let e=String(a);return e.includes(c)||e.includes(\`
2213
+ \`)||e.includes('"')?\`"\${e.replace(/"/g,'""')}"\`:e}})();
2214
+ `;
2215
+
2216
+ class CsvExportService {
2217
+ isBrowser;
2218
+ constructor(platformId) {
2219
+ this.isBrowser = isPlatformBrowser(platformId);
2220
+ }
2221
+ exportDataAsCsv(options, columns, data, config) {
2222
+ return new Promise((resolve, reject) => {
2223
+ if (!this.isBrowser) {
2224
+ console.warn('CSV Export is only supported in browser environments');
2225
+ return resolve();
2226
+ }
2227
+ // Initialize Worker from inline JavaScript string blob
2228
+ const blob = new Blob([EXPORT_WORKER_CODE], { type: 'application/javascript' });
2229
+ const workerUrl = URL.createObjectURL(blob);
2230
+ const worker = new Worker(workerUrl);
2231
+ worker.onmessage = ({ data: workerData }) => {
2232
+ const { action, payload, error } = workerData;
2233
+ if (action === 'COMPLETE') {
2234
+ this.downloadBlob(payload, options.fileName || 'export.csv');
2235
+ worker.terminate();
2236
+ URL.revokeObjectURL(workerUrl);
2237
+ resolve();
2238
+ }
2239
+ else if (action === 'ERROR') {
2240
+ worker.terminate();
2241
+ URL.revokeObjectURL(workerUrl);
2242
+ reject(new Error(error));
2243
+ }
2244
+ };
2245
+ worker.onerror = (err) => {
2246
+ worker.terminate();
2247
+ URL.revokeObjectURL(workerUrl);
2248
+ reject(err);
2249
+ };
2250
+ // Post the data to process
2251
+ worker.postMessage({
2252
+ action: 'START_EXPORT',
2253
+ columns,
2254
+ data,
2255
+ options
2256
+ });
2257
+ });
2258
+ }
2259
+ downloadBlob(blobParams, fileName) {
2260
+ if (!fileName.toLowerCase().endsWith('.csv')) {
2261
+ fileName += '.csv';
2262
+ }
2263
+ // Add UTF-8 BOM so Excel opens CSV files with correct characters natively
2264
+ const BOM = '\uFEFF';
2265
+ const blob = new Blob([BOM, ...blobParams.parts], { type: blobParams.type });
2266
+ const url = URL.createObjectURL(blob);
2267
+ const a = document.createElement('a');
2268
+ a.href = url;
2269
+ a.download = fileName;
2270
+ document.body.appendChild(a);
2271
+ a.click();
2272
+ // Clean up
2273
+ setTimeout(() => {
2274
+ document.body.removeChild(a);
2275
+ URL.revokeObjectURL(url);
2276
+ }, 100);
2277
+ }
2278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: CsvExportService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
2279
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: CsvExportService, providedIn: 'root' });
2280
+ }
2281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: CsvExportService, decorators: [{
2282
+ type: Injectable,
2283
+ args: [{
2284
+ providedIn: 'root'
2285
+ }]
2286
+ }], ctorParameters: () => [{ type: Object, decorators: [{
2287
+ type: Inject,
2288
+ args: [PLATFORM_ID]
2289
+ }] }] });
2290
+
2291
+ class UltraGridComponent {
2292
+ cdr;
2293
+ ngZone;
2294
+ flattener;
2295
+ csvExportService;
2296
+ _workerInitialized = false;
2297
+ columns = [];
2298
+ rowData = [];
2299
+ serverDataSource;
2300
+ config = {
2301
+ rowHeight: 40,
2302
+ theme: 'ag-theme-alpine'
2303
+ };
2304
+ pagination = false;
2305
+ pageSize = 100;
2306
+ rowClicked = new EventEmitter();
2307
+ sortChanged = new EventEmitter();
2308
+ isBrowser;
2309
+ visibleColumns = [];
2310
+ sortModel = [];
2311
+ groupModel = [];
2312
+ valuesModel = [];
2313
+ groupStateMap = new Map();
2314
+ _internalDataSource;
2315
+ _localTreeData = [];
2316
+ _unpaginatedLocalData = [];
2317
+ _mappedRowData = []; // Cached array
2318
+ renderedLocalData = [];
2319
+ ssrData = [];
2320
+ totalCount = 0;
2321
+ currentPage = 1;
2322
+ selectionVersion = 0;
2323
+ isMenuOpen = false;
2324
+ activeMenuColumn;
2325
+ menuPosition = { x: 0, y: 0 };
2326
+ isFilterOpen = false;
2327
+ activeFilterColumn;
2328
+ filterPosition = { x: 0, y: 0 };
2329
+ activeFilterUniqueValues = [];
2330
+ // Tracks active filters per column: fieldName -> Set of checked values
2331
+ activeFilters = new Map();
2332
+ // Tracks condition-based filters per column
2333
+ conditionFilters = new Map();
2334
+ isChooseColumnsOpen = false;
2335
+ chooseColumnsPosition = { x: 0, y: 0 };
2336
+ destroy$ = new Subject();
2337
+ sortingWorker;
2338
+ sortingWorkerUrl;
2339
+ _unflattenedTreeData = [];
2340
+ hasCheckboxSelection = false;
2341
+ hasHeaderCheckboxSelection = false;
2342
+ get rowHeight() { return this.config.rowHeight || 40; }
2343
+ get showPagination() { return this.pagination || !!this.serverDataSource; }
2344
+ constructor(platformId, cdr, ngZone, flattener, csvExportService) {
2345
+ this.cdr = cdr;
2346
+ this.ngZone = ngZone;
2347
+ this.flattener = flattener;
2348
+ this.csvExportService = csvExportService;
2349
+ this.isBrowser = isPlatformBrowser(platformId);
2350
+ this.initSortingWorker();
2351
+ }
2352
+ ngOnInit() {
2353
+ this.hasCheckboxSelection = this.columns.some(c => c.checkboxSelection);
2354
+ this.hasHeaderCheckboxSelection = this.columns.some(c => c.headerCheckboxSelection);
2355
+ this.updateVisibleColumns();
2356
+ if (this.serverDataSource && this.isBrowser) {
2357
+ if (!this.pagination) {
2358
+ this._internalDataSource = new InfiniteScrollDataSource(this.wrapServerDataSource(this.serverDataSource), this.config.bufferSize || 200);
2359
+ }
2360
+ else {
2361
+ this.fetchServerPage(1);
2362
+ }
2363
+ }
2364
+ }
2365
+ get selectedRowsCount() {
2366
+ if (!this._mappedRowData)
2367
+ return 0;
2368
+ return this._mappedRowData.filter(row => row.selected).length;
2369
+ }
2370
+ ngOnChanges(changes) {
2371
+ if (changes['rowData']) {
2372
+ this.totalCount = this.rowData.length;
2373
+ this._mappedRowData = this.rowData.map((data, index) => ({
2374
+ id: index,
2375
+ type: 'leaf',
2376
+ level: 0,
2377
+ data: data
2378
+ }));
2379
+ if (this.sortingWorker) {
2380
+ this.sortingWorker.postMessage({ action: 'INIT', rows: this._mappedRowData });
2381
+ }
2382
+ // We do not immediately `applyLocalData()` here anymore if we have a worker,
2383
+ // because INIT needs to finish first. If no worker, we apply directly.
2384
+ if (!this.sortingWorker) {
2385
+ this.applyLocalData();
2386
+ }
2387
+ if (!this.isBrowser) {
2388
+ this.ssrData = this.renderedLocalData.slice(0, 50);
2389
+ }
2390
+ }
2391
+ }
2392
+ ngOnDestroy() {
2393
+ this.destroy$.next();
2394
+ this.destroy$.complete();
2395
+ if (this.sortingWorker) {
2396
+ this.sortingWorker.terminate();
2397
+ }
2398
+ if (this.sortingWorkerUrl) {
2399
+ URL.revokeObjectURL(this.sortingWorkerUrl);
2400
+ }
2401
+ if (this._internalDataSource) {
2402
+ this._internalDataSource.disconnect();
2403
+ }
2404
+ }
2405
+ initSortingWorker() {
2406
+ if (this.isBrowser && typeof Worker !== 'undefined') {
2407
+ try {
2408
+ // Initialize Worker from inline JavaScript string blob
2409
+ const blob = new Blob([SORTING_WORKER_CODE], { type: 'application/javascript' });
2410
+ this.sortingWorkerUrl = URL.createObjectURL(blob);
2411
+ this.sortingWorker = new Worker(this.sortingWorkerUrl);
2412
+ this.sortingWorker.onmessage = ({ data }) => {
2413
+ this.ngZone.run(() => {
2414
+ if (data && data.action === 'INIT_DONE') {
2415
+ this._workerInitialized = true;
2416
+ // Now that worker has cached the data, run the first execution
2417
+ this.applyLocalData();
2418
+ return;
2419
+ }
2420
+ const flatList = data.flatList;
2421
+ this._unpaginatedLocalData = flatList;
2422
+ const groupSelectionMap = new Map();
2423
+ // If sort/group happened, the worker returns a new structural tree. Cache it.
2424
+ if (data.resultTree) {
2425
+ this._unflattenedTreeData = data.resultTree;
2426
+ }
2427
+ // Always hydrate the structural tree so group node checkmarks strictly persist across flatten events
2428
+ const hydrateTree = (nodes) => {
2429
+ let allSelected = true;
2430
+ if (nodes.length === 0)
2431
+ allSelected = false;
2432
+ for (let i = 0; i < nodes.length; i++) {
2433
+ const node = nodes[i];
2434
+ if (node.type === 'leaf') {
2435
+ const cached = this._mappedRowData[node.id];
2436
+ if (cached) {
2437
+ node.data = cached.data;
2438
+ node.selected = cached.selected;
2439
+ if (!cached.selected)
2440
+ allSelected = false;
2441
+ }
2442
+ else {
2443
+ allSelected = false;
2444
+ }
2445
+ }
2446
+ else if (node.type === 'group' && node.children) {
2447
+ const isGroupSelected = hydrateTree(node.children);
2448
+ node.selected = isGroupSelected;
2449
+ groupSelectionMap.set(node.id, isGroupSelected);
2450
+ if (!isGroupSelected)
2451
+ allSelected = false;
2452
+ }
2453
+ }
2454
+ return allSelected;
2455
+ };
2456
+ if (this._unflattenedTreeData) {
2457
+ hydrateTree(this._unflattenedTreeData);
2458
+ }
2459
+ // Sync the flat list data payload and strictly enforce selection states
2460
+ for (let i = 0; i < flatList.length; i++) {
2461
+ const row = flatList[i];
2462
+ if (row.type === 'leaf') {
2463
+ const cached = this._mappedRowData[row.id];
2464
+ if (cached) {
2465
+ if (!row.data)
2466
+ row.data = cached.data;
2467
+ row.selected = cached.selected;
2468
+ }
2469
+ }
2470
+ else if (row.type === 'group') {
2471
+ row.selected = groupSelectionMap.has(row.id) ? groupSelectionMap.get(row.id) : (row.selected || false);
2472
+ }
2473
+ }
2474
+ this.totalCount = this._unpaginatedLocalData.length;
2475
+ this.updatePaginationForLocalData();
2476
+ this.cdr.markForCheck();
2477
+ });
2478
+ };
2479
+ }
2480
+ catch (e) {
2481
+ console.warn('Web worker initialization failed, falling back to local sorting', e);
2482
+ }
2483
+ }
2484
+ }
2485
+ updateVisibleColumns() {
2486
+ const temp = this.columns.filter(c => !c.hide);
2487
+ const left = temp.filter(c => c.pinned === 'left');
2488
+ const right = temp.filter(c => c.pinned === 'right');
2489
+ const center = temp.filter(c => !c.pinned);
2490
+ let currLeft = 0;
2491
+ left.forEach(c => {
2492
+ c._leftOffset = currLeft;
2493
+ currLeft += (c.width || 200);
2494
+ });
2495
+ let currRight = 0;
2496
+ [...right].reverse().forEach(c => {
2497
+ c._rightOffset = currRight;
2498
+ currRight += (c.width || 200);
2499
+ });
2500
+ this.visibleColumns = [...left, ...center, ...right];
2501
+ // Ensure checkbox selection strictly stays on the first visible column, regardless of Drag&Drop swaps
2502
+ if (this.visibleColumns.length > 0) {
2503
+ this.visibleColumns.forEach((c, index) => {
2504
+ c.checkboxSelection = index === 0 ? this.hasCheckboxSelection : false;
2505
+ c.headerCheckboxSelection = index === 0 ? this.hasHeaderCheckboxSelection : false;
2506
+ });
2507
+ }
2508
+ }
2509
+ wrapServerDataSource(source) {
2510
+ return {
2511
+ getRows: (params) => {
2512
+ return source.getRows({ ...params, sortModel: this.sortModel }).pipe(map(res => ({
2513
+ ...res,
2514
+ rows: res.rows.map((r, i) => ({ id: i + params.startRow, type: 'leaf', level: 0, data: r }))
2515
+ })));
2516
+ }
2517
+ };
2518
+ }
2519
+ applyLocalData() {
2520
+ if (!this.rowData)
2521
+ return;
2522
+ if (this.sortingWorker && !this._workerInitialized)
2523
+ return; // Wait for worker INIT_DONE
2524
+ let processed = this._mappedRowData;
2525
+ // Prepare Filter Model for Worker by converting Set to Array
2526
+ const filterModelObj = {};
2527
+ let hasFilter = false;
2528
+ if (this.activeFilters.size > 0) {
2529
+ hasFilter = true;
2530
+ for (const [field, selectedSet] of this.activeFilters.entries()) {
2531
+ filterModelObj[field] = Array.from(selectedSet);
2532
+ }
2533
+ }
2534
+ const hasSort = this.sortModel.length > 0;
2535
+ // Prepare State Map for Worker
2536
+ const stateMapObj = {};
2537
+ for (const [key, value] of this.groupStateMap.entries()) {
2538
+ stateMapObj[key] = value;
2539
+ }
2540
+ // Prepare Aggregations map: merge static aggFunc from column defs with dynamic valuesModel
2541
+ const aggregations = {};
2542
+ // Static aggFunc from column definitions
2543
+ for (const col of this.columns) {
2544
+ if (col.aggFunc) {
2545
+ aggregations[col.field] = col.aggFunc;
2546
+ }
2547
+ }
2548
+ // Dynamic values from Values panel
2549
+ if (this.valuesModel.length > 0) {
2550
+ for (const vCol of this.valuesModel) {
2551
+ if (!aggregations[vCol.field]) {
2552
+ aggregations[vCol.field] = vCol.aggFunc;
2553
+ }
2554
+ }
2555
+ }
2556
+ else {
2557
+ // Default: aggregate all numeric columns if Values panel is empty
2558
+ for (const col of this.columns) {
2559
+ if (!aggregations[col.field] && this.isNumericColumn(col.field)) {
2560
+ aggregations[col.field] = 'sum';
2561
+ }
2562
+ }
2563
+ }
2564
+ if (hasFilter || hasSort || this.groupModel.length > 0 || this.conditionFilters.size > 0) {
2565
+ if (this.sortingWorker) {
2566
+ this.sortingWorker.postMessage({
2567
+ action: 'EXECUTE',
2568
+ sortModel: this.sortModel,
2569
+ filterModel: hasFilter ? filterModelObj : null,
2570
+ conditionFilters: this.conditionFilters.size > 0 ? Object.fromEntries(this.conditionFilters) : null,
2571
+ groupModel: this.groupModel,
2572
+ groupStateMap: stateMapObj,
2573
+ aggregations: aggregations,
2574
+ groupIncludeFooter: !!this.config.groupIncludeFooter
2575
+ });
2576
+ return;
2577
+ }
2578
+ else {
2579
+ // Fallback to local filtering if Worker fails/unsupported
2580
+ if (hasFilter) {
2581
+ processed = processed.filter(row => {
2582
+ for (const [field, selectedSet] of this.activeFilters.entries()) {
2583
+ const cellValue = row.data ? row.data[field] : null;
2584
+ if (!selectedSet.has(cellValue))
2585
+ return false;
2586
+ }
2587
+ return true;
2588
+ });
2589
+ }
2590
+ if (hasSort) {
2591
+ processed = this.sortLocal(processed);
2592
+ }
2593
+ }
2594
+ }
2595
+ this._localTreeData = processed;
2596
+ this.refreshFlattener();
2597
+ }
2598
+ refreshFlattener() {
2599
+ this._unpaginatedLocalData = this.flattener.flatten(this._localTreeData, this.groupStateMap, !!this.config.groupIncludeFooter);
2600
+ this.totalCount = this._unpaginatedLocalData.length;
2601
+ this.updatePaginationForLocalData();
2602
+ this.cdr.markForCheck();
2603
+ }
2604
+ isNumericColumn(field) {
2605
+ // Check up to the first 5 rows to determine if the column contains numeric data
2606
+ const rowsToCheck = Math.min(20, this._unpaginatedLocalData.length);
2607
+ if (rowsToCheck === 0)
2608
+ return false;
2609
+ let hasNumeric = false;
2610
+ for (let i = 0; i < Math.min(rowsToCheck, this._unpaginatedLocalData.length); i++) {
2611
+ const val = this._unpaginatedLocalData[i]?.data?.[field];
2612
+ if (val !== undefined && val !== null && val !== '') {
2613
+ const strVal = String(val);
2614
+ // Reject if it contains alphabetic letters (e.g. "User 1" or "101 USD")
2615
+ if (/[a-zA-Z]/.test(strVal))
2616
+ return false;
2617
+ // Remove currency symbols, commas, and spaces, then check if it's a valid number
2618
+ const parsed = Number(strVal.replace(/[^0-9.-]+/g, ''));
2619
+ if (!isNaN(parsed)) {
2620
+ hasNumeric = true;
2621
+ }
2622
+ }
2623
+ }
2624
+ return hasNumeric;
2625
+ }
2626
+ toggleGroup(groupId) {
2627
+ const isExpanded = this.groupStateMap.get(groupId);
2628
+ this.groupStateMap.set(groupId, !isExpanded);
2629
+ const stateObj = Object.fromEntries(this.groupStateMap);
2630
+ if (this.sortingWorker) {
2631
+ // High-performance path: only trigger flattening on the already-cached tree
2632
+ this.sortingWorker.postMessage({
2633
+ action: 'FLATTEN_ONLY',
2634
+ groupStateMap: stateObj,
2635
+ groupIncludeFooter: !!this.config.groupIncludeFooter
2636
+ });
2637
+ }
2638
+ else {
2639
+ // Fallback for non-worker mode
2640
+ this.refreshFlattener();
2641
+ }
2642
+ }
2643
+ isGroupExpanded(row) {
2644
+ if (!row || row.type !== 'group')
2645
+ return false;
2646
+ return !!this.groupStateMap.get(row.id);
2647
+ }
2648
+ sortLocal(rows) {
2649
+ return [...rows].sort((a, b) => {
2650
+ for (const sort of this.sortModel) {
2651
+ const { field, direction } = sort;
2652
+ const valA = a.data ? a.data[field] : null;
2653
+ const valB = b.data ? b.data[field] : null;
2654
+ if (valA === valB)
2655
+ continue;
2656
+ const comparison = valA > valB ? 1 : -1;
2657
+ return direction === 'asc' ? comparison : -comparison;
2658
+ }
2659
+ return 0;
2660
+ });
2661
+ }
2662
+ updatePaginationForLocalData() {
2663
+ if (this.pagination && !this.serverDataSource) {
2664
+ const start = (this.currentPage - 1) * this.pageSize;
2665
+ const end = start + this.pageSize;
2666
+ this.renderedLocalData = this._unpaginatedLocalData.slice(start, end);
2667
+ }
2668
+ else if (!this.serverDataSource) {
2669
+ this.renderedLocalData = this._unpaginatedLocalData;
2670
+ }
2671
+ }
2672
+ fetchServerPage(page) {
2673
+ if (!this.serverDataSource)
2674
+ return;
2675
+ const startRow = (page - 1) * this.pageSize;
2676
+ const endRow = startRow + this.pageSize;
2677
+ this.serverDataSource.getRows({
2678
+ startRow,
2679
+ endRow,
2680
+ sortModel: this.sortModel
2681
+ }).subscribe(res => {
2682
+ this.totalCount = res.totalCount;
2683
+ this.renderedLocalData = res.rows.map((r, i) => ({ id: startRow + i, type: 'leaf', level: 0, data: r }));
2684
+ this.currentPage = page;
2685
+ this.cdr.markForCheck();
2686
+ });
2687
+ }
2688
+ /**
2689
+ * Triggers a Web-Worker powered CSV export of the grid data.
2690
+ * @param options Configures whether to export all records or just visible records, as well as CSV formatting.
2691
+ */
2692
+ async exportCsv(options = {}) {
2693
+ const mode = options.mode || 'all';
2694
+ const sourceColumns = options.columnKeys
2695
+ ? this.columns.filter(c => options.columnKeys.includes(c.field))
2696
+ : this.visibleColumns;
2697
+ let exportData = [];
2698
+ if (mode === 'all') {
2699
+ exportData = this.rowData;
2700
+ if (this.serverDataSource) {
2701
+ console.warn('UltraGrid: "all" export mode is not fully supported with ServerDataSource since all rows are not loaded locally.');
2702
+ }
2703
+ }
2704
+ else {
2705
+ // When grouped, _unpaginatedLocalData only has expanded visible nodes.
2706
+ // We use the full nested _unflattenedTreeData so the export worker can extract all nested rows recursively.
2707
+ exportData = this._unflattenedTreeData && this._unflattenedTreeData.length > 0
2708
+ ? this._unflattenedTreeData
2709
+ : this._unpaginatedLocalData;
2710
+ }
2711
+ try {
2712
+ await this.csvExportService.exportDataAsCsv(options, sourceColumns, exportData, this.config);
2713
+ }
2714
+ catch (error) {
2715
+ console.error('UltraGrid CSV Export Failed:', error);
2716
+ }
2717
+ }
2718
+ onPageChanged(page) {
2719
+ if (this.serverDataSource && this.pagination) {
2720
+ this.fetchServerPage(page);
2721
+ }
2722
+ else {
2723
+ this.currentPage = page;
2724
+ this.applyLocalData();
2725
+ }
2726
+ }
2727
+ onSortChanged(event) {
2728
+ if (this.config.multiSort) {
2729
+ const existingIdx = this.sortModel.findIndex(m => m.field === event.field);
2730
+ if (event.direction === null) {
2731
+ if (existingIdx >= 0) {
2732
+ this.sortModel = this.sortModel.filter(m => m.field !== event.field);
2733
+ }
2734
+ }
2735
+ else {
2736
+ if (existingIdx >= 0) {
2737
+ const newModel = [...this.sortModel];
2738
+ newModel[existingIdx] = { ...newModel[existingIdx], direction: event.direction };
2739
+ this.sortModel = newModel;
2740
+ }
2741
+ else {
2742
+ this.sortModel = [...this.sortModel, { field: event.field, direction: event.direction }];
2743
+ }
2744
+ }
2745
+ }
2746
+ else {
2747
+ if (event.direction === null)
2748
+ this.sortModel = [];
2749
+ else
2750
+ this.sortModel = [{ field: event.field, direction: event.direction }];
2751
+ }
2752
+ this.sortChanged.emit(this.sortModel);
2753
+ // Re-fetch or re-sort
2754
+ if (this.serverDataSource) {
2755
+ if (this._internalDataSource) {
2756
+ this._internalDataSource = new InfiniteScrollDataSource(this.wrapServerDataSource(this.serverDataSource), this.config.bufferSize || 200);
2757
+ }
2758
+ else if (this.pagination) {
2759
+ this.fetchServerPage(1);
2760
+ }
2761
+ }
2762
+ else {
2763
+ this.applyLocalData();
2764
+ }
2765
+ }
2766
+ onColumnsReordered(cols) {
2767
+ this.columns = cols;
2768
+ this.updateVisibleColumns();
2769
+ this.cdr.markForCheck();
2770
+ }
2771
+ onSideBarColumnsUpdated(cols) {
2772
+ this.columns = cols;
2773
+ this.updateVisibleColumns();
2774
+ this.cdr.markForCheck();
2775
+ }
2776
+ onSideBarGroupModelUpdated(model) {
2777
+ this.groupModel = model;
2778
+ this.applyLocalData();
2779
+ this.cdr.markForCheck();
2780
+ }
2781
+ onValuesModelUpdated(model) {
2782
+ this.valuesModel = model;
2783
+ this.applyLocalData();
2784
+ this.cdr.markForCheck();
2785
+ }
2786
+ onColumnResized(event) {
2787
+ const col = this.columns.find(c => c.field === event.field);
2788
+ if (col) {
2789
+ col.width = event.width;
2790
+ this.updateVisibleColumns();
2791
+ this.cdr.markForCheck();
2792
+ }
2793
+ }
2794
+ get isAllSelected() {
2795
+ const targetArray = this._unpaginatedLocalData;
2796
+ if (!targetArray || targetArray.length === 0)
2797
+ return false;
2798
+ return targetArray.every(r => r.selected);
2799
+ }
2800
+ onHeaderCheckboxClicked(selected) {
2801
+ // 1. Traverse the full unflattened tree to persist selection to ALL rows matching the filter
2802
+ const traverse = (nodes) => {
2803
+ for (const n of nodes) {
2804
+ n.selected = selected;
2805
+ if (n.type === 'leaf' && this._mappedRowData[n.id]) {
2806
+ this._mappedRowData[n.id].selected = selected;
2807
+ }
2808
+ else if (n.type === 'group' && n.children) {
2809
+ traverse(n.children);
2810
+ }
2811
+ }
2812
+ };
2813
+ traverse(this._unflattenedTreeData);
2814
+ // 2. Update the currently flattened array for instant UI update
2815
+ for (const row of this._unpaginatedLocalData) {
2816
+ row.selected = selected;
2817
+ }
2818
+ this.selectionVersion++;
2819
+ this.cdr.markForCheck();
2820
+ }
2821
+ onPageSizeChanged(size) {
2822
+ this.pageSize = size;
2823
+ this.currentPage = 1;
2824
+ if (this.serverDataSource && this.pagination) {
2825
+ this.fetchServerPage(1);
2826
+ }
2827
+ else {
2828
+ this.updatePaginationForLocalData();
2829
+ this.cdr.markForCheck();
2830
+ }
2831
+ }
2832
+ selectNodeAndChildren(nodeId, selected) {
2833
+ // Find the node in the unflattened tree using DFS
2834
+ const findNode = (nodes, id) => {
2835
+ for (const node of nodes) {
2836
+ if (node.id === id)
2837
+ return node;
2838
+ if (node.type === 'group' && node.children) {
2839
+ const found = findNode(node.children, id);
2840
+ if (found)
2841
+ return found;
2842
+ }
2843
+ }
2844
+ return undefined;
2845
+ };
2846
+ const targetNode = findNode(this._unflattenedTreeData, nodeId);
2847
+ if (!targetNode)
2848
+ return;
2849
+ // Collect all leaf IDs under this node, and set group selections
2850
+ const leafIdsToUpdate = new Set();
2851
+ const groupIdsToUpdate = new Set();
2852
+ const traverse = (n) => {
2853
+ n.selected = selected;
2854
+ if (n.type === 'leaf') {
2855
+ leafIdsToUpdate.add(n.id);
2856
+ if (this._mappedRowData[n.id]) {
2857
+ this._mappedRowData[n.id].selected = selected;
2858
+ }
2859
+ }
2860
+ else if (n.type === 'group' && n.children) {
2861
+ groupIdsToUpdate.add(n.id);
2862
+ n.children.forEach((child) => traverse(child));
2863
+ }
2864
+ };
2865
+ traverse(targetNode);
2866
+ // Update the flattened list in O(N) time so the UI reflects it instantly
2867
+ for (let i = 0; i < this._unpaginatedLocalData.length; i++) {
2868
+ const row = this._unpaginatedLocalData[i];
2869
+ if (row.type === 'leaf' && leafIdsToUpdate.has(row.id)) {
2870
+ row.selected = selected;
2871
+ }
2872
+ else if (row.type === 'group' && groupIdsToUpdate.has(row.id)) {
2873
+ row.selected = selected;
2874
+ }
2875
+ }
2876
+ }
2877
+ onRowClicked(row) {
2878
+ if (!row)
2879
+ return;
2880
+ let newSelectedState = true;
2881
+ if (this.config.rowSelection === 'single') {
2882
+ this._unpaginatedLocalData.forEach(r => r.selected = false);
2883
+ this._mappedRowData.forEach(r => { if (r)
2884
+ r.selected = false; });
2885
+ row.selected = true;
2886
+ if (row.type === 'leaf' && this._mappedRowData[row.id]) {
2887
+ this._mappedRowData[row.id].selected = true;
2888
+ }
2889
+ }
2890
+ else if (this.config.rowSelection === 'multiple') {
2891
+ newSelectedState = !row.selected;
2892
+ row.selected = newSelectedState;
2893
+ if (row.type === 'leaf' && this._mappedRowData[row.id]) {
2894
+ this._mappedRowData[row.id].selected = newSelectedState;
2895
+ }
2896
+ else if (row.type === 'group') {
2897
+ this.selectNodeAndChildren(row.id, newSelectedState);
2898
+ }
2899
+ }
2900
+ this.selectionVersion++;
2901
+ this.rowClicked.emit(row);
2902
+ this.cdr.markForCheck();
2903
+ }
2904
+ getPlaceholderRow(index) {
2905
+ return {
2906
+ id: `placeholder-${index}`,
2907
+ type: 'leaf',
2908
+ level: 0,
2909
+ data: null
2910
+ };
2911
+ }
2912
+ trackByRowId(index, item) {
2913
+ return item ? item.id : index;
2914
+ }
2915
+ onHeaderMenuClicked(event) {
2916
+ this.activeMenuColumn = event.column;
2917
+ this.menuPosition = { x: event.event.clientX, y: event.event.clientY };
2918
+ this.isMenuOpen = true;
2919
+ this.cdr.markForCheck();
2920
+ }
2921
+ onMenuClosed() {
2922
+ this.isMenuOpen = false;
2923
+ this.activeMenuColumn = undefined;
2924
+ this.cdr.markForCheck();
2925
+ }
2926
+ onMenuSort(event) {
2927
+ this.onSortChanged(event);
2928
+ }
2929
+ onMenuPin(event) {
2930
+ const col = this.columns.find(c => c.field === event.field);
2931
+ if (col) {
2932
+ col.pinned = event.pinned || undefined;
2933
+ this.updateVisibleColumns();
2934
+ this.cdr.markForCheck();
2935
+ }
2936
+ }
2937
+ onMenuAutosize(event) {
2938
+ // Basic implementation: set width to 200px or calculate based on sampling
2939
+ // A complete implementation would measure Canvas TextMetrics
2940
+ if (event.all) {
2941
+ this.columns.forEach(c => c.width = 200);
2942
+ }
2943
+ else {
2944
+ const col = this.columns.find(c => c.field === event.field);
2945
+ if (col)
2946
+ col.width = 250;
2947
+ }
2948
+ this.updateVisibleColumns();
2949
+ this.cdr.markForCheck();
2950
+ }
2951
+ onMenuGroup(event) {
2952
+ if (!this.groupModel) {
2953
+ this.groupModel = [];
2954
+ }
2955
+ if (this.groupModel.includes(event.field)) {
2956
+ // Remove from grouping
2957
+ this.groupModel = this.groupModel.filter(f => f !== event.field);
2958
+ }
2959
+ else {
2960
+ // Add to grouping
2961
+ this.groupModel = [...this.groupModel, event.field];
2962
+ }
2963
+ this.applyLocalData();
2964
+ this.cdr.markForCheck();
2965
+ }
2966
+ onMenuChooseColumns() {
2967
+ this.isChooseColumnsOpen = true;
2968
+ // Position it slightly shifted from the menu icon
2969
+ this.chooseColumnsPosition = { x: this.menuPosition.x + 20, y: this.menuPosition.y + 20 };
2970
+ this.isMenuOpen = false;
2971
+ this.cdr.markForCheck();
2972
+ }
2973
+ onChooseColumnsUpdated(columns) {
2974
+ this.columns = columns;
2975
+ this.updateVisibleColumns();
2976
+ this.cdr.markForCheck();
2977
+ }
2978
+ // --- Filter logic ---
2979
+ getFilterSetForColumn(field) {
2980
+ if (!field)
2981
+ return new Set();
2982
+ return this.activeFilters.get(field) || new Set();
2983
+ }
2984
+ onHeaderFilterClicked(event) {
2985
+ this.activeFilterColumn = event.column;
2986
+ this.filterPosition = { x: event.event.clientX, y: event.event.clientY };
2987
+ this.isFilterOpen = true;
2988
+ // Calculate unique values based on un-paginated / un-filtered raw data
2989
+ const uniqueSet = new Set();
2990
+ this.rowData.forEach(row => {
2991
+ const val = row[event.column.field];
2992
+ uniqueSet.add(val);
2993
+ });
2994
+ // Convert to array and sort (keeping nulls/blanks separate logically)
2995
+ this.activeFilterUniqueValues = Array.from(uniqueSet).sort((a, b) => {
2996
+ if (a === null || a === undefined || a === '')
2997
+ return -1;
2998
+ if (b === null || b === undefined || b === '')
2999
+ return 1;
3000
+ return a > b ? 1 : -1;
3001
+ });
3002
+ this.cdr.markForCheck();
3003
+ }
3004
+ onFilterClosed() {
3005
+ this.isFilterOpen = false;
3006
+ this.activeFilterColumn = undefined;
3007
+ this.cdr.markForCheck();
3008
+ }
3009
+ onFilterApplied(event) {
3010
+ if (event.selected.size === 0) {
3011
+ // Empty filter means "Clear Filter / Show All"
3012
+ this.activeFilters.delete(event.field);
3013
+ }
3014
+ else {
3015
+ this.activeFilters.set(event.field, event.selected);
3016
+ }
3017
+ this.currentPage = 1; // Reset to page 1 on filter
3018
+ this.applyLocalData(); // Re-run pipeline (filter -> sort -> paginate)
3019
+ }
3020
+ onClearAllFilters() {
3021
+ this.activeFilters.clear();
3022
+ this.conditionFilters.clear();
3023
+ this.currentPage = 1;
3024
+ this.applyLocalData();
3025
+ this.cdr.markForCheck();
3026
+ }
3027
+ onConditionFilterChanged(event) {
3028
+ if (event.filter === null) {
3029
+ this.conditionFilters.delete(event.field);
3030
+ }
3031
+ else {
3032
+ this.conditionFilters.set(event.field, event.filter);
3033
+ }
3034
+ this.currentPage = 1;
3035
+ this.applyLocalData();
3036
+ this.cdr.markForCheck();
3037
+ }
3038
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: UltraGridComponent, deps: [{ token: PLATFORM_ID }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: GridFlattenerService }, { token: CsvExportService }], target: i0.ɵɵFactoryTarget.Component });
3039
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: UltraGridComponent, isStandalone: true, selector: "bways-grid", inputs: { columns: "columns", rowData: "rowData", serverDataSource: "serverDataSource", config: "config", pagination: "pagination", pageSize: "pageSize" }, outputs: { rowClicked: "rowClicked", sortChanged: "sortChanged" }, usesOnChanges: true, ngImport: i0, template: `
3040
+ <div class="ug-wrapper" [ngClass]="config.theme || 'ag-theme-alpine'">
3041
+
3042
+ <div class="ug-main">
3043
+ <ug-header
3044
+ [columns]="visibleColumns"
3045
+ [sortModel]="sortModel"
3046
+ [isAllSelected]="isAllSelected"
3047
+ (sortChanged)="onSortChanged($event)"
3048
+ (columnsReordered)="onColumnsReordered($event)"
3049
+ (columnResized)="onColumnResized($event)"
3050
+ (headerCheckboxClicked)="onHeaderCheckboxClicked($event)"
3051
+ (menuClicked)="onHeaderMenuClicked($event)"
3052
+ (filterClicked)="onHeaderFilterClicked($event)">
3053
+ </ug-header>
3054
+
3055
+ <div class="ug-body">
3056
+ <ng-container *ngIf="isBrowser; else ssrPlaceholder">
3057
+ <cdk-virtual-scroll-viewport
3058
+ [itemSize]="rowHeight"
3059
+ class="ug-viewport">
3060
+
3061
+ <ug-row
3062
+ *cdkVirtualFor="let row of _internalDataSource || renderedLocalData; let i = index; trackBy: trackByRowId"
3063
+ [row]="row || getPlaceholderRow(i)"
3064
+ [columns]="visibleColumns"
3065
+ [rowHeight]="rowHeight"
3066
+ [isExpanded]="isGroupExpanded(row!)"
3067
+ [selectionVersion]="selectionVersion"
3068
+ (groupToggled)="toggleGroup(row!.id.toString())"
3069
+ (click)="onRowClicked(row)">
3070
+ </ug-row>
3071
+
3072
+ </cdk-virtual-scroll-viewport>
3073
+ </ng-container>
3074
+
3075
+ <ng-template #ssrPlaceholder>
3076
+ <div class="ug-ssr-container">
3077
+ <ug-row
3078
+ *ngFor="let row of ssrData; trackBy: trackByRowId"
3079
+ [row]="row"
3080
+ [columns]="visibleColumns"
3081
+ [rowHeight]="rowHeight"
3082
+ [selectionVersion]="selectionVersion">
3083
+ </ug-row>
3084
+ </div>
3085
+ </ng-template>
3086
+ </div>
3087
+
3088
+ <div class="ug-status-bar" *ngIf="config.statusBar">
3089
+ <span>Total Rows: {{ totalCount }}</span>
3090
+ <span style="margin-left: 16px;">Selected: {{ selectedRowsCount }}</span>
3091
+ </div>
3092
+
3093
+ <ug-pagination
3094
+ *ngIf="showPagination"
3095
+ [totalCount]="totalCount"
3096
+ [pageSize]="pageSize"
3097
+ [currentPage]="currentPage"
3098
+ (pageChanged)="onPageChanged($event)"
3099
+ (pageSizeChanged)="onPageSizeChanged($event)">
3100
+ </ug-pagination>
3101
+ </div>
3102
+
3103
+ <ug-side-bar
3104
+ *ngIf="config.sideBar"
3105
+ [columns]="columns"
3106
+ [rowData]="rowData"
3107
+ [activeFilters]="activeFilters"
3108
+ [groupModel]="groupModel"
3109
+ [valuesModel]="valuesModel"
3110
+ (columnsUpdated)="onSideBarColumnsUpdated($event)"
3111
+ (groupModelUpdated)="onSideBarGroupModelUpdated($event)"
3112
+ (valuesModelUpdated)="onValuesModelUpdated($event)"
3113
+ (exportCsvClicked)="exportCsv({ mode: 'visible' })"
3114
+ (filterApplied)="onFilterApplied($event)"
3115
+ (conditionFilterChanged)="onConditionFilterChanged($event)"
3116
+ (clearAllFilters)="onClearAllFilters()">
3117
+ </ug-side-bar>
3118
+
3119
+
3120
+ <ug-header-menu
3121
+ [column]="activeMenuColumn"
3122
+ [isOpen]="isMenuOpen"
3123
+ [position]="menuPosition"
3124
+ [groupModel]="groupModel"
3125
+ (closeMenu)="onMenuClosed()"
3126
+ (sort)="onMenuSort($event)"
3127
+ (pin)="onMenuPin($event)"
3128
+ (autosize)="onMenuAutosize($event)"
3129
+ (group)="onMenuGroup($event)"
3130
+ (chooseColumns)="onMenuChooseColumns()">
3131
+ </ug-header-menu>
3132
+
3133
+ <ug-header-filter
3134
+ [column]="activeFilterColumn"
3135
+ [isOpen]="isFilterOpen"
3136
+ [position]="filterPosition"
3137
+ [uniqueValues]="activeFilterUniqueValues"
3138
+ [activeFilterSet]="getFilterSetForColumn(activeFilterColumn?.field)"
3139
+ (closeFilter)="onFilterClosed()"
3140
+ (filterApplied)="onFilterApplied($event)">
3141
+ </ug-header-filter>
3142
+
3143
+ <div class="ug-cc-overlay-container" *ngIf="isChooseColumnsOpen" [style.top.px]="chooseColumnsPosition.y" [style.left.px]="chooseColumnsPosition.x">
3144
+ <ug-choose-columns
3145
+ [columns]="columns"
3146
+ (columnsChanged)="onChooseColumnsUpdated($event)"
3147
+ (closePanel)="isChooseColumnsOpen = false">
3148
+ </ug-choose-columns>
3149
+ </div>
3150
+
3151
+ </div>
3152
+ `, isInline: true, styles: ["bways-grid{display:block;height:100%;width:100%}.ug-wrapper{display:flex;flex-direction:row;height:100%;width:100%;border:1px solid var(--ug-border-color);background-color:var(--ug-bg-color);box-sizing:border-box;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-header-color);overflow:hidden}.ug-main{display:flex;flex-direction:column;flex:1;min-width:0;overflow:hidden}.ug-body{flex:1;overflow:hidden;position:relative}.ug-viewport{height:100%;width:100%;overflow:auto}.ug-ssr-container{height:100%;width:100%;overflow:hidden}.ug-status-bar{display:flex;align-items:center;padding:0 16px;min-height:48px;border-top:1px solid var(--ug-border-color);font-weight:500;font-size:13px;color:var(--ug-header-color);background-color:var(--ug-footer-bg)}.ug-cc-overlay-container{position:fixed;z-index:1002}.ug-wrapper{--ug-border-color: #dde2eb;--ug-bg-color: #ffffff;--ug-header-bg: #f8f8f8;--ug-header-color: #181d1f;--ug-header-hover-bg: #ececec;--ug-row-bg: #ffffff;--ug-row-hover-bg: #f1f5f9;--ug-row-selected-bg: #dcebf7;--ug-primary-color: #1890ff;--ug-footer-bg: #ffffff;--ug-footer-color: #181d1f;--ug-icon-color: #68686e;--ug-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;--ug-font-size: 13px;--ug-header-font-weight: 500}.ug-wrapper.ag-theme-alpine-dark{--ug-border-color: #334155;--ug-bg-color: #1e293b;--ug-header-bg: #0f172a;--ug-header-color: #e2e8f0;--ug-header-hover-bg: #1e293b;--ug-row-bg: #1e293b;--ug-row-hover-bg: #334155;--ug-row-selected-bg: #0f172a;--ug-primary-color: #60a5fa;--ug-footer-bg: #0f172a;--ug-footer-color: #94a3b8;--ug-icon-color: #94a3b8}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3$1.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3$1.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3$1.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: HeaderComponent, selector: "ug-header", inputs: ["columns", "sortModel", "isAllSelected"], outputs: ["sortChanged", "columnsReordered", "columnResized", "headerCheckboxClicked", "menuClicked", "filterClicked"] }, { kind: "component", type: RowComponent, selector: "ug-row", inputs: ["columns", "row", "rowHeight", "isExpanded", "selectionVersion"], outputs: ["groupToggled"] }, { kind: "component", type: PaginationComponent, selector: "ug-pagination", inputs: ["totalCount", "pageSize", "currentPage"], outputs: ["pageChanged", "pageSizeChanged"] }, { kind: "component", type: HeaderMenuComponent, selector: "ug-header-menu", inputs: ["column", "isOpen", "position", "groupModel"], outputs: ["closeMenu", "sort", "pin", "autosize", "group", "chooseColumns"] }, { kind: "component", type: ChooseColumnsComponent, selector: "ug-choose-columns", inputs: ["columns"], outputs: ["columnsChanged", "closePanel"] }, { kind: "component", type: HeaderFilterComponent, selector: "ug-header-filter", inputs: ["column", "isOpen", "position", "uniqueValues", "activeFilterSet"], outputs: ["closeFilter", "filterApplied"] }, { kind: "component", type: SideBarComponent, selector: "ug-side-bar", inputs: ["columns", "rowData", "activeFilters", "groupModel", "valuesModel"], outputs: ["columnsUpdated", "groupModelUpdated", "valuesModelUpdated", "exportCsvClicked", "filterApplied", "conditionFilterChanged", "clearAllFilters"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3153
+ }
3154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: UltraGridComponent, decorators: [{
3155
+ type: Component,
3156
+ args: [{ selector: 'bways-grid', standalone: true, imports: [
3157
+ CommonModule,
3158
+ ScrollingModule,
3159
+ HeaderComponent,
3160
+ RowComponent,
3161
+ PaginationComponent,
3162
+ HeaderMenuComponent,
3163
+ ChooseColumnsComponent,
3164
+ HeaderFilterComponent,
3165
+ SideBarComponent
3166
+ ], template: `
3167
+ <div class="ug-wrapper" [ngClass]="config.theme || 'ag-theme-alpine'">
3168
+
3169
+ <div class="ug-main">
3170
+ <ug-header
3171
+ [columns]="visibleColumns"
3172
+ [sortModel]="sortModel"
3173
+ [isAllSelected]="isAllSelected"
3174
+ (sortChanged)="onSortChanged($event)"
3175
+ (columnsReordered)="onColumnsReordered($event)"
3176
+ (columnResized)="onColumnResized($event)"
3177
+ (headerCheckboxClicked)="onHeaderCheckboxClicked($event)"
3178
+ (menuClicked)="onHeaderMenuClicked($event)"
3179
+ (filterClicked)="onHeaderFilterClicked($event)">
3180
+ </ug-header>
3181
+
3182
+ <div class="ug-body">
3183
+ <ng-container *ngIf="isBrowser; else ssrPlaceholder">
3184
+ <cdk-virtual-scroll-viewport
3185
+ [itemSize]="rowHeight"
3186
+ class="ug-viewport">
3187
+
3188
+ <ug-row
3189
+ *cdkVirtualFor="let row of _internalDataSource || renderedLocalData; let i = index; trackBy: trackByRowId"
3190
+ [row]="row || getPlaceholderRow(i)"
3191
+ [columns]="visibleColumns"
3192
+ [rowHeight]="rowHeight"
3193
+ [isExpanded]="isGroupExpanded(row!)"
3194
+ [selectionVersion]="selectionVersion"
3195
+ (groupToggled)="toggleGroup(row!.id.toString())"
3196
+ (click)="onRowClicked(row)">
3197
+ </ug-row>
3198
+
3199
+ </cdk-virtual-scroll-viewport>
3200
+ </ng-container>
3201
+
3202
+ <ng-template #ssrPlaceholder>
3203
+ <div class="ug-ssr-container">
3204
+ <ug-row
3205
+ *ngFor="let row of ssrData; trackBy: trackByRowId"
3206
+ [row]="row"
3207
+ [columns]="visibleColumns"
3208
+ [rowHeight]="rowHeight"
3209
+ [selectionVersion]="selectionVersion">
3210
+ </ug-row>
3211
+ </div>
3212
+ </ng-template>
3213
+ </div>
3214
+
3215
+ <div class="ug-status-bar" *ngIf="config.statusBar">
3216
+ <span>Total Rows: {{ totalCount }}</span>
3217
+ <span style="margin-left: 16px;">Selected: {{ selectedRowsCount }}</span>
3218
+ </div>
3219
+
3220
+ <ug-pagination
3221
+ *ngIf="showPagination"
3222
+ [totalCount]="totalCount"
3223
+ [pageSize]="pageSize"
3224
+ [currentPage]="currentPage"
3225
+ (pageChanged)="onPageChanged($event)"
3226
+ (pageSizeChanged)="onPageSizeChanged($event)">
3227
+ </ug-pagination>
3228
+ </div>
3229
+
3230
+ <ug-side-bar
3231
+ *ngIf="config.sideBar"
3232
+ [columns]="columns"
3233
+ [rowData]="rowData"
3234
+ [activeFilters]="activeFilters"
3235
+ [groupModel]="groupModel"
3236
+ [valuesModel]="valuesModel"
3237
+ (columnsUpdated)="onSideBarColumnsUpdated($event)"
3238
+ (groupModelUpdated)="onSideBarGroupModelUpdated($event)"
3239
+ (valuesModelUpdated)="onValuesModelUpdated($event)"
3240
+ (exportCsvClicked)="exportCsv({ mode: 'visible' })"
3241
+ (filterApplied)="onFilterApplied($event)"
3242
+ (conditionFilterChanged)="onConditionFilterChanged($event)"
3243
+ (clearAllFilters)="onClearAllFilters()">
3244
+ </ug-side-bar>
3245
+
3246
+
3247
+ <ug-header-menu
3248
+ [column]="activeMenuColumn"
3249
+ [isOpen]="isMenuOpen"
3250
+ [position]="menuPosition"
3251
+ [groupModel]="groupModel"
3252
+ (closeMenu)="onMenuClosed()"
3253
+ (sort)="onMenuSort($event)"
3254
+ (pin)="onMenuPin($event)"
3255
+ (autosize)="onMenuAutosize($event)"
3256
+ (group)="onMenuGroup($event)"
3257
+ (chooseColumns)="onMenuChooseColumns()">
3258
+ </ug-header-menu>
3259
+
3260
+ <ug-header-filter
3261
+ [column]="activeFilterColumn"
3262
+ [isOpen]="isFilterOpen"
3263
+ [position]="filterPosition"
3264
+ [uniqueValues]="activeFilterUniqueValues"
3265
+ [activeFilterSet]="getFilterSetForColumn(activeFilterColumn?.field)"
3266
+ (closeFilter)="onFilterClosed()"
3267
+ (filterApplied)="onFilterApplied($event)">
3268
+ </ug-header-filter>
3269
+
3270
+ <div class="ug-cc-overlay-container" *ngIf="isChooseColumnsOpen" [style.top.px]="chooseColumnsPosition.y" [style.left.px]="chooseColumnsPosition.x">
3271
+ <ug-choose-columns
3272
+ [columns]="columns"
3273
+ (columnsChanged)="onChooseColumnsUpdated($event)"
3274
+ (closePanel)="isChooseColumnsOpen = false">
3275
+ </ug-choose-columns>
3276
+ </div>
3277
+
3278
+ </div>
3279
+ `, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["bways-grid{display:block;height:100%;width:100%}.ug-wrapper{display:flex;flex-direction:row;height:100%;width:100%;border:1px solid var(--ug-border-color);background-color:var(--ug-bg-color);box-sizing:border-box;font-family:var(--ug-font-family);font-size:var(--ug-font-size);color:var(--ug-header-color);overflow:hidden}.ug-main{display:flex;flex-direction:column;flex:1;min-width:0;overflow:hidden}.ug-body{flex:1;overflow:hidden;position:relative}.ug-viewport{height:100%;width:100%;overflow:auto}.ug-ssr-container{height:100%;width:100%;overflow:hidden}.ug-status-bar{display:flex;align-items:center;padding:0 16px;min-height:48px;border-top:1px solid var(--ug-border-color);font-weight:500;font-size:13px;color:var(--ug-header-color);background-color:var(--ug-footer-bg)}.ug-cc-overlay-container{position:fixed;z-index:1002}.ug-wrapper{--ug-border-color: #dde2eb;--ug-bg-color: #ffffff;--ug-header-bg: #f8f8f8;--ug-header-color: #181d1f;--ug-header-hover-bg: #ececec;--ug-row-bg: #ffffff;--ug-row-hover-bg: #f1f5f9;--ug-row-selected-bg: #dcebf7;--ug-primary-color: #1890ff;--ug-footer-bg: #ffffff;--ug-footer-color: #181d1f;--ug-icon-color: #68686e;--ug-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;--ug-font-size: 13px;--ug-header-font-weight: 500}.ug-wrapper.ag-theme-alpine-dark{--ug-border-color: #334155;--ug-bg-color: #1e293b;--ug-header-bg: #0f172a;--ug-header-color: #e2e8f0;--ug-header-hover-bg: #1e293b;--ug-row-bg: #1e293b;--ug-row-hover-bg: #334155;--ug-row-selected-bg: #0f172a;--ug-primary-color: #60a5fa;--ug-footer-bg: #0f172a;--ug-footer-color: #94a3b8;--ug-icon-color: #94a3b8}\n"] }]
3280
+ }], ctorParameters: () => [{ type: Object, decorators: [{
3281
+ type: Inject,
3282
+ args: [PLATFORM_ID]
3283
+ }] }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: GridFlattenerService }, { type: CsvExportService }], propDecorators: { columns: [{
3284
+ type: Input
3285
+ }], rowData: [{
3286
+ type: Input
3287
+ }], serverDataSource: [{
3288
+ type: Input
3289
+ }], config: [{
3290
+ type: Input
3291
+ }], pagination: [{
3292
+ type: Input
3293
+ }], pageSize: [{
3294
+ type: Input
3295
+ }], rowClicked: [{
3296
+ type: Output
3297
+ }], sortChanged: [{
3298
+ type: Output
3299
+ }] } });
3300
+
3301
+ const COMPONENTS = [
3302
+ UltraGridComponent,
3303
+ HeaderComponent,
3304
+ RowComponent,
3305
+ CellComponent,
3306
+ PaginationComponent,
3307
+ ColumnResizeDirective
3308
+ ];
3309
+ class BwaysGridModule {
3310
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BwaysGridModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
3311
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.18", ngImport: i0, type: BwaysGridModule, imports: [UltraGridComponent,
3312
+ HeaderComponent,
3313
+ RowComponent,
3314
+ CellComponent,
3315
+ PaginationComponent,
3316
+ ColumnResizeDirective,
3317
+ SideBarComponent], exports: [UltraGridComponent,
3318
+ HeaderComponent,
3319
+ RowComponent,
3320
+ CellComponent,
3321
+ PaginationComponent,
3322
+ ColumnResizeDirective,
3323
+ SideBarComponent] });
3324
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BwaysGridModule, imports: [UltraGridComponent,
3325
+ HeaderComponent,
3326
+ RowComponent,
3327
+ CellComponent,
3328
+ PaginationComponent,
3329
+ SideBarComponent] });
3330
+ }
3331
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: BwaysGridModule, decorators: [{
3332
+ type: NgModule,
3333
+ args: [{
3334
+ imports: [
3335
+ UltraGridComponent,
3336
+ HeaderComponent,
3337
+ RowComponent,
3338
+ CellComponent,
3339
+ PaginationComponent,
3340
+ ColumnResizeDirective,
3341
+ SideBarComponent
3342
+ ],
3343
+ exports: [
3344
+ UltraGridComponent,
3345
+ HeaderComponent,
3346
+ RowComponent,
3347
+ CellComponent,
3348
+ PaginationComponent,
3349
+ ColumnResizeDirective,
3350
+ SideBarComponent
3351
+ ]
3352
+ }]
3353
+ }] });
3354
+
3355
+ /**
3356
+ * Condition-based filter model for AG Grid-style advanced filtering.
3357
+ * Each column can have up to 2 conditions joined by AND/OR.
3358
+ */
3359
+
3360
+ class GridEngineService {
3361
+ _eventBus = new Subject();
3362
+ get events$() {
3363
+ return this._eventBus.asObservable();
3364
+ }
3365
+ dispatch(name, payload) {
3366
+ this._eventBus.next({ name, payload });
3367
+ }
3368
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridEngineService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3369
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridEngineService, providedIn: 'root' });
3370
+ }
3371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: GridEngineService, decorators: [{
3372
+ type: Injectable,
3373
+ args: [{
3374
+ providedIn: 'root'
3375
+ }]
3376
+ }] });
3377
+
3378
+ class ViewportManager {
3379
+ _scrollTop = 0;
3380
+ _viewportHeight = 600;
3381
+ _rowHeight = 40;
3382
+ _bufferSize = 20;
3383
+ get scrollTop() { return this._scrollTop; }
3384
+ set scrollTop(val) { this._scrollTop = val; }
3385
+ get viewportHeight() { return this._viewportHeight; }
3386
+ set viewportHeight(val) { this._viewportHeight = val; }
3387
+ get rowHeight() { return this._rowHeight; }
3388
+ set rowHeight(val) { this._rowHeight = val; }
3389
+ get bufferSize() { return this._bufferSize; }
3390
+ set bufferSize(val) { this._bufferSize = val; }
3391
+ getVisibleRange() {
3392
+ const startRowIndex = Math.floor(this._scrollTop / this._rowHeight);
3393
+ const endRowIndex = startRowIndex + Math.ceil(this._viewportHeight / this._rowHeight);
3394
+ const start = Math.max(0, startRowIndex - this._bufferSize);
3395
+ const end = endRowIndex + this._bufferSize;
3396
+ return { start, end };
3397
+ }
3398
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ViewportManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3399
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ViewportManager, providedIn: 'root' });
3400
+ }
3401
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: ViewportManager, decorators: [{
3402
+ type: Injectable,
3403
+ args: [{
3404
+ providedIn: 'root'
3405
+ }]
3406
+ }] });
3407
+
3408
+ class RowCache {
3409
+ _cache = new WeakMap();
3410
+ getRow(data) {
3411
+ return this._cache.get(data);
3412
+ }
3413
+ setRow(data, row) {
3414
+ if (data) {
3415
+ this._cache.set(data, row);
3416
+ }
3417
+ }
3418
+ clear() {
3419
+ this._cache = new WeakMap();
3420
+ }
3421
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: RowCache, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3422
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: RowCache, providedIn: 'root' });
3423
+ }
3424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: RowCache, decorators: [{
3425
+ type: Injectable,
3426
+ args: [{
3427
+ providedIn: 'root'
3428
+ }]
3429
+ }] });
3430
+
3431
+ /*
3432
+ * Public API Surface of bways-grid
3433
+ */
3434
+
3435
+ /**
3436
+ * Generated bundle index. Do not edit.
3437
+ */
3438
+
3439
+ export { BwaysGridModule, CellComponent, ColumnResizeDirective, ColumnToolPanelComponent, FilterToolPanelComponent, GridEngineService, HeaderComponent, InfiniteScrollDataSource, PaginationComponent, RowCache, RowComponent, SideBarComponent, UltraGridComponent, ViewportManager };
3440
+ //# sourceMappingURL=bways-grid.mjs.map