ng-prime-tools 1.0.96 → 1.0.98

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.
@@ -61,6 +61,8 @@ import * as i2$2 from 'primeng/toast';
61
61
  import { ToastModule } from 'primeng/toast';
62
62
  import * as i2$3 from 'primeng/dialog';
63
63
  import { DialogModule } from 'primeng/dialog';
64
+ import * as i5$1 from 'primeng/menu';
65
+ import { MenuModule } from 'primeng/menu';
64
66
 
65
67
  /**
66
68
  * Calculates the width required for a column based on the header text (column title).
@@ -4223,14 +4225,24 @@ const createDefaultChartOptions = (chartType, theme, config) => {
4223
4225
  label: (context) => config.tooltipLabel(context, isCircularChart),
4224
4226
  },
4225
4227
  },
4228
+ /*
4229
+ * Do not use `display: isCircularChart`.
4230
+ * For non-circular charts, this plugin version can still enter
4231
+ * its drawing lifecycle and throw `undefined.length`.
4232
+ */
4226
4233
  datalabels: {
4227
- display: isCircularChart,
4234
+ display: () => isCircularChart,
4228
4235
  color: theme.textColor,
4229
4236
  anchor: isCircularChart ? 'center' : 'end',
4230
4237
  align: isCircularChart ? 'center' : 'top',
4231
4238
  clamp: true,
4232
4239
  clip: false,
4233
- formatter: (value, context) => config.dataLabel(value, context, isCircularChart),
4240
+ formatter: (value, context) => {
4241
+ if (!isCircularChart) {
4242
+ return '';
4243
+ }
4244
+ return config.dataLabel(value, context, true);
4245
+ },
4234
4246
  font: {
4235
4247
  family: config.fontFamily,
4236
4248
  size: PT_CHART_FONT_SIZES.dataLabel,
@@ -4322,7 +4334,11 @@ class PTChartComponent {
4322
4334
  this.colorSchemeChangeListener = () => {
4323
4335
  this.refreshTheme();
4324
4336
  };
4325
- Chart.register(...registerables, ChartDataLabels);
4337
+ /*
4338
+ * Do not register ChartDataLabels globally.
4339
+ * It must only be enabled for pie, doughnut and polar-area charts.
4340
+ */
4341
+ Chart.register(...registerables);
4326
4342
  }
4327
4343
  ngAfterViewInit() {
4328
4344
  this.viewInitialized = true;
@@ -4355,10 +4371,9 @@ class PTChartComponent {
4355
4371
  if (!this.viewInitialized || !this.chartConfig) {
4356
4372
  return;
4357
4373
  }
4358
- const canvas = this.canvasRef.nativeElement;
4359
4374
  this.destroyChart();
4360
4375
  const configuration = this.buildChartConfiguration();
4361
- this.chart = new Chart(canvas, configuration);
4376
+ this.chart = new Chart(this.canvasRef.nativeElement, configuration);
4362
4377
  this.currentChartType = this.chartConfig.type;
4363
4378
  this.scheduleChartResize();
4364
4379
  }
@@ -4392,6 +4407,12 @@ class PTChartComponent {
4392
4407
  type: chartType,
4393
4408
  data: this.cloneChartData(this.chartConfig.data),
4394
4409
  options: this.mergeChartOptions(defaultOptions, consumerOptions, chartType),
4410
+ /*
4411
+ * ChartDataLabels is attached only for circular charts.
4412
+ * It is not registered globally, so it cannot break line,
4413
+ * bar, scatter, bubble or comparison charts.
4414
+ */
4415
+ plugins: isCircularChartType(chartType) ? [ChartDataLabels] : [],
4395
4416
  };
4396
4417
  }
4397
4418
  buildDefaultOptions(chartType, theme) {
@@ -4465,12 +4486,21 @@ class PTChartComponent {
4465
4486
  ...consumerPlugins?.tooltip?.callbacks,
4466
4487
  },
4467
4488
  },
4489
+ },
4490
+ };
4491
+ /*
4492
+ * Add datalabel options only to circular charts.
4493
+ * For Cartesian charts, the plugin is not installed at all.
4494
+ */
4495
+ if (isCircularChartType(chartType)) {
4496
+ mergedOptions.plugins = {
4497
+ ...mergedOptions.plugins,
4468
4498
  datalabels: {
4469
4499
  ...defaultPlugins?.datalabels,
4470
4500
  ...consumerPlugins?.datalabels,
4471
4501
  },
4472
- },
4473
- };
4502
+ };
4503
+ }
4474
4504
  if (isCartesianChartType(chartType)) {
4475
4505
  mergedOptions.scales = this.mergeScales(defaultOptions.scales, consumerOptions.scales);
4476
4506
  }
