@ktortu/aaa 0.1.0-beta.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +27 -9
  2. package/cdk/styles/tabs.css +100 -21
  3. package/fesm2022/ktortu-aaa-button.mjs +18 -11
  4. package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
  5. package/fesm2022/ktortu-aaa-card.mjs +29 -4
  6. package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
  7. package/fesm2022/ktortu-aaa-cdk.mjs +59 -15
  8. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
  9. package/fesm2022/ktortu-aaa-dialog.mjs +134 -24
  10. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
  11. package/fesm2022/ktortu-aaa-forms.mjs +748 -294
  12. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
  13. package/fesm2022/ktortu-aaa-i18n.mjs +139 -0
  14. package/fesm2022/ktortu-aaa-i18n.mjs.map +1 -0
  15. package/fesm2022/ktortu-aaa-menu.mjs +38 -13
  16. package/fesm2022/ktortu-aaa-menu.mjs.map +1 -1
  17. package/fesm2022/ktortu-aaa-snackbar.mjs +465 -0
  18. package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -0
  19. package/fesm2022/ktortu-aaa-tabs.mjs +319 -42
  20. package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -1
  21. package/fesm2022/ktortu-aaa-tooltip.mjs +3 -2
  22. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
  23. package/fesm2022/ktortu-aaa.mjs +2 -0
  24. package/fesm2022/ktortu-aaa.mjs.map +1 -1
  25. package/forms/checkbox/checkbox-group.css +0 -8
  26. package/forms/chips/chip-list.css +5 -0
  27. package/forms/radio/radio-group.css +0 -8
  28. package/forms/styles/field-box.css +152 -2
  29. package/forms/styles/field-pending.css +46 -0
  30. package/forms/styles/select-panel.css +4 -0
  31. package/forms/styles/tokens.css +3 -0
  32. package/menu/menu.css +8 -4
  33. package/package.json +9 -1
  34. package/snackbar/snackbar-tokens.css +53 -0
  35. package/snackbar/snackbar.css +175 -0
  36. package/styles/forms.css +1 -0
  37. package/styles/snackbar.css +9 -0
  38. package/styles/styles.css +1 -0
  39. package/types/ktortu-aaa-button.d.ts +22 -8
  40. package/types/ktortu-aaa-card.d.ts +24 -4
  41. package/types/ktortu-aaa-cdk.d.ts +38 -0
  42. package/types/ktortu-aaa-dialog.d.ts +129 -31
  43. package/types/ktortu-aaa-forms.d.ts +503 -160
  44. package/types/ktortu-aaa-i18n.d.ts +77 -0
  45. package/types/ktortu-aaa-menu.d.ts +15 -8
  46. package/types/ktortu-aaa-snackbar.d.ts +275 -0
  47. package/types/ktortu-aaa-tabs.d.ts +130 -13
  48. package/types/ktortu-aaa-tooltip.d.ts +5 -0
  49. package/types/ktortu-aaa.d.ts +2 -0
@@ -12,19 +12,31 @@ import { map } from 'rxjs';
12
12
  * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne
