@ktortu/aaa 0.10.2 → 0.11.0

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.
@@ -155,8 +155,14 @@ class KtSidenavComponent {
155
155
  this.trapFocus.enabled = this.layout.isMobileOpen();
156
156
  });
157
157
  }
158
+ onEscape(event) {
159
+ if (this.layout.isMobileOpen()) {
160
+ event.stopPropagation();
161
+ this.layout.close();
162
+ }
163
+ }
158
164
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSidenavComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
159
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.1", type: KtSidenavComponent, isStandalone: true, selector: "kt-sidenav", host: { attributes: { "role": "navigation" }, properties: { "class.is-hidden": "isHidden()", "class.is-rail": "layout.isRail()", "class.is-mobile-open": "layout.isMobileOpen()", "attr.aria-expanded": "layout.isExpanded()", "attr.aria-hidden": "isHidden()" }, classAttribute: "kt-sidenav" }, hostDirectives: [{ directive: i1.CdkTrapFocus }], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true });
165
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.1", type: KtSidenavComponent, isStandalone: true, selector: "kt-sidenav", host: { attributes: { "role": "navigation" }, listeners: { "document:keydown.escape": "onEscape($event)" }, properties: { "class.is-hidden": "isHidden()", "class.is-rail": "layout.isRail()", "class.is-mobile-open": "layout.isMobileOpen()", "attr.aria-expanded": "layout.isExpanded()", "attr.aria-hidden": "isHidden()" }, classAttribute: "kt-sidenav" }, hostDirectives: [{ directive: i1.CdkTrapFocus }], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true });
160
166
  }