@@ -7621,9 +7651,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
7621
7651
 
7622
7652
  // src/lib/components/pt-metric-panel/pt-metric-panel.component.ts
7623
7653
  class PTMetricPanelComponent {
7654
+ /**
7655
+ * Controlled by the parent component.
7656
+ * The parent must set it to false when its request completes,
7657
+ * including error cases.
7658
+ */
7659
+ set loading(value) {
7660
+ const previousLoading = this.dashboardLoading;
7661
+ this.dashboardLoading = value;
7662
+ if (value) {
7663
+ this.dashboardLoadingWasStarted = true;
7664
+ }
7665
+ if (previousLoading && !value && this.dashboardLoadingWasStarted) {
7666
+ this.localRefreshLoading = false;
7667
+ this.dashboardLoadingWasStarted = false;
7668
+ }
7669
+ this.rebuildActionMenuItems();
7670
+ }
7671
+ get loading() {
7672
+ return this.dashboardLoading;
7673
+ }
7624
7674
  constructor(router) {
7625
7675
  this.router = router;
7626
7676
  this.cardConfig = this.getDefaultCardConfig();
7677
+ /**
7678
+ * Displays the refresh action in the panel header.
7679
+ */
7680
+ this.showRefresh = false;
7681
+ /**
7682
+ * Displays the contextual actions menu in the panel header.
7683
+ */
7684
+ this.showMenu = false;
7685
+ /**
7686
+ * Additional menu items supplied by the parent component.
7687
+ * "Actualiser" is automatically added when showRefresh is true.
7688
+ */
7689
+ this.menuItems = [];
7690
+ /**
7691
+ * Emitted when the user clicks the refresh action.
7692
+ * The parent executes the API request.
7693
+ */
7694
+ this.refresh = new EventEmitter();
7695
+ this.actionMenuItems = [];
7696
+ this.dashboardLoading = false;
7697
+ this.localRefreshLoading = false;
7698
+ this.dashboardLoadingWasStarted = false;
7699
+ }
7700
+ ngOnChanges(changes) {
7701
+ if (changes['showRefresh'] || changes['showMenu'] || changes['menuItems']) {
7702
+ this.rebuildActionMenuItems();
7703
+ }
7704
+ }
7705
+ get isLoading() {
7706
+ return this.dashboardLoading || this.localRefreshLoading;
7707
+ }
7708
+ get hasCustomMenuItems() {
7709
+ return this.menuItems.length > 0;
7710
+ }
7711
+ get shouldShowMenu() {
7712
+ return this.showMenu || this.showRefresh || this.hasCustomMenuItems;
7627
7713
  }
7628
7714
  getDefaultCardConfig() {
7629
7715
  return {
@@ -7694,32 +7780,83 @@ class PTMetricPanelComponent {
7694
7780
  return typeof value === 'object' && value !== null && 'text' in value;
7695
7781
  }
7696
7782
  handleClick(url) {
7697
- if (url) {
7783
+ if (url && !this.isLoading) {
7698
7784
  this.router.navigate([url]);
7699
7785
  }
7700
7786
  }
7787
+ onRefreshClick(event) {
7788
+ event.preventDefault();
7789
+ event.stopPropagation();
7790
+ this.requestRefresh();
7791
+ }
7792
+ toggleMenu(event) {
7793
+ event.preventDefault();
7794
+ event.stopPropagation();
7795
+ if (this.isLoading) {
7796
+ return;
7797
+ }
7798
+ this.actionMenu?.toggle(event);
7799
+ }
7800
+ requestRefresh() {
7801
+ if (this.isLoading) {
7802
+ return;
7803
+ }
7804
+ this.localRefreshLoading = true;
7805
+ this.rebuildActionMenuItems();
7806
+ this.refresh.emit();
7807
+ }
7808
+ rebuildActionMenuItems() {
7809
+ const items = [];
7810
+ if (this.showRefresh) {
7811
+ items.push({
7812
+ label: 'Actualiser',
7813
+ icon: this.isLoading ? 'pi pi-spin pi-spinner' : 'pi pi-refresh',
7814
+ disabled: this.isLoading,
7815
+ command: () => this.requestRefresh(),
7816
+ });
7817
+ }
7818
+ if (this.showRefresh && this.hasCustomMenuItems) {
7819
+ items.push({
7820
+ separator: true,
7821
+ });
7822
+ }
7823
+ this.actionMenuItems = [...items, ...this.menuItems];
7824
+ }
7701
7825
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelComponent, deps: [{ token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Component }); }
7702
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PTMetricPanelComponent, isStandalone: false, selector: "pt-metric-panel", inputs: { panelData: "panelData", cardConfig: "cardConfig" }, ngImport: i0, template: "<pt-card [config]=\"cardConfig\">\n <div class=\"panel-header\" [ngStyle]=\"getTitleStyles()\">\n @if (getTitleIcon()) {\n <i class=\"panel-title-icon\" [ngClass]=\"getIconClass(getTitleIcon())\"></i>\n }\n\n <span class=\"panel-title-text\">{{ getTitleText() }}</span>\n </div>\n\n <div class=\"panel-separator\"></div>\n\n <div class=\"metric-list\">\n @for (item of panelData.indicators; track $index) {\n <div\n class=\"metric-item\"\n [class.metric-item-clickable]=\"!!item.url\"\n [pTooltip]=\"item.tooltip || ''\"\n tooltipPosition=\"top\"\n [tooltipDisabled]=\"!item.tooltip\"\n (click)=\"handleClick(item.url)\"\n >\n <div class=\"metric-left\">\n @if (item.icon) {\n <span class=\"metric-icon-wrapper\">\n <i\n [ngClass]=\"getIconClass(item.icon.code)\"\n [ngStyle]=\"getIconStyles(item.icon)\"\n ></i>\n </span>\n }\n\n <span class=\"metric-title\">\n {{ isTitleStyle(item.title) ? item.title.text : item.title }}\n </span>\n </div>\n\n <span\n class=\"metric-value\"\n [class.metric-value-highlighted]=\"item.highlighted\"\n [ngStyle]=\"getValueStyles(item.value)\"\n >\n {{ isTitleStyle(item.value) ? item.value.text : item.value }}\n </span>\n </div>\n }\n </div>\n</pt-card>\n", styles: [":host{display:block}.panel-header{display:flex;align-items:center;gap:12px;margin-bottom:18px;font-weight:800;line-height:1.2}.panel-title-icon{display:inline-flex;align-items:center;justify-content:center;min-width:38px;min-height:38px;padding:10px;color:#6734c9;background:#f0e7ff;border-radius:12px;font-size:1.05rem}.panel-title-text{color:inherit}.metric-list{display:flex;flex-direction:column;gap:12px}.metric-item{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:68px;padding:12px 14px;background:#fff;border:1px solid #edf0f5;border-radius:14px;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease}.metric-item:hover{border-color:#ddd2f8;box-shadow:0 5px 14px #4a209014}.metric-item-clickable{cursor:pointer}.metric-item-clickable:hover{transform:translateY(-1px)}.metric-left{display:flex;align-items:center;min-width:0;gap:12px}.metric-icon-wrapper{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.metric-icon-wrapper i{display:inline-flex!important;align-items:center;justify-content:center;min-width:40px;min-height:40px;box-sizing:border-box}.metric-title{overflow:hidden;color:#344054;font-size:1rem;font-weight:700;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.metric-value{flex:0 0 auto;padding:7px 11px;color:#1f2937;background:#f8f9fc;border:1px solid #e8ebf1;border-radius:10px;font-size:.95rem;font-weight:800;line-height:1;text-align:right;white-space:nowrap}@media(max-width:576px){.metric-item{min-height:60px;padding:10px}.metric-title{font-size:.92rem}.metric-value{padding:6px 8px;font-size:.85rem}}.metric-value-highlighted{color:#15803d!important;background:#dcfce7!important;border-color:#86efac!important;animation:metric-value-flash 1.8s ease-out}@keyframes metric-value-flash{0%{color:#166534;background:#bbf7d0;border-color:#4ade80;transform:scale(1.08)}to{color:#15803d;background:#dcfce7;border-color:#86efac;transform:scale(1)}}.panel-separator{width:100%;height:1px;margin:0 0 16px;background:#e7eaf0}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: PTCardComponent, selector: "pt-card", inputs: ["config"] }, { kind: "directive", type: i12.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }] }); }
7826
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PTMetricPanelComponent, isStandalone: false, selector: "pt-metric-panel", inputs: { panelData: "panelData", cardConfig: "cardConfig", showRefresh: "showRefresh", showMenu: "showMenu", menuItems: "menuItems", loading: "loading" }, outputs: { refresh: "refresh" }, viewQueries: [{ propertyName: "actionMenu", first: true, predicate: ["actionMenu"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- src/lib/components/pt-metric-panel/pt-metric-panel.component.html -->\n\n<pt-card [config]=\"cardConfig\">\n <div class=\"panel-header\">\n <div class=\"panel-title-container\" [ngStyle]=\"getTitleStyles()\">\n @if (getTitleIcon()) {\n <i\n class=\"panel-title-icon\"\n [ngClass]=\"getIconClass(getTitleIcon())\"\n ></i>\n }\n\n <span class=\"panel-title-text\">\n {{ getTitleText() }}\n </span>\n </div>\n\n @if (showRefresh || shouldShowMenu) {\n <div class=\"panel-actions\">\n @if (showRefresh) {\n <button\n type=\"button\"\n class=\"panel-action-button\"\n [class.panel-action-button-loading]=\"isLoading\"\n [disabled]=\"isLoading\"\n aria-label=\"Actualiser\"\n title=\"Actualiser\"\n (click)=\"onRefreshClick($event)\"\n >\n <i\n class=\"pi\"\n [ngClass]=\"isLoading ? 'pi-spin pi-spinner' : 'pi-refresh'\"\n ></i>\n </button>\n }\n\n @if (shouldShowMenu) {\n <button\n type=\"button\"\n class=\"panel-action-button\"\n [disabled]=\"isLoading\"\n aria-label=\"Plus d\u2019actions\"\n title=\"Plus d\u2019actions\"\n (click)=\"toggleMenu($event)\"\n >\n <i class=\"pi pi-ellipsis-v\"></i>\n </button>\n\n <p-menu\n #actionMenu\n [model]=\"actionMenuItems\"\n [popup]=\"true\"\n appendTo=\"body\"\n ></p-menu>\n }\n </div>\n }\n </div>\n\n <div class=\"panel-separator\"></div>\n\n <div class=\"metric-content\">\n @if (isLoading) {\n <div\n class=\"metric-loading-overlay\"\n aria-live=\"polite\"\n aria-label=\"Chargement des donn\u00E9es\"\n >\n <i class=\"pi pi-spinner pi-spin metric-loading-spinner\"></i>\n <span>Chargement des donn\u00E9es...</span>\n </div>\n }\n\n <div\n class=\"metric-list\"\n [class.metric-list-loading]=\"isLoading\"\n [attr.aria-busy]=\"isLoading\"\n >\n @for (item of panelData.indicators; track $index) {\n <div\n class=\"metric-item\"\n [class.metric-item-clickable]=\"!!item.url && !isLoading\"\n [pTooltip]=\"item.tooltip || ''\"\n tooltipPosition=\"top\"\n [tooltipDisabled]=\"!item.tooltip || isLoading\"\n (click)=\"!isLoading && handleClick(item.url)\"\n >\n <div class=\"metric-left\">\n @if (item.icon) {\n <span class=\"metric-icon-wrapper\">\n <i\n [ngClass]=\"getIconClass(item.icon.code)\"\n [ngStyle]=\"getIconStyles(item.icon)\"\n ></i>\n </span>\n }\n\n <span class=\"metric-title\">\n {{ isTitleStyle(item.title) ? item.title.text : item.title }}\n </span>\n </div>\n\n <span\n class=\"metric-value\"\n [class.metric-value-highlighted]=\"item.highlighted && !isLoading\"\n [ngStyle]=\"getValueStyles(item.value)\"\n >\n {{ isTitleStyle(item.value) ? item.value.text : item.value }}\n </span>\n </div>\n }\n </div>\n </div>\n</pt-card>\n", styles: [":host{display:block}.panel-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}.panel-title-container{display:flex;align-items:center;min-width:0;gap:12px;font-weight:800;line-height:1.2}.panel-title-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;min-width:38px;min-height:38px;padding:10px;color:#6734c9;background:#f0e7ff;border-radius:12px;font-size:1.05rem}.panel-title-text{overflow:hidden;color:inherit;text-overflow:ellipsis;white-space:nowrap}.panel-actions{display:flex;align-items:center;flex:0 0 auto;gap:4px}.panel-action-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;cursor:pointer;border:1px solid transparent;border-radius:10px;color:#667085;background:transparent;transition:background-color .16s ease,border-color .16s ease,color .16s ease,transform .16s ease}.panel-action-button:hover:not(:disabled){border-color:#e3d8fb;color:#6734c9;background:#f7f3ff}.panel-action-button:active:not(:disabled){transform:scale(.95)}.panel-action-button:disabled{cursor:wait;opacity:.65}.panel-action-button-loading{color:#6734c9}.panel-action-button:focus-visible{outline:2px solid #8b5cf6;outline-offset:2px}.panel-separator{width:100%;height:1px;margin:0 0 16px;background:#e7eaf0}.metric-list{display:flex;flex-direction:column;gap:12px}.metric-item{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:68px;padding:12px 14px;background:#fff;border:1px solid #edf0f5;border-radius:14px;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease}.metric-item:hover{border-color:#ddd2f8;box-shadow:0 5px 14px #4a209014}.metric-item-clickable{cursor:pointer}.metric-item-clickable:hover{transform:translateY(-1px)}.metric-left{display:flex;align-items:center;min-width:0;gap:12px}.metric-icon-wrapper{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.metric-icon-wrapper i{display:inline-flex!important;align-items:center;justify-content:center;min-width:40px;min-height:40px;box-sizing:border-box}.metric-title{overflow:hidden;color:#344054;font-size:1rem;font-weight:700;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.metric-value{flex:0 0 auto;padding:7px 11px;color:#1f2937;background:#f8f9fc;border:1px solid #e8ebf1;border-radius:10px;font-size:.95rem;font-weight:800;line-height:1;text-align:right;white-space:nowrap}.metric-value-highlighted{color:#15803d!important;background:#dcfce7!important;border-color:#86efac!important;animation:metric-value-flash 1.8s ease-out}@keyframes metric-value-flash{0%{color:#166534;background:#bbf7d0;border-color:#4ade80;transform:scale(1.08)}to{color:#15803d;background:#dcfce7;border-color:#86efac;transform:scale(1)}}@media(max-width:576px){.panel-header,.panel-title-container{gap:8px}.panel-title-icon{min-width:34px;min-height:34px;padding:8px}.panel-action-button{width:32px;height:32px}.metric-item{min-height:60px;padding:10px}.metric-title{font-size:.92rem}.metric-value{padding:6px 8px;font-size:.85rem}}.metric-content{position:relative;min-height:120px}.metric-loading-overlay{position:absolute;z-index:2;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.7rem;min-height:120px;border-radius:14px;color:#6734c9;background:#ffffffd1;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);font-size:.9rem;font-weight:700}.metric-loading-spinner{font-size:1.5rem}.metric-list-loading{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.42;filter:blur(.4px)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: PTCardComponent, selector: "pt-card", inputs: ["config"] }, { kind: "directive", type: i12.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i5$1.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }] }); }
7703
7827
  }
7704
7828
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelComponent, decorators: [{
7705
7829
  type: Component,
7706
- args: [{ selector: 'pt-metric-panel', standalone: false, template: "<pt-card [config]=\"cardConfig\">\n <div class=\"panel-header\" [ngStyle]=\"getTitleStyles()\">\n @if (getTitleIcon()) {\n <i class=\"panel-title-icon\" [ngClass]=\"getIconClass(getTitleIcon())\"></i>\n }\n\n <span class=\"panel-title-text\">{{ getTitleText() }}</span>\n </div>\n\n <div class=\"panel-separator\"></div>\n\n <div class=\"metric-list\">\n @for (item of panelData.indicators; track $index) {\n <div\n class=\"metric-item\"\n [class.metric-item-clickable]=\"!!item.url\"\n [pTooltip]=\"item.tooltip || ''\"\n tooltipPosition=\"top\"\n [tooltipDisabled]=\"!item.tooltip\"\n (click)=\"handleClick(item.url)\"\n >\n <div class=\"metric-left\">\n @if (item.icon) {\n <span class=\"metric-icon-wrapper\">\n <i\n [ngClass]=\"getIconClass(item.icon.code)\"\n [ngStyle]=\"getIconStyles(item.icon)\"\n ></i>\n </span>\n }\n\n <span class=\"metric-title\">\n {{ isTitleStyle(item.title) ? item.title.text : item.title }}\n </span>\n </div>\n\n <span\n class=\"metric-value\"\n [class.metric-value-highlighted]=\"item.highlighted\"\n [ngStyle]=\"getValueStyles(item.value)\"\n >\n {{ isTitleStyle(item.value) ? item.value.text : item.value }}\n </span>\n </div>\n }\n </div>\n</pt-card>\n", styles: [":host{display:block}.panel-header{display:flex;align-items:center;gap:12px;margin-bottom:18px;font-weight:800;line-height:1.2}.panel-title-icon{display:inline-flex;align-items:center;justify-content:center;min-width:38px;min-height:38px;padding:10px;color:#6734c9;background:#f0e7ff;border-radius:12px;font-size:1.05rem}.panel-title-text{color:inherit}.metric-list{display:flex;flex-direction:column;gap:12px}.metric-item{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:68px;padding:12px 14px;background:#fff;border:1px solid #edf0f5;border-radius:14px;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease}.metric-item:hover{border-color:#ddd2f8;box-shadow:0 5px 14px #4a209014}.metric-item-clickable{cursor:pointer}.metric-item-clickable:hover{transform:translateY(-1px)}.metric-left{display:flex;align-items:center;min-width:0;gap:12px}.metric-icon-wrapper{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.metric-icon-wrapper i{display:inline-flex!important;align-items:center;justify-content:center;min-width:40px;min-height:40px;box-sizing:border-box}.metric-title{overflow:hidden;color:#344054;font-size:1rem;font-weight:700;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.metric-value{flex:0 0 auto;padding:7px 11px;color:#1f2937;background:#f8f9fc;border:1px solid #e8ebf1;border-radius:10px;font-size:.95rem;font-weight:800;line-height:1;text-align:right;white-space:nowrap}@media(max-width:576px){.metric-item{min-height:60px;padding:10px}.metric-title{font-size:.92rem}.metric-value{padding:6px 8px;font-size:.85rem}}.metric-value-highlighted{color:#15803d!important;background:#dcfce7!important;border-color:#86efac!important;animation:metric-value-flash 1.8s ease-out}@keyframes metric-value-flash{0%{color:#166534;background:#bbf7d0;border-color:#4ade80;transform:scale(1.08)}to{color:#15803d;background:#dcfce7;border-color:#86efac;transform:scale(1)}}.panel-separator{width:100%;height:1px;margin:0 0 16px;background:#e7eaf0}\n"] }]
7830
+ args: [{ selector: 'pt-metric-panel', standalone: false, template: "<!-- src/lib/components/pt-metric-panel/pt-metric-panel.component.html -->\n\n<pt-card [config]=\"cardConfig\">\n <div class=\"panel-header\">\n <div class=\"panel-title-container\" [ngStyle]=\"getTitleStyles()\">\n @if (getTitleIcon()) {\n <i\n class=\"panel-title-icon\"\n [ngClass]=\"getIconClass(getTitleIcon())\"\n ></i>\n }\n\n <span class=\"panel-title-text\">\n {{ getTitleText() }}\n </span>\n </div>\n\n @if (showRefresh || shouldShowMenu) {\n <div class=\"panel-actions\">\n @if (showRefresh) {\n <button\n type=\"button\"\n class=\"panel-action-button\"\n [class.panel-action-button-loading]=\"isLoading\"\n [disabled]=\"isLoading\"\n aria-label=\"Actualiser\"\n title=\"Actualiser\"\n (click)=\"onRefreshClick($event)\"\n >\n <i\n class=\"pi\"\n [ngClass]=\"isLoading ? 'pi-spin pi-spinner' : 'pi-refresh'\"\n ></i>\n </button>\n }\n\n @if (shouldShowMenu) {\n <button\n type=\"button\"\n class=\"panel-action-button\"\n [disabled]=\"isLoading\"\n aria-label=\"Plus d\u2019actions\"\n title=\"Plus d\u2019actions\"\n (click)=\"toggleMenu($event)\"\n >\n <i class=\"pi pi-ellipsis-v\"></i>\n </button>\n\n <p-menu\n #actionMenu\n [model]=\"actionMenuItems\"\n [popup]=\"true\"\n appendTo=\"body\"\n ></p-menu>\n }\n </div>\n }\n </div>\n\n <div class=\"panel-separator\"></div>\n\n <div class=\"metric-content\">\n @if (isLoading) {\n <div\n class=\"metric-loading-overlay\"\n aria-live=\"polite\"\n aria-label=\"Chargement des donn\u00E9es\"\n >\n <i class=\"pi pi-spinner pi-spin metric-loading-spinner\"></i>\n <span>Chargement des donn\u00E9es...</span>\n </div>\n }\n\n <div\n class=\"metric-list\"\n [class.metric-list-loading]=\"isLoading\"\n [attr.aria-busy]=\"isLoading\"\n >\n @for (item of panelData.indicators; track $index) {\n <div\n class=\"metric-item\"\n [class.metric-item-clickable]=\"!!item.url && !isLoading\"\n [pTooltip]=\"item.tooltip || ''\"\n tooltipPosition=\"top\"\n [tooltipDisabled]=\"!item.tooltip || isLoading\"\n (click)=\"!isLoading && handleClick(item.url)\"\n >\n <div class=\"metric-left\">\n @if (item.icon) {\n <span class=\"metric-icon-wrapper\">\n <i\n [ngClass]=\"getIconClass(item.icon.code)\"\n [ngStyle]=\"getIconStyles(item.icon)\"\n ></i>\n </span>\n }\n\n <span class=\"metric-title\">\n {{ isTitleStyle(item.title) ? item.title.text : item.title }}\n </span>\n </div>\n\n <span\n class=\"metric-value\"\n [class.metric-value-highlighted]=\"item.highlighted && !isLoading\"\n [ngStyle]=\"getValueStyles(item.value)\"\n >\n {{ isTitleStyle(item.value) ? item.value.text : item.value }}\n </span>\n </div>\n }\n </div>\n </div>\n</pt-card>\n", styles: [":host{display:block}.panel-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}.panel-title-container{display:flex;align-items:center;min-width:0;gap:12px;font-weight:800;line-height:1.2}.panel-title-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;min-width:38px;min-height:38px;padding:10px;color:#6734c9;background:#f0e7ff;border-radius:12px;font-size:1.05rem}.panel-title-text{overflow:hidden;color:inherit;text-overflow:ellipsis;white-space:nowrap}.panel-actions{display:flex;align-items:center;flex:0 0 auto;gap:4px}.panel-action-button{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;padding:0;cursor:pointer;border:1px solid transparent;border-radius:10px;color:#667085;background:transparent;transition:background-color .16s ease,border-color .16s ease,color .16s ease,transform .16s ease}.panel-action-button:hover:not(:disabled){border-color:#e3d8fb;color:#6734c9;background:#f7f3ff}.panel-action-button:active:not(:disabled){transform:scale(.95)}.panel-action-button:disabled{cursor:wait;opacity:.65}.panel-action-button-loading{color:#6734c9}.panel-action-button:focus-visible{outline:2px solid #8b5cf6;outline-offset:2px}.panel-separator{width:100%;height:1px;margin:0 0 16px;background:#e7eaf0}.metric-list{display:flex;flex-direction:column;gap:12px}.metric-item{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:68px;padding:12px 14px;background:#fff;border:1px solid #edf0f5;border-radius:14px;transition:border-color .16s ease,box-shadow .16s ease,transform .16s ease}.metric-item:hover{border-color:#ddd2f8;box-shadow:0 5px 14px #4a209014}.metric-item-clickable{cursor:pointer}.metric-item-clickable:hover{transform:translateY(-1px)}.metric-left{display:flex;align-items:center;min-width:0;gap:12px}.metric-icon-wrapper{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.metric-icon-wrapper i{display:inline-flex!important;align-items:center;justify-content:center;min-width:40px;min-height:40px;box-sizing:border-box}.metric-title{overflow:hidden;color:#344054;font-size:1rem;font-weight:700;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.metric-value{flex:0 0 auto;padding:7px 11px;color:#1f2937;background:#f8f9fc;border:1px solid #e8ebf1;border-radius:10px;font-size:.95rem;font-weight:800;line-height:1;text-align:right;white-space:nowrap}.metric-value-highlighted{color:#15803d!important;background:#dcfce7!important;border-color:#86efac!important;animation:metric-value-flash 1.8s ease-out}@keyframes metric-value-flash{0%{color:#166534;background:#bbf7d0;border-color:#4ade80;transform:scale(1.08)}to{color:#15803d;background:#dcfce7;border-color:#86efac;transform:scale(1)}}@media(max-width:576px){.panel-header,.panel-title-container{gap:8px}.panel-title-icon{min-width:34px;min-height:34px;padding:8px}.panel-action-button{width:32px;height:32px}.metric-item{min-height:60px;padding:10px}.metric-title{font-size:.92rem}.metric-value{padding:6px 8px;font-size:.85rem}}.metric-content{position:relative;min-height:120px}.metric-loading-overlay{position:absolute;z-index:2;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.7rem;min-height:120px;border-radius:14px;color:#6734c9;background:#ffffffd1;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);font-size:.9rem;font-weight:700}.metric-loading-spinner{font-size:1.5rem}.metric-list-loading{pointer-events:none;-webkit-user-select:none;user-select:none;opacity:.42;filter:blur(.4px)}\n"] }]
7707
7831
  }], ctorParameters: () => [{ type: i1$2.Router }], propDecorators: { panelData: [{
7708
7832
  type: Input
7709
7833
  }], cardConfig: [{
7710
7834
  type: Input
7835
+ }], showRefresh: [{
7836
+ type: Input
7837
+ }], showMenu: [{
7838
+ type: Input
7839
+ }], menuItems: [{
7840
+ type: Input
7841
+ }], loading: [{
7842
+ type: Input
7843
+ }], refresh: [{
7844
+ type: Output
7845
+ }], actionMenu: [{
7846
+ type: ViewChild,
7847
+ args: ['actionMenu']
7711
7848
  }] } });
7712
7849
 
7713
7850
  class PTMetricPanelModule {
7714
7851
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7715
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, declarations: [PTMetricPanelComponent], imports: [CommonModule, PTCardModule, TooltipModule], exports: [PTMetricPanelComponent] }); }
7716
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, imports: [CommonModule, PTCardModule, TooltipModule] }); }
7852
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, declarations: [PTMetricPanelComponent], imports: [CommonModule, PTCardModule, TooltipModule, MenuModule], exports: [PTMetricPanelComponent] }); }
7853
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, imports: [CommonModule, PTCardModule, TooltipModule, MenuModule] }); }
7717
7854
  }
