@ktortu/aaa 0.9.1 → 0.9.2

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.
Files changed (39) hide show
  1. package/fesm2022/ktortu-aaa-button.mjs +10 -2
  2. package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
  3. package/fesm2022/ktortu-aaa-card.mjs +14 -5
  4. package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
  5. package/fesm2022/ktortu-aaa-cdk.mjs +49 -2
  6. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
  7. package/fesm2022/ktortu-aaa-dialog.mjs +19 -31
  8. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
  9. package/fesm2022/ktortu-aaa-disclosure.mjs +201 -0
  10. package/fesm2022/ktortu-aaa-disclosure.mjs.map +1 -0
  11. package/fesm2022/ktortu-aaa-forms.mjs +210 -93
  12. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
  13. package/fesm2022/ktortu-aaa-progress-bar.mjs +91 -0
  14. package/fesm2022/ktortu-aaa-progress-bar.mjs.map +1 -0
  15. package/fesm2022/ktortu-aaa-snackbar.mjs +16 -3
  16. package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -1
  17. package/fesm2022/ktortu-aaa-tooltip.mjs +20 -30
  18. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
  19. package/fesm2022/ktortu-aaa.mjs +1 -0
  20. package/fesm2022/ktortu-aaa.mjs.map +1 -1
  21. package/forms/field/field.css +16 -0
  22. package/forms/styles/field-box.css +11 -5
  23. package/package.json +9 -1
  24. package/progress-bar/progress-bar.css +127 -0
  25. package/styles/disclosure.css +6 -0
  26. package/styles/progress-bar.css +5 -0
  27. package/styles/styles.css +2 -0
  28. package/themes/theme-cyberpunk.css +11 -0
  29. package/tooltip/tooltip.css +1 -1
  30. package/types/ktortu-aaa-button.d.ts +2 -0
  31. package/types/ktortu-aaa-card.d.ts +3 -0
  32. package/types/ktortu-aaa-cdk.d.ts +27 -1
  33. package/types/ktortu-aaa-dialog.d.ts +7 -14
  34. package/types/ktortu-aaa-disclosure.d.ts +112 -0
  35. package/types/ktortu-aaa-forms.d.ts +66 -14
  36. package/types/ktortu-aaa-progress-bar.d.ts +27 -0
  37. package/types/ktortu-aaa-snackbar.d.ts +11 -68
  38. package/types/ktortu-aaa-tooltip.d.ts +0 -1
  39. package/types/ktortu-aaa.d.ts +1 -0
@@ -1,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, ElementRef, PLATFORM_ID, signal, input, booleanAttribute, computed, effect, isDevMode, Directive } from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, PLATFORM_ID, signal, input, booleanAttribute, computed, DestroyRef, effect, Directive } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
+ import { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';
4
5
 
5
6
  const KT_BUTTON_CONFIG = new InjectionToken('KT_BUTTON_CONFIG');
6
7
  /**
@@ -30,8 +31,10 @@ class KtButton {
30
31
  config = inject(KT_BUTTON_CONFIG, { optional: true });
31
32
  host = inject(ElementRef).nativeElement;
32
33
  platformId = inject(PLATFORM_ID);
34
+ isDestroyed = false;
33
35
  viewInitialized = signal(false, /* @ts-ignore */
34
36
  ...(ngDevMode ? [{ debugName: "viewInitialized" }] : /* istanbul ignore next */ []));
37
+ auditEnabled = inject(KT_AUDIT_ENABLED);
35
38
  // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.
36
39
  isLink = this.host.tagName === 'A';
37
40
  // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).
