ps-helix 6.1.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A comprehensive Angular component library built with Angular 22+ featuring modern design patterns, accessibility-first development, and optimal developer experience.
4
4
 
5
- [![npm version](https://img.shields.io/badge/npm-6.1.0-blue.svg)](https://www.npmjs.com/package/ps-helix)
5
+ [![npm version](https://img.shields.io/badge/npm-6.2.1-blue.svg)](https://www.npmjs.com/package/ps-helix)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Angular](https://img.shields.io/badge/Angular-22.0.0-red.svg)](https://angular.dev/)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0.0-blue.svg)](https://www.typescriptlang.org/)
@@ -106,7 +106,7 @@ After installation, verify that ps-helix is in your `package.json`:
106
106
  ```json
107
107
  {
108
108
  "dependencies": {
109
- "ps-helix": "^6.1.0"
109
+ "ps-helix": "^6.2.1"
110
110
  }
111
111
  }
112
112
  ```
@@ -1182,7 +1182,7 @@ Copyright (c) 2025 PACK Solutions
1182
1182
 
1183
1183
  ---
1184
1184
 
1185
- **Version**: 6.1.0
1185
+ **Version**: 6.2.1
1186
1186
  **Built with**: Angular 22.0.0, TypeScript 6.0.0, Phosphor Icons 2.0.3
1187
1187
  **Author**: Fabrice PEREZ | Design Engineer at PACK Solutions
1188
1188
  **Last Updated**: January 2026
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, computed, ChangeDetectionStrategy, Component, model, inject, ElementRef, signal, PLATFORM_ID, ViewEncapsulation, InjectionToken, viewChild, effect, Injectable, DestroyRef, Directive, ViewContainerRef, ChangeDetectorRef, ViewChild, Injector, afterNextRender, HostListener, Renderer2, contentChild, linkedSignal, EventEmitter, Output, Input, contentChildren } from '@angular/core';
2
+ import { input, output, computed, ChangeDetectionStrategy, Component, model, inject, ElementRef, signal, PLATFORM_ID, ViewEncapsulation, InjectionToken, viewChild, effect, Injectable, DestroyRef, Directive, ViewContainerRef, ChangeDetectorRef, ViewChild, Injector, afterNextRender, HostListener, Renderer2, contentChild, linkedSignal, isDevMode, EventEmitter, Output, Input, contentChildren } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
4
  import { CommonModule, DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
5
5
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
@@ -1134,6 +1134,31 @@ class PshInfoCardComponent {
1134
1134
  return false;
1135
1135
  return val.toString().trim().length > 0;
1136
1136
  }
1137
+ /**
1138
+ * Calcule les classes CSS de la valeur d'une ligne à partir de son emphasis.
1139
+ * Fonction pure (compatible OnPush) : aucun effet de bord.
1140
+ * Un emphasis explicite prime sur l'auto-muted des valeurs nullish.
1141
+ */
1142
+ getValueClasses(item) {
1143
+ const classes = ['info-card-value'];
1144
+ const emphasis = item.emphasis;
1145
+ const isEmpty = item.value == null;
1146
+ const autoMuted = isEmpty && this.options().mutedEmptyValues !== false && !emphasis;
1147
+ if (autoMuted) {
1148
+ classes.push('info-card-value--italic', 'info-card-value--tone-muted');
1149
+ }
1150
+ else if (emphasis) {
1151
+ if (emphasis.italic)
1152
+ classes.push('info-card-value--italic');
1153
+ if (emphasis.bold)
1154
+ classes.push('info-card-value--bold');
1155
+ if (emphasis.strikethrough)
1156
+ classes.push('info-card-value--strikethrough');
1157
+ if (emphasis.tone)
1158
+ classes.push(`info-card-value--tone-${emphasis.tone}`);
1159
+ }
1160
+ return classes.join(' ');
1161
+ }
1137
1162
  copyRowValue(item, index) {
1138
1163
  if (!isPlatformBrowser(this.platformId)) {
1139
1164
  this.copyFailed.emit(item);
@@ -1159,13 +1184,13 @@ class PshInfoCardComponent {
1159
1184
  });
1160
1185
  }
1161
1186
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshInfoCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1162
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: PshInfoCardComponent, isStandalone: true, selector: "psh-info-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, customStyle: { classPropertyName: "customStyle", publicName: "customStyle", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null }, copyButtonLabel: { classPropertyName: "copyButtonLabel", publicName: "copyButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, copyFeedbackText: { classPropertyName: "copyFeedbackText", publicName: "copyFeedbackText", isSignal: true, isRequired: false, transformFunction: null }, autoFullWidthOnMobile: { classPropertyName: "autoFullWidthOnMobile", publicName: "autoFullWidthOnMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", copied: "copied", copyFailed: "copyFailed" }, host: { styleAttribute: "display: block; height: 100%;" }, ngImport: i0, template: "<div\r\n [class]=\"computedClasses()\"\r\n [ngStyle]=\"computedStyles()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\"\r\n role=\"region\"\r\n [attr.tabindex]=\"interactive() && !disabled() ? 0 : null\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n [attr.aria-busy]=\"loading() ? 'true' : null\"\r\n>\r\n @if (title() || hasHeaderActions()) {\r\n <div class=\"info-card-header\">\r\n @if (title()) {\r\n <h3 class=\"info-card-title\">\r\n @if (icon()) {\r\n <i\r\n class=\"ph {{ titleIcon() }}\"\r\n aria-hidden=\"true\"\r\n [attr.data-icon]=\"icon()\"\r\n ></i>\r\n }\r\n {{ title() }}\r\n </h3>\r\n }\r\n <div class=\"info-card-header-actions\">\r\n <ng-content select=\"[card-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"info-card-body\">\r\n @if (loading()) {\r\n <div class=\"info-card-loading\">\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line short\"></div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"info-card-content\"\r\n role=\"list\"\r\n >\r\n @if (shouldShowEmptyState()) {\r\n <div\r\n class=\"info-card-empty-state\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n [attr.data-testid]=\"'info-card-empty-state'\"\r\n >\r\n {{ getEmptyStateMessage() }}\r\n </div>\r\n } @else {\r\n @for (item of data(); track $index) {\r\n <div\r\n class=\"info-card-row\"\r\n role=\"listitem\"\r\n [attr.data-index]=\"$index\"\r\n [class]=\"item.customClass || ''\"\r\n >\r\n <div\r\n class=\"info-card-label\"\r\n [style.width]=\"item.labelWidth || options().labelWidth\"\r\n [attr.data-label]=\"item.label\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n <div\r\n class=\"info-card-value-group\"\r\n [style.width]=\"item.valueWidth || options().valueWidth\"\r\n >\r\n <span\r\n class=\"info-card-value\"\r\n [attr.data-value]=\"item.value\"\r\n [title]=\"item.value?.toString() || ''\"\r\n >{{ item.value ?? 'Non renseign\u00E9' }}</span>\r\n @if (isRowCopyable(item) && hasNonEmptyValue(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"info-card-copy-btn\"\r\n [class.copied]=\"copiedRowIndex() === $index\"\r\n [attr.aria-label]=\"copyButtonLabel() + ' ' + item.label\"\r\n (click)=\"copyRowValue(item, $index); $event.stopPropagation()\"\r\n >\r\n @if (copiedRowIndex() === $index) {\r\n <i class=\"ph ph-check\" aria-hidden=\"true\"></i>\r\n <span class=\"info-card-copy-feedback\" aria-hidden=\"true\">{{ copyFeedbackText() }}</span>\r\n } @else {\r\n <i class=\"ph ph-copy\" aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <span\r\n class=\"info-card-sr-only\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n >\r\n @if (copiedRowIndex() !== null) {\r\n {{ copyFeedbackText() }}\r\n }\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"info-card-actions\" [class]=\"getActionsClasses()\">\r\n <ng-content select=\"[card-actions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: [".info-card{background:var(--surface-card);border-radius:var(--border-radius-lg, .75rem);overflow:hidden;transition:all .2s ease;width:100%;height:100%;display:flex;flex-direction:column;position:relative}.info-card.variant-elevated{box-shadow:var(--shadow-sm)}.info-card.variant-elevated.hoverable:hover{box-shadow:var(--shadow-md)}.info-card.variant-outlined{border:1px solid var(--surface-border)}.info-card.variant-default{background:var(--surface-section)}.info-card.hoverable:hover{transform:translateY(-2px)}.info-card.interactive{cursor:pointer;-webkit-user-select:none;user-select:none}.info-card.interactive:focus{outline:none;box-shadow:0 0 0 3px rgba(var(--primary-500-rgb, 59, 130, 246),.2)}.info-card.interactive:focus:not(:focus-visible){box-shadow:none}.info-card.interactive:active{transform:translateY(0)}.info-card.disabled{opacity:.6;pointer-events:none;cursor:not-allowed}.info-card.loading{pointer-events:none}.info-card-header{background:var(--surface-ground);border-bottom:var(--border-width-1) solid var(--surface-border);display:flex;align-items:center;justify-content:space-between;gap:var(--size-2)}.info-card-header-actions{display:flex;align-items:center;gap:var(--spacing-xs);padding-right:var(--size-2);flex-shrink:0}.info-card-header-actions:empty{display:none}.info-card-title{color:var(--primary-color);margin:0;font-size:var(--font-size-base);font-weight:var(--font-weight-semibold);padding:var(--size-2) var(--size-4);display:flex;align-items:center;gap:var(--size-2);transition:all var(--animation-duration-fast) var(--animation-easing-default)}.info-card-title i{font-size:var(--icon-size-md);flex-shrink:0}.info-card-body{flex:1;display:flex;flex-direction:column}.info-card-content{padding:var(--size-2);min-height:var(--size-48);display:flex;flex-direction:column;transition:opacity var(--animation-duration-normal) var(--animation-easing-default)}.info-card-row{display:flex;align-items:center;padding:var(--size-1) 0;min-height:var(--size-8);animation:fadeIn var(--animation-duration-fast) var(--animation-easing-default);transition:background-color var(--animation-duration-fast) var(--animation-easing-default)}.info-card-row:hover{background-color:var(--surface-hover);border-radius:var(--radius-base)}.info-card-row:last-child{border-bottom:none}.info-card-label{font-weight:var(--font-weight-medium);color:var(--text-color-secondary);font-size:var(--font-size-sm);padding-right:var(--size-2);position:relative;min-width:0;flex:1;line-height:var(--line-height-normal)}.info-card-label:after{content:\"\";position:absolute;right:0;top:50%;width:var(--size-1);height:var(--border-width-1);background-color:var(--surface-border)}.info-card-value-group{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding-left:var(--size-2);min-width:0;flex:2}.info-card-value{font-weight:var(--font-weight-medium);color:var(--text-color);font-size:var(--font-size-sm);text-align:left;min-width:0;flex-shrink:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:var(--line-height-normal)}.info-card-empty-state{flex:1;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--text-color-secondary);font-style:italic;font-size:var(--font-size-sm);padding:var(--size-6);background:var(--surface-ground);border-radius:var(--radius-lg);margin:var(--size-2);border:var(--border-width-1) dashed var(--surface-border);animation:fadeIn var(--animation-duration-normal) var(--animation-easing-default)}.info-card-actions>*{padding:var(--size-3) var(--size-4);background:var(--surface-ground);border-top:var(--border-width-1) solid var(--surface-border);display:flex;gap:var(--spacing-sm);align-items:center;justify-content:flex-end}.info-card-actions:not(:has(*)){display:none}.info-card-actions.mobile-full-width-buttons>*{display:flex;flex-direction:column;width:100%;gap:var(--spacing-sm)}.info-card-loading{display:flex;flex-direction:column;gap:var(--spacing-sm);padding:var(--size-2);animation:pulse 1.5s ease-in-out infinite}.skeleton-line{height:var(--size-4);background:linear-gradient(90deg,var(--surface-border) 25%,var(--surface-hover) 50%,var(--surface-border) 75%);background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:var(--spacing-xs)}.skeleton-line.short{width:60%}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes fadeIn{0%{opacity:0;transform:translateY(var(--size-1))}to{opacity:1;transform:translateY(0)}}.info-card-copy-btn{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-xs);border:none;background:transparent;color:var(--text-color-secondary);border-radius:var(--border-radius);cursor:pointer;min-width:24px;min-height:24px;flex-shrink:0;transition:opacity var(--animation-duration-fast) var(--animation-easing-default),color var(--animation-duration-fast) var(--animation-easing-default)}@media(hover:hover){.info-card-copy-btn{opacity:0}.info-card-row:hover .info-card-copy-btn,.info-card-row:focus-within .info-card-copy-btn,.info-card-value-group:hover .info-card-copy-btn,.info-card-value-group:focus-within .info-card-copy-btn{opacity:1}}.info-card-copy-btn:hover{color:var(--primary-color)}.info-card-copy-btn:focus-visible{outline:none;box-shadow:0 0 0 2px var(--primary-color);opacity:1}.info-card-copy-btn.copied{color:var(--success-color);opacity:1}.info-card-copy-btn i{font-size:var(--icon-size-sm)}.info-card-copy-feedback{font-size:var(--font-size-xs);font-weight:var(--font-weight-medium);color:var(--success-color);white-space:nowrap}.info-card-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:768px){.info-card-title{font-size:var(--font-size-sm);padding:var(--size-1-5) var(--size-3);gap:var(--size-1-5)}.info-card-title i{font-size:var(--icon-size-sm)}.info-card-header-actions{padding-right:var(--size-1-5)}.info-card-content{padding:var(--size-1-5);min-height:var(--size-36)}.info-card-row{min-height:var(--size-7);padding:var(--size-0-5) 0}.info-card-label,.info-card-value{font-size:var(--font-size-xs)}.info-card-empty-state{font-size:var(--font-size-xs);padding:var(--size-4);margin:var(--size-1-5)}.info-card-actions{padding:var(--size-2) var(--size-3)}}@media(max-width:480px){.info-card-header{flex-wrap:wrap;gap:var(--size-1)}.info-card-title{font-size:var(--font-size-xs);padding:var(--size-1) var(--size-2);gap:var(--size-1)}.info-card-title i{font-size:var(--icon-size-xs)}.info-card-header-actions{padding:0 var(--size-1) var(--size-1) var(--size-1)}.info-card-content{padding:var(--size-1);min-height:auto}.info-card-row{flex-direction:column;align-items:flex-start;gap:var(--size-0-5);padding:var(--size-1-5) 0;min-height:auto}.info-card-row:hover{background-color:transparent}.info-card-label{width:100%;padding-right:0;text-align:left;flex:none;font-weight:var(--font-weight-semibold);color:var(--text-color)}.info-card-label:after{display:none}.info-card-value-group{width:100%;padding-left:0;flex:none}.info-card-value{padding-left:0;text-align:left;flex-shrink:1;font-weight:var(--font-weight-normal);white-space:normal}.info-card-empty-state{padding:var(--size-3);margin:var(--size-1)}.info-card-actions{padding:var(--size-1-5) var(--size-2);flex-direction:column;gap:var(--spacing-md)}.info-card-actions>*{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1187
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.4", type: PshInfoCardComponent, isStandalone: true, selector: "psh-info-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null }, customStyle: { classPropertyName: "customStyle", publicName: "customStyle", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null }, copyButtonLabel: { classPropertyName: "copyButtonLabel", publicName: "copyButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, copyFeedbackText: { classPropertyName: "copyFeedbackText", publicName: "copyFeedbackText", isSignal: true, isRequired: false, transformFunction: null }, autoFullWidthOnMobile: { classPropertyName: "autoFullWidthOnMobile", publicName: "autoFullWidthOnMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked", copied: "copied", copyFailed: "copyFailed" }, host: { styleAttribute: "display: block; height: 100%;" }, ngImport: i0, template: "<div\r\n [class]=\"computedClasses()\"\r\n [ngStyle]=\"computedStyles()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\"\r\n role=\"region\"\r\n [attr.tabindex]=\"interactive() && !disabled() ? 0 : null\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n [attr.aria-busy]=\"loading() ? 'true' : null\"\r\n>\r\n @if (title() || hasHeaderActions()) {\r\n <div class=\"info-card-header\">\r\n @if (title()) {\r\n <h3 class=\"info-card-title\">\r\n @if (icon()) {\r\n <i\r\n class=\"ph {{ titleIcon() }}\"\r\n aria-hidden=\"true\"\r\n [attr.data-icon]=\"icon()\"\r\n ></i>\r\n }\r\n {{ title() }}\r\n </h3>\r\n }\r\n <div class=\"info-card-header-actions\">\r\n <ng-content select=\"[card-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"info-card-body\">\r\n @if (loading()) {\r\n <div class=\"info-card-loading\">\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line short\"></div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"info-card-content\"\r\n role=\"list\"\r\n >\r\n @if (shouldShowEmptyState()) {\r\n <div\r\n class=\"info-card-empty-state\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n [attr.data-testid]=\"'info-card-empty-state'\"\r\n >\r\n {{ getEmptyStateMessage() }}\r\n </div>\r\n } @else {\r\n @for (item of data(); track $index) {\r\n <div\r\n class=\"info-card-row\"\r\n role=\"listitem\"\r\n [attr.data-index]=\"$index\"\r\n [class]=\"item.customClass || ''\"\r\n >\r\n <div\r\n class=\"info-card-label\"\r\n [style.width]=\"item.labelWidth || options().labelWidth\"\r\n [attr.data-label]=\"item.label\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n <div\r\n class=\"info-card-value-group\"\r\n [style.width]=\"item.valueWidth || options().valueWidth\"\r\n >\r\n <span\r\n [class]=\"getValueClasses(item)\"\r\n [attr.data-value]=\"item.value\"\r\n [title]=\"item.value?.toString() || ''\"\r\n >{{ item.value ?? 'Non renseign\u00E9' }}</span>\r\n @if (isRowCopyable(item) && hasNonEmptyValue(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"info-card-copy-btn\"\r\n [class.copied]=\"copiedRowIndex() === $index\"\r\n [attr.aria-label]=\"copyButtonLabel() + ' ' + item.label\"\r\n (click)=\"copyRowValue(item, $index); $event.stopPropagation()\"\r\n >\r\n @if (copiedRowIndex() === $index) {\r\n <i class=\"ph ph-check\" aria-hidden=\"true\"></i>\r\n <span class=\"info-card-copy-feedback\" aria-hidden=\"true\">{{ copyFeedbackText() }}</span>\r\n } @else {\r\n <i class=\"ph ph-copy\" aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <span\r\n class=\"info-card-sr-only\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n >\r\n @if (copiedRowIndex() !== null) {\r\n {{ copyFeedbackText() }}\r\n }\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"info-card-actions\" [class]=\"getActionsClasses()\">\r\n <ng-content select=\"[card-actions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: [".info-card{background:var(--surface-card);border-radius:var(--border-radius-lg, .75rem);overflow:hidden;transition:all .2s ease;width:100%;height:100%;display:flex;flex-direction:column;position:relative}.info-card.variant-elevated{box-shadow:var(--shadow-sm)}.info-card.variant-elevated.hoverable:hover{box-shadow:var(--shadow-md)}.info-card.variant-outlined{border:1px solid var(--surface-border)}.info-card.variant-default{background:var(--surface-section)}.info-card.hoverable:hover{transform:translateY(-2px)}.info-card.interactive{cursor:pointer;-webkit-user-select:none;user-select:none}.info-card.interactive:focus{outline:none;box-shadow:0 0 0 3px rgba(var(--primary-500-rgb, 59, 130, 246),.2)}.info-card.interactive:focus:not(:focus-visible){box-shadow:none}.info-card.interactive:active{transform:translateY(0)}.info-card.disabled{opacity:.6;pointer-events:none;cursor:not-allowed}.info-card.loading{pointer-events:none}.info-card-header{background:var(--surface-ground);border-bottom:var(--border-width-1) solid var(--surface-border);display:flex;align-items:center;justify-content:space-between;gap:var(--size-2)}.info-card-header-actions{display:flex;align-items:center;gap:var(--spacing-xs);padding-right:var(--size-2);flex-shrink:0}.info-card-header-actions:empty{display:none}.info-card-title{color:var(--primary-color);margin:0;font-size:var(--font-size-base);font-weight:var(--font-weight-semibold);padding:var(--size-2) var(--size-4);display:flex;align-items:center;gap:var(--size-2);transition:all var(--animation-duration-fast) var(--animation-easing-default)}.info-card-title i{font-size:var(--icon-size-md);flex-shrink:0}.info-card-body{flex:1;display:flex;flex-direction:column}.info-card-content{padding:var(--size-2);min-height:var(--size-48);display:flex;flex-direction:column;transition:opacity var(--animation-duration-normal) var(--animation-easing-default)}.info-card-row{display:flex;align-items:center;padding:var(--size-1) 0;min-height:var(--size-8);animation:fadeIn var(--animation-duration-fast) var(--animation-easing-default);transition:background-color var(--animation-duration-fast) var(--animation-easing-default)}.info-card-row:hover{background-color:var(--surface-hover);border-radius:var(--radius-base)}.info-card-row:last-child{border-bottom:none}.info-card-label{font-weight:var(--font-weight-medium);color:var(--text-color-secondary);font-size:var(--font-size-sm);padding-right:var(--size-2);position:relative;min-width:0;flex:1;line-height:var(--line-height-normal)}.info-card-label:after{content:\"\";position:absolute;right:0;top:50%;width:var(--size-1);height:var(--border-width-1);background-color:var(--surface-border)}.info-card-value-group{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding-left:var(--size-2);min-width:0;flex:2}.info-card-value{font-weight:var(--font-weight-medium);color:var(--text-color);font-size:var(--font-size-sm);text-align:left;min-width:0;flex-shrink:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:var(--line-height-normal)}.info-card-value--italic{font-style:italic}.info-card-value--strikethrough{text-decoration:line-through}.info-card-value.info-card-value--bold{font-weight:var(--font-weight-semibold)}.info-card-value--tone-primary{color:var(--primary-color)}.info-card-value--tone-success{color:var(--success-color)}.info-card-value--tone-warning{color:var(--warning-color)}.info-card-value--tone-danger{color:var(--danger-color)}.info-card-value--tone-info{color:var(--alert-info-text-color)}.info-card-value.info-card-value--tone-muted{color:var(--text-color-secondary);font-weight:var(--font-weight-normal)}.info-card-empty-state{flex:1;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--text-color-secondary);font-style:italic;font-size:var(--font-size-sm);padding:var(--size-6);background:var(--surface-ground);border-radius:var(--radius-lg);margin:var(--size-2);border:var(--border-width-1) dashed var(--surface-border);animation:fadeIn var(--animation-duration-normal) var(--animation-easing-default)}.info-card-actions>*{padding:var(--size-3) var(--size-4);background:var(--surface-ground);border-top:var(--border-width-1) solid var(--surface-border);display:flex;gap:var(--spacing-sm);align-items:center;justify-content:flex-end}.info-card-actions:not(:has(*)){display:none}.info-card-actions.mobile-full-width-buttons>*{display:flex;flex-direction:column;width:100%;gap:var(--spacing-sm)}.info-card-loading{display:flex;flex-direction:column;gap:var(--spacing-sm);padding:var(--size-2);animation:pulse 1.5s ease-in-out infinite}.skeleton-line{height:var(--size-4);background:linear-gradient(90deg,var(--surface-border) 25%,var(--surface-hover) 50%,var(--surface-border) 75%);background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:var(--spacing-xs)}.skeleton-line.short{width:60%}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes fadeIn{0%{opacity:0;transform:translateY(var(--size-1))}to{opacity:1;transform:translateY(0)}}.info-card-copy-btn{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-xs);border:none;background:transparent;color:var(--text-color-secondary);border-radius:var(--border-radius);cursor:pointer;min-width:24px;min-height:24px;flex-shrink:0;transition:opacity var(--animation-duration-fast) var(--animation-easing-default),color var(--animation-duration-fast) var(--animation-easing-default)}@media(hover:hover){.info-card-copy-btn{opacity:0}.info-card-row:hover .info-card-copy-btn,.info-card-row:focus-within .info-card-copy-btn,.info-card-value-group:hover .info-card-copy-btn,.info-card-value-group:focus-within .info-card-copy-btn{opacity:1}}.info-card-copy-btn:hover{color:var(--primary-color)}.info-card-copy-btn:focus-visible{outline:none;box-shadow:0 0 0 2px var(--primary-color);opacity:1}.info-card-copy-btn.copied{color:var(--success-color);opacity:1}.info-card-copy-btn i{font-size:var(--icon-size-sm)}.info-card-copy-feedback{font-size:var(--font-size-xs);font-weight:var(--font-weight-medium);color:var(--success-color);white-space:nowrap}.info-card-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:768px){.info-card-title{font-size:var(--font-size-sm);padding:var(--size-1-5) var(--size-3);gap:var(--size-1-5)}.info-card-title i{font-size:var(--icon-size-sm)}.info-card-header-actions{padding-right:var(--size-1-5)}.info-card-content{padding:var(--size-1-5);min-height:var(--size-36)}.info-card-row{min-height:var(--size-7);padding:var(--size-0-5) 0}.info-card-label,.info-card-value{font-size:var(--font-size-xs)}.info-card-empty-state{font-size:var(--font-size-xs);padding:var(--size-4);margin:var(--size-1-5)}.info-card-actions{padding:var(--size-2) var(--size-3)}}@media(max-width:480px){.info-card-header{flex-wrap:wrap;gap:var(--size-1)}.info-card-title{font-size:var(--font-size-xs);padding:var(--size-1) var(--size-2);gap:var(--size-1)}.info-card-title i{font-size:var(--icon-size-xs)}.info-card-header-actions{padding:0 var(--size-1) var(--size-1) var(--size-1)}.info-card-content{padding:var(--size-1);min-height:auto}.info-card-row{flex-direction:column;align-items:flex-start;gap:var(--size-0-5);padding:var(--size-1-5) 0;min-height:auto}.info-card-row:hover{background-color:transparent}.info-card-label{width:100%;padding-right:0;text-align:left;flex:none;font-weight:var(--font-weight-semibold);color:var(--text-color)}.info-card-label:after{display:none}.info-card-value-group{width:100%;padding-left:0;flex:none}.info-card-value{padding-left:0;text-align:left;flex-shrink:1;font-weight:var(--font-weight-normal);white-space:normal}.info-card-empty-state{padding:var(--size-3);margin:var(--size-1)}.info-card-actions{padding:var(--size-1-5) var(--size-2);flex-direction:column;gap:var(--spacing-md)}.info-card-actions>*{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1163
1188
  }
1164
1189
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: PshInfoCardComponent, decorators: [{
1165
1190
  type: Component,
1166
1191
  args: [{ selector: 'psh-info-card', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
1167
1192
  style: 'display: block; height: 100%;'
1168
- }, template: "<div\r\n [class]=\"computedClasses()\"\r\n [ngStyle]=\"computedStyles()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\"\r\n role=\"region\"\r\n [attr.tabindex]=\"interactive() && !disabled() ? 0 : null\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n [attr.aria-busy]=\"loading() ? 'true' : null\"\r\n>\r\n @if (title() || hasHeaderActions()) {\r\n <div class=\"info-card-header\">\r\n @if (title()) {\r\n <h3 class=\"info-card-title\">\r\n @if (icon()) {\r\n <i\r\n class=\"ph {{ titleIcon() }}\"\r\n aria-hidden=\"true\"\r\n [attr.data-icon]=\"icon()\"\r\n ></i>\r\n }\r\n {{ title() }}\r\n </h3>\r\n }\r\n <div class=\"info-card-header-actions\">\r\n <ng-content select=\"[card-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"info-card-body\">\r\n @if (loading()) {\r\n <div class=\"info-card-loading\">\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line short\"></div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"info-card-content\"\r\n role=\"list\"\r\n >\r\n @if (shouldShowEmptyState()) {\r\n <div\r\n class=\"info-card-empty-state\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n [attr.data-testid]=\"'info-card-empty-state'\"\r\n >\r\n {{ getEmptyStateMessage() }}\r\n </div>\r\n } @else {\r\n @for (item of data(); track $index) {\r\n <div\r\n class=\"info-card-row\"\r\n role=\"listitem\"\r\n [attr.data-index]=\"$index\"\r\n [class]=\"item.customClass || ''\"\r\n >\r\n <div\r\n class=\"info-card-label\"\r\n [style.width]=\"item.labelWidth || options().labelWidth\"\r\n [attr.data-label]=\"item.label\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n <div\r\n class=\"info-card-value-group\"\r\n [style.width]=\"item.valueWidth || options().valueWidth\"\r\n >\r\n <span\r\n class=\"info-card-value\"\r\n [attr.data-value]=\"item.value\"\r\n [title]=\"item.value?.toString() || ''\"\r\n >{{ item.value ?? 'Non renseign\u00E9' }}</span>\r\n @if (isRowCopyable(item) && hasNonEmptyValue(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"info-card-copy-btn\"\r\n [class.copied]=\"copiedRowIndex() === $index\"\r\n [attr.aria-label]=\"copyButtonLabel() + ' ' + item.label\"\r\n (click)=\"copyRowValue(item, $index); $event.stopPropagation()\"\r\n >\r\n @if (copiedRowIndex() === $index) {\r\n <i class=\"ph ph-check\" aria-hidden=\"true\"></i>\r\n <span class=\"info-card-copy-feedback\" aria-hidden=\"true\">{{ copyFeedbackText() }}</span>\r\n } @else {\r\n <i class=\"ph ph-copy\" aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <span\r\n class=\"info-card-sr-only\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n >\r\n @if (copiedRowIndex() !== null) {\r\n {{ copyFeedbackText() }}\r\n }\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"info-card-actions\" [class]=\"getActionsClasses()\">\r\n <ng-content select=\"[card-actions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: [".info-card{background:var(--surface-card);border-radius:var(--border-radius-lg, .75rem);overflow:hidden;transition:all .2s ease;width:100%;height:100%;display:flex;flex-direction:column;position:relative}.info-card.variant-elevated{box-shadow:var(--shadow-sm)}.info-card.variant-elevated.hoverable:hover{box-shadow:var(--shadow-md)}.info-card.variant-outlined{border:1px solid var(--surface-border)}.info-card.variant-default{background:var(--surface-section)}.info-card.hoverable:hover{transform:translateY(-2px)}.info-card.interactive{cursor:pointer;-webkit-user-select:none;user-select:none}.info-card.interactive:focus{outline:none;box-shadow:0 0 0 3px rgba(var(--primary-500-rgb, 59, 130, 246),.2)}.info-card.interactive:focus:not(:focus-visible){box-shadow:none}.info-card.interactive:active{transform:translateY(0)}.info-card.disabled{opacity:.6;pointer-events:none;cursor:not-allowed}.info-card.loading{pointer-events:none}.info-card-header{background:var(--surface-ground);border-bottom:var(--border-width-1) solid var(--surface-border);display:flex;align-items:center;justify-content:space-between;gap:var(--size-2)}.info-card-header-actions{display:flex;align-items:center;gap:var(--spacing-xs);padding-right:var(--size-2);flex-shrink:0}.info-card-header-actions:empty{display:none}.info-card-title{color:var(--primary-color);margin:0;font-size:var(--font-size-base);font-weight:var(--font-weight-semibold);padding:var(--size-2) var(--size-4);display:flex;align-items:center;gap:var(--size-2);transition:all var(--animation-duration-fast) var(--animation-easing-default)}.info-card-title i{font-size:var(--icon-size-md);flex-shrink:0}.info-card-body{flex:1;display:flex;flex-direction:column}.info-card-content{padding:var(--size-2);min-height:var(--size-48);display:flex;flex-direction:column;transition:opacity var(--animation-duration-normal) var(--animation-easing-default)}.info-card-row{display:flex;align-items:center;padding:var(--size-1) 0;min-height:var(--size-8);animation:fadeIn var(--animation-duration-fast) var(--animation-easing-default);transition:background-color var(--animation-duration-fast) var(--animation-easing-default)}.info-card-row:hover{background-color:var(--surface-hover);border-radius:var(--radius-base)}.info-card-row:last-child{border-bottom:none}.info-card-label{font-weight:var(--font-weight-medium);color:var(--text-color-secondary);font-size:var(--font-size-sm);padding-right:var(--size-2);position:relative;min-width:0;flex:1;line-height:var(--line-height-normal)}.info-card-label:after{content:\"\";position:absolute;right:0;top:50%;width:var(--size-1);height:var(--border-width-1);background-color:var(--surface-border)}.info-card-value-group{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding-left:var(--size-2);min-width:0;flex:2}.info-card-value{font-weight:var(--font-weight-medium);color:var(--text-color);font-size:var(--font-size-sm);text-align:left;min-width:0;flex-shrink:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:var(--line-height-normal)}.info-card-empty-state{flex:1;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--text-color-secondary);font-style:italic;font-size:var(--font-size-sm);padding:var(--size-6);background:var(--surface-ground);border-radius:var(--radius-lg);margin:var(--size-2);border:var(--border-width-1) dashed var(--surface-border);animation:fadeIn var(--animation-duration-normal) var(--animation-easing-default)}.info-card-actions>*{padding:var(--size-3) var(--size-4);background:var(--surface-ground);border-top:var(--border-width-1) solid var(--surface-border);display:flex;gap:var(--spacing-sm);align-items:center;justify-content:flex-end}.info-card-actions:not(:has(*)){display:none}.info-card-actions.mobile-full-width-buttons>*{display:flex;flex-direction:column;width:100%;gap:var(--spacing-sm)}.info-card-loading{display:flex;flex-direction:column;gap:var(--spacing-sm);padding:var(--size-2);animation:pulse 1.5s ease-in-out infinite}.skeleton-line{height:var(--size-4);background:linear-gradient(90deg,var(--surface-border) 25%,var(--surface-hover) 50%,var(--surface-border) 75%);background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:var(--spacing-xs)}.skeleton-line.short{width:60%}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes fadeIn{0%{opacity:0;transform:translateY(var(--size-1))}to{opacity:1;transform:translateY(0)}}.info-card-copy-btn{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-xs);border:none;background:transparent;color:var(--text-color-secondary);border-radius:var(--border-radius);cursor:pointer;min-width:24px;min-height:24px;flex-shrink:0;transition:opacity var(--animation-duration-fast) var(--animation-easing-default),color var(--animation-duration-fast) var(--animation-easing-default)}@media(hover:hover){.info-card-copy-btn{opacity:0}.info-card-row:hover .info-card-copy-btn,.info-card-row:focus-within .info-card-copy-btn,.info-card-value-group:hover .info-card-copy-btn,.info-card-value-group:focus-within .info-card-copy-btn{opacity:1}}.info-card-copy-btn:hover{color:var(--primary-color)}.info-card-copy-btn:focus-visible{outline:none;box-shadow:0 0 0 2px var(--primary-color);opacity:1}.info-card-copy-btn.copied{color:var(--success-color);opacity:1}.info-card-copy-btn i{font-size:var(--icon-size-sm)}.info-card-copy-feedback{font-size:var(--font-size-xs);font-weight:var(--font-weight-medium);color:var(--success-color);white-space:nowrap}.info-card-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:768px){.info-card-title{font-size:var(--font-size-sm);padding:var(--size-1-5) var(--size-3);gap:var(--size-1-5)}.info-card-title i{font-size:var(--icon-size-sm)}.info-card-header-actions{padding-right:var(--size-1-5)}.info-card-content{padding:var(--size-1-5);min-height:var(--size-36)}.info-card-row{min-height:var(--size-7);padding:var(--size-0-5) 0}.info-card-label,.info-card-value{font-size:var(--font-size-xs)}.info-card-empty-state{font-size:var(--font-size-xs);padding:var(--size-4);margin:var(--size-1-5)}.info-card-actions{padding:var(--size-2) var(--size-3)}}@media(max-width:480px){.info-card-header{flex-wrap:wrap;gap:var(--size-1)}.info-card-title{font-size:var(--font-size-xs);padding:var(--size-1) var(--size-2);gap:var(--size-1)}.info-card-title i{font-size:var(--icon-size-xs)}.info-card-header-actions{padding:0 var(--size-1) var(--size-1) var(--size-1)}.info-card-content{padding:var(--size-1);min-height:auto}.info-card-row{flex-direction:column;align-items:flex-start;gap:var(--size-0-5);padding:var(--size-1-5) 0;min-height:auto}.info-card-row:hover{background-color:transparent}.info-card-label{width:100%;padding-right:0;text-align:left;flex:none;font-weight:var(--font-weight-semibold);color:var(--text-color)}.info-card-label:after{display:none}.info-card-value-group{width:100%;padding-left:0;flex:none}.info-card-value{padding-left:0;text-align:left;flex-shrink:1;font-weight:var(--font-weight-normal);white-space:normal}.info-card-empty-state{padding:var(--size-3);margin:var(--size-1)}.info-card-actions{padding:var(--size-1-5) var(--size-2);flex-direction:column;gap:var(--spacing-md)}.info-card-actions>*{width:100%}}\n"] }]
1193
+ }, template: "<div\r\n [class]=\"computedClasses()\"\r\n [ngStyle]=\"computedStyles()\"\r\n (click)=\"handleClick($event)\"\r\n (keydown)=\"handleKeydown($event)\"\r\n role=\"region\"\r\n [attr.tabindex]=\"interactive() && !disabled() ? 0 : null\"\r\n [attr.aria-label]=\"computedAriaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\r\n [attr.aria-busy]=\"loading() ? 'true' : null\"\r\n>\r\n @if (title() || hasHeaderActions()) {\r\n <div class=\"info-card-header\">\r\n @if (title()) {\r\n <h3 class=\"info-card-title\">\r\n @if (icon()) {\r\n <i\r\n class=\"ph {{ titleIcon() }}\"\r\n aria-hidden=\"true\"\r\n [attr.data-icon]=\"icon()\"\r\n ></i>\r\n }\r\n {{ title() }}\r\n </h3>\r\n }\r\n <div class=\"info-card-header-actions\">\r\n <ng-content select=\"[card-header-actions]\"></ng-content>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"info-card-body\">\r\n @if (loading()) {\r\n <div class=\"info-card-loading\">\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line\"></div>\r\n <div class=\"skeleton-line short\"></div>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"info-card-content\"\r\n role=\"list\"\r\n >\r\n @if (shouldShowEmptyState()) {\r\n <div\r\n class=\"info-card-empty-state\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n [attr.data-testid]=\"'info-card-empty-state'\"\r\n >\r\n {{ getEmptyStateMessage() }}\r\n </div>\r\n } @else {\r\n @for (item of data(); track $index) {\r\n <div\r\n class=\"info-card-row\"\r\n role=\"listitem\"\r\n [attr.data-index]=\"$index\"\r\n [class]=\"item.customClass || ''\"\r\n >\r\n <div\r\n class=\"info-card-label\"\r\n [style.width]=\"item.labelWidth || options().labelWidth\"\r\n [attr.data-label]=\"item.label\"\r\n >\r\n {{ item.label }}\r\n </div>\r\n <div\r\n class=\"info-card-value-group\"\r\n [style.width]=\"item.valueWidth || options().valueWidth\"\r\n >\r\n <span\r\n [class]=\"getValueClasses(item)\"\r\n [attr.data-value]=\"item.value\"\r\n [title]=\"item.value?.toString() || ''\"\r\n >{{ item.value ?? 'Non renseign\u00E9' }}</span>\r\n @if (isRowCopyable(item) && hasNonEmptyValue(item)) {\r\n <button\r\n type=\"button\"\r\n class=\"info-card-copy-btn\"\r\n [class.copied]=\"copiedRowIndex() === $index\"\r\n [attr.aria-label]=\"copyButtonLabel() + ' ' + item.label\"\r\n (click)=\"copyRowValue(item, $index); $event.stopPropagation()\"\r\n >\r\n @if (copiedRowIndex() === $index) {\r\n <i class=\"ph ph-check\" aria-hidden=\"true\"></i>\r\n <span class=\"info-card-copy-feedback\" aria-hidden=\"true\">{{ copyFeedbackText() }}</span>\r\n } @else {\r\n <i class=\"ph ph-copy\" aria-hidden=\"true\"></i>\r\n }\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n <span\r\n class=\"info-card-sr-only\"\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n >\r\n @if (copiedRowIndex() !== null) {\r\n {{ copyFeedbackText() }}\r\n }\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"info-card-actions\" [class]=\"getActionsClasses()\">\r\n <ng-content select=\"[card-actions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: [".info-card{background:var(--surface-card);border-radius:var(--border-radius-lg, .75rem);overflow:hidden;transition:all .2s ease;width:100%;height:100%;display:flex;flex-direction:column;position:relative}.info-card.variant-elevated{box-shadow:var(--shadow-sm)}.info-card.variant-elevated.hoverable:hover{box-shadow:var(--shadow-md)}.info-card.variant-outlined{border:1px solid var(--surface-border)}.info-card.variant-default{background:var(--surface-section)}.info-card.hoverable:hover{transform:translateY(-2px)}.info-card.interactive{cursor:pointer;-webkit-user-select:none;user-select:none}.info-card.interactive:focus{outline:none;box-shadow:0 0 0 3px rgba(var(--primary-500-rgb, 59, 130, 246),.2)}.info-card.interactive:focus:not(:focus-visible){box-shadow:none}.info-card.interactive:active{transform:translateY(0)}.info-card.disabled{opacity:.6;pointer-events:none;cursor:not-allowed}.info-card.loading{pointer-events:none}.info-card-header{background:var(--surface-ground);border-bottom:var(--border-width-1) solid var(--surface-border);display:flex;align-items:center;justify-content:space-between;gap:var(--size-2)}.info-card-header-actions{display:flex;align-items:center;gap:var(--spacing-xs);padding-right:var(--size-2);flex-shrink:0}.info-card-header-actions:empty{display:none}.info-card-title{color:var(--primary-color);margin:0;font-size:var(--font-size-base);font-weight:var(--font-weight-semibold);padding:var(--size-2) var(--size-4);display:flex;align-items:center;gap:var(--size-2);transition:all var(--animation-duration-fast) var(--animation-easing-default)}.info-card-title i{font-size:var(--icon-size-md);flex-shrink:0}.info-card-body{flex:1;display:flex;flex-direction:column}.info-card-content{padding:var(--size-2);min-height:var(--size-48);display:flex;flex-direction:column;transition:opacity var(--animation-duration-normal) var(--animation-easing-default)}.info-card-row{display:flex;align-items:center;padding:var(--size-1) 0;min-height:var(--size-8);animation:fadeIn var(--animation-duration-fast) var(--animation-easing-default);transition:background-color var(--animation-duration-fast) var(--animation-easing-default)}.info-card-row:hover{background-color:var(--surface-hover);border-radius:var(--radius-base)}.info-card-row:last-child{border-bottom:none}.info-card-label{font-weight:var(--font-weight-medium);color:var(--text-color-secondary);font-size:var(--font-size-sm);padding-right:var(--size-2);position:relative;min-width:0;flex:1;line-height:var(--line-height-normal)}.info-card-label:after{content:\"\";position:absolute;right:0;top:50%;width:var(--size-1);height:var(--border-width-1);background-color:var(--surface-border)}.info-card-value-group{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding-left:var(--size-2);min-width:0;flex:2}.info-card-value{font-weight:var(--font-weight-medium);color:var(--text-color);font-size:var(--font-size-sm);text-align:left;min-width:0;flex-shrink:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:var(--line-height-normal)}.info-card-value--italic{font-style:italic}.info-card-value--strikethrough{text-decoration:line-through}.info-card-value.info-card-value--bold{font-weight:var(--font-weight-semibold)}.info-card-value--tone-primary{color:var(--primary-color)}.info-card-value--tone-success{color:var(--success-color)}.info-card-value--tone-warning{color:var(--warning-color)}.info-card-value--tone-danger{color:var(--danger-color)}.info-card-value--tone-info{color:var(--alert-info-text-color)}.info-card-value.info-card-value--tone-muted{color:var(--text-color-secondary);font-weight:var(--font-weight-normal)}.info-card-empty-state{flex:1;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--text-color-secondary);font-style:italic;font-size:var(--font-size-sm);padding:var(--size-6);background:var(--surface-ground);border-radius:var(--radius-lg);margin:var(--size-2);border:var(--border-width-1) dashed var(--surface-border);animation:fadeIn var(--animation-duration-normal) var(--animation-easing-default)}.info-card-actions>*{padding:var(--size-3) var(--size-4);background:var(--surface-ground);border-top:var(--border-width-1) solid var(--surface-border);display:flex;gap:var(--spacing-sm);align-items:center;justify-content:flex-end}.info-card-actions:not(:has(*)){display:none}.info-card-actions.mobile-full-width-buttons>*{display:flex;flex-direction:column;width:100%;gap:var(--spacing-sm)}.info-card-loading{display:flex;flex-direction:column;gap:var(--spacing-sm);padding:var(--size-2);animation:pulse 1.5s ease-in-out infinite}.skeleton-line{height:var(--size-4);background:linear-gradient(90deg,var(--surface-border) 25%,var(--surface-hover) 50%,var(--surface-border) 75%);background-size:200% 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:var(--spacing-xs)}.skeleton-line.short{width:60%}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes fadeIn{0%{opacity:0;transform:translateY(var(--size-1))}to{opacity:1;transform:translateY(0)}}.info-card-copy-btn{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding:var(--spacing-xs);border:none;background:transparent;color:var(--text-color-secondary);border-radius:var(--border-radius);cursor:pointer;min-width:24px;min-height:24px;flex-shrink:0;transition:opacity var(--animation-duration-fast) var(--animation-easing-default),color var(--animation-duration-fast) var(--animation-easing-default)}@media(hover:hover){.info-card-copy-btn{opacity:0}.info-card-row:hover .info-card-copy-btn,.info-card-row:focus-within .info-card-copy-btn,.info-card-value-group:hover .info-card-copy-btn,.info-card-value-group:focus-within .info-card-copy-btn{opacity:1}}.info-card-copy-btn:hover{color:var(--primary-color)}.info-card-copy-btn:focus-visible{outline:none;box-shadow:0 0 0 2px var(--primary-color);opacity:1}.info-card-copy-btn.copied{color:var(--success-color);opacity:1}.info-card-copy-btn i{font-size:var(--icon-size-sm)}.info-card-copy-feedback{font-size:var(--font-size-xs);font-weight:var(--font-weight-medium);color:var(--success-color);white-space:nowrap}.info-card-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(max-width:768px){.info-card-title{font-size:var(--font-size-sm);padding:var(--size-1-5) var(--size-3);gap:var(--size-1-5)}.info-card-title i{font-size:var(--icon-size-sm)}.info-card-header-actions{padding-right:var(--size-1-5)}.info-card-content{padding:var(--size-1-5);min-height:var(--size-36)}.info-card-row{min-height:var(--size-7);padding:var(--size-0-5) 0}.info-card-label,.info-card-value{font-size:var(--font-size-xs)}.info-card-empty-state{font-size:var(--font-size-xs);padding:var(--size-4);margin:var(--size-1-5)}.info-card-actions{padding:var(--size-2) var(--size-3)}}@media(max-width:480px){.info-card-header{flex-wrap:wrap;gap:var(--size-1)}.info-card-title{font-size:var(--font-size-xs);padding:var(--size-1) var(--size-2);gap:var(--size-1)}.info-card-title i{font-size:var(--icon-size-xs)}.info-card-header-actions{padding:0 var(--size-1) var(--size-1) var(--size-1)}.info-card-content{padding:var(--size-1);min-height:auto}.info-card-row{flex-direction:column;align-items:flex-start;gap:var(--size-0-5);padding:var(--size-1-5) 0;min-height:auto}.info-card-row:hover{background-color:transparent}.info-card-label{width:100%;padding-right:0;text-align:left;flex:none;font-weight:var(--font-weight-semibold);color:var(--text-color)}.info-card-label:after{display:none}.info-card-value-group{width:100%;padding-left:0;flex:none}.info-card-value{padding-left:0;text-align:left;flex-shrink:1;font-weight:var(--font-weight-normal);white-space:normal}.info-card-empty-state{padding:var(--size-3);margin:var(--size-1)}.info-card-actions{padding:var(--size-1-5) var(--size-2);flex-direction:column;gap:var(--spacing-md)}.info-card-actions>*{width:100%}}\n"] }]
1169
1194
  }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], cssClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClass", required: false }] }], customStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "customStyle", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }], copyable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyable", required: false }] }], copyButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyButtonLabel", required: false }] }], copyFeedbackText: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyFeedbackText", required: false }] }], copied: [{ type: i0.Output, args: ["copied"] }], copyFailed: [{ type: i0.Output, args: ["copyFailed"] }], autoFullWidthOnMobile: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFullWidthOnMobile", required: false }] }] } });