13
13
  * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`
14
14
  * de `KtViewport`) — la primitive est volontairement agnostique.
15
+ *
16
+ * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.
17
+ * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).
18
+ * @example
19
+ * ```ts
20
+ * const drag = createKtSheetDrag({
21
+ * pane: () => paneEl(),
22
+ * onDismiss: () => dialogRef.close(),
23
+ * draggingClass: 'kt-sheet--dragging',
24
+ * });
25
+ * // <div class="handle" (pointerdown)="drag.start($event)"></div>
26
+ * ```
15
27
  */
16
28
  function createKtSheetDrag(opts) {
17
29
  const threshold = opts.threshold ?? 0.25;
18
30
  let startY = 0;
19
31
  let pane = null;
20
32
  let handle = null;
33
+ let ownerDoc = null;
21
34
  const cleanupListeners = () => {
22
- if (!handle)
35
+ if (!ownerDoc)
23
36
  return;
24
- handle.removeEventListener('pointermove', onMove);
25
- handle.removeEventListener('pointerup', onEnd);
26
- handle.removeEventListener('pointercancel', onEnd);
27
- handle.removeEventListener('lostpointercapture', onEnd);
37
+ ownerDoc.removeEventListener('pointermove', onMove);
38
+ ownerDoc.removeEventListener('pointerup', onEnd);
39
+ ownerDoc.removeEventListener('pointercancel', onEnd);
28
40
  };
29
41
  const onMove = (event) => {
30
42
  if (!pane)
@@ -36,17 +48,25 @@ function createKtSheetDrag(opts) {
36
48
  if (!pane || !handle)
37
49
  return;
38
50
  cleanupListeners();
39
- pane.classList.remove(opts.draggingClass);
40
51
  const dragged = event.clientY - startY;
41
- if (dragged > pane.getBoundingClientRect().height * threshold) {
42
- pane.style.translate = '0 100%'; // continue le glissement jusqu'en bas, puis ferme
43
- opts.onDismiss();
52
+ const dismiss = dragged > pane.getBoundingClientRect().height * threshold;
53
+ // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.
54
+ const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
55
+ if (reduceMotion) {
56
+ // draggingClass (transition coupée) encore présente : la translation finale est instantanée,
57
+ // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).
58
+ pane.style.translate = dismiss ? '0 100%' : '';
59
+ pane.classList.remove(opts.draggingClass);
44
60
  }
45
61
  else {
46
- pane.style.translate = ''; // snap-back : la transition CSS ramène à 0
62
+ pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)
63
+ pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0
47
64
  }
65
+ if (dismiss)
66
+ opts.onDismiss();
48
67
  pane = null;
49
68
  handle = null;
69
+ ownerDoc = null;
50
70
  };
51
71
  return {
52
72
  start(event) {
@@ -56,21 +76,23 @@ function createKtSheetDrag(opts) {
56
76
  if (!el)
57
77
  return;
58
78
  handle = event.currentTarget;
79
+ ownerDoc = handle.ownerDocument;
59
80
  // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).
60
81
  event.preventDefault();
61
- handle.setPointerCapture(event.pointerId);
62
82
  startY = event.clientY;
63
83
  pane = el;
64
84
  pane.classList.add(opts.draggingClass);
65
- handle.addEventListener('pointermove', onMove);
66
- handle.addEventListener('pointerup', onEnd);
67
- handle.addEventListener('pointercancel', onEnd);
68
- handle.addEventListener('lostpointercapture', onEnd);
85
+ // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une
86
+ // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.
87
+ ownerDoc.addEventListener('pointermove', onMove);
88
+ ownerDoc.addEventListener('pointerup', onEnd);
89
+ ownerDoc.addEventListener('pointercancel', onEnd);
69
90
  },
70
91
  destroy() {
71
92
  cleanupListeners();
72
93
  pane = null;
73
94
  handle = null;
95
+ ownerDoc = null;
74
96
  },
75
97
  };
76
98
  }
@@ -84,6 +106,8 @@ const KT_BREAKPOINTS = new InjectionToken('KT_BREAKPOINTS', {
84
106
  });
85
107
  /**
86
108
  * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.
109
+ * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.
110
+ * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.
87
111
  * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })
88
112
  * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })
89
113
  */
@@ -106,6 +130,9 @@ function join(...parts) {
106
130
  * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris
107
131
  * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le
108
132
  * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.
133
+ * @param bp Seuils responsive à convertir (nombres et/ou chaînes).
134
+ * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).
135
+ * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)
109
136
  */
110
137
  function resolveKtBreakpointMedia(bp) {
111
138
  const { mobile, tablet, desktop } = bp;
@@ -154,9 +181,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
154
181
  args: [{ providedIn: 'root' }]
155
182
  }] });
156
183
 
184
+ /**
185
+ * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,
186
+ * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const idGen = inject(KtIdGenerator);
191
+ * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;
192
+ * ```
193
+ */
157
194
  class KtIdGenerator {
158
195
  counters = new Map();
159
196
  nextGlobalId = 0;
197
+ /**
198
+ * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,
199
+ * utilise un compteur dédié à ce préfixe (séquences indépendantes).
200
+ * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.
201
+ * @returns Le compteur courant (number) pour la portée demandée, avant incrément.
202
+ */
160
203
  generateId(prefix) {
161
204
  if (!prefix) {
162
205
  return this.nextGlobalId++;
@@ -171,6 +214,7 @@ class KtIdGenerator {
171
214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtIdGenerator, decorators: [{
172
215
  type: Injectable,
173
216
  args: [{
217
+ // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).
174
218
  providedIn: 'root',
175
219
  }]
176
220
  }] });
@@ -1 +1 @@
1
- {"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n start(event: PointerEvent): void;\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n\n const cleanupListeners = (): void => {\n if (!handle) return;\n handle.removeEventListener('pointermove', onMove);\n handle.removeEventListener('pointerup', onEnd);\n handle.removeEventListener('pointercancel', onEnd);\n handle.removeEventListener('lostpointercapture', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n pane.classList.remove(opts.draggingClass);\n\n const dragged = event.clientY - startY;\n if (dragged > pane.getBoundingClientRect().height * threshold) {\n pane.style.translate = '0 100%'; // continue le glissement jusqu'en bas, puis ferme\n opts.onDismiss();\n } else {\n pane.style.translate = ''; // snap-back : la transition CSS ramène à 0\n }\n pane = null;\n handle = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n handle.setPointerCapture(event.pointerId);\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n handle.addEventListener('pointermove', onMove);\n handle.addEventListener('pointerup', onEnd);\n handle.addEventListener('pointercancel', onEnd);\n handle.addEventListener('lostpointercapture', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n },\n };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n mobile: string;\n tablet: string;\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n /** Démarre le suivi du glissement ; à brancher sur le `pointerdown` de la poignée. */\n start(event: PointerEvent): void;\n /** Détache les écouteurs et réinitialise l'état ; à appeler à la destruction. */\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n *\n * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.\n * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).\n * @example\n * ```ts\n * const drag = createKtSheetDrag({\n * pane: () => paneEl(),\n * onDismiss: () => dialogRef.close(),\n * draggingClass: 'kt-sheet--dragging',\n * });\n * // <div class=\"handle\" (pointerdown)=\"drag.start($event)\"></div>\n * ```\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n let ownerDoc: Document | null = null;\n\n const cleanupListeners = (): void => {\n if (!ownerDoc) return;\n ownerDoc.removeEventListener('pointermove', onMove);\n ownerDoc.removeEventListener('pointerup', onEnd);\n ownerDoc.removeEventListener('pointercancel', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n\n const dragged = event.clientY - startY;\n const dismiss = dragged > pane.getBoundingClientRect().height * threshold;\n // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.\n const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n if (reduceMotion) {\n // draggingClass (transition coupée) encore présente : la translation finale est instantanée,\n // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).\n pane.style.translate = dismiss ? '0 100%' : '';\n pane.classList.remove(opts.draggingClass);\n } else {\n pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)\n pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0\n }\n if (dismiss) opts.onDismiss();\n\n pane = null;\n handle = null;\n ownerDoc = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n ownerDoc = handle.ownerDocument;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une\n // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.\n ownerDoc.addEventListener('pointermove', onMove);\n ownerDoc.addEventListener('pointerup', onEnd);\n ownerDoc.addEventListener('pointercancel', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n ownerDoc = null;\n },\n };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.\n * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n /** Media query de la bande mobile. */\n mobile: string;\n /** Media query de la bande tablette. */\n tablet: string;\n /** Media query de la bande desktop. */\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n * @param bp Seuils responsive à convertir (nombres et/ou chaînes).\n * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).\n * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,\n * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.\n *\n * @example\n * ```ts\n * const idGen = inject(KtIdGenerator);\n * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;\n * ```\n */\n@Injectable({\n // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n /**\n * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,\n * utilise un compteur dédié à ce préfixe (séquences indépendantes).\n * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.\n * @returns Le compteur courant (number) pour la portée demandée, avant incrément.\n */\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { DialogRef, DEFAULT_DIALOG_CONFIG, CdkDialogContainer, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, ElementRef, Renderer2, afterNextRender, Directive, input, isDevMode, DestroyRef, PLATFORM_ID, signal, Component, ViewContainerRef } from '@angular/core';
3
+ import { inject, ElementRef, Renderer2, afterNextRender, Directive, input, isDevMode, DestroyRef, PLATFORM_ID, signal, ChangeDetectionStrategy, Component, ViewContainerRef } from '@angular/core';
4
4
  import { KtIdGenerator, createKtSheetDrag, KtViewport } from '@ktortu/aaa/cdk';
5
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
6
  import { isPlatformBrowser } from '@angular/common';
6
7
  import * as i1 from '@angular/cdk/portal';
7
8
  import { PortalModule } from '@angular/cdk/portal';
@@ -26,8 +27,11 @@ class KtDialogTitle {
26
27
  host = inject(ElementRef).nativeElement;
27
28
  renderer = inject(Renderer2);
28
29
  idGen = inject(KtIdGenerator);
29
- /** Id de l'hôte câblé en `aria-labelledby` : préserve un id fourni par le consommateur, sinon en génère un. */
30
- id = this.host.id || this.dialogRef?.config.ariaLabelledBy || `kt-dialog-title-${this.idGen.generateId()}`;
30
+ /**
31
+ * Id de l'hôte câblé en `aria-labelledby` : préserve un id fourni par le consommateur, sinon en génère un.
32
+ * @default `host.id` ?? `dialogRef.config.ariaLabelledBy` ?? `kt-dialog-title-<généré>`
33
+ */
34
+ id = this.host.id || this.dialogRef?.config.ariaLabelledBy || `kt-dialog-title-${this.idGen.generateId('dialog')}`;
31
35
  constructor() {
32
36
  const preConfigured = this.dialogRef?.config.ariaLabelledBy;
33
37
  if (!preConfigured) {
@@ -72,8 +76,11 @@ class KtDialogDescription {
72
76
  host = inject(ElementRef).nativeElement;
73
77
  renderer = inject(Renderer2);
74
78
  idGen = inject(KtIdGenerator);
75
- /** Id de l'hôte câblé en `aria-describedby` : préserve un id fourni par le consommateur, sinon en génère un. */
76
- id = this.host.id || this.dialogRef?.config.ariaDescribedBy || `kt-dialog-desc-${this.idGen.generateId()}`;
79
+ /**
80
+ * Id de l'hôte câblé en `aria-describedby` : préserve un id fourni par le consommateur, sinon en génère un.
81
+ * @default `host.id` ?? `dialogRef.config.ariaDescribedBy` ?? `kt-dialog-desc-<généré>`
82
+ */
83
+ id = this.host.id || this.dialogRef?.config.ariaDescribedBy || `kt-dialog-desc-${this.idGen.generateId('dialog')}`;
77
84
  constructor() {
78
85
  const preConfigured = this.dialogRef?.config.ariaDescribedBy;
79
86
  if (!preConfigured) {
@@ -88,6 +95,9 @@ class KtDialogDescription {
88
95
  }
89
96
  }
90
97
  ngOnInit() {
98
+ // Asymétrie ASSUMÉE avec `ktDialogTitle` : la description peut porter un `id` d'hôte fourni par
99
+ // le consommateur ; on aligne alors `config.ariaDescribedBy` dessus (le host binding live du
100
+ // conteneur reflète la valeur). Un titre est posé une fois et n'a pas ce besoin.
91
101
  if (this.dialogRef && this.dialogRef.config.ariaDescribedBy !== this.id) {
92
102
  this.dialogRef.config.ariaDescribedBy = this.id;
93
103
  }
@@ -305,6 +315,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
305
315
  * Pour focaliser un élément précis à l'ouverture (ex. éviter un bouton destructeur),
306
316
  * passez `autoFocus: '[ktDialogFocusInitial]'` dans la config d'ouverture et posez la
307
317
  * directive `ktDialogFocusInitial` sur la cible.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * // valeur de base, généralement consommée via provideKtDialogDefaults()
322
+ * provideKtDialogDefaults({ ...KT_DIALOG_AAA_DEFAULTS });
323
+ * ```
308
324
  */
309
325
  const KT_DIALOG_AAA_DEFAULTS = {
310
326
  role: 'dialog',
@@ -319,6 +335,16 @@ const KT_DIALOG_AAA_DEFAULTS = {
319
335
  * Enregistre les valeurs par défaut AAA du dialog au niveau application.
320
336
  * À ajouter aux `providers` de `app.config.ts`. `overrides` permet d'ajuster sans
321
337
  * tout réécrire (ex. `provideKtDialogDefaults({ maxWidth: '40rem' })`).
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * // app.config.ts
342
+ * export const appConfig: ApplicationConfig = {
343
+ * providers: [provideKtDialogDefaults({ maxWidth: '40rem' })],
344
+ * };
345
+ * ```
346
+ * @param overrides Surcharges partielles fusionnées par-dessus `KT_DIALOG_AAA_DEFAULTS`.
347
+ * @returns Un `Provider` pour le token `DEFAULT_DIALOG_CONFIG`.
322
348
  */
323
349
  function provideKtDialogDefaults(overrides) {
324
350
  return {
@@ -339,6 +365,15 @@ function concreteDialogPanelClass(p) {
339
365
  * `centered-*`, l'appelant fournit `compact` (= `KtViewport.isCompact()`, largeur seule) — centré
340
366
  * sur desktop, plein écran / sheet sur écran compact. Fonction pure (testable sans DOM).
341
367
  * Appelée par `injectKtDialogOpener` à chaque ouverture ; exportée pour un usage direct éventuel.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * resolveKtDialogPanelClass('sheet'); // ['kt-dialog', 'kt-dialog--sheet']
372
+ * resolveKtDialogPanelClass('centered-sheet', true); // bottom-sheet sur écran compact
373
+ * ```
374
+ * @param presentation Présentation choisie par le dev (responsive comprise). Défaut `'centered'`.
375
+ * @param compact `true` quand l'écran est compact (= `KtViewport.isCompact()`), pour résoudre les variantes `centered-*`. Défaut `false`.
376
+ * @returns La liste des classes CSS de la présentation concrète résolue.
342
377
  */
343
378
  function resolveKtDialogPanelClass(presentation = 'centered', compact = false) {
344
379
  if (presentation === 'centered' || presentation === 'fullscreen' || presentation === 'sheet') {
@@ -355,6 +390,7 @@ class KtDialogContainer extends CdkDialogContainer {
355
390
  elementRef = inject(ElementRef);
356
391
  host = this.elementRef.nativeElement;
357
392
  platformId = inject(PLATFORM_ID);
393
+ destroyRef = inject(DestroyRef);
358
394
  isClosing = signal(false, /* @ts-ignore */
359
395
  ...(ngDevMode ? [{ debugName: "isClosing" }] : /* istanbul ignore next */ []));
360
396
  constructor() {
@@ -364,12 +400,30 @@ class KtDialogContainer extends CdkDialogContainer {
364
400
  this.dialogRef.close = (result) => {
365
401
  this.animateAndClose(result, originalClose);
366
402
  };
403
+ // L'ouvreur pré-réserve des ids titre/description ; les directives [ktDialogTitle]/[ktDialogDescription]
404
+ // les adoptent. En leur absence, ces ids pointent dans le vide. Au rendu (navigateur) :
405
+ // - aria-describedby orphelin → on RETIRE l'attribut (pas de description fournie) ;
406
+ // - aucun nom accessible (ni titre ni aria-label) → warn dev (WCAG 4.1.2).
407
+ afterNextRender(() => {
408
+ const doc = this.host.ownerDocument;
409
+ const describedBy = this.host.getAttribute('aria-describedby');
410
+ if (describedBy && !doc.getElementById(describedBy)) {
411
+ this.host.removeAttribute('aria-describedby');
412
+ }
413
+ if (!isDevMode())
414
+ return;
415
+ const labelledBy = this.host.getAttribute('aria-labelledby');
416
+ const hasName = !!this.host.getAttribute('aria-label') || (!!labelledBy && !!doc.getElementById(labelledBy));
417
+ if (!hasName) {
418
+ console.warn('[ktDialog] dialog sans nom accessible : ajoutez un [ktDialogTitle] (ou `aria-label` via la config d’ouverture) — WCAG 4.1.2.');
419
+ }
420
+ });
367
421
  }
368
422
  ngOnInit() {
369
423
  // Si la fermeture par clic extérieur/Échap est autorisée, on l'écoute pour lancer l'animation
370
424
  if (!this._config.disableClose) {
371
- this.dialogRef.backdropClick.subscribe(() => this.dialogRef.close());
372
- this.dialogRef.keydownEvents.subscribe((event) => {
425
+ this.dialogRef.backdropClick.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => this.dialogRef.close());
426
+ this.dialogRef.keydownEvents.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
373
427
  if (event.key === 'Escape') {
374
428
  event.preventDefault();
375
429
  this.dialogRef.close();
@@ -415,12 +469,13 @@ class KtDialogContainer extends CdkDialogContainer {
415
469
  <div class="kt-dialog-container__layout">
416
470
  <ng-template cdkPortalOutlet></ng-template>
417
471
  </div>
418
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }] });
472
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
419
473
  }
420
474
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtDialogContainer, decorators: [{
421
475
  type: Component,
422
476
  args: [{
423
477
  selector: 'kt-dialog-container',
478
+ changeDetection: ChangeDetectionStrategy.OnPush,
424
479
  imports: [PortalModule],
425
480
  host: {
426
481
  class: 'cdk-dialog-container kt-dialog-container',
@@ -443,19 +498,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
443
498
  }], ctorParameters: () => [] });
444
499
 
445
500
  /**
446
- * Sucre typé pour récupérer les données injectées dans un composant de dialogue (`DIALOG_DATA`).
447
- * À appeler en contexte d'injection (ex. initialiseur de champ).
448
- *
449
- * @example
450
- * ```ts
451
- * interface RenameData { currentName: string; }
501
+ * ⚠️ API BAS-NIVEAU n'utilisez PAS ceci par défaut. Pour implémenter un dialog, passez par
502
+ * {@link defineKtDialog} et son `injectData()` (qui lie le type des données à l'ouvreur).
452
503
  *
453
- * @Component({ … })
454
- * export class RenameDialog {
455
- * protected readonly data = injectKtDialogData<RenameData>();
456
- * // this.data.currentName → string
457
- * }
458
- * ```
504
+ * Sucre typé pour récupérer les données injectées dans un composant de dialogue (`DIALOG_DATA`).
505
+ * Le type `T` est affirmé ICI indépendamment de l'ouvreur : rien ne garantit qu'il corresponde au
506
+ * type passé à `injectKtDialogOpener` → risque de divergence silencieuse. À réserver à un cas où
507
+ * `defineKtDialog` ne convient pas.
459
508
  */
460
509
  function injectKtDialogData() {
461
510
  return inject(DIALOG_DATA);
@@ -474,7 +523,7 @@ function injectKtDialogOpener(component, baseConfig) {
474
523
  // Dédoublonné : une classe déjà fournie par la présentation (ex. `kt-dialog`) et repassée
475
524
  // en `panelClass` par le consommateur ne doit pas apparaître deux fois.
476
525
  const merged = [...new Set([...resolveKtDialogPanelClass(presentation, viewport.isCompact()), ...extra])];
477
- const uid = idGen.generateId();
526
+ const uid = idGen.generateId('dialog');
478
527
  const titleId = `kt-dialog-title-${uid}`;
479
528
  const descId = `kt-dialog-desc-${uid}`;
480
529
  return dialog.open(component, {
@@ -488,12 +537,73 @@ function injectKtDialogOpener(component, baseConfig) {
488
537
  });
489
538
  };
490
539
  }
540
+ /**
541
+ * ✅ STRATÉGIE PAR DÉFAUT pour implémenter un dialog `@ktortu/aaa`. Toute nouvelle dialog DOIT être
542
+ * écrite ainsi. N'utilisez `injectKtDialogData` / `injectKtDialogOpener` directement QUE dans un cas
543
+ * avancé non couvert ici.
544
+ *
545
+ * Définit le contrat typé du dialog (données `D` + résultat `R`) en UN SEUL endroit, co-localisé avec
546
+ * le composant. Renvoie trois accès liés aux mêmes types — `injectData()` (données), `injectRef()`
547
+ * (référence dont `close()` n'accepte que `R`) et `injectOpener()` (ouvreur) — d'où l'impossibilité
548
+ * d'une divergence de type entre composant et ouvreur.
549
+ *
550
+ * Recette à copier pour CHAQUE dialog :
551
+ * 1. `interface XxxData { … }` (ou `void` si aucune donnée) ;
552
+ * 2. `const xxxDialog = defineKtDialog<XxxData, Résultat>();` ;
553
+ * 3. dans le composant : `injectData()` pour lire la donnée, `injectRef()` pour fermer avec résultat ;
554
+ * 4. `export const injectXxxDialog = () => xxxDialog.injectOpener(XxxDialog);` (co-localisé) ;
555
+ * 5. côté consommateur : `private readonly openXxx = injectXxxDialog();` (initialiseur de champ).
556
+ *
557
+ * Convention de fermeture : annuler / fermer SANS résultat via la directive `[ktDialogClose]`
558
+ * (→ `undefined`) ; renvoyer un résultat via `ref.close(résultat)` (typé `R`). Passer un résultat par
559
+ * `[ktDialogClose]="x"` reste possible mais N'EST PAS typé (la directive ignore `R`).
560
+ *
561
+ * @example
562
+ * ```ts
563
+ * // ─── rename-dialog.ts : composant + contrat + ouvreur, co-localisés ───
564
+ * export interface RenameData { currentName: string; }
565
+ *
566
+ * const renameDialog = defineKtDialog<RenameData, string>(); // D et R déclarés UNE seule fois
567
+ *
568
+ * @Component({
569
+ * imports: [KtButton, KtDialogImports],
570
+ * template: `
571
+ * <h2 ktDialogTitle>Renommer</h2>
572
+ * <footer ktDialogActions>
573
+ * <button ktButton ktDialogClose>Annuler</button> // ferme sans résultat (undefined)
574
+ * <button ktButton (click)="submit()">Renommer</button> // ferme avec résultat typé
575
+ * </footer>`,
576
+ * })
577
+ * export class RenameDialog {
578
+ * protected readonly data = renameDialog.injectData(); // RenameData garanti
579
+ * private readonly ref = renameDialog.injectRef(); // DialogRef<string>
580
+ * protected submit() { this.ref.close('nouveau-nom'); } // close(result?: string) — typé
581
+ * }
582
+ *
583
+ * export const injectRenameDialog = () => renameDialog.injectOpener(RenameDialog);
584
+ *
585
+ * // ─── consommateur ───
586
+ * private readonly openRename = injectRenameDialog(); // initialiseur de champ = contexte d'injection
587
+ * rename() {
588
+ * this.openRename({ currentName: 'X' }).closed.subscribe(name => {
589
+ * // name: string | undefined
590
+ * });
591
+ * }
592
+ * ```
593
+ */
594
+ function defineKtDialog() {
595
+ return {
596
+ injectData: () => inject(DIALOG_DATA),
597
+ injectRef: () => inject(DialogRef),
598
+ injectOpener: (component, baseConfig) => injectKtDialogOpener(component, baseConfig),
599
+ };
600
+ }
491
601
 
492
602
  /**
493
603
  * Import ergonomique de toute la famille dialog en une fois :
494
- * `imports: [KT_DIALOG]` au lieu d'énumérer chaque directive structurelle.
604
+ * `imports: [KtDialogImports]` au lieu d'énumérer chaque directive structurelle.
495
605
  */
496
- const KT_DIALOG = [
606
+ const KtDialogImports = [
497
607
  KtDialogHeader,
498
608
  KtDialogTitle,
499
609
  KtDialogDescription,
@@ -508,5 +618,5 @@ const KT_DIALOG = [
508
618
  * Generated bundle index. Do not edit.
509
619
  */
510
620
 
511
- export { KT_DIALOG, KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogSheetHandle, KtDialogTitle, injectKtDialogData, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
621
+ export { KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, defineKtDialog, injectKtDialogData, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
512
622
  //# sourceMappingURL=ktortu-aaa-dialog.mjs.map