@ktortu/aaa 0.9.9 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/dialog/dialog.css +85 -0
- package/fesm2022/ktortu-aaa-dialog.mjs +172 -13
- package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-i18n.mjs +7 -0
- package/fesm2022/ktortu-aaa-i18n.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-icon.mjs +174 -0
- package/fesm2022/ktortu-aaa-icon.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-layout.mjs +12 -2
- package/fesm2022/ktortu-aaa-layout.mjs.map +1 -1
- package/fesm2022/ktortu-aaa.mjs +1 -0
- package/fesm2022/ktortu-aaa.mjs.map +1 -1
- package/icon/icon.css +52 -0
- package/package.json +5 -1
- package/styles/icon.css +7 -0
- package/styles/styles.css +1 -0
- package/types/ktortu-aaa-dialog.d.ts +92 -7
- package/types/ktortu-aaa-i18n.d.ts +3 -0
- package/types/ktortu-aaa-icon.d.ts +158 -0
- package/types/ktortu-aaa-layout.d.ts +11 -1
- package/types/ktortu-aaa.d.ts +1 -0
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ export class Exemple {
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Points d'entrée TypeScript : `@ktortu/aaa/button`, `/card`, `/dialog`, `/layout`, `/menu`, `/snackbar`, `/tabs`,
|
|
43
|
-
`/tooltip`, `/forms`, `/cdk` (breakpoints, viewport, sheet-drag, id-generator) et `/i18n`
|
|
43
|
+
`/tooltip`, `/forms`, `/icon` (icônes), `/cdk` (breakpoints, viewport, sheet-drag, id-generator) et `/i18n`
|
|
44
44
|
(traductions). La racine `@ktortu/aaa` ré-exporte tout par commodité, mais importer depuis le
|
|
45
45
|
point d'entrée précis préserve le tree-shaking. Les thèmes sont des fichiers CSS (cf. §Styles),
|
|
46
46
|
pas un point d'entrée TypeScript.
|
|
@@ -90,15 +90,49 @@ type `Partial<…>`) ou via les helpers `provideKt*` :
|
|
|
90
90
|
```ts
|
|
91
91
|
import { KT_BUTTON_CONFIG } from '@ktortu/aaa/button';
|
|
92
92
|
import { provideKtBreakpoints } from '@ktortu/aaa/cdk';
|
|
93
|
-
import { provideKtDialogDefaults } from '@ktortu/aaa/dialog';
|
|
93
|
+
import { provideKtDialog, provideKtDialogDefaults } from '@ktortu/aaa/dialog';
|
|
94
94
|
|
|
95
95
|
providers: [
|
|
96
96
|
{ provide: KT_BUTTON_CONFIG, useValue: { size: 'lg' } },
|
|
97
97
|
provideKtBreakpoints({ tablet: 768, desktop: 1200 }),
|
|
98
|
-
provideKtDialogDefaults({ maxWidth: '40rem' }),
|
|
98
|
+
provideKtDialogDefaults({ maxWidth: '40rem' }), // config CDK du dialog
|
|
99
|
+
provideKtDialog({ sheetCloseButton: true }), // options maison du dialog
|
|
99
100
|
];
|
|
100
101
|
```
|
|
101
102
|
|
|
103
|
+
Le dialog a **deux** providers, complémentaires et sans recouvrement :
|
|
104
|
+
`provideKtDialogDefaults()` porte la config `@angular/cdk/dialog` (plancher a11y, largeurs,
|
|
105
|
+
classes) ; `provideKtDialog()` porte les options propres à la lib (`KT_DIALOG_CONFIG`).
|
|
106
|
+
|
|
107
|
+
#### Bouton de fermeture des bottom-sheets
|
|
108
|
+
|
|
109
|
+
`sheetCloseButton` ajoute une croix auto-rendue en haut de la carte, hors flux (cible tactile
|
|
110
|
+
44 px, WCAG 2.5.5). **Désactivée par défaut** — pour une raison de compatibilité, pas d'ergonomie.
|
|
111
|
+
|
|
112
|
+
`[ktDialogHeader]` ne rend aucune croix : c'est une simple rangée flex, où l'on POSE la sienne
|
|
113
|
+
(`<button ktButton iconOnly icon="close" ktDialogClose>`). Mais comme c'est la composition usuelle,
|
|
114
|
+
activer l'option par défaut ferait apparaître une **deuxième** croix chez ces consommateurs-là sans
|
|
115
|
+
qu'ils aient rien changé. Le conteneur avertit en mode dev si les deux coexistent hors barre
|
|
116
|
+
d'actions.
|
|
117
|
+
|
|
118
|
+
Recommandée, donc, sur toute sheet qui ne pose pas déjà son propre bouton de fermeture en tête.
|
|
119
|
+
|
|
120
|
+
Surchargeable par ouverture, comme `presentation` :
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export const injectShareSheet = () =>
|
|
124
|
+
shareSheet.injectOpener(ShareSheet, { presentation: 'sheet', sheetCloseButton: true });
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
La position s'ajuste **en CSS seul** (propriétés logiques, donc correctes en RTL) :
|
|
128
|
+
`--dialog-close-inset-block-start`, `--dialog-close-inset-inline-end`, `--dialog-close-size`,
|
|
129
|
+
`--dialog-close-icon-size`, `--dialog-close-color`, `--dialog-close-bg`, `--dialog-close-glyph`.
|
|
130
|
+
Chacune retombe sur une couche partagée `--kt-sheet-close-*`, pour harmoniser d'un coup ce bouton
|
|
131
|
+
et celui du Select compact.
|
|
132
|
+
|
|
133
|
+
L'option `sheetHandle: false` retire la poignée décorative (ADR-0005). Elle remplace le
|
|
134
|
+
`panelClass` `kt-dialog--no-handle`, **déprécié** mais toujours honoré.
|
|
135
|
+
|
|
102
136
|
## Styles (CSS)
|
|
103
137
|
|
|
104
138
|
L'apparence vit dans des **feuilles CSS globales** thémées par des tokens `--kt-*` : importez le
|
|
@@ -169,6 +203,7 @@ Bundles disponibles :
|
|
|
169
203
|
| `@ktortu/aaa/snackbar.css` | snackbar (requiert l'overlay CDK + `a11y-prebuilt.css`) |
|
|
170
204
|
| `@ktortu/aaa/forms.css` | base des formulaires (champs, chips, switch) |
|
|
171
205
|
| `@ktortu/aaa/tabs.css` | onglets |
|
|
206
|
+
| `@ktortu/aaa/icon.css` | icônes (`ktIcon`) |
|
|
172
207
|
|
|
173
208
|
> `foundation.css` doit **toujours** être importé en premier (les bundles composant en dérivent).
|
|
174
209
|
> Si vous utilisez plusieurs composants, l'agrégat `@ktortu/aaa/styles.css` fait tout cela dans le
|
|
@@ -177,6 +212,8 @@ Bundles disponibles :
|
|
|
177
212
|
> Les formulaires « riches » (Select, MultiSelect, Field, Chips) embarquent leur CSS via les
|
|
178
213
|
> composants eux-mêmes (`styleUrl`) : rien à importer en plus pour ceux-ci.
|
|
179
214
|
|
|
215
|
+
> **Note icônes remplies** : L'utilisation de `[fill]` ou `fill` sur un `ktIcon` exige de charger une police dont l'axe `FILL` est ajustable (ex. Material Symbols avec l'axe dans la requête URL).
|
|
216
|
+
|
|
180
217
|
## Développement
|
|
181
218
|
|
|
182
219
|
```bash
|
package/dialog/dialog.css
CHANGED
|
@@ -31,6 +31,22 @@
|
|
|
31
31
|
--dialog-focus-ring-color: var(--kt-focus-ring-color, #0842a0);
|
|
32
32
|
--dialog-focus-ring-width: var(--kt-focus-ring-width, 2px);
|
|
33
33
|
|
|
34
|
+
/* ============ Bouton de fermeture auto-rendu de la bottom-sheet ============ */
|
|
35
|
+
/* Rendu seulement si l'option `sheetCloseButton` est active (cf. KtDialogConfig). Chaque token
|
|
36
|
+
retombe sur une couche PARTAGÉE --kt-sheet-close-* (même mécanisme que --dialog-radius →
|
|
37
|
+
--kt-sheet-radius) : un thème harmonise d'un coup ce bouton et celui du Select compact.
|
|
38
|
+
La cible fait 44px RÉELS (WCAG 2.5.5 AAA) pour un glyphe de 1.25rem — l'anneau de focus
|
|
39
|
+
épouse donc la cible, comme sur le Select. */
|
|
40
|
+
--dialog-close-size: var(--kt-sheet-close-size, 44px);
|
|
41
|
+
--dialog-close-icon-size: var(--kt-sheet-close-icon-size, 1.25rem);
|
|
42
|
+
--dialog-close-inset-block-start: var(--kt-sheet-close-inset-block-start, 0.25rem);
|
|
43
|
+
--dialog-close-inset-inline-end: var(--kt-sheet-close-inset-inline-end, 0.25rem);
|
|
44
|
+
--dialog-close-color: var(--kt-sheet-close-color, var(--dialog-muted));
|
|
45
|
+
/* Bascules NON déclarées exprès (défauts entre parenthèses) :
|
|
46
|
+
--dialog-close-bg (transparent) : pastille opaque quand la croix flotte au-dessus d'un contenu
|
|
47
|
+
à fond propre (image, média) — sans elle, le contraste 1.4.11 n'est pas garantissable.
|
|
48
|
+
--dialog-close-glyph ('close') : ligature du glyphe. */
|
|
49
|
+
|
|
34
50
|
/* ============ Gouttière du conteneur scrollable ============ */
|
|
35
51
|
/* Le dialog a des coins arrondis + une barre d'actions sticky : la gouttière à TRACK PLEIN
|
|
36
52
|
héritée de la page y dessine un bandeau vertical disgracieux le long de l'arrondi. On force
|
|
@@ -241,6 +257,14 @@
|
|
|
241
257
|
/* Le scrim translucide disparaît en forced-colors : on garde une séparation visible. */
|
|
242
258
|
background: rgb(0 0 0 / 50%);
|
|
243
259
|
}
|
|
260
|
+
/* Le fond transparent et la couleur atténuée du bouton de fermeture sont écrasés par le
|
|
261
|
+
système : on le rattache aux couleurs de contrôle pour qu'il reste identifiable. */
|
|
262
|
+
.kt-dialog-container__sheet-close {
|
|
263
|
+
color: ButtonText;
|
|
264
|
+
}
|
|
265
|
+
.kt-dialog-container__sheet-close:focus-visible {
|
|
266
|
+
outline-color: Highlight;
|
|
267
|
+
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
/* ====================================================================================
|
|
@@ -351,6 +375,10 @@
|
|
|
351
375
|
|
|
352
376
|
/* La carte : reprend la surface du dialog (déplacée du conteneur, devenu scroller). */
|
|
353
377
|
.cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__layout {
|
|
378
|
+
/* Référentiel du bouton de fermeture absolu. IMPÉRATIF que ce soit la CARTE et non le
|
|
379
|
+
conteneur : celui-ci est le scroller à snap, un ancrage dessus suivrait le geste et sortirait
|
|
380
|
+
de l'écran au premier glissement. */
|
|
381
|
+
position: relative;
|
|
354
382
|
scroll-snap-align: end;
|
|
355
383
|
scroll-snap-stop: always;
|
|
356
384
|
display: flex;
|
|
@@ -399,6 +427,63 @@
|
|
|
399
427
|
background: var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5));
|
|
400
428
|
}
|
|
401
429
|
|
|
430
|
+
/* ===== Bouton de fermeture auto-rendu (option `sheetCloseButton`) =====
|
|
431
|
+
Hors flux : ancré en absolu sur la carte, il ne décale RIEN (zéro layout shift) et n'impose
|
|
432
|
+
aucune contrainte au markup projeté. Pas de `touch-action` : le glissement doit rester
|
|
433
|
+
saisissable AU TRAVERS du bouton (ADR-0005 — la sheet s'attrape partout). Un vrai glissement
|
|
434
|
+
n'émet pas de `click` (le navigateur le supprime après un défilement) : seul un tap ferme. */
|
|
435
|
+
.cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__sheet-close {
|
|
436
|
+
position: absolute;
|
|
437
|
+
inset-block-start: var(--dialog-close-inset-block-start);
|
|
438
|
+
inset-inline-end: var(--dialog-close-inset-inline-end);
|
|
439
|
+
/* Au-dessus d'un éventuel [ktDialogHeader] collant projeté par le consommateur (z-index 1). */
|
|
440
|
+
z-index: 2;
|
|
441
|
+
display: inline-flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
justify-content: center;
|
|
444
|
+
inline-size: var(--dialog-close-size);
|
|
445
|
+
block-size: var(--dialog-close-size);
|
|
446
|
+
padding: 0;
|
|
447
|
+
border: 0;
|
|
448
|
+
border-radius: 50%;
|
|
449
|
+
background: var(--dialog-close-bg, transparent);
|
|
450
|
+
color: var(--dialog-close-color);
|
|
451
|
+
cursor: pointer;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__sheet-close:focus-visible {
|
|
455
|
+
outline: var(--dialog-focus-ring-width) solid var(--dialog-focus-ring-color);
|
|
456
|
+
outline-offset: 2px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.kt-dialog-container__sheet-close-icon {
|
|
460
|
+
display: inline-grid;
|
|
461
|
+
place-items: center;
|
|
462
|
+
inline-size: var(--dialog-close-icon-size);
|
|
463
|
+
block-size: var(--dialog-close-icon-size);
|
|
464
|
+
overflow: hidden; /* Sécurité anti-débordement de ligature brute (cf. .kt-alert-dialog__icon) */
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.kt-dialog-container__sheet-close-icon::before {
|
|
468
|
+
content: var(--dialog-close-glyph, 'close');
|
|
469
|
+
font-family: var(--kt-icon-font, 'Material Symbols Outlined');
|
|
470
|
+
font-weight: var(--kt-icon-font-weight, normal);
|
|
471
|
+
font-style: normal;
|
|
472
|
+
font-size: var(--dialog-close-icon-size);
|
|
473
|
+
line-height: 1;
|
|
474
|
+
font-feature-settings: 'liga';
|
|
475
|
+
-webkit-font-smoothing: antialiased;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* Réserve horizontale sous la croix : sans elle, un titre long passerait DESSOUS. Ciblage
|
|
479
|
+
`> * >` (l'hôte du composant projeté est l'intermédiaire) = régions de PREMIER niveau seulement,
|
|
480
|
+
ce qui exclut volontairement un [ktDialogTitle] imbriqué dans un [ktDialogHeader] — celui-ci
|
|
481
|
+
garde son padding nul et c'est le header qui porte la réserve. */
|
|
482
|
+
.cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__layout--closable > * > [ktDialogTitle],
|
|
483
|
+
.cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__layout--closable > * > [ktDialogHeader] {
|
|
484
|
+
padding-inline-end: calc(var(--dialog-close-size) + var(--dialog-close-inset-inline-end));
|
|
485
|
+
}
|
|
486
|
+
|
|
402
487
|
/* Mouvement réduit : pas de fondu (WCAG 2.3.3). La sheet gère le sien en JS (scroll instantané). */
|
|
403
488
|
@media (prefers-reduced-motion: reduce) {
|
|
404
489
|
.cdk-overlay-pane.kt-dialog--fullscreen .cdk-dialog-container {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { DialogRef, DEFAULT_DIALOG_CONFIG, CdkDialogContainer, Dialog, DIALOG_DATA } from '@angular/cdk/dialog';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, ElementRef, Renderer2, afterNextRender, Directive, input, isDevMode, PLATFORM_ID, DestroyRef, signal, ChangeDetectionStrategy, Component, ViewContainerRef, Injectable } from '@angular/core';
|
|
3
|
+
import { inject, ElementRef, Renderer2, afterNextRender, Directive, input, isDevMode, InjectionToken, PLATFORM_ID, DestroyRef, signal, ChangeDetectionStrategy, Component, ViewContainerRef, Injectable } from '@angular/core';
|
|
4
4
|
import { KtIdGenerator, KtBodyScrollLock, KtViewport } from '@ktortu/aaa/cdk';
|
|
5
5
|
import { isPlatformBrowser } from '@angular/common';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
7
|
import * as i1 from '@angular/cdk/portal';
|
|
7
8
|
import { PortalModule } from '@angular/cdk/portal';
|
|
8
9
|
import { map } from 'rxjs/operators';
|
|
@@ -182,6 +183,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
182
183
|
* aria-labelledby). En usage simple — titre nu sans header — [ktDialogTitle] conserve son propre
|
|
183
184
|
* padding : ce header n'est à utiliser que pour les en-têtes composés (icône, close top-right…).
|
|
184
185
|
*
|
|
186
|
+
* ⚠️ Ne rend RIEN par lui-même — pas de bouton de fermeture implicite : c'est vous qui posez la
|
|
187
|
+
* croix (cf. exemple). En présentation `sheet`, l'option `sheetCloseButton` en rend une
|
|
188
|
+
* automatiquement, hors flux ; les deux ensemble feraient doublon (le conteneur avertit en dev).
|
|
189
|
+
*
|
|
185
190
|
* @example
|
|
186
191
|
* ```html
|
|
187
192
|
* <header ktDialogHeader>
|
|
@@ -256,7 +261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
256
261
|
}] });
|
|
257
262
|
/**
|
|
258
263
|
* @deprecated ADR-0005 — la poignée est désormais RENDUE AUTOMATIQUEMENT par `KtDialogContainer`
|
|
259
|
-
* en présentation `sheet` (opt-out :
|
|
264
|
+
* en présentation `sheet` (opt-out : option typée `sheetHandle: false`), et le
|
|
260
265
|
* drag-to-dismiss s'attrape sur TOUTE la surface de la sheet (scroll-snap natif). Cette
|
|
261
266
|
* directive est INERTE et son hôte est masqué (évite une double poignée) : retirez-la de vos
|
|
262
267
|
* templates. Sera supprimée dans une prochaine version majeure.
|
|
@@ -334,6 +339,41 @@ function provideKtDialogDefaults(overrides) {
|
|
|
334
339
|
useValue: { ...KT_DIALOG_AAA_DEFAULTS, ...overrides },
|
|
335
340
|
};
|
|
336
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Défauts effectifs des options maison du dialog. Objet `Required` figé : c'est la SOURCE des
|
|
344
|
+
* valeurs de repli, lue à la fois par l'ouvreur (résolution) et par le conteneur (fusion
|
|
345
|
+
* idempotente), et énumérée par la garde de complétude i18n.
|
|
346
|
+
*/
|
|
347
|
+
const DEFAULT_KT_DIALOG_CONFIG = {
|
|
348
|
+
sheetCloseButton: false,
|
|
349
|
+
sheetCloseLabel: 'Close',
|
|
350
|
+
sheetHandle: true,
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Token des options maison du dialog. Deux usages, un seul chemin de résolution :
|
|
354
|
+
* - **application** : fourni via {@link provideKtDialog} (valeur partielle) ;
|
|
355
|
+
* - **par ouverture** : `injectKtDialogOpener` REFOURNIT ce même token, déjà RÉSOLU, dans
|
|
356
|
+
* l'injecteur du conteneur (canal `container.providers` du CDK). Le conteneur fusionne
|
|
357
|
+
* toujours par-dessus {@link DEFAULT_KT_DIALOG_CONFIG} : l'opération est idempotente sur une
|
|
358
|
+
* valeur déjà résolue, et reste correcte si le conteneur est monté sans passer par l'ouvreur.
|
|
359
|
+
*/
|
|
360
|
+
const KT_DIALOG_CONFIG = new InjectionToken('KT_DIALOG_CONFIG');
|
|
361
|
+
/**
|
|
362
|
+
* Enregistre les options maison du dialog pour l'application (ou un sous-arbre).
|
|
363
|
+
* Complémentaire de {@link provideKtDialogDefaults}, qui porte la config du CDK : les deux
|
|
364
|
+
* coexistent et ne se recouvrent pas.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* // app.config.ts — bouton de fermeture sur toutes les bottom-sheets
|
|
369
|
+
* providers: [provideKtDialog({ sheetCloseButton: true })];
|
|
370
|
+
* ```
|
|
371
|
+
* @param config Options partielles fusionnées par-dessus `DEFAULT_KT_DIALOG_CONFIG`.
|
|
372
|
+
* @returns Un `Provider` pour le token `KT_DIALOG_CONFIG`.
|
|
373
|
+
*/
|
|
374
|
+
function provideKtDialog(config) {
|
|
375
|
+
return { provide: KT_DIALOG_CONFIG, useValue: config };
|
|
376
|
+
}
|
|
337
377
|
/** panelClass d'une présentation CONCRÈTE (sans variante responsive). */
|
|
338
378
|
function concreteDialogPanelClass(p) {
|
|
339
379
|
if (p === 'fullscreen')
|
|
@@ -367,6 +407,8 @@ function resolveKtDialogPanelClass(presentation = 'centered', compact = false) {
|
|
|
367
407
|
return concreteDialogPanelClass(compact ? 'sheet' : 'centered'); // 'centered-sheet'
|
|
368
408
|
}
|
|
369
409
|
|
|
410
|
+
/** panelClass d'opt-out historique de la poignée, conservé comme repli DÉPRÉCIÉ (cf. `sheetHandle`). */
|
|
411
|
+
const LEGACY_NO_HANDLE_CLASS = 'kt-dialog--no-handle';
|
|
370
412
|
class KtDialogContainer extends CdkDialogContainer {
|
|
371
413
|
dialogRef = inject(DialogRef);
|
|
372
414
|
elementRef = inject(ElementRef);
|
|
@@ -374,6 +416,14 @@ class KtDialogContainer extends CdkDialogContainer {
|
|
|
374
416
|
platformId = inject(PLATFORM_ID);
|
|
375
417
|
destroyRef = inject(DestroyRef);
|
|
376
418
|
bodyScrollLock = inject(KtBodyScrollLock);
|
|
419
|
+
/** Options maison RÉSOLUES. `injectKtDialogOpener` refournit `KT_DIALOG_CONFIG` déjà résolu dans
|
|
420
|
+
l'injecteur du conteneur : la fusion ci-dessous est alors un no-op. Elle reste indispensable
|
|
421
|
+
quand le conteneur est monté sans l'ouvreur (`dialog.open` brut), où le token remonte à
|
|
422
|
+
l'application et n'est que partiel. Un seul chemin de code, idempotent. */
|
|
423
|
+
ktConfig = {
|
|
424
|
+
...DEFAULT_KT_DIALOG_CONFIG,
|
|
425
|
+
...(inject(KT_DIALOG_CONFIG, { optional: true }) ?? {}),
|
|
426
|
+
};
|
|
377
427
|
isClosing = signal(false, /* @ts-ignore */
|
|
378
428
|
...(ngDevMode ? [{ debugName: "isClosing" }] : /* istanbul ignore next */ []));
|
|
379
429
|
// --- Sheet scroll-snap (ADR-0005) : détection du snap « fermé » + fermeture différée ---
|
|
@@ -419,14 +469,74 @@ class KtDialogContainer extends CdkDialogContainer {
|
|
|
419
469
|
if (!hasName) {
|
|
420
470
|
console.warn('[ktDialog] dialog sans nom accessible : ajoutez un [ktDialogTitle] (ou `aria-label` via la config d’ouverture) — WCAG 4.1.2.');
|
|
421
471
|
}
|
|
472
|
+
this.warnOnConfigMisuse();
|
|
473
|
+
});
|
|
474
|
+
// Repli de FOCUS INITIAL. La config par défaut de la lib vise un SÉLECTEUR
|
|
475
|
+
// (`autoFocus: '[ktDialogFocusInitial]'`) ; quand aucun élément ne le porte, le CDK ne focalise
|
|
476
|
+
// RIEN (son repli sur le conteneur n'existe que pour les modes `first-tabbable`/`dialog`/false).
|
|
477
|
+
// Le focus reste alors sur le déclencheur, HORS du dialog, et Tab promène l'utilisateur dans la
|
|
478
|
+
// page derrière. On rattrape ici sur le conteneur (tabindex -1) — jamais sur un bouton :
|
|
479
|
+
// atterrir d'emblée sur « Fermer » est un anti-pattern lecteur d'écran (WCAG 2.4.3).
|
|
480
|
+
// `preventScroll` : en présentation sheet le conteneur EST le scroller à snap, focaliser ne doit
|
|
481
|
+
// pas perturber le scroll d'entrée en cours.
|
|
482
|
+
this._focusTrapped.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
|
483
|
+
if (this.host.contains(this.host.ownerDocument.activeElement))
|
|
484
|
+
return;
|
|
485
|
+
this.host.focus({ preventScroll: true });
|
|
422
486
|
});
|
|
423
487
|
}
|
|
488
|
+
/** Garde-fous dev sur la config maison : usage hors sheet, double bouton de fermeture, opt-out
|
|
489
|
+
déprécié de la poignée. On AVERTIT sans jamais corriger en silence — un masquage automatique
|
|
490
|
+
dépendrait de l'ordre de rendu du portail et serait indébogable. */
|
|
491
|
+
warnOnConfigMisuse() {
|
|
492
|
+
if (this.hasLegacyNoHandleClass()) {
|
|
493
|
+
console.warn(`[ktDialog] panelClass '${LEGACY_NO_HANDLE_CLASS}' déprécié : utilisez l’option typée ` +
|
|
494
|
+
'`sheetHandle: false` (à l’ouverture ou via provideKtDialog).');
|
|
495
|
+
}
|
|
496
|
+
if (this.ktConfig.sheetCloseButton && !this.isSheet()) {
|
|
497
|
+
console.warn('[ktDialog] `sheetCloseButton` ne s’applique qu’à la présentation `sheet` et reste sans effet ' +
|
|
498
|
+
'ici : en centré / plein écran, posez le bouton dans un [ktDialogHeader].');
|
|
499
|
+
}
|
|
500
|
+
// Collision visuelle recherchée : une croix posée en TÊTE (typiquement dans un [ktDialogHeader]),
|
|
501
|
+
// qui doublerait celle du conteneur. Un [ktDialogClose] dans la barre d'ACTIONS est légitime
|
|
502
|
+
// (« Annuler », ou une action qui ferme au passage) et ne collisionne pas : on l'exclut.
|
|
503
|
+
// Heuristique volontairement partielle par ailleurs : elle voit la forme ATTRIBUT `ktDialogClose`,
|
|
504
|
+
// pas la forme liée `[ktDialogClose]="x"` (un binding de propriété ne reflète aucun attribut).
|
|
505
|
+
const strayClose = this.showSheetClose() &&
|
|
506
|
+
[...this.host.querySelectorAll('[ktDialogClose]')].some((el) => !el.closest('[ktDialogActions]'));
|
|
507
|
+
if (strayClose) {
|
|
508
|
+
console.warn('[ktDialog] double bouton de fermeture : `sheetCloseButton` est actif alors que le contenu ' +
|
|
509
|
+
'porte déjà un [ktDialogClose] hors barre d’actions (en-tête ?). Gardez l’un OU l’autre.');
|
|
510
|
+
}
|
|
511
|
+
}
|
|
424
512
|
isSheet() {
|
|
425
513
|
return this._config.panelClass?.includes('kt-dialog--sheet') ?? false;
|
|
426
514
|
}
|
|
427
|
-
/**
|
|
515
|
+
/** panelClass d'opt-out historique de la poignée — repli DÉPRÉCIÉ, remplacé par `sheetHandle`. */
|
|
516
|
+
hasLegacyNoHandleClass() {
|
|
517
|
+
return this._config.panelClass?.includes(LEGACY_NO_HANDLE_CLASS) ?? false;
|
|
518
|
+
}
|
|
519
|
+
/** Poignée décorative auto-rendue en présentation sheet (ADR-0005). Opt-out : option `sheetHandle`
|
|
520
|
+
(le panelClass historique reste honoré, en déprécié). */
|
|
428
521
|
showSheetHandle() {
|
|
429
|
-
|
|
522
|
+
if (this.hasLegacyNoHandleClass())
|
|
523
|
+
return false;
|
|
524
|
+
return this.ktConfig.sheetHandle;
|
|
525
|
+
}
|
|
526
|
+
/** Bouton de fermeture auto-rendu : opt-in `sheetCloseButton`, et UNIQUEMENT en présentation sheet
|
|
527
|
+
(ailleurs, la croix se pose à la main dans un `[ktDialogHeader]` — cf. `KtDialogConfig`). */
|
|
528
|
+
showSheetClose() {
|
|
529
|
+
return this.isSheet() && this.ktConfig.sheetCloseButton;
|
|
530
|
+
}
|
|
531
|
+
/** Clic sur le bouton de fermeture auto-rendu. Ne consulte VOLONTAIREMENT pas `disableClose` :
|
|
532
|
+
contrairement au tap-extérieur et à Échap (fermetures « par inadvertance » que `disableClose`
|
|
533
|
+
protège), ce bouton est une sortie EXPLICITEMENT demandée par le dev via `sheetCloseButton` —
|
|
534
|
+
le combiner à `disableClose` est le patron « une seule sortie contrôlée ». Le drapeau
|
|
535
|
+
`isClosing` garde l'idempotence face à un geste de glissement simultané. */
|
|
536
|
+
onSheetCloseClick() {
|
|
537
|
+
if (this.isClosing())
|
|
538
|
+
return;
|
|
539
|
+
this.dialogRef.close();
|
|
430
540
|
}
|
|
431
541
|
/** Tap sur le spacer (zone extérieure à la carte) = fermeture, façon backdrop. Le conteneur
|
|
432
542
|
captant désormais le tactile (pointer-events auto, requis pour le drag iOS), le clic ne
|
|
@@ -560,10 +670,23 @@ class KtDialogContainer extends CdkDialogContainer {
|
|
|
560
670
|
backdrop CDK, la fermeture au tap est donc portée ici (disableClose respecté). -->
|
|
561
671
|
<div class="kt-dialog-container__spacer" aria-hidden="true" (click)="onScrimClick()"></div>
|
|
562
672
|
}
|
|
563
|
-
<div class="kt-dialog-container__layout">
|
|
673
|
+
<div class="kt-dialog-container__layout" [class.kt-dialog-container__layout--closable]="showSheetClose()">
|
|
674
|
+
@if (showSheetClose()) {
|
|
675
|
+
<!-- Bouton de fermeture AUTO-RENDU (opt-in 'sheetCloseButton'). Placé en TÊTE du document
|
|
676
|
+
pour ouvrir l'ordre de tabulation, mais ancré en absolu sur la carte (cf. dialog.css) :
|
|
677
|
+
il ne consomme pas de place dans la colonne. Aucun touch-action ici — le geste de
|
|
678
|
+
glissement doit rester saisissable au travers du bouton (ADR-0005). -->
|
|
679
|
+
<button
|
|
680
|
+
type="button"
|
|
681
|
+
class="kt-dialog-container__sheet-close"
|
|
682
|
+
[attr.aria-label]="ktConfig.sheetCloseLabel"
|
|
683
|
+
(click)="onSheetCloseClick()"
|
|
684
|
+
>
|
|
685
|
+
<span class="kt-dialog-container__sheet-close-icon" aria-hidden="true"></span>
|
|
686
|
+
</button>
|
|
687
|
+
}
|
|
564
688
|
@if (isSheet() && showSheetHandle()) {
|
|
565
|
-
<!-- Poignée DÉCORATIVE auto-rendue : la sheet s'attrape partout (opt-out :
|
|
566
|
-
panelClass additionnel 'kt-dialog--no-handle' à l'ouverture). -->
|
|
689
|
+
<!-- Poignée DÉCORATIVE auto-rendue : la sheet s'attrape partout (opt-out : 'sheetHandle'). -->
|
|
567
690
|
<div class="kt-dialog-container__sheet-handle" aria-hidden="true"></div>
|
|
568
691
|
}
|
|
569
692
|
<ng-template cdkPortalOutlet></ng-template>
|
|
@@ -597,10 +720,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
597
720
|
backdrop CDK, la fermeture au tap est donc portée ici (disableClose respecté). -->
|
|
598
721
|
<div class="kt-dialog-container__spacer" aria-hidden="true" (click)="onScrimClick()"></div>
|
|
599
722
|
}
|
|
600
|
-
<div class="kt-dialog-container__layout">
|
|
723
|
+
<div class="kt-dialog-container__layout" [class.kt-dialog-container__layout--closable]="showSheetClose()">
|
|
724
|
+
@if (showSheetClose()) {
|
|
725
|
+
<!-- Bouton de fermeture AUTO-RENDU (opt-in 'sheetCloseButton'). Placé en TÊTE du document
|
|
726
|
+
pour ouvrir l'ordre de tabulation, mais ancré en absolu sur la carte (cf. dialog.css) :
|
|
727
|
+
il ne consomme pas de place dans la colonne. Aucun touch-action ici — le geste de
|
|
728
|
+
glissement doit rester saisissable au travers du bouton (ADR-0005). -->
|
|
729
|
+
<button
|
|
730
|
+
type="button"
|
|
731
|
+
class="kt-dialog-container__sheet-close"
|
|
732
|
+
[attr.aria-label]="ktConfig.sheetCloseLabel"
|
|
733
|
+
(click)="onSheetCloseClick()"
|
|
734
|
+
>
|
|
735
|
+
<span class="kt-dialog-container__sheet-close-icon" aria-hidden="true"></span>
|
|
736
|
+
</button>
|
|
737
|
+
}
|
|
601
738
|
@if (isSheet() && showSheetHandle()) {
|
|
602
|
-
<!-- Poignée DÉCORATIVE auto-rendue : la sheet s'attrape partout (opt-out :
|
|
603
|
-
panelClass additionnel 'kt-dialog--no-handle' à l'ouverture). -->
|
|
739
|
+
<!-- Poignée DÉCORATIVE auto-rendue : la sheet s'attrape partout (opt-out : 'sheetHandle'). -->
|
|
604
740
|
<div class="kt-dialog-container__sheet-handle" aria-hidden="true"></div>
|
|
605
741
|
}
|
|
606
742
|
<ng-template cdkPortalOutlet></ng-template>
|
|
@@ -609,17 +745,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
609
745
|
}]
|
|
610
746
|
}], ctorParameters: () => [] });
|
|
611
747
|
|
|
748
|
+
/** Retire les clés à `undefined` : une option NON fournie à l'ouverture ne doit pas écraser
|
|
749
|
+
la valeur du token global (une clé absente et une clé à `undefined` sont équivalentes ici). */
|
|
750
|
+
function definedOnly(source) {
|
|
751
|
+
return Object.fromEntries(Object.entries(source).filter(([, value]) => value !== undefined));
|
|
752
|
+
}
|
|
612
753
|
function injectKtDialogOpener(component, baseConfig) {
|
|
613
754
|
const dialog = inject(Dialog);
|
|
614
755
|
const viewport = inject(KtViewport);
|
|
615
756
|
// Optionnel : hors d'un arbre de vue (ex. service root), il n'y a pas de ViewContainerRef.
|
|
616
757
|
const viewContainerRef = inject(ViewContainerRef, { optional: true }) ?? undefined;
|
|
617
758
|
const idGen = inject(KtIdGenerator);
|
|
759
|
+
// Options maison au niveau application (partielles) : socle de la cascade résolue ci-dessous.
|
|
760
|
+
const appKtConfig = inject(KT_DIALOG_CONFIG, { optional: true });
|
|
618
761
|
return (data, config) => {
|
|
619
762
|
// La présentation (choisie par le dev) est résolue ICI, à l'ouverture : les variantes
|
|
620
763
|
// responsive lisent le signal d'écran compact. Le panelClass final = présentation concrète + extras.
|
|
621
|
-
const { presentation, panelClass, ...rest } = {
|
|
764
|
+
const { presentation, panelClass, sheetCloseButton, sheetCloseLabel, sheetHandle, ...rest } = {
|
|
765
|
+
...baseConfig,
|
|
766
|
+
...config,
|
|
767
|
+
};
|
|
622
768
|
const extra = panelClass ? (Array.isArray(panelClass) ? panelClass : [panelClass]) : [];
|
|
769
|
+
// Cascade des options maison, résolue UNE SEULE FOIS ici (ADR-0003) : défaut → token
|
|
770
|
+
// application → option d'ouverture. Le conteneur ne relit JAMAIS le token de son côté ; il
|
|
771
|
+
// reçoit cette valeur déjà résolue par le canal `container.providers` du CDK (les
|
|
772
|
+
// `providers` de la config générale, eux, n'atteignent QUE le portail de contenu).
|
|
773
|
+
const ktConfig = {
|
|
774
|
+
...DEFAULT_KT_DIALOG_CONFIG,
|
|
775
|
+
...definedOnly(appKtConfig ?? {}),
|
|
776
|
+
...definedOnly({ sheetCloseButton, sheetCloseLabel, sheetHandle }),
|
|
777
|
+
};
|
|
623
778
|
// Dédoublonné : une classe déjà fournie par la présentation (ex. `kt-dialog`) et repassée
|
|
624
779
|
// en `panelClass` par le consommateur ne doit pas apparaître deux fois.
|
|
625
780
|
const merged = [...new Set([...resolveKtDialogPanelClass(presentation, viewport.isCompact()), ...extra])];
|
|
@@ -631,7 +786,11 @@ function injectKtDialogOpener(component, baseConfig) {
|
|
|
631
786
|
// consommateur n'a pas câblé provideKtDialogDefaults(). `...rest` (config par appel) prime.
|
|
632
787
|
...KT_DIALOG_AAA_DEFAULTS,
|
|
633
788
|
viewContainerRef,
|
|
634
|
-
|
|
789
|
+
// Canal TYPÉ vers le conteneur : ces providers-là atterrissent bien dans SON injecteur.
|
|
790
|
+
container: {
|
|
791
|
+
type: KtDialogContainer,
|
|
792
|
+
providers: () => [{ provide: KT_DIALOG_CONFIG, useValue: ktConfig }],
|
|
793
|
+
},
|
|
635
794
|
ariaLabelledBy: titleId,
|
|
636
795
|
ariaDescribedBy: descId,
|
|
637
796
|
...rest,
|
|
@@ -953,5 +1112,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
953
1112
|
* Generated bundle index. Do not edit.
|
|
954
1113
|
*/
|
|
955
1114
|
|
|
956
|
-
export { KT_DIALOG_AAA_DEFAULTS, KtAlertDialog, KtConfirmDialog, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, KtQuickDialog, defineKtDialog, injectAlertDialog, injectConfirmDialog, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
|
|
1115
|
+
export { DEFAULT_KT_DIALOG_CONFIG, KT_DIALOG_AAA_DEFAULTS, KT_DIALOG_CONFIG, KtAlertDialog, KtConfirmDialog, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, KtQuickDialog, defineKtDialog, injectAlertDialog, injectConfirmDialog, injectKtDialogOpener, provideKtDialog, provideKtDialogDefaults, resolveKtDialogPanelClass };
|
|
957
1116
|
//# sourceMappingURL=ktortu-aaa-dialog.mjs.map
|