7718
7855
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PTMetricPanelModule, decorators: [{
7719
7856
  type: NgModule,
7720
7857
  args: [{
7721
7858
  declarations: [PTMetricPanelComponent],
7722
- imports: [CommonModule, PTCardModule, TooltipModule],
7859
+ imports: [CommonModule, PTCardModule, TooltipModule, MenuModule],
7723
7860
  exports: [PTMetricPanelComponent],
7724
7861
  }]
7725
7862
  }] });
@@ -7765,7 +7902,11 @@ class PTChartComparisonComponent {
7765
7902
  this.xAxisTitle = PT_CHART_COMPARISON_DEFAULT_X_AXIS_TITLE;
7766
7903
  this.yAxisTitle = PT_CHART_COMPARISON_DEFAULT_Y_AXIS_TITLE;
7767
7904
  this.viewInitialized = false;
7768
- Chart.register(...registerables, ChartDataLabels);
7905
+ /*
7906
+ * Do not register ChartDataLabels here.
7907
+ * PTChartComparison is a line/comparison chart and does not use it.
7908
+ */
7909
+ Chart.register(...registerables);
7769
7910
  }
7770
7911
  ngAfterViewInit() {
7771
7912
  this.viewInitialized = true;
@@ -7807,19 +7948,19 @@ class PTChartComparisonComponent {
7807
7948
  if (!this.chartConfig || !this.canvasRef) {
7808
7949
  return;
7809
7950
  }
7810
- const canvas = this.canvasRef.nativeElement;
7811
7951
  this.destroyChart();
7812
7952
  const configuration = {
7813
7953
  type: this.chartConfig.type || 'line',
7814
7954
  data: this.getFormattedChartData(),
7815
7955
  options: this.getChartOptions(),
7816
7956
  };
7817
- this.chart = new Chart(canvas, configuration);
7957
+ this.chart = new Chart(this.canvasRef.nativeElement, configuration);
7818
7958
  this.scheduleResize();
7819
7959
  }
7820
7960
  getFormattedChartData() {
7961
+ const sourceDatasets = this.chartConfig.data.datasets ?? [];
7821
7962
  return {
7822
- labels: this.chartConfig.data.labels,
7963
+ labels: [...(this.chartConfig.data.labels ?? [])],
7823
7964
  datasets: [
7824
7965
  {
7825
7966
  label: this.chartConfig.medianTitle?.trim() ||
@@ -7828,7 +7969,10 @@ class PTChartComparisonComponent {
7828
7969
  data: this.calculateMedian(),
7829
7970
  ...PT_CHART_COMPARISON_MEDIAN_DATASET,
7830
7971
  },
7831
- ...this.chartConfig.data.datasets,
7972
+ ...sourceDatasets.map((dataset) => ({
7973
+ ...dataset,
7974
+ data: [...(dataset.data ?? [])],
7975
+ })),
7832
7976
  ],
7833
7977
  };
7834
7978
  }
@@ -7837,7 +7981,7 @@ class PTChartComparisonComponent {
7837
7981
  const labels = this.chartConfig.data.labels ?? [];
7838
7982
  return labels.map((_, index) => {
7839
7983
  const valuesAtTime = datasets
7840
- .map((dataset) => dataset.data[index])
7984
+ .map((dataset) => dataset.data?.[index])
7841
7985
  .filter((value) => typeof value === 'number' && Number.isFinite(value))
7842
7986
  .sort((left, right) => left - right);
7843
7987
  if (!valuesAtTime.length) {
@@ -7862,6 +8006,11 @@ class PTChartComparisonComponent {
7862
8006
  ...externalOptions,
7863
8007
  responsive: true,
7864
8008
  maintainAspectRatio: false,
8009
+ interaction: {
8010
+ mode: 'index',
8011
+ intersect: false,
8012
+ ...(externalOptions.interaction ?? {}),
8013
+ },
7865
8014
  plugins: {
7866
8015
  ...externalPlugins,
7867
8016
  legend: {
@@ -7870,13 +8019,10 @@ class PTChartComparisonComponent {
7870
8019
  ...(externalPlugins.legend ?? {}),
7871
8020
  },
7872
8021
  tooltip: {
8022
+ enabled: true,
7873
8023
  ...PT_CHART_COMPARISON_DEFAULT_TOOLTIP,
7874
8024
  ...(externalPlugins.tooltip ?? {}),
7875
8025
  },
7876
- datalabels: {
7877
- display: false,
7878
- ...(externalPlugins.datalabels ?? {}),
7879
- },
7880
8026
  },
7881
8027
  scales: {
7882
8028
  x: {