@oneteme/jquery-organizer 0.0.2 → 0.0.3
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/esm2022/lib/models/organizer-chart-adapter.mjs +62 -32
- package/esm2022/lib/models/organizer-chart-config.mjs +37 -0
- package/esm2022/lib/models/organizer-config.interface.mjs +1 -1
- package/esm2022/lib/models/organizer-utils.mjs +10 -20
- package/esm2022/lib/organizer-button/organizer-button.component.mjs +32 -3
- package/esm2022/lib/slice-panel/slice-panel.component.mjs +3 -3
- package/fesm2022/oneteme-jquery-organizer.mjs +138 -54
- package/fesm2022/oneteme-jquery-organizer.mjs.map +1 -1
- package/lib/models/organizer-chart-adapter.d.ts +9 -12
- package/lib/models/organizer-chart-config.d.ts +32 -0
- package/lib/models/organizer-config.interface.d.ts +1 -0
- package/package.json +1 -1
|
@@ -83,32 +83,22 @@ function resolveMatchingTemplateId(templates, state, config) {
|
|
|
83
83
|
return sameOrganizerState(templateState, normalizedState);
|
|
84
84
|
})?.id;
|
|
85
85
|
}
|
|
86
|
-
function
|
|
87
|
-
const normalizedY = normalizeYSelection(
|
|
86
|
+
function normalizeStateCore(selectedX, selectedY, selectedYAggregate, selectedGroupBy, selectedSlices, config, originalState) {
|
|
87
|
+
const normalizedY = normalizeYSelection(selectedY, selectedYAggregate, config.yFields, originalState);
|
|
88
88
|
return {
|
|
89
|
-
|
|
90
|
-
selectedX: hasOption(config.xFields, template.xField) ? template.xField : baseState.selectedX,
|
|
89
|
+
selectedX: hasOption(config.xFields, selectedX) ? selectedX : undefined,
|
|
91
90
|
selectedY: normalizedY.selectedY,
|
|
92
91
|
selectedYAggregate: normalizedY.selectedYAggregate,
|
|
93
|
-
selectedGroupBy: hasOption(config.groups,
|
|
94
|
-
selectedSlices:
|
|
95
|
-
? template.selectedSlices.filter(sliceId => hasOption(config.slices, sliceId))
|
|
96
|
-
: (baseState.selectedSlices || []),
|
|
97
|
-
selectedTemplate: undefined,
|
|
92
|
+
selectedGroupBy: hasOption(config.groups, selectedGroupBy) ? selectedGroupBy : undefined,
|
|
93
|
+
selectedSlices: (selectedSlices || []).filter(sliceId => hasOption(config.slices, sliceId)),
|
|
98
94
|
};
|
|
99
95
|
}
|
|
96
|
+
function normalizeTemplateToComparableState(template, baseState, config) {
|
|
97
|
+
return normalizeStateCore(template.xField, template.yField, template.yAggregate, template.groupBy, template.selectedSlices, config, baseState);
|
|
98
|
+
}
|
|
100
99
|
function normalizeStateWithoutTemplate(state, config) {
|
|
101
100
|
const base = state || {};
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
...base,
|
|
105
|
-
selectedX: hasOption(config.xFields, base.selectedX) ? base.selectedX : undefined,
|
|
106
|
-
selectedY: normalizedY.selectedY,
|
|
107
|
-
selectedYAggregate: normalizedY.selectedYAggregate,
|
|
108
|
-
selectedGroupBy: hasOption(config.groups, base.selectedGroupBy) ? base.selectedGroupBy : undefined,
|
|
109
|
-
selectedSlices: (base.selectedSlices || []).filter(sliceId => hasOption(config.slices, sliceId)),
|
|
110
|
-
selectedTemplate: undefined,
|
|
111
|
-
};
|
|
101
|
+
return normalizeStateCore(base.selectedX, base.selectedY, base.selectedYAggregate, base.selectedGroupBy, base.selectedSlices, config, base);
|
|
112
102
|
}
|
|
113
103
|
function normalizeYSelection(selectedY, selectedYAggregate, yFields, fallbackState) {
|
|
114
104
|
if (!selectedY) {
|
|
@@ -179,7 +169,13 @@ class OrganizerButtonComponent {
|
|
|
179
169
|
this.fieldDataCache = new Map();
|
|
180
170
|
this.destroy$ = new Subject();
|
|
181
171
|
}
|
|
182
|
-
ngOnInit() {
|
|
172
|
+
ngOnInit() {
|
|
173
|
+
console.debug('[OrganizerButton] Initialized', {
|
|
174
|
+
configFields: this.config?.fields?.length ?? 0,
|
|
175
|
+
hasState: !!this.state,
|
|
176
|
+
hideMenuValues: this.hideMenuValues
|
|
177
|
+
});
|
|
178
|
+
}
|
|
183
179
|
ngOnDestroy() {
|
|
184
180
|
this.destroy$.next();
|
|
185
181
|
this.destroy$.complete();
|
|
@@ -197,6 +193,7 @@ class OrganizerButtonComponent {
|
|
|
197
193
|
return `${visible}/${total}`;
|
|
198
194
|
}
|
|
199
195
|
onFieldToggle(fieldId, visible) {
|
|
196
|
+
console.debug('[OrganizerButton] Field toggled', { fieldId, visible });
|
|
200
197
|
const visibleFields = this.state?.visibleFields || [];
|
|
201
198
|
const updated = visible
|
|
202
199
|
? [...visibleFields, fieldId]
|
|
@@ -204,25 +201,45 @@ class OrganizerButtonComponent {
|
|
|
204
201
|
this.emitChange('fieldToggled', { visibleFields: updated });
|
|
205
202
|
}
|
|
206
203
|
onXFieldSelect(fieldId) {
|
|
204
|
+
console.debug('[OrganizerButton] X field selected', { fieldId, current: this.state?.selectedX });
|
|
205
|
+
// Évite une requête inutile si le X est déjà sélectionné
|
|
206
|
+
if (this.state?.selectedX === fieldId) {
|
|
207
|
+
console.debug('[OrganizerButton] X field already selected, skipping emit');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
207
210
|
this.emitChange('xSelected', { selectedX: fieldId });
|
|
208
211
|
}
|
|
209
212
|
onYFieldSelect(fieldId) {
|
|
213
|
+
console.debug('[OrganizerButton] Y field selected', { fieldId, current: this.state?.selectedY });
|
|
214
|
+
// Évite une requête inutile si le Y est déjà sélectionné
|
|
215
|
+
if (this.state?.selectedY === fieldId) {
|
|
216
|
+
console.debug('[OrganizerButton] Y field already selected, skipping emit');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
210
219
|
this.emitChange('ySelected', {
|
|
211
220
|
selectedY: fieldId,
|
|
212
221
|
selectedYAggregate: undefined
|
|
213
222
|
});
|
|
214
223
|
}
|
|
215
224
|
onYAggregateSelect(yFieldId, aggregateId) {
|
|
225
|
+
console.debug('[OrganizerButton] Y aggregate selected', { yFieldId, aggregateId, current: this.state?.selectedYAggregate });
|
|
226
|
+
// Évite une requête inutile si l'aggregate est déjà sélectionné
|
|
227
|
+
if (this.state?.selectedYAggregate === aggregateId && this.state?.selectedY === yFieldId) {
|
|
228
|
+
console.debug('[OrganizerButton] Y aggregate already selected, skipping emit');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
216
231
|
this.emitChange('ySelected', {
|
|
217
232
|
selectedY: yFieldId,
|
|
218
233
|
selectedYAggregate: aggregateId
|
|
219
234
|
});
|
|
220
235
|
}
|
|
221
236
|
onGroupBySelect(groupId) {
|
|
237
|
+
console.debug('[OrganizerButton] Group by selected', { groupId });
|
|
222
238
|
const current = this.state.selectedGroupBy;
|
|
223
239
|
this.emitChange('groupBySelected', { selectedGroupBy: current === groupId ? undefined : groupId });
|
|
224
240
|
}
|
|
225
241
|
onTemplateSelect(templateId) {
|
|
242
|
+
console.debug('[OrganizerButton] Template selected', { templateId });
|
|
226
243
|
if (!this.config.templates?.length)
|
|
227
244
|
return;
|
|
228
245
|
const template = this.config.templates?.find(t => t.id === templateId);
|
|
@@ -233,6 +250,7 @@ class OrganizerButtonComponent {
|
|
|
233
250
|
this.syncSliceState(newState.selectedSlices);
|
|
234
251
|
}
|
|
235
252
|
onSliceClick(sliceId) {
|
|
253
|
+
console.debug('[OrganizerButton] Slice clicked', { sliceId });
|
|
236
254
|
if (this.config.onSliceClick) {
|
|
237
255
|
this.config.onSliceClick();
|
|
238
256
|
}
|
|
@@ -270,7 +288,8 @@ class OrganizerButtonComponent {
|
|
|
270
288
|
const handleData = (tasks) => {
|
|
271
289
|
this.sliceStateChange.emit({
|
|
272
290
|
sliceConfigs: [{ title: slice.label ?? sliceId, columnKey: sliceId }],
|
|
273
|
-
tasks
|
|
291
|
+
tasks,
|
|
292
|
+
filterApplied: false // Panel vient d'être ouvert, pas de filtre sélectionné encore
|
|
274
293
|
});
|
|
275
294
|
onLoaded?.();
|
|
276
295
|
};
|
|
@@ -848,11 +867,11 @@ class SlicePanelComponent {
|
|
|
848
867
|
}, 50);
|
|
849
868
|
}
|
|
850
869
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
851
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyData: "lazyData", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, host: { properties: { "class.collapsed": "this.hostCollapsed" } }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\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: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
870
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SlicePanelComponent, isStandalone: true, selector: "slice-panel", inputs: { sliceConfigs: "sliceConfigs", columns: "columns", data: "data", lazyData: "lazyData", lazyStatus: "lazyStatus", showToggle: "showToggle", showCounts: "showCounts", collapsedByDefault: "collapsedByDefault", alwaysShow: "alwaysShow" }, outputs: { filterChange: "filterChange", activeKeysChange: "activeKeysChange", dynamicSliceKeysChange: "dynamicSliceKeysChange", collapsedChange: "collapsedChange" }, host: { properties: { "class.collapsed": "this.hostCollapsed" } }, viewQueries: [{ propertyName: "slicePanelBodyRef", first: true, predicate: ["slicePanelBody"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: ["@keyframes slideInLeft{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:translate(0)}}:host{display:block;height:100%;min-height:0;box-sizing:border-box;animation:slideInLeft .3s cubic-bezier(.34,1.56,.64,1) forwards}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\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: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
852
871
|
}
|
|
853
872
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
|
|
854
873
|
type: Component,
|
|
855
|
-
args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: [":host{display:block;height:100%;min-height:0;box-sizing:border-box}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
874
|
+
args: [{ standalone: true, selector: 'slice-panel', imports: [CommonModule, MatIconModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"slice-panel-wrapper\" [class.collapsed]=\"isCollapsed\">\n <aside class=\"slice-panel\" [class.collapsed]=\"isCollapsed\">\n <div class=\"slice-panel-header\">\n <span *ngIf=\"!isCollapsed\" class=\"slice-panel-title\">\n {{ i18n.filterPanelTitle(slices.length) }}\n </span>\n <button\n *ngIf=\"showToggle\"\n type=\"button\"\n class=\"slice-toggle\"\n [attr.aria-label]=\"isCollapsed ? i18n.expandFilterTitle : i18n.collapseFilterTitle\"\n (click)=\"togglePanel()\"\n >\n <mat-icon class=\"slice-toggle-mat-icon\">{{ isCollapsed ? 'filter_list' : 'filter_list_off' }}</mat-icon>\n </button>\n </div>\n\n <div class=\"slice-panel-body\" *ngIf=\"!isCollapsed\" #slicePanelBody>\n <ng-container *ngFor=\"let slice of slices; let si = index; trackBy: trackBySliceIndex\">\n <div class=\"slice-title-row\" [attr.data-slice-index]=\"si\" (click)=\"toggleSliceCollapse(si)\">\n <span class=\"slice-title-chevron\" [class.expanded]=\"!isSliceCollapsed(si)\">\u203A</span>\n <mat-icon *ngIf=\"getSliceIcon(slice)\" class=\"slice-title-icon\">{{ getSliceIcon(slice) }}</mat-icon>\n <p class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</p>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n (click)=\"$event.stopPropagation(); removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div class=\"slice-section-items\" *ngIf=\"!isSliceCollapsed(si)\">\n <ng-container *ngIf=\"isSliceLoading(si); else sliceContent\">\n <div class=\"slice-lazy-loading\">\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n <span class=\"lazy-skeleton\"></span>\n </div>\n </ng-container>\n <ng-template #sliceContent>\n <ng-container *ngFor=\"let category of slice.categories; trackBy: trackByCategoryKey\">\n <button\n *ngIf=\"sliceCategoryCount(si, category.key) > 0\"\n class=\"slice-item\"\n type=\"button\"\n [class.active]=\"isSliceCategoryActive(si, category.key)\"\n [attr.data-category-key]=\"category.key\"\n [title]=\"category.label\"\n (click)=\"onSliceCategorySelected(si, category.key)\"\n >\n <span>{{ category.label }}</span>\n <span *ngIf=\"showCounts\">{{ sliceCategoryCount(si, category.key) }}</span>\n </button>\n </ng-container>\n <div *ngIf=\"isSliceEmpty(si)\" class=\"slice-empty-state\">{{ i18n.filterEmptyState }}</div>\n </ng-template>\n </div>\n\n <hr *ngIf=\"si < slices.length - 1\" class=\"slice-divider\" />\n </ng-container>\n </div>\n </aside>\n</div>\n", styles: ["@keyframes slideInLeft{0%{opacity:0;transform:translate(-24px)}to{opacity:1;transform:translate(0)}}:host{display:block;height:100%;min-height:0;box-sizing:border-box;animation:slideInLeft .3s cubic-bezier(.34,1.56,.64,1) forwards}.slice-panel-wrapper{height:100%;max-height:100%;overflow-y:auto;box-sizing:border-box}.slice-panel{margin-top:0;border:1px solid var(--jqt-border-color);border-radius:10px;background:var(--jqt-surface-color);display:flex;flex-direction:column;min-width:0;height:100%;overflow:hidden;box-sizing:border-box}.slice-panel-header{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;height:34px;padding:0 8px;margin-bottom:8px}.slice-panel-header:has(.slice-panel-title){justify-content:space-between;padding-left:12px}.slice-panel-body{flex:1 1 0;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:4px;padding:4px 10px 10px;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-panel-body::-webkit-scrollbar{width:2px}.slice-panel-body::-webkit-scrollbar-thumb{background:var(--jqt-primary-color);border-radius:4px}.slice-panel-title{margin:0;padding-top:2px;font-size:11px;font-weight:700;line-height:1;text-transform:uppercase;letter-spacing:.08em;color:var(--jqt-text-color);-webkit-user-select:none;user-select:none;align-self:center;display:inline-flex;align-items:center;gap:5px}.slice-panel-title .slice-panel-title-icon{font-size:16px;width:16px;height:16px;line-height:1;flex-shrink:0}.slice-section-items{display:flex;flex-direction:column;gap:2px;flex:0 0 auto;scrollbar-width:thin;scrollbar-color:var(--jqt-border-color) transparent}.slice-section-items::-webkit-scrollbar{width:2px}.slice-section-items .slice-empty-state{padding:8px 10px;font-size:12px;color:var(--jqt-text-muted, #9ca3af);font-style:italic;text-align:center}.slice-section-items::-webkit-scrollbar-thumb{background:var(--jqt-border-color);border-radius:4px}.slice-toggle{border:0;background:var(--jqt-surface-color);color:var(--jqt-text-color);border-radius:6px;width:22px;height:22px;padding:0;font-size:14px;text-align:center;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;line-height:1}.slice-toggle-mat-icon{font-size:16px;width:16px;height:16px;line-height:1}.slice-panel.collapsed{border-color:transparent;background:transparent;overflow:visible}.slice-panel.collapsed .slice-panel-header{height:34px;padding:0;justify-content:center;align-items:center}.slice-panel.collapsed .slice-toggle{width:34px;height:34px;border:1px solid var(--jqt-border-color);background:var(--jqt-surface-color);border-radius:8px}.slice-panel.collapsed .slice-toggle:hover{border-color:var(--jqt-text-muted)}.slice-panel.collapsed .slice-toggle .slice-toggle-mat-icon{font-size:18px;width:18px;height:18px}.slice-title-row{display:flex;align-items:center;justify-content:space-between;gap:6px;margin:4px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.slice-title-row:hover .slice-title{color:var(--jqt-text-color)}.slice-title-row:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-chevron{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--jqt-text-muted);transition:transform .15s ease;transform:rotate(0);position:relative;top:-1px}.slice-title-chevron.expanded{transform:rotate(90deg)}.slice-title{flex:1 1 auto;margin:0;font-size:14px;font-weight:600;color:var(--jqt-title-color)}.slice-title-icon{flex-shrink:0;font-size:14px!important;width:14px!important;height:14px!important;color:var(--jqt-title-color)}.slice-remove-btn{background:none;border:none;cursor:pointer;color:var(--jqt-border-color);font-size:12px;padding:0 2px;line-height:1;flex-shrink:0;opacity:.5;transition:opacity .15s,color .15s}.slice-remove-btn:hover{color:var(--jqt-text-secondary);opacity:1}.slice-title-row:hover .slice-remove-btn{opacity:.8}.slice-item{display:flex;justify-content:space-between;align-items:center;text-align:left;width:100%;padding:5px 10px 5px 16px;background:transparent;border:0;cursor:pointer;border-radius:8px;color:var(--jqt-text-color);box-sizing:border-box;min-width:0;gap:8px}.slice-item.active{background:var(--jqt-row-hover-bg);font-weight:600}.slice-item.slice-multi{padding-left:6px}.slice-item span:first-child{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slice-item span:last-child{flex:0 0 auto;min-width:1.2em;text-align:right}.slice-check-box{flex:0 0 16px;width:16px;height:16px;border:1.5px solid var(--jqt-text-muted);border-radius:4px;background:var(--jqt-surface-color);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:background .15s,border-color .15s}.slice-check-box.checked{background:var(--jqt-text-secondary);border-color:var(--jqt-text-secondary)}.slice-check-box.checked:after{content:\"\";display:block;width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;transform:rotate(-45deg) translateY(-1px)}.slice-divider{border:none;border-top:1px solid var(--jqt-border-color);margin:6px 0}.lazy-skeleton{display:block;height:12px;width:70%;border-radius:4px;background:linear-gradient(90deg,var(--jqt-border-color) 25%,var(--jqt-surface-alt-color) 50%,var(--jqt-border-color) 75%);background-size:200% 100%;animation:lazy-shimmer 1.4s ease-in-out infinite}.slice-lazy-loading{display:flex;flex-direction:column;gap:8px;padding:8px 10px}.slice-lazy-loading .lazy-skeleton{width:80%}.slice-lazy-loading .lazy-skeleton:nth-child(2){width:60%}.slice-lazy-loading .lazy-skeleton:nth-child(3){width:45%}@keyframes lazy-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
|
|
856
875
|
}], propDecorators: { sliceConfigs: [{
|
|
857
876
|
type: Input
|
|
858
877
|
}], columns: [{
|
|
@@ -917,16 +936,61 @@ const DEFAULT_MENU_ICONS = {
|
|
|
917
936
|
'custom': 'settings'
|
|
918
937
|
};
|
|
919
938
|
|
|
939
|
+
var SyntheticItemKeys;
|
|
940
|
+
(function (SyntheticItemKeys) {
|
|
941
|
+
SyntheticItemKeys["EMPTY"] = "__empty__";
|
|
942
|
+
SyntheticItemKeys["SYNTHETIC_MARKER"] = "__synthetic__";
|
|
943
|
+
})(SyntheticItemKeys || (SyntheticItemKeys = {}));
|
|
944
|
+
var SlicePanelI18nKeys;
|
|
945
|
+
(function (SlicePanelI18nKeys) {
|
|
946
|
+
SlicePanelI18nKeys["EXPAND_FILTER"] = "expandFilterTitle";
|
|
947
|
+
SlicePanelI18nKeys["COLLAPSE_FILTER"] = "collapseFilterTitle";
|
|
948
|
+
SlicePanelI18nKeys["FILTER_PANEL_TITLE_SINGLE"] = "filterPanelTitleSingle";
|
|
949
|
+
SlicePanelI18nKeys["FILTER_PANEL_TITLE_PLURAL"] = "filterPanelTitlePlural";
|
|
950
|
+
SlicePanelI18nKeys["FILTER_BLOCK_DEFAULT_TITLE"] = "filterBlockDefaultTitle";
|
|
951
|
+
SlicePanelI18nKeys["FILTER_EMPTY_STATE"] = "filterEmptyState";
|
|
952
|
+
})(SlicePanelI18nKeys || (SlicePanelI18nKeys = {}));
|
|
953
|
+
var OrganizerButtonEventType;
|
|
954
|
+
(function (OrganizerButtonEventType) {
|
|
955
|
+
OrganizerButtonEventType["FIELD_TOGGLED"] = "fieldToggled";
|
|
956
|
+
OrganizerButtonEventType["X_SELECTED"] = "xSelected";
|
|
957
|
+
OrganizerButtonEventType["Y_SELECTED"] = "ySelected";
|
|
958
|
+
OrganizerButtonEventType["GROUP_BY_SELECTED"] = "groupBySelected";
|
|
959
|
+
OrganizerButtonEventType["TEMPLATE_SELECTED"] = "templateSelected";
|
|
960
|
+
OrganizerButtonEventType["SLICE_SELECTED"] = "sliceSelected";
|
|
961
|
+
OrganizerButtonEventType["VIEW_SWITCHED"] = "viewSwitched";
|
|
962
|
+
})(OrganizerButtonEventType || (OrganizerButtonEventType = {}));
|
|
963
|
+
function getGroupItems(config) {
|
|
964
|
+
return config?.groups?.items ?? [];
|
|
965
|
+
}
|
|
966
|
+
function getIndicatorItems(config) {
|
|
967
|
+
return config?.indicators?.items ?? [];
|
|
968
|
+
}
|
|
969
|
+
function getFilterItems(config) {
|
|
970
|
+
return config?.filters?.items ?? [];
|
|
971
|
+
}
|
|
972
|
+
function getStackItems(indicator) {
|
|
973
|
+
return indicator?.extra?.stacks?.items ?? [];
|
|
974
|
+
}
|
|
975
|
+
|
|
920
976
|
function chartConfigToOrganizer(chartConfig, options = {}) {
|
|
921
|
-
if (!chartConfig)
|
|
977
|
+
if (!chartConfig) {
|
|
978
|
+
console.debug('[OrganizerChartAdapter] Empty chart config, returning empty organizer config');
|
|
922
979
|
return {};
|
|
980
|
+
}
|
|
981
|
+
console.debug('[OrganizerChartAdapter] Converting chart config to organizer', {
|
|
982
|
+
hasGroups: !!chartConfig.groups,
|
|
983
|
+
indicatorCount: chartConfig.indicators?.items?.length ?? 0,
|
|
984
|
+
filterCount: chartConfig.filters?.items?.length ?? 0
|
|
985
|
+
});
|
|
923
986
|
const { onFetchSliceData, onExportVisual, onExportData, switchView } = options;
|
|
924
|
-
const
|
|
925
|
-
const
|
|
987
|
+
const indicators = getIndicatorItems(chartConfig);
|
|
988
|
+
const activeIndicator = indicators.find(i => i.selected);
|
|
989
|
+
const baseXFields = getGroupItems(chartConfig)
|
|
926
990
|
.filter(g => !_isSynthetic(g))
|
|
927
991
|
.map(g => ({ id: g.key, label: g.menu.label || g.key }));
|
|
928
|
-
const yFields = buildYFields(
|
|
929
|
-
const stackFields = (activeIndicator
|
|
992
|
+
const yFields = buildYFields(indicators.map(i => ({ id: i.key, label: i.menu.label || i.key, group: i.group })));
|
|
993
|
+
const stackFields = getStackItems(activeIndicator)
|
|
930
994
|
.filter((s) => !_isSynthetic(s))
|
|
931
995
|
.map((s) => ({ id: s.key, label: s.menu.label || s.key }));
|
|
932
996
|
const seenKeys = new Set();
|
|
@@ -940,7 +1004,7 @@ function chartConfigToOrganizer(chartConfig, options = {}) {
|
|
|
940
1004
|
xFields: mergedGroupFields,
|
|
941
1005
|
yFields,
|
|
942
1006
|
groups: mergedGroupFields,
|
|
943
|
-
slices: (chartConfig
|
|
1007
|
+
slices: getFilterItems(chartConfig).map(f => ({ id: f.key, label: f.menu.label || f.key })),
|
|
944
1008
|
templates: chartConfig.templates,
|
|
945
1009
|
showReset: false,
|
|
946
1010
|
onFetchSliceData,
|
|
@@ -951,13 +1015,22 @@ function chartConfigToOrganizer(chartConfig, options = {}) {
|
|
|
951
1015
|
};
|
|
952
1016
|
}
|
|
953
1017
|
function chartConfigToState(chartConfig) {
|
|
954
|
-
if (!chartConfig)
|
|
1018
|
+
if (!chartConfig) {
|
|
1019
|
+
console.debug('[OrganizerChartAdapter] Empty chart config for state conversion');
|
|
955
1020
|
return {};
|
|
956
|
-
|
|
957
|
-
const
|
|
958
|
-
const
|
|
959
|
-
const
|
|
1021
|
+
}
|
|
1022
|
+
const indicators = getIndicatorItems(chartConfig);
|
|
1023
|
+
const activeIndicator = indicators.find(i => i.selected);
|
|
1024
|
+
const activeStack = getStackItems(activeIndicator).find((s) => s.selected);
|
|
1025
|
+
const activeGroup = getGroupItems(chartConfig).find(g => g.selected);
|
|
1026
|
+
const activeSlices = getFilterItems(chartConfig).filter(f => f.selected);
|
|
960
1027
|
const indicatorGroup = activeIndicator?.group;
|
|
1028
|
+
console.debug('[OrganizerChartAdapter] Current chart state', {
|
|
1029
|
+
activeGroup: activeGroup?.key,
|
|
1030
|
+
activeIndicator: activeIndicator?.key,
|
|
1031
|
+
activeStack: activeStack?.key,
|
|
1032
|
+
sliceCount: activeSlices.length
|
|
1033
|
+
});
|
|
961
1034
|
const state = {
|
|
962
1035
|
selectedX: activeGroup?.key,
|
|
963
1036
|
selectedY: indicatorGroup ?? activeIndicator?.key,
|
|
@@ -986,8 +1059,11 @@ function chartConfigToUnifiedState(chartConfig, viewMode = 'chart') {
|
|
|
986
1059
|
};
|
|
987
1060
|
}
|
|
988
1061
|
function applyOrganizerEventToChart(event, chartConfig) {
|
|
989
|
-
if (!chartConfig)
|
|
1062
|
+
if (!chartConfig) {
|
|
1063
|
+
console.debug('[OrganizerChartAdapter] Cannot apply event to empty chart config');
|
|
990
1064
|
return;
|
|
1065
|
+
}
|
|
1066
|
+
console.debug('[OrganizerChartAdapter] Applying organizer event', { eventType: event.type, state: event.state });
|
|
991
1067
|
const state = event.state;
|
|
992
1068
|
_cleanSynthetics(chartConfig);
|
|
993
1069
|
if (state.selectedX !== undefined)
|
|
@@ -1001,6 +1077,7 @@ function applyOrganizerEventToChart(event, chartConfig) {
|
|
|
1001
1077
|
event.resolvedYUnit = resolveYUnit(chartConfig, event.state.selectedY, event.state.selectedYAggregate);
|
|
1002
1078
|
}
|
|
1003
1079
|
function handleOrganizerChartEvent(event, chartConfig, currentBinding, options = {}) {
|
|
1080
|
+
console.debug('[OrganizerChartAdapter] Handling organizer event', { eventType: event.type });
|
|
1004
1081
|
if (event.type === 'viewSwitched') {
|
|
1005
1082
|
return {
|
|
1006
1083
|
binding: {
|
|
@@ -1029,7 +1106,7 @@ function resolveYUnit(chartConfig, selectedY, selectedYAggregate) {
|
|
|
1029
1106
|
if (!chartConfig || !selectedY)
|
|
1030
1107
|
return undefined;
|
|
1031
1108
|
const actualKey = resolveYKey(selectedY, selectedYAggregate);
|
|
1032
|
-
return chartConfig.
|
|
1109
|
+
return getIndicatorItems(chartConfig).find(i => i.key === actualKey)?.unit;
|
|
1033
1110
|
}
|
|
1034
1111
|
const _syntheticItems = new WeakSet();
|
|
1035
1112
|
function _makeSynthetic(item) {
|
|
@@ -1040,24 +1117,30 @@ function _isSynthetic(item) {
|
|
|
1040
1117
|
return _syntheticItems.has(item);
|
|
1041
1118
|
}
|
|
1042
1119
|
function _cleanSynthetics(chartConfig) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1120
|
+
if (chartConfig.groups?.items) {
|
|
1121
|
+
chartConfig.groups.items = chartConfig.groups.items.filter(g => !_isSynthetic(g));
|
|
1122
|
+
}
|
|
1123
|
+
if (chartConfig.indicators?.items) {
|
|
1124
|
+
chartConfig.indicators.items.forEach(ind => {
|
|
1125
|
+
if (ind.extra?.stacks?.items) {
|
|
1126
|
+
ind.extra.stacks.items = ind.extra.stacks.items.filter((s) => !_isSynthetic(s));
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1049
1130
|
}
|
|
1050
1131
|
function _applyXSelection(selectedX, chartConfig) {
|
|
1051
|
-
const
|
|
1132
|
+
const groupItems = getGroupItems(chartConfig);
|
|
1133
|
+
const isGroupItem = groupItems.some(g => g.key === selectedX);
|
|
1052
1134
|
if (isGroupItem) {
|
|
1053
|
-
|
|
1135
|
+
groupItems.forEach(g => { g.selected = g.key === selectedX; });
|
|
1054
1136
|
return;
|
|
1055
1137
|
}
|
|
1056
|
-
const
|
|
1138
|
+
const indicators = getIndicatorItems(chartConfig);
|
|
1139
|
+
const allStacks = indicators.flatMap(i => getStackItems(i));
|
|
1057
1140
|
const stackItem = allStacks.find(s => s.key === selectedX);
|
|
1058
1141
|
if (stackItem) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1142
|
+
groupItems.forEach(g => { g.selected = false; });
|
|
1143
|
+
groupItems.push(_makeSynthetic({
|
|
1061
1144
|
key: stackItem.key,
|
|
1062
1145
|
selected: true,
|
|
1063
1146
|
menu: stackItem.menu,
|
|
@@ -1069,14 +1152,15 @@ function _applyXSelection(selectedX, chartConfig) {
|
|
|
1069
1152
|
}
|
|
1070
1153
|
function _applyYSelection(selectedY, selectedYAggregate, chartConfig) {
|
|
1071
1154
|
const actualKey = resolveYKey(selectedY, selectedYAggregate);
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1155
|
+
const indicators = getIndicatorItems(chartConfig);
|
|
1156
|
+
indicators.forEach(i => { i.selected = i.key === actualKey; });
|
|
1157
|
+
indicators.forEach(i => {
|
|
1158
|
+
getStackItems(i).forEach((s) => { s.selected = false; });
|
|
1075
1159
|
});
|
|
1076
1160
|
}
|
|
1077
1161
|
function _applyGroupBySelection(effectiveGroupBy, chartConfig) {
|
|
1078
|
-
const activeIndicator = chartConfig.
|
|
1079
|
-
const stackItems = activeIndicator
|
|
1162
|
+
const activeIndicator = getIndicatorItems(chartConfig).find(i => i.selected);
|
|
1163
|
+
const stackItems = getStackItems(activeIndicator);
|
|
1080
1164
|
if (effectiveGroupBy === undefined) {
|
|
1081
1165
|
stackItems.forEach(s => { s.selected = false; });
|
|
1082
1166
|
return;
|
|
@@ -1086,7 +1170,7 @@ function _applyGroupBySelection(effectiveGroupBy, chartConfig) {
|
|
|
1086
1170
|
stackItems.forEach(s => { s.selected = s.key === effectiveGroupBy; });
|
|
1087
1171
|
return;
|
|
1088
1172
|
}
|
|
1089
|
-
const groupItem = chartConfig.
|
|
1173
|
+
const groupItem = getGroupItems(chartConfig).find(g => g.key === effectiveGroupBy);
|
|
1090
1174
|
if (groupItem && activeIndicator) {
|
|
1091
1175
|
stackItems.forEach(s => { s.selected = false; });
|
|
1092
1176
|
if (!activeIndicator.extra) {
|
|
@@ -1111,7 +1195,7 @@ function _applyGroupBySelection(effectiveGroupBy, chartConfig) {
|
|
|
1111
1195
|
}
|
|
1112
1196
|
}
|
|
1113
1197
|
function _applySliceSelection(selectedSlices, chartConfig) {
|
|
1114
|
-
chartConfig.
|
|
1198
|
+
getFilterItems(chartConfig).forEach(f => { f.selected = selectedSlices.includes(f.key); });
|
|
1115
1199
|
}
|
|
1116
1200
|
|
|
1117
1201
|
function humanizeKey(key) {
|