@oneteme/jquery-organizer 0.0.5 → 0.0.7

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.
@@ -9,7 +9,7 @@ import * as i3 from '@angular/material/menu';
9
9
  import { MatMenuModule } from '@angular/material/menu';
10
10
  import * as i4 from '@angular/material/divider';
11
11
  import { MatDividerModule } from '@angular/material/divider';
12
- import { Subject, isObservable, take, takeUntil as takeUntil$1 } from 'rxjs';
12
+ import { Subject, isObservable } from 'rxjs';
13
13
  import { takeUntil } from 'rxjs/operators';
14
14
 
15
15
  function buildYFields(indicators) {
@@ -41,10 +41,12 @@ function normalizeOrganizerState(state, config) {
41
41
  const base = state || {};
42
42
  const normalizedY = normalizeYSelection(base.selectedY, base.selectedYAggregate, config.yFields);
43
43
  const selectedX = hasOption(config.xFields, base.selectedX) ? base.selectedX : undefined;
44
+ const selectedChartType = hasOption(config.chartTypes, base.selectedChartType) ? base.selectedChartType : undefined;
44
45
  const selectedGroupBy = hasOption(config.groups, base.selectedGroupBy) ? base.selectedGroupBy : undefined;
45
46
  const selectedSlices = (base.selectedSlices || []).filter(sliceId => hasOption(config.slices, sliceId));
46
47
  const normalized = {
47
48
  ...base,
49
+ selectedChartType,
48
50
  selectedX,
49
51
  selectedY: normalizedY.selectedY,
50
52
  selectedYAggregate: normalizedY.selectedYAggregate,
@@ -62,6 +64,7 @@ function normalizeTemplateToState(template, currentState, config) {
62
64
  const nextState = {
63
65
  viewMode: base.viewMode,
64
66
  visibleFields: base.visibleFields,
67
+ selectedChartType: base.selectedChartType,
65
68
  selectedX: hasOption(config.xFields, template.xField) ? template.xField : base.selectedX,
66
69
  selectedY: normalizedY.selectedY,
67
70
  selectedYAggregate: normalizedY.selectedYAggregate,
@@ -168,15 +171,11 @@ class OrganizerButtonComponent {
168
171
  this.sliceStateChange = new EventEmitter();
169
172
  this.fieldDataCache = new Map();
170
173
  this.destroy$ = new Subject();
171
- }
172
- ngOnInit() {
173
- console.debug('[OrganizerButton] Initialized', {
174
- configFields: this.config?.fields?.length ?? 0,
175
- hasState: !!this.state,
176
- hideMenuValues: this.hideMenuValues
177
- });
174
+ this.destroyed = false;
175
+ this.sliceRequestId = 0;
178
176
  }
179
177
  ngOnDestroy() {
178
+ this.destroyed = true;
180
179
  this.destroy$.next();
181
180
  this.destroy$.complete();
182
181
  }
@@ -193,27 +192,27 @@ class OrganizerButtonComponent {
193
192
  return `${visible}/${total}`;
194
193
  }
195
194
  onFieldToggle(fieldId, visible) {
196
- console.debug('[OrganizerButton] Field toggled', { fieldId, visible });
197
- const visibleFields = this.state?.visibleFields || [];
198
- const updated = visible
199
- ? [...visibleFields, fieldId]
200
- : visibleFields.filter(f => f !== fieldId);
195
+ const current = this.state?.visibleFields
196
+ ?? (this.config.fields ?? []).filter(f => f.visible !== false).map(f => f.id);
197
+ let updated;
198
+ if (visible) {
199
+ updated = current.includes(fieldId) ? current : [...current, fieldId];
200
+ }
201
+ else {
202
+ updated = current.filter(f => f !== fieldId);
203
+ }
201
204
  this.emitChange('fieldToggled', { visibleFields: updated });
202
205
  }
203
206
  onXFieldSelect(fieldId) {
204
- console.debug('[OrganizerButton] X field selected', { fieldId, current: this.state?.selectedX });
205
207
  // Évite une requête inutile si le X est déjà sélectionné
206
208
  if (this.state?.selectedX === fieldId) {
207
- console.debug('[OrganizerButton] X field already selected, skipping emit');
208
209
  return;
209
210
  }
210
211
  this.emitChange('xSelected', { selectedX: fieldId });
211
212
  }
212
213
  onYFieldSelect(fieldId) {
213
- console.debug('[OrganizerButton] Y field selected', { fieldId, current: this.state?.selectedY });
214
214
  // Évite une requête inutile si le Y est déjà sélectionné
215
215
  if (this.state?.selectedY === fieldId) {
216
- console.debug('[OrganizerButton] Y field already selected, skipping emit');
217
216
  return;
218
217
  }
219
218
  this.emitChange('ySelected', {
@@ -222,10 +221,8 @@ class OrganizerButtonComponent {
222
221
  });
223
222
  }
224
223
  onYAggregateSelect(yFieldId, aggregateId) {
225
- console.debug('[OrganizerButton] Y aggregate selected', { yFieldId, aggregateId, current: this.state?.selectedYAggregate });
226
224
  // Évite une requête inutile si l'aggregate est déjà sélectionné
227
225
  if (this.state?.selectedYAggregate === aggregateId && this.state?.selectedY === yFieldId) {
228
- console.debug('[OrganizerButton] Y aggregate already selected, skipping emit');
229
226
  return;
230
227
  }
231
228
  this.emitChange('ySelected', {
@@ -234,12 +231,16 @@ class OrganizerButtonComponent {
234
231
  });
235
232
  }
236
233
  onGroupBySelect(groupId) {
237
- console.debug('[OrganizerButton] Group by selected', { groupId });
238
- const current = this.state.selectedGroupBy;
234
+ const current = this.state?.selectedGroupBy;
239
235
  this.emitChange('groupBySelected', { selectedGroupBy: current === groupId ? undefined : groupId });
240
236
  }
237
+ onChartTypeSelect(chartType) {
238
+ const option = this.config.chartTypes?.find(type => type.id === chartType);
239
+ if (!option || option.disabled || this.state?.selectedChartType === chartType)
240
+ return;
241
+ this.emitChange('chartTypeSelected', { selectedChartType: chartType });
242
+ }
241
243
  onTemplateSelect(templateId) {
242
- console.debug('[OrganizerButton] Template selected', { templateId });
243
244
  if (!this.config.templates?.length)
244
245
  return;
245
246
  const template = this.config.templates?.find(t => t.id === templateId);
@@ -250,7 +251,6 @@ class OrganizerButtonComponent {
250
251
  this.syncSliceState(newState.selectedSlices);
251
252
  }
252
253
  onSliceClick(sliceId) {
253
- console.debug('[OrganizerButton] Slice clicked', { sliceId });
254
254
  if (this.config.onSliceClick) {
255
255
  this.config.onSliceClick();
256
256
  }
@@ -274,6 +274,10 @@ class OrganizerButtonComponent {
274
274
  });
275
275
  }
276
276
  syncSliceState(selectedSlices, onLoaded) {
277
+ // Une nouvelle demande annule la précédente : seule la réponse la plus récente est prise en compte.
278
+ const requestId = ++this.sliceRequestId;
279
+ this.sliceSubscription?.unsubscribe();
280
+ this.sliceSubscription = undefined;
277
281
  const sliceId = selectedSlices?.[0];
278
282
  if (!sliceId || !this.config.onFetchSliceData) {
279
283
  this.sliceStateChange.emit(null);
@@ -288,6 +292,8 @@ class OrganizerButtonComponent {
288
292
  }
289
293
  const result = this.config.onFetchSliceData(sliceId);
290
294
  const handleData = (tasks) => {
295
+ if (this.destroyed || requestId !== this.sliceRequestId)
296
+ return;
291
297
  this.sliceStateChange.emit({
292
298
  sliceConfigs: [{ title: slice.label ?? sliceId, columnKey: sliceId }],
293
299
  tasks,
@@ -295,11 +301,18 @@ class OrganizerButtonComponent {
295
301
  });
296
302
  onLoaded?.();
297
303
  };
304
+ const handleError = (error) => {
305
+ if (this.destroyed || requestId !== this.sliceRequestId)
306
+ return;
307
+ console.error('[OrganizerButton] Slice data loading failed', error);
308
+ this.sliceStateChange.emit(null);
309
+ onLoaded?.();
310
+ };
298
311
  if (isObservable(result)) {
299
- result.pipe(takeUntil(this.destroy$)).subscribe({ next: handleData });
312
+ this.sliceSubscription = result.pipe(takeUntil(this.destroy$)).subscribe({ next: handleData, error: handleError });
300
313
  }
301
314
  else {
302
- result.then(handleData);
315
+ void result.then(handleData, handleError);
303
316
  }
304
317
  }
305
318
  async loadFieldData(fieldId) {
@@ -316,6 +329,9 @@ class OrganizerButtonComponent {
316
329
  const data = await this.config.onFetchFieldData(fieldId);
317
330
  this.fieldDataCache.set(fieldId, data);
318
331
  }
332
+ catch (error) {
333
+ console.error('[OrganizerButton] Field data loading failed', error);
334
+ }
319
335
  finally {
320
336
  this.loadingFieldId = undefined;
321
337
  this.cdr.markForCheck();
@@ -351,6 +367,7 @@ class OrganizerButtonComponent {
351
367
  hasMenuItemsBeforeActions() {
352
368
  return this.hasChartFields()
353
369
  || this.hasTableFields()
370
+ || (this.config.chartTypes?.length ?? 0) > 0
354
371
  || (this.config.groups?.length ?? 0) > 0
355
372
  || (this.config.slices?.length ?? 0) > 0
356
373
  || (this.config.templates?.length ?? 0) > 0;
@@ -402,14 +419,18 @@ class OrganizerButtonComponent {
402
419
  this.mainMenuTrigger?.closeMenu();
403
420
  }
404
421
  onReset() {
422
+ const visibleFields = this.config.fields
423
+ ?.filter(field => field.visible !== false)
424
+ .map(field => field.id);
405
425
  this.emitChange('reset', {
406
- visibleFields: [],
426
+ visibleFields,
407
427
  selectedX: undefined,
408
428
  selectedY: undefined,
409
429
  selectedYAggregate: undefined,
410
430
  selectedGroupBy: undefined,
411
431
  selectedSlices: []
412
432
  });
433
+ this.mainMenuTrigger?.closeMenu();
413
434
  }
414
435
  activeXLabel() {
415
436
  const id = this.state?.selectedX;
@@ -435,6 +456,12 @@ class OrganizerButtonComponent {
435
456
  return '';
436
457
  return this.config.groups?.find(g => g.id === id)?.label ?? id;
437
458
  }
459
+ activeChartTypeLabel() {
460
+ const id = this.state?.selectedChartType;
461
+ if (!id)
462
+ return '';
463
+ return this.config.chartTypes?.find(type => type.id === id)?.label ?? id;
464
+ }
438
465
  activeSlicesLabel() {
439
466
  const ids = this.state?.selectedSlices;
440
467
  if (!ids?.length)
@@ -453,6 +480,9 @@ class OrganizerButtonComponent {
453
480
  hasChartFields() {
454
481
  return (this.config.xFields?.length ?? 0) > 0 || (this.config.yFields?.length ?? 0) > 0;
455
482
  }
483
+ hasChartTypes() {
484
+ return (this.config.chartTypes?.length ?? 0) > 0;
485
+ }
456
486
  hasTableFields() {
457
487
  return (this.config.fields?.length ?? 0) > 0;
458
488
  }
@@ -462,6 +492,9 @@ class OrganizerButtonComponent {
462
492
  isYAggregateActive(yFieldId, aggregateId) {
463
493
  return this.state?.selectedY === yFieldId && this.state?.selectedYAggregate === aggregateId;
464
494
  }
495
+ isChartTypeActive(chartType) {
496
+ return this.state?.selectedChartType === chartType;
497
+ }
465
498
  emitChange(type, stateUpdate) {
466
499
  const isFullState = 'selectedX' in stateUpdate && 'selectedTemplate' in stateUpdate;
467
500
  const rawState = isFullState ? stateUpdate : { ...this.state, ...stateUpdate };
@@ -477,11 +510,11 @@ class OrganizerButtonComponent {
477
510
  this.viewChange.emit(event);
478
511
  }
479
512
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
480
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OrganizerButtonComponent, isStandalone: true, selector: "organizer-button", inputs: { config: "config", state: "state", hideMenuValues: "hideMenuValues" }, outputs: { viewChange: "viewChange", sliceStateChange: "sliceStateChange" }, viewQueries: [{ propertyName: "mainMenuTrigger", first: true, predicate: ["mainMenuTrigger"], descendants: true }], ngImport: i0, template: "<button type=\"button\" class=\"organizer-btn\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (mousedown)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (mousedown)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (mousedown)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (mousedown)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (mousedown)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (mousedown)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (mousedown)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (mousedown)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:block;height:34px;width:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:absolute;inset:0;display:block!important;width:18px!important;height:18px!important;margin:auto!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:#000}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;overflow:hidden}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\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: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
513
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: OrganizerButtonComponent, isStandalone: true, selector: "organizer-button", inputs: { config: "config", state: "state", hideMenuValues: "hideMenuValues" }, outputs: { viewChange: "viewChange", sliceStateChange: "sliceStateChange" }, viewQueries: [{ propertyName: "mainMenuTrigger", first: true, predicate: ["mainMenuTrigger"], descendants: true }], ngImport: i0, template: "<button type=\"button\" class=\"organizer-btn\" [class.organizer-btn--labeled]=\"!!config.buttonLabel\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"config.buttonLabel || 'Personnaliser la vue'\" [attr.title]=\"config.buttonLabel || 'Personnaliser la vue'\">\n <mat-icon *ngIf=\"config.showButtonIcon !== false\" class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n <span *ngIf=\"config.buttonLabel\" class=\"organizer-btn-label\">{{ config.buttonLabel }}</span>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Type -->\n <button *ngIf=\"hasChartTypes()\" mat-menu-item [matMenuTriggerFor]=\"chartTypesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Type</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeChartTypeLabel()\">{{ activeChartTypeLabel() }}</span>\n </button>\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <mat-divider *ngIf=\"config.showReset === true\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showReset === true\" mat-menu-item type=\"button\" class=\"organizer-menu-reset\" (click)=\"onReset()\">\n <mat-icon class=\"organizer-menu-export-icon\">restart_alt</mat-icon>\n <span class=\"organizer-menu-label\">R\u00E9initialiser la vue</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : types de graphique -->\n<mat-menu #chartTypesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let chartType of config.chartTypes\"\n [class.active]=\"isChartTypeActive(chartType.id)\"\n [disabled]=\"chartType.disabled\"\n (click)=\"onChartTypeSelect(chartType.id)\">\n <span class=\"organizer-submenu-check\">{{ isChartTypeActive(chartType.id) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"chartType.icon\" class=\"organizer-submenu-icon\">{{ chartType.icon }}</mat-icon>\n {{ chartType.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (click)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (click)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (click)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (click)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (click)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (click)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (click)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (click)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--jqt-organizer-button-height, 34px);width:var(--jqt-organizer-button-width, 34px);min-width:var(--jqt-organizer-button-min-width, 34px);border:1px solid var(--jqt-organizer-button-border-color, #e0e0e0);border-radius:var(--jqt-organizer-button-radius, 8px);background:var(--jqt-organizer-button-background, #fff);padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:var(--jqt-organizer-button-hover-border-color, #9e9e9e);background:var(--jqt-organizer-button-hover-background, var(--jqt-organizer-button-background, #fff))}.organizer-btn--labeled{width:auto;padding:0 12px}.organizer-btn-label{font-size:13px;font-weight:600;line-height:1;white-space:nowrap}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:static;display:block!important;width:18px!important;height:18px!important;margin:0!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:var(--jqt-organizer-button-icon-color, #000000)}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1 1 auto;min-width:0;max-width:calc(100% - 20px);overflow:hidden;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-label,.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{display:block}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\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: MatButtonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i3.MatMenu, selector: "mat-menu", exportAs: ["matMenu"] }, { kind: "component", type: i3.MatMenuItem, selector: "[mat-menu-item]", inputs: ["disabled", "disableRipple", "role"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i3.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i3.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
481
514
  }
482
515
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: OrganizerButtonComponent, decorators: [{
483
516
  type: Component,
484
- args: [{ standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule, MatDividerModule], selector: 'organizer-button', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button type=\"button\" class=\"organizer-btn\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"'View'\">\n <mat-icon class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (mousedown)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (mousedown)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (mousedown)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (mousedown)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (mousedown)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (mousedown)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (mousedown)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (mousedown)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (mousedown)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (mousedown)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:block;height:34px;width:34px;border:1px solid #e0e0e0;border-radius:8px;background:#fff;padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:#9e9e9e}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:absolute;inset:0;display:block!important;width:18px!important;height:18px!important;margin:auto!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:#000}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mdc-list-item__primary-text{display:flex;align-items:center;width:100%;overflow:hidden}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"] }]
517
+ args: [{ standalone: true, imports: [CommonModule, MatButtonModule, MatIconModule, MatMenuModule, MatDividerModule], selector: 'organizer-button', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button type=\"button\" class=\"organizer-btn\" [class.organizer-btn--labeled]=\"!!config.buttonLabel\" [matMenuTriggerFor]=\"viewMenu\" #mainMenuTrigger=\"matMenuTrigger\" [attr.aria-label]=\"config.buttonLabel || 'Personnaliser la vue'\" [attr.title]=\"config.buttonLabel || 'Personnaliser la vue'\">\n <mat-icon *ngIf=\"config.showButtonIcon !== false\" class=\"organizer-btn-icon\">{{ config.buttonIcon || 'settings' }}</mat-icon>\n <span *ngIf=\"config.buttonLabel\" class=\"organizer-btn-label\">{{ config.buttonLabel }}</span>\n</button>\n\n<mat-menu #viewMenu=\"matMenu\" panelClass=\"organizer-menu-panel\">\n\n <!-- Type -->\n <button *ngIf=\"hasChartTypes()\" mat-menu-item [matMenuTriggerFor]=\"chartTypesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Type</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeChartTypeLabel()\">{{ activeChartTypeLabel() }}</span>\n </button>\n\n <!-- Champs : mode chart (X + Y) -->\n <button *ngIf=\"hasChartFields()\" mat-menu-item [matMenuTriggerFor]=\"champsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && (activeXLabel() || activeYLabel())\">\n {{ activeXLabel() }}<ng-container *ngIf=\"activeXLabel() && activeYLabel()\"> / </ng-container>{{ activeYLabel() }}\n </span>\n </button>\n\n <!-- Champs : mode table (toggle colonnes) -->\n <button *ngIf=\"hasTableFields()\" mat-menu-item [matMenuTriggerFor]=\"fieldsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Champs</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues\">{{ fieldsSummary() }}</span>\n </button>\n\n <!-- Grouper par -->\n <button *ngIf=\"config.groups?.length\" mat-menu-item [matMenuTriggerFor]=\"groupsSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Grouper par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeGroupByLabel()\">{{ activeGroupByLabel() }}</span>\n </button>\n\n <!-- Filtrer par -->\n <button *ngIf=\"config.slices?.length\" mat-menu-item [matMenuTriggerFor]=\"slicesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Filtrer par</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeSlicesLabel()\">{{ activeSlicesLabel() }}</span>\n </button>\n\n <!-- Template -->\n <button *ngIf=\"config.templates?.length\" mat-menu-item [matMenuTriggerFor]=\"templateSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Template</span>\n <span class=\"organizer-menu-value\" *ngIf=\"!hideMenuValues && activeTemplateLabel()\">{{ activeTemplateLabel() }}</span>\n </button>\n\n <!-- Actions du visuel courant -->\n <mat-divider *ngIf=\"hasActions() && hasMenuItemsBeforeActions()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasActions() && hasOtherMenuItems()\" mat-menu-item [matMenuTriggerFor]=\"actionsSubMenu\" class=\"organizer-menu-trigger\">\n <mat-icon *ngIf=\"config.actions?.icon\" class=\"organizer-menu-export-icon\">{{ config.actions?.icon }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ actionsLabel() }}</span>\n </button>\n\n <!-- Actions directes lorsqu'il n'y a aucun autre item principal -->\n <ng-container *ngIf=\"hasActions() && !hasOtherMenuItems()\">\n <button *ngIf=\"isCopyActionVisible()\" mat-menu-item type=\"button\" (click)=\"onCopyVisual()\">\n <mat-icon class=\"organizer-menu-export-icon\">content_copy</mat-icon>\n <span class=\"organizer-menu-label\">Copier</span>\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" mat-menu-item type=\"button\" (click)=\"onToggleFullscreen()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}</span>\n </button>\n </ng-container>\n\n <!-- Export direct (mode table) -->\n <mat-divider *ngIf=\"config.showExport\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showExport && !hasExportSubMenu()\" mat-menu-item type=\"button\" class=\"organizer-menu-export\" (click)=\"onExport()\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Export graphique (mode chart) \u2014 ouvre un sous-menu -->\n <button *ngIf=\"config.showExport && hasExportSubMenu()\" mat-menu-item [matMenuTriggerFor]=\"exportSubMenu\" class=\"organizer-menu-trigger organizer-menu-export\">\n <mat-icon class=\"organizer-menu-export-icon\">download</mat-icon>\n <span class=\"organizer-menu-label\">Exporter</span>\n </button>\n\n <!-- Pr\u00E9f\u00E9rences -->\n <mat-divider *ngIf=\"config.showPreferences\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showPreferences\" mat-menu-item [matMenuTriggerFor]=\"preferencesSubMenu\" class=\"organizer-menu-trigger\">\n <span class=\"organizer-menu-label\">Pr\u00E9f\u00E9rences</span>\n </button>\n\n <mat-divider *ngIf=\"config.showReset === true\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"config.showReset === true\" mat-menu-item type=\"button\" class=\"organizer-menu-reset\" (click)=\"onReset()\">\n <mat-icon class=\"organizer-menu-export-icon\">restart_alt</mat-icon>\n <span class=\"organizer-menu-label\">R\u00E9initialiser la vue</span>\n </button>\n\n <!-- Switch chart / table -->\n <mat-divider *ngIf=\"hasSwitchView()\" class=\"organizer-divider\"></mat-divider>\n <button *ngIf=\"hasSwitchView()\" mat-menu-item type=\"button\" class=\"organizer-menu-trigger\" (click)=\"onSwitchView()\">\n <mat-icon class=\"organizer-menu-export-icon\">{{ config.switchView?.currentView === 'chart' ? 'table_chart' : 'bar_chart' }}</mat-icon>\n <span class=\"organizer-menu-label\">{{ config.switchView?.currentView === 'chart' ? 'Tableau' : 'Graphique' }}</span>\n </button>\n\n</mat-menu>\n\n<!-- Sous-menu : types de graphique -->\n<mat-menu #chartTypesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let chartType of config.chartTypes\"\n [class.active]=\"isChartTypeActive(chartType.id)\"\n [disabled]=\"chartType.disabled\"\n (click)=\"onChartTypeSelect(chartType.id)\">\n <span class=\"organizer-submenu-check\">{{ isChartTypeActive(chartType.id) ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"chartType.icon\" class=\"organizer-submenu-icon\">{{ chartType.icon }}</mat-icon>\n {{ chartType.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : actions du visuel -->\n<mat-menu #actionsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"isCopyActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onCopyVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">content_copy</mat-icon>\n Copier\n </button>\n <button *ngIf=\"isFullscreenActionVisible()\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onToggleFullscreen()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">{{ config.isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>\n {{ config.isFullscreen ? 'Quitter le plein \u00E9cran' : 'Plein \u00E9cran' }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Export graphique -->\n<mat-menu #exportSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button *ngIf=\"config.onExportVisual\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportVisual()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">image</mat-icon>\n Visuel\n </button>\n <button *ngIf=\"config.onExportData\" type=\"button\" class=\"organizer-submenu-item\"\n (click)=\"onExportData()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">table_chart</mat-icon>\n Donn\u00E9es\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Champs chart (X + Y) -->\n<mat-menu #champsSubMenu=\"matMenu\" panelClass=\"organizer-champs-panel\">\n <div class=\"organizer-champs-wrapper\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n\n <!-- Bloc X -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.xFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe horizontal \u2014 X</span>\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let xField of config.xFields\"\n [class.active]=\"state?.selectedX === xField.id\"\n [disabled]=\"xField.disabled\"\n (click)=\"onXFieldSelect(xField.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedX === xField.id ? '\u2713' : '' }}</span>\n {{ xField.label }}\n </button>\n </div>\n\n <mat-divider *ngIf=\"config.xFields?.length && config.yFields?.length\" class=\"organizer-champs-divider\"></mat-divider>\n\n <!-- Bloc Y -->\n <div class=\"organizer-champs-block\" *ngIf=\"config.yFields?.length\">\n <span class=\"organizer-champs-block-title\">Axe vertical \u2014 Y</span>\n <ng-container *ngFor=\"let yField of config.yFields\">\n\n <!-- Y simple (Count sans agr\u00E9gat ni stack) -->\n <!-- Y simple (Count ou m\u00E9trique directe) -->\n <button *ngIf=\"!yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n (click)=\"onYFieldSelect(yField.id)\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n </button>\n\n <!-- Y avec agr\u00E9gats \u2192 sous-menu -->\n <button *ngIf=\"yField.aggregates?.length\"\n type=\"button\" class=\"organizer-submenu-item organizer-submenu-item--has-children\"\n [class.active]=\"isYActive(yField.id)\"\n [disabled]=\"yField.disabled\"\n [matMenuTriggerFor]=\"yAggSubMenu\"\n [matMenuTriggerData]=\"{ yField: yField }\"\n (mousedown)=\"$event.stopPropagation()\">\n <span class=\"organizer-submenu-check\">{{ isYActive(yField.id) ? '\u2713' : '' }}</span>\n {{ yField.label }}\n <mat-icon class=\"organizer-submenu-arrow\">chevron_right</mat-icon>\n </button>\n\n </ng-container>\n </div>\n\n </div>\n</mat-menu>\n\n<!-- Sous-menu dynamique : agr\u00E9gats d'un champ Y -->\n<mat-menu #yAggSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <ng-template matMenuContent let-yField=\"yField\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let agg of yField.aggregates\"\n [class.active]=\"isYAggregateActive(yField.id, agg.id)\"\n (click)=\"onYAggregateSelect(yField.id, agg.id)\">\n <span class=\"organizer-submenu-check\">{{ isYAggregateActive(yField.id, agg.id) ? '\u2713' : '' }}</span>\n {{ agg.label }}\n </button>\n </div>\n </ng-template>\n</mat-menu>\n\n<!-- Sous-menu : Champs table (toggle colonnes) -->\n<mat-menu #fieldsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let field of config.fields\"\n [class.active]=\"isFieldVisible(field.id)\"\n (click)=\"onFieldToggle(field.id, !isFieldVisible(field.id))\">\n <span class=\"organizer-submenu-check\">{{ isFieldVisible(field.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"field.icon\" class=\"organizer-submenu-icon\">{{ field.icon }}</mat-icon> {{ field.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Grouper par -->\n<mat-menu #groupsSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let group of config.groups\"\n [class.active]=\"state?.selectedGroupBy === group.id\"\n (click)=\"onGroupBySelect(group.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedGroupBy === group.id ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"group.icon\" class=\"organizer-submenu-icon\">{{ group.icon }}</mat-icon> {{ group.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Filtrer par -->\n<mat-menu #slicesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let slice of config.slices\"\n [class.active]=\"state?.selectedSlices?.includes(slice.id)\"\n (click)=\"onSliceClick(slice.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedSlices?.includes(slice.id) ? '\u2713' : '' }}</span> <mat-icon *ngIf=\"slice.icon\" class=\"organizer-submenu-icon\">{{ slice.icon }}</mat-icon> {{ slice.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Template -->\n<mat-menu #templateSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\"\n *ngFor=\"let tmpl of config.templates\"\n [class.active]=\"state?.selectedTemplate === tmpl.id\"\n [title]=\"tmpl.description || ''\"\n (click)=\"onTemplateSelect(tmpl.id)\">\n <span class=\"organizer-submenu-check\">{{ state?.selectedTemplate === tmpl.id ? '\u2713' : '' }}</span>\n <mat-icon *ngIf=\"tmpl.icon\" class=\"organizer-submenu-icon\">{{ tmpl.icon }}</mat-icon>\n {{ tmpl.label }}\n </button>\n </div>\n</mat-menu>\n\n<!-- Sous-menu : Pr\u00E9f\u00E9rences -->\n<mat-menu #preferencesSubMenu=\"matMenu\" panelClass=\"organizer-submenu-panel\">\n <div class=\"organizer-submenu\"\n (mousedown)=\"$event.stopPropagation()\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"$event.stopPropagation()\">\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesEdit()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">edit</mat-icon>\u00C9diter\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" (click)=\"onPreferencesSave()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">save</mat-icon>Sauvegarder\n </button>\n <button type=\"button\" class=\"organizer-submenu-item\" [disabled]=\"!config.hasSavedPreferences\" (click)=\"onPreferencesClear()\">\n <span class=\"organizer-submenu-check\"></span>\n <mat-icon class=\"organizer-submenu-icon\">delete_outline</mat-icon>R\u00E9initialiser\n </button>\n </div>\n</mat-menu>\n\n", styles: [":host{display:inline-flex;align-items:center;justify-content:center;line-height:0}.organizer-btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:8px;height:var(--jqt-organizer-button-height, 34px);width:var(--jqt-organizer-button-width, 34px);min-width:var(--jqt-organizer-button-min-width, 34px);border:1px solid var(--jqt-organizer-button-border-color, #e0e0e0);border-radius:var(--jqt-organizer-button-radius, 8px);background:var(--jqt-organizer-button-background, #fff);padding:0;overflow:hidden;cursor:pointer;box-sizing:border-box}.organizer-btn:hover{border-color:var(--jqt-organizer-button-hover-border-color, #9e9e9e);background:var(--jqt-organizer-button-hover-background, var(--jqt-organizer-button-background, #fff))}.organizer-btn--labeled{width:auto;padding:0 12px}.organizer-btn-label{font-size:13px;font-weight:600;line-height:1;white-space:nowrap}.organizer-btn-icon.mat-icon,.organizer-btn-icon.material-icons{position:static;display:block!important;width:18px!important;height:18px!important;margin:0!important;padding:0!important;font-size:18px!important;line-height:18px!important;text-align:center;vertical-align:middle;transform:none;color:var(--jqt-organizer-button-icon-color, #000000)}.mat-mdc-menu-panel:has(.organizer-menu-label){min-width:170px!important;max-width:320px!important;overflow:visible!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .mat-mdc-menu-content{padding:4px 0!important}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item{min-height:42px;padding:0 14px;display:flex;align-items:center;box-sizing:border-box}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .mat-mdc-menu-item-text{display:flex;align-items:center;flex:1 1 auto;min-width:0;max-width:calc(100% - 20px);overflow:hidden;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-label,.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mat-mdc-menu-panel:has(.organizer-menu-label) .organizer-menu-trigger.mat-mdc-menu-item .organizer-menu-value{display:block}.organizer-menu-label{font-size:13px;font-weight:500;color:#212121;flex:0 0 auto;white-space:nowrap;padding-right:8px}.organizer-menu-value{font-size:12px;color:#757575;flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:right}.organizer-submenu{padding:4px 6px;min-width:180px;box-sizing:border-box}.organizer-submenu .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-submenu .organizer-submenu-item:hover{background:#f5f5f5}.organizer-submenu .organizer-submenu-item.active{font-weight:600}.organizer-submenu .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-submenu-icon{font-size:14px!important;width:14px!important;height:14px!important;color:#212121;margin-right:4px}.organizer-menu-export.mat-mdc-menu-item{min-height:42px;padding:0 14px}.organizer-menu-export-icon{font-size:16px!important;width:16px!important;height:16px!important;color:#424242;margin-right:8px}.organizer-divider{margin:4px 0!important}.organizer-champs-wrapper{padding:4px 6px;min-width:200px;box-sizing:border-box;display:flex;flex-direction:column}.organizer-champs-block{display:flex;flex-direction:column}.organizer-champs-block-title{font-size:10px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:#9e9e9e;padding:6px 2px 2px}.organizer-champs-divider{margin:4px 0!important}.organizer-champs-wrapper .organizer-submenu-item{display:flex;align-items:center;gap:6px;width:100%;padding:7px 8px;border:none;background:transparent;border-radius:6px;font-size:13px;font-family:inherit;color:#212121;cursor:pointer;text-align:left;box-sizing:border-box;white-space:nowrap}.organizer-champs-wrapper .organizer-submenu-item:hover:not([disabled]){background:#f5f5f5}.organizer-champs-wrapper .organizer-submenu-item.active{font-weight:600}.organizer-champs-wrapper .organizer-submenu-item[disabled]{opacity:.38;cursor:default}.organizer-champs-wrapper .organizer-submenu-check{flex-shrink:0;width:16px;text-align:center;font-size:12px;color:#212121}.organizer-champs-wrapper .organizer-submenu-item--has-children{padding-right:8px}.organizer-champs-wrapper .organizer-submenu-arrow{font-size:18px!important;width:18px!important;height:18px!important;color:#00000061;margin-left:auto}\n"] }]
485
518
  }], ctorParameters: function () { return [{ type: i0.ChangeDetectorRef }]; }, propDecorators: { config: [{
486
519
  type: Input
487
520
  }], state: [{
@@ -511,7 +544,6 @@ class SlicePanelComponent {
511
544
  this.sliceConfigs = [];
512
545
  this.columns = [];
513
546
  this.data = [];
514
- this.lazyData = new Map();
515
547
  this.lazyStatus = new Map();
516
548
  this.showToggle = true;
517
549
  this.showCounts = true;
@@ -531,23 +563,28 @@ class SlicePanelComponent {
531
563
  this._countCache = new Map();
532
564
  this.trackBySliceIndex = (index, _) => index;
533
565
  this.trackByCategoryKey = (_, cat) => cat.key;
566
+ this._columnIndex = new Map();
534
567
  }
535
568
  get hostCollapsed() { return this.isCollapsed; }
536
569
  ngOnInit() {
537
570
  this.isCollapsed = this.collapsedByDefault;
538
571
  }
539
572
  ngOnChanges(changes) {
540
- if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']) {
573
+ if (changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyStatus']) {
574
+ const hadActiveFilters = this._hasActiveFilters();
541
575
  this._refreshDynamicSlices();
542
576
  this._rebuildCache();
543
- const shouldReemitFilter = !!(changes['data'] || changes['columns'] || changes['lazyData'] || changes['lazyStatus']);
544
- const hasActiveFilters = [...this.activeKeysBySlice.values()].some(s => s.size > 0);
545
- if (hasActiveFilters && shouldReemitFilter) {
577
+ const shouldReemitFilter = !!(changes['sliceConfigs'] || changes['data'] || changes['columns'] || changes['lazyStatus']);
578
+ // Un slice retiré de la config emporte sa sélection : le parent doit en être informé.
579
+ if (shouldReemitFilter && (hadActiveFilters || this._hasActiveFilters())) {
546
580
  this._emitFilter();
547
581
  }
548
582
  this._cdr.markForCheck();
549
583
  }
550
584
  }
585
+ _hasActiveFilters() {
586
+ return [...this.activeKeysBySlice.values()].some(s => s.size > 0);
587
+ }
551
588
  get showPanel() {
552
589
  const hasCfg = (this.sliceConfigs || []).length > 0 || this._dynamicSlices.length > 0;
553
590
  if (this.alwaysShow)
@@ -656,14 +693,16 @@ class SlicePanelComponent {
656
693
  return !this.activeKeysBySlice.has(sliceIndex) || this.activeKeysBySlice.get(sliceIndex).size === 0;
657
694
  }
658
695
  sliceCategoryCount(sliceIndex, categoryKey) {
659
- const cacheKey = `${sliceIndex}:${categoryKey}`;
660
- if (this._countCache.has(cacheKey)) {
661
- return this._countCache.get(cacheKey);
662
- }
663
696
  const slice = this._cachedSlices[sliceIndex];
664
697
  if (!slice)
665
698
  return 0;
666
699
  const category = (slice.categories || []).find((c) => c.key === categoryKey);
700
+ if (category?.count !== undefined)
701
+ return category.count;
702
+ const cacheKey = `${sliceIndex}:${categoryKey}`;
703
+ if (this._countCache.has(cacheKey)) {
704
+ return this._countCache.get(cacheKey);
705
+ }
667
706
  const count = category ? (this.data || []).filter((row) => category.filter ? category.filter(row) : true).length : 0;
668
707
  this._countCache.set(cacheKey, count);
669
708
  return count;
@@ -731,11 +770,11 @@ class SlicePanelComponent {
731
770
  addDynamicSlice(column) {
732
771
  if (this._dynamicSlices.some(slice => slice.key === column.key))
733
772
  return;
734
- const distinctValues = this._computeDistinctValues(column.key);
773
+ const distinct = this._computeDistinctValues(column.key);
735
774
  const newSlice = {
736
775
  title: column.header,
737
776
  columnKey: column.key,
738
- categories: distinctValues.map((v) => this._buildCategoryFromValue(column.key, v)),
777
+ categories: this._buildCategories(column.key, distinct),
739
778
  };
740
779
  this._dynamicSlices = [...this._dynamicSlices, { key: column.key, slice: newSlice }];
741
780
  this._rebuildCache();
@@ -747,14 +786,7 @@ class SlicePanelComponent {
747
786
  const dynamicIndex = sliceIndex - this.staticSliceCount;
748
787
  if (dynamicIndex < 0)
749
788
  return;
750
- const newActiveKeys = new Map();
751
- this.activeKeysBySlice.forEach((keys, idx) => {
752
- if (idx < sliceIndex)
753
- newActiveKeys.set(idx, keys);
754
- else if (idx > sliceIndex)
755
- newActiveKeys.set(idx - 1, keys);
756
- });
757
- this.activeKeysBySlice = newActiveKeys;
789
+ // Les sélections sont rapprochées des slices restants par identité dans _rebuildCache.
758
790
  this._dynamicSlices = this._dynamicSlices.filter((_, i) => i !== dynamicIndex);
759
791
  this._rebuildCache();
760
792
  this.dynamicSliceKeysChange.emit(this._dynamicSlices.map(d => d.key));
@@ -811,32 +843,37 @@ class SlicePanelComponent {
811
843
  if (!slice.columnKey || (slice.categories && slice.categories.length > 0)) {
812
844
  return slice;
813
845
  }
814
- const col = (this.columns || []).find(c => c.key === slice.columnKey);
846
+ const col = this._column(slice.columnKey);
815
847
  const isLazy = !!col?.lazy;
816
848
  if (isLazy && this.lazyStatus.get(slice.columnKey) !== 'loaded') {
817
849
  return { ...slice, categories: [] };
818
850
  }
819
851
  if (slice.bucket) {
820
- const distinctValues = [...new Set((this.data || []).map(slice.bucket))].sort();
852
+ const bucket = slice.bucket;
853
+ const counts = new Map();
854
+ (this.data || []).forEach(row => { const v = bucket(row); counts.set(v, (counts.get(v) ?? 0) + 1); });
821
855
  return {
822
856
  ...slice,
823
- categories: distinctValues.map((v) => ({
824
- key: v, label: v,
825
- filter: (row) => slice.bucket(row) === v,
857
+ categories: [...counts.keys()].sort().map((v) => ({
858
+ key: v, label: v, count: counts.get(v),
859
+ filter: (row) => bucket(row) === v,
826
860
  })),
827
861
  };
828
862
  }
829
- const distinctValues = this._computeDistinctValues(slice.columnKey);
830
863
  return {
831
864
  ...slice,
832
- categories: distinctValues.map((v) => this._buildCategoryFromValue(slice.columnKey, v)),
865
+ categories: this._buildCategories(slice.columnKey, this._computeDistinctValues(slice.columnKey)),
833
866
  };
834
867
  }
835
- _buildCategoryFromValue(columnKey, v) {
868
+ _buildCategories(columnKey, distinct) {
869
+ return distinct.values.map((v) => this._buildCategoryFromValue(columnKey, v, distinct.counts.get(v)));
870
+ }
871
+ _buildCategoryFromValue(columnKey, v, count) {
836
872
  if (v === EMPTY_CATEGORY_KEY) {
837
873
  return {
838
874
  key: EMPTY_CATEGORY_KEY,
839
875
  label: EMPTY_CATEGORY_LABEL,
876
+ count,
840
877
  filter: (row) => {
841
878
  const val = this._getLazyAwareValue(columnKey, row);
842
879
  return val == null || val === '';
@@ -844,24 +881,44 @@ class SlicePanelComponent {
844
881
  };
845
882
  }
846
883
  return {
847
- key: v, label: v,
884
+ key: v, label: v, count,
848
885
  filter: (row) => String(this._getLazyAwareValue(columnKey, row) ?? '') === v,
849
886
  };
850
887
  }
888
+ /** Valeurs distinctes (triées, « vide » en dernier) et effectifs, calculés en un seul passage. */
851
889
  _computeDistinctValues(columnKey) {
852
- const rawValues = (this.data || []).map((row) => {
890
+ const counts = new Map();
891
+ let empty = 0;
892
+ (this.data || []).forEach((row) => {
853
893
  const val = this._getLazyAwareValue(columnKey, row);
854
- return val != null && val !== '' ? String(val) : null;
894
+ if (val != null && val !== '') {
895
+ const text = String(val);
896
+ counts.set(text, (counts.get(text) ?? 0) + 1);
897
+ }
898
+ else {
899
+ empty++;
900
+ }
855
901
  });
856
- const distinct = [...new Set(rawValues.filter((v) => v !== null))].sort();
857
- if (rawValues.some(v => v === null))
858
- distinct.push(EMPTY_CATEGORY_KEY);
859
- return distinct;
902
+ const values = [...counts.keys()].sort();
903
+ if (empty > 0) {
904
+ values.push(EMPTY_CATEGORY_KEY);
905
+ counts.set(EMPTY_CATEGORY_KEY, empty);
906
+ }
907
+ return { values, counts };
908
+ }
909
+ _column(key) {
910
+ if (this._columnsRef !== this.columns) {
911
+ this._columnsRef = this.columns;
912
+ this._columnIndex = new Map();
913
+ (this.columns || []).forEach(c => { if (!this._columnIndex.has(c.key))
914
+ this._columnIndex.set(c.key, c); });
915
+ }
916
+ return key === undefined ? undefined : this._columnIndex.get(key);
860
917
  }
861
918
  _getLazyAwareValue(columnKey, row) {
862
- const colDef = (this.columns || []).find(c => c.key === columnKey);
863
- if (colDef?.lazy) {
864
- return this.lazyData.get(columnKey)?.get(row) ?? null;
919
+ const colDef = this._column(columnKey);
920
+ if (colDef?.lazy && this.lazyStatus.get(columnKey) !== 'loaded') {
921
+ return null;
865
922
  }
866
923
  if (colDef?.value) {
867
924
  return colDef.value(row, 0);
@@ -894,16 +951,10 @@ class SlicePanelComponent {
894
951
  this._countCache.clear();
895
952
  }
896
953
  _refreshDynamicSlices() {
897
- this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => {
898
- const distinctValues = this._computeDistinctValues(key);
899
- return {
900
- key,
901
- slice: {
902
- ...slice,
903
- categories: distinctValues.map((v) => this._buildCategoryFromValue(key, v)),
904
- },
905
- };
906
- });
954
+ this._dynamicSlices = this._dynamicSlices.map(({ key, slice }) => ({
955
+ key,
956
+ slice: { ...slice, categories: this._buildCategories(key, this._computeDistinctValues(key)) },
957
+ }));
907
958
  }
908
959
  _scrollSliceTitleIntoView(sliceIndex) {
909
960
  setTimeout(() => {
@@ -915,19 +966,17 @@ class SlicePanelComponent {
915
966
  }, 50);
916
967
  }
917
968
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
918
- 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 }); }
969
+ 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", 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\">\n <button\n type=\"button\"\n class=\"slice-title-toggle\"\n [attr.aria-controls]=\"'slice-section-' + si\"\n [attr.aria-expanded]=\"!isSliceCollapsed(si)\"\n (click)=\"toggleSliceCollapse(si)\"\n >\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 <span class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</span>\n </button>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n [attr.aria-label]=\"'Supprimer le filtre ' + (slice.title || i18n.filterBlockDefaultTitle)\"\n (click)=\"removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div [id]=\"'slice-section-' + si\" 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:var(--jqt-slice-header-height, 34px);padding:0 8px;margin-bottom:var(--jqt-slice-header-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:var(--jqt-slice-body-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;-webkit-user-select:none;user-select:none}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-toggle{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;text-align:left}.slice-title-toggle:hover .slice-title{color:var(--jqt-text-color)}.slice-title-toggle:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-toggle:focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:2px;border-radius:4px}.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:var(--jqt-slice-section-title-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%;min-height:var(--jqt-slice-item-min-height, 30px);padding:var(--jqt-slice-item-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;font-size:var(--jqt-slice-item-font-size, 13px)}.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 }); }
919
970
  }
920
971
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SlicePanelComponent, decorators: [{
921
972
  type: Component,
922
- 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"] }]
973
+ 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\">\n <button\n type=\"button\"\n class=\"slice-title-toggle\"\n [attr.aria-controls]=\"'slice-section-' + si\"\n [attr.aria-expanded]=\"!isSliceCollapsed(si)\"\n (click)=\"toggleSliceCollapse(si)\"\n >\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 <span class=\"slice-title\">{{ slice.title || i18n.filterBlockDefaultTitle }}</span>\n </button>\n <button\n *ngIf=\"isSliceDynamic(si)\"\n type=\"button\"\n class=\"slice-remove-btn\"\n [attr.aria-label]=\"'Supprimer le filtre ' + (slice.title || i18n.filterBlockDefaultTitle)\"\n (click)=\"removeDynamicSlice(si)\"\n >\u00D7</button>\n </div>\n\n <div [id]=\"'slice-section-' + si\" 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:var(--jqt-slice-header-height, 34px);padding:0 8px;margin-bottom:var(--jqt-slice-header-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:var(--jqt-slice-body-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;-webkit-user-select:none;user-select:none}.slice-title-row:first-of-type .slice-title{margin-top:0}.slice-title-toggle{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:6px;padding:0;border:0;background:transparent;color:inherit;cursor:pointer;text-align:left}.slice-title-toggle:hover .slice-title{color:var(--jqt-text-color)}.slice-title-toggle:hover .slice-title-chevron{color:var(--jqt-text-secondary)}.slice-title-toggle:focus-visible{outline:2px solid var(--jqt-primary-color);outline-offset:2px;border-radius:4px}.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:var(--jqt-slice-section-title-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%;min-height:var(--jqt-slice-item-min-height, 30px);padding:var(--jqt-slice-item-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;font-size:var(--jqt-slice-item-font-size, 13px)}.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"] }]
923
974
  }], propDecorators: { sliceConfigs: [{
924
975
  type: Input
925
976
  }], columns: [{
926
977
  type: Input
927
978
  }], data: [{
928
979
  type: Input
929
- }], lazyData: [{
930
- type: Input
931
980
  }], lazyStatus: [{
932
981
  type: Input
933
982
  }], showToggle: [{
@@ -1006,6 +1055,7 @@ var OrganizerButtonEventType;
1006
1055
  OrganizerButtonEventType["GROUP_BY_SELECTED"] = "groupBySelected";
1007
1056
  OrganizerButtonEventType["TEMPLATE_SELECTED"] = "templateSelected";
1008
1057
  OrganizerButtonEventType["SLICE_SELECTED"] = "sliceSelected";
1058
+ OrganizerButtonEventType["CHART_TYPE_SELECTED"] = "chartTypeSelected";
1009
1059
  OrganizerButtonEventType["VIEW_SWITCHED"] = "viewSwitched";
1010
1060
  })(OrganizerButtonEventType || (OrganizerButtonEventType = {}));
1011
1061
  function getGroupItems(config) {
@@ -1023,14 +1073,8 @@ function getStackItems(indicator) {
1023
1073
 
1024
1074
  function chartConfigToOrganizer(chartConfig, options = {}) {
1025
1075
  if (!chartConfig) {
1026
- console.debug('[OrganizerChartAdapter] Empty chart config, returning empty organizer config');
1027
1076
  return {};
1028
1077
  }
1029
- console.debug('[OrganizerChartAdapter] Converting chart config to organizer', {
1030
- hasGroups: !!chartConfig.groups,
1031
- indicatorCount: chartConfig.indicators?.items?.length ?? 0,
1032
- filterCount: chartConfig.filters?.items?.length ?? 0
1033
- });
1034
1078
  const { onFetchSliceData, onExportVisual, onExportData, switchView } = options;
1035
1079
  const indicators = getIndicatorItems(chartConfig);
1036
1080
  const activeIndicator = indicators.find(i => i.selected);
@@ -1064,7 +1108,6 @@ function chartConfigToOrganizer(chartConfig, options = {}) {
1064
1108
  }
1065
1109
  function chartConfigToState(chartConfig) {
1066
1110
  if (!chartConfig) {
1067
- console.debug('[OrganizerChartAdapter] Empty chart config for state conversion');
1068
1111
  return {};
1069
1112
  }
1070
1113
  const indicators = getIndicatorItems(chartConfig);
@@ -1073,12 +1116,6 @@ function chartConfigToState(chartConfig) {
1073
1116
  const activeGroup = getGroupItems(chartConfig).find(g => g.selected);
1074
1117
  const activeSlices = getFilterItems(chartConfig).filter(f => f.selected);
1075
1118
  const indicatorGroup = activeIndicator?.group;
1076
- console.debug('[OrganizerChartAdapter] Current chart state', {
1077
- activeGroup: activeGroup?.key,
1078
- activeIndicator: activeIndicator?.key,
1079
- activeStack: activeStack?.key,
1080
- sliceCount: activeSlices.length
1081
- });
1082
1119
  const state = {
1083
1120
  selectedX: activeGroup?.key,
1084
1121
  selectedY: indicatorGroup ?? activeIndicator?.key,
@@ -1108,10 +1145,8 @@ function chartConfigToUnifiedState(chartConfig, viewMode = 'chart') {
1108
1145
  }
1109
1146
  function applyOrganizerEventToChart(event, chartConfig) {
1110
1147
  if (!chartConfig) {
1111
- console.debug('[OrganizerChartAdapter] Cannot apply event to empty chart config');
1112
1148
  return;
1113
1149
  }
1114
- console.debug('[OrganizerChartAdapter] Applying organizer event', { eventType: event.type, state: event.state });
1115
1150
  const state = event.state;
1116
1151
  _cleanSynthetics(chartConfig);
1117
1152
  if (state.selectedX !== undefined)
@@ -1125,7 +1160,6 @@ function applyOrganizerEventToChart(event, chartConfig) {
1125
1160
  event.resolvedYUnit = resolveYUnit(chartConfig, event.state.selectedY, event.state.selectedYAggregate);
1126
1161
  }
1127
1162
  function handleOrganizerChartEvent(event, chartConfig, currentBinding, options = {}) {
1128
- console.debug('[OrganizerChartAdapter] Handling organizer event', { eventType: event.type });
1129
1163
  if (event.type === 'viewSwitched') {
1130
1164
  return {
1131
1165
  binding: {
@@ -1420,19 +1454,10 @@ class OrganizerFacade {
1420
1454
  }
1421
1455
  onDynamicSliceKeysChange(keys) {
1422
1456
  const allFields = this._panelConfig.fields ?? [];
1423
- const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
1424
- const dynamicKeySet = new Set(keys);
1425
- const existingKeys = new Set([...staticKeys, ...dynamicKeySet]);
1426
1457
  this.sliceBy.activeDynamicFields = keys
1427
1458
  .map(k => allFields.find(c => c.key === k))
1428
1459
  .filter((c) => !!c);
1429
- const seen = new Set();
1430
- this.sliceBy.availableForDynamic = allFields.filter(c => {
1431
- if (c.sliceable === false || existingKeys.has(c.key) || seen.has(c.key))
1432
- return false;
1433
- seen.add(c.key);
1434
- return true;
1435
- });
1460
+ this.sliceBy.availableForDynamic = this._availableForDynamic(new Set(keys));
1436
1461
  this._computeSliceLabel();
1437
1462
  this._events$.next({ type: 'dynamicSlicesChanged', keys });
1438
1463
  }
@@ -1457,23 +1482,42 @@ class OrganizerFacade {
1457
1482
  this.fields.activeFields = [...defaultFields];
1458
1483
  }
1459
1484
  else {
1460
- const allowedKeys = new Set((this._panelConfig.fields || []).map(c => c.key));
1461
- this.fields.activeFields = this.fields.activeFields.filter(c => allowedKeys.has(c.key));
1485
+ const current = this._definitionsByKey();
1486
+ this.fields.activeFields = this.fields.activeFields
1487
+ .filter(c => current.has(c.key))
1488
+ .map(c => current.get(c.key));
1462
1489
  }
1463
1490
  }
1464
- _refreshDynamicSliceMeta() {
1465
- const allFields = this._panelConfig.fields ?? [];
1466
- const staticKeys = new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
1467
- const activeDynKeys = new Set(this.sliceBy.activeDynamicFields.map(c => c.key));
1468
- const seen1 = new Set();
1469
- this.sliceBy.availableForDynamic = allFields.filter(c => {
1470
- if (c.sliceable === false || staticKeys.has(c.key) || activeDynKeys.has(c.key) || seen1.has(c.key))
1491
+ /** Première définition de chaque clé dans la config courante. */
1492
+ _definitionsByKey() {
1493
+ const byKey = new Map();
1494
+ (this._panelConfig.fields || []).forEach(c => { if (!byKey.has(c.key))
1495
+ byKey.set(c.key, c); });
1496
+ return byKey;
1497
+ }
1498
+ _staticSliceKeys() {
1499
+ return new Set((this._panelConfig.sliceConfigs ?? []).map(s => s.columnKey).filter((k) => !!k));
1500
+ }
1501
+ /** Champs proposables en slice dynamique, hors slices statiques et slices déjà actifs. */
1502
+ _availableForDynamic(activeKeys) {
1503
+ const staticKeys = this._staticSliceKeys();
1504
+ const seen = new Set();
1505
+ return (this._panelConfig.fields ?? []).filter(c => {
1506
+ if (c.sliceable === false || staticKeys.has(c.key) || activeKeys.has(c.key) || seen.has(c.key))
1471
1507
  return false;
1472
1508
  if (!c.header && c.sliceable !== true)
1473
1509
  return false;
1474
- seen1.add(c.key);
1510
+ seen.add(c.key);
1475
1511
  return true;
1476
1512
  });
1513
+ }
1514
+ _refreshDynamicSliceMeta() {
1515
+ const allFields = this._panelConfig.fields ?? [];
1516
+ const staticKeys = this._staticSliceKeys();
1517
+ const current = this._definitionsByKey();
1518
+ this.sliceBy.activeDynamicFields = this.sliceBy.activeDynamicFields.map(c => current.get(c.key) ?? c);
1519
+ const activeDynKeys = new Set(this.sliceBy.activeDynamicFields.map(c => c.key));
1520
+ this.sliceBy.availableForDynamic = this._availableForDynamic(activeDynKeys);
1477
1521
  const seen2 = new Set();
1478
1522
  this.sliceBy.allDynamicFields = allFields.filter(c => {
1479
1523
  if (c.sliceable === false || staticKeys.has(c.key) || seen2.has(c.key))
@@ -1563,6 +1607,8 @@ class GroupByManager {
1563
1607
  }
1564
1608
  }
1565
1609
  groupPageForward(groupKey, totalCount) {
1610
+ if (this.groupPageSize <= 0)
1611
+ return;
1566
1612
  const maxPage = Math.ceil(totalCount / this.groupPageSize) - 1;
1567
1613
  const current = this._groupPages.get(groupKey) ?? 0;
1568
1614
  if (current < maxPage) {
@@ -1587,99 +1633,9 @@ class GroupByManager {
1587
1633
  }
1588
1634
  }
1589
1635
 
1590
- const LAZY_FIELD_LOADING_VALUE = '__lazy_loading__';
1591
- const LAZY_FIELD_ERROR_VALUE = '__lazy_error__';
1592
- class LazyFieldManager {
1593
- constructor() {
1594
- this._status = new Map();
1595
- this._data = new Map();
1596
- this._cancels = new Map();
1597
- }
1598
- get status() {
1599
- return this._status;
1600
- }
1601
- get data() {
1602
- return this._data;
1603
- }
1604
- getStatus(key) {
1605
- return this._status.get(key) ?? 'idle';
1606
- }
1607
- getRenderType(rowValue) {
1608
- if (rowValue === LAZY_FIELD_LOADING_VALUE)
1609
- return 'loading';
1610
- if (rowValue === LAZY_FIELD_ERROR_VALUE)
1611
- return 'error';
1612
- return 'value';
1613
- }
1614
- cancelAll() {
1615
- this._cancels.forEach(s => { s.next(); s.complete(); });
1616
- this._cancels.clear();
1617
- this._status = new Map();
1618
- this._data = new Map();
1619
- }
1620
- retry(field, resolvedData, callbacks) {
1621
- const k = field.key;
1622
- const prev = this._cancels.get(k);
1623
- if (prev) {
1624
- prev.next();
1625
- prev.complete();
1626
- }
1627
- this._cancels.delete(k);
1628
- const newStatus = new Map(this._status);
1629
- newStatus.delete(k);
1630
- this._status = newStatus;
1631
- const newData = new Map(this._data);
1632
- newData.delete(k);
1633
- this._data = newData;
1634
- this.fetch(field, resolvedData, callbacks);
1635
- }
1636
- fetch(field, resolvedData, callbacks) {
1637
- if (!field.lazy)
1638
- return;
1639
- const k = field.key;
1640
- if (this._status.get(k) === 'loading')
1641
- return;
1642
- const prev = this._cancels.get(k);
1643
- if (prev) {
1644
- prev.next();
1645
- prev.complete();
1646
- }
1647
- const cancel$ = new Subject();
1648
- this._cancels.set(k, cancel$);
1649
- const newStatus = new Map(this._status);
1650
- newStatus.set(k, 'loading');
1651
- this._status = newStatus;
1652
- callbacks.onRefresh();
1653
- field.lazy.fetchFn().pipe(take(1), takeUntil$1(cancel$)).subscribe({
1654
- next: (values) => {
1655
- const rowMap = new Map();
1656
- values.forEach((value, i) => {
1657
- if (i < resolvedData.length)
1658
- rowMap.set(resolvedData[i], value);
1659
- });
1660
- const newData = new Map(this._data);
1661
- newData.set(k, rowMap);
1662
- this._data = newData;
1663
- const newStatus2 = new Map(this._status);
1664
- newStatus2.set(k, 'loaded');
1665
- this._status = newStatus2;
1666
- callbacks.onRefresh();
1667
- callbacks.onMarkForCheck();
1668
- },
1669
- error: () => {
1670
- const newStatusErr = new Map(this._status);
1671
- newStatusErr.set(k, 'error');
1672
- this._status = newStatusErr;
1673
- callbacks.onRefresh();
1674
- callbacks.onMarkForCheck();
1675
- },
1676
- });
1677
- }
1678
- }
1679
-
1680
1636
  /**
1681
1637
  * Generated bundle index. Do not edit.
1682
1638
  */
1683
1639
 
1684
- export { DEFAULT_MENU_ICONS, DEFAULT_MENU_LABELS, GroupByManager, LAZY_FIELD_ERROR_VALUE, LAZY_FIELD_LOADING_VALUE, LazyFieldManager, OrganizerButtonComponent, OrganizerButtonModule, OrganizerFacade, SlicePanelComponent, applyOrganizerEventToChart, buildOrganizerChartBinding, buildYFields, chartConfigToOrganizer, chartConfigToState, chartConfigToUnifiedState, handleOrganizerChartEvent, humanizeKey, resolveYKey, resolveYUnit };
1640
+ export { DEFAULT_MENU_ICONS, DEFAULT_MENU_LABELS, GroupByManager, OrganizerButtonComponent, OrganizerButtonEventType, OrganizerButtonModule, OrganizerFacade, SlicePanelComponent, applyOrganizerEventToChart, buildOrganizerChartBinding, buildYFields, chartConfigToOrganizer, chartConfigToState, chartConfigToUnifiedState, handleOrganizerChartEvent, humanizeKey, resolveYKey, resolveYUnit };
1685
1641
  //# sourceMappingURL=oneteme-jquery-organizer.mjs.map