@@ -74,12 +77,17 @@ class KtButton {
74
77
  resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null, /* @ts-ignore */
75
78
  ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
76
79
  constructor() {
80
+ inject(DestroyRef).onDestroy(() => {
81
+ this.isDestroyed = true;
82
+ });
77
83
  effect(() => {
84
+ if (this.isDestroyed)
85
+ return;
78
86
  if (!isPlatformBrowser(this.platformId))
79
87
  return;
80
88
  if (!this.viewInitialized())
81
89
  return;
82
- if (!isDevMode() || !this.iconOnly())
90
+ if (!this.auditEnabled || !this.iconOnly())
83
91
  return;
84
92
  if (!this.icon()) {
85
93
  console.warn('[ktButton] iconOnly attend une icône via [icon].');
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-button.mjs","sources":["../../../../projects/ktortu/aaa/button/button.ts","../../../../projects/ktortu/aaa/button/ktortu-aaa-button.ts"],"sourcesContent":["import {\n Directive,\n ElementRef,\n InjectionToken,\n Provider,\n booleanAttribute,\n computed,\n inject,\n input,\n isDevMode,\n AfterViewInit,\n PLATFORM_ID,\n effect,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\n/** Axe \"emphase\" (apparence). */\nexport type KtButtonMode = 'filled' | 'tonal' | 'outlined' | 'text';\n/** Axe \"intention\" (couleur sémantique). */\nexport type KtButtonColor = 'primary' | 'neutral' | 'danger';\n/** Taille (la cible md ~44-48px vise le confort tactile / AAA). */\nexport type KtButtonSize = 'sm' | 'md' | 'lg';\n\n/** Défauts applicables à tous les `[ktButton]` (surchargeables par bouton via les inputs). */\nexport interface KtButtonConfig {\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). */\n disabledInteractive: boolean;\n /** Emphase visuelle par défaut (apparence). */\n mode: KtButtonMode;\n /** Intention sémantique par défaut (couleur). */\n color: KtButtonColor;\n /** Taille par défaut. */\n size: KtButtonSize;\n}\n\nexport const KT_BUTTON_CONFIG = new InjectionToken<Partial<KtButtonConfig>>('KT_BUTTON_CONFIG');\n\n/**\n * Fournit des défauts de bouton (mode/couleur/taille…) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtButton({ mode: 'tonal', size: 'lg' })]\n * ```\n */\nexport function provideKtButton(config: Partial<KtButtonConfig>): Provider {\n return { provide: KT_BUTTON_CONFIG, useValue: config };\n}\n\n/**\n * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`\n * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement\n * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).\n *\n * @example\n * ```html\n * <button ktButton mode=\"filled\" color=\"primary\">Enregistrer</button>\n * <a ktButton mode=\"text\" href=\"/aide\">Aide</a>\n * <button ktButton iconOnly icon=\"close\" ariaLabel=\"Fermer\"></button>\n * ```\n */\n@Directive({\n selector: 'button[ktButton], a[ktButton]',\n host: {\n '[attr.data-mode]': 'mode()',\n '[attr.data-color]': 'color()',\n '[attr.data-size]': 'size()',\n '[attr.data-full-width]': 'fullWidth() ? \"\" : null',\n '[attr.data-icon-only]': 'iconOnly() ? \"\" : null',\n '[attr.type]': '!isLink ? type() : null',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[class.loading]': 'loading()',\n '[class.disabled]': 'isDisabled()',\n '[attr.data-icon]': 'icon()',\n '[attr.data-icon-position]': 'iconPosition()',\n '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? \"\" : null',\n '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null',\n '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null',\n '[attr.aria-busy]': 'loading() ? \"true\" : null',\n '(click)': 'haltDisabledEvents($event)',\n },\n})\nexport class KtButton implements AfterViewInit {\n private readonly config = inject(KT_BUTTON_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private readonly viewInitialized = signal(false);\n\n // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.\n protected readonly isLink = this.host.tagName === 'A';\n // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).\n private readonly nativeAriaLabel = this.host.getAttribute('aria-label');\n\n /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */\n readonly mode = input<KtButtonMode>(this.config?.mode ?? 'filled');\n\n /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */\n readonly color = input<KtButtonColor>(this.config?.color ?? 'primary');\n\n /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */\n readonly size = input<KtButtonSize>(this.config?.size ?? 'md');\n\n /** Étire le bouton sur toute la largeur disponible. @default false */\n readonly fullWidth = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */\n readonly iconOnly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom accessible. **Obligatoire si `iconOnly`** ; à défaut, l'`aria-label` natif est préservé. @default undefined */\n readonly ariaLabel = input<string>();\n\n /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */\n readonly type = input<'button' | 'submit' | 'reset'>('button');\n\n /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */\n readonly loading = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom de l'icône (rendu via CSS `data-icon`). @default undefined */\n readonly icon = input<string>();\n\n /** Position de l'icône relative au texte. @default 'start' */\n readonly iconPosition = input<'start' | 'end'>('start');\n\n /** Désactive le bouton. @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */\n readonly disabledInteractive = input<boolean, unknown>(this.config?.disabledInteractive ?? false, {\n transform: booleanAttribute,\n });\n\n // loading implique un bouton inerte\n protected readonly isDisabled = computed(() => this.disabled() || this.loading());\n\n // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.\n protected readonly resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null);\n\n constructor() {\n effect(() => {\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.viewInitialized()) return;\n if (!isDevMode() || !this.iconOnly()) return;\n\n if (!this.icon()) {\n console.warn('[ktButton] iconOnly attend une icône via [icon].');\n }\n if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {\n console.warn(\n '[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +\n 'le bouton serait annoncé sans libellé.',\n );\n }\n });\n }\n\n ngAfterViewInit(): void {\n this.viewInitialized.set(true);\n }\n\n protected haltDisabledEvents(event: Event): void {\n if (!this.isDisabled()) return;\n\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-button.mjs","sources":["../../../../projects/ktortu/aaa/button/button.ts","../../../../projects/ktortu/aaa/button/ktortu-aaa-button.ts"],"sourcesContent":["import {\n DestroyRef,\n Directive,\n ElementRef,\n InjectionToken,\n Provider,\n booleanAttribute,\n computed,\n inject,\n input,\n AfterViewInit,\n PLATFORM_ID,\n effect,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';\n\n/** Axe \"emphase\" (apparence). */\nexport type KtButtonMode = 'filled' | 'tonal' | 'outlined' | 'text';\n/** Axe \"intention\" (couleur sémantique). */\nexport type KtButtonColor = 'primary' | 'neutral' | 'danger';\n/** Taille (la cible md ~44-48px vise le confort tactile / AAA). */\nexport type KtButtonSize = 'sm' | 'md' | 'lg';\n\n/** Défauts applicables à tous les `[ktButton]` (surchargeables par bouton via les inputs). */\nexport interface KtButtonConfig {\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). */\n disabledInteractive: boolean;\n /** Emphase visuelle par défaut (apparence). */\n mode: KtButtonMode;\n /** Intention sémantique par défaut (couleur). */\n color: KtButtonColor;\n /** Taille par défaut. */\n size: KtButtonSize;\n}\n\nexport const KT_BUTTON_CONFIG = new InjectionToken<Partial<KtButtonConfig>>('KT_BUTTON_CONFIG');\n\n/**\n * Fournit des défauts de bouton (mode/couleur/taille…) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtButton({ mode: 'tonal', size: 'lg' })]\n * ```\n */\nexport function provideKtButton(config: Partial<KtButtonConfig>): Provider {\n return { provide: KT_BUTTON_CONFIG, useValue: config };\n}\n\n/**\n * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`\n * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement\n * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).\n *\n * @example\n * ```html\n * <button ktButton mode=\"filled\" color=\"primary\">Enregistrer</button>\n * <a ktButton mode=\"text\" href=\"/aide\">Aide</a>\n * <button ktButton iconOnly icon=\"close\" ariaLabel=\"Fermer\"></button>\n * ```\n */\n@Directive({\n selector: 'button[ktButton], a[ktButton]',\n host: {\n '[attr.data-mode]': 'mode()',\n '[attr.data-color]': 'color()',\n '[attr.data-size]': 'size()',\n '[attr.data-full-width]': 'fullWidth() ? \"\" : null',\n '[attr.data-icon-only]': 'iconOnly() ? \"\" : null',\n '[attr.type]': '!isLink ? type() : null',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[class.loading]': 'loading()',\n '[class.disabled]': 'isDisabled()',\n '[attr.data-icon]': 'icon()',\n '[attr.data-icon-position]': 'iconPosition()',\n '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? \"\" : null',\n '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null',\n '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null',\n '[attr.aria-busy]': 'loading() ? \"true\" : null',\n '(click)': 'haltDisabledEvents($event)',\n },\n})\nexport class KtButton implements AfterViewInit {\n private readonly config = inject(KT_BUTTON_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private isDestroyed = false;\n private readonly viewInitialized = signal(false);\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n\n // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.\n protected readonly isLink = this.host.tagName === 'A';\n // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).\n private readonly nativeAriaLabel = this.host.getAttribute('aria-label');\n\n /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */\n readonly mode = input<KtButtonMode>(this.config?.mode ?? 'filled');\n\n /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */\n readonly color = input<KtButtonColor>(this.config?.color ?? 'primary');\n\n /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */\n readonly size = input<KtButtonSize>(this.config?.size ?? 'md');\n\n /** Étire le bouton sur toute la largeur disponible. @default false */\n readonly fullWidth = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */\n readonly iconOnly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom accessible. **Obligatoire si `iconOnly`** ; à défaut, l'`aria-label` natif est préservé. @default undefined */\n readonly ariaLabel = input<string>();\n\n /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */\n readonly type = input<'button' | 'submit' | 'reset'>('button');\n\n /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */\n readonly loading = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom de l'icône (rendu via CSS `data-icon`). @default undefined */\n readonly icon = input<string>();\n\n /** Position de l'icône relative au texte. @default 'start' */\n readonly iconPosition = input<'start' | 'end'>('start');\n\n /** Désactive le bouton. @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */\n readonly disabledInteractive = input<boolean, unknown>(this.config?.disabledInteractive ?? false, {\n transform: booleanAttribute,\n });\n\n // loading implique un bouton inerte\n protected readonly isDisabled = computed(() => this.disabled() || this.loading());\n\n // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.\n protected readonly resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null);\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n this.isDestroyed = true;\n });\n\n effect(() => {\n if (this.isDestroyed) return;\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.viewInitialized()) return;\n if (!this.auditEnabled || !this.iconOnly()) return;\n\n if (!this.icon()) {\n console.warn('[ktButton] iconOnly attend une icône via [icon].');\n }\n if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {\n console.warn(\n '[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +\n 'le bouton serait annoncé sans libellé.',\n );\n }\n });\n }\n\n ngAfterViewInit(): void {\n this.viewInitialized.set(true);\n }\n\n protected haltDisabledEvents(event: Event): void {\n if (!this.isDisabled()) return;\n\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, inject, ElementRef, afterNextRender, isDevMode, InjectionToken, PLATFORM_ID, contentChild, input, booleanAttribute, effect } from '@angular/core';
2
+ import { Directive, inject, ElementRef, afterNextRender, InjectionToken, PLATFORM_ID, contentChild, input, booleanAttribute, DestroyRef, effect } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
+ import { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';
4
5
 
5
6
  /**
6
7
  * En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur
@@ -97,6 +98,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
97
98
  */
98
99
  class KtCardLink {
99
100
  host = inject(ElementRef).nativeElement;
101
+ auditEnabled = inject(KT_AUDIT_ENABLED);
100
102
  /**
101
103
  * Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par
102
104
  * hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer
@@ -110,9 +112,9 @@ class KtCardLink {
110
112
  }
111
113
  }
112
114
  constructor() {
113
- // Garde-fou a11y (dev seulement) : un lien étiré sans nom accessible n'est pas annonçable.
115
+ // Garde-fou a11y : un lien étiré sans nom accessible n'est pas annonçable.
114
116
  afterNextRender(() => {
115
- if (!isDevMode())
117
+ if (!this.auditEnabled)
116
118
  return;
117
119
  const hasName = !!this.host.textContent?.trim() ||
118
120
  !!this.host.getAttribute('aria-label')?.trim() ||
@@ -178,6 +180,7 @@ class KtCard {
178
180
  config = inject(KT_CARD_CONFIG, { optional: true });
179
181
  host = inject(ElementRef).nativeElement;
180
182
  platformId = inject(PLATFORM_ID);
183
+ auditEnabled = inject(KT_AUDIT_ENABLED);
181
184
  /** Référence réactive sur le lien primaire de la carte */
182
185
  cardLink = contentChild(KtCardLink, { ...(ngDevMode ? { debugName: "cardLink" } : /* istanbul ignore next */ {}), descendants: true });
183
186
  /** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */
@@ -190,13 +193,19 @@ class KtCard {
190
193
  interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
191
194
  /** Rend la surface inerte (état `data-disabled`). @default false */
192
195
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
196
+ isDestroyed = false;
193
197
  constructor() {
194
- // Garde-fou a11y (dev seulement) : une carte interactive sans cible cliquable affiche une
198
+ inject(DestroyRef).onDestroy(() => {
199
+ this.isDestroyed = true;
200
+ });
201
+ // Garde-fou a11y : une carte interactive sans cible cliquable affiche une
195
202
  // affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).
196
203
  effect(() => {
204
+ if (this.isDestroyed)
205
+ return;
197
206
  if (!isPlatformBrowser(this.platformId))
198
207
  return;
199
- if (!isDevMode() || !this.interactive())
208
+ if (!this.auditEnabled || !this.interactive())
200
209
  return;
201
210
  const hasTarget = this.host.matches('a, button') || !!this.cardLink();
202
211
  if (!hasTarget) {
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-card.mjs","sources":["../../../../projects/ktortu/aaa/card/card-structure.ts","../../../../projects/ktortu/aaa/card/card.ts","../../../../projects/ktortu/aaa/card/public-api.ts","../../../../projects/ktortu/aaa/card/ktortu-aaa-card.ts"],"sourcesContent":["import { Directive, ElementRef, afterNextRender, inject, isDevMode } from '@angular/core';\n\nimport { KtCard } from './card';\n\n/**\n * En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur\n * structurel sans logique — la mise en forme vit dans `card.css` via `[ktCardHeader]`.\n * L'ÉTIQUETTE accessible reste le titre fourni par le consommateur (`<h3 id>` + `aria-labelledby`\n * sur l'hôte), comme [ktDialogTitle] pour le dialog : on sépare layout et sémantique.\n *\n * @example\n * ```html\n * <header ktCardHeader><h3 id=\"t1\">Titre</h3></header>\n * ```\n */\n@Directive({ selector: '[ktCardHeader]' })\nexport class KtCardHeader {}\n\n/**\n * Média pleine largeur (image/vidéo). Marqueur structurel sans logique : `card.css` lui donne le\n * full-bleed (marge négative = padding de la carte) et `[ktCard]` clippe au rayon quand un média\n * est présent. À envelopper autour d'un `<img ngSrc>` (NgOptimizedImage).\n *\n * @example\n * ```html\n * <div ktCardMedia><img ngSrc=\"cover.jpg\" width=\"400\" height=\"200\" alt=\"\" /></div>\n * ```\n */\n@Directive({ selector: '[ktCardMedia]' })\nexport class KtCardMedia {}\n\n/**\n * Corps de la carte. Marqueur structurel sans logique : la mise en forme (rythme vertical) vit\n * dans `card.css` via `[ktCardContent]`.\n *\n * @example\n * ```html\n * <div ktCardContent>Texte de la carte.</div>\n * ```\n */\n@Directive({ selector: '[ktCardContent]' })\nexport class KtCardContent {}\n\n/**\n * Barre d'actions de la carte (rangée de boutons/liens). Marqueur structurel sans logique :\n * la mise en forme (flex, gap, épinglée en pied) vit dans `card.css` via `[ktCardActions]`.\n *\n * @example\n * ```html\n * <footer ktCardActions><button ktButton>Action</button></footer>\n * ```\n */\n@Directive({ selector: '[ktCardActions]' })\nexport class KtCardActions {}\n\n/**\n * Lien (ou bouton) PRIMAIRE d'une carte interactive : pattern « lien étiré » (Inclusive\n * Components). Un pseudo-élément `::after` couvre toute la carte (cf. `card.css`) → toute la\n * surface est cliquable, SANS imbriquer de contrôles interactifs (anti-pattern WCAG 4.1.2). Les\n * actions secondaires de la carte repassent au-dessus du lien (z-index dans `card.css`).\n *\n * UN SEUL [ktCardLink] par carte. Le focus clavier est porté sur ce lien ; l'anneau de focus est\n * relayé sur toute la carte (`[ktCard]:has([ktCardLink]:focus-visible)`).\n *\n * Quand la carte ancêtre est `disabled`, le lien sort de l'ordre de tabulation (`tabindex=\"-1\"`)\n * et est annoncé `aria-disabled` : une carte inerte ne piège pas le focus clavier (WCAG 2.4.3).\n *\n * @example\n * ```html\n * <article ktCard interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: 'a[ktCardLink], button[ktCardLink]',\n host: {\n '[attr.aria-disabled]': 'card?.disabled() ? \"true\" : null',\n '[attr.tabindex]': 'card?.disabled() ? \"-1\" : null',\n '(click)': 'handleClick($event)',\n },\n})\nexport class KtCardLink {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n /**\n * Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par\n * hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer\n * l'état `disabled` de la carte (aria-disabled / tabindex) sans planter hors contexte.\n */\n protected readonly card = inject(KtCard, { optional: true });\n\n handleClick(event: Event): void {\n if (this.card?.disabled()) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n constructor() {\n // Garde-fou a11y (dev seulement) : un lien étiré sans nom accessible n'est pas annonçable.\n afterNextRender(() => {\n if (!isDevMode()) return;\n\n const hasName =\n !!this.host.textContent?.trim() ||\n !!this.host.getAttribute('aria-label')?.trim() ||\n this.host.hasAttribute('aria-labelledby');\n if (!hasName) {\n console.warn(\n '[ktCardLink] lien sans nom accessible : ajoutez du texte visible, [attr.aria-label] ' +\n 'ou aria-labelledby (WCAG 2.4.4 / 4.1.2).',\n );\n }\n });\n }\n}\n","import {\n Directive,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n Provider,\n booleanAttribute,\n contentChild,\n effect,\n inject,\n input,\n isDevMode,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KtCardLink } from './card-structure';\n\n/** Axe \"apparence\" de la surface. */\nexport type KtCardVariant = 'elevated' | 'outlined' | 'filled';\n\n/** Défauts applicables à toutes les `[ktCard]` (surchargeables par carte via les inputs). */\nexport interface KtCardConfig {\n /** Apparence par défaut de la surface. */\n variant: KtCardVariant;\n}\n\nexport const KT_CARD_CONFIG = new InjectionToken<Partial<KtCardConfig>>('KT_CARD_CONFIG');\n\n/**\n * Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtCard({ variant: 'outlined' })]\n * ```\n */\nexport function provideKtCard(config: Partial<KtCardConfig>): Provider {\n return { provide: KT_CARD_CONFIG, useValue: config };\n}\n\n/**\n * Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi\n * par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de\n * wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).\n *\n * Trois axes pilotés en `data-*` (même contrat que ktButton) :\n * - variant : data-variant = elevated | outlined | filled (apparence)\n * - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable\n * est un [ktCardLink], lien/bouton primaire au lien étiré)\n * - disabled : data-disabled (surface inerte)\n *\n * La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs\n * et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.\n *\n * @example\n * ```html\n * <article ktCard variant=\"outlined\" interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: '[ktCard]',\n host: {\n '[attr.data-variant]': 'variant()',\n '[attr.data-interactive]': 'interactive() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n },\n})\nexport class KtCard {\n private readonly config = inject(KT_CARD_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n\n /** Référence réactive sur le lien primaire de la carte */\n readonly cardLink = contentChild(KtCardLink, { descendants: true });\n\n /** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */\n readonly variant = input<KtCardVariant>(this.config?.variant ?? 'elevated');\n\n /**\n * Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton\n * primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false\n */\n readonly interactive = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Rend la surface inerte (état `data-disabled`). @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n constructor() {\n // Garde-fou a11y (dev seulement) : une carte interactive sans cible cliquable affiche une\n // affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).\n effect(() => {\n if (!isPlatformBrowser(this.platformId)) return;\n if (!isDevMode() || !this.interactive()) return;\n\n const hasTarget = this.host.matches('a, button') || !!this.cardLink();\n if (!hasTarget) {\n console.warn(\n '[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +\n 'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.',\n );\n }\n });\n }\n}\n","import { KtCard } from './card';\nimport { KtCardActions, KtCardContent, KtCardHeader, KtCardLink, KtCardMedia } from './card-structure';\n\nexport * from './card';\nexport * from './card-structure';\n\n/**\n * Import ergonomique de toute la famille card en une fois :\n * `imports: [KtCardImports]` au lieu d'énumérer chaque directive.\n */\nexport const KtCardImports = [KtCard, KtCardHeader, KtCardMedia, KtCardContent, KtCardActions, KtCardLink] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-card.mjs","sources":["../../../../projects/ktortu/aaa/card/card-structure.ts","../../../../projects/ktortu/aaa/card/card.ts","../../../../projects/ktortu/aaa/card/public-api.ts","../../../../projects/ktortu/aaa/card/ktortu-aaa-card.ts"],"sourcesContent":["import { Directive, ElementRef, afterNextRender, inject } from '@angular/core';\nimport { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';\n\nimport { KtCard } from './card';\n\n/**\n * En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur\n * structurel sans logique — la mise en forme vit dans `card.css` via `[ktCardHeader]`.\n * L'ÉTIQUETTE accessible reste le titre fourni par le consommateur (`<h3 id>` + `aria-labelledby`\n * sur l'hôte), comme [ktDialogTitle] pour le dialog : on sépare layout et sémantique.\n *\n * @example\n * ```html\n * <header ktCardHeader><h3 id=\"t1\">Titre</h3></header>\n * ```\n */\n@Directive({ selector: '[ktCardHeader]' })\nexport class KtCardHeader {}\n\n/**\n * Média pleine largeur (image/vidéo). Marqueur structurel sans logique : `card.css` lui donne le\n * full-bleed (marge négative = padding de la carte) et `[ktCard]` clippe au rayon quand un média\n * est présent. À envelopper autour d'un `<img ngSrc>` (NgOptimizedImage).\n *\n * @example\n * ```html\n * <div ktCardMedia><img ngSrc=\"cover.jpg\" width=\"400\" height=\"200\" alt=\"\" /></div>\n * ```\n */\n@Directive({ selector: '[ktCardMedia]' })\nexport class KtCardMedia {}\n\n/**\n * Corps de la carte. Marqueur structurel sans logique : la mise en forme (rythme vertical) vit\n * dans `card.css` via `[ktCardContent]`.\n *\n * @example\n * ```html\n * <div ktCardContent>Texte de la carte.</div>\n * ```\n */\n@Directive({ selector: '[ktCardContent]' })\nexport class KtCardContent {}\n\n/**\n * Barre d'actions de la carte (rangée de boutons/liens). Marqueur structurel sans logique :\n * la mise en forme (flex, gap, épinglée en pied) vit dans `card.css` via `[ktCardActions]`.\n *\n * @example\n * ```html\n * <footer ktCardActions><button ktButton>Action</button></footer>\n * ```\n */\n@Directive({ selector: '[ktCardActions]' })\nexport class KtCardActions {}\n\n/**\n * Lien (ou bouton) PRIMAIRE d'une carte interactive : pattern « lien étiré » (Inclusive\n * Components). Un pseudo-élément `::after` couvre toute la carte (cf. `card.css`) → toute la\n * surface est cliquable, SANS imbriquer de contrôles interactifs (anti-pattern WCAG 4.1.2). Les\n * actions secondaires de la carte repassent au-dessus du lien (z-index dans `card.css`).\n *\n * UN SEUL [ktCardLink] par carte. Le focus clavier est porté sur ce lien ; l'anneau de focus est\n * relayé sur toute la carte (`[ktCard]:has([ktCardLink]:focus-visible)`).\n *\n * Quand la carte ancêtre est `disabled`, le lien sort de l'ordre de tabulation (`tabindex=\"-1\"`)\n * et est annoncé `aria-disabled` : une carte inerte ne piège pas le focus clavier (WCAG 2.4.3).\n *\n * @example\n * ```html\n * <article ktCard interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: 'a[ktCardLink], button[ktCardLink]',\n host: {\n '[attr.aria-disabled]': 'card?.disabled() ? \"true\" : null',\n '[attr.tabindex]': 'card?.disabled() ? \"-1\" : null',\n '(click)': 'handleClick($event)',\n },\n})\nexport class KtCardLink {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n /**\n * Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par\n * hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer\n * l'état `disabled` de la carte (aria-disabled / tabindex) sans planter hors contexte.\n */\n protected readonly card = inject(KtCard, { optional: true });\n\n handleClick(event: Event): void {\n if (this.card?.disabled()) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n constructor() {\n // Garde-fou a11y : un lien étiré sans nom accessible n'est pas annonçable.\n afterNextRender(() => {\n if (!this.auditEnabled) return;\n\n const hasName =\n !!this.host.textContent?.trim() ||\n !!this.host.getAttribute('aria-label')?.trim() ||\n this.host.hasAttribute('aria-labelledby');\n if (!hasName) {\n console.warn(\n '[ktCardLink] lien sans nom accessible : ajoutez du texte visible, [attr.aria-label] ' +\n 'ou aria-labelledby (WCAG 2.4.4 / 4.1.2).',\n );\n }\n });\n }\n}\n","import {\n DestroyRef,\n Directive,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n Provider,\n booleanAttribute,\n contentChild,\n effect,\n inject,\n input,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';\nimport { KtCardLink } from './card-structure';\n\n/** Axe \"apparence\" de la surface. */\nexport type KtCardVariant = 'elevated' | 'outlined' | 'filled';\n\n/** Défauts applicables à toutes les `[ktCard]` (surchargeables par carte via les inputs). */\nexport interface KtCardConfig {\n /** Apparence par défaut de la surface. */\n variant: KtCardVariant;\n}\n\nexport const KT_CARD_CONFIG = new InjectionToken<Partial<KtCardConfig>>('KT_CARD_CONFIG');\n\n/**\n * Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtCard({ variant: 'outlined' })]\n * ```\n */\nexport function provideKtCard(config: Partial<KtCardConfig>): Provider {\n return { provide: KT_CARD_CONFIG, useValue: config };\n}\n\n/**\n * Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi\n * par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de\n * wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).\n *\n * Trois axes pilotés en `data-*` (même contrat que ktButton) :\n * - variant : data-variant = elevated | outlined | filled (apparence)\n * - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable\n * est un [ktCardLink], lien/bouton primaire au lien étiré)\n * - disabled : data-disabled (surface inerte)\n *\n * La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs\n * et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.\n *\n * @example\n * ```html\n * <article ktCard variant=\"outlined\" interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: '[ktCard]',\n host: {\n '[attr.data-variant]': 'variant()',\n '[attr.data-interactive]': 'interactive() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n },\n})\nexport class KtCard {\n private readonly config = inject(KT_CARD_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n\n /** Référence réactive sur le lien primaire de la carte */\n readonly cardLink = contentChild(KtCardLink, { descendants: true });\n\n /** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */\n readonly variant = input<KtCardVariant>(this.config?.variant ?? 'elevated');\n\n /**\n * Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton\n * primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false\n */\n readonly interactive = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Rend la surface inerte (état `data-disabled`). @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n private isDestroyed = false;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n this.isDestroyed = true;\n });\n\n // Garde-fou a11y : une carte interactive sans cible cliquable affiche une\n // affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).\n effect(() => {\n if (this.isDestroyed) return;\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.auditEnabled || !this.interactive()) return;\n\n const hasTarget = this.host.matches('a, button') || !!this.cardLink();\n if (!hasTarget) {\n console.warn(\n '[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +\n 'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.',\n );\n }\n });\n }\n}\n","import { KtCard } from './card';\nimport { KtCardActions, KtCardContent, KtCardHeader, KtCardLink, KtCardMedia } from './card-structure';\n\nexport * from './card';\nexport * from './card-structure';\n\n/**\n * Import ergonomique de toute la famille card en une fois :\n * `imports: [KtCardImports]` au lieu d'énumérer chaque directive.\n */\nexport const KtCardImports = [KtCard, KtCardHeader, KtCardMedia, KtCardContent, KtCardActions, KtCardLink] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
+ import { DOCUMENT } from '@angular/common';
1
2
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable } from '@angular/core';
3
+ import { inject, Injectable, InjectionToken, isDevMode } from '@angular/core';
3
4
  import { toSignal } from '@angular/core/rxjs-interop';
4
5
  import { BreakpointObserver } from '@angular/cdk/layout';
5
6
  import { map } from 'rxjs';
@@ -97,6 +98,43 @@ function createKtSheetDrag(opts) {
97
98
  };
98
99
  }
99
100
 
101
+ /**
102
+ * Verrou de défilement du `<body>` à COMPTEUR DE RÉFÉRENCES, partagé (`providedIn: 'root'`).
103
+ *
104
+ * Plusieurs surfaces modales simultanées (sheets, popups) peuvent verrouiller le scroll du fond :
105
+ * `overflow: hidden` n'est posé qu'au premier `lock()` et restauré (à sa valeur d'origine) qu'au
106
+ * dernier `unlock()`. Sans ce compteur, deux consommateurs par-instance se désynchronisent — la
107
+ * fermeture du premier restaure le scroll alors que le second est encore ouvert.
108
+ */
109
+ class KtBodyScrollLock {
110
+ doc = inject(DOCUMENT);
111
+ count = 0;
112
+ previousOverflow = '';
113
+ /** Pose un verrou (premier appelant : mémorise puis force `overflow: hidden`). */
114
+ lock() {
115
+ if (this.count === 0) {
116
+ this.previousOverflow = this.doc.body.style.overflow;
117
+ this.doc.body.style.overflow = 'hidden';
118
+ }
119
+ this.count++;
120
+ }
121
+ /** Relâche un verrou ; restaure l'`overflow` d'origine quand le dernier consommateur relâche. */
122
+ unlock() {
123
+ if (this.count === 0)
124
+ return;
125
+ this.count--;
126
+ if (this.count === 0) {
127
+ this.doc.body.style.overflow = this.previousOverflow;
128
+ }
129
+ }
130
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
131
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, providedIn: 'root' });
132
+ }
133
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, decorators: [{
134
+ type: Injectable,
135
+ args: [{ providedIn: 'root' }]
136
+ }] });
137
+
100
138
  /** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */
101
139
  const KT_DEFAULT_BREAKPOINTS = { mobile: 0, tablet: 600, desktop: 1024 };
102
140
  /** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */
@@ -219,9 +257,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
219
257
  }]
220
258
  }] });
221
259
 
260
+ /**
261
+ * InjectionToken permettant d'activer ou désactiver les audits d'accessibilité à l'exécution
262
+ * (gardes-fous console.warn en mode dev). Activé par défaut si `isDevMode()` est vrai.
263
+ */
264
+ const KT_AUDIT_ENABLED = new InjectionToken('KT_AUDIT_ENABLED', {
265
+ providedIn: 'root',
266
+ factory: () => isDevMode(),
267
+ });
268
+
222
269
  /**
223
270
  * Generated bundle index. Do not edit.
224
271
  */
225
272
 
226
- export { KT_BREAKPOINTS, KT_DEFAULT_BREAKPOINTS, KtIdGenerator, KtViewport, createKtSheetDrag, provideKtBreakpoints, resolveKtBreakpointMedia };
273
+ export { KT_AUDIT_ENABLED, KT_BREAKPOINTS, KT_DEFAULT_BREAKPOINTS, KtBodyScrollLock, KtIdGenerator, KtViewport, createKtSheetDrag, provideKtBreakpoints, resolveKtBreakpointMedia };
227
274
  //# sourceMappingURL=ktortu-aaa-cdk.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n /** Démarre le suivi du glissement ; à brancher sur le `pointerdown` de la poignée. */\n start(event: PointerEvent): void;\n /** Détache les écouteurs et réinitialise l'état ; à appeler à la destruction. */\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n *\n * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.\n * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).\n * @example\n * ```ts\n * const drag = createKtSheetDrag({\n * pane: () => paneEl(),\n * onDismiss: () => dialogRef.close(),\n * draggingClass: 'kt-sheet--dragging',\n * });\n * // <div class=\"handle\" (pointerdown)=\"drag.start($event)\"></div>\n * ```\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n let ownerDoc: Document | null = null;\n\n const cleanupListeners = (): void => {\n if (!ownerDoc) return;\n ownerDoc.removeEventListener('pointermove', onMove);\n ownerDoc.removeEventListener('pointerup', onEnd);\n ownerDoc.removeEventListener('pointercancel', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n\n const dragged = event.clientY - startY;\n const dismiss = dragged > pane.getBoundingClientRect().height * threshold;\n // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.\n const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n if (reduceMotion) {\n // draggingClass (transition coupée) encore présente : la translation finale est instantanée,\n // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).\n pane.style.translate = dismiss ? '0 100%' : '';\n pane.classList.remove(opts.draggingClass);\n } else {\n pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)\n pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0\n }\n if (dismiss) opts.onDismiss();\n\n pane = null;\n handle = null;\n ownerDoc = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n ownerDoc = handle.ownerDocument;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une\n // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.\n ownerDoc.addEventListener('pointermove', onMove);\n ownerDoc.addEventListener('pointerup', onEnd);\n ownerDoc.addEventListener('pointercancel', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n ownerDoc = null;\n },\n };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.\n * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n /** Media query de la bande mobile. */\n mobile: string;\n /** Media query de la bande tablette. */\n tablet: string;\n /** Media query de la bande desktop. */\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n * @param bp Seuils responsive à convertir (nombres et/ou chaînes).\n * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).\n * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,\n * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.\n *\n * @example\n * ```ts\n * const idGen = inject(KtIdGenerator);\n * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;\n * ```\n */\n@Injectable({\n // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n /**\n * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,\n * utilise un compteur dédié à ce préfixe (séquences indépendantes).\n * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.\n * @returns Le compteur courant (number) pour la portée demandée, avant incrément.\n */\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/sheet/body-scroll-lock.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/audit.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n /** Démarre le suivi du glissement ; à brancher sur le `pointerdown` de la poignée. */\n start(event: PointerEvent): void;\n /** Détache les écouteurs et réinitialise l'état ; à appeler à la destruction. */\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n *\n * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.\n * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).\n * @example\n * ```ts\n * const drag = createKtSheetDrag({\n * pane: () => paneEl(),\n * onDismiss: () => dialogRef.close(),\n * draggingClass: 'kt-sheet--dragging',\n * });\n * // <div class=\"handle\" (pointerdown)=\"drag.start($event)\"></div>\n * ```\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n let ownerDoc: Document | null = null;\n\n const cleanupListeners = (): void => {\n if (!ownerDoc) return;\n ownerDoc.removeEventListener('pointermove', onMove);\n ownerDoc.removeEventListener('pointerup', onEnd);\n ownerDoc.removeEventListener('pointercancel', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n\n const dragged = event.clientY - startY;\n const dismiss = dragged > pane.getBoundingClientRect().height * threshold;\n // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.\n const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n if (reduceMotion) {\n // draggingClass (transition coupée) encore présente : la translation finale est instantanée,\n // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).\n pane.style.translate = dismiss ? '0 100%' : '';\n pane.classList.remove(opts.draggingClass);\n } else {\n pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)\n pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0\n }\n if (dismiss) opts.onDismiss();\n\n pane = null;\n handle = null;\n ownerDoc = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n ownerDoc = handle.ownerDocument;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une\n // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.\n ownerDoc.addEventListener('pointermove', onMove);\n ownerDoc.addEventListener('pointerup', onEnd);\n ownerDoc.addEventListener('pointercancel', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n ownerDoc = null;\n },\n };\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, inject } from '@angular/core';\n\n/**\n * Verrou de défilement du `<body>` à COMPTEUR DE RÉFÉRENCES, partagé (`providedIn: 'root'`).\n *\n * Plusieurs surfaces modales simultanées (sheets, popups) peuvent verrouiller le scroll du fond :\n * `overflow: hidden` n'est posé qu'au premier `lock()` et restauré (à sa valeur d'origine) qu'au\n * dernier `unlock()`. Sans ce compteur, deux consommateurs par-instance se désynchronisent — la\n * fermeture du premier restaure le scroll alors que le second est encore ouvert.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtBodyScrollLock {\n private readonly doc = inject(DOCUMENT);\n private count = 0;\n private previousOverflow = '';\n\n /** Pose un verrou (premier appelant : mémorise puis force `overflow: hidden`). */\n lock(): void {\n if (this.count === 0) {\n this.previousOverflow = this.doc.body.style.overflow;\n this.doc.body.style.overflow = 'hidden';\n }\n this.count++;\n }\n\n /** Relâche un verrou ; restaure l'`overflow` d'origine quand le dernier consommateur relâche. */\n unlock(): void {\n if (this.count === 0) return;\n this.count--;\n if (this.count === 0) {\n this.doc.body.style.overflow = this.previousOverflow;\n }\n }\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.\n * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n /** Media query de la bande mobile. */\n mobile: string;\n /** Media query de la bande tablette. */\n tablet: string;\n /** Media query de la bande desktop. */\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n * @param bp Seuils responsive à convertir (nombres et/ou chaînes).\n * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).\n * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,\n * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.\n *\n * @example\n * ```ts\n * const idGen = inject(KtIdGenerator);\n * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;\n * ```\n */\n@Injectable({\n // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n /**\n * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,\n * utilise un compteur dédié à ce préfixe (séquences indépendantes).\n * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.\n * @returns Le compteur courant (number) pour la portée demandée, avant incrément.\n */\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","import { InjectionToken, isDevMode } from '@angular/core';\n\n/**\n * InjectionToken permettant d'activer ou désactiver les audits d'accessibilité à l'exécution\n * (gardes-fous console.warn en mode dev). Activé par défaut si `isDevMode()` est vrai.\n */\nexport const KT_AUDIT_ENABLED = new InjectionToken<boolean>('KT_AUDIT_ENABLED', {\n providedIn: 'root',\n factory: () => isDevMode(),\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAmBA;;;;;;;;;;;;;;;;;;;;AAoBG;AACG,SAAU,iBAAiB,CAAC,IAAwB,EAAA;AACxD,IAAA,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI;IACxC,IAAI,MAAM,GAAG,CAAC;IACd,IAAI,IAAI,GAAuB,IAAI;IACnC,IAAI,MAAM,GAAuB,IAAI;IACrC,IAAI,QAAQ,GAAoB,IAAI;IAEpC,MAAM,gBAAgB,GAAG,MAAW;AAClC,QAAA,IAAI,CAAC,QAAQ;YAAE;AACf,QAAA,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,MAAM,CAAC;AACnD,QAAA,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,KAAK,CAAC;AAChD,QAAA,QAAQ,CAAC,mBAAmB,CAAC,eAAe,EAAE,KAAK,CAAC;AACtD,IAAA,CAAC;AAED,IAAA,MAAM,MAAM,GAAG,CAAC,KAAmB,KAAU;AAC3C,QAAA,IAAI,CAAC,IAAI;YAAE;AACX,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAA,EAAA,EAAK,EAAE,IAAI;AACpC,IAAA,CAAC;AAED,IAAA,MAAM,KAAK,GAAG,CAAC,KAAmB,KAAU;AAC1C,QAAA,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE;AACtB,QAAA,gBAAgB,EAAE;AAElB,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,GAAG,MAAM;AACtC,QAAA,MAAM,OAAO,GAAG,OAAO,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC,MAAM,GAAG,SAAS;;AAEzE,QAAA,MAAM,YAAY,GAAG,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,GAAG,kCAAkC,CAAC,CAAC,OAAO;QAEtG,IAAI,YAAY,EAAE;;;AAGhB,YAAA,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,EAAE;YAC9C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;QAC3C;aAAO;YACL,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAC1C,YAAA,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,EAAE,CAAC;QACjD;AACA,QAAA,IAAI,OAAO;YAAE,IAAI,CAAC,SAAS,EAAE;QAE7B,IAAI,GAAG,IAAI;QACX,MAAM,GAAG,IAAI;QACb,QAAQ,GAAG,IAAI;AACjB,IAAA,CAAC;IAED,OAAO;AACL,QAAA,KAAK,CAAC,KAAmB,EAAA;AACvB,YAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE;AACxB,YAAA,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE;AACtB,YAAA,IAAI,CAAC,EAAE;gBAAE;AACT,YAAA,MAAM,GAAG,KAAK,CAAC,aAA4B;AAC3C,YAAA,QAAQ,GAAG,MAAM,CAAC,aAAa;;YAE/B,KAAK,CAAC,cAAc,EAAE;AACtB,YAAA,MAAM,GAAG,KAAK,CAAC,OAAO;YACtB,IAAI,GAAG,EAAE;YACT,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC;;;AAGtC,YAAA,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC;AAChD,YAAA,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,KAAK,CAAC;AAC7C,YAAA,QAAQ,CAAC,gBAAgB,CAAC,eAAe,EAAE,KAAK,CAAC;QACnD,CAAC;QACD,OAAO,GAAA;AACL,YAAA,gBAAgB,EAAE;YAClB,IAAI,GAAG,IAAI;YACX,MAAM,GAAG,IAAI;YACb,QAAQ,GAAG,IAAI;QACjB,CAAC;KACF;AACH;;AC3GA;;;;;;;AAOG;MAEU,gBAAgB,CAAA;AACV,IAAA,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC/B,KAAK,GAAG,CAAC;IACT,gBAAgB,GAAG,EAAE;;IAG7B,IAAI,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE;AACpB,YAAA,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ;YACpD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ;QACzC;QACA,IAAI,CAAC,KAAK,EAAE;IACd;;IAGA,MAAM,GAAA;AACJ,QAAA,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC;YAAE;QACtB,IAAI,CAAC,KAAK,EAAE;AACZ,QAAA,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE;AACpB,YAAA,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,gBAAgB;QACtD;IACF;uGArBW,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAhB,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,gBAAgB,cADH,MAAM,EAAA,CAAA;;2FACnB,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACclC;AACO,MAAM,sBAAsB,GAAkB,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI;AAE5F;MACa,cAAc,GAAG,IAAI,cAAc,CAAgB,gBAAgB,EAAE;AAChF,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,sBAAsB;AACtC,CAAA;AAED;;;;;;AAMG;AACG,SAAU,oBAAoB,CAAC,SAAiC,EAAA;AACpE,IAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,EAAE,GAAG,sBAAsB,EAAE,GAAG,SAAS,EAAE,EAAE;AAC3F;AAYA;AACA,MAAM,OAAO,GAAG,IAAI;AAEpB,SAAS,QAAQ,CAAC,CAAS,EAAA;IACzB,OAAO,CAAA,YAAA,EAAe,CAAC,CAAA,GAAA,CAAK;AAC9B;AAEA,SAAS,QAAQ,CAAC,SAAiB,EAAA;AACjC,IAAA,OAAO,CAAA,YAAA,EAAe,SAAS,GAAG,OAAO,KAAK;AAChD;AAEA;AACA,SAAS,IAAI,CAAC,GAAG,KAAe,EAAA;AAC9B,IAAA,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,SAAS;AACzD;AAEA;;;;;;;AAOG;AACG,SAAU,wBAAwB,CAAC,EAAiB,EAAA;IACxD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE;IAEtC,OAAO;AACL,QAAA,MAAM,EACJ,OAAO,MAAM,KAAK;AAChB,cAAE;AACF,cAAE,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,OAAO,MAAM,KAAK,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;AAClG,QAAA,MAAM,EACJ,OAAO,MAAM,KAAK;AAChB,cAAE;cACA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,OAAO,KAAK,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;AAClF,QAAA,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;KACnE;AACH;;ACvFA;;;;;;;;AAQG;MAEU,UAAU,CAAA;AACJ,IAAA,QAAQ,GAAG,MAAM,CAAC,kBAAkB,CAAC;;IAG7C,KAAK,GAAG,wBAAwB,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;;IAGxD,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;;IAE1C,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;;IAE1C,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;;AAE5C,IAAA,SAAS,GAAG,IAAI,CAAC,QAAQ;AAE1B,IAAA,OAAO,CAAC,KAAa,EAAA;QAC3B,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE;AAChF,YAAA,YAAY,EAAE,KAAK;AACpB,SAAA,CAAC;IACJ;uGAnBW,UAAU,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAV,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,UAAU,cADG,MAAM,EAAA,CAAA;;2FACnB,UAAU,EAAA,UAAA,EAAA,CAAA;kBADtB,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;ACblC;;;;;;;;;AASG;MAKU,aAAa,CAAA;AACP,IAAA,QAAQ,GAAG,IAAI,GAAG,EAAkB;IAC7C,YAAY,GAAG,CAAC;AAExB;;;;;AAKG;AACH,IAAA,UAAU,CAAC,MAAe,EAAA;QACxB,IAAI,CAAC,MAAM,EAAE;AACX,YAAA,OAAO,IAAI,CAAC,YAAY,EAAE;QAC5B;AACA,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC;AACtC,QAAA,OAAO,OAAO;IAChB;uGAjBW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAb,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,cAFZ,MAAM,EAAA,CAAA;;2FAEP,aAAa,EAAA,UAAA,EAAA,CAAA;kBAJzB,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;;AAEV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;;ACbD;;;AAGG;MACU,gBAAgB,GAAG,IAAI,cAAc,CAAU,kBAAkB,EAAE;AAC9E,IAAA,UAAU,EAAE,MAAM;AAClB,IAAA,OAAO,EAAE,MAAM,SAAS,EAAE;AAC3B,CAAA;;ACTD;;AAEG;;;;"}
@@ -1,8 +1,7 @@
1
- import { DialogRef, DEFAULT_DIALOG_CONFIG, CdkDialogContainer, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
1
+ import { DialogRef, DEFAULT_DIALOG_CONFIG, CdkDialogContainer, Dialog, DIALOG_DATA } from '@angular/cdk/dialog';
2
2
  import * as i0 from '@angular/core';
3
3
  import { inject, ElementRef, Renderer2, afterNextRender, Directive, input, isDevMode, DestroyRef, PLATFORM_ID, signal, ChangeDetectionStrategy, Component, ViewContainerRef } from '@angular/core';
4
4
  import { KtIdGenerator, createKtSheetDrag, KtViewport } from '@ktortu/aaa/cdk';
5
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
5
  import { isPlatformBrowser } from '@angular/common';
7
6
  import * as i1 from '@angular/cdk/portal';
8
7
  import { PortalModule } from '@angular/cdk/portal';
@@ -390,12 +389,22 @@ class KtDialogContainer extends CdkDialogContainer {
390
389
  elementRef = inject(ElementRef);
391
390
  host = this.elementRef.nativeElement;
392
391
  platformId = inject(PLATFORM_ID);
393
- destroyRef = inject(DestroyRef);
394
392
  isClosing = signal(false, /* @ts-ignore */
395
393
  ...(ngDevMode ? [{ debugName: "isClosing" }] : /* istanbul ignore next */ []));
394
+ /** Premier id de la file `aria-labelledby` exposée par `CdkDialogContainer`. Le membre
395
+ `_ariaLabelledByQueue` est interne au CDK (préfixe `_`) : on l'encapsule ICI, gardé, pour que
396
+ le binding de template ne lise jamais l'interne directement. Si une montée du CDK retire ou
397
+ renomme ce membre, on dégrade proprement vers `null` au lieu de casser le rendu du conteneur. */
398
+ get ariaLabelledBy() {
399
+ const queue = this._ariaLabelledByQueue;
400
+ return queue?.[0] ?? null;
401
+ }
396
402
  constructor() {
397
403
  super();
398
- // Monkey-patch de dialogRef.close pour intercepter toutes les demandes de fermeture
404
+ // CDK Dialog n'expose AUCUN hook « avant fermeture ». Pour jouer l'animation de SORTIE sur
405
+ // TOUTE fermeture, on intercepte la méthode publique `close` de la réf : c'est le point de
406
+ // passage unique. Échap et clic backdrop sont gérés NATIVEMENT par le CDK, qui appelle lui aussi
407
+ // `dialogRef.close()` → ils transitent donc par cette interception (inutile de les réimplémenter).
399
408
  const originalClose = this.dialogRef.close.bind(this.dialogRef);
400
409
  this.dialogRef.close = (result) => {
401
410
  this.animateAndClose(result, originalClose);
@@ -419,18 +428,6 @@ class KtDialogContainer extends CdkDialogContainer {
419
428
  }
420
429
  });
421
430
  }
422
- ngOnInit() {
423
- // Si la fermeture par clic extérieur/Échap est autorisée, on l'écoute pour lancer l'animation
424
- if (!this._config.disableClose) {
425
- this.dialogRef.backdropClick.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.dialogRef.close());
426
- this.dialogRef.keydownEvents.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
427
- if (event.key === 'Escape') {
428
- event.preventDefault();
429
- this.dialogRef.close();
430
- }
431
- });
432
- }
433
- }
434
431
  isSheet() {
435
432
  return this._config.panelClass?.includes('kt-dialog--sheet') ?? false;
436
433
  }
@@ -465,7 +462,7 @@ class KtDialogContainer extends CdkDialogContainer {
465
462
  }, durationMs + 50);
466
463
  }
467
464
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDialogContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
468
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.1", type: KtDialogContainer, isStandalone: true, selector: "kt-dialog-container", host: { attributes: { "tabindex": "-1" }, properties: { "attr.id": "_config.id || null", "attr.role": "_config.role", "attr.aria-modal": "_config.ariaModal", "attr.aria-labelledby": "_config.ariaLabel ? null : _ariaLabelledByQueue[0]", "attr.aria-label": "_config.ariaLabel", "attr.aria-describedby": "_config.ariaDescribedBy || null", "class.kt-dialog-container--sheet": "isSheet()", "class.kt-dialog-container--closing": "isClosing()" }, classAttribute: "cdk-dialog-container kt-dialog-container" }, usesInheritance: true, ngImport: i0, template: `
465
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.1", type: KtDialogContainer, isStandalone: true, selector: "kt-dialog-container", host: { attributes: { "tabindex": "-1" }, properties: { "attr.id": "_config.id || null", "attr.role": "_config.role", "attr.aria-modal": "_config.ariaModal", "attr.aria-labelledby": "_config.ariaLabel ? null : ariaLabelledBy", "attr.aria-label": "_config.ariaLabel", "attr.aria-describedby": "_config.ariaDescribedBy || null", "class.kt-dialog-container--sheet": "isSheet()", "class.kt-dialog-container--closing": "isClosing()" }, classAttribute: "cdk-dialog-container kt-dialog-container" }, usesInheritance: true, ngImport: i0, template: `
469
466
  <div class="kt-dialog-container__layout">
470
467
  <ng-template cdkPortalOutlet></ng-template>
471
468
  </div>
@@ -483,7 +480,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
483
480
  '[attr.id]': '_config.id || null',
484
481
  '[attr.role]': '_config.role',
485
482
  '[attr.aria-modal]': '_config.ariaModal',
486
- '[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',
483
+ '[attr.aria-labelledby]': '_config.ariaLabel ? null : ariaLabelledBy',
487
484
  '[attr.aria-label]': '_config.ariaLabel',
488
485
  '[attr.aria-describedby]': '_config.ariaDescribedBy || null',
489
486
  '[class.kt-dialog-container--sheet]': 'isSheet()',
@@ -497,18 +494,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
497
494
  }]
