@ktortu/aaa 0.1.0-beta.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -9
- package/cdk/styles/tabs.css +100 -21
- package/fesm2022/ktortu-aaa-button.mjs +18 -11
- package/fesm2022/ktortu-aaa-button.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-card.mjs +29 -4
- package/fesm2022/ktortu-aaa-card.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-cdk.mjs +59 -15
- package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-dialog.mjs +134 -24
- package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-forms.mjs +748 -294
- package/fesm2022/ktortu-aaa-forms.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-i18n.mjs +139 -0
- package/fesm2022/ktortu-aaa-i18n.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-menu.mjs +38 -13
- package/fesm2022/ktortu-aaa-menu.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-snackbar.mjs +465 -0
- package/fesm2022/ktortu-aaa-snackbar.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-tabs.mjs +319 -42
- package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -1
- package/fesm2022/ktortu-aaa-tooltip.mjs +3 -2
- 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/radio/radio-group.css +0 -8
- package/forms/styles/field-box.css +152 -2
- package/forms/styles/field-pending.css +46 -0
- package/forms/styles/select-panel.css +4 -0
- package/forms/styles/tokens.css +3 -0
- package/menu/menu.css +8 -4
- package/package.json +9 -1
- package/snackbar/snackbar-tokens.css +53 -0
- package/snackbar/snackbar.css +175 -0
- package/styles/forms.css +1 -0
- package/styles/snackbar.css +9 -0
- package/styles/styles.css +1 -0
- package/types/ktortu-aaa-button.d.ts +22 -8
- package/types/ktortu-aaa-card.d.ts +24 -4
- package/types/ktortu-aaa-cdk.d.ts +38 -0
- package/types/ktortu-aaa-dialog.d.ts +129 -31
- package/types/ktortu-aaa-forms.d.ts +503 -160
- package/types/ktortu-aaa-i18n.d.ts +77 -0
- package/types/ktortu-aaa-menu.d.ts +15 -8
- package/types/ktortu-aaa-snackbar.d.ts +275 -0
- package/types/ktortu-aaa-tabs.d.ts +130 -13
- package/types/ktortu-aaa-tooltip.d.ts +5 -0
- package/types/ktortu-aaa.d.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ktortu-aaa.mjs","sources":["../../../../projects/ktortu/aaa/src/public-api.ts","../../../../projects/ktortu/aaa/src/ktortu-aaa.ts"],"sourcesContent":["/*\n * Public API Surface of @ktortu/aaa\n */\nexport * from '@ktortu/aaa/button';\nexport * from '@ktortu/aaa/card';\nexport * from '@ktortu/aaa/cdk';\nexport * from '@ktortu/aaa/dialog';\nexport * from '@ktortu/aaa/forms';\nexport * from '@ktortu/aaa/menu';\nexport * from '@ktortu/aaa/tabs';\nexport * from '@ktortu/aaa/tooltip';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ktortu-aaa.mjs","sources":["../../../../projects/ktortu/aaa/src/public-api.ts","../../../../projects/ktortu/aaa/src/ktortu-aaa.ts"],"sourcesContent":["/*\n * Public API Surface of @ktortu/aaa\n */\nexport * from '@ktortu/aaa/button';\nexport * from '@ktortu/aaa/card';\nexport * from '@ktortu/aaa/cdk';\nexport * from '@ktortu/aaa/dialog';\nexport * from '@ktortu/aaa/forms';\nexport * from '@ktortu/aaa/i18n';\nexport * from '@ktortu/aaa/menu';\nexport * from '@ktortu/aaa/snackbar';\nexport * from '@ktortu/aaa/tabs';\nexport * from '@ktortu/aaa/tooltip';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;AAAA;;AAEG;;ACFH;;AAEG"}
|
|
@@ -21,14 +21,6 @@
|
|
|
21
21
|
color: var(--field-required-color, var(--kt-danger, #8c1d18));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/* Disposition des options : pilotable par token (colonne par défaut, ligne possible). */
|
|
25
|
-
.kt-checkbox-group__options {
|
|
26
|
-
display: flex;
|
|
27
|
-
flex-direction: var(--checkbox-group-direction, column);
|
|
28
|
-
flex-wrap: wrap;
|
|
29
|
-
gap: var(--checkbox-group-gap, 0.25rem);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
24
|
.kt-checkbox-group__hint {
|
|
33
25
|
margin: 0;
|
|
34
26
|
font-size: var(--field-hint-font-size, 0.8125rem);
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
/* Texte réservé aux lecteurs d'écran (live region des retraits). */
|
|
53
53
|
.kt-chip-list__status {
|
|
54
54
|
position: absolute;
|
|
55
|
+
/* Ancrée au coin de l'hôte : sans `inset`, un `position: absolute` sans ancêtre positionné
|
|
56
|
+
retombe sur sa position STATIQUE (bas de la liste). Dans un conteneur scrollable, ce 1px
|
|
57
|
+
atterrit loin dans le document et crée une 2ᵉ barre de défilement fantôme. */
|
|
58
|
+
inset-block-start: 0;
|
|
59
|
+
inset-inline-start: 0;
|
|
55
60
|
inline-size: 1px;
|
|
56
61
|
block-size: 1px;
|
|
57
62
|
padding: 0;
|
|
@@ -21,14 +21,6 @@
|
|
|
21
21
|
color: var(--field-required-color, var(--kt-danger, #8c1d18));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/* Disposition des options : pilotable par token (colonne par défaut, ligne possible). */
|
|
25
|
-
.kt-radio-group__options {
|
|
26
|
-
display: flex;
|
|
27
|
-
flex-direction: var(--radio-group-direction, column);
|
|
28
|
-
flex-wrap: wrap;
|
|
29
|
-
gap: var(--radio-group-gap, 0.25rem);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
24
|
.kt-radio-group__hint {
|
|
33
25
|
margin: 0;
|
|
34
26
|
font-size: var(--field-hint-font-size, 0.8125rem);
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
@layer kt-aaa.components {
|
|
2
|
-
/* Boîte de contrôle partagée par les champs simples (TextField, NumberField)
|
|
2
|
+
/* Boîte de contrôle partagée par les champs simples (TextField, NumberField) et la famille
|
|
3
|
+
Temporal (Date/Time/DateTime/YearMonth/Instant), qui réutilise la même `.kt-field-box`.
|
|
3
4
|
Globale + classes namespacées : partagée entre plusieurs composants, donc non scopable. */
|
|
4
5
|
kt-text-field,
|
|
5
|
-
kt-
|
|
6
|
+
kt-text-area,
|
|
7
|
+
kt-number-field,
|
|
8
|
+
kt-date-field,
|
|
9
|
+
kt-time-field,
|
|
10
|
+
kt-date-time-field,
|
|
11
|
+
kt-year-month-field,
|
|
12
|
+
kt-instant-field,
|
|
13
|
+
kt-select,
|
|
14
|
+
kt-multi-select {
|
|
6
15
|
display: block;
|
|
16
|
+
/* Largeur pilotable d'un coup via --field-width. Défaut `auto` : remplit le conteneur en
|
|
17
|
+
contexte bloc (formulaire), se dimensionne au contenu en flex. Surcharger sur :root ou un
|
|
18
|
+
conteneur pour fixer tous les champs (ex. --field-width: 320px). */
|
|
19
|
+
width: var(--field-width, auto);
|
|
7
20
|
}
|
|
8
21
|
|
|
9
22
|
.kt-field-box {
|
|
@@ -168,4 +181,141 @@
|
|
|
168
181
|
field-sizing: content;
|
|
169
182
|
resize: vertical;
|
|
170
183
|
}
|
|
184
|
+
|
|
185
|
+
/* =========================================================================
|
|
186
|
+
Apparence OUTLINE (opt-in via [appearance]="'outline'" ou KT_FIELD_CONFIG).
|
|
187
|
+
Label flottant qui se loge dans la bordure haute de la boîte (façon Material).
|
|
188
|
+
Rendu GLOBAL (et non dans field.css scopé) car il référence .kt-field-box*,
|
|
189
|
+
contenu PROJETÉ depuis le composant de champ — inatteignable en CSS scopé.
|
|
190
|
+
L'apparence par défaut (fill) n'est jamais touchée.
|
|
191
|
+
========================================================================= */
|
|
192
|
+
kt-field[data-appearance='outline'] {
|
|
193
|
+
position: relative;
|
|
194
|
+
margin-top: 0.5rem; /* espace pour le label une fois flotté au-dessus de la bordure */
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Le header (label + aide) recouvre la boîte sans réserver de hauteur en flux. */
|
|
198
|
+
kt-field[data-appearance='outline'] .kt-field__header {
|
|
199
|
+
position: absolute;
|
|
200
|
+
inset: 0 0 auto 0;
|
|
201
|
+
height: var(--field-min-height, 44px);
|
|
202
|
+
margin: 0;
|
|
203
|
+
padding-inline: var(--field-padding-x, 0.75rem);
|
|
204
|
+
z-index: 1;
|
|
205
|
+
pointer-events: none; /* la boîte/l'input dessous restent cliquables */
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Label au repos = position « placeholder » (centré verticalement dans la boîte). */
|
|
209
|
+
kt-field[data-appearance='outline'] .kt-field__label {
|
|
210
|
+
position: absolute;
|
|
211
|
+
inset-inline-start: var(--field-padding-x, 0.75rem);
|
|
212
|
+
top: 50%;
|
|
213
|
+
transform: translateY(-50%);
|
|
214
|
+
transform-origin: left center;
|
|
215
|
+
max-width: calc(100% - 2 * var(--field-padding-x, 0.75rem));
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
text-overflow: ellipsis;
|
|
219
|
+
background: transparent;
|
|
220
|
+
padding-inline: 0.25rem;
|
|
221
|
+
font-size: var(--field-font-size, 1rem);
|
|
222
|
+
font-weight: 400;
|
|
223
|
+
color: var(--field-hint-color, #5f6368);
|
|
224
|
+
transition:
|
|
225
|
+
transform 0.15s ease,
|
|
226
|
+
color 0.15s ease;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* RTL : `transform-origin` n'accepte pas de mot-clé logique → on bascule l'origine de la mise à
|
|
230
|
+
l'échelle vers le bord de départ (droite en RTL) pour rester aligné sur `inset-inline-start`. */
|
|
231
|
+
[dir='rtl'] kt-field[data-appearance='outline'] .kt-field__label {
|
|
232
|
+
transform-origin: right center;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* Bouton d'aide : recliquable et calé à droite du header. */
|
|
236
|
+
kt-field[data-appearance='outline'] .kt-field__help {
|
|
237
|
+
margin-inline-start: auto;
|
|
238
|
+
pointer-events: auto;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Label flotté — DEUX règles séparées à dessein (dégradation gracieuse) : la 1re (focus / always)
|
|
242
|
+
reste valide même si le moteur ne supporte pas `:has()`. Une seule pseudo-classe inconnue dans une
|
|
243
|
+
liste de sélecteurs invaliderait TOUTE la liste → le label ne flotterait jamais, focus compris.
|
|
244
|
+
La 2nde ajoute les cas qui exigent `:has()` (input rempli, select `[data-filled]`). Les inputs
|
|
245
|
+
date/heure n'ayant pas de placeholder CSS, `:not(:placeholder-shown)` les garde flottés en permanence. */
|
|
246
|
+
kt-field[data-appearance='outline'][data-float-label='always'] .kt-field__label,
|
|
247
|
+
kt-field[data-appearance='outline']:focus-within .kt-field__label {
|
|
248
|
+
transform: translateY(-50%) translateY(calc(var(--field-min-height, 44px) / -2)) scale(0.75);
|
|
249
|
+
color: var(--field-label-color, inherit);
|
|
250
|
+
/* Fond du label flotté : masque la ligne d'outline sous le label. Défaut : fond PLEIN du champ.
|
|
251
|
+
Le consommateur peut poser un gradient (ex. `linear-gradient(transparent 50%, var(--field-bg) 50%)`)
|
|
252
|
+
via le token --field-label-float-bg. */
|
|
253
|
+
background: var(--field-label-float-bg, var(--field-bg, #fff));
|
|
254
|
+
}
|
|
255
|
+
kt-field[data-appearance='outline']:has(.kt-field-box[data-filled]) .kt-field__label,
|
|
256
|
+
kt-field[data-appearance='outline']:has(.kt-field-box__input:not(:placeholder-shown)) .kt-field__label {
|
|
257
|
+
transform: translateY(-50%) translateY(calc(var(--field-min-height, 44px) / -2)) scale(0.75);
|
|
258
|
+
color: var(--field-label-color, inherit);
|
|
259
|
+
background: var(--field-label-float-bg, var(--field-bg, #fff));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* Bordure de l'outline : token dédié (état de repos). Surcharge possible indépendamment du
|
|
263
|
+
mode `fill`. Les états hover/focus/invalid gardent leurs propres tokens (réaffirmés ici
|
|
264
|
+
pour rester prioritaires sur la règle de repos). */
|
|
265
|
+
kt-field[data-appearance='outline'] .kt-field-box {
|
|
266
|
+
border-color: var(--field-outline-border-color, var(--field-border-color, #c4c7c5));
|
|
267
|
+
}
|
|
268
|
+
kt-field[data-appearance='outline'] .kt-field-box:hover {
|
|
269
|
+
border-color: var(--field-border-color-hover, #5f6368);
|
|
270
|
+
}
|
|
271
|
+
kt-field[data-appearance='outline'] .kt-field-box:focus-within {
|
|
272
|
+
border-color: var(--field-border-color-focus, #0b57d0);
|
|
273
|
+
}
|
|
274
|
+
kt-field[data-appearance='outline'] .kt-field-box[data-invalid] {
|
|
275
|
+
border-color: var(--field-error-color, #b3261e);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* En outline AUTO, le label flottant tient lieu de placeholder au repos : on masque le placeholder
|
|
279
|
+
propre du select (sinon doublon avec le label en position « placeholder »). En ALWAYS, le label
|
|
280
|
+
est déjà en haut → on laisse le placeholder du select s'afficher. */
|
|
281
|
+
kt-field[data-appearance='outline']:not([data-float-label='always']) .kt-select__placeholder {
|
|
282
|
+
visibility: hidden;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* En outline, le placeholder natif ne s'affiche QUE lorsque le label flotte : au repos le label
|
|
286
|
+
tient lieu de placeholder (sinon les deux se superposeraient). Masqué par défaut, révélé au
|
|
287
|
+
focus ou en floatLabel='always'. (Quand le champ est rempli, le navigateur ne le rend pas.) */
|
|
288
|
+
kt-field[data-appearance='outline'] .kt-field-box__input::placeholder {
|
|
289
|
+
color: transparent;
|
|
290
|
+
}
|
|
291
|
+
kt-field[data-appearance='outline']:focus-within .kt-field-box__input::placeholder,
|
|
292
|
+
kt-field[data-appearance='outline'][data-float-label='always'] .kt-field-box__input::placeholder {
|
|
293
|
+
color: var(--field-placeholder-color, var(--field-hint-color, #5f6368));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/* Accent au focus / état d'erreur. */
|
|
297
|
+
kt-field[data-appearance='outline']:focus-within .kt-field__label {
|
|
298
|
+
color: var(--field-border-color-focus, #0b57d0);
|
|
299
|
+
}
|
|
300
|
+
kt-field[data-appearance='outline']:has(.kt-field-box[data-invalid]) .kt-field__label {
|
|
301
|
+
color: var(--field-error-color, #b3261e);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
@media (prefers-reduced-motion: reduce) {
|
|
305
|
+
kt-field[data-appearance='outline'] .kt-field__label {
|
|
306
|
+
transition: none;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* Contraste forcé (Windows High Contrast) : le fond du label flotté masque la bordure ; un gradient
|
|
311
|
+
custom `--field-label-float-bg` serait ignoré → on force une couleur système opaque pour garantir
|
|
312
|
+
le « notch ». Au repos le label est transparent (non listé), donc rien à forcer. */
|
|
313
|
+
@media (forced-colors: active) {
|
|
314
|
+
kt-field[data-appearance='outline'][data-float-label='always'] .kt-field__label,
|
|
315
|
+
kt-field[data-appearance='outline']:focus-within .kt-field__label,
|
|
316
|
+
kt-field[data-appearance='outline']:has(.kt-field-box[data-filled]) .kt-field__label,
|
|
317
|
+
kt-field[data-appearance='outline']:has(.kt-field-box__input:not(:placeholder-shown)) .kt-field__label {
|
|
318
|
+
background: Canvas;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
171
321
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
@layer kt-aaa.components {
|
|
2
|
+
/* Spinner « validation asynchrone en cours » par défaut.
|
|
3
|
+
|
|
4
|
+
100 % CSS (pseudo-élément, aucun markup) et piloté par l'attribut `[data-pending]` posé par les
|
|
5
|
+
contrôles quand leur input `pending` est vrai (l'accessibilité passe par `aria-busy`, indépendant).
|
|
6
|
+
|
|
7
|
+
Entièrement surchargeable / suppressible côté consommateur :
|
|
8
|
+
- retailler/recolorer via les tokens `--field-spinner-*` ;
|
|
9
|
+
- supprimer : kt-text-field .kt-field-box[data-pending]::after { content: none; }
|
|
10
|
+
- remplacer par sa propre affordance (le hook `[data-pending]` reste disponible). */
|
|
11
|
+
@keyframes kt-field-spin {
|
|
12
|
+
to {
|
|
13
|
+
transform: rotate(1turn);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.kt-field-box[data-pending]::after,
|
|
18
|
+
.kt-switch-field[data-pending] .kt-switch-row::after,
|
|
19
|
+
.kt-checkbox-field[data-pending] .kt-checkbox::after,
|
|
20
|
+
.kt-radio-group-field[data-pending] .kt-radio-group__legend::after,
|
|
21
|
+
.kt-checkbox-group-field[data-pending] .kt-checkbox-group__legend::after {
|
|
22
|
+
content: '';
|
|
23
|
+
display: inline-block;
|
|
24
|
+
flex: none;
|
|
25
|
+
vertical-align: middle;
|
|
26
|
+
inline-size: var(--field-spinner-size, 1em);
|
|
27
|
+
block-size: var(--field-spinner-size, 1em);
|
|
28
|
+
margin-inline-start: var(--field-spinner-gap, 0.5rem);
|
|
29
|
+
border: var(--field-spinner-width, 2px) solid var(--field-spinner-track, currentColor);
|
|
30
|
+
border-top-color: var(--field-spinner-color, var(--field-border-color-focus, #1a73e8));
|
|
31
|
+
border-radius: 50%;
|
|
32
|
+
opacity: var(--field-spinner-opacity, 0.7);
|
|
33
|
+
animation: kt-field-spin var(--field-spinner-duration, 0.7s) linear infinite;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Respect de prefers-reduced-motion : on fige la rotation (le cercle reste visible comme indicateur). */
|
|
37
|
+
@media (prefers-reduced-motion: reduce) {
|
|
38
|
+
.kt-field-box[data-pending]::after,
|
|
39
|
+
.kt-switch-field[data-pending] .kt-switch-row::after,
|
|
40
|
+
.kt-checkbox-field[data-pending] .kt-checkbox::after,
|
|
41
|
+
.kt-radio-group-field[data-pending] .kt-radio-group__legend::after,
|
|
42
|
+
.kt-checkbox-group-field[data-pending] .kt-checkbox-group__legend::after {
|
|
43
|
+
animation: none;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -178,6 +178,10 @@
|
|
|
178
178
|
/* Texte réservé aux lecteurs d'écran (live region du nombre de résultats). */
|
|
179
179
|
.kt-select__sr-only {
|
|
180
180
|
position: absolute;
|
|
181
|
+
/* Ancrée au coin : sinon ce 1px `absolute` retombe sur sa position statique et, dans un
|
|
182
|
+
conteneur scrollable, étire le document → barre de défilement fantôme. */
|
|
183
|
+
inset-block-start: 0;
|
|
184
|
+
inset-inline-start: 0;
|
|
181
185
|
inline-size: 1px;
|
|
182
186
|
block-size: 1px;
|
|
183
187
|
padding: 0;
|
package/forms/styles/tokens.css
CHANGED
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
--field-padding-y: 0.5rem;
|
|
36
36
|
--field-gap: 0.375rem;
|
|
37
37
|
--field-control-gap: 0.5rem;
|
|
38
|
+
/* Largeur du champ : un seul réglage pilote tous les champs. Défaut `auto` (remplit le
|
|
39
|
+
conteneur en bloc, se dimensionne au contenu en flex). Ex. global : --field-width: 320px. */
|
|
40
|
+
--field-width: auto;
|
|
38
41
|
|
|
39
42
|
/* Typo */
|
|
40
43
|
--field-font-size: var(--kt-control-font);
|
package/menu/menu.css
CHANGED
|
@@ -42,8 +42,11 @@
|
|
|
42
42
|
[ktMenu] {
|
|
43
43
|
position: fixed;
|
|
44
44
|
inset: auto;
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
/* Dropdown sous le déclencheur, aligné sur son bord inline-start. On utilise `position-area`
|
|
46
|
+
et NON la fonction `anchor()` : `anchor()` résout mal une ancre imbriquée dans un sous-arbre
|
|
47
|
+
déjà ancré → les sous-menus s'EMPILENT dès le 3ᵉ niveau. `position-area` cascade correctement
|
|
48
|
+
à n'importe quelle profondeur (validé Chrome 125+ / Firefox 147+ / Safari 26+). */
|
|
49
|
+
position-area: block-end span-inline-end;
|
|
47
50
|
margin-block-start: var(--kt-menu-offset, 0.25rem);
|
|
48
51
|
/* Bascule de côté / au-dessus si le bas/droite manque de place. */
|
|
49
52
|
position-try-fallbacks:
|
|
@@ -61,8 +64,9 @@
|
|
|
61
64
|
--_submenu-offset: calc(
|
|
62
65
|
var(--kt-menu-padding, 0.25rem) + var(--kt-menu-border-width, 1px) + var(--kt-menu-submenu-gap, 0.375rem)
|
|
63
66
|
);
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
/* À l'inline-end de l'item parent, aligné sur son haut. `position-area` (et non `anchor()`)
|
|
68
|
+
pour cascader correctement à toute profondeur — cf. note sur le menu racine ci-dessus. */
|
|
69
|
+
position-area: inline-end span-block-end;
|
|
66
70
|
margin-block-start: 0;
|
|
67
71
|
margin-inline-start: var(--_submenu-offset);
|
|
68
72
|
position-try-fallbacks:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ktortu/aaa",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Bibliothèque de composants Angular headless + thémés par tokens CSS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -77,10 +77,18 @@
|
|
|
77
77
|
"types": "./types/ktortu-aaa-forms.d.ts",
|
|
78
78
|
"default": "./fesm2022/ktortu-aaa-forms.mjs"
|
|
79
79
|
},
|
|
80
|
+
"./i18n": {
|
|
81
|
+
"types": "./types/ktortu-aaa-i18n.d.ts",
|
|
82
|
+
"default": "./fesm2022/ktortu-aaa-i18n.mjs"
|
|
83
|
+
},
|
|
80
84
|
"./menu": {
|
|
81
85
|
"types": "./types/ktortu-aaa-menu.d.ts",
|
|
82
86
|
"default": "./fesm2022/ktortu-aaa-menu.mjs"
|
|
83
87
|
},
|
|
88
|
+
"./snackbar": {
|
|
89
|
+
"types": "./types/ktortu-aaa-snackbar.d.ts",
|
|
90
|
+
"default": "./fesm2022/ktortu-aaa-snackbar.mjs"
|
|
91
|
+
},
|
|
84
92
|
"./tabs": {
|
|
85
93
|
"types": "./types/ktortu-aaa-tabs.d.ts",
|
|
86
94
|
"default": "./fesm2022/ktortu-aaa-tabs.mjs"
|
|
@@ -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
|
+
}
|
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