161
167
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtSidenavComponent, decorators: [{
162
168
  type: Component,
@@ -171,6 +177,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
171
177
  '[class.is-mobile-open]': 'layout.isMobileOpen()',
172
178
  '[attr.aria-expanded]': 'layout.isExpanded()',
173
179
  '[attr.aria-hidden]': 'isHidden()',
180
+ '(document:keydown.escape)': 'onEscape($event)',
174
181
  role: 'navigation',
175
182
  },
176
183
  hostDirectives: [CdkTrapFocus],
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-layout.mjs","sources":["../../../../projects/ktortu/aaa/layout/layout.service.ts","../../../../projects/ktortu/aaa/layout/layout.component.ts","../../../../projects/ktortu/aaa/layout/sidenav.component.ts","../../../../projects/ktortu/aaa/layout/toolbar.component.ts","../../../../projects/ktortu/aaa/layout/nav-item.component.ts","../../../../projects/ktortu/aaa/layout/sidenav-toggle.directive.ts","../../../../projects/ktortu/aaa/layout/ktortu-aaa-layout.ts"],"sourcesContent":["import { Injectable, computed, inject, InjectionToken, signal } from '@angular/core';\nimport { KtViewport } from '@ktortu/aaa/cdk';\n\n/** Mode d'état de la navigation latérale. */\nexport type KtSidenavState = 'expanded' | 'rail' | 'hidden';\n\nexport const KT_LAYOUT_DEFAULT_STATE = new InjectionToken<KtSidenavState>('KT_LAYOUT_DEFAULT_STATE', {\n providedIn: 'root',\n factory: () => 'expanded',\n});\n\n/**\n * Service global de gestion de la mise en page (Layout).\n * Gère l'état d'ouverture/fermeture de la barre latérale sur mobile et bureau,\n * ainsi que le comportement spécifique de \"rail\" sur bureau.\n *\n * NOTE: Ce service n'est pas \"providedIn: 'root'\". Le développeur doit le fournir\n * (via le tableau providers d'un composant parent ou de l'application) pour\n * maîtriser le scope (ex: avoir plusieurs layouts indépendants).\n */\n@Injectable()\nexport class KtLayoutService {\n private readonly viewport = inject(KtViewport);\n private readonly defaultState = inject(KT_LAYOUT_DEFAULT_STATE);\n\n /**\n * Comportement actif lors de la fermeture sur ordinateur (écran non mobile).\n * Renseigné généralement par le composant racine `KtLayoutComponent`.\n */\n readonly desktopCloseBehavior = signal<'hidden' | 'rail'>('hidden');\n\n private readonly _isDesktopOpen = signal<boolean>(this.defaultState !== 'rail' && this.defaultState !== 'hidden');\n private readonly _isMobileOpen = signal<boolean>(false);\n\n readonly isMobile = this.viewport.isMobile;\n readonly isRail = computed(\n () => !this.isMobile() && !this._isDesktopOpen() && this.desktopCloseBehavior() === 'rail',\n );\n readonly isExpanded = computed(() => !this.isMobile() && this._isDesktopOpen());\n readonly isHidden = computed(\n () =>\n (this.isMobile() && !this._isMobileOpen()) ||\n (!this.isMobile() && !this._isDesktopOpen() && this.desktopCloseBehavior() === 'hidden'),\n );\n readonly isMobileOpen = computed(() => this.isMobile() && this._isMobileOpen());\n\n toggle() {\n if (this.isMobile()) {\n this._isMobileOpen.update((v) => !v);\n } else {\n this._isDesktopOpen.update((v) => !v);\n }\n }\n\n open() {\n if (this.isMobile()) {\n this._isMobileOpen.set(true);\n } else {\n this._isDesktopOpen.set(true);\n }\n }\n\n close() {\n if (this.isMobile()) {\n this._isMobileOpen.set(false);\n } else {\n this._isDesktopOpen.set(false);\n }\n }\n}\n","import { Component, inject, effect, input } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Composant racine de l'application qui gère la structure de base (Sidebar, Toolbar, Contenu).\n * Configure dynamiquement le comportement du layout.\n */\n@Component({\n selector: 'kt-layout',\n standalone: true,\n host: {\n class: 'kt-layout-container',\n },\n template: `\n <ng-content select=\"kt-sidenav\"></ng-content>\n <div class=\"kt-layout-main-wrapper\">\n <ng-content select=\"kt-toolbar\"></ng-content>\n <main class=\"kt-layout-main\" [attr.aria-hidden]=\"layout.isMobileOpen()\">\n <ng-content></ng-content>\n </main>\n </div>\n <div\n class=\"kt-layout-backdrop\"\n [class.is-visible]=\"layout.isMobileOpen()\"\n aria-hidden=\"true\"\n (click)=\"layout.close()\"\n ></div>\n `,\n})\nexport class KtLayoutComponent {\n /**\n * Définit le comportement de la navigation latérale lors de sa fermeture sur bureau (écran large).\n * - `'hidden'` (défaut) : le menu est complètement masqué et retiré du flux visuel/a11y.\n * - `'rail'` : le menu est réduit à une fine barre verticale (mini-sidebar), permettant\n * d'afficher uniquement les icônes. Idéal si les liens possèdent des icônes explicites.\n */\n readonly desktopCloseBehavior = input<'hidden' | 'rail'>('hidden');\n protected readonly layout = inject(KtLayoutService);\n\n constructor() {\n effect(\n () => {\n this.layout.desktopCloseBehavior.set(this.desktopCloseBehavior());\n },\n { allowSignalWrites: true },\n );\n\n effect(\n () => {\n // Clean up mobile state when switching back to desktop\n if (!this.layout.isMobile() && this.layout.isMobileOpen()) {\n this.layout.close();\n }\n },\n { allowSignalWrites: true },\n );\n }\n}\n","import { Component, inject, computed, effect } from '@angular/core';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Composant représentant le panneau latéral de navigation.\n * Se comporte comme un tiroir (drawer) superposé sur mobile, et comme un\n * menu latéral persistant (réductible ou masquable) sur ordinateur.\n */\n@Component({\n selector: 'kt-sidenav',\n standalone: true,\n imports: [CdkTrapFocus],\n host: {\n class: 'kt-sidenav',\n '[class.is-hidden]': 'isHidden()',\n '[class.is-rail]': 'layout.isRail()',\n '[class.is-mobile-open]': 'layout.isMobileOpen()',\n '[attr.aria-expanded]': 'layout.isExpanded()',\n '[attr.aria-hidden]': 'isHidden()',\n role: 'navigation',\n },\n hostDirectives: [CdkTrapFocus],\n template: `<ng-content></ng-content>`,\n})\nexport class KtSidenavComponent {\n protected readonly layout = inject(KtLayoutService);\n protected readonly isHidden = computed(() => this.layout.isHidden());\n private readonly trapFocus = inject(CdkTrapFocus);\n\n constructor() {\n effect(() => {\n this.trapFocus.autoCapture = this.layout.isMobileOpen();\n this.trapFocus.enabled = this.layout.isMobileOpen();\n });\n }\n}\n","import { Component } from '@angular/core';\n\n/**\n * Barre d'outils supérieure (Toolbar / Top app bar).\n * Se place généralement dans un `kt-layout`.\n */\n@Component({\n selector: 'kt-toolbar',\n standalone: true,\n host: {\n class: 'kt-toolbar',\n role: 'banner',\n },\n template: `<ng-content></ng-content>`,\n})\nexport class KtToolbarComponent {}\n","import { Component, input, inject } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Item de navigation : projette une icône dans son slot dédié + un libellé.\n *\n * Le slot d'icône accepte :\n * - la directive `[ktIcon]` (primitive d'icône `@ktortu/aaa/icon`), forme canonique.\n * - l'attribut de projection `kt-icon` (historique/legacy).\n *\n * Note : la classe `.kt-icon` (posée à l'exécution par la directive) n'est PAS un critère de\n * projection valable (ng-content matche la forme statique du template) — d'où le sélecteur d'attribut.\n */\n@Component({\n selector: 'kt-nav-item',\n standalone: true,\n host: {\n class: 'kt-nav-item',\n },\n template: `\n <div class=\"kt-nav-item-icon\">\n <ng-content select=\"[ktIcon], [kt-icon]\"></ng-content>\n </div>\n <div class=\"kt-nav-item-label\">\n {{ label() }}\n <ng-content></ng-content>\n </div>\n <!-- TODO: Add Tooltip component injection here conditionally based on layout.isRail() -->\n `,\n})\nexport class KtNavItemComponent {\n label = input.required<string>();\n protected readonly layout = inject(KtLayoutService);\n}\n","import { Directive, inject } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Directive applicable sur n'importe quel élément (souvent un bouton) pour basculer\n * l'état d'ouverture du panneau latéral (Sidenav).\n *\n * Expose l'état du layout pour des utilisations dynamiques dans le template\n * (ex: changer l'icône selon l'état).\n *\n * @example\n * ```html\n * <button ktSidenavToggle #btn=\"ktSidenavToggle\" [icon]=\"btn.isRail() ? 'right' : 'left'\">\n * ```\n */\n@Directive({\n selector: '[ktSidenavToggle]',\n exportAs: 'ktSidenavToggle',\n standalone: true,\n host: {\n '(click)': 'onClick()',\n },\n})\nexport class KtSidenavToggleDirective {\n private readonly layout = inject(KtLayoutService);\n\n /** Indique si le menu est actuellement réduit en mode rail. */\n readonly isRail = this.layout.isRail;\n\n /** Indique si le menu est actuellement complètement étendu sur bureau. */\n readonly isExpanded = this.layout.isExpanded;\n\n /** Indique si le menu est totalement masqué. */\n readonly isHidden = this.layout.isHidden;\n\n /** Indique si le tiroir est actuellement ouvert par-dessus le contenu (mobile). */\n readonly isMobileOpen = this.layout.isMobileOpen;\n\n /** Indique si l'écran actuel est de taille compacte (mobile). */\n readonly isMobile = this.layout.isMobile;\n\n onClick() {\n this.layout.toggle();\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-layout.mjs","sources":["../../../../projects/ktortu/aaa/layout/layout.service.ts","../../../../projects/ktortu/aaa/layout/layout.component.ts","../../../../projects/ktortu/aaa/layout/sidenav.component.ts","../../../../projects/ktortu/aaa/layout/toolbar.component.ts","../../../../projects/ktortu/aaa/layout/nav-item.component.ts","../../../../projects/ktortu/aaa/layout/sidenav-toggle.directive.ts","../../../../projects/ktortu/aaa/layout/ktortu-aaa-layout.ts"],"sourcesContent":["import { Injectable, computed, inject, InjectionToken, signal } from '@angular/core';\nimport { KtViewport } from '@ktortu/aaa/cdk';\n\n/** Mode d'état de la navigation latérale. */\nexport type KtSidenavState = 'expanded' | 'rail' | 'hidden';\n\nexport const KT_LAYOUT_DEFAULT_STATE = new InjectionToken<KtSidenavState>('KT_LAYOUT_DEFAULT_STATE', {\n providedIn: 'root',\n factory: () => 'expanded',\n});\n\n/**\n * Service global de gestion de la mise en page (Layout).\n * Gère l'état d'ouverture/fermeture de la barre latérale sur mobile et bureau,\n * ainsi que le comportement spécifique de \"rail\" sur bureau.\n *\n * NOTE: Ce service n'est pas \"providedIn: 'root'\". Le développeur doit le fournir\n * (via le tableau providers d'un composant parent ou de l'application) pour\n * maîtriser le scope (ex: avoir plusieurs layouts indépendants).\n */\n@Injectable()\nexport class KtLayoutService {\n private readonly viewport = inject(KtViewport);\n private readonly defaultState = inject(KT_LAYOUT_DEFAULT_STATE);\n\n /**\n * Comportement actif lors de la fermeture sur ordinateur (écran non mobile).\n * Renseigné généralement par le composant racine `KtLayoutComponent`.\n */\n readonly desktopCloseBehavior = signal<'hidden' | 'rail'>('hidden');\n\n private readonly _isDesktopOpen = signal<boolean>(this.defaultState !== 'rail' && this.defaultState !== 'hidden');\n private readonly _isMobileOpen = signal<boolean>(false);\n\n readonly isMobile = this.viewport.isMobile;\n readonly isRail = computed(\n () => !this.isMobile() && !this._isDesktopOpen() && this.desktopCloseBehavior() === 'rail',\n );\n readonly isExpanded = computed(() => !this.isMobile() && this._isDesktopOpen());\n readonly isHidden = computed(\n () =>\n (this.isMobile() && !this._isMobileOpen()) ||\n (!this.isMobile() && !this._isDesktopOpen() && this.desktopCloseBehavior() === 'hidden'),\n );\n readonly isMobileOpen = computed(() => this.isMobile() && this._isMobileOpen());\n\n toggle() {\n if (this.isMobile()) {\n this._isMobileOpen.update((v) => !v);\n } else {\n this._isDesktopOpen.update((v) => !v);\n }\n }\n\n open() {\n if (this.isMobile()) {\n this._isMobileOpen.set(true);\n } else {\n this._isDesktopOpen.set(true);\n }\n }\n\n close() {\n if (this.isMobile()) {\n this._isMobileOpen.set(false);\n } else {\n this._isDesktopOpen.set(false);\n }\n }\n}\n","import { Component, inject, effect, input } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Composant racine de l'application qui gère la structure de base (Sidebar, Toolbar, Contenu).\n * Configure dynamiquement le comportement du layout.\n */\n@Component({\n selector: 'kt-layout',\n standalone: true,\n host: {\n class: 'kt-layout-container',\n },\n template: `\n <ng-content select=\"kt-sidenav\"></ng-content>\n <div class=\"kt-layout-main-wrapper\">\n <ng-content select=\"kt-toolbar\"></ng-content>\n <main class=\"kt-layout-main\" [attr.aria-hidden]=\"layout.isMobileOpen()\">\n <ng-content></ng-content>\n </main>\n </div>\n <div\n class=\"kt-layout-backdrop\"\n [class.is-visible]=\"layout.isMobileOpen()\"\n aria-hidden=\"true\"\n (click)=\"layout.close()\"\n ></div>\n `,\n})\nexport class KtLayoutComponent {\n /**\n * Définit le comportement de la navigation latérale lors de sa fermeture sur bureau (écran large).\n * - `'hidden'` (défaut) : le menu est complètement masqué et retiré du flux visuel/a11y.\n * - `'rail'` : le menu est réduit à une fine barre verticale (mini-sidebar), permettant\n * d'afficher uniquement les icônes. Idéal si les liens possèdent des icônes explicites.\n */\n readonly desktopCloseBehavior = input<'hidden' | 'rail'>('hidden');\n protected readonly layout = inject(KtLayoutService);\n\n constructor() {\n effect(\n () => {\n this.layout.desktopCloseBehavior.set(this.desktopCloseBehavior());\n },\n { allowSignalWrites: true },\n );\n\n effect(\n () => {\n // Clean up mobile state when switching back to desktop\n if (!this.layout.isMobile() && this.layout.isMobileOpen()) {\n this.layout.close();\n }\n },\n { allowSignalWrites: true },\n );\n }\n}\n","import { Component, inject, computed, effect } from '@angular/core';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Composant représentant le panneau latéral de navigation.\n * Se comporte comme un tiroir (drawer) superposé sur mobile, et comme un\n * menu latéral persistant (réductible ou masquable) sur ordinateur.\n */\n@Component({\n selector: 'kt-sidenav',\n standalone: true,\n imports: [CdkTrapFocus],\n host: {\n class: 'kt-sidenav',\n '[class.is-hidden]': 'isHidden()',\n '[class.is-rail]': 'layout.isRail()',\n '[class.is-mobile-open]': 'layout.isMobileOpen()',\n '[attr.aria-expanded]': 'layout.isExpanded()',\n '[attr.aria-hidden]': 'isHidden()',\n '(document:keydown.escape)': 'onEscape($event)',\n role: 'navigation',\n },\n hostDirectives: [CdkTrapFocus],\n template: `<ng-content></ng-content>`,\n})\nexport class KtSidenavComponent {\n protected readonly layout = inject(KtLayoutService);\n protected readonly isHidden = computed(() => this.layout.isHidden());\n private readonly trapFocus = inject(CdkTrapFocus);\n\n constructor() {\n effect(() => {\n this.trapFocus.autoCapture = this.layout.isMobileOpen();\n this.trapFocus.enabled = this.layout.isMobileOpen();\n });\n }\n\n protected onEscape(event: Event): void {\n if (this.layout.isMobileOpen()) {\n event.stopPropagation();\n this.layout.close();\n }\n }\n}\n","import { Component } from '@angular/core';\n\n/**\n * Barre d'outils supérieure (Toolbar / Top app bar).\n * Se place généralement dans un `kt-layout`.\n */\n@Component({\n selector: 'kt-toolbar',\n standalone: true,\n host: {\n class: 'kt-toolbar',\n role: 'banner',\n },\n template: `<ng-content></ng-content>`,\n})\nexport class KtToolbarComponent {}\n","import { Component, input, inject } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Item de navigation : projette une icône dans son slot dédié + un libellé.\n *\n * Le slot d'icône accepte :\n * - la directive `[ktIcon]` (primitive d'icône `@ktortu/aaa/icon`), forme canonique.\n * - l'attribut de projection `kt-icon` (historique/legacy).\n *\n * Note : la classe `.kt-icon` (posée à l'exécution par la directive) n'est PAS un critère de\n * projection valable (ng-content matche la forme statique du template) — d'où le sélecteur d'attribut.\n */\n@Component({\n selector: 'kt-nav-item',\n standalone: true,\n host: {\n class: 'kt-nav-item',\n },\n template: `\n <div class=\"kt-nav-item-icon\">\n <ng-content select=\"[ktIcon], [kt-icon]\"></ng-content>\n </div>\n <div class=\"kt-nav-item-label\">\n {{ label() }}\n <ng-content></ng-content>\n </div>\n <!-- TODO: Add Tooltip component injection here conditionally based on layout.isRail() -->\n `,\n})\nexport class KtNavItemComponent {\n label = input.required<string>();\n protected readonly layout = inject(KtLayoutService);\n}\n","import { Directive, inject } from '@angular/core';\nimport { KtLayoutService } from './layout.service';\n\n/**\n * Directive applicable sur n'importe quel élément (souvent un bouton) pour basculer\n * l'état d'ouverture du panneau latéral (Sidenav).\n *\n * Expose l'état du layout pour des utilisations dynamiques dans le template\n * (ex: changer l'icône selon l'état).\n *\n * @example\n * ```html\n * <button ktSidenavToggle #btn=\"ktSidenavToggle\" [icon]=\"btn.isRail() ? 'right' : 'left'\">\n * ```\n */\n@Directive({\n selector: '[ktSidenavToggle]',\n exportAs: 'ktSidenavToggle',\n standalone: true,\n host: {\n '(click)': 'onClick()',\n },\n})\nexport class KtSidenavToggleDirective {\n private readonly layout = inject(KtLayoutService);\n\n /** Indique si le menu est actuellement réduit en mode rail. */\n readonly isRail = this.layout.isRail;\n\n /** Indique si le menu est actuellement complètement étendu sur bureau. */\n readonly isExpanded = this.layout.isExpanded;\n\n /** Indique si le menu est totalement masqué. */\n readonly isHidden = this.layout.isHidden;\n\n /** Indique si le tiroir est actuellement ouvert par-dessus le contenu (mobile). */\n readonly isMobileOpen = this.layout.isMobileOpen;\n\n /** Indique si l'écran actuel est de taille compacte (mobile). */\n readonly isMobile = this.layout.isMobile;\n\n onClick() {\n this.layout.toggle();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Directive, inject, PLATFORM_ID, effect, ElementRef, model, afterNextRender, isDevMode, InjectionToken, input, computed } from '@angular/core';
2
+ import { Directive, inject, PLATFORM_ID, signal, effect, ElementRef, model, afterNextRender, isDevMode, InjectionToken, input, computed } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
4
  import { MenuItem, MenuTrigger } from '@angular/aria/menu';
5
5
  import { KtIdGenerator } from '@ktortu/aaa/cdk';
@@ -60,7 +60,8 @@ class KtMenuItem {
60
60
  platformId = inject(PLATFORM_ID);
61
61
  // Alloué à la PREMIÈRE détection d'un sous-menu (et non pour chaque item) : un item simple ne
62
62
  // consomme pas d'anchor-name.
63
- anchorName;
63
+ anchorName = signal(undefined, /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "anchorName" }] : /* istanbul ignore next */ []));
64
65
  idGen = inject(KtIdGenerator);
65
66
  constructor() {
66
67
  if (!this.ariaItem)
@@ -68,12 +69,13 @@ class KtMenuItem {
68
69
  effect(() => {
69
70
  const submenu = this.ariaItem.submenu()?.element;
70
71
  if (!submenu) {
71
- this.anchorName = undefined;
72
+ this.anchorName.set(undefined);
72
73
  return;
73
74
  }
74
- this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;
75
+ const name = this.anchorName() ?? `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;
76
+ this.anchorName.set(name);
75
77
  if (isPlatformBrowser(this.platformId)) {
76
- submenu.style.setProperty('position-anchor', this.anchorName);
78
+ submenu.style.setProperty('position-anchor', name);
77
79
  // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non
78
80
  // sous l'item, avec ses propres fallbacks de débordement.
79
81
  submenu.setAttribute('data-kt-submenu', '');
@@ -81,14 +83,14 @@ class KtMenuItem {
81
83
  });
82
84
  }
83
85
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
84
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: KtMenuItem, isStandalone: true, selector: "[ktMenuItem]", host: { properties: { "style.anchor-name": "anchorName" } }, ngImport: i0 });
86
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: KtMenuItem, isStandalone: true, selector: "[ktMenuItem]", host: { properties: { "style.anchor-name": "anchorName()" } }, ngImport: i0 });
85
87
  }
86
88
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtMenuItem, decorators: [{
87
89
  type: Directive,
88
90
  args: [{
89
91
  selector: '[ktMenuItem]',
90
92
  host: {
91
- '[style.anchor-name]': 'anchorName',
93
+ '[style.anchor-name]': 'anchorName()',
92
94
  },
93
95
  }]
94
96
  }], ctorParameters: () => [] });
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-menu.mjs","sources":["../../../../projects/ktortu/aaa/menu/menu.ts","../../../../projects/ktortu/aaa/menu/menu-trigger.ts","../../../../projects/ktortu/aaa/menu/menu-toggle.ts","../../../../projects/ktortu/aaa/menu/public-api.ts","../../../../projects/ktortu/aaa/menu/ktortu-aaa-menu.ts"],"sourcesContent":["import { Directive, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenu]` — marqueur de THÈME posé sur l'hôte `[ngMenu]` d'`@angular/aria`. Style UNIQUEMENT la\n * SURFACE du menu (fond, bord, ombre, rayon, padding, positionnement en dropdown ancré). N'ajoute\n * AUCUN rôle ni comportement : la sémantique (`role=\"menu\"`), le clavier, le focus roving, le\n * retour de focus au trigger, les sous-menus et le typeahead viennent intégralement d'aria\n * (`[ngMenu]`). Même philosophie que `[ktCard]` : la lib thématise, aria gère l'accessibilité.\n *\n * La directive n'a pas de logique propre : tout vit dans `menu.css` via le sélecteur `[ktMenu]`,\n * piloté par l'attribut `data-visible` qu'aria pose sur l'hôte (ouvert/fermé). Le positionnement\n * (CSS Anchor Positioning) est câblé par `[ktMenuTrigger]` (menu racine) et par `[ktMenuItem]`\n * (sous-menus) qui posent les `anchor-name` / `position-anchor`.\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Options</button>\n * <div ngMenu ktMenu #m=\"ngMenu\" (itemSelected)=\"onSelect($event)\" aria-label=\"Options\">\n * <button ngMenuItem ktMenuItem value=\"new\">Nouveau</button>\n * </div>\n * ```\n */\n@Directive({ selector: '[ktMenu]' })\nexport class KtMenu {}\n\n/**\n * `[ktMenuItem]` — marqueur de thème posé sur l'hôte `[ngMenuItem]`. Style la RANGÉE (hauteur de\n * cible tactile ≥44px, focus, survol, désactivé, item de sous-menu avec chevron). Comme `[ktMenu]`,\n * sans logique de sélection : l'état stylé est lu sur les attributs ARIA déjà posés par aria\n * (`:focus-visible` pour l'item actif via focus roving, `[aria-disabled]`, `[aria-expanded]`,\n * `[aria-haspopup]`, `[aria-checked]`).\n *\n * SEULE responsabilité technique : si l'item porte un sous-menu (`[submenu]` côté aria), on câble\n * l'ancrage CSS du sous-menu sur cet item (anchor-name sur l'item, position-anchor sur la surface\n * du sous-menu, marqueur `data-kt-submenu` pour l'ouvrir en latéral plutôt qu'en dropdown).\n *\n * EXIGENCE DE STRUCTURE (sous-menus) : le `[ngMenu]` d'un sous-menu DOIT être un DESCENDANT DOM du\n * menu parent (et non un frère). aria décide de maintenir un menu ouvert via `element.contains()`\n * sur la cible du focus/survol ; un sous-menu placé hors du sous-arbre du parent ferait croire à\n * aria que le focus a quitté le menu et provoquerait sa fermeture dès qu'on survole le sous-menu.\n * Le sous-menu reste rendu en place (top-layer non requis : `position: fixed` + ancrage CSS).\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem value=\"open\">Ouvrir</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItem]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuItem {\n // Optionnel : `[ktMenuItem]` reste utilisable seul (simple thème) même hors d'un `[ngMenuItem]`.\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n // Alloué à la PREMIÈRE détection d'un sous-menu (et non pour chaque item) : un item simple ne\n // consomme pas d'anchor-name.\n protected anchorName: string | undefined;\n private readonly idGen = inject(KtIdGenerator);\n\n constructor() {\n if (!this.ariaItem) return;\n\n effect(() => {\n const submenu = this.ariaItem!.submenu()?.element;\n if (!submenu) {\n this.anchorName = undefined;\n return;\n }\n this.anchorName ??= `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;\n if (isPlatformBrowser(this.platformId)) {\n submenu.style.setProperty('position-anchor', this.anchorName);\n // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non\n // sous l'item, avec ses propres fallbacks de débordement.\n submenu.setAttribute('data-kt-submenu', '');\n }\n });\n }\n}\n\n/**\n * `[ktMenuSeparator]` — filet de séparation entre groupes d'items. Pose `role=\"separator\"`\n * (sémantique standard d'un séparateur de menu) ; la ligne elle-même vit dans `menu.css`.\n *\n * @example\n * ```html\n * <hr ktMenuSeparator />\n * ```\n */\n@Directive({\n selector: '[ktMenuSeparator]',\n host: { role: 'separator' },\n})\nexport class KtMenuSeparator {}\n","import { Directive, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuTrigger as AriaMenuTrigger } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenuTrigger]` — sucre de POSITIONNEMENT pour le menu racine. À poser SUR le même élément\n * qu'`[ngMenuTrigger]` (le bouton qui ouvre le menu). Il ne réimplémente RIEN du comportement :\n * l'ouverture/fermeture, `aria-expanded`, Échap, le clic-dehors et le retour de focus sont gérés\n * par aria (`[ngMenuTrigger]`). Sa seule tâche : poser un `anchor-name` sur le trigger et le\n * `position-anchor` correspondant sur la surface du menu, pour que `menu.css` l'ancre en dropdown\n * (CSS Anchor Positioning), comme le popup du Select — sans CDK Overlay (styles scopés conservés,\n * et pas de coordination fragile avec le focus qu'aria pose à l'ouverture).\n *\n * Requiert `[ngMenuTrigger]` sur le même hôte (injecté en `self`).\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Fichier</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuTrigger]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuTrigger {\n private readonly ariaTrigger = inject(AriaMenuTrigger, { self: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n private readonly idGen = inject(KtIdGenerator);\n protected readonly anchorName = `--kt-menu-anchor-${this.idGen.generateId('menu')}`;\n\n constructor() {\n effect(() => {\n const menuEl = this.ariaTrigger.menu()?.element;\n if (!menuEl) return;\n if (isPlatformBrowser(this.platformId)) {\n menuEl.style.setProperty('position-anchor', this.anchorName);\n }\n });\n }\n}\n","import {\n Directive,\n ElementRef,\n afterNextRender,\n computed,\n inject,\n input,\n InjectionToken,\n isDevMode,\n model,\n} from '@angular/core';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\n\n/**\n * Items de menu À ÉTAT (checkbox / radio).\n *\n * Pourquoi ces directives existent : `@angular/aria` accepte bien `role=\"menuitemcheckbox\"` /\n * `\"menuitemradio\"` sur `[ngMenuItem]`, MAIS NE POSE JAMAIS `aria-checked` — son pattern se\n * contente d'émettre `itemSelected(value)`. Sans `aria-checked`, l'item coché est MUET pour les\n * lecteurs d'écran (échec WCAG 4.1.2). Ces directives comblent ce trou : elles possèdent l'état\n * coché, le bindent en `aria-checked`, et le basculent à l'activation (clic / Entrée / Espace).\n *\n * Elles ne touchent pas au `role` (laissé à l'input `role` d'aria, posé par le consommateur) pour\n * éviter tout conflit de binding sur `[attr.role]` ; la démo montre l'usage `role=\"menuitemradio\"`.\n */\n\n/**\n * Activation commune : clic ET clavier (Entrée / Espace). Les deux liaisons coexistent VOLONTAIREMENT\n * pour supporter un hôte non-`<button>` (ex. `<div ngMenuItem>`), où le clavier n'émet pas de `click`\n * natif. Sur un `<button>`, `activate()` appelle `event.preventDefault()` sur le `keydown`, ce qui\n * neutralise le `click` synthétique → une seule bascule (pas de double-toggle). Couvert en E2E.\n */\nconst ACTIVATION_HOST = {\n '(click)': 'activate($event)',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n} as const;\n\n/**\n * `[ktMenuItemCheckbox]` — case à cocher de menu (bascule indépendante). À poser sur un\n * `[ngMenuItem] role=\"menuitemcheckbox\"`. Bind `aria-checked` et bascule `checked` à l'activation.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemCheckbox role=\"menuitemcheckbox\" [(checked)]=\"wrap\">\n * Retour à la ligne\n * </button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemCheckbox]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemCheckbox {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /**\n * État coché, bidirectionnel : `[(checked)]`.\n * @default false\n */\n readonly checked = model<boolean>(false);\n\n constructor() {\n // Garde-fou dev : sans `role=\"menuitemcheckbox\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemcheckbox') return;\n console.warn(\n '[ktMenuItemCheckbox] attend `role=\"menuitemcheckbox\"` sur l’hôte, sinon `aria-checked` est inopérant.',\n );\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.checked.update((v) => !v);\n }\n}\n\n/** Source de la valeur sélectionnée d'un groupe radio, exposée aux `[ktMenuItemRadio]` enfants. */\nexport const KT_MENU_RADIO_GROUP = new InjectionToken<KtMenuRadioGroup<unknown>>('KT_MENU_RADIO_GROUP');\n\n/**\n * `[ktMenuRadioGroup]` — coordinateur d'un groupe d'items radio mutuellement exclusifs. À poser sur\n * un conteneur englobant les `[ktMenuItemRadio]` (typiquement un `role=\"group\"`). Détient la valeur\n * sélectionnée ; chaque radio s'y compare pour son `aria-checked` et la met à jour à l'activation.\n *\n * @example\n * ```html\n * <div role=\"group\" ktMenuRadioGroup [(value)]=\"sortBy\" aria-label=\"Trier par\">\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Nom</button>\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'date'\">Date</button>\n * </div>\n * ```\n */\n@Directive({\n selector: '[ktMenuRadioGroup]',\n exportAs: 'ktMenuRadioGroup',\n providers: [{ provide: KT_MENU_RADIO_GROUP, useExisting: KtMenuRadioGroup }],\n})\nexport class KtMenuRadioGroup<V> {\n /**\n * Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`.\n * @default null\n */\n readonly value = model<V | null>(null);\n}\n\n/**\n * `[ktMenuItemRadio]` — bouton radio de menu. À poser sur un `[ngMenuItem] role=\"menuitemradio\"`\n * dans un `[ktMenuRadioGroup]`. `aria-checked` reflète l'égalité avec la valeur du groupe ;\n * l'activation sélectionne cette valeur.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Trier par nom</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemRadio]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemRadio<V> {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly group = inject<KtMenuRadioGroup<V>>(KT_MENU_RADIO_GROUP, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Valeur portée par ce radio (comparée à celle du groupe). @default (requis) */\n readonly value = input.required<V>();\n\n protected readonly checked = computed(() => this.group?.value() === this.value());\n\n constructor() {\n // Garde-fou dev : sans [ktMenuRadioGroup] parent, aria-checked reste figé (item muet pour les SR).\n if (isDevMode() && !this.group) {\n console.warn(\n '[ktMenuItemRadio] doit être placé dans un [ktMenuRadioGroup] : ' +\n 'sans groupe, la sélection et `aria-checked` restent inopérants.',\n );\n }\n // Garde-fou dev : sans `role=\"menuitemradio\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemradio') return;\n console.warn('[ktMenuItemRadio] attend `role=\"menuitemradio\"` sur l’hôte, sinon `aria-checked` est inopérant.');\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.group?.value.set(this.value());\n }\n}\n","import { KtMenu, KtMenuItem, KtMenuSeparator } from './menu';\nimport { KtMenuTrigger } from './menu-trigger';\nimport { KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup } from './menu-toggle';\n\nexport * from './menu';\nexport * from './menu-trigger';\nexport * from './menu-toggle';\n\n/**\n * Import ergonomique de toute la famille menu (couche de THÈME ktortu) en une fois :\n * `imports: [KtMenuImports]` au lieu d'énumérer chaque directive. À composer avec les directives\n * d'`@angular/aria/menu` (`Menu`, `MenuItem`, `MenuTrigger`, `MenuContent`), qui apportent le\n * comportement accessible.\n */\nexport const KtMenuImports = [\n KtMenu,\n KtMenuItem,\n KtMenuSeparator,\n KtMenuTrigger,\n KtMenuItemCheckbox,\n KtMenuItemRadio,\n KtMenuRadioGroup,\n] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["AriaMenuItem","AriaMenuTrigger"],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-menu.mjs","sources":["../../../../projects/ktortu/aaa/menu/menu.ts","../../../../projects/ktortu/aaa/menu/menu-trigger.ts","../../../../projects/ktortu/aaa/menu/menu-toggle.ts","../../../../projects/ktortu/aaa/menu/public-api.ts","../../../../projects/ktortu/aaa/menu/ktortu-aaa-menu.ts"],"sourcesContent":["import { Directive, effect, inject, signal, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenu]` — marqueur de THÈME posé sur l'hôte `[ngMenu]` d'`@angular/aria`. Style UNIQUEMENT la\n * SURFACE du menu (fond, bord, ombre, rayon, padding, positionnement en dropdown ancré). N'ajoute\n * AUCUN rôle ni comportement : la sémantique (`role=\"menu\"`), le clavier, le focus roving, le\n * retour de focus au trigger, les sous-menus et le typeahead viennent intégralement d'aria\n * (`[ngMenu]`). Même philosophie que `[ktCard]` : la lib thématise, aria gère l'accessibilité.\n *\n * La directive n'a pas de logique propre : tout vit dans `menu.css` via le sélecteur `[ktMenu]`,\n * piloté par l'attribut `data-visible` qu'aria pose sur l'hôte (ouvert/fermé). Le positionnement\n * (CSS Anchor Positioning) est câblé par `[ktMenuTrigger]` (menu racine) et par `[ktMenuItem]`\n * (sous-menus) qui posent les `anchor-name` / `position-anchor`.\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Options</button>\n * <div ngMenu ktMenu #m=\"ngMenu\" (itemSelected)=\"onSelect($event)\" aria-label=\"Options\">\n * <button ngMenuItem ktMenuItem value=\"new\">Nouveau</button>\n * </div>\n * ```\n */\n@Directive({ selector: '[ktMenu]' })\nexport class KtMenu {}\n\n/**\n * `[ktMenuItem]` — marqueur de thème posé sur l'hôte `[ngMenuItem]`. Style la RANGÉE (hauteur de\n * cible tactile ≥44px, focus, survol, désactivé, item de sous-menu avec chevron). Comme `[ktMenu]`,\n * sans logique de sélection : l'état stylé est lu sur les attributs ARIA déjà posés par aria\n * (`:focus-visible` pour l'item actif via focus roving, `[aria-disabled]`, `[aria-expanded]`,\n * `[aria-haspopup]`, `[aria-checked]`).\n *\n * SEULE responsabilité technique : si l'item porte un sous-menu (`[submenu]` côté aria), on câble\n * l'ancrage CSS du sous-menu sur cet item (anchor-name sur l'item, position-anchor sur la surface\n * du sous-menu, marqueur `data-kt-submenu` pour l'ouvrir en latéral plutôt qu'en dropdown).\n *\n * EXIGENCE DE STRUCTURE (sous-menus) : le `[ngMenu]` d'un sous-menu DOIT être un DESCENDANT DOM du\n * menu parent (et non un frère). aria décide de maintenir un menu ouvert via `element.contains()`\n * sur la cible du focus/survol ; un sous-menu placé hors du sous-arbre du parent ferait croire à\n * aria que le focus a quitté le menu et provoquerait sa fermeture dès qu'on survole le sous-menu.\n * Le sous-menu reste rendu en place (top-layer non requis : `position: fixed` + ancrage CSS).\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem value=\"open\">Ouvrir</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItem]',\n host: {\n '[style.anchor-name]': 'anchorName()',\n },\n})\nexport class KtMenuItem {\n // Optionnel : `[ktMenuItem]` reste utilisable seul (simple thème) même hors d'un `[ngMenuItem]`.\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n // Alloué à la PREMIÈRE détection d'un sous-menu (et non pour chaque item) : un item simple ne\n // consomme pas d'anchor-name.\n protected readonly anchorName = signal<string | undefined>(undefined);\n private readonly idGen = inject(KtIdGenerator);\n\n constructor() {\n if (!this.ariaItem) return;\n\n effect(() => {\n const submenu = this.ariaItem!.submenu()?.element;\n if (!submenu) {\n this.anchorName.set(undefined);\n return;\n }\n const name = this.anchorName() ?? `--kt-submenu-anchor-${this.idGen.generateId('menu')}`;\n this.anchorName.set(name);\n if (isPlatformBrowser(this.platformId)) {\n submenu.style.setProperty('position-anchor', name);\n // Marque la surface comme un SOUS-menu : menu.css l'ouvre en latéral (inline-end) et non\n // sous l'item, avec ses propres fallbacks de débordement.\n submenu.setAttribute('data-kt-submenu', '');\n }\n });\n }\n}\n\n/**\n * `[ktMenuSeparator]` — filet de séparation entre groupes d'items. Pose `role=\"separator\"`\n * (sémantique standard d'un séparateur de menu) ; la ligne elle-même vit dans `menu.css`.\n *\n * @example\n * ```html\n * <hr ktMenuSeparator />\n * ```\n */\n@Directive({\n selector: '[ktMenuSeparator]',\n host: { role: 'separator' },\n})\nexport class KtMenuSeparator {}\n","import { Directive, effect, inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MenuTrigger as AriaMenuTrigger } from '@angular/aria/menu';\nimport { KtIdGenerator } from '@ktortu/aaa/cdk';\n\n/**\n * `[ktMenuTrigger]` — sucre de POSITIONNEMENT pour le menu racine. À poser SUR le même élément\n * qu'`[ngMenuTrigger]` (le bouton qui ouvre le menu). Il ne réimplémente RIEN du comportement :\n * l'ouverture/fermeture, `aria-expanded`, Échap, le clic-dehors et le retour de focus sont gérés\n * par aria (`[ngMenuTrigger]`). Sa seule tâche : poser un `anchor-name` sur le trigger et le\n * `position-anchor` correspondant sur la surface du menu, pour que `menu.css` l'ancre en dropdown\n * (CSS Anchor Positioning), comme le popup du Select — sans CDK Overlay (styles scopés conservés,\n * et pas de coordination fragile avec le focus qu'aria pose à l'ouverture).\n *\n * Requiert `[ngMenuTrigger]` sur le même hôte (injecté en `self`).\n *\n * @example\n * ```html\n * <button ngMenuTrigger ktMenuTrigger [menu]=\"m\">Fichier</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuTrigger]',\n host: {\n '[style.anchor-name]': 'anchorName',\n },\n})\nexport class KtMenuTrigger {\n private readonly ariaTrigger = inject(AriaMenuTrigger, { self: true });\n private readonly platformId = inject(PLATFORM_ID);\n\n private readonly idGen = inject(KtIdGenerator);\n protected readonly anchorName = `--kt-menu-anchor-${this.idGen.generateId('menu')}`;\n\n constructor() {\n effect(() => {\n const menuEl = this.ariaTrigger.menu()?.element;\n if (!menuEl) return;\n if (isPlatformBrowser(this.platformId)) {\n menuEl.style.setProperty('position-anchor', this.anchorName);\n }\n });\n }\n}\n","import {\n Directive,\n ElementRef,\n afterNextRender,\n computed,\n inject,\n input,\n InjectionToken,\n isDevMode,\n model,\n} from '@angular/core';\nimport { MenuItem as AriaMenuItem } from '@angular/aria/menu';\n\n/**\n * Items de menu À ÉTAT (checkbox / radio).\n *\n * Pourquoi ces directives existent : `@angular/aria` accepte bien `role=\"menuitemcheckbox\"` /\n * `\"menuitemradio\"` sur `[ngMenuItem]`, MAIS NE POSE JAMAIS `aria-checked` — son pattern se\n * contente d'émettre `itemSelected(value)`. Sans `aria-checked`, l'item coché est MUET pour les\n * lecteurs d'écran (échec WCAG 4.1.2). Ces directives comblent ce trou : elles possèdent l'état\n * coché, le bindent en `aria-checked`, et le basculent à l'activation (clic / Entrée / Espace).\n *\n * Elles ne touchent pas au `role` (laissé à l'input `role` d'aria, posé par le consommateur) pour\n * éviter tout conflit de binding sur `[attr.role]` ; la démo montre l'usage `role=\"menuitemradio\"`.\n */\n\n/**\n * Activation commune : clic ET clavier (Entrée / Espace). Les deux liaisons coexistent VOLONTAIREMENT\n * pour supporter un hôte non-`<button>` (ex. `<div ngMenuItem>`), où le clavier n'émet pas de `click`\n * natif. Sur un `<button>`, `activate()` appelle `event.preventDefault()` sur le `keydown`, ce qui\n * neutralise le `click` synthétique → une seule bascule (pas de double-toggle). Couvert en E2E.\n */\nconst ACTIVATION_HOST = {\n '(click)': 'activate($event)',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n} as const;\n\n/**\n * `[ktMenuItemCheckbox]` — case à cocher de menu (bascule indépendante). À poser sur un\n * `[ngMenuItem] role=\"menuitemcheckbox\"`. Bind `aria-checked` et bascule `checked` à l'activation.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemCheckbox role=\"menuitemcheckbox\" [(checked)]=\"wrap\">\n * Retour à la ligne\n * </button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemCheckbox]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemCheckbox {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /**\n * État coché, bidirectionnel : `[(checked)]`.\n * @default false\n */\n readonly checked = model<boolean>(false);\n\n constructor() {\n // Garde-fou dev : sans `role=\"menuitemcheckbox\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemcheckbox') return;\n console.warn(\n '[ktMenuItemCheckbox] attend `role=\"menuitemcheckbox\"` sur l’hôte, sinon `aria-checked` est inopérant.',\n );\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.checked.update((v) => !v);\n }\n}\n\n/** Source de la valeur sélectionnée d'un groupe radio, exposée aux `[ktMenuItemRadio]` enfants. */\nexport const KT_MENU_RADIO_GROUP = new InjectionToken<KtMenuRadioGroup<unknown>>('KT_MENU_RADIO_GROUP');\n\n/**\n * `[ktMenuRadioGroup]` — coordinateur d'un groupe d'items radio mutuellement exclusifs. À poser sur\n * un conteneur englobant les `[ktMenuItemRadio]` (typiquement un `role=\"group\"`). Détient la valeur\n * sélectionnée ; chaque radio s'y compare pour son `aria-checked` et la met à jour à l'activation.\n *\n * @example\n * ```html\n * <div role=\"group\" ktMenuRadioGroup [(value)]=\"sortBy\" aria-label=\"Trier par\">\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Nom</button>\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'date'\">Date</button>\n * </div>\n * ```\n */\n@Directive({\n selector: '[ktMenuRadioGroup]',\n exportAs: 'ktMenuRadioGroup',\n providers: [{ provide: KT_MENU_RADIO_GROUP, useExisting: KtMenuRadioGroup }],\n})\nexport class KtMenuRadioGroup<V> {\n /**\n * Valeur sélectionnée du groupe, bidirectionnelle : `[(value)]`.\n * @default null\n */\n readonly value = model<V | null>(null);\n}\n\n/**\n * `[ktMenuItemRadio]` — bouton radio de menu. À poser sur un `[ngMenuItem] role=\"menuitemradio\"`\n * dans un `[ktMenuRadioGroup]`. `aria-checked` reflète l'égalité avec la valeur du groupe ;\n * l'activation sélectionne cette valeur.\n *\n * @example\n * ```html\n * <button ngMenuItem ktMenuItem ktMenuItemRadio role=\"menuitemradio\" [value]=\"'name'\">Trier par nom</button>\n * ```\n */\n@Directive({\n selector: '[ktMenuItemRadio]',\n host: {\n ...ACTIVATION_HOST,\n '[attr.aria-checked]': 'checked()',\n },\n})\nexport class KtMenuItemRadio<V> {\n private readonly ariaItem = inject(AriaMenuItem, { self: true, optional: true });\n private readonly group = inject<KtMenuRadioGroup<V>>(KT_MENU_RADIO_GROUP, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Valeur portée par ce radio (comparée à celle du groupe). @default (requis) */\n readonly value = input.required<V>();\n\n protected readonly checked = computed(() => this.group?.value() === this.value());\n\n constructor() {\n // Garde-fou dev : sans [ktMenuRadioGroup] parent, aria-checked reste figé (item muet pour les SR).\n if (isDevMode() && !this.group) {\n console.warn(\n '[ktMenuItemRadio] doit être placé dans un [ktMenuRadioGroup] : ' +\n 'sans groupe, la sélection et `aria-checked` restent inopérants.',\n );\n }\n // Garde-fou dev : sans `role=\"menuitemradio\"`, `aria-checked` est ignoré → item muet pour les SR.\n afterNextRender(() => {\n if (!isDevMode() || this.host.getAttribute('role') === 'menuitemradio') return;\n console.warn('[ktMenuItemRadio] attend `role=\"menuitemradio\"` sur l’hôte, sinon `aria-checked` est inopérant.');\n });\n }\n\n protected activate(event: Event): void {\n if (this.ariaItem?.disabled()) return;\n if (event.type === 'keydown') {\n event.preventDefault();\n }\n this.group?.value.set(this.value());\n }\n}\n","import { KtMenu, KtMenuItem, KtMenuSeparator } from './menu';\nimport { KtMenuTrigger } from './menu-trigger';\nimport { KtMenuItemCheckbox, KtMenuItemRadio, KtMenuRadioGroup } from './menu-toggle';\n\nexport * from './menu';\nexport * from './menu-trigger';\nexport * from './menu-toggle';\n\n/**\n * Import ergonomique de toute la famille menu (couche de THÈME ktortu) en une fois :\n * `imports: [KtMenuImports]` au lieu d'énumérer chaque directive. À composer avec les directives\n * d'`@angular/aria/menu` (`Menu`, `MenuItem`, `MenuTrigger`, `MenuContent`), qui apportent le\n * comportement accessible.\n */\nexport const KtMenuImports = [\n KtMenu,\n KtMenuItem,\n KtMenuSeparator,\n KtMenuTrigger,\n KtMenuItemCheckbox,\n KtMenuItemRadio,\n KtMenuRadioGroup,\n] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["AriaMenuItem","AriaMenuTrigger"],"mappings":"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"}
@@ -303,6 +303,7 @@ class KtSnackbar {
303
303
  /** File FIFO : `queue[0]` est la snackbar affichée dès qu'elle est attachée. */
304
304
  queue = [];
305
305
  active = null;
306
+ isExiting = false;
306
307
  escapeRegistered = false;
307
308
  /**
308
309
  * Ouvre une snackbar affichant `message`. Les `options` priment sur `KT_SNACKBAR_CONFIG`, qui
@@ -352,6 +353,7 @@ class KtSnackbar {
352
353
  this.unregisterEscape();
353
354
  this.active?.overlayRef.dispose();
354
355
  this.active = null;
356
+ this.isExiting = false;
355
357
  this.queue.length = 0;
356
358
  }
357
359
  /** Borne la file (affichée + en attente) : retire les plus ANCIENNES en attente au-delà de `max`. */
@@ -367,7 +369,7 @@ class KtSnackbar {
367
369
  }
368
370
  /** Affiche la tête de file si rien n'est actuellement visible. */
369
371
  showHead() {
370
- if (this.active || this.queue.length === 0)
372
+ if (this.active || this.isExiting || this.queue.length === 0)
371
373
  return;
372
374
  const item = this.queue[0];
373
375
  const positionStrategy = this.overlay.position().global().centerHorizontally();
@@ -400,11 +402,13 @@ class KtSnackbar {
400
402
  if (this.active && this.active.item === item) {
401
403
  const { overlayRef, cmp } = this.active;
402
404
  this.active = null;
405
+ this.isExiting = true;
403
406
  this.queue.splice(index, 1);
404
407
  cmp.instance.playExit(() => {
405
408
  overlayRef.dispose();
409
+ this.isExiting = false;
406
410
  this.showHead();
407
- if (this.queue.length === 0)
411
+ if (this.queue.length === 0 && !this.active)
408
412
  this.unregisterEscape();
409
413
  });
410
414
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-snackbar.mjs","sources":["../../../../projects/ktortu/aaa/snackbar/snackbar-config.ts","../../../../projects/ktortu/aaa/snackbar/snackbar-ref.ts","../../../../projects/ktortu/aaa/snackbar/snackbar-container.ts","../../../../projects/ktortu/aaa/snackbar/snackbar.service.ts","../../../../projects/ktortu/aaa/snackbar/public-api.ts","../../../../projects/ktortu/aaa/snackbar/ktortu-aaa-snackbar.ts"],"sourcesContent":["import { InjectionToken, Provider } from '@angular/core';\n\n/** Politesse de l'annonce au lecteur d'écran (relayée à `LiveAnnouncer`). */\nexport type KtSnackbarPoliteness = 'polite' | 'assertive';\n\n/** Bord d'ancrage de la snackbar dans le viewport. */\nexport type KtSnackbarPosition = 'top' | 'bottom';\n\n/**\n * Régime temporel de la snackbar :\n * - `'auto'` : disparition automatique après `duration`, **mise en pause au survol et au focus**\n * clavier (WCAG 1.4.13 / 2.2.1). Conforme **AA**. C'est le défaut.\n * - `'manual'` : **aucune** minuterie, la snackbar reste jusqu'à fermeture explicite\n * (bouton de fermeture ou `ref.dismiss()`). Conforme **2.2.3 (AAA)** au sens strict.\n *\n * Conformément aux recommandations d'accessibilité (Roselli/Soueidan), la snackbar ne porte **pas\n * d'action interactive** (ex. « Annuler ») : une action auto-disparaissante n'est pas atteignable et\n * une live region n'expose pas ses boutons. Pour un undo, préférez un mécanisme atteignable côté app\n * (Ctrl+Z, bannière persistante).\n */\nexport type KtSnackbarTiming = 'auto' | 'manual';\n\n/**\n * Variante sémantique de la snackbar. Pilote uniquement l'**apparence** (couleur d'accent + icône),\n * via l'attribut `data-variant` et les tokens CSS `--snackbar-*` — donc entièrement gérée par le\n * thème, sans logique TypeScript. Découplée de la `politeness` (une « erreur » n'est pas forcément\n * une urgence assertive). La couleur n'est jamais le seul indice : chaque variante porte une **icône**\n * de forme distincte (WCAG 1.4.1).\n * - `'neutral'` : pastille neutre, sans icône (défaut) ;\n * - `'info' | 'success' | 'warning' | 'error'` : accent + icône dédiés.\n */\nexport type KtSnackbarVariant = 'neutral' | 'info' | 'success' | 'warning' | 'error';\n\n/**\n * Défauts de la snackbar, injectables via `provideKtSnackbar` / `KT_SNACKBAR_CONFIG`.\n * Tous les champs sont surchargeables **par appel** via les options de `KtSnackbar.open()`.\n *\n * Résolution en cascade (convention de la lib) : `option d'open ?? KT_SNACKBAR_CONFIG ?? défaut`.\n */\nexport interface KtSnackbarConfig {\n /**\n * Durée d'affichage en régime `'auto'`. Soit un **nombre fixe** (ms), soit le sentinel\n * **`'reading-time'`** (DÉFAUT) qui **calcule** la durée d'après la longueur du message :\n * `clamp(longueur × readingTimePerChar, readingTimeMin, readingTimeMax)`. Passer un nombre à\n * `open()` force donc une durée fixe pour cet appel.\n *\n * ⚠️ Gardez le message **court** : une snackbar est un message transitoire (pas un paragraphe).\n * Un message long fait grimper la durée jusqu'au plafond `readingTimeMax`.\n * @default 'reading-time'\n */\n duration: number | 'reading-time';\n /** Plancher (ms) de la durée calculée (`'reading-time'`) — laisse le temps de lire un message court. @default 4000 */\n readingTimeMin: number;\n /** Plafond (ms) de la durée calculée (`'reading-time'`) — borne un message long. @default 10000 */\n readingTimeMax: number;\n /** Coefficient de lecture : millisecondes ajoutées par caractère du message. @default 60 (~200 mots/min) */\n readingTimePerChar: number;\n /** Régime temporel (cf. {@link KtSnackbarTiming}). @default 'auto' (AA + pause) */\n timing: KtSnackbarTiming;\n /** Bord d'ancrage dans le viewport. @default 'bottom' */\n position: KtSnackbarPosition;\n /** Politesse de l'annonce lecteur d'écran. @default 'polite' */\n politeness: KtSnackbarPoliteness;\n /** Variante sémantique (apparence seule, cf. {@link KtSnackbarVariant}). @default 'neutral' */\n variant: KtSnackbarVariant;\n /** Affiche un bouton de fermeture (cible 44px, AAA). @default true */\n closable: boolean;\n /** Nom accessible du bouton de fermeture. @default 'Close' (FR fourni en lot L3) */\n closeLabel: string;\n /**\n * Taille maximale de la file FIFO (snackbar affichée + en attente). Au-delà, les **plus anciennes\n * en attente** sont retirées silencieusement. Une seule snackbar est visible à la fois. @default 3\n */\n max: number;\n}\n\n/** Défauts AAA-orientés de la snackbar (anglais neutre). */\nexport const KT_SNACKBAR_DEFAULTS: KtSnackbarConfig = {\n // Stryker disable next-line StringLiteral: équivalent — computeDuration() traite toute valeur non numérique comme reading-time\n duration: 'reading-time',\n readingTimeMin: 4000,\n readingTimeMax: 10000,\n readingTimePerChar: 60,\n timing: 'auto',\n position: 'bottom',\n politeness: 'polite',\n variant: 'neutral',\n closable: true,\n closeLabel: 'Close',\n max: 3,\n};\n\n// Stryker disable next-line StringLiteral: libellé de debug du token d'injection (sans effet runtime)\nexport const KT_SNACKBAR_CONFIG = new InjectionToken<Partial<KtSnackbarConfig>>('KT_SNACKBAR_CONFIG');\n\n/**\n * Options ponctuelles d'ouverture d'une snackbar : un sous-ensemble (toutes facultatives) de la\n * config, prioritaire sur `KT_SNACKBAR_CONFIG` et sur les défauts.\n */\nexport type KtSnackbarOptions = Partial<KtSnackbarConfig>;\n\n/**\n * Fournit des défauts de snackbar pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * // app.config.ts — bascule TOUTE l'app en AAA strict (aucune disparition automatique)\n * providers: [provideKtSnackbar({ timing: 'manual' })]\n * ```\n * @example\n * ```ts\n * providers: [provideKtSnackbar({ duration: 8000, position: 'top' })]\n * ```\n */\nexport function provideKtSnackbar(config: Partial<KtSnackbarConfig>): Provider {\n return { provide: KT_SNACKBAR_CONFIG, useValue: config };\n}\n","import { Observable, Subject } from 'rxjs';\n\n/**\n * Raison de fermeture d'une snackbar, transmise par {@link KtSnackbarRef.afterDismissed}.\n * - `'timeout'` : la minuterie (régime `'auto'`) est arrivée à échéance ;\n * - `'dismiss'` : fermeture explicite (bouton de fermeture, `Échap`, ou `ref.dismiss()`) ;\n * - `'replaced'` : la snackbar a été retirée de la file sans être affichée (file pleine — cf. `max`).\n */\nexport type KtSnackbarDismissReason = 'timeout' | 'dismiss' | 'replaced';\n\n/**\n * Référence d'une snackbar ouverte, renvoyée par `KtSnackbar.open()`. Permet de fermer la\n * snackbar par programmation et d'observer sa fermeture.\n *\n * Le débutant peut l'ignorer (`snackbar.open('…')`) ; le code avancé s'en sert pour piloter la\n * fermeture et réagir à la raison de fin.\n *\n * @example\n * ```ts\n * const ref = snackbar.open('Brouillon enregistré');\n * ref.afterDismissed().subscribe((reason) => console.log(reason)); // 'timeout' | 'dismiss' | 'replaced'\n * // plus tard : ref.dismiss();\n * ```\n */\nexport class KtSnackbarRef {\n private readonly _afterDismissed = new Subject<KtSnackbarDismissReason>();\n private settled = false;\n\n /**\n * @param onDismiss Rappel exécuté une fois à la fermeture (animation de sortie + démontage de\n * l'overlay + avance de la file), fourni par le service.\n * @internal Construit par `KtSnackbar` — n'instanciez pas `KtSnackbarRef` directement.\n */\n constructor(private readonly onDismiss: (reason: KtSnackbarDismissReason) => void) {}\n\n /**\n * Ferme la snackbar (idempotent). Déclenche la sortie/démontage puis émet la raison sur\n * `afterDismissed()`.\n * @param reason Raison de fermeture. @default 'dismiss'\n */\n dismiss(reason: KtSnackbarDismissReason = 'dismiss'): void {\n if (this.settled) return;\n this.settled = true;\n this.onDismiss(reason);\n this._afterDismissed.next(reason);\n this._afterDismissed.complete();\n }\n\n /** Émet une fois (puis complète) à la fermeture, avec la {@link KtSnackbarDismissReason}. */\n afterDismissed(): Observable<KtSnackbarDismissReason> {\n return this._afterDismissed.asObservable();\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n afterNextRender,\n inject,\n signal,\n} from '@angular/core';\n\nimport { KtSnackbarTiming, KtSnackbarVariant } from './snackbar-config';\nimport { KtSnackbarRef } from './snackbar-ref';\n\n/**\n * Données résolues passées au conteneur visuel à l'ouverture (injectées via {@link KT_SNACKBAR_CONTEXT}).\n * Produites par `KtSnackbar` après résolution `option ?? config ?? défaut`.\n */\nexport interface KtSnackbarContext {\n /** Message affiché et annoncé. */\n readonly message: string;\n /** Variante sémantique (apparence : accent + icône). */\n readonly variant: KtSnackbarVariant;\n /** Affiche le bouton de fermeture. */\n readonly closable: boolean;\n /** Nom accessible du bouton de fermeture. */\n readonly closeLabel: string;\n /** Régime temporel (la minuterie n'existe qu'en `'auto'`). */\n readonly timing: KtSnackbarTiming;\n /** Durée (ms) avant disparition automatique en régime `'auto'`. */\n readonly duration: number;\n}\n\n// Stryker disable next-line StringLiteral: libellé de debug du token d'injection (sans effet runtime)\nexport const KT_SNACKBAR_CONTEXT = new InjectionToken<KtSnackbarContext>('KT_SNACKBAR_CONTEXT');\n\n/**\n * Conteneur visuel d'une snackbar. **Volontairement pas une live region** (aucun `role`/`aria-live`\n * sur l'hôte) : l'annonce passe par un canal UNIQUE, le `LiveAnnouncer` du service. On évite ainsi\n * la double-annonce et l'écrasement de politesse observés sur d'autres libs.\n *\n * En régime `'auto'`, la minuterie de disparition se met **en pause au survol et au focus** clavier\n * (WCAG 1.4.13 / 2.2.1) et reprend quand ni le pointeur ni le focus ne sont sur la snackbar.\n * L'icône de variante est **décorative** (`aria-hidden`) : la forme distincte sert d'indice non\n * coloré (WCAG 1.4.1), le sens reste porté par le texte.\n *\n * @internal Monté par `KtSnackbar` via un `ComponentPortal`.\n */\n@Component({\n selector: 'kt-snackbar-container',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'kt-snackbar',\n '[attr.data-variant]': 'context.variant',\n '[class.kt-snackbar--leaving]': 'leaving()',\n '(mouseenter)': 'onPointerEnter()',\n '(mouseleave)': 'onPointerLeave()',\n '(focusin)': 'onFocusEnter()',\n '(focusout)': 'onFocusLeave()',\n },\n template: `\n @if (context.variant !== 'neutral') {\n <span class=\"kt-snackbar__icon\" aria-hidden=\"true\"></span>\n }\n <span class=\"kt-snackbar__message\">{{ context.message }}</span>\n @if (context.closable) {\n <button\n type=\"button\"\n class=\"kt-snackbar__close\"\n [attr.aria-label]=\"context.closeLabel\"\n (click)=\"close()\"\n ></button>\n }\n `,\n})\nexport class KtSnackbarContainer {\n protected readonly context = inject(KT_SNACKBAR_CONTEXT);\n private readonly ref = inject(KtSnackbarRef);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Vrai pendant l'animation de sortie (déclenche la classe `kt-snackbar--leaving`). */\n protected readonly leaving = signal(false);\n\n private timer: ReturnType<typeof setTimeout> | undefined;\n private remaining = this.context.duration;\n private deadline = 0;\n private hovered = false;\n private focused = false;\n\n constructor() {\n if (this.context.timing === 'auto' && isPlatformBrowser(this.platformId)) {\n afterNextRender(() => this.startTimer(this.context.duration));\n }\n inject(DestroyRef).onDestroy(() => this.clearTimer());\n }\n\n protected close(): void {\n this.ref.dismiss('dismiss');\n }\n\n /**\n * Joue l'animation de sortie puis invoque `done` (démontage de l'overlay côté service). Appelé par\n * le service à la fermeture. Sans animation (durée nulle, `prefers-reduced-motion`, ou SSR), `done`\n * est invoqué immédiatement. Filet de sécurité par `setTimeout` si `transitionend` ne se déclenche pas.\n */\n playExit(done: () => void): void {\n this.clearTimer();\n if (!isPlatformBrowser(this.platformId)) {\n done();\n return;\n }\n const durationMs = this.exitDurationMs();\n // Stryker disable all: animation de sortie pilotée par une transition CSS réelle. En jsdom\n // `transitionDuration` est vide → `exitDurationMs()` vaut 0 → ce code n'est jamais atteint.\n // Comportement (jeu de l'animation puis démontage) vérifié en e2e (sortie + reduced-motion).\n if (durationMs <= 0) {\n done();\n return;\n }\n\n this.leaving.set(true);\n let finished = false;\n const finish = (): void => {\n if (finished) return;\n finished = true;\n this.host.removeEventListener('transitionend', onTransitionEnd);\n done();\n };\n const onTransitionEnd = (event: TransitionEvent): void => {\n if (event.target === this.host) finish();\n };\n this.host.addEventListener('transitionend', onTransitionEnd);\n setTimeout(finish, durationMs + 50);\n // Stryker restore all\n }\n\n protected onPointerEnter(): void {\n this.hovered = true;\n this.pauseTimer();\n }\n\n protected onPointerLeave(): void {\n this.hovered = false;\n this.maybeResume();\n }\n\n protected onFocusEnter(): void {\n this.focused = true;\n this.pauseTimer();\n }\n\n protected onFocusLeave(): void {\n this.focused = false;\n this.maybeResume();\n }\n\n private maybeResume(): void {\n if (this.hovered || this.focused) return;\n if (this.context.timing !== 'auto' || this.timer !== undefined || this.leaving()) return;\n if (this.remaining <= 0) {\n this.ref.dismiss('timeout');\n return;\n }\n this.startTimer(this.remaining);\n }\n\n private startTimer(ms: number): void {\n this.deadline = Date.now() + ms;\n this.timer = setTimeout(() => this.ref.dismiss('timeout'), ms);\n }\n\n private pauseTimer(): void {\n if (this.timer === undefined) return;\n this.remaining = Math.max(0, this.deadline - Date.now());\n this.clearTimer();\n }\n\n private clearTimer(): void {\n if (this.timer === undefined) return;\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n /** Durée (ms) de la transition de sortie, lue sur l'hôte (0 si aucune — ex. reduced-motion). */\n private exitDurationMs(): number {\n // Stryker disable all: lecture/parsing de `transition-duration` CSS — en jsdom\n // `getComputedStyle().transitionDuration` est toujours vide (renvoie 0). Le parsing réel\n // (ms vs s, valeurs multiples, NaN) est exercé en e2e ; intestable en environnement jsdom.\n const raw = getComputedStyle(this.host).transitionDuration || '';\n const first = raw.split(',')[0].trim();\n if (!first) return 0;\n const value = parseFloat(first);\n if (Number.isNaN(value)) return 0;\n return first.endsWith('ms') ? value : value * 1000;\n // Stryker restore all\n }\n}\n","import { LiveAnnouncer } from '@angular/cdk/a11y';\nimport { Overlay, OverlayRef } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { ComponentRef, Injectable, Injector, OnDestroy, PLATFORM_ID, inject } from '@angular/core';\n\nimport {\n KT_SNACKBAR_CONFIG,\n KT_SNACKBAR_DEFAULTS,\n KtSnackbarConfig,\n KtSnackbarOptions,\n KtSnackbarPoliteness,\n KtSnackbarPosition,\n} from './snackbar-config';\nimport { KT_SNACKBAR_CONTEXT, KtSnackbarContainer, KtSnackbarContext } from './snackbar-container';\nimport { KtSnackbarRef } from './snackbar-ref';\n\n/** Élément de la file FIFO interne (une seule snackbar visible à la fois). */\ninterface KtSnackbarQueueItem {\n ref: KtSnackbarRef;\n readonly context: KtSnackbarContext;\n readonly position: KtSnackbarPosition;\n readonly politeness: KtSnackbarPoliteness;\n}\n\n/**\n * Service d'ouverture des snackbars `@ktortu/aaa` — feedback **non bloquant** qui confirme une\n * action ou signale un événement transitoire sans interrompre la tâche (n'utilisez PAS de snackbar\n * pour une information critique à acquitter : préférez le dialog).\n *\n * Architecture a11y :\n * - **CDK Overlay** pour le panneau visuel, **CDK LiveAnnouncer** pour l'annonce — **un seul canal**\n * d'annonce (le panneau n'est pas une live region) ;\n * - **le focus n'est jamais déplacé** vers la snackbar (RGAA « message de statut » / WCAG 4.1.3) ;\n * - disparition automatique **en pause au survol et au focus** (régime `'auto'`, défaut AA), ou\n * persistante (`timing: 'manual'`, AAA) ;\n * - **file FIFO** : une seule snackbar visible, les suivantes patientent (live region non saturée).\n * La file est bornée par `max` (défaut 3) ; les messages **identiques** sont fusionnés (coalescing) ;\n * - **Échap** ferme la snackbar affichée (la plus récente).\n *\n * Requiert côté hôte les styles `@angular/cdk/overlay-prebuilt.css` **et**\n * `@angular/cdk/a11y-prebuilt.css` (ce dernier masque l'élément du LiveAnnouncer), en plus de\n * `@ktortu/aaa/snackbar.css`.\n *\n * > [!IMPORTANT]\n * > **Prérequis d'intégration obligatoire :**\n * > Ce service ne fonctionnera pas correctement sans l'importation de ces deux fichiers CSS de l'overlay CDK et d'accessibilité dans votre feuille de styles globale ou votre build.\n *\n * @example\n * ```ts\n * private readonly snackbar = inject(KtSnackbar);\n * this.snackbar.open('Brouillon enregistré'); // disparaît, annonce polie\n * this.snackbar.open('Fichier supprimé', { variant: 'success' }); // variante (couleur + icône)\n * this.snackbar.open('Hors ligne', { timing: 'manual' }); // reste jusqu'à fermeture (AAA)\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class KtSnackbar implements OnDestroy {\n private readonly overlay = inject(Overlay);\n private readonly injector = inject(Injector);\n private readonly liveAnnouncer = inject(LiveAnnouncer);\n private readonly config = inject(KT_SNACKBAR_CONFIG, { optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n private readonly doc = inject(DOCUMENT);\n\n /** File FIFO : `queue[0]` est la snackbar affichée dès qu'elle est attachée. */\n private readonly queue: KtSnackbarQueueItem[] = [];\n private active: { item: KtSnackbarQueueItem; overlayRef: OverlayRef; cmp: ComponentRef<KtSnackbarContainer> } | null =\n null;\n private escapeRegistered = false;\n\n /**\n * Ouvre une snackbar affichant `message`. Les `options` priment sur `KT_SNACKBAR_CONFIG`, qui\n * prime sur les défauts. Renvoie une {@link KtSnackbarRef} (ignorable dans le cas simple).\n *\n * Une seule snackbar est visible : si une autre est affichée, celle-ci patiente en file (FIFO).\n * Un `message` identique à une snackbar déjà affichée ou en attente est **fusionné** : on renvoie\n * alors la référence existante sans rien ré-empiler.\n *\n * > [!IMPORTANT]\n * > Assurez-vous d'avoir importé `@angular/cdk/overlay-prebuilt.css` et `@angular/cdk/a11y-prebuilt.css` dans votre application hôte.\n *\n * @param message Texte affiché et annoncé au lecteur d'écran.\n * @param options Surcharges ponctuelles (durée, régime, position, politesse, variante, fermeture).\n * @returns La référence de la snackbar (existante en cas de fusion).\n */\n open(message: string, options?: KtSnackbarOptions): KtSnackbarRef {\n const resolved = this.resolve(options);\n\n // SSR : aucun overlay côté serveur — on renvoie une référence inerte déjà fermée.\n if (!isPlatformBrowser(this.platformId)) {\n const inert = new KtSnackbarRef(() => {\n /* SSR : aucun overlay à fermer. */\n });\n inert.dismiss('dismiss');\n return inert;\n }\n\n // Coalescing : message identique déjà affiché ou en file → on réutilise la référence existante.\n const existing = this.queue.find((entry) => entry.context.message === message);\n if (existing) return existing.ref;\n\n const context: KtSnackbarContext = {\n message,\n variant: resolved.variant,\n closable: resolved.closable,\n closeLabel: resolved.closeLabel,\n timing: resolved.timing,\n duration: this.computeDuration(message, resolved),\n };\n const item = { context, position: resolved.position, politeness: resolved.politeness } as KtSnackbarQueueItem;\n item.ref = new KtSnackbarRef(() => this.handleDismiss(item));\n\n this.queue.push(item);\n this.enforceMax(resolved.max);\n this.showHead();\n return item.ref;\n }\n\n ngOnDestroy(): void {\n this.unregisterEscape();\n this.active?.overlayRef.dispose();\n this.active = null;\n this.queue.length = 0;\n }\n\n /** Borne la file (affichée + en attente) : retire les plus ANCIENNES en attente au-delà de `max`. */\n private enforceMax(max: number): void {\n const limit = Math.max(1, max);\n while (this.queue.length > limit) {\n const oldestWaiting = this.queue.find((entry) => entry !== this.active?.item);\n if (!oldestWaiting) break;\n // Jamais affichée : la fermeture est routée via handleDismiss (qui la retire de la file).\n oldestWaiting.ref.dismiss('replaced');\n }\n }\n\n /** Affiche la tête de file si rien n'est actuellement visible. */\n private showHead(): void {\n if (this.active || this.queue.length === 0) return;\n const item = this.queue[0];\n\n const positionStrategy = this.overlay.position().global().centerHorizontally();\n if (item.position === 'top') positionStrategy.top('0');\n else positionStrategy.bottom('0');\n\n const overlayRef = this.overlay.create({\n positionStrategy,\n panelClass: ['kt-snackbar-pane', `kt-snackbar-pane--${item.position}`],\n });\n\n const injector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: KtSnackbarRef, useValue: item.ref },\n { provide: KT_SNACKBAR_CONTEXT, useValue: item.context },\n ],\n });\n\n const cmp = overlayRef.attach(new ComponentPortal(KtSnackbarContainer, null, injector));\n\n // Canal d'annonce UNIQUE : le panneau visuel n'est pas une live region.\n void this.liveAnnouncer.announce(item.context.message, item.politeness);\n\n this.active = { item, overlayRef, cmp };\n this.registerEscape();\n }\n\n /** Retire l'élément de la file ; s'il est affiché, joue la sortie puis démonte et avance la file. */\n private handleDismiss(item: KtSnackbarQueueItem): void {\n const index = this.queue.indexOf(item);\n if (index === -1) return;\n\n if (this.active && this.active.item === item) {\n const { overlayRef, cmp } = this.active;\n this.active = null;\n this.queue.splice(index, 1);\n cmp.instance.playExit(() => {\n overlayRef.dispose();\n this.showHead();\n if (this.queue.length === 0) this.unregisterEscape();\n });\n return;\n }\n\n // En attente : aucun visuel à démonter.\n this.queue.splice(index, 1);\n }\n\n private readonly onDocumentKeydown = (event: KeyboardEvent): void => {\n // Échap ferme la plus récente (= la snackbar affichée).\n if (event.key === 'Escape') this.active?.item.ref.dismiss('dismiss');\n };\n\n private registerEscape(): void {\n if (this.escapeRegistered) return;\n this.doc.addEventListener('keydown', this.onDocumentKeydown);\n this.escapeRegistered = true;\n }\n\n private unregisterEscape(): void {\n if (!this.escapeRegistered) return;\n this.doc.removeEventListener('keydown', this.onDocumentKeydown);\n this.escapeRegistered = false;\n }\n\n /**\n * Durée effective (ms) en régime `'auto'`. Un nombre est pris tel quel (durée fixe) ; le sentinel\n * `'reading-time'` calcule `clamp(longueur × perChar, min, max)`.\n */\n private computeDuration(message: string, resolved: KtSnackbarConfig): number {\n const setting = resolved.duration;\n if (typeof setting === 'number') return setting;\n const computed = message.length * resolved.readingTimePerChar;\n return Math.min(resolved.readingTimeMax, Math.max(resolved.readingTimeMin, computed));\n }\n\n /** Résolution en cascade `option ?? KT_SNACKBAR_CONFIG ?? défaut`, champ par champ. */\n private resolve(options?: KtSnackbarOptions): KtSnackbarConfig {\n const config = this.config;\n return {\n duration: options?.duration ?? config?.duration ?? KT_SNACKBAR_DEFAULTS.duration,\n readingTimeMin: options?.readingTimeMin ?? config?.readingTimeMin ?? KT_SNACKBAR_DEFAULTS.readingTimeMin,\n readingTimeMax: options?.readingTimeMax ?? config?.readingTimeMax ?? KT_SNACKBAR_DEFAULTS.readingTimeMax,\n readingTimePerChar:\n options?.readingTimePerChar ?? config?.readingTimePerChar ?? KT_SNACKBAR_DEFAULTS.readingTimePerChar,\n timing: options?.timing ?? config?.timing ?? KT_SNACKBAR_DEFAULTS.timing,\n position: options?.position ?? config?.position ?? KT_SNACKBAR_DEFAULTS.position,\n politeness: options?.politeness ?? config?.politeness ?? KT_SNACKBAR_DEFAULTS.politeness,\n variant: options?.variant ?? config?.variant ?? KT_SNACKBAR_DEFAULTS.variant,\n closable: options?.closable ?? config?.closable ?? KT_SNACKBAR_DEFAULTS.closable,\n // `|| ...` (et non seulement `??`) : un closeLabel vide/blanc produirait un bouton de fermeture\n // SANS nom accessible (WCAG 4.1.2) ; on cascade vers le niveau suivant, défaut neutre en dernier.\n closeLabel: options?.closeLabel?.trim() || config?.closeLabel?.trim() || KT_SNACKBAR_DEFAULTS.closeLabel,\n max: options?.max ?? config?.max ?? KT_SNACKBAR_DEFAULTS.max,\n };\n }\n}\n","export * from './snackbar.service';\nexport * from './snackbar-ref';\nexport * from './snackbar-config';\n// `KtSnackbarContainer` / `KT_SNACKBAR_CONTEXT` / `KtSnackbarContext` sont des artefacts internes\n// (montés/fournis par `KtSnackbar` via `ComponentPortal`) : ils ne sont volontairement PAS exposés.\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-snackbar.mjs","sources":["../../../../projects/ktortu/aaa/snackbar/snackbar-config.ts","../../../../projects/ktortu/aaa/snackbar/snackbar-ref.ts","../../../../projects/ktortu/aaa/snackbar/snackbar-container.ts","../../../../projects/ktortu/aaa/snackbar/snackbar.service.ts","../../../../projects/ktortu/aaa/snackbar/public-api.ts","../../../../projects/ktortu/aaa/snackbar/ktortu-aaa-snackbar.ts"],"sourcesContent":["import { InjectionToken, Provider } from '@angular/core';\n\n/** Politesse de l'annonce au lecteur d'écran (relayée à `LiveAnnouncer`). */\nexport type KtSnackbarPoliteness = 'polite' | 'assertive';\n\n/** Bord d'ancrage de la snackbar dans le viewport. */\nexport type KtSnackbarPosition = 'top' | 'bottom';\n\n/**\n * Régime temporel de la snackbar :\n * - `'auto'` : disparition automatique après `duration`, **mise en pause au survol et au focus**\n * clavier (WCAG 1.4.13 / 2.2.1). Conforme **AA**. C'est le défaut.\n * - `'manual'` : **aucune** minuterie, la snackbar reste jusqu'à fermeture explicite\n * (bouton de fermeture ou `ref.dismiss()`). Conforme **2.2.3 (AAA)** au sens strict.\n *\n * Conformément aux recommandations d'accessibilité (Roselli/Soueidan), la snackbar ne porte **pas\n * d'action interactive** (ex. « Annuler ») : une action auto-disparaissante n'est pas atteignable et\n * une live region n'expose pas ses boutons. Pour un undo, préférez un mécanisme atteignable côté app\n * (Ctrl+Z, bannière persistante).\n */\nexport type KtSnackbarTiming = 'auto' | 'manual';\n\n/**\n * Variante sémantique de la snackbar. Pilote uniquement l'**apparence** (couleur d'accent + icône),\n * via l'attribut `data-variant` et les tokens CSS `--snackbar-*` — donc entièrement gérée par le\n * thème, sans logique TypeScript. Découplée de la `politeness` (une « erreur » n'est pas forcément\n * une urgence assertive). La couleur n'est jamais le seul indice : chaque variante porte une **icône**\n * de forme distincte (WCAG 1.4.1).\n * - `'neutral'` : pastille neutre, sans icône (défaut) ;\n * - `'info' | 'success' | 'warning' | 'error'` : accent + icône dédiés.\n */\nexport type KtSnackbarVariant = 'neutral' | 'info' | 'success' | 'warning' | 'error';\n\n/**\n * Défauts de la snackbar, injectables via `provideKtSnackbar` / `KT_SNACKBAR_CONFIG`.\n * Tous les champs sont surchargeables **par appel** via les options de `KtSnackbar.open()`.\n *\n * Résolution en cascade (convention de la lib) : `option d'open ?? KT_SNACKBAR_CONFIG ?? défaut`.\n */\nexport interface KtSnackbarConfig {\n /**\n * Durée d'affichage en régime `'auto'`. Soit un **nombre fixe** (ms), soit le sentinel\n * **`'reading-time'`** (DÉFAUT) qui **calcule** la durée d'après la longueur du message :\n * `clamp(longueur × readingTimePerChar, readingTimeMin, readingTimeMax)`. Passer un nombre à\n * `open()` force donc une durée fixe pour cet appel.\n *\n * ⚠️ Gardez le message **court** : une snackbar est un message transitoire (pas un paragraphe).\n * Un message long fait grimper la durée jusqu'au plafond `readingTimeMax`.\n * @default 'reading-time'\n */\n duration: number | 'reading-time';\n /** Plancher (ms) de la durée calculée (`'reading-time'`) — laisse le temps de lire un message court. @default 4000 */\n readingTimeMin: number;\n /** Plafond (ms) de la durée calculée (`'reading-time'`) — borne un message long. @default 10000 */\n readingTimeMax: number;\n /** Coefficient de lecture : millisecondes ajoutées par caractère du message. @default 60 (~200 mots/min) */\n readingTimePerChar: number;\n /** Régime temporel (cf. {@link KtSnackbarTiming}). @default 'auto' (AA + pause) */\n timing: KtSnackbarTiming;\n /** Bord d'ancrage dans le viewport. @default 'bottom' */\n position: KtSnackbarPosition;\n /** Politesse de l'annonce lecteur d'écran. @default 'polite' */\n politeness: KtSnackbarPoliteness;\n /** Variante sémantique (apparence seule, cf. {@link KtSnackbarVariant}). @default 'neutral' */\n variant: KtSnackbarVariant;\n /** Affiche un bouton de fermeture (cible 44px, AAA). @default true */\n closable: boolean;\n /** Nom accessible du bouton de fermeture. @default 'Close' (FR fourni en lot L3) */\n closeLabel: string;\n /**\n * Taille maximale de la file FIFO (snackbar affichée + en attente). Au-delà, les **plus anciennes\n * en attente** sont retirées silencieusement. Une seule snackbar est visible à la fois. @default 3\n */\n max: number;\n}\n\n/** Défauts AAA-orientés de la snackbar (anglais neutre). */\nexport const KT_SNACKBAR_DEFAULTS: KtSnackbarConfig = {\n // Stryker disable next-line StringLiteral: équivalent — computeDuration() traite toute valeur non numérique comme reading-time\n duration: 'reading-time',\n readingTimeMin: 4000,\n readingTimeMax: 10000,\n readingTimePerChar: 60,\n timing: 'auto',\n position: 'bottom',\n politeness: 'polite',\n variant: 'neutral',\n closable: true,\n closeLabel: 'Close',\n max: 3,\n};\n\n// Stryker disable next-line StringLiteral: libellé de debug du token d'injection (sans effet runtime)\nexport const KT_SNACKBAR_CONFIG = new InjectionToken<Partial<KtSnackbarConfig>>('KT_SNACKBAR_CONFIG');\n\n/**\n * Options ponctuelles d'ouverture d'une snackbar : un sous-ensemble (toutes facultatives) de la\n * config, prioritaire sur `KT_SNACKBAR_CONFIG` et sur les défauts.\n */\nexport type KtSnackbarOptions = Partial<KtSnackbarConfig>;\n\n/**\n * Fournit des défauts de snackbar pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * // app.config.ts — bascule TOUTE l'app en AAA strict (aucune disparition automatique)\n * providers: [provideKtSnackbar({ timing: 'manual' })]\n * ```\n * @example\n * ```ts\n * providers: [provideKtSnackbar({ duration: 8000, position: 'top' })]\n * ```\n */\nexport function provideKtSnackbar(config: Partial<KtSnackbarConfig>): Provider {\n return { provide: KT_SNACKBAR_CONFIG, useValue: config };\n}\n","import { Observable, Subject } from 'rxjs';\n\n/**\n * Raison de fermeture d'une snackbar, transmise par {@link KtSnackbarRef.afterDismissed}.\n * - `'timeout'` : la minuterie (régime `'auto'`) est arrivée à échéance ;\n * - `'dismiss'` : fermeture explicite (bouton de fermeture, `Échap`, ou `ref.dismiss()`) ;\n * - `'replaced'` : la snackbar a été retirée de la file sans être affichée (file pleine — cf. `max`).\n */\nexport type KtSnackbarDismissReason = 'timeout' | 'dismiss' | 'replaced';\n\n/**\n * Référence d'une snackbar ouverte, renvoyée par `KtSnackbar.open()`. Permet de fermer la\n * snackbar par programmation et d'observer sa fermeture.\n *\n * Le débutant peut l'ignorer (`snackbar.open('…')`) ; le code avancé s'en sert pour piloter la\n * fermeture et réagir à la raison de fin.\n *\n * @example\n * ```ts\n * const ref = snackbar.open('Brouillon enregistré');\n * ref.afterDismissed().subscribe((reason) => console.log(reason)); // 'timeout' | 'dismiss' | 'replaced'\n * // plus tard : ref.dismiss();\n * ```\n */\nexport class KtSnackbarRef {\n private readonly _afterDismissed = new Subject<KtSnackbarDismissReason>();\n private settled = false;\n\n /**\n * @param onDismiss Rappel exécuté une fois à la fermeture (animation de sortie + démontage de\n * l'overlay + avance de la file), fourni par le service.\n * @internal Construit par `KtSnackbar` — n'instanciez pas `KtSnackbarRef` directement.\n */\n constructor(private readonly onDismiss: (reason: KtSnackbarDismissReason) => void) {}\n\n /**\n * Ferme la snackbar (idempotent). Déclenche la sortie/démontage puis émet la raison sur\n * `afterDismissed()`.\n * @param reason Raison de fermeture. @default 'dismiss'\n */\n dismiss(reason: KtSnackbarDismissReason = 'dismiss'): void {\n if (this.settled) return;\n this.settled = true;\n this.onDismiss(reason);\n this._afterDismissed.next(reason);\n this._afterDismissed.complete();\n }\n\n /** Émet une fois (puis complète) à la fermeture, avec la {@link KtSnackbarDismissReason}. */\n afterDismissed(): Observable<KtSnackbarDismissReason> {\n return this._afterDismissed.asObservable();\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n afterNextRender,\n inject,\n signal,\n} from '@angular/core';\n\nimport { KtSnackbarTiming, KtSnackbarVariant } from './snackbar-config';\nimport { KtSnackbarRef } from './snackbar-ref';\n\n/**\n * Données résolues passées au conteneur visuel à l'ouverture (injectées via {@link KT_SNACKBAR_CONTEXT}).\n * Produites par `KtSnackbar` après résolution `option ?? config ?? défaut`.\n */\nexport interface KtSnackbarContext {\n /** Message affiché et annoncé. */\n readonly message: string;\n /** Variante sémantique (apparence : accent + icône). */\n readonly variant: KtSnackbarVariant;\n /** Affiche le bouton de fermeture. */\n readonly closable: boolean;\n /** Nom accessible du bouton de fermeture. */\n readonly closeLabel: string;\n /** Régime temporel (la minuterie n'existe qu'en `'auto'`). */\n readonly timing: KtSnackbarTiming;\n /** Durée (ms) avant disparition automatique en régime `'auto'`. */\n readonly duration: number;\n}\n\n// Stryker disable next-line StringLiteral: libellé de debug du token d'injection (sans effet runtime)\nexport const KT_SNACKBAR_CONTEXT = new InjectionToken<KtSnackbarContext>('KT_SNACKBAR_CONTEXT');\n\n/**\n * Conteneur visuel d'une snackbar. **Volontairement pas une live region** (aucun `role`/`aria-live`\n * sur l'hôte) : l'annonce passe par un canal UNIQUE, le `LiveAnnouncer` du service. On évite ainsi\n * la double-annonce et l'écrasement de politesse observés sur d'autres libs.\n *\n * En régime `'auto'`, la minuterie de disparition se met **en pause au survol et au focus** clavier\n * (WCAG 1.4.13 / 2.2.1) et reprend quand ni le pointeur ni le focus ne sont sur la snackbar.\n * L'icône de variante est **décorative** (`aria-hidden`) : la forme distincte sert d'indice non\n * coloré (WCAG 1.4.1), le sens reste porté par le texte.\n *\n * @internal Monté par `KtSnackbar` via un `ComponentPortal`.\n */\n@Component({\n selector: 'kt-snackbar-container',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n class: 'kt-snackbar',\n '[attr.data-variant]': 'context.variant',\n '[class.kt-snackbar--leaving]': 'leaving()',\n '(mouseenter)': 'onPointerEnter()',\n '(mouseleave)': 'onPointerLeave()',\n '(focusin)': 'onFocusEnter()',\n '(focusout)': 'onFocusLeave()',\n },\n template: `\n @if (context.variant !== 'neutral') {\n <span class=\"kt-snackbar__icon\" aria-hidden=\"true\"></span>\n }\n <span class=\"kt-snackbar__message\">{{ context.message }}</span>\n @if (context.closable) {\n <button\n type=\"button\"\n class=\"kt-snackbar__close\"\n [attr.aria-label]=\"context.closeLabel\"\n (click)=\"close()\"\n ></button>\n }\n `,\n})\nexport class KtSnackbarContainer {\n protected readonly context = inject(KT_SNACKBAR_CONTEXT);\n private readonly ref = inject(KtSnackbarRef);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n\n /** Vrai pendant l'animation de sortie (déclenche la classe `kt-snackbar--leaving`). */\n protected readonly leaving = signal(false);\n\n private timer: ReturnType<typeof setTimeout> | undefined;\n private remaining = this.context.duration;\n private deadline = 0;\n private hovered = false;\n private focused = false;\n\n constructor() {\n if (this.context.timing === 'auto' && isPlatformBrowser(this.platformId)) {\n afterNextRender(() => this.startTimer(this.context.duration));\n }\n inject(DestroyRef).onDestroy(() => this.clearTimer());\n }\n\n protected close(): void {\n this.ref.dismiss('dismiss');\n }\n\n /**\n * Joue l'animation de sortie puis invoque `done` (démontage de l'overlay côté service). Appelé par\n * le service à la fermeture. Sans animation (durée nulle, `prefers-reduced-motion`, ou SSR), `done`\n * est invoqué immédiatement. Filet de sécurité par `setTimeout` si `transitionend` ne se déclenche pas.\n */\n playExit(done: () => void): void {\n this.clearTimer();\n if (!isPlatformBrowser(this.platformId)) {\n done();\n return;\n }\n const durationMs = this.exitDurationMs();\n // Stryker disable all: animation de sortie pilotée par une transition CSS réelle. En jsdom\n // `transitionDuration` est vide → `exitDurationMs()` vaut 0 → ce code n'est jamais atteint.\n // Comportement (jeu de l'animation puis démontage) vérifié en e2e (sortie + reduced-motion).\n if (durationMs <= 0) {\n done();\n return;\n }\n\n this.leaving.set(true);\n let finished = false;\n const finish = (): void => {\n if (finished) return;\n finished = true;\n this.host.removeEventListener('transitionend', onTransitionEnd);\n done();\n };\n const onTransitionEnd = (event: TransitionEvent): void => {\n if (event.target === this.host) finish();\n };\n this.host.addEventListener('transitionend', onTransitionEnd);\n setTimeout(finish, durationMs + 50);\n // Stryker restore all\n }\n\n protected onPointerEnter(): void {\n this.hovered = true;\n this.pauseTimer();\n }\n\n protected onPointerLeave(): void {\n this.hovered = false;\n this.maybeResume();\n }\n\n protected onFocusEnter(): void {\n this.focused = true;\n this.pauseTimer();\n }\n\n protected onFocusLeave(): void {\n this.focused = false;\n this.maybeResume();\n }\n\n private maybeResume(): void {\n if (this.hovered || this.focused) return;\n if (this.context.timing !== 'auto' || this.timer !== undefined || this.leaving()) return;\n if (this.remaining <= 0) {\n this.ref.dismiss('timeout');\n return;\n }\n this.startTimer(this.remaining);\n }\n\n private startTimer(ms: number): void {\n this.deadline = Date.now() + ms;\n this.timer = setTimeout(() => this.ref.dismiss('timeout'), ms);\n }\n\n private pauseTimer(): void {\n if (this.timer === undefined) return;\n this.remaining = Math.max(0, this.deadline - Date.now());\n this.clearTimer();\n }\n\n private clearTimer(): void {\n if (this.timer === undefined) return;\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n /** Durée (ms) de la transition de sortie, lue sur l'hôte (0 si aucune — ex. reduced-motion). */\n private exitDurationMs(): number {\n // Stryker disable all: lecture/parsing de `transition-duration` CSS — en jsdom\n // `getComputedStyle().transitionDuration` est toujours vide (renvoie 0). Le parsing réel\n // (ms vs s, valeurs multiples, NaN) est exercé en e2e ; intestable en environnement jsdom.\n const raw = getComputedStyle(this.host).transitionDuration || '';\n const first = raw.split(',')[0].trim();\n if (!first) return 0;\n const value = parseFloat(first);\n if (Number.isNaN(value)) return 0;\n return first.endsWith('ms') ? value : value * 1000;\n // Stryker restore all\n }\n}\n","import { LiveAnnouncer } from '@angular/cdk/a11y';\nimport { Overlay, OverlayRef } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { ComponentRef, Injectable, Injector, OnDestroy, PLATFORM_ID, inject } from '@angular/core';\n\nimport {\n KT_SNACKBAR_CONFIG,\n KT_SNACKBAR_DEFAULTS,\n KtSnackbarConfig,\n KtSnackbarOptions,\n KtSnackbarPoliteness,\n KtSnackbarPosition,\n} from './snackbar-config';\nimport { KT_SNACKBAR_CONTEXT, KtSnackbarContainer, KtSnackbarContext } from './snackbar-container';\nimport { KtSnackbarRef } from './snackbar-ref';\n\n/** Élément de la file FIFO interne (une seule snackbar visible à la fois). */\ninterface KtSnackbarQueueItem {\n ref: KtSnackbarRef;\n readonly context: KtSnackbarContext;\n readonly position: KtSnackbarPosition;\n readonly politeness: KtSnackbarPoliteness;\n}\n\n/**\n * Service d'ouverture des snackbars `@ktortu/aaa` — feedback **non bloquant** qui confirme une\n * action ou signale un événement transitoire sans interrompre la tâche (n'utilisez PAS de snackbar\n * pour une information critique à acquitter : préférez le dialog).\n *\n * Architecture a11y :\n * - **CDK Overlay** pour le panneau visuel, **CDK LiveAnnouncer** pour l'annonce — **un seul canal**\n * d'annonce (le panneau n'est pas une live region) ;\n * - **le focus n'est jamais déplacé** vers la snackbar (RGAA « message de statut » / WCAG 4.1.3) ;\n * - disparition automatique **en pause au survol et au focus** (régime `'auto'`, défaut AA), ou\n * persistante (`timing: 'manual'`, AAA) ;\n * - **file FIFO** : une seule snackbar visible, les suivantes patientent (live region non saturée).\n * La file est bornée par `max` (défaut 3) ; les messages **identiques** sont fusionnés (coalescing) ;\n * - **Échap** ferme la snackbar affichée (la plus récente).\n *\n * Requiert côté hôte les styles `@angular/cdk/overlay-prebuilt.css` **et**\n * `@angular/cdk/a11y-prebuilt.css` (ce dernier masque l'élément du LiveAnnouncer), en plus de\n * `@ktortu/aaa/snackbar.css`.\n *\n * > [!IMPORTANT]\n * > **Prérequis d'intégration obligatoire :**\n * > Ce service ne fonctionnera pas correctement sans l'importation de ces deux fichiers CSS de l'overlay CDK et d'accessibilité dans votre feuille de styles globale ou votre build.\n *\n * @example\n * ```ts\n * private readonly snackbar = inject(KtSnackbar);\n * this.snackbar.open('Brouillon enregistré'); // disparaît, annonce polie\n * this.snackbar.open('Fichier supprimé', { variant: 'success' }); // variante (couleur + icône)\n * this.snackbar.open('Hors ligne', { timing: 'manual' }); // reste jusqu'à fermeture (AAA)\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class KtSnackbar implements OnDestroy {\n private readonly overlay = inject(Overlay);\n private readonly injector = inject(Injector);\n private readonly liveAnnouncer = inject(LiveAnnouncer);\n private readonly config = inject(KT_SNACKBAR_CONFIG, { optional: true });\n private readonly platformId = inject(PLATFORM_ID);\n private readonly doc = inject(DOCUMENT);\n\n /** File FIFO : `queue[0]` est la snackbar affichée dès qu'elle est attachée. */\n private readonly queue: KtSnackbarQueueItem[] = [];\n private active: { item: KtSnackbarQueueItem; overlayRef: OverlayRef; cmp: ComponentRef<KtSnackbarContainer> } | null =\n null;\n private isExiting = false;\n private escapeRegistered = false;\n\n /**\n * Ouvre une snackbar affichant `message`. Les `options` priment sur `KT_SNACKBAR_CONFIG`, qui\n * prime sur les défauts. Renvoie une {@link KtSnackbarRef} (ignorable dans le cas simple).\n *\n * Une seule snackbar est visible : si une autre est affichée, celle-ci patiente en file (FIFO).\n * Un `message` identique à une snackbar déjà affichée ou en attente est **fusionné** : on renvoie\n * alors la référence existante sans rien ré-empiler.\n *\n * > [!IMPORTANT]\n * > Assurez-vous d'avoir importé `@angular/cdk/overlay-prebuilt.css` et `@angular/cdk/a11y-prebuilt.css` dans votre application hôte.\n *\n * @param message Texte affiché et annoncé au lecteur d'écran.\n * @param options Surcharges ponctuelles (durée, régime, position, politesse, variante, fermeture).\n * @returns La référence de la snackbar (existante en cas de fusion).\n */\n open(message: string, options?: KtSnackbarOptions): KtSnackbarRef {\n const resolved = this.resolve(options);\n\n // SSR : aucun overlay côté serveur — on renvoie une référence inerte déjà fermée.\n if (!isPlatformBrowser(this.platformId)) {\n const inert = new KtSnackbarRef(() => {\n /* SSR : aucun overlay à fermer. */\n });\n inert.dismiss('dismiss');\n return inert;\n }\n\n // Coalescing : message identique déjà affiché ou en file → on réutilise la référence existante.\n const existing = this.queue.find((entry) => entry.context.message === message);\n if (existing) return existing.ref;\n\n const context: KtSnackbarContext = {\n message,\n variant: resolved.variant,\n closable: resolved.closable,\n closeLabel: resolved.closeLabel,\n timing: resolved.timing,\n duration: this.computeDuration(message, resolved),\n };\n const item = { context, position: resolved.position, politeness: resolved.politeness } as KtSnackbarQueueItem;\n item.ref = new KtSnackbarRef(() => this.handleDismiss(item));\n\n this.queue.push(item);\n this.enforceMax(resolved.max);\n this.showHead();\n return item.ref;\n }\n\n ngOnDestroy(): void {\n this.unregisterEscape();\n this.active?.overlayRef.dispose();\n this.active = null;\n this.isExiting = false;\n this.queue.length = 0;\n }\n\n /** Borne la file (affichée + en attente) : retire les plus ANCIENNES en attente au-delà de `max`. */\n private enforceMax(max: number): void {\n const limit = Math.max(1, max);\n while (this.queue.length > limit) {\n const oldestWaiting = this.queue.find((entry) => entry !== this.active?.item);\n if (!oldestWaiting) break;\n // Jamais affichée : la fermeture est routée via handleDismiss (qui la retire de la file).\n oldestWaiting.ref.dismiss('replaced');\n }\n }\n\n /** Affiche la tête de file si rien n'est actuellement visible. */\n private showHead(): void {\n if (this.active || this.isExiting || this.queue.length === 0) return;\n const item = this.queue[0];\n\n const positionStrategy = this.overlay.position().global().centerHorizontally();\n if (item.position === 'top') positionStrategy.top('0');\n else positionStrategy.bottom('0');\n\n const overlayRef = this.overlay.create({\n positionStrategy,\n panelClass: ['kt-snackbar-pane', `kt-snackbar-pane--${item.position}`],\n });\n\n const injector = Injector.create({\n parent: this.injector,\n providers: [\n { provide: KtSnackbarRef, useValue: item.ref },\n { provide: KT_SNACKBAR_CONTEXT, useValue: item.context },\n ],\n });\n\n const cmp = overlayRef.attach(new ComponentPortal(KtSnackbarContainer, null, injector));\n\n // Canal d'annonce UNIQUE : le panneau visuel n'est pas une live region.\n void this.liveAnnouncer.announce(item.context.message, item.politeness);\n\n this.active = { item, overlayRef, cmp };\n this.registerEscape();\n }\n\n /** Retire l'élément de la file ; s'il est affiché, joue la sortie puis démonte et avance la file. */\n private handleDismiss(item: KtSnackbarQueueItem): void {\n const index = this.queue.indexOf(item);\n if (index === -1) return;\n\n if (this.active && this.active.item === item) {\n const { overlayRef, cmp } = this.active;\n this.active = null;\n this.isExiting = true;\n this.queue.splice(index, 1);\n cmp.instance.playExit(() => {\n overlayRef.dispose();\n this.isExiting = false;\n this.showHead();\n if (this.queue.length === 0 && !this.active) this.unregisterEscape();\n });\n return;\n }\n\n // En attente : aucun visuel à démonter.\n this.queue.splice(index, 1);\n }\n\n private readonly onDocumentKeydown = (event: KeyboardEvent): void => {\n // Échap ferme la plus récente (= la snackbar affichée).\n if (event.key === 'Escape') this.active?.item.ref.dismiss('dismiss');\n };\n\n private registerEscape(): void {\n if (this.escapeRegistered) return;\n this.doc.addEventListener('keydown', this.onDocumentKeydown);\n this.escapeRegistered = true;\n }\n\n private unregisterEscape(): void {\n if (!this.escapeRegistered) return;\n this.doc.removeEventListener('keydown', this.onDocumentKeydown);\n this.escapeRegistered = false;\n }\n\n /**\n * Durée effective (ms) en régime `'auto'`. Un nombre est pris tel quel (durée fixe) ; le sentinel\n * `'reading-time'` calcule `clamp(longueur × perChar, min, max)`.\n */\n private computeDuration(message: string, resolved: KtSnackbarConfig): number {\n const setting = resolved.duration;\n if (typeof setting === 'number') return setting;\n const computed = message.length * resolved.readingTimePerChar;\n return Math.min(resolved.readingTimeMax, Math.max(resolved.readingTimeMin, computed));\n }\n\n /** Résolution en cascade `option ?? KT_SNACKBAR_CONFIG ?? défaut`, champ par champ. */\n private resolve(options?: KtSnackbarOptions): KtSnackbarConfig {\n const config = this.config;\n return {\n duration: options?.duration ?? config?.duration ?? KT_SNACKBAR_DEFAULTS.duration,\n readingTimeMin: options?.readingTimeMin ?? config?.readingTimeMin ?? KT_SNACKBAR_DEFAULTS.readingTimeMin,\n readingTimeMax: options?.readingTimeMax ?? config?.readingTimeMax ?? KT_SNACKBAR_DEFAULTS.readingTimeMax,\n readingTimePerChar:\n options?.readingTimePerChar ?? config?.readingTimePerChar ?? KT_SNACKBAR_DEFAULTS.readingTimePerChar,\n timing: options?.timing ?? config?.timing ?? KT_SNACKBAR_DEFAULTS.timing,\n position: options?.position ?? config?.position ?? KT_SNACKBAR_DEFAULTS.position,\n politeness: options?.politeness ?? config?.politeness ?? KT_SNACKBAR_DEFAULTS.politeness,\n variant: options?.variant ?? config?.variant ?? KT_SNACKBAR_DEFAULTS.variant,\n closable: options?.closable ?? config?.closable ?? KT_SNACKBAR_DEFAULTS.closable,\n // `|| ...` (et non seulement `??`) : un closeLabel vide/blanc produirait un bouton de fermeture\n // SANS nom accessible (WCAG 4.1.2) ; on cascade vers le niveau suivant, défaut neutre en dernier.\n closeLabel: options?.closeLabel?.trim() || config?.closeLabel?.trim() || KT_SNACKBAR_DEFAULTS.closeLabel,\n max: options?.max ?? config?.max ?? KT_SNACKBAR_DEFAULTS.max,\n };\n }\n}\n","export * from './snackbar.service';\nexport * from './snackbar-ref';\nexport * from './snackbar-config';\n// `KtSnackbarContainer` / `KT_SNACKBAR_CONTEXT` / `KtSnackbarContext` sont des artefacts internes\n// (montés/fournis par `KtSnackbar` via `ComponentPortal`) : ils ne sont volontairement PAS exposés.\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -154,6 +154,30 @@ class KtTabScroller {
154
154
  if (!selected)
155
155
  return null;
156
156
  const container = this.list;
157
+ const selectedRect = selected.getBoundingClientRect();
158
+ const containerRect = container.getBoundingClientRect();
159
+ // En environnement réel de rendu (navigateur), getBoundingClientRect est géométriquement
160
+ // universel (indépendant du signe de scrollLeft, de RTL/LTR et des arrondis subpixel).
161
+ // En jsdom (tests unitaires sans layout), les rectangles sont à 0 -> repli sur offsetLeft/Top.
162
+ const hasLayout = selectedRect.width > 0 || selectedRect.height > 0 || selectedRect.left !== 0 || selectedRect.top !== 0;
163
+ if (hasLayout) {
164
+ if (this.isVertical()) {
165
+ if (selectedRect.top < containerRect.top) {
166
+ return { axis: 'top', value: container.scrollTop + (selectedRect.top - containerRect.top) };
167
+ }
168
+ if (selectedRect.bottom > containerRect.bottom) {
169
+ return { axis: 'top', value: container.scrollTop + (selectedRect.bottom - containerRect.bottom) };
170
+ }
171
+ return null;
172
+ }
173
+ if (selectedRect.left < containerRect.left) {
174
+ return { axis: 'left', value: container.scrollLeft + (selectedRect.left - containerRect.left) };
175
+ }
176
+ if (selectedRect.right > containerRect.right) {
177
+ return { axis: 'left', value: container.scrollLeft + (selectedRect.right - containerRect.right) };
178
+ }
179
+ return null;
180
+ }
157
181
  if (this.isVertical()) {
158
182
  const selectedTop = selected.offsetTop;
159
183
  const selectedBottom = selectedTop + selected.offsetHeight;
@@ -165,6 +189,17 @@ class KtTabScroller {
165
189
  return { axis: 'top', value: selectedBottom - container.clientHeight };
166
190
  return null;
167
191
  }
192
+ if (this.isRtl()) {
193
+ const selectedStart = container.scrollWidth - (selected.offsetLeft + selected.offsetWidth);
194
+ const selectedEnd = container.scrollWidth - selected.offsetLeft;
195
+ const containerStart = -container.scrollLeft;
196
+ const containerEnd = containerStart + container.clientWidth;
197
+ if (selectedStart < containerStart)
198
+ return { axis: 'left', value: -selectedStart };
199
+ if (selectedEnd > containerEnd)
200
+ return { axis: 'left', value: -(selectedEnd - container.clientWidth) };
201
+ return null;
202
+ }
168
203
  const selectedLeft = selected.offsetLeft;
169
204
  const selectedRight = selectedLeft + selected.offsetWidth;
170
205
  const containerLeft = container.scrollLeft;
@@ -281,7 +316,7 @@ class KtTabScrollerPager {
281
316
  [disabled]="!scroller().canScrollEnd()"
282
317
  (click)="scroller().scrollByPage('end')"
283
318
  ></button>
284
- `, isInline: true, dependencies: [{ kind: "directive", type: KtButton, selector: "button[ktButton], a[ktButton]", inputs: ["mode", "color", "size", "fullWidth", "iconOnly", "ariaLabel", "type", "loading", "icon", "iconPosition", "disabled", "disabledInteractive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
319
+ `, isInline: true, dependencies: [{ kind: "directive", type: KtButton, selector: "button[ktButton], a[ktButton]", inputs: ["mode", "color", "size", "fullWidth", "iconOnly", "collapseCompact", "ariaLabel", "type", "loading", "icon", "compactIcon", "iconPosition", "disabled", "disabledInteractive"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
285
320
  }
286
321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtTabScrollerPager, decorators: [{
287
322
  type: Component,