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.
- package/README.md +76 -0
- package/fesm2022/bways-grid.mjs +3440 -0
- package/fesm2022/bways-grid.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/bways-grid.module.d.ts +13 -0
- package/lib/components/cell/cell.component.d.ts +20 -0
- package/lib/components/choose-columns/choose-columns.component.d.ts +17 -0
- package/lib/components/column-tool-panel/column-tool-panel.component.d.ts +36 -0
- package/lib/components/filter-tool-panel/filter-tool-panel.component.d.ts +63 -0
- package/lib/components/header/header.component.d.ts +42 -0
- package/lib/components/header-filter/header-filter.component.d.ts +31 -0
- package/lib/components/header-menu/header-menu.component.d.ts +41 -0
- package/lib/components/pagination/pagination.component.d.ts +22 -0
- package/lib/components/row/row.component.d.ts +19 -0
- package/lib/components/side-bar/side-bar.component.d.ts +42 -0
- package/lib/components/ultra-grid/ultra-grid.component.d.ts +155 -0
- package/lib/core/grid-engine.service.d.ts +14 -0
- package/lib/core/grid-flattener.service.d.ts +8 -0
- package/lib/core/row-cache.d.ts +10 -0
- package/lib/core/viewport-manager.d.ts +21 -0
- package/lib/datasources/infinite-scroll.datasource.d.ts +17 -0
- package/lib/datasources/server-datasource.interface.d.ts +14 -0
- package/lib/directives/column-resize.directive.d.ts +18 -0
- package/lib/models/column.model.d.ts +21 -0
- package/lib/models/csv-export.model.d.ts +12 -0
- package/lib/models/filter.model.d.ts +18 -0
- package/lib/models/grid-config.model.d.ts +11 -0
- package/lib/models/row.model.d.ts +26 -0
- package/lib/services/csv-export.service.d.ts +11 -0
- package/lib/workers/generated/export.worker.code.d.ts +1 -0
- package/lib/workers/generated/sorting.worker.code.d.ts +1 -0
- package/package.json +23 -0
- package/public-api.d.ts +19 -0
- package/src/lib/workers/export.worker.ts +110 -0
- package/src/lib/workers/generated/export.worker.code.ts +7 -0
- package/src/lib/workers/generated/sorting.worker.code.ts +4 -0
- 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;">×</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;">×</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;">×</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;">×</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
|