@ktortu/aaa 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/fesm2022/ktortu-aaa-button.mjs +10 -2
- package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-card.mjs +14 -5
- package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-cdk.mjs +50 -2
- package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-dialog.mjs +80 -39
- package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-disclosure.mjs +201 -0
- package/fesm2022/ktortu-aaa-disclosure.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-forms.mjs +525 -149
- package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-i18n.mjs +27 -2
- package/fesm2022/ktortu-aaa-i18n.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-menu.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 +478 -0
- package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-tabs.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 +2 -0
- package/fesm2022/ktortu-aaa.mjs.map +1 -1
- package/forms/checkbox/checkbox-group.css +0 -8
- package/forms/chips/chip-list.css +5 -0
- package/forms/field/field.css +16 -0
- package/forms/radio/radio-group.css +1 -9
- package/forms/styles/field-box.css +14 -6
- package/forms/styles/field-pending.css +46 -0
- package/forms/styles/select-panel.css +4 -0
- package/package.json +13 -1
- package/progress-bar/progress-bar.css +127 -0
- package/snackbar/snackbar-tokens.css +53 -0
- package/snackbar/snackbar.css +175 -0
- package/styles/disclosure.css +6 -0
- package/styles/forms.css +1 -0
- package/styles/progress-bar.css +5 -0
- package/styles/snackbar.css +9 -0
- package/styles/styles.css +3 -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 +3 -0
- package/types/ktortu-aaa-cdk.d.ts +27 -1
- package/types/ktortu-aaa-dialog.d.ts +89 -34
- package/types/ktortu-aaa-disclosure.d.ts +112 -0
- package/types/ktortu-aaa-forms.d.ts +238 -30
- package/types/ktortu-aaa-i18n.d.ts +3 -0
- package/types/ktortu-aaa-progress-bar.d.ts +27 -0
- package/types/ktortu-aaa-snackbar.d.ts +218 -0
- package/types/ktortu-aaa-tooltip.d.ts +0 -1
- package/types/ktortu-aaa.d.ts +2 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
|
+
gap: var(--kt-progress-bar-gap, 8px);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.kt-progress-bar-label {
|
|
9
|
+
font-family: var(--kt-font-family, sans-serif);
|
|
10
|
+
font-size: var(--kt-font-size-sm, 0.875rem);
|
|
11
|
+
color: var(--kt-text-color-secondary, var(--kt-neutral-dark, #555));
|
|
12
|
+
text-align: var(--kt-progress-bar-label-align, start);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Si le label est vide (pas d'input et ng-content vide), on le masque pour éviter le gap */
|
|
16
|
+
.kt-progress-bar-label:empty {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.kt-progress-bar-track {
|
|
21
|
+
width: 100%;
|
|
22
|
+
height: var(--kt-progress-bar-height, 4px);
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
background-color: var(--kt-progress-bar-track-color, var(--kt-neutral-light, #e0e0e0));
|
|
25
|
+
border-radius: var(--kt-progress-bar-border-radius, 2px);
|
|
26
|
+
position: relative;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.kt-progress-bar-indicator {
|
|
30
|
+
position: absolute;
|
|
31
|
+
top: 0;
|
|
32
|
+
bottom: 0;
|
|
33
|
+
left: 0;
|
|
34
|
+
right: 0;
|
|
35
|
+
background-color: var(--kt-progress-bar-indicator-color, var(--kt-primary, #0056b3));
|
|
36
|
+
transform-origin: top left;
|
|
37
|
+
transition: transform 250ms ease;
|
|
38
|
+
will-change: transform;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Mode indéterminé : triple animation parallèle dissociée */
|
|
42
|
+
:host([data-mode='indeterminate']) .kt-progress-bar-indicator {
|
|
43
|
+
animation:
|
|
44
|
+
kt-progress-bar-indeterminate-translate 2.2s infinite cubic-bezier(0.4, 0, 0.2, 1),
|
|
45
|
+
kt-progress-bar-indeterminate-scale 2.2s infinite ease-in-out,
|
|
46
|
+
kt-progress-bar-indeterminate-fade 2.2s infinite linear;
|
|
47
|
+
transform-origin: left;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Masquage visuel accessible (sr-only) */
|
|
51
|
+
.kt-sr-only {
|
|
52
|
+
position: absolute;
|
|
53
|
+
width: 1px;
|
|
54
|
+
height: 1px;
|
|
55
|
+
padding: 0;
|
|
56
|
+
margin: -1px;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
clip: rect(0, 0, 0, 0);
|
|
59
|
+
white-space: nowrap;
|
|
60
|
+
border: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes kt-progress-bar-indeterminate-translate {
|
|
64
|
+
0% {
|
|
65
|
+
translate: -100% 0;
|
|
66
|
+
}
|
|
67
|
+
100% {
|
|
68
|
+
translate: 100% 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@keyframes kt-progress-bar-indeterminate-scale {
|
|
73
|
+
0% {
|
|
74
|
+
scale: 0.15 1;
|
|
75
|
+
}
|
|
76
|
+
35% {
|
|
77
|
+
scale: 0.7 1;
|
|
78
|
+
}
|
|
79
|
+
70% {
|
|
80
|
+
scale: 0.4 1;
|
|
81
|
+
}
|
|
82
|
+
100% {
|
|
83
|
+
scale: 0.15 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@keyframes kt-progress-bar-indeterminate-fade {
|
|
88
|
+
0% {
|
|
89
|
+
opacity: 0;
|
|
90
|
+
}
|
|
91
|
+
15% {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
}
|
|
94
|
+
85% {
|
|
95
|
+
opacity: 1;
|
|
96
|
+
}
|
|
97
|
+
100% {
|
|
98
|
+
opacity: 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Support pour prefers-reduced-motion ou bypass réduit manuel */
|
|
103
|
+
@media (prefers-reduced-motion: reduce) {
|
|
104
|
+
:host([data-mode='indeterminate']) .kt-progress-bar-indicator {
|
|
105
|
+
animation: kt-progress-bar-pulsate 2s infinite ease-in-out;
|
|
106
|
+
transform: none;
|
|
107
|
+
transform-origin: center;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.kt-progress-bar-indicator.kt-reduced-motion {
|
|
112
|
+
animation: kt-progress-bar-pulsate 2s infinite ease-in-out !important;
|
|
113
|
+
transform: none !important;
|
|
114
|
+
transform-origin: center !important;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@keyframes kt-progress-bar-pulsate {
|
|
118
|
+
0% {
|
|
119
|
+
opacity: 0.3;
|
|
120
|
+
}
|
|
121
|
+
50% {
|
|
122
|
+
opacity: 0.8;
|
|
123
|
+
}
|
|
124
|
+
100% {
|
|
125
|
+
opacity: 0.3;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/* Design tokens de la snackbar (service KtSnackbar) — contrat public de theming.
|
|
2
|
+
|
|
3
|
+
PRINCIPE (identique au reste de la lib) : tout DÉRIVE du socle --kt-*. Surcharger un --kt-*
|
|
4
|
+
rethématise la snackbar en même temps que les champs, boutons et dialog. Chaque valeur reste
|
|
5
|
+
surchargeable en redéclarant son token --snackbar-* (sur :root ou un conteneur de spécificité ≥).
|
|
6
|
+
|
|
7
|
+
La snackbar est posée sur une « surface inversée » (fond sombre, texte clair) façon Material —
|
|
8
|
+
d'où des on-* dérivés de --kt-on-surface / --kt-surface, qui suivent automatiquement un thème.
|
|
9
|
+
Les variantes ne recolorisent PAS la pastille (le texte reste à contraste AAA) : elles ne pilotent
|
|
10
|
+
que l'accent + l'icône. Un thème qui veut des pastilles pleinement colorées surcharge simplement
|
|
11
|
+
--snackbar-bg / --snackbar-fg sous [data-variant='…']. */
|
|
12
|
+
@layer kt-aaa.tokens {
|
|
13
|
+
:root {
|
|
14
|
+
/* Surface inversée : fond ~ on-surface, texte ~ surface (contraste ≈ 16:1 → AAA 1.4.6). */
|
|
15
|
+
--snackbar-bg: color-mix(in oklab, var(--kt-on-surface, #1f1f1f) 92%, var(--kt-surface, #ffffff));
|
|
16
|
+
--snackbar-fg: var(--kt-surface, #ffffff);
|
|
17
|
+
|
|
18
|
+
--snackbar-radius: var(--kt-control-radius, 8px);
|
|
19
|
+
--snackbar-shadow: 0 4px 12px rgb(0 0 0 / 24%);
|
|
20
|
+
|
|
21
|
+
/* Géométrie interne */
|
|
22
|
+
--snackbar-padding-block: 0.625rem;
|
|
23
|
+
--snackbar-padding-inline: 1rem;
|
|
24
|
+
--snackbar-gap: 0.75rem;
|
|
25
|
+
--snackbar-font: var(--kt-control-font, 1rem);
|
|
26
|
+
|
|
27
|
+
/* Placement : distance au bord du viewport, avec marge de sécurité mobile (encoches / barres). */
|
|
28
|
+
--snackbar-offset: 16px;
|
|
29
|
+
--snackbar-offset-bottom: calc(var(--snackbar-offset) + env(safe-area-inset-bottom, 0px));
|
|
30
|
+
--snackbar-offset-top: calc(var(--snackbar-offset) + env(safe-area-inset-top, 0px));
|
|
31
|
+
--snackbar-max-inline-size: 560px;
|
|
32
|
+
|
|
33
|
+
/* Bouton de fermeture : cible 44px (AAA 2.5.5). */
|
|
34
|
+
--snackbar-close-size: var(--kt-control-height, 44px);
|
|
35
|
+
--snackbar-close-icon-size: 1rem;
|
|
36
|
+
|
|
37
|
+
/* Variantes : accent (icône) clair pour contraster sur la pastille sombre (≥ 3:1), et glyphe
|
|
38
|
+
(ligature de la police d'icônes --kt-icon-font). Couleur + FORME : la forme distincte est
|
|
39
|
+
l'indice non coloré (WCAG 1.4.1). Surchargez accent/glyphe pour rebrander une variante. */
|
|
40
|
+
--snackbar-icon-size: 1.25rem;
|
|
41
|
+
--snackbar-accent-info: #8ab4f8;
|
|
42
|
+
--snackbar-accent-success: #81c995;
|
|
43
|
+
--snackbar-accent-warning: #fdd663;
|
|
44
|
+
--snackbar-accent-error: #f28b82;
|
|
45
|
+
--snackbar-glyph-info: 'info';
|
|
46
|
+
--snackbar-glyph-success: 'check_circle';
|
|
47
|
+
--snackbar-glyph-warning: 'warning';
|
|
48
|
+
--snackbar-glyph-error: 'error';
|
|
49
|
+
|
|
50
|
+
/* Animation d'entrée/sortie. Token de durée surchargeable ; neutralisée en prefers-reduced-motion. */
|
|
51
|
+
--snackbar-anim-duration: 150ms;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
@layer kt-aaa.components {
|
|
2
|
+
/* Styles de la snackbar (conteneur monté dans un overlay CDK).
|
|
3
|
+
Global : l'élément est ajouté hors de toute vue (overlay top-layer).
|
|
4
|
+
Tokens (--snackbar-*) définis dans snackbar-tokens.css (chargé avant ce fichier).
|
|
5
|
+
|
|
6
|
+
Requiert côté hôte : '@angular/cdk/overlay-prebuilt.css' (positionnement de l'overlay) ET
|
|
7
|
+
'@angular/cdk/a11y-prebuilt.css' (masque l'élément du LiveAnnouncer qui porte l'annonce). */
|
|
8
|
+
|
|
9
|
+
/* Le pane d'overlay ne capte pas les clics hors de la snackbar (feedback NON bloquant). */
|
|
10
|
+
.kt-snackbar-pane {
|
|
11
|
+
pointer-events: none;
|
|
12
|
+
max-inline-size: 100vw;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.kt-snackbar {
|
|
16
|
+
pointer-events: auto; /* la snackbar elle-même reste survolable / cliquable */
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: var(--snackbar-gap);
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
|
|
22
|
+
margin: var(--snackbar-offset);
|
|
23
|
+
inline-size: max-content;
|
|
24
|
+
max-inline-size: min(var(--snackbar-max-inline-size), 100vw - 2 * var(--snackbar-offset));
|
|
25
|
+
|
|
26
|
+
padding-block: var(--snackbar-padding-block);
|
|
27
|
+
padding-inline: var(--snackbar-padding-inline);
|
|
28
|
+
|
|
29
|
+
border-radius: var(--snackbar-radius);
|
|
30
|
+
background: var(--snackbar-bg);
|
|
31
|
+
color: var(--snackbar-fg);
|
|
32
|
+
box-shadow: var(--snackbar-shadow);
|
|
33
|
+
|
|
34
|
+
font-size: var(--snackbar-font);
|
|
35
|
+
line-height: 1.4;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Marge de sécurité mobile (encoches / barres système) selon le bord d'ancrage. */
|
|
39
|
+
.kt-snackbar-pane--bottom .kt-snackbar {
|
|
40
|
+
margin-block-end: var(--snackbar-offset-bottom);
|
|
41
|
+
}
|
|
42
|
+
.kt-snackbar-pane--top .kt-snackbar {
|
|
43
|
+
margin-block-start: var(--snackbar-offset-top);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.kt-snackbar__message {
|
|
47
|
+
flex: 1 1 auto;
|
|
48
|
+
min-inline-size: 0; /* autorise le retour à la ligne d'un message long */
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ============ ICÔNE DE VARIANTE (décorative, aria-hidden) ============ */
|
|
52
|
+
/* Couleur = accent de la variante ; glyphe = ligature de la police d'icônes (comme [ktButton]).
|
|
53
|
+
Si la police d'icônes est absente, la ligature dégrade en texte (même comportement que le bouton). */
|
|
54
|
+
.kt-snackbar__icon {
|
|
55
|
+
flex: none;
|
|
56
|
+
display: inline-grid;
|
|
57
|
+
place-items: center;
|
|
58
|
+
color: var(--snackbar-accent, currentColor);
|
|
59
|
+
}
|
|
60
|
+
.kt-snackbar__icon::before {
|
|
61
|
+
content: var(--snackbar-glyph, '');
|
|
62
|
+
font-family: var(--kt-icon-font, 'Material Symbols Outlined');
|
|
63
|
+
font-weight: var(--kt-icon-font-weight, normal);
|
|
64
|
+
font-style: normal;
|
|
65
|
+
font-size: var(--snackbar-icon-size, 1.25rem);
|
|
66
|
+
line-height: 1;
|
|
67
|
+
font-feature-settings: 'liga';
|
|
68
|
+
-webkit-font-smoothing: antialiased;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Mapping variante → accent + glyphe (un thème surcharge accent/glyphe, ou bg/fg pour recolorer). */
|
|
72
|
+
.kt-snackbar[data-variant='info'] {
|
|
73
|
+
--snackbar-accent: var(--snackbar-accent-info);
|
|
74
|
+
--snackbar-glyph: var(--snackbar-glyph-info);
|
|
75
|
+
}
|
|
76
|
+
.kt-snackbar[data-variant='success'] {
|
|
77
|
+
--snackbar-accent: var(--snackbar-accent-success);
|
|
78
|
+
--snackbar-glyph: var(--snackbar-glyph-success);
|
|
79
|
+
}
|
|
80
|
+
.kt-snackbar[data-variant='warning'] {
|
|
81
|
+
--snackbar-accent: var(--snackbar-accent-warning);
|
|
82
|
+
--snackbar-glyph: var(--snackbar-glyph-warning);
|
|
83
|
+
}
|
|
84
|
+
.kt-snackbar[data-variant='error'] {
|
|
85
|
+
--snackbar-accent: var(--snackbar-accent-error);
|
|
86
|
+
--snackbar-glyph: var(--snackbar-glyph-error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ============ BOUTON DE FERMETURE ============ */
|
|
90
|
+
/* Cible 44px, croix dessinée en CSS (zéro dépendance à une police d'icônes). */
|
|
91
|
+
.kt-snackbar__close {
|
|
92
|
+
position: relative;
|
|
93
|
+
flex: none;
|
|
94
|
+
inline-size: var(--snackbar-close-size);
|
|
95
|
+
block-size: var(--snackbar-close-size);
|
|
96
|
+
/* compense optiquement le padding inline du conteneur, sans réduire la cible de 44px */
|
|
97
|
+
margin-inline-end: calc(-0.5 * var(--snackbar-padding-inline));
|
|
98
|
+
|
|
99
|
+
display: inline-grid;
|
|
100
|
+
place-items: center;
|
|
101
|
+
padding: 0;
|
|
102
|
+
border: 0;
|
|
103
|
+
border-radius: 50%;
|
|
104
|
+
background: transparent;
|
|
105
|
+
color: inherit;
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.kt-snackbar__close::before,
|
|
110
|
+
.kt-snackbar__close::after {
|
|
111
|
+
content: '';
|
|
112
|
+
position: absolute;
|
|
113
|
+
inline-size: var(--snackbar-close-icon-size);
|
|
114
|
+
block-size: 2px;
|
|
115
|
+
border-radius: 2px;
|
|
116
|
+
background: currentColor;
|
|
117
|
+
translate: 0 0;
|
|
118
|
+
}
|
|
119
|
+
.kt-snackbar__close::before {
|
|
120
|
+
rotate: 45deg;
|
|
121
|
+
}
|
|
122
|
+
.kt-snackbar__close::after {
|
|
123
|
+
rotate: -45deg;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.kt-snackbar__close:hover {
|
|
127
|
+
background: color-mix(in srgb, currentColor 14%, transparent);
|
|
128
|
+
}
|
|
129
|
+
.kt-snackbar__close:focus-visible {
|
|
130
|
+
outline: var(--kt-focus-ring-width, 2px) solid currentColor;
|
|
131
|
+
outline-offset: 2px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ============ ANIMATION D'ENTRÉE / SORTIE ============ */
|
|
135
|
+
/* Entrée par keyframe (glissement depuis le bord), sortie par transition (classe --leaving).
|
|
136
|
+
Toutes deux sont activées UNIQUEMENT hors prefers-reduced-motion → en reduced-motion la durée
|
|
137
|
+
de transition est nulle, donc playExit() démonte instantanément (WCAG 2.3.3). */
|
|
138
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
139
|
+
.kt-snackbar {
|
|
140
|
+
transition:
|
|
141
|
+
opacity var(--snackbar-anim-duration) ease,
|
|
142
|
+
translate var(--snackbar-anim-duration) ease;
|
|
143
|
+
}
|
|
144
|
+
.kt-snackbar-pane--bottom .kt-snackbar {
|
|
145
|
+
animation: kt-snackbar-in-bottom var(--snackbar-anim-duration) ease-out;
|
|
146
|
+
}
|
|
147
|
+
.kt-snackbar-pane--top .kt-snackbar {
|
|
148
|
+
animation: kt-snackbar-in-top var(--snackbar-anim-duration) ease-out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Sortie : on fond + on glisse vers le bord d'ancrage. */
|
|
152
|
+
.kt-snackbar--leaving {
|
|
153
|
+
opacity: 0;
|
|
154
|
+
}
|
|
155
|
+
.kt-snackbar-pane--bottom .kt-snackbar--leaving {
|
|
156
|
+
translate: 0 100%;
|
|
157
|
+
}
|
|
158
|
+
.kt-snackbar-pane--top .kt-snackbar--leaving {
|
|
159
|
+
translate: 0 -100%;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@keyframes kt-snackbar-in-bottom {
|
|
164
|
+
from {
|
|
165
|
+
opacity: 0;
|
|
166
|
+
translate: 0 100%;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
@keyframes kt-snackbar-in-top {
|
|
170
|
+
from {
|
|
171
|
+
opacity: 0;
|
|
172
|
+
translate: 0 -100%;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Disclosure (ktDisclosure + déclencheur + panneau) : tokens + apparence. Nécessite le socle.
|
|
4
|
+
@import '@ktortu/aaa/disclosure.css'; */
|
|
5
|
+
@import '../disclosure/disclosure-tokens.css';
|
|
6
|
+
@import '../disclosure/disclosure.css';
|
package/styles/forms.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
@import '../forms/styles/tokens.css';
|
|
7
7
|
@import '../forms/chips/tokens.css';
|
|
8
8
|
@import '../forms/styles/field-box.css';
|
|
9
|
+
@import '../forms/styles/field-pending.css';
|
|
9
10
|
@import '../forms/switch/switch.css';
|
|
10
11
|
@import '../forms/checkbox/checkbox.css';
|
|
11
12
|
@import '../forms/checkbox/checkbox-group.css';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Snackbar (service KtSnackbar, overlay CDK). Nécessite le socle (@ktortu/aaa/foundation.css),
|
|
4
|
+
ET côté hôte l'overlay CDK + le masquage du LiveAnnouncer :
|
|
5
|
+
@import '@angular/cdk/overlay-prebuilt.css';
|
|
6
|
+
@import '@angular/cdk/a11y-prebuilt.css';
|
|
7
|
+
@import '@ktortu/aaa/snackbar.css'; */
|
|
8
|
+
@import '../snackbar/snackbar-tokens.css';
|
|
9
|
+
@import '../snackbar/snackbar.css';
|
package/styles/styles.css
CHANGED
|
@@ -138,6 +138,10 @@
|
|
|
138
138
|
--tab-radius: 6px 6px 0 0;
|
|
139
139
|
--tab-border: none;
|
|
140
140
|
--tab-margin: 0 4px 0 0;
|
|
141
|
+
|
|
142
|
+
/* ============ Progress Bar ============ */
|
|
143
|
+
--kt-progress-bar-track-color: #10182a;
|
|
144
|
+
--kt-progress-bar-indicator-color: transparent;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/* Fills DUOTONE cyan→magenta (ex-Synthwave) : niveau élément pour substituer var(--btn-fill) ;
|
|
@@ -158,6 +162,13 @@
|
|
|
158
162
|
--btn-shadow-focus: 0 0 16px color-mix(in srgb, var(--btn-fg) 75%, transparent);
|
|
159
163
|
}
|
|
160
164
|
|
|
165
|
+
[data-theme='cyberpunk'] .kt-progress-bar-indicator {
|
|
166
|
+
background-image: linear-gradient(90deg, #28d6ef, #ff3d8b);
|
|
167
|
+
box-shadow:
|
|
168
|
+
0 0 8px rgb(40 214 239 / 60%),
|
|
169
|
+
0 0 12px rgb(255 61 139 / 40%);
|
|
170
|
+
}
|
|
171
|
+
|
|
161
172
|
/* LASER en bord HAUT des cartes : ligne néon qui balaie (signature « partout »). Le pseudo
|
|
162
173
|
est libre (le lien étiré utilise ::after du lien, pas de la carte). z-index au-dessus du média. */
|
|
163
174
|
[data-theme='cyberpunk'] [ktCard] {
|
package/tooltip/tooltip.css
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
white-space: pre-line; /* Interprète les \n et les sauts de ligne dans les chaînes de texte */
|
|
29
29
|
box-shadow: 0 4px 12px color-mix(in srgb, #000 25%, transparent);
|
|
30
30
|
pointer-events: auto; /* requis pour le « hoverable » (WCAG 1.4.13) */
|
|
31
|
-
user-select: text; /*
|
|
31
|
+
user-select: text; /* Autorise la sélection et la traduction du texte du tooltip. */
|
|
32
32
|
cursor: default;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -44,7 +44,9 @@ declare class KtButton implements AfterViewInit {
|
|
|
44
44
|
private readonly config;
|
|
45
45
|
private readonly host;
|
|
46
46
|
private readonly platformId;
|
|
47
|
+
private isDestroyed;
|
|
47
48
|
private readonly viewInitialized;
|
|
49
|
+
private readonly auditEnabled;
|
|
48
50
|
protected readonly isLink: boolean;
|
|
49
51
|
private readonly nativeAriaLabel;
|
|
50
52
|
/** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */
|
|
@@ -80,6 +80,7 @@ declare class KtCardActions {
|
|
|
80
80
|
*/
|
|
81
81
|
declare class KtCardLink {
|
|
82
82
|
private readonly host;
|
|
83
|
+
private readonly auditEnabled;
|
|
83
84
|
/**
|
|
84
85
|
* Carte ancêtre, injectée optionnellement via `inject(Card, { optional: true })` (DI par
|
|
85
86
|
* hiérarchie d'éléments) : `null` si [ktCardLink] est utilisé hors d'une [ktCard]. Sert à relayer
|
|
@@ -137,6 +138,7 @@ declare class KtCard {
|
|
|
137
138
|
private readonly config;
|
|
138
139
|
private readonly host;
|
|
139
140
|
private readonly platformId;
|
|
141
|
+
private readonly auditEnabled;
|
|
140
142
|
/** Référence réactive sur le lien primaire de la carte */
|
|
141
143
|
readonly cardLink: i0.Signal<KtCardLink | undefined>;
|
|
142
144
|
/** Apparence de la surface : `elevated` | `outlined` | `filled`. @default 'elevated' (ou `KT_CARD_CONFIG.variant`) */
|
|
@@ -148,6 +150,7 @@ declare class KtCard {
|
|
|
148
150
|
readonly interactive: i0.InputSignalWithTransform<boolean, unknown>;
|
|
149
151
|
/** Rend la surface inerte (état `data-disabled`). @default false */
|
|
150
152
|
readonly disabled: i0.InputSignalWithTransform<boolean, unknown>;
|
|
153
|
+
private isDestroyed;
|
|
151
154
|
constructor();
|
|
152
155
|
static ɵfac: i0.ɵɵFactoryDeclaration<KtCard, never>;
|
|
153
156
|
static ɵdir: i0.ɵɵDirectiveDeclaration<KtCard, "[ktCard]", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, ["cardLink"], never, true, never>;
|
|
@@ -42,6 +42,26 @@ interface KtSheetDragOptions {
|
|
|
42
42
|
*/
|
|
43
43
|
declare function createKtSheetDrag(opts: KtSheetDragOptions): KtSheetDrag;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Verrou de défilement du `<body>` à COMPTEUR DE RÉFÉRENCES, partagé (`providedIn: 'root'`).
|
|
47
|
+
*
|
|
48
|
+
* Plusieurs surfaces modales simultanées (sheets, popups) peuvent verrouiller le scroll du fond :
|
|
49
|
+
* `overflow: hidden` n'est posé qu'au premier `lock()` et restauré (à sa valeur d'origine) qu'au
|
|
50
|
+
* dernier `unlock()`. Sans ce compteur, deux consommateurs par-instance se désynchronisent — la
|
|
51
|
+
* fermeture du premier restaure le scroll alors que le second est encore ouvert.
|
|
52
|
+
*/
|
|
53
|
+
declare class KtBodyScrollLock {
|
|
54
|
+
private readonly doc;
|
|
55
|
+
private count;
|
|
56
|
+
private previousOverflow;
|
|
57
|
+
/** Pose un verrou (premier appelant : mémorise puis force `overflow: hidden`). */
|
|
58
|
+
lock(): void;
|
|
59
|
+
/** Relâche un verrou ; restaure l'`overflow` d'origine quand le dernier consommateur relâche. */
|
|
60
|
+
unlock(): void;
|
|
61
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<KtBodyScrollLock, never>;
|
|
62
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<KtBodyScrollLock>;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
/**
|
|
46
66
|
* Valeur d'un palier de breakpoint, CONFIGURABLE PAR APPLICATION :
|
|
47
67
|
* - un **nombre** = largeur (px) à partir de laquelle le palier commence ; la lib en dérive une
|
|
@@ -144,5 +164,11 @@ declare class KtIdGenerator {
|
|
|
144
164
|
static ɵprov: i0.ɵɵInjectableDeclaration<KtIdGenerator>;
|
|
145
165
|
}
|
|
146
166
|
|
|
147
|
-
|
|
167
|
+
/**
|
|
168
|
+
* InjectionToken permettant d'activer ou désactiver les audits d'accessibilité à l'exécution
|
|
169
|
+
* (gardes-fous console.warn en mode dev). Activé par défaut si `isDevMode()` est vrai.
|
|
170
|
+
*/
|
|
171
|
+
declare const KT_AUDIT_ENABLED: InjectionToken<boolean>;
|
|
172
|
+
|
|
173
|
+
export { KT_AUDIT_ENABLED, KT_BREAKPOINTS, KT_DEFAULT_BREAKPOINTS, KtBodyScrollLock, KtIdGenerator, KtViewport, createKtSheetDrag, provideKtBreakpoints, resolveKtBreakpointMedia };
|
|
148
174
|
export type { KtBreakpointMedia, KtBreakpointValue, KtBreakpoints, KtSheetDrag, KtSheetDragOptions };
|
|
@@ -249,22 +249,6 @@ type KtDialogPresentation = 'centered' | 'fullscreen' | 'sheet' | 'centered-full
|
|
|
249
249
|
*/
|
|
250
250
|
declare function resolveKtDialogPanelClass(presentation?: KtDialogPresentation, compact?: boolean): string[];
|
|
251
251
|
|
|
252
|
-
/**
|
|
253
|
-
* Sucre typé pour récupérer les données injectées dans un composant de dialogue (`DIALOG_DATA`).
|
|
254
|
-
* À appeler en contexte d'injection (ex. initialiseur de champ).
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* ```ts
|
|
258
|
-
* interface RenameData { currentName: string; }
|
|
259
|
-
*
|
|
260
|
-
* @Component({ … })
|
|
261
|
-
* export class RenameDialog {
|
|
262
|
-
* protected readonly data = injectKtDialogData<RenameData>();
|
|
263
|
-
* // this.data.currentName → string
|
|
264
|
-
* }
|
|
265
|
-
* ```
|
|
266
|
-
*/
|
|
267
|
-
declare function injectKtDialogData<T>(): T;
|
|
268
252
|
/** Config d'ouverture sans le champ `data` (fourni séparément, typé), enrichie de `presentation`. */
|
|
269
253
|
type KtDialogOpenerConfig<D, R, C> = Omit<DialogConfig<D, DialogRef<R, C>>, 'data'> & {
|
|
270
254
|
/** Présentation du dialog (cf. `KtDialogPresentation`) — choisie par le dev. Résolue à CHAQUE
|
|
@@ -273,39 +257,110 @@ type KtDialogOpenerConfig<D, R, C> = Omit<DialogConfig<D, DialogRef<R, C>>, 'dat
|
|
|
273
257
|
presentation?: KtDialogPresentation;
|
|
274
258
|
};
|
|
275
259
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
260
|
+
* ⚠️ API BAS-NIVEAU — n'utilisez PAS ceci par défaut. Pour implémenter un dialog, passez par
|
|
261
|
+
* {@link defineKtDialog} puis son `injectOpener(...)` : il fixe données + résultat une seule fois et
|
|
262
|
+
* les partage avec `injectData()`/`injectRef()`. N'appelez `injectKtDialogOpener` directement que
|
|
263
|
+
* dans un cas avancé où le contrat groupé de `defineKtDialog` ne convient pas.
|
|
264
|
+
*
|
|
265
|
+
* Crée un ouvreur de dialog typé en contexte d'injection : le contrat (composant + type de `data` +
|
|
266
|
+
* type de résultat + config par défaut) est figé via les génériques `<Composant, Data, Résultat>`
|
|
267
|
+
* (qu'il faut répéter — d'où la préférence pour `defineKtDialog`). L'ouvreur renvoyé est une closure
|
|
268
|
+
* appelable n'importe où dans la classe, 100 % typée, sans nouvel `inject`.
|
|
280
269
|
*
|
|
281
270
|
* Conserve le `ViewContainerRef` du contexte appelant (le dialog hérite donc de l'arbre logique
|
|
282
271
|
* d'injection), reste testable (TestBed fournit `Dialog`) et sans état global (SSR-safe).
|
|
283
272
|
*
|
|
284
|
-
* @example
|
|
285
|
-
* // co-localisé avec le composant
|
|
286
|
-
* export const injectRenameDialog = () =>
|
|
287
|
-
* injectKtDialogOpener<RenameDialog, RenameData, string>(RenameDialog, { autoFocus: '[ktDialogFocusInitial]' });
|
|
288
|
-
*
|
|
289
|
-
* // dans un composant/service consommateur
|
|
290
|
-
* private readonly openRename = injectRenameDialog();
|
|
291
|
-
* rename() { this.openRename({ currentName: 'X' }).closed.subscribe(n => { n: string | undefined }); }
|
|
292
|
-
*
|
|
293
273
|
* @param component Composant à monter dans le dialog.
|
|
294
274
|
* @param baseConfig Config d'ouverture par défaut (sans `data`), enrichie de `presentation` ; fusionnée à chaque ouverture.
|
|
295
275
|
* @returns Une closure d'ouverture typée : `() => DialogRef` (sans data) ou `(data, config?) => DialogRef` (avec data).
|
|
276
|
+
* @see {@link defineKtDialog} — la façon recommandée d'obtenir cet ouvreur.
|
|
296
277
|
*/
|
|
297
278
|
declare function injectKtDialogOpener<C, R = unknown>(component: ComponentType<C>, baseConfig?: KtDialogOpenerConfig<void, R, C>): () => DialogRef<R, C>;
|
|
298
279
|
declare function injectKtDialogOpener<C, D, R = unknown>(component: ComponentType<C>, baseConfig?: KtDialogOpenerConfig<D, R, C>): (data: D, config?: KtDialogOpenerConfig<D, R, C>) => DialogRef<R, C>;
|
|
280
|
+
/**
|
|
281
|
+
* Contrat de dialog typé renvoyé par `defineKtDialog` : les trois accès (données, référence,
|
|
282
|
+
* ouvreur) dérivent des MÊMES génériques `D`/`R`, déclarés une seule fois — impossible donc que
|
|
283
|
+
* le type des données ou du résultat diverge entre le composant et l'ouvreur.
|
|
284
|
+
*
|
|
285
|
+
* @template D Type des données injectées (`void` si aucune).
|
|
286
|
+
* @template R Type du résultat renvoyé à la fermeture.
|
|
287
|
+
*/
|
|
288
|
+
interface KtDialogContract<D, R> {
|
|
289
|
+
/** Données injectées (à appeler en contexte d'injection DANS le composant de dialog). */
|
|
290
|
+
injectData(): D;
|
|
291
|
+
/** Référence typée du dialog : `close()` n'accepte QUE le résultat `R` (sûreté de fermeture). */
|
|
292
|
+
injectRef(): DialogRef<R>;
|
|
293
|
+
/** Ouvreur typé CO-LOCALISÉ pour ce composant (cf. `injectKtDialogOpener`). */
|
|
294
|
+
injectOpener<C>(component: ComponentType<C>, baseConfig?: KtDialogOpenerConfig<D, R, C>): (data: D, config?: KtDialogOpenerConfig<D, R, C>) => DialogRef<R, C>;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* ✅ STRATÉGIE PAR DÉFAUT pour implémenter un dialog `@ktortu/aaa`. Toute nouvelle dialog DOIT être
|
|
298
|
+
* écrite ainsi. N'utilisez `injectKtDialogData` / `injectKtDialogOpener` directement QUE dans un cas
|
|
299
|
+
* avancé non couvert ici.
|
|
300
|
+
*
|
|
301
|
+
* Définit le contrat typé du dialog (données `D` + résultat `R`) en UN SEUL endroit, co-localisé avec
|
|
302
|
+
* le composant. Renvoie trois accès liés aux mêmes types — `injectData()` (données), `injectRef()`
|
|
303
|
+
* (référence dont `close()` n'accepte que `R`) et `injectOpener()` (ouvreur) — d'où l'impossibilité
|
|
304
|
+
* d'une divergence de type entre composant et ouvreur.
|
|
305
|
+
*
|
|
306
|
+
* Recette à copier pour CHAQUE dialog :
|
|
307
|
+
* 1. `interface XxxData { … }` (ou `void` si aucune donnée) ;
|
|
308
|
+
* 2. `const xxxDialog = defineKtDialog<XxxData, Résultat>();` ;
|
|
309
|
+
* 3. dans le composant : `injectData()` pour lire la donnée, `injectRef()` pour fermer avec résultat ;
|
|
310
|
+
* 4. `export const injectXxxDialog = () => xxxDialog.injectOpener(XxxDialog);` (co-localisé) ;
|
|
311
|
+
* 5. côté consommateur : `private readonly openXxx = injectXxxDialog();` (initialiseur de champ).
|
|
312
|
+
*
|
|
313
|
+
* Convention de fermeture : annuler / fermer SANS résultat via la directive `[ktDialogClose]`
|
|
314
|
+
* (→ `undefined`) ; renvoyer un résultat via `ref.close(résultat)` (typé `R`). Passer un résultat par
|
|
315
|
+
* `[ktDialogClose]="x"` reste possible mais N'EST PAS typé (la directive ignore `R`).
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* // ─── rename-dialog.ts : composant + contrat + ouvreur, co-localisés ───
|
|
320
|
+
* export interface RenameData { currentName: string; }
|
|
321
|
+
*
|
|
322
|
+
* const renameDialog = defineKtDialog<RenameData, string>(); // D et R déclarés UNE seule fois
|
|
323
|
+
*
|
|
324
|
+
* @Component({
|
|
325
|
+
* imports: [KtButton, KtDialogImports],
|
|
326
|
+
* template: `
|
|
327
|
+
* <h2 ktDialogTitle>Renommer</h2>
|
|
328
|
+
* <footer ktDialogActions>
|
|
329
|
+
* <button ktButton ktDialogClose>Annuler</button> // ferme sans résultat (undefined)
|
|
330
|
+
* <button ktButton (click)="submit()">Renommer</button> // ferme avec résultat typé
|
|
331
|
+
* </footer>`,
|
|
332
|
+
* })
|
|
333
|
+
* export class RenameDialog {
|
|
334
|
+
* protected readonly data = renameDialog.injectData(); // RenameData garanti
|
|
335
|
+
* private readonly ref = renameDialog.injectRef(); // DialogRef<string>
|
|
336
|
+
* protected submit() { this.ref.close('nouveau-nom'); } // close(result?: string) — typé
|
|
337
|
+
* }
|
|
338
|
+
*
|
|
339
|
+
* export const injectRenameDialog = () => renameDialog.injectOpener(RenameDialog);
|
|
340
|
+
*
|
|
341
|
+
* // ─── consommateur ───
|
|
342
|
+
* private readonly openRename = injectRenameDialog(); // initialiseur de champ = contexte d'injection
|
|
343
|
+
* rename() {
|
|
344
|
+
* this.openRename({ currentName: 'X' }).closed.subscribe(name => {
|
|
345
|
+
* // name: string | undefined
|
|
346
|
+
* });
|
|
347
|
+
* }
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
declare function defineKtDialog<D = void, R = unknown>(): KtDialogContract<D, R>;
|
|
299
351
|
|
|
300
|
-
declare class KtDialogContainer extends CdkDialogContainer
|
|
352
|
+
declare class KtDialogContainer extends CdkDialogContainer {
|
|
301
353
|
private readonly dialogRef;
|
|
302
354
|
private readonly elementRef;
|
|
303
355
|
private readonly host;
|
|
304
356
|
private readonly platformId;
|
|
305
|
-
private readonly destroyRef;
|
|
306
357
|
protected readonly isClosing: i0.WritableSignal<boolean>;
|
|
358
|
+
/** Premier id de la file `aria-labelledby` exposée par `CdkDialogContainer`. Le membre
|
|
359
|
+
`_ariaLabelledByQueue` est interne au CDK (préfixe `_`) : on l'encapsule ICI, gardé, pour que
|
|
360
|
+
le binding de template ne lise jamais l'interne directement. Si une montée du CDK retire ou
|
|
361
|
+
renomme ce membre, on dégrade proprement vers `null` au lieu de casser le rendu du conteneur. */
|
|
362
|
+
protected get ariaLabelledBy(): string | null;
|
|
307
363
|
constructor();
|
|
308
|
-
ngOnInit(): void;
|
|
309
364
|
protected isSheet(): boolean;
|
|
310
365
|
private animateAndClose;
|
|
311
366
|
static ɵfac: i0.ɵɵFactoryDeclaration<KtDialogContainer, never>;
|
|
@@ -318,5 +373,5 @@ declare class KtDialogContainer extends CdkDialogContainer implements OnInit {
|
|
|
318
373
|
*/
|
|
319
374
|
declare const KtDialogImports: readonly [typeof KtDialogHeader, typeof KtDialogTitle, typeof KtDialogDescription, typeof KtDialogContent, typeof KtDialogActions, typeof KtDialogClose, typeof KtDialogFocusInitial, typeof KtDialogSheetHandle];
|
|
320
375
|
|
|
321
|
-
export { KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle,
|
|
322
|
-
export type { KtDialogOpenerConfig, KtDialogPresentation };
|
|
376
|
+
export { KT_DIALOG_AAA_DEFAULTS, KtDialogActions, KtDialogClose, KtDialogContainer, KtDialogContent, KtDialogDescription, KtDialogFocusInitial, KtDialogHeader, KtDialogImports, KtDialogSheetHandle, KtDialogTitle, defineKtDialog, injectKtDialogOpener, provideKtDialogDefaults, resolveKtDialogPanelClass };
|
|
377
|
+
export type { KtDialogContract, KtDialogOpenerConfig, KtDialogPresentation };
|