1170
1195
 
1171
1196
  const CHECKBOX_CONFIG = new InjectionToken('CHECKBOX_CONFIG', {
@@ -3480,16 +3505,31 @@ class PshPaginationComponent {
3480
3505
  this.itemsPerPageInput = input(10, { ...(ngDevMode ? { debugName: "itemsPerPageInput" } : /* istanbul ignore next */ {}), alias: 'itemsPerPage' });
3481
3506
  this.itemsPerPage = linkedSignal(this.itemsPerPageInput, /* @ts-ignore */
3482
3507
  ...(ngDevMode ? [{ debugName: "itemsPerPage" }] : /* istanbul ignore next */ []));
3508
+ /**
3509
+ * Number of pages used for rendering and navigation: never below 1, and never
3510
+ * written back to the `totalPages` input. `totalPages` is derived data owned by
3511
+ * the caller (`Math.ceil(total / pageSize)`, or a server-side page count), so the
3512
+ * component reads it and clamps its own view of it instead of correcting it.
3513
+ */
3514
+ this.effectiveTotalPages = computed(() => {
3515
+ const total = this.totalPages();
3516
+ return Number.isInteger(total) && total >= 1 ? total : 1;
3517
+ }, /* @ts-ignore */
3518
+ ...(ngDevMode ? [{ debugName: "effectiveTotalPages" }] : /* istanbul ignore next */ []));
3483
3519
  this.size = input(this.config.size ?? 'medium', { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: (value) => {
3484
3520
  if (!['small', 'medium', 'large'].includes(value)) {
3485
- console.warn(`[psh-pagination] Invalid size "${value}", falling back to "medium"`);
3521
+ if (isDevMode()) {
3522
+ console.warn(`[psh-pagination] Invalid size "${value}", falling back to "medium"`);
3523
+ }
3486
3524
  return 'medium';
3487
3525
  }
3488
3526
  return value;
3489
3527
  } });
3490
3528
  this.variant = input(this.config.variant ?? 'default', { ...(ngDevMode ? { debugName: "variant" } : /* istanbul ignore next */ {}), transform: (value) => {
3491
3529
  if (!['default', 'outline'].includes(value)) {
3492
- console.warn(`[psh-pagination] Invalid variant "${value}", falling back to "default"`);
3530
+ if (isDevMode()) {
3531
+ console.warn(`[psh-pagination] Invalid variant "${value}", falling back to "default"`);
3532
+ }
3493
3533
  return 'default';
3494
3534
  }
3495
3535
  return value;
@@ -3534,7 +3574,7 @@ class PshPaginationComponent {
3534
3574
  const pages = [];
3535
3575
  const halfVisible = Math.floor(this.maxVisiblePages() / 2);
3536
3576
  let start = Math.max(1, this.currentPage() - halfVisible);
3537
- const end = Math.min(this.totalPages(), start + this.maxVisiblePages() - 1);
3577
+ const end = Math.min(this.effectiveTotalPages(), start + this.maxVisiblePages() - 1);
3538
3578
  if (end - start + 1 < this.maxVisiblePages()) {
3539
3579
  start = Math.max(1, end - this.maxVisiblePages() + 1);
3540
3580
  }
@@ -3546,60 +3586,70 @@ class PshPaginationComponent {
3546
3586
  ...(ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []));
3547
3587
  this.state = computed(() => this.getState(), /* @ts-ignore */
3548
3588
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
3549
- this.canGoNext = computed(() => this.currentPage() < this.totalPages(), /* @ts-ignore */
3589
+ this.canGoNext = computed(() => this.currentPage() < this.effectiveTotalPages(), /* @ts-ignore */
3550
3590
  ...(ngDevMode ? [{ debugName: "canGoNext" }] : /* istanbul ignore next */ []));
3551
3591
  this.canGoPrevious = computed(() => this.currentPage() > 1, /* @ts-ignore */
3552
3592
  ...(ngDevMode ? [{ debugName: "canGoPrevious" }] : /* istanbul ignore next */ []));
3553
3593
  this.isFirstPage = computed(() => this.currentPage() === 1, /* @ts-ignore */
3554
3594
  ...(ngDevMode ? [{ debugName: "isFirstPage" }] : /* istanbul ignore next */ []));
3555
- this.isLastPage = computed(() => this.currentPage() === this.totalPages(), /* @ts-ignore */
3595
+ this.isLastPage = computed(() => this.currentPage() === this.effectiveTotalPages(), /* @ts-ignore */
3556
3596
  ...(ngDevMode ? [{ debugName: "isLastPage" }] : /* istanbul ignore next */ []));
3557
3597
  this.currentPageAnnouncement = computed(() => {
3558
- return `Page ${this.currentPage()} sur ${this.totalPages()}`;
3598
+ return `Page ${this.currentPage()} sur ${this.effectiveTotalPages()}`;
3559
3599
  }, /* @ts-ignore */
3560
3600
  ...(ngDevMode ? [{ debugName: "currentPageAnnouncement" }] : /* istanbul ignore next */ []));
3561
- effect(() => {
3562
- const total = this.totalPages();
3563
- if (total < 1 || !Number.isFinite(total)) {
3564
- console.warn(`[psh-pagination] Invalid totalPages "${total}", setting to 1`);
3565
- this.totalPages.set(1);
3566
- }
3567
- });
3601
+ if (isDevMode()) {
3602
+ effect(() => {
3603
+ // `0` is a legitimate state (empty list, or a server-side page count that is
3604
+ // not loaded yet): stay silent. Only report what betrays a caller bug.
3605
+ const total = this.totalPages();
3606
+ if (total !== 0 && (!Number.isInteger(total) || total < 1)) {
3607
+ console.warn(`[psh-pagination] Invalid totalPages "${total}", using 1 page`);
3608
+ }
3609
+ });
3610
+ effect(() => {
3611
+ const maxVisible = this.maxVisiblePages();
3612
+ if (maxVisible < 1 || !Number.isFinite(maxVisible)) {
3613
+ console.warn(`[psh-pagination] Invalid maxVisiblePages "${maxVisible}", must be >= 1`);
3614
+ }
3615
+ });
3616
+ }
3617
+ // `currentPage` IS owned by the component (it navigates), so correcting it with
3618
+ // a write-back is the intended behaviour — clamped on the derived page count.
3568
3619
  effect(() => {
3569
3620
  const current = this.currentPage();
3570
- const total = this.totalPages();
3621
+ const total = this.effectiveTotalPages();
3571
3622
  if (current < 1 || !Number.isFinite(current)) {
3572
- console.warn(`[psh-pagination] Invalid currentPage "${current}", setting to 1`);
3623
+ if (isDevMode()) {
3624
+ console.warn(`[psh-pagination] Invalid currentPage "${current}", setting to 1`);
3625
+ }
3573
3626
  this.currentPage.set(1);
3574
3627
  }
3575
- else if (current > total && total >= 1) {
3576
- console.warn(`[psh-pagination] currentPage "${current}" exceeds totalPages "${total}", setting to ${total}`);
3628
+ else if (current > total) {
3629
+ if (isDevMode()) {
3630
+ console.warn(`[psh-pagination] currentPage "${current}" exceeds totalPages "${total}", setting to ${total}`);
3631
+ }
3577
3632
  this.currentPage.set(total);
3578
3633
  }
3579
3634
  });
3580
- effect(() => {
3581
- const maxVisible = this.maxVisiblePages();
3582
- if (maxVisible < 1 || !Number.isFinite(maxVisible)) {
3583
- console.warn(`[psh-pagination] Invalid maxVisiblePages "${maxVisible}", must be >= 1`);
3584
- }
3585
- });
3586
3635
  }
3587
3636
  getState() {
3588
3637
  if (this.currentPage() === 1)
3589
3638
  return 'first';
3590
- if (this.currentPage() === this.totalPages())
3639
+ if (this.currentPage() === this.effectiveTotalPages())
3591
3640
  return 'last';
3592
3641
  return 'default';
3593
3642
  }
3594
3643
  goToPage(page) {
3595
- if (page !== this.currentPage() && page >= 1 && page <= this.totalPages()) {
3644
+ const total = this.effectiveTotalPages();
3645
+ if (page !== this.currentPage() && page >= 1 && page <= total) {
3596
3646
  this.currentPage.set(page);
3597
3647
  this.pageChange.emit(page);
3598
3648
  }
3599
- else if (page < 1 || page > this.totalPages()) {
3649
+ else if (page < 1 || page > total) {
3600
3650
  this.navigationError.emit({
3601
3651
  action: 'goToPage',
3602
- reason: `Page ${page} is out of bounds (1-${this.totalPages()})`,
3652
+ reason: `Page ${page} is out of bounds (1-${total})`,
3603
3653
  });
3604
3654
  }
3605
3655
  }
@@ -3607,7 +3657,7 @@ class PshPaginationComponent {
3607
3657
  this.goToPage(1);
3608
3658
  }
3609
3659
  goToLastPage() {
3610
- this.goToPage(this.totalPages());
3660
+ this.goToPage(this.effectiveTotalPages());
3611
3661
  }
3612
3662
  goToNextPage() {
3613
3663
  if (this.canGoNext()) {