498
495
  }], ctorParameters: () => [] });
499
496
 
500
- /**
501
- * ⚠️ API BAS-NIVEAU — n'utilisez PAS ceci par défaut. Pour implémenter un dialog, passez par
502
- * {@link defineKtDialog} et son `injectData()` (qui lie le type des données à l'ouvreur).
503
- *
504
- * Sucre typé pour récupérer les données injectées dans un composant de dialogue (`DIALOG_DATA`).
505
- * Le type `T` est affirmé ICI indépendamment de l'ouvreur : rien ne garantit qu'il corresponde au
506
- * type passé à `injectKtDialogOpener` → risque de divergence silencieuse. À réserver à un cas où
507
- * `defineKtDialog` ne convient pas.
508
- */
509
- function injectKtDialogData() {
510
- return inject(DIALOG_DATA);
511
- }
512
497
  function injectKtDialogOpener(component, baseConfig) {
513
498
  const dialog = inject(Dialog);
514
499
  const viewport = inject(KtViewport);
@@ -527,6 +512,9 @@ function injectKtDialogOpener(component, baseConfig) {
527
512
  const titleId = `kt-dialog-title-${uid}`;
528
513
  const descId = `kt-dialog-desc-${uid}`;
529
514
  return dialog.open(component, {
515
+ // Plancher a11y garanti par l'opener (ariaModal/role/restoreFocus/autoFocus…), même si le
516
+ // consommateur n'a pas câblé provideKtDialogDefaults(). `...rest` (config par appel) prime.
517
+ ...KT_DIALOG_AAA_DEFAULTS,
530
518
  viewContainerRef,
531
519
  container: KtDialogContainer,
532
520
  ariaLabelledBy: titleId,
@@ -618,5 +606,5 @@ const KtDialogImports = [
618
606
  * Generated bundle index. Do not edit.
619
607
  */
620
608
 
621
- export { KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, defineKtDialog, injectKtDialogData, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
609
+ export { KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, defineKtDialog, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
622
610
  //# sourceMappingURL=ktortu-aaa-dialog.mjs.map