@ktortu/aaa 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dialog/dialog.css +6 -0
- package/fesm2022/ktortu-aaa-button.mjs +10 -2
- package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-card.mjs +112 -99
- package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-cdk.mjs +49 -2
- package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-dialog.mjs +240 -32
- package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-disclosure.mjs +203 -0
- package/fesm2022/ktortu-aaa-disclosure.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-forms.mjs +217 -103
- package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-progress-bar.mjs +91 -0
- package/fesm2022/ktortu-aaa-progress-bar.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-snackbar.mjs +16 -3
- package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-tooltip.mjs +20 -30
- package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -1
- package/fesm2022/ktortu-aaa.mjs +1 -0
- package/fesm2022/ktortu-aaa.mjs.map +1 -1
- package/forms/field/field.css +16 -0
- package/forms/styles/field-box.css +11 -5
- package/package.json +9 -1
- package/progress-bar/progress-bar.css +127 -0
- package/styles/disclosure.css +6 -0
- package/styles/progress-bar.css +5 -0
- package/styles/styles.css +2 -0
- package/themes/theme-cyberpunk.css +11 -0
- package/tooltip/tooltip.css +1 -1
- package/types/ktortu-aaa-button.d.ts +2 -0
- package/types/ktortu-aaa-card.d.ts +69 -65
- package/types/ktortu-aaa-cdk.d.ts +27 -1
- package/types/ktortu-aaa-dialog.d.ts +134 -15
- package/types/ktortu-aaa-disclosure.d.ts +121 -0
- package/types/ktortu-aaa-forms.d.ts +69 -16
- package/types/ktortu-aaa-progress-bar.d.ts +27 -0
- package/types/ktortu-aaa-snackbar.d.ts +11 -68
- package/types/ktortu-aaa-tooltip.d.ts +0 -1
- package/types/ktortu-aaa.d.ts +1 -0
package/dialog/dialog.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, ElementRef, PLATFORM_ID, signal, input, booleanAttribute, computed,
|
|
2
|
+
import { InjectionToken, inject, ElementRef, PLATFORM_ID, signal, input, booleanAttribute, computed, DestroyRef, effect, Directive } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
+
import { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';
|
|
4
5
|
|
|
5
6
|
const KT_BUTTON_CONFIG = new InjectionToken('KT_BUTTON_CONFIG');
|
|
6
7
|
/**
|
|
@@ -30,8 +31,10 @@ class KtButton {
|
|
|
30
31
|
config = inject(KT_BUTTON_CONFIG, { optional: true });
|
|
31
32
|
host = inject(ElementRef).nativeElement;
|
|
32
33
|
platformId = inject(PLATFORM_ID);
|
|
34
|
+
isDestroyed = false;
|
|
33
35
|
viewInitialized = signal(false, /* @ts-ignore */
|
|
34
36
|
...(ngDevMode ? [{ debugName: "viewInitialized" }] : /* istanbul ignore next */ []));
|
|
37
|
+
auditEnabled = inject(KT_AUDIT_ENABLED);
|
|
35
38
|
// Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.
|
|
36
39
|
isLink = this.host.tagName === 'A';
|
|
37
40
|
// aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).
|
|
@@ -74,12 +77,17 @@ class KtButton {
|
|
|
74
77
|
resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null, /* @ts-ignore */
|
|
75
78
|
...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
|
|
76
79
|
constructor() {
|
|
80
|
+
inject(DestroyRef).onDestroy(() => {
|
|
81
|
+
this.isDestroyed = true;
|
|
82
|
+
});
|
|
77
83
|
effect(() => {
|
|
84
|
+
if (this.isDestroyed)
|
|
85
|
+
return;
|
|
78
86
|
if (!isPlatformBrowser(this.platformId))
|
|
79
87
|
return;
|
|
80
88
|
if (!this.viewInitialized())
|
|
81
89
|
return;
|
|
82
|
-
if (!
|
|
90
|
+
if (!this.auditEnabled || !this.iconOnly())
|
|
83
91
|
return;
|
|
84
92
|
if (!this.icon()) {
|
|
85
93
|
console.warn('[ktButton] iconOnly attend une icône via [icon].');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ktortu-aaa-button.mjs","sources":["../../../../projects/ktortu/aaa/button/button.ts","../../../../projects/ktortu/aaa/button/ktortu-aaa-button.ts"],"sourcesContent":["import {\n Directive,\n ElementRef,\n InjectionToken,\n Provider,\n booleanAttribute,\n computed,\n inject,\n input,\n isDevMode,\n AfterViewInit,\n PLATFORM_ID,\n effect,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\n/** Axe \"emphase\" (apparence). */\nexport type KtButtonMode = 'filled' | 'tonal' | 'outlined' | 'text';\n/** Axe \"intention\" (couleur sémantique). */\nexport type KtButtonColor = 'primary' | 'neutral' | 'danger';\n/** Taille (la cible md ~44-48px vise le confort tactile / AAA). */\nexport type KtButtonSize = 'sm' | 'md' | 'lg';\n\n/** Défauts applicables à tous les `[ktButton]` (surchargeables par bouton via les inputs). */\nexport interface KtButtonConfig {\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). */\n disabledInteractive: boolean;\n /** Emphase visuelle par défaut (apparence). */\n mode: KtButtonMode;\n /** Intention sémantique par défaut (couleur). */\n color: KtButtonColor;\n /** Taille par défaut. */\n size: KtButtonSize;\n}\n\nexport const KT_BUTTON_CONFIG = new InjectionToken<Partial<KtButtonConfig>>('KT_BUTTON_CONFIG');\n\n/**\n * Fournit des défauts de bouton (mode/couleur/taille…) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtButton({ mode: 'tonal', size: 'lg' })]\n * ```\n */\nexport function provideKtButton(config: Partial<KtButtonConfig>): Provider {\n return { provide: KT_BUTTON_CONFIG, useValue: config };\n}\n\n/**\n * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`\n * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement\n * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).\n *\n * @example\n * ```html\n * <button ktButton mode=\"filled\" color=\"primary\">Enregistrer</button>\n * <a ktButton mode=\"text\" href=\"/aide\">Aide</a>\n * <button ktButton iconOnly icon=\"close\" ariaLabel=\"Fermer\"></button>\n * ```\n */\n@Directive({\n selector: 'button[ktButton], a[ktButton]',\n host: {\n '[attr.data-mode]': 'mode()',\n '[attr.data-color]': 'color()',\n '[attr.data-size]': 'size()',\n '[attr.data-full-width]': 'fullWidth() ? \"\" : null',\n '[attr.data-icon-only]': 'iconOnly() ? \"\" : null',\n '[attr.type]': '!isLink ? type() : null',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[class.loading]': 'loading()',\n '[class.disabled]': 'isDisabled()',\n '[attr.data-icon]': 'icon()',\n '[attr.data-icon-position]': 'iconPosition()',\n '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? \"\" : null',\n '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null',\n '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null',\n '[attr.aria-busy]': 'loading() ? \"true\" : null',\n '(click)': 'haltDisabledEvents($event)',\n },\n})\nexport class KtButton implements AfterViewInit {\n private readonly config = inject(KT_BUTTON_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private readonly viewInitialized = signal(false);\n\n // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.\n protected readonly isLink = this.host.tagName === 'A';\n // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).\n private readonly nativeAriaLabel = this.host.getAttribute('aria-label');\n\n /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */\n readonly mode = input<KtButtonMode>(this.config?.mode ?? 'filled');\n\n /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */\n readonly color = input<KtButtonColor>(this.config?.color ?? 'primary');\n\n /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */\n readonly size = input<KtButtonSize>(this.config?.size ?? 'md');\n\n /** Étire le bouton sur toute la largeur disponible. @default false */\n readonly fullWidth = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */\n readonly iconOnly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom accessible. **Obligatoire si `iconOnly`** ; à défaut, l'`aria-label` natif est préservé. @default undefined */\n readonly ariaLabel = input<string>();\n\n /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */\n readonly type = input<'button' | 'submit' | 'reset'>('button');\n\n /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */\n readonly loading = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom de l'icône (rendu via CSS `data-icon`). @default undefined */\n readonly icon = input<string>();\n\n /** Position de l'icône relative au texte. @default 'start' */\n readonly iconPosition = input<'start' | 'end'>('start');\n\n /** Désactive le bouton. @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */\n readonly disabledInteractive = input<boolean, unknown>(this.config?.disabledInteractive ?? false, {\n transform: booleanAttribute,\n });\n\n // loading implique un bouton inerte\n protected readonly isDisabled = computed(() => this.disabled() || this.loading());\n\n // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.\n protected readonly resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null);\n\n constructor() {\n effect(() => {\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.viewInitialized()) return;\n if (!isDevMode() || !this.iconOnly()) return;\n\n if (!this.icon()) {\n console.warn('[ktButton] iconOnly attend une icône via [icon].');\n }\n if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {\n console.warn(\n '[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +\n 'le bouton serait annoncé sans libellé.',\n );\n }\n });\n }\n\n ngAfterViewInit(): void {\n this.viewInitialized.set(true);\n }\n\n protected haltDisabledEvents(event: Event): void {\n if (!this.isDisabled()) return;\n\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ktortu-aaa-button.mjs","sources":["../../../../projects/ktortu/aaa/button/button.ts","../../../../projects/ktortu/aaa/button/ktortu-aaa-button.ts"],"sourcesContent":["import {\n DestroyRef,\n Directive,\n ElementRef,\n InjectionToken,\n Provider,\n booleanAttribute,\n computed,\n inject,\n input,\n AfterViewInit,\n PLATFORM_ID,\n effect,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';\n\n/** Axe \"emphase\" (apparence). */\nexport type KtButtonMode = 'filled' | 'tonal' | 'outlined' | 'text';\n/** Axe \"intention\" (couleur sémantique). */\nexport type KtButtonColor = 'primary' | 'neutral' | 'danger';\n/** Taille (la cible md ~44-48px vise le confort tactile / AAA). */\nexport type KtButtonSize = 'sm' | 'md' | 'lg';\n\n/** Défauts applicables à tous les `[ktButton]` (surchargeables par bouton via les inputs). */\nexport interface KtButtonConfig {\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). */\n disabledInteractive: boolean;\n /** Emphase visuelle par défaut (apparence). */\n mode: KtButtonMode;\n /** Intention sémantique par défaut (couleur). */\n color: KtButtonColor;\n /** Taille par défaut. */\n size: KtButtonSize;\n}\n\nexport const KT_BUTTON_CONFIG = new InjectionToken<Partial<KtButtonConfig>>('KT_BUTTON_CONFIG');\n\n/**\n * Fournit des défauts de bouton (mode/couleur/taille…) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtButton({ mode: 'tonal', size: 'lg' })]\n * ```\n */\nexport function provideKtButton(config: Partial<KtButtonConfig>): Provider {\n return { provide: KT_BUTTON_CONFIG, useValue: config };\n}\n\n/**\n * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`\n * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement\n * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).\n *\n * @example\n * ```html\n * <button ktButton mode=\"filled\" color=\"primary\">Enregistrer</button>\n * <a ktButton mode=\"text\" href=\"/aide\">Aide</a>\n * <button ktButton iconOnly icon=\"close\" ariaLabel=\"Fermer\"></button>\n * ```\n */\n@Directive({\n selector: 'button[ktButton], a[ktButton]',\n host: {\n '[attr.data-mode]': 'mode()',\n '[attr.data-color]': 'color()',\n '[attr.data-size]': 'size()',\n '[attr.data-full-width]': 'fullWidth() ? \"\" : null',\n '[attr.data-icon-only]': 'iconOnly() ? \"\" : null',\n '[attr.type]': '!isLink ? type() : null',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[class.loading]': 'loading()',\n '[class.disabled]': 'isDisabled()',\n '[attr.data-icon]': 'icon()',\n '[attr.data-icon-position]': 'iconPosition()',\n '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? \"\" : null',\n '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null',\n '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null',\n '[attr.aria-busy]': 'loading() ? \"true\" : null',\n '(click)': 'haltDisabledEvents($event)',\n },\n})\nexport class KtButton implements AfterViewInit {\n private readonly config = inject(KT_BUTTON_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private isDestroyed = false;\n private readonly viewInitialized = signal(false);\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n\n // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.\n protected readonly isLink = this.host.tagName === 'A';\n // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).\n private readonly nativeAriaLabel = this.host.getAttribute('aria-label');\n\n /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */\n readonly mode = input<KtButtonMode>(this.config?.mode ?? 'filled');\n\n /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */\n readonly color = input<KtButtonColor>(this.config?.color ?? 'primary');\n\n /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */\n readonly size = input<KtButtonSize>(this.config?.size ?? 'md');\n\n /** Étire le bouton sur toute la largeur disponible. @default false */\n readonly fullWidth = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */\n readonly iconOnly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom accessible. **Obligatoire si `iconOnly`** ; à défaut, l'`aria-label` natif est préservé. @default undefined */\n readonly ariaLabel = input<string>();\n\n /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */\n readonly type = input<'button' | 'submit' | 'reset'>('button');\n\n /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */\n readonly loading = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom de l'icône (rendu via CSS `data-icon`). @default undefined */\n readonly icon = input<string>();\n\n /** Position de l'icône relative au texte. @default 'start' */\n readonly iconPosition = input<'start' | 'end'>('start');\n\n /** Désactive le bouton. @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */\n readonly disabledInteractive = input<boolean, unknown>(this.config?.disabledInteractive ?? false, {\n transform: booleanAttribute,\n });\n\n // loading implique un bouton inerte\n protected readonly isDisabled = computed(() => this.disabled() || this.loading());\n\n // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.\n protected readonly resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null);\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n this.isDestroyed = true;\n });\n\n effect(() => {\n if (this.isDestroyed) return;\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.viewInitialized()) return;\n if (!this.auditEnabled || !this.iconOnly()) return;\n\n if (!this.icon()) {\n console.warn('[ktButton] iconOnly attend une icône via [icon].');\n }\n if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {\n console.warn(\n '[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +\n 'le bouton serait annoncé sans libellé.',\n );\n }\n });\n }\n\n ngAfterViewInit(): void {\n this.viewInitialized.set(true);\n }\n\n protected haltDisabledEvents(event: Event): void {\n if (!this.isDisabled()) return;\n\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,103 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { InjectionToken, inject, ElementRef, PLATFORM_ID, contentChild, forwardRef, input, booleanAttribute, signal, DestroyRef, effect, Directive } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
+
import { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';
|
|
4
5
|
|
|
6
|
+
const KT_CARD_CONFIG = new InjectionToken('KT_CARD_CONFIG');
|
|
7
|
+
/**
|
|
8
|
+
* Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* providers: [provideKtCard({ variant: 'outlined' })]
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function provideKtCard(config) {
|
|
16
|
+
return { provide: KT_CARD_CONFIG, useValue: config };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi
|
|
20
|
+
* par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de
|
|
21
|
+
* wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).
|
|
22
|
+
*
|
|
23
|
+
* Trois axes pilotés en `data-*` (même contrat que ktButton) :
|
|
24
|
+
* - variant : data-variant = elevated | outlined | filled (apparence)
|
|
25
|
+
* - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable
|
|
26
|
+
* est un [ktCardLink], lien/bouton primaire au lien étiré)
|
|
27
|
+
* - disabled : data-disabled (surface inerte)
|
|
28
|
+
*
|
|
29
|
+
* La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs
|
|
30
|
+
* et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```html
|
|
34
|
+
* <article ktCard variant="outlined" interactive>
|
|
35
|
+
* <div ktCardContent>
|
|
36
|
+
* <h3 id="t1">Titre</h3>
|
|
37
|
+
* <a ktCardLink routerLink="/detail" aria-labelledby="t1">Voir le détail</a>
|
|
38
|
+
* </div>
|
|
39
|
+
* </article>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
class KtCard {
|
|
43
|
+
config = inject(KT_CARD_CONFIG, { optional: true });
|
|
44
|
+
host = inject(ElementRef).nativeElement;
|
|
45
|
+
platformId = inject(PLATFORM_ID);
|
|
46
|
+
auditEnabled = inject(KT_AUDIT_ENABLED);
|
|
47
|
+
/** Référence réactive sur le lien primaire de la carte */
|
|
48
|
+
cardLink = contentChild(forwardRef(() => KtCardLink), { ...(ngDevMode ? { debugName: "cardLink" } : /* istanbul ignore next */ {}), descendants: true });
|
|
49
|
+
/** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */
|
|
50
|
+
variant = input(this.config?.variant ?? 'elevated', /* @ts-ignore */
|
|
51
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
52
|
+
/**
|
|
53
|
+
* Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton
|
|
54
|
+
* primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false
|
|
55
|
+
*/
|
|
56
|
+
interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
57
|
+
/** Rend la surface inerte (état `data-disabled`). @default false */
|
|
58
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
59
|
+
isDestroyed = false;
|
|
60
|
+
contentInitialized = signal(false, /* @ts-ignore */
|
|
61
|
+
...(ngDevMode ? [{ debugName: "contentInitialized" }] : /* istanbul ignore next */ []));
|
|
62
|
+
constructor() {
|
|
63
|
+
inject(DestroyRef).onDestroy(() => {
|
|
64
|
+
this.isDestroyed = true;
|
|
65
|
+
});
|
|
66
|
+
effect(() => {
|
|
67
|
+
if (this.isDestroyed)
|
|
68
|
+
return;
|
|
69
|
+
if (!isPlatformBrowser(this.platformId))
|
|
70
|
+
return;
|
|
71
|
+
if (!this.contentInitialized())
|
|
72
|
+
return;
|
|
73
|
+
if (!this.auditEnabled || !this.interactive())
|
|
74
|
+
return;
|
|
75
|
+
// Garde-fou a11y : une carte interactive sans cible cliquable affiche une
|
|
76
|
+
// affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).
|
|
77
|
+
const hasTarget = this.host.matches('a, button') || !!this.cardLink();
|
|
78
|
+
if (!hasTarget) {
|
|
79
|
+
console.warn('[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +
|
|
80
|
+
'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
ngAfterContentInit() {
|
|
85
|
+
this.contentInitialized.set(true);
|
|
86
|
+
}
|
|
87
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
88
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtCard, isStandalone: true, selector: "[ktCard]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-variant": "variant()", "attr.data-interactive": "interactive() ? \"\" : null", "attr.data-disabled": "disabled() ? \"\" : null" } }, queries: [{ propertyName: "cardLink", first: true, predicate: i0.forwardRef(() => KtCardLink), descendants: true, isSignal: true }], ngImport: i0 });
|
|
89
|
+
}
|
|
90
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCard, decorators: [{
|
|
91
|
+
type: Directive,
|
|
92
|
+
args: [{
|
|
93
|
+
selector: '[ktCard]',
|
|
94
|
+
host: {
|
|
95
|
+
'[attr.data-variant]': 'variant()',
|
|
96
|
+
'[attr.data-interactive]': 'interactive() ? "" : null',
|
|
97
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
98
|
+
},
|
|
99
|
+
}]
|
|
100
|
+
}], ctorParameters: () => [], propDecorators: { cardLink: [{ type: i0.ContentChild, args: [forwardRef(() => KtCardLink), { ...{ descendants: true }, isSignal: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
5
101
|
/**
|
|
6
102
|
* En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur
|
|
7
103
|
* structurel sans logique — la mise en forme vit dans `card.css` via `[ktCardHeader]`.
|
|
@@ -75,7 +171,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
75
171
|
}] });
|
|
76
172
|
/**
|
|
77
173
|
* Lien (ou bouton) PRIMAIRE d'une carte interactive : pattern « lien étiré » (Inclusive
|
|
78
|
-
* Components). Un pseudo-élément `::after`
|
|
174
|
+
* Components). Un pseudo-élément `::after` covers toute la carte (cf. `card.css`) → toute la
|
|
79
175
|
* surface est cliquable, SANS imbriquer de contrôles interactifs (anti-pattern WCAG 4.1.2). Les
|
|
80
176
|
* actions secondaires de la carte repassent au-dessus du lien (z-index dans `card.css`).
|
|
81
177
|
*
|
|
@@ -97,31 +193,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
97
193
|
*/
|
|
98
194
|
class KtCardLink {
|
|
99
195
|
host = inject(ElementRef).nativeElement;
|
|
196
|
+
auditEnabled = inject(KT_AUDIT_ENABLED);
|
|
100
197
|
/**
|
|
101
198
|
* Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par
|
|
102
199
|
* hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer
|
|
103
200
|
* l'état `disabled` de la carte (aria-disabled / tabindex) sans planter hors contexte.
|
|
104
201
|
*/
|
|
105
|
-
card = inject(KtCard, { optional: true });
|
|
202
|
+
card = inject(forwardRef(() => KtCard), { optional: true });
|
|
106
203
|
handleClick(event) {
|
|
107
204
|
if (this.card?.disabled()) {
|
|
108
205
|
event.preventDefault();
|
|
109
206
|
event.stopPropagation();
|
|
110
207
|
}
|
|
111
208
|
}
|
|
112
|
-
|
|
113
|
-
// Garde-fou a11y
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
});
|
|
209
|
+
ngAfterViewInit() {
|
|
210
|
+
// Garde-fou a11y : un lien étiré sans nom accessible n'est pas annonçable.
|
|
211
|
+
if (!this.auditEnabled)
|
|
212
|
+
return;
|
|
213
|
+
const hasName = !!this.host.textContent?.trim() ||
|
|
214
|
+
!!this.host.getAttribute('aria-label')?.trim() ||
|
|
215
|
+
this.host.hasAttribute('aria-labelledby');
|
|
216
|
+
if (!hasName) {
|
|
217
|
+
console.warn('[ktCardLink] lien sans nom accessible : ajoutez du texte visible, [attr.aria-label] ' +
|
|
218
|
+
'ou aria-labelledby (WCAG 2.4.4 / 4.1.2).');
|
|
219
|
+
}
|
|
125
220
|
}
|
|
126
221
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCardLink, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
127
222
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: KtCardLink, isStandalone: true, selector: "a[ktCardLink], button[ktCardLink]", host: { listeners: { "click": "handleClick($event)" }, properties: { "attr.aria-disabled": "card?.disabled() ? \"true\" : null", "attr.tabindex": "card?.disabled() ? \"-1\" : null" } }, ngImport: i0 });
|
|
@@ -136,89 +231,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
136
231
|
'(click)': 'handleClick($event)',
|
|
137
232
|
},
|
|
138
233
|
}]
|
|
139
|
-
}]
|
|
140
|
-
|
|
141
|
-
const KT_CARD_CONFIG = new InjectionToken('KT_CARD_CONFIG');
|
|
142
|
-
/**
|
|
143
|
-
* Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* ```ts
|
|
147
|
-
* providers: [provideKtCard({ variant: 'outlined' })]
|
|
148
|
-
* ```
|
|
149
|
-
*/
|
|
150
|
-
function provideKtCard(config) {
|
|
151
|
-
return { provide: KT_CARD_CONFIG, useValue: config };
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi
|
|
155
|
-
* par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de
|
|
156
|
-
* wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).
|
|
157
|
-
*
|
|
158
|
-
* Trois axes pilotés en `data-*` (même contrat que ktButton) :
|
|
159
|
-
* - variant : data-variant = elevated | outlined | filled (apparence)
|
|
160
|
-
* - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable
|
|
161
|
-
* est un [ktCardLink], lien/bouton primaire au lien étiré)
|
|
162
|
-
* - disabled : data-disabled (surface inerte)
|
|
163
|
-
*
|
|
164
|
-
* La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs
|
|
165
|
-
* et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```html
|
|
169
|
-
* <article ktCard variant="outlined" interactive>
|
|
170
|
-
* <div ktCardContent>
|
|
171
|
-
* <h3 id="t1">Titre</h3>
|
|
172
|
-
* <a ktCardLink routerLink="/detail" aria-labelledby="t1">Voir le détail</a>
|
|
173
|
-
* </div>
|
|
174
|
-
* </article>
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
class KtCard {
|
|
178
|
-
config = inject(KT_CARD_CONFIG, { optional: true });
|
|
179
|
-
host = inject(ElementRef).nativeElement;
|
|
180
|
-
platformId = inject(PLATFORM_ID);
|
|
181
|
-
/** Référence réactive sur le lien primaire de la carte */
|
|
182
|
-
cardLink = contentChild(KtCardLink, { ...(ngDevMode ? { debugName: "cardLink" } : /* istanbul ignore next */ {}), descendants: true });
|
|
183
|
-
/** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */
|
|
184
|
-
variant = input(this.config?.variant ?? 'elevated', /* @ts-ignore */
|
|
185
|
-
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
186
|
-
/**
|
|
187
|
-
* Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton
|
|
188
|
-
* primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false
|
|
189
|
-
*/
|
|
190
|
-
interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
191
|
-
/** Rend la surface inerte (état `data-disabled`). @default false */
|
|
192
|
-
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
193
|
-
constructor() {
|
|
194
|
-
// Garde-fou a11y (dev seulement) : une carte interactive sans cible cliquable affiche une
|
|
195
|
-
// affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).
|
|
196
|
-
effect(() => {
|
|
197
|
-
if (!isPlatformBrowser(this.platformId))
|
|
198
|
-
return;
|
|
199
|
-
if (!isDevMode() || !this.interactive())
|
|
200
|
-
return;
|
|
201
|
-
const hasTarget = this.host.matches('a, button') || !!this.cardLink();
|
|
202
|
-
if (!hasTarget) {
|
|
203
|
-
console.warn('[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +
|
|
204
|
-
'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.');
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCard, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
209
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: KtCard, isStandalone: true, selector: "[ktCard]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-variant": "variant()", "attr.data-interactive": "interactive() ? \"\" : null", "attr.data-disabled": "disabled() ? \"\" : null" } }, queries: [{ propertyName: "cardLink", first: true, predicate: KtCardLink, descendants: true, isSignal: true }], ngImport: i0 });
|
|
210
|
-
}
|
|
211
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtCard, decorators: [{
|
|
212
|
-
type: Directive,
|
|
213
|
-
args: [{
|
|
214
|
-
selector: '[ktCard]',
|
|
215
|
-
host: {
|
|
216
|
-
'[attr.data-variant]': 'variant()',
|
|
217
|
-
'[attr.data-interactive]': 'interactive() ? "" : null',
|
|
218
|
-
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
219
|
-
},
|
|
220
|
-
}]
|
|
221
|
-
}], ctorParameters: () => [], propDecorators: { cardLink: [{ type: i0.ContentChild, args: [i0.forwardRef(() => KtCardLink), { ...{ descendants: true }, isSignal: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
234
|
+
}] });
|
|
222
235
|
|
|
223
236
|
/**
|
|
224
237
|
* Import ergonomique de toute la famille card en une fois :
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ktortu-aaa-card.mjs","sources":["../../../../projects/ktortu/aaa/card/card-structure.ts","../../../../projects/ktortu/aaa/card/card.ts","../../../../projects/ktortu/aaa/card/public-api.ts","../../../../projects/ktortu/aaa/card/ktortu-aaa-card.ts"],"sourcesContent":["import { Directive, ElementRef, afterNextRender, inject, isDevMode } from '@angular/core';\n\nimport { KtCard } from './card';\n\n/**\n * En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur\n * structurel sans logique — la mise en forme vit dans `card.css` via `[ktCardHeader]`.\n * L'ÉTIQUETTE accessible reste le titre fourni par le consommateur (`<h3 id>` + `aria-labelledby`\n * sur l'hôte), comme [ktDialogTitle] pour le dialog : on sépare layout et sémantique.\n *\n * @example\n * ```html\n * <header ktCardHeader><h3 id=\"t1\">Titre</h3></header>\n * ```\n */\n@Directive({ selector: '[ktCardHeader]' })\nexport class KtCardHeader {}\n\n/**\n * Média pleine largeur (image/vidéo). Marqueur structurel sans logique : `card.css` lui donne le\n * full-bleed (marge négative = padding de la carte) et `[ktCard]` clippe au rayon quand un média\n * est présent. À envelopper autour d'un `<img ngSrc>` (NgOptimizedImage).\n *\n * @example\n * ```html\n * <div ktCardMedia><img ngSrc=\"cover.jpg\" width=\"400\" height=\"200\" alt=\"\" /></div>\n * ```\n */\n@Directive({ selector: '[ktCardMedia]' })\nexport class KtCardMedia {}\n\n/**\n * Corps de la carte. Marqueur structurel sans logique : la mise en forme (rythme vertical) vit\n * dans `card.css` via `[ktCardContent]`.\n *\n * @example\n * ```html\n * <div ktCardContent>Texte de la carte.</div>\n * ```\n */\n@Directive({ selector: '[ktCardContent]' })\nexport class KtCardContent {}\n\n/**\n * Barre d'actions de la carte (rangée de boutons/liens). Marqueur structurel sans logique :\n * la mise en forme (flex, gap, épinglée en pied) vit dans `card.css` via `[ktCardActions]`.\n *\n * @example\n * ```html\n * <footer ktCardActions><button ktButton>Action</button></footer>\n * ```\n */\n@Directive({ selector: '[ktCardActions]' })\nexport class KtCardActions {}\n\n/**\n * Lien (ou bouton) PRIMAIRE d'une carte interactive : pattern « lien étiré » (Inclusive\n * Components). Un pseudo-élément `::after` couvre toute la carte (cf. `card.css`) → toute la\n * surface est cliquable, SANS imbriquer de contrôles interactifs (anti-pattern WCAG 4.1.2). Les\n * actions secondaires de la carte repassent au-dessus du lien (z-index dans `card.css`).\n *\n * UN SEUL [ktCardLink] par carte. Le focus clavier est porté sur ce lien ; l'anneau de focus est\n * relayé sur toute la carte (`[ktCard]:has([ktCardLink]:focus-visible)`).\n *\n * Quand la carte ancêtre est `disabled`, le lien sort de l'ordre de tabulation (`tabindex=\"-1\"`)\n * et est annoncé `aria-disabled` : une carte inerte ne piège pas le focus clavier (WCAG 2.4.3).\n *\n * @example\n * ```html\n * <article ktCard interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: 'a[ktCardLink], button[ktCardLink]',\n host: {\n '[attr.aria-disabled]': 'card?.disabled() ? \"true\" : null',\n '[attr.tabindex]': 'card?.disabled() ? \"-1\" : null',\n '(click)': 'handleClick($event)',\n },\n})\nexport class KtCardLink {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n /**\n * Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par\n * hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer\n * l'état `disabled` de la carte (aria-disabled / tabindex) sans planter hors contexte.\n */\n protected readonly card = inject(KtCard, { optional: true });\n\n handleClick(event: Event): void {\n if (this.card?.disabled()) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n constructor() {\n // Garde-fou a11y (dev seulement) : un lien étiré sans nom accessible n'est pas annonçable.\n afterNextRender(() => {\n if (!isDevMode()) return;\n\n const hasName =\n !!this.host.textContent?.trim() ||\n !!this.host.getAttribute('aria-label')?.trim() ||\n this.host.hasAttribute('aria-labelledby');\n if (!hasName) {\n console.warn(\n '[ktCardLink] lien sans nom accessible : ajoutez du texte visible, [attr.aria-label] ' +\n 'ou aria-labelledby (WCAG 2.4.4 / 4.1.2).',\n );\n }\n });\n }\n}\n","import {\n Directive,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n Provider,\n booleanAttribute,\n contentChild,\n effect,\n inject,\n input,\n isDevMode,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KtCardLink } from './card-structure';\n\n/** Axe \"apparence\" de la surface. */\nexport type KtCardVariant = 'elevated' | 'outlined' | 'filled';\n\n/** Défauts applicables à toutes les `[ktCard]` (surchargeables par carte via les inputs). */\nexport interface KtCardConfig {\n /** Apparence par défaut de la surface. */\n variant: KtCardVariant;\n}\n\nexport const KT_CARD_CONFIG = new InjectionToken<Partial<KtCardConfig>>('KT_CARD_CONFIG');\n\n/**\n * Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtCard({ variant: 'outlined' })]\n * ```\n */\nexport function provideKtCard(config: Partial<KtCardConfig>): Provider {\n return { provide: KT_CARD_CONFIG, useValue: config };\n}\n\n/**\n * Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi\n * par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de\n * wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).\n *\n * Trois axes pilotés en `data-*` (même contrat que ktButton) :\n * - variant : data-variant = elevated | outlined | filled (apparence)\n * - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable\n * est un [ktCardLink], lien/bouton primaire au lien étiré)\n * - disabled : data-disabled (surface inerte)\n *\n * La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs\n * et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.\n *\n * @example\n * ```html\n * <article ktCard variant=\"outlined\" interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: '[ktCard]',\n host: {\n '[attr.data-variant]': 'variant()',\n '[attr.data-interactive]': 'interactive() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n },\n})\nexport class KtCard {\n private readonly config = inject(KT_CARD_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n\n /** Référence réactive sur le lien primaire de la carte */\n readonly cardLink = contentChild(KtCardLink, { descendants: true });\n\n /** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */\n readonly variant = input<KtCardVariant>(this.config?.variant ?? 'elevated');\n\n /**\n * Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton\n * primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false\n */\n readonly interactive = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Rend la surface inerte (état `data-disabled`). @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n constructor() {\n // Garde-fou a11y (dev seulement) : une carte interactive sans cible cliquable affiche une\n // affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).\n effect(() => {\n if (!isPlatformBrowser(this.platformId)) return;\n if (!isDevMode() || !this.interactive()) return;\n\n const hasTarget = this.host.matches('a, button') || !!this.cardLink();\n if (!hasTarget) {\n console.warn(\n '[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +\n 'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.',\n );\n }\n });\n }\n}\n","import { KtCard } from './card';\nimport { KtCardActions, KtCardContent, KtCardHeader, KtCardLink, KtCardMedia } from './card-structure';\n\nexport * from './card';\nexport * from './card-structure';\n\n/**\n * Import ergonomique de toute la famille card en une fois :\n * `imports: [KtCardImports]` au lieu d'énumérer chaque directive.\n */\nexport const KtCardImports = [KtCard, KtCardHeader, KtCardMedia, KtCardContent, KtCardActions, KtCardLink] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ktortu-aaa-card.mjs","sources":["../../../../projects/ktortu/aaa/card/card.ts","../../../../projects/ktortu/aaa/card/public-api.ts","../../../../projects/ktortu/aaa/card/ktortu-aaa-card.ts"],"sourcesContent":["import {\n AfterContentInit,\n AfterViewInit,\n DestroyRef,\n Directive,\n ElementRef,\n InjectionToken,\n PLATFORM_ID,\n Provider,\n booleanAttribute,\n contentChild,\n effect,\n forwardRef,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { KT_AUDIT_ENABLED } from '@ktortu/aaa/cdk';\n\n/** Axe \"apparence\" de la surface. */\nexport type KtCardVariant = 'elevated' | 'outlined' | 'filled';\n\n/** Défauts applicables à toutes les `[ktCard]` (surchargeables par carte via les inputs). */\nexport interface KtCardConfig {\n /** Apparence par défaut de la surface. */\n variant: KtCardVariant;\n}\n\nexport const KT_CARD_CONFIG = new InjectionToken<Partial<KtCardConfig>>('KT_CARD_CONFIG');\n\n/**\n * Fournit des défauts de carte (apparence de surface) pour un sous-arbre ou l'application entière.\n *\n * @example\n * ```ts\n * providers: [provideKtCard({ variant: 'outlined' })]\n * ```\n */\nexport function provideKtCard(config: Partial<KtCardConfig>): Provider {\n return { provide: KT_CARD_CONFIG, useValue: config };\n}\n\n/**\n * Carte : SURFACE de contenu. Directive (pas de composant) posée sur l'élément SÉMANTIQUE choisi\n * par le consommateur (`<article>`, `<section>`, `<li>`, `<a>`…) — la lib n'impose jamais de\n * wrapper non sémantique ni de rôle (cf. précédent Dialog : la sémantique appartient à l'hôte).\n *\n * Trois axes pilotés en `data-*` (même contrat que ktButton) :\n * - variant : data-variant = elevated | outlined | filled (apparence)\n * - interactive : data-interactive (affordance hover/focus — PAS un rôle ; la cible cliquable\n * est un [ktCardLink], lien/bouton primaire au lien étiré)\n * - disabled : data-disabled (surface inerte)\n *\n * La mise en forme (layout des marqueurs, lien étiré, états) vit dans `card.css` ; les couleurs\n * et la géométrie dérivent du socle `--kt-*` via `card-tokens.css`.\n *\n * @example\n * ```html\n * <article ktCard variant=\"outlined\" interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: '[ktCard]',\n host: {\n '[attr.data-variant]': 'variant()',\n '[attr.data-interactive]': 'interactive() ? \"\" : null',\n '[attr.data-disabled]': 'disabled() ? \"\" : null',\n },\n})\nexport class KtCard implements AfterContentInit {\n private readonly config = inject(KT_CARD_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n\n /** Référence réactive sur le lien primaire de la carte */\n readonly cardLink = contentChild<KtCardLink>(\n forwardRef(() => KtCardLink),\n { descendants: true },\n );\n\n /** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */\n readonly variant = input<KtCardVariant>(this.config?.variant ?? 'elevated');\n\n /**\n * Affordance visuelle d'élément cliquable (hover/focus). N'AJOUTE pas de rôle : un lien/bouton\n * primaire ([ktCardLink]) porte l'interaction et le nom accessible. @default false\n */\n readonly interactive = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Rend la surface inerte (état `data-disabled`). @default false */\n readonly disabled = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n private isDestroyed = false;\n private readonly contentInitialized = signal(false);\n\n constructor() {\n inject(DestroyRef).onDestroy(() => {\n this.isDestroyed = true;\n });\n\n effect(() => {\n if (this.isDestroyed) return;\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.contentInitialized()) return;\n if (!this.auditEnabled || !this.interactive()) return;\n\n // Garde-fou a11y : une carte interactive sans cible cliquable affiche une\n // affordance trompeuse (hover/focus) qui ne mène à rien (WCAG 1.3.1).\n const hasTarget = this.host.matches('a, button') || !!this.cardLink();\n if (!hasTarget) {\n console.warn(\n '[ktCard] interactive sans cible cliquable : ajoutez un [ktCardLink] (lien/bouton ' +\n 'primaire) ou posez [ktCard] sur un <a>/<button> — sinon l’affordance est trompeuse.',\n );\n }\n });\n }\n\n ngAfterContentInit(): void {\n this.contentInitialized.set(true);\n }\n}\n\n/**\n * En-tête de la carte (rangée flex : média/avatar + titre + éventuelle action). Marqueur\n * structurel sans logique — la mise en forme vit dans `card.css` via `[ktCardHeader]`.\n * L'ÉTIQUETTE accessible reste le titre fourni par le consommateur (`<h3 id>` + `aria-labelledby`\n * sur l'hôte), comme [ktDialogTitle] pour le dialog : on sépare layout et sémantique.\n *\n * @example\n * ```html\n * <header ktCardHeader><h3 id=\"t1\">Titre</h3></header>\n * ```\n */\n@Directive({ selector: '[ktCardHeader]' })\nexport class KtCardHeader {}\n\n/**\n * Média pleine largeur (image/vidéo). Marqueur structurel sans logique : `card.css` lui donne le\n * full-bleed (marge négative = padding de la carte) et `[ktCard]` clippe au rayon quand un média\n * est présent. À envelopper autour d'un `<img ngSrc>` (NgOptimizedImage).\n *\n * @example\n * ```html\n * <div ktCardMedia><img ngSrc=\"cover.jpg\" width=\"400\" height=\"200\" alt=\"\" /></div>\n * ```\n */\n@Directive({ selector: '[ktCardMedia]' })\nexport class KtCardMedia {}\n\n/**\n * Corps de la carte. Marqueur structurel sans logique : la mise en forme (rythme vertical) vit\n * dans `card.css` via `[ktCardContent]`.\n *\n * @example\n * ```html\n * <div ktCardContent>Texte de la carte.</div>\n * ```\n */\n@Directive({ selector: '[ktCardContent]' })\nexport class KtCardContent {}\n\n/**\n * Barre d'actions de la carte (rangée de boutons/liens). Marqueur structurel sans logique :\n * la mise en forme (flex, gap, épinglée en pied) vit dans `card.css` via `[ktCardActions]`.\n *\n * @example\n * ```html\n * <footer ktCardActions><button ktButton>Action</button></footer>\n * ```\n */\n@Directive({ selector: '[ktCardActions]' })\nexport class KtCardActions {}\n\n/**\n * Lien (ou bouton) PRIMAIRE d'une carte interactive : pattern « lien étiré » (Inclusive\n * Components). Un pseudo-élément `::after` covers toute la carte (cf. `card.css`) → toute la\n * surface est cliquable, SANS imbriquer de contrôles interactifs (anti-pattern WCAG 4.1.2). Les\n * actions secondaires de la carte repassent au-dessus du lien (z-index dans `card.css`).\n *\n * UN SEUL [ktCardLink] par carte. Le focus clavier est porté sur ce lien ; l'anneau de focus est\n * relayé sur toute la carte (`[ktCard]:has([ktCardLink]:focus-visible)`).\n *\n * Quand la carte ancêtre est `disabled`, le lien sort de l'ordre de tabulation (`tabindex=\"-1\"`)\n * et est annoncé `aria-disabled` : une carte inerte ne piège pas le focus clavier (WCAG 2.4.3).\n *\n * @example\n * ```html\n * <article ktCard interactive>\n * <div ktCardContent>\n * <h3 id=\"t1\">Titre</h3>\n * <a ktCardLink routerLink=\"/detail\" aria-labelledby=\"t1\">Voir le détail</a>\n * </div>\n * </article>\n * ```\n */\n@Directive({\n selector: 'a[ktCardLink], button[ktCardLink]',\n host: {\n '[attr.aria-disabled]': 'card?.disabled() ? \"true\" : null',\n '[attr.tabindex]': 'card?.disabled() ? \"-1\" : null',\n '(click)': 'handleClick($event)',\n },\n})\nexport class KtCardLink implements AfterViewInit {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly auditEnabled = inject(KT_AUDIT_ENABLED);\n /**\n * Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par\n * hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer\n * l'état `disabled` de la carte (aria-disabled / tabindex) sans planter hors contexte.\n */\n protected readonly card = inject<KtCard>(\n forwardRef(() => KtCard),\n { optional: true },\n );\n\n handleClick(event: Event): void {\n if (this.card?.disabled()) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n ngAfterViewInit(): void {\n // Garde-fou a11y : un lien étiré sans nom accessible n'est pas annonçable.\n if (!this.auditEnabled) return;\n\n const hasName =\n !!this.host.textContent?.trim() ||\n !!this.host.getAttribute('aria-label')?.trim() ||\n this.host.hasAttribute('aria-labelledby');\n if (!hasName) {\n console.warn(\n '[ktCardLink] lien sans nom accessible : ajoutez du texte visible, [attr.aria-label] ' +\n 'ou aria-labelledby (WCAG 2.4.4 / 4.1.2).',\n );\n }\n }\n}\n","import { KtCard, KtCardActions, KtCardContent, KtCardHeader, KtCardLink, KtCardMedia } from './card';\n\nexport * from './card';\n\n/**\n * Import ergonomique de toute la famille card en une fois :\n * `imports: [KtCardImports]` au lieu d'énumérer chaque directive.\n */\nexport const KtCardImports = [KtCard, KtCardHeader, KtCardMedia, KtCardContent, KtCardActions, KtCardLink] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;MA6Ba,cAAc,GAAG,IAAI,cAAc,CAAwB,gBAAgB;AAExF;;;;;;;AAOG;AACG,SAAU,aAAa,CAAC,MAA6B,EAAA;IACzD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE;AACtD;AAEA;;;;;;;;;;;;;;;;;;;;;;;AAuBG;MASU,MAAM,CAAA;IACA,MAAM,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACnD,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;AAChE,IAAA,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC;AAChC,IAAA,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;;AAG/C,IAAA,QAAQ,GAAG,YAAY,CAC9B,UAAU,CAAC,MAAM,UAAU,CAAC,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,UAAA,EAAA,8BAAA,EAAA,CAAA,EAC1B,WAAW,EAAE,IAAI,GACpB;;IAGQ,OAAO,GAAG,KAAK,CAAgB,IAAI,CAAC,MAAM,EAAE,OAAO,IAAI,UAAU;gFAAC;AAE3E;;;AAGG;IACM,WAAW,GAAG,KAAK,CAAmB,KAAK,mFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;IAG7E,QAAQ,GAAG,KAAK,CAAmB,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;IAE3E,WAAW,GAAG,KAAK;IACV,kBAAkB,GAAG,MAAM,CAAC,KAAK;2FAAC;AAEnD,IAAA,WAAA,GAAA;AACE,QAAA,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAK;AAChC,YAAA,IAAI,CAAC,WAAW,GAAG,IAAI;AACzB,QAAA,CAAC,CAAC;QAEF,MAAM,CAAC,MAAK;YACV,IAAI,IAAI,CAAC,WAAW;gBAAE;AACtB,YAAA,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;gBAAE;AACzC,YAAA,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;gBAAE;YAChC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;gBAAE;;;AAI/C,YAAA,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE;YACrE,IAAI,CAAC,SAAS,EAAE;gBACd,OAAO,CAAC,IAAI,CACV,mFAAmF;AACjF,oBAAA,qFAAqF,CACxF;YACH;AACF,QAAA,CAAC,CAAC;IACJ;IAEA,kBAAkB,GAAA;AAChB,QAAA,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC;IACnC;uGApDW,MAAM,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAN,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAM,yrBAQE,UAAU,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FARlB,MAAM,EAAA,UAAA,EAAA,CAAA;kBARlB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,UAAU;AACpB,oBAAA,IAAI,EAAE;AACJ,wBAAA,qBAAqB,EAAE,WAAW;AAClC,wBAAA,yBAAyB,EAAE,2BAA2B;AACtD,wBAAA,sBAAsB,EAAE,wBAAwB;AACjD,qBAAA;AACF,iBAAA;mGASG,UAAU,CAAC,MAAM,UAAU,CAAC,EAAA,EAAA,GAC5B,EAAE,WAAW,EAAE,IAAI,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,OAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,SAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,WAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,aAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,UAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;AA8CzB;;;;;;;;;;AAUG;MAEU,YAAY,CAAA;uGAAZ,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAZ,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAZ,YAAY,EAAA,UAAA,EAAA,CAAA;kBADxB,SAAS;mBAAC,EAAE,QAAQ,EAAE,gBAAgB,EAAE;;AAGzC;;;;;;;;;AASG;MAEU,WAAW,CAAA;uGAAX,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAX,WAAW,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAX,WAAW,EAAA,UAAA,EAAA,CAAA;kBADvB,SAAS;mBAAC,EAAE,QAAQ,EAAE,eAAe,EAAE;;AAGxC;;;;;;;;AAQG;MAEU,aAAa,CAAA;uGAAb,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB,SAAS;mBAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE;;AAG1C;;;;;;;;AAQG;MAEU,aAAa,CAAA;uGAAb,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBADzB,SAAS;mBAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE;;AAG1C;;;;;;;;;;;;;;;;;;;;;AAqBG;MASU,UAAU,CAAA;AACJ,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;AAChE,IAAA,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACxD;;;;AAIG;AACgB,IAAA,IAAI,GAAG,MAAM,CAC9B,UAAU,CAAC,MAAM,MAAM,CAAC,EACxB,EAAE,QAAQ,EAAE,IAAI,EAAE,CACnB;AAED,IAAA,WAAW,CAAC,KAAY,EAAA;AACtB,QAAA,IAAI,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE;YACzB,KAAK,CAAC,cAAc,EAAE;YACtB,KAAK,CAAC,eAAe,EAAE;QACzB;IACF;IAEA,eAAe,GAAA;;QAEb,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE;QAExB,MAAM,OAAO,GACX,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE;YAC/B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE;AAC9C,YAAA,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC;QAC3C,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,CAAC,IAAI,CACV,sFAAsF;AACpF,gBAAA,0CAA0C,CAC7C;QACH;IACF;uGAlCW,UAAU,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAV,UAAU,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,mCAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,OAAA,EAAA,qBAAA,EAAA,EAAA,UAAA,EAAA,EAAA,oBAAA,EAAA,oCAAA,EAAA,eAAA,EAAA,kCAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAV,UAAU,EAAA,UAAA,EAAA,CAAA;kBARtB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,mCAAmC;AAC7C,oBAAA,IAAI,EAAE;AACJ,wBAAA,sBAAsB,EAAE,kCAAkC;AAC1D,wBAAA,iBAAiB,EAAE,gCAAgC;AACnD,wBAAA,SAAS,EAAE,qBAAqB;AACjC,qBAAA;AACF,iBAAA;;;AC9MD;;;AAGG;AACI,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU;;ACRzG;;AAEG;;;;"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { DOCUMENT } from '@angular/common';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
+
import { inject, Injectable, InjectionToken, isDevMode } from '@angular/core';
|
|
3
4
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
4
5
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
5
6
|
import { map } from 'rxjs';
|
|
@@ -97,6 +98,43 @@ function createKtSheetDrag(opts) {
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Verrou de défilement du `<body>` à COMPTEUR DE RÉFÉRENCES, partagé (`providedIn: 'root'`).
|
|
103
|
+
*
|
|
104
|
+
* Plusieurs surfaces modales simultanées (sheets, popups) peuvent verrouiller le scroll du fond :
|
|
105
|
+
* `overflow: hidden` n'est posé qu'au premier `lock()` et restauré (à sa valeur d'origine) qu'au
|
|
106
|
+
* dernier `unlock()`. Sans ce compteur, deux consommateurs par-instance se désynchronisent — la
|
|
107
|
+
* fermeture du premier restaure le scroll alors que le second est encore ouvert.
|
|
108
|
+
*/
|
|
109
|
+
class KtBodyScrollLock {
|
|
110
|
+
doc = inject(DOCUMENT);
|
|
111
|
+
count = 0;
|
|
112
|
+
previousOverflow = '';
|
|
113
|
+
/** Pose un verrou (premier appelant : mémorise puis force `overflow: hidden`). */
|
|
114
|
+
lock() {
|
|
115
|
+
if (this.count === 0) {
|
|
116
|
+
this.previousOverflow = this.doc.body.style.overflow;
|
|
117
|
+
this.doc.body.style.overflow = 'hidden';
|
|
118
|
+
}
|
|
119
|
+
this.count++;
|
|
120
|
+
}
|
|
121
|
+
/** Relâche un verrou ; restaure l'`overflow` d'origine quand le dernier consommateur relâche. */
|
|
122
|
+
unlock() {
|
|
123
|
+
if (this.count === 0)
|
|
124
|
+
return;
|
|
125
|
+
this.count--;
|
|
126
|
+
if (this.count === 0) {
|
|
127
|
+
this.doc.body.style.overflow = this.previousOverflow;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
131
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, providedIn: 'root' });
|
|
132
|
+
}
|
|
133
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtBodyScrollLock, decorators: [{
|
|
134
|
+
type: Injectable,
|
|
135
|
+
args: [{ providedIn: 'root' }]
|
|
136
|
+
}] });
|
|
137
|
+
|
|
100
138
|
/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */
|
|
101
139
|
const KT_DEFAULT_BREAKPOINTS = { mobile: 0, tablet: 600, desktop: 1024 };
|
|
102
140
|
/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */
|
|
@@ -219,9 +257,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
219
257
|
}]
|
|
220
258
|
}] });
|
|
221
259
|
|
|
260
|
+
/**
|
|
261
|
+
* InjectionToken permettant d'activer ou désactiver les audits d'accessibilité à l'exécution
|
|
262
|
+
* (gardes-fous console.warn en mode dev). Activé par défaut si `isDevMode()` est vrai.
|
|
263
|
+
*/
|
|
264
|
+
const KT_AUDIT_ENABLED = new InjectionToken('KT_AUDIT_ENABLED', {
|
|
265
|
+
providedIn: 'root',
|
|
266
|
+
factory: () => isDevMode(),
|
|
267
|
+
});
|
|
268
|
+
|
|
222
269
|
/**
|
|
223
270
|
* Generated bundle index. Do not edit.
|
|
224
271
|
*/
|
|
225
272
|
|
|
226
|
-
export { KT_BREAKPOINTS, KT_DEFAULT_BREAKPOINTS, KtIdGenerator, KtViewport, createKtSheetDrag, provideKtBreakpoints, resolveKtBreakpointMedia };
|
|
273
|
+
export { KT_AUDIT_ENABLED, KT_BREAKPOINTS, KT_DEFAULT_BREAKPOINTS, KtBodyScrollLock, KtIdGenerator, KtViewport, createKtSheetDrag, provideKtBreakpoints, resolveKtBreakpointMedia };
|
|
227
274
|
//# sourceMappingURL=ktortu-aaa-cdk.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n /** Démarre le suivi du glissement ; à brancher sur le `pointerdown` de la poignée. */\n start(event: PointerEvent): void;\n /** Détache les écouteurs et réinitialise l'état ; à appeler à la destruction. */\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n *\n * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.\n * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).\n * @example\n * ```ts\n * const drag = createKtSheetDrag({\n * pane: () => paneEl(),\n * onDismiss: () => dialogRef.close(),\n * draggingClass: 'kt-sheet--dragging',\n * });\n * // <div class=\"handle\" (pointerdown)=\"drag.start($event)\"></div>\n * ```\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n let ownerDoc: Document | null = null;\n\n const cleanupListeners = (): void => {\n if (!ownerDoc) return;\n ownerDoc.removeEventListener('pointermove', onMove);\n ownerDoc.removeEventListener('pointerup', onEnd);\n ownerDoc.removeEventListener('pointercancel', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n\n const dragged = event.clientY - startY;\n const dismiss = dragged > pane.getBoundingClientRect().height * threshold;\n // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.\n const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n if (reduceMotion) {\n // draggingClass (transition coupée) encore présente : la translation finale est instantanée,\n // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).\n pane.style.translate = dismiss ? '0 100%' : '';\n pane.classList.remove(opts.draggingClass);\n } else {\n pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)\n pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0\n }\n if (dismiss) opts.onDismiss();\n\n pane = null;\n handle = null;\n ownerDoc = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n ownerDoc = handle.ownerDocument;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une\n // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.\n ownerDoc.addEventListener('pointermove', onMove);\n ownerDoc.addEventListener('pointerup', onEnd);\n ownerDoc.addEventListener('pointercancel', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n ownerDoc = null;\n },\n };\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.\n * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n /** Media query de la bande mobile. */\n mobile: string;\n /** Media query de la bande tablette. */\n tablet: string;\n /** Media query de la bande desktop. */\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n * @param bp Seuils responsive à convertir (nombres et/ou chaînes).\n * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).\n * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,\n * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.\n *\n * @example\n * ```ts\n * const idGen = inject(KtIdGenerator);\n * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;\n * ```\n */\n@Injectable({\n // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n /**\n * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,\n * utilise un compteur dédié à ce préfixe (séquences indépendantes).\n * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.\n * @returns Le compteur courant (number) pour la portée demandée, avant incrément.\n */\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ktortu-aaa-cdk.mjs","sources":["../../../../projects/ktortu/aaa/cdk/sheet/sheet-drag.ts","../../../../projects/ktortu/aaa/cdk/sheet/body-scroll-lock.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/breakpoints.ts","../../../../projects/ktortu/aaa/cdk/breakpoints/viewport.ts","../../../../projects/ktortu/aaa/cdk/id-generator.ts","../../../../projects/ktortu/aaa/cdk/audit.ts","../../../../projects/ktortu/aaa/cdk/ktortu-aaa-cdk.ts"],"sourcesContent":["/** Contrôleur de drag-to-dismiss d'une bottom-sheet. À câbler sur le `pointerdown` de la poignée. */\nexport interface KtSheetDrag {\n /** Démarre le suivi du glissement ; à brancher sur le `pointerdown` de la poignée. */\n start(event: PointerEvent): void;\n /** Détache les écouteurs et réinitialise l'état ; à appeler à la destruction. */\n destroy(): void;\n}\n\nexport interface KtSheetDragOptions {\n /** Élément translaté pendant le glissement (la feuille elle-même). Lu à chaque `start`. */\n pane: () => HTMLElement | null;\n /** Fermeture déclenchée quand le seuil est franchi (ex. `expanded.set(false)` / `dialogRef.close()`). */\n onDismiss: () => void;\n /** Classe togglée sur le pane pendant le drag (coupe la transition CSS du glissement). */\n draggingClass: string;\n /** Fraction de la hauteur de la feuille au-delà de laquelle on ferme (défaut 0.25). */\n threshold?: number;\n}\n\n/**\n * Drag-to-dismiss vertical (vers le BAS uniquement) d'une bottom-sheet, factorisé entre le Select\n * (popup Popover) et le Dialog (pane CDK). Chaque appelant ne fournit que SON élément à translater\n * et SON callback de fermeture.\n *\n * Geste DOUBLÉ côté appelant par Échap + bouton Fermer + tap-extérieur (WCAG 2.5.1) : ce module ne\n * gère que le glissement. Le gating « écran compact uniquement » reste à l'appelant (signal `isCompact`\n * de `KtViewport`) — la primitive est volontairement agnostique.\n *\n * @param opts Élément à translater, callback de fermeture, classe de drag et seuil optionnel.\n * @returns Un contrôleur {@link KtSheetDrag} (`start` à brancher sur `pointerdown`, `destroy` au teardown).\n * @example\n * ```ts\n * const drag = createKtSheetDrag({\n * pane: () => paneEl(),\n * onDismiss: () => dialogRef.close(),\n * draggingClass: 'kt-sheet--dragging',\n * });\n * // <div class=\"handle\" (pointerdown)=\"drag.start($event)\"></div>\n * ```\n */\nexport function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag {\n const threshold = opts.threshold ?? 0.25;\n let startY = 0;\n let pane: HTMLElement | null = null;\n let handle: HTMLElement | null = null;\n let ownerDoc: Document | null = null;\n\n const cleanupListeners = (): void => {\n if (!ownerDoc) return;\n ownerDoc.removeEventListener('pointermove', onMove);\n ownerDoc.removeEventListener('pointerup', onEnd);\n ownerDoc.removeEventListener('pointercancel', onEnd);\n };\n\n const onMove = (event: PointerEvent): void => {\n if (!pane) return;\n const dy = Math.max(0, event.clientY - startY); // on ne tire que vers le bas\n pane.style.translate = `0 ${dy}px`;\n };\n\n const onEnd = (event: PointerEvent): void => {\n if (!pane || !handle) return;\n cleanupListeners();\n\n const dragged = event.clientY - startY;\n const dismiss = dragged > pane.getBoundingClientRect().height * threshold;\n // WCAG 2.3.3 : sous prefers-reduced-motion, on applique l'état final SANS animation.\n const reduceMotion = !!ownerDoc?.defaultView?.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n if (reduceMotion) {\n // draggingClass (transition coupée) encore présente : la translation finale est instantanée,\n // puis on retire la classe (aucun changement de valeur ⇒ aucune animation déclenchée).\n pane.style.translate = dismiss ? '0 100%' : '';\n pane.classList.remove(opts.draggingClass);\n } else {\n pane.classList.remove(opts.draggingClass); // réactive la transition CSS (glissement / snap-back)\n pane.style.translate = dismiss ? '0 100%' : ''; // dismiss : glisse jusqu'en bas ; sinon snap-back à 0\n }\n if (dismiss) opts.onDismiss();\n\n pane = null;\n handle = null;\n ownerDoc = null;\n };\n\n return {\n start(event: PointerEvent): void {\n if (event.button !== 0) return;\n const el = opts.pane();\n if (!el) return;\n handle = event.currentTarget as HTMLElement;\n ownerDoc = handle.ownerDocument;\n // La poignée ne doit PAS voler le focus (sinon un close-on-blur fermerait prématurément).\n event.preventDefault();\n startY = event.clientY;\n pane = el;\n pane.classList.add(opts.draggingClass);\n // Écoute au niveau du document : un pointer qui sort de la poignée (petite cible) ou une\n // capture perdue n'interrompt plus le suivi du glissement ni le snap-back.\n ownerDoc.addEventListener('pointermove', onMove);\n ownerDoc.addEventListener('pointerup', onEnd);\n ownerDoc.addEventListener('pointercancel', onEnd);\n },\n destroy(): void {\n cleanupListeners();\n pane = null;\n handle = null;\n ownerDoc = null;\n },\n };\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, inject } from '@angular/core';\n\n/**\n * Verrou de défilement du `<body>` à COMPTEUR DE RÉFÉRENCES, partagé (`providedIn: 'root'`).\n *\n * Plusieurs surfaces modales simultanées (sheets, popups) peuvent verrouiller le scroll du fond :\n * `overflow: hidden` n'est posé qu'au premier `lock()` et restauré (à sa valeur d'origine) qu'au\n * dernier `unlock()`. Sans ce compteur, deux consommateurs par-instance se désynchronisent — la\n * fermeture du premier restaure le scroll alors que le second est encore ouvert.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtBodyScrollLock {\n private readonly doc = inject(DOCUMENT);\n private count = 0;\n private previousOverflow = '';\n\n /** Pose un verrou (premier appelant : mémorise puis force `overflow: hidden`). */\n lock(): void {\n if (this.count === 0) {\n this.previousOverflow = this.doc.body.style.overflow;\n this.doc.body.style.overflow = 'hidden';\n }\n this.count++;\n }\n\n /** Relâche un verrou ; restaure l'`overflow` d'origine quand le dernier consommateur relâche. */\n unlock(): void {\n if (this.count === 0) return;\n this.count--;\n if (this.count === 0) {\n this.doc.body.style.overflow = this.previousOverflow;\n }\n }\n}\n","import { InjectionToken, Provider } from '@angular/core';\n\n/**\n * Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :\n * - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une\n * media query LARGEUR SEULE (bornes calculées par rapport au palier suivant) ;\n * - une **chaîne** = media query complète du palier, utilisée VERBATIM (échappatoire pour un critère\n * non-largeur : `orientation`, `pointer`, ratio…).\n */\nexport type KtBreakpointValue = number | string;\n\n/**\n * Seuils responsive de la lib, surchargeables par appli via `provideKtBreakpoints`. Sémantique\n * « plancher de palier » : chaque valeur indique où le palier COMMENCE → 3 bandes mutuellement\n * exclusives (`mobile` < `tablet` < `desktop`).\n */\nexport interface KtBreakpoints {\n /** Plancher du mobile (typiquement 0). */\n mobile: KtBreakpointValue;\n /** Largeur où commence la tablette (= plafond mobile). */\n tablet: KtBreakpointValue;\n /** Largeur où commence le desktop (= plafond tablette). */\n desktop: KtBreakpointValue;\n}\n\n/** Défauts : bascule bottom-sheet sous 600px (aligné sur l'ancien `MOBILE_QUERY`). */\nexport const KT_DEFAULT_BREAKPOINTS: KtBreakpoints = { mobile: 0, tablet: 600, desktop: 1024 };\n\n/** Token des seuils responsive. Défaut = `KT_DEFAULT_BREAKPOINTS` ; surchargé par `provideKtBreakpoints`. */\nexport const KT_BREAKPOINTS = new InjectionToken<KtBreakpoints>('KT_BREAKPOINTS', {\n providedIn: 'root',\n factory: () => KT_DEFAULT_BREAKPOINTS,\n});\n\n/**\n * À ajouter aux `providers` de `app.config.ts`. Fusionne avec les défauts → surcharge partielle OK.\n * @param overrides Seuils à surcharger ; les clés absentes gardent leur défaut `KT_DEFAULT_BREAKPOINTS`.\n * @returns Provider liant `KT_BREAKPOINTS` aux seuils fusionnés.\n * @example provideKtBreakpoints({ tablet: 768, desktop: 1200 })\n * @example provideKtBreakpoints({ tablet: '(min-width: 768px) and (orientation: landscape)' })\n */\nexport function provideKtBreakpoints(overrides: Partial<KtBreakpoints>): Provider {\n return { provide: KT_BREAKPOINTS, useValue: { ...KT_DEFAULT_BREAKPOINTS, ...overrides } };\n}\n\n/** Trois chaînes de media queries résolues, prêtes pour `matchMedia` / `BreakpointObserver`. */\nexport interface KtBreakpointMedia {\n /** Media query de la bande mobile. */\n mobile: string;\n /** Media query de la bande tablette. */\n tablet: string;\n /** Media query de la bande desktop. */\n desktop: string;\n}\n\n/** Évite le chevauchement d'1px entre deux bandes adjacentes (max-width juste sous le plancher suivant). */\nconst EPSILON = 0.02;\n\nfunction minWidth(v: number): string {\n return `(min-width: ${v}px)`;\n}\n\nfunction maxWidth(nextFloor: number): string {\n return `(max-width: ${nextFloor - EPSILON}px)`;\n}\n\n/** `not all` ne matche jamais : repli sûr (= desktop) si une bande numérique se retrouve sans borne. */\nfunction join(...parts: string[]): string {\n return parts.filter(Boolean).join(' and ') || 'not all';\n}\n\n/**\n * Convertit les seuils (nombres et/ou chaînes) en 3 media queries. Un palier-chaîne est repris\n * verbatim ; un palier-nombre devient la bande `[plancher courant .. plancher suivant[`. Si le\n * plancher suivant est une chaîne (pas de borne numérique), la bande reste ouverte de ce côté.\n * @param bp Seuils responsive à convertir (nombres et/ou chaînes).\n * @returns Les trois media queries résolues (`mobile`, `tablet`, `desktop`).\n * @example resolveKtBreakpointMedia(KT_DEFAULT_BREAKPOINTS)\n */\nexport function resolveKtBreakpointMedia(bp: KtBreakpoints): KtBreakpointMedia {\n const { mobile, tablet, desktop } = bp;\n\n return {\n mobile:\n typeof mobile === 'string'\n ? mobile\n : join(mobile > 0 ? minWidth(mobile) : '', typeof tablet === 'number' ? maxWidth(tablet) : ''),\n tablet:\n typeof tablet === 'string'\n ? tablet\n : join(minWidth(tablet), typeof desktop === 'number' ? maxWidth(desktop) : ''),\n desktop: typeof desktop === 'string' ? desktop : minWidth(desktop),\n };\n}\n","import { Injectable, Signal, inject } from '@angular/core';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { BreakpointObserver } from '@angular/cdk/layout';\nimport { map } from 'rxjs';\nimport { KT_BREAKPOINTS, resolveKtBreakpointMedia } from './breakpoints';\n\n/**\n * Expose l'état responsive courant en SIGNALS, dérivé des seuils configurés par l'appli\n * (`KT_BREAKPOINTS` / `provideKtBreakpoints`). Fondé sur `BreakpointObserver` (`@angular/cdk/layout`,\n * déjà dépendance) : SSR-safe (`matches: false` côté serveur → desktop par défaut) et nettoyage des\n * listeners géré par le CDK.\n *\n * À injecter aussi bien dans les composants de la lib (bascule bottom-sheet) que dans le code des\n * applis consommatrices pour leur propre layout responsive.\n */\n@Injectable({ providedIn: 'root' })\nexport class KtViewport {\n private readonly observer = inject(BreakpointObserver);\n\n /** Media queries résolues (largeur dérivée ou verbatim selon le token) — utile pour miroir CSS. */\n readonly media = resolveKtBreakpointMedia(inject(KT_BREAKPOINTS));\n\n /** Le viewport est dans la bande mobile. */\n readonly isMobile = this.matches(this.media.mobile);\n /** Le viewport est dans la bande tablette. */\n readonly isTablet = this.matches(this.media.tablet);\n /** Le viewport est dans la bande desktop. */\n readonly isDesktop = this.matches(this.media.desktop);\n /** Alias de `isMobile` : palier où les composants de la lib basculent en bottom-sheet. */\n readonly isCompact = this.isMobile;\n\n private matches(query: string): Signal<boolean> {\n return toSignal(this.observer.observe(query).pipe(map((state) => state.matches)), {\n initialValue: false,\n });\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * Génère des identifiants numériques incrémentaux uniques au sein de l'application, soit globalement,\n * soit par préfixe (compteur dédié). Sert à construire des `anchor-name` / `id` stables et distincts.\n *\n * @example\n * ```ts\n * const idGen = inject(KtIdGenerator);\n * const anchor = `--kt-menu-anchor-${idGen.generateId('menu')}`;\n * ```\n */\n@Injectable({\n // Stryker disable next-line all: `providedIn` doit rester statiquement analysable par l'AOT (NG1010).\n providedIn: 'root',\n})\nexport class KtIdGenerator {\n private readonly counters = new Map<string, number>();\n private nextGlobalId = 0;\n\n /**\n * Renvoie le prochain identifiant. Sans `prefix`, utilise le compteur global ; avec `prefix`,\n * utilise un compteur dédié à ce préfixe (séquences indépendantes).\n * @param prefix Clé optionnelle de compteur ; chaque préfixe a sa propre séquence.\n * @returns Le compteur courant (number) pour la portée demandée, avant incrément.\n */\n generateId(prefix?: string): number {\n if (!prefix) {\n return this.nextGlobalId++;\n }\n const current = this.counters.get(prefix) ?? 0;\n this.counters.set(prefix, current + 1);\n return current;\n }\n}\n","import { InjectionToken, isDevMode } from '@angular/core';\n\n/**\n * InjectionToken permettant d'activer ou désactiver les audits d'accessibilité à l'exécution\n * (gardes-fous console.warn en mode dev). Activé par défaut si `isDevMode()` est vrai.\n */\nexport const KT_AUDIT_ENABLED = new InjectionToken<boolean>('KT_AUDIT_ENABLED', {\n providedIn: 'root',\n factory: () => isDevMode(),\n});\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|