@ktortu/aaa 0.1.0-beta.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/button/button-tokens.css +152 -0
  4. package/button/button.css +319 -0
  5. package/card/card-tokens.css +49 -0
  6. package/card/card.css +200 -0
  7. package/cdk/styles/foundation.css +83 -0
  8. package/cdk/styles/tabs.css +276 -0
  9. package/dialog/dialog.css +350 -0
  10. package/fesm2022/ktortu-aaa-button.mjs +128 -0
  11. package/fesm2022/ktortu-aaa-button.mjs.map +1 -0
  12. package/fesm2022/ktortu-aaa-card.mjs +209 -0
  13. package/fesm2022/ktortu-aaa-card.mjs.map +1 -0
  14. package/fesm2022/ktortu-aaa-cdk.mjs +183 -0
  15. package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -0
  16. package/fesm2022/ktortu-aaa-dialog.mjs +512 -0
  17. package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -0
  18. package/fesm2022/ktortu-aaa-forms.mjs +3215 -0
  19. package/fesm2022/ktortu-aaa-forms.mjs.map +1 -0
  20. package/fesm2022/ktortu-aaa-menu.mjs +315 -0
  21. package/fesm2022/ktortu-aaa-menu.mjs.map +1 -0
  22. package/fesm2022/ktortu-aaa-tabs.mjs +79 -0
  23. package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -0
  24. package/fesm2022/ktortu-aaa-tooltip.mjs +356 -0
  25. package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -0
  26. package/fesm2022/ktortu-aaa.mjs +17 -0
  27. package/fesm2022/ktortu-aaa.mjs.map +1 -0
  28. package/forms/checkbox/checkbox-group.css +55 -0
  29. package/forms/checkbox/checkbox.css +216 -0
  30. package/forms/chips/chip-list.css +70 -0
  31. package/forms/chips/chip.css +92 -0
  32. package/forms/chips/tokens.css +102 -0
  33. package/forms/field/field.css +87 -0
  34. package/forms/multi-select/multi-select.css +136 -0
  35. package/forms/radio/radio-group.css +55 -0
  36. package/forms/radio/radio.css +165 -0
  37. package/forms/styles/field-box.css +171 -0
  38. package/forms/styles/select-panel.css +464 -0
  39. package/forms/styles/tokens.css +67 -0
  40. package/forms/switch/switch.css +188 -0
  41. package/menu/menu-tokens.css +58 -0
  42. package/menu/menu.css +224 -0
  43. package/package.json +96 -0
  44. package/styles/button.css +6 -0
  45. package/styles/card.css +6 -0
  46. package/styles/dialog.css +6 -0
  47. package/styles/forms.css +13 -0
  48. package/styles/foundation.css +7 -0
  49. package/styles/menu.css +6 -0
  50. package/styles/styles.css +24 -0
  51. package/styles/tabs.css +5 -0
  52. package/styles/tooltip.css +5 -0
  53. package/themes/theme-ant.css +44 -0
  54. package/themes/theme-architecte.css +83 -0
  55. package/themes/theme-aurora.css +97 -0
  56. package/themes/theme-bootstrap.css +46 -0
  57. package/themes/theme-carbon.css +49 -0
  58. package/themes/theme-catppuccin.css +66 -0
  59. package/themes/theme-cyberpunk.css +211 -0
  60. package/themes/theme-fluent.css +45 -0
  61. package/themes/theme-material-you.css +74 -0
  62. package/themes/theme-material.css +48 -0
  63. package/themes/theme-primer.css +46 -0
  64. package/themes/theme-vegetal.css +78 -0
  65. package/tooltip/tooltip.css +129 -0
  66. package/types/ktortu-aaa-button.d.ts +70 -0
  67. package/types/ktortu-aaa-card.d.ts +143 -0
  68. package/types/ktortu-aaa-cdk.d.ts +110 -0
  69. package/types/ktortu-aaa-dialog.d.ts +286 -0
  70. package/types/ktortu-aaa-forms.d.ts +1574 -0
  71. package/types/ktortu-aaa-menu.d.ts +171 -0
  72. package/types/ktortu-aaa-tabs.d.ts +27 -0
  73. package/types/ktortu-aaa-tooltip.d.ts +90 -0
  74. package/types/ktortu-aaa.d.ts +8 -0
@@ -0,0 +1,350 @@
1
+ @layer kt-aaa.components {
2
+ /* Styles du Dialog (moteur @angular/cdk/dialog + directives kt).
3
+ Même architecture que le reste de la lib : tokens --dialog-* dérivés du socle --kt-* ET
4
+ des tokens « feuille/popup » du select que les thèmes définissent DÉJÀ (le dialog partage
5
+ l'identité visuelle de ces surfaces flottantes). Bascules non déclarées pour les effets.
6
+ Global obligatoire : le dialog est rendu dans l'overlay CDK, hors de toute vue de composant. */
7
+
8
+ :root {
9
+ /* ============ Surface ============ */
10
+ --dialog-bg: var(--kt-surface, #ffffff);
11
+ --dialog-fg: var(--kt-on-surface, #1f1f1f);
12
+ --dialog-muted: var(--kt-muted, #474747);
13
+ --dialog-border-color: var(--kt-outline, #c4c7c5);
14
+ --dialog-border-width: 1px;
15
+
16
+ /* Forme : suit le radius de la feuille du select (déjà thémé), sinon le radius de contrôle. */
17
+ --dialog-radius: var(--kt-sheet-radius, var(--kt-control-radius, 8px));
18
+
19
+ --dialog-pad: 1.5rem;
20
+ --dialog-gap: 1rem;
21
+ --dialog-max-width: 32rem;
22
+
23
+ /* Ombre : réutilise l'ombre de popup du select si le thème l'a posée. */
24
+ --dialog-shadow: var(--select-popup-shadow, 0 10px 25px rgb(0 0 0 / 20%));
25
+
26
+ /* Scrim du backdrop : réutilise le voile de feuille du select. */
27
+ --dialog-scrim: var(--kt-sheet-scrim, rgb(0 0 0 / 40%));
28
+
29
+ /* ============ Anneau de focus (sur les éléments internes) ============ */
30
+ --dialog-focus-ring-color: var(--kt-focus-ring-color, #0842a0);
31
+ --dialog-focus-ring-width: var(--kt-focus-ring-width, 2px);
32
+
33
+ /* ============ Gouttière du conteneur scrollable ============ */
34
+ /* Le dialog a des coins arrondis + une barre d'actions sticky : la gouttière à TRACK PLEIN
35
+ héritée de la page y dessine un bandeau vertical disgracieux le long de l'arrondi. On force
36
+ ici une gouttière FINE à track TRANSPARENT (thumb seul, en overlay), dérivée de la surface
37
+ du dialog — neutralise au passage les gouttières chunky de certains thèmes (Win98, Cartoon)
38
+ dans ce contexte restreint. Bascule --dialog-scrollbar-color pour un thème qui voudrait la sienne. */
39
+ --dialog-scrollbar-thumb: color-mix(in srgb, var(--dialog-fg) 26%, var(--dialog-bg));
40
+
41
+ /* Bascules NON déclarées exprès (défauts entre parenthèses) :
42
+ --dialog-backdrop-filter (none) : flou de la surface (thèmes verre).
43
+ --dialog-scrim-backdrop-filter (none) : flou du backdrop.
44
+ --dialog-anim-duration (160ms) : durée de l'animation d'entrée.
45
+ --dialog-anim-easing (ease) : courbe de l'animation d'entrée.
46
+ --dialog-anim-from-transform (translateY(8px) scale(.97)) : état initial de l'entrée.
47
+ --dialog-title-font / --dialog-title-size / --dialog-title-weight : typo du titre.
48
+ --dialog-scroll-shadow / --dialog-scroll-shadow-bottom (ombres radiales douces) : voile de
49
+ défilement sous le header / au-dessus des actions, visible seulement quand le contenu déborde. */
50
+ }
51
+
52
+ /* ============ Dimensionnement du pane overlay (panelClass: kt-dialog) ============ */
53
+ .cdk-overlay-pane.kt-dialog {
54
+ inline-size: min(var(--dialog-max-width), 100vw - 2rem);
55
+ }
56
+
57
+ /* ============ Surface = conteneur CDK (porte role="dialog") ============ */
58
+ .kt-dialog .cdk-dialog-container {
59
+ display: flex;
60
+ flex-direction: column;
61
+ box-sizing: border-box;
62
+ /* Reflow AAA (1.4.10) : conteneur plafonné en hauteur ; le contenu long ([ktDialogContent])
63
+ scrolle verticalement (jamais en 2D), titre et actions restant visibles. */
64
+ max-block-size: calc(100dvh - 2rem);
65
+ /* overflow hidden = clippe header / contenu / actions au rayon. Le scroll vit dans
66
+ [ktDialogContent] : une bande CENTRALE entre le header et les actions FIXES — sa gouttière
67
+ longe donc le bord droit rectiligne, jamais un coin arrondi.
68
+ Pas de padding ici : chaque région (titre / description / contenu / actions) porte le sien
69
+ (cf. plus bas). Source de padding par région => fonds de header/actions pleine largeur SANS
70
+ marge négative, et aucun couplage padding-conteneur ↔ marges des régions. */
71
+ overflow: hidden;
72
+ color: var(--dialog-fg);
73
+ background: var(--dialog-bg);
74
+ border: var(--dialog-border-width) solid var(--dialog-border-color);
75
+ border-radius: var(--dialog-radius);
76
+ box-shadow: var(--dialog-shadow);
77
+ backdrop-filter: var(--dialog-backdrop-filter, none);
78
+ outline: none; /* le focus visible est porté par les éléments internes, pas le conteneur */
79
+ }
80
+
81
+ /* Le contenu projeté est encapsulé dans l'hôte du composant dialog (<kt-*-dialog>), SEUL enfant
82
+ direct du conteneur CDK. On le relaie en colonne flex pour que titre / contenu / actions en
83
+ deviennent les vrais items — sans quoi [ktDialogContent] ne pourrait pas scroller (l'hôte,
84
+ item unique, prendrait toute la hauteur du contenu). */
85
+ .kt-dialog .cdk-dialog-container > * {
86
+ display: flex;
87
+ flex-direction: column;
88
+ flex: 1 1 auto;
89
+ min-block-size: 0;
90
+ }
91
+
92
+ /* ============ Titre ([ktDialogTitle], idéalement un <h2>) ============ */
93
+ [ktDialogTitle] {
94
+ /* Région FIXE en tête : porte le padding haut + latéral du dialog. */
95
+ padding: var(--dialog-pad) var(--dialog-pad) 0;
96
+ margin: 0 0 0.25rem;
97
+ font-family: var(--dialog-title-font, inherit);
98
+ font-size: var(--dialog-title-size, 1.25rem);
99
+ font-weight: var(--dialog-title-weight, 600);
100
+ line-height: 1.3;
101
+ color: var(--dialog-fg);
102
+ }
103
+
104
+ /* ============ En-tête riche ([ktDialogHeader], OPTIONNEL) ============
105
+ Rangée flex pour composer titre + icône + bouton close. C'est LUI qui porte le padding de
106
+ région (à la place du titre, réinitialisé ci-dessous) : un close ancré en haut à droite tient
107
+ sans position absolue ni marge négative. Région FIXE (enfant direct de la colonne, comme un
108
+ titre nu) — il ne scrolle pas avec le contenu. */
109
+ [ktDialogHeader] {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.75rem;
113
+ padding: var(--dialog-pad) var(--dialog-pad) 0;
114
+ margin-block-end: 0.25rem;
115
+ }
116
+
117
+ /* Dans un header, le titre n'est plus que l'étiquette (a11y + typo) : il lâche son padding/marge
118
+ de région et prend l'espace libre, ce qui repousse un éventuel bouton close à droite. */
119
+ [ktDialogHeader] [ktDialogTitle] {
120
+ flex: 1 1 auto;
121
+ min-inline-size: 0;
122
+ padding: 0;
123
+ margin: 0;
124
+ }
125
+
126
+ /* ============ Description courte ([ktDialogDescription]) ============ */
127
+ [ktDialogDescription] {
128
+ padding-inline: var(--dialog-pad);
129
+ margin: 0 0 var(--dialog-gap);
130
+ color: var(--dialog-muted);
131
+ line-height: 1.5;
132
+ }
133
+
134
+ /* ============ Zone de contenu ([ktDialogContent]) ============
135
+ SEULE région scrollable du dialog : flex: 1 dans la colonne, min-block-size: 0 pour
136
+ autoriser le rétrécissement (sinon le contenu déborderait au lieu de scroller). */
137
+ [ktDialogContent] {
138
+ display: block;
139
+ flex: 1 1 auto;
140
+ min-block-size: 0;
141
+ padding-inline: var(--dialog-pad);
142
+ overflow-y: auto;
143
+ /* Gouttière fine en overlay (track transparent), dérivée de la surface du dialog. Le scroll
144
+ vivant ICI — à l'intérieur du padding du conteneur — la barre est en retrait du coin arrondi. */
145
+ scrollbar-width: thin;
146
+ scrollbar-color: var(--dialog-scrollbar-color, var(--dialog-scrollbar-thumb) transparent);
147
+
148
+ /* Ombres de défilement — affordance des bandeaux FIXES (header en haut, actions en bas) :
149
+ une ombre douce sous le header (resp. au-dessus des actions) qui n'apparaît QUE quand le
150
+ contenu déborde de ce côté. Technique CSS pure (Lea Verou) : 4 couches de background —
151
+ deux « caches » couleur-surface en `local` (ils défilent AVEC le contenu et révèlent
152
+ l'ombre une fois poussés hors-champ) au-dessus de deux ombres en `scroll` (fixées au
153
+ cadre). Pas de JS, pas d'élément wrapper (le titre est projeté directement).
154
+ Bascules --dialog-scroll-shadow / --dialog-scroll-shadow-bottom pour ajuster ou neutraliser
155
+ (ex. filet net sans flou en Win98). */
156
+ background:
157
+ linear-gradient(var(--dialog-bg), transparent) center top / 100% 1.25rem no-repeat local,
158
+ linear-gradient(transparent, var(--dialog-bg)) center bottom / 100% 1.25rem no-repeat local,
159
+ var(--dialog-scroll-shadow, radial-gradient(farthest-side at 50% 0, rgb(0 0 0 / 16%), transparent)) center top /
160
+ 100% 0.6rem no-repeat scroll,
161
+ var(--dialog-scroll-shadow-bottom, radial-gradient(farthest-side at 50% 100%, rgb(0 0 0 / 16%), transparent))
162
+ center bottom / 100% 0.6rem no-repeat scroll;
163
+ }
164
+
165
+ /* ============ Barre d'actions ([ktDialogActions]) ============
166
+ USAGE RECOMMANDÉ : FRÈRE de [ktDialogContent] (cf. les trois dialogs de démo) — item flex non
167
+ rétractable, épinglé en pied de colonne, le contenu scrollant au-dessus. Si un bouton doit
168
+ soumettre un <form> porté par le contenu, on le relie via l'attribut natif `form="id"` plutôt
169
+ que d'imbriquer les actions DANS le form (sinon le padding latéral du contenu s'ajouterait à
170
+ celui des actions). `position: sticky` reste un garde-fou si la barre est malgré tout imbriquée
171
+ dans une zone scrollable. Fond opaque (--dialog-bg) pour masquer le contenu qui glisse dessous. */
172
+ [ktDialogActions] {
173
+ display: flex;
174
+ flex: none;
175
+ flex-wrap: wrap;
176
+ gap: 0.5rem;
177
+ justify-content: flex-end;
178
+ align-items: center;
179
+ position: sticky;
180
+ inset-block-end: 0;
181
+ margin-block-start: var(--dialog-gap);
182
+ /* Région FIXE en pied : porte le padding latéral + bas du dialog (+ un peu de haut pour que le
183
+ contenu ne colle pas aux boutons quand il glisse dessous dans le cas imbriqué). */
184
+ padding: 0.75rem var(--dialog-pad) var(--dialog-pad);
185
+ background: var(--dialog-bg);
186
+ }
187
+
188
+ /* ============ Backdrop (backdropClass: kt-dialog__backdrop) ============ */
189
+ .kt-dialog__backdrop {
190
+ background: var(--dialog-scrim);
191
+ backdrop-filter: var(--dialog-scrim-backdrop-filter, none);
192
+ }
193
+
194
+ /* ============ Animation d'entrée (@starting-style — le conteneur est ajouté au DOM) ============ */
195
+ .kt-dialog .cdk-dialog-container {
196
+ opacity: 1;
197
+ transform: none;
198
+ transition:
199
+ opacity var(--dialog-anim-duration, 160ms) var(--dialog-anim-easing, ease),
200
+ transform var(--dialog-anim-duration, 160ms) var(--dialog-anim-easing, ease);
201
+ }
202
+ @starting-style {
203
+ .kt-dialog .cdk-dialog-container {
204
+ opacity: 0;
205
+ transform: var(--dialog-anim-from-transform, translateY(8px) scale(0.97));
206
+ }
207
+ }
208
+
209
+ /* ============ Mouvement réduit (WCAG 2.3.3 / éco-conception) ============ */
210
+ @media (prefers-reduced-motion: reduce) {
211
+ .kt-dialog .cdk-dialog-container {
212
+ transition: none;
213
+ }
214
+ @starting-style {
215
+ .kt-dialog .cdk-dialog-container {
216
+ opacity: 1;
217
+ transform: none;
218
+ }
219
+ }
220
+ }
221
+
222
+ /* ============ Contraste élevé Windows / forced-colors ============ */
223
+ @media (forced-colors: active) {
224
+ .kt-dialog .cdk-dialog-container {
225
+ border: 1px solid CanvasText;
226
+ }
227
+ .kt-dialog__backdrop {
228
+ /* Le scrim translucide disparaît en forced-colors : on garde une séparation visible. */
229
+ background: rgb(0 0 0 / 50%);
230
+ }
231
+ }
232
+
233
+ /* ====================================================================================
234
+ PRÉSENTATIONS PLEIN ÉCRAN / BOTTOM-SHEET — pilotées par CLASSE, SANS media query.
235
+ La présentation est CHOISIE par le dev à l'ouverture (option `presentation` de l'ouvreur).
236
+ Les variantes responsive (`centered-*`) sont résolues EN JS (matchMedia) au moment de
237
+ l'ouverture, qui pose alors la classe concrète : le CSS ne fait AUCUNE bascule automatique.
238
+ `--fullscreen` / `--sheet` s'appliquent donc à TOUTE taille dès que la classe est présente.
239
+ Préfixe `.cdk-overlay-pane` (0,3,0) pour battre le centré de base (0,2,0).
240
+
241
+ Entrées : keyframes — fiable, contrairement à @starting-style + `translate`. Le glissement de la
242
+ sheet réutilise la keyframe PARTAGÉE `kt-sheet-in` (foundation.css), commune au popup compact du
243
+ Select : une keyframe est globale et joue aussi bien sur un conteneur CDK que sur un Popover.
244
+ ==================================================================================== */
245
+ @keyframes kt-dialog-fullscreen-in {
246
+ from {
247
+ opacity: 0;
248
+ }
249
+ }
250
+
251
+ /* ===================== PLEIN ÉCRAN (kt-dialog--fullscreen) =====================
252
+ Surface bord-à-bord ; [ktDialogHeader] devient la top app bar. */
253
+ .cdk-overlay-pane.kt-dialog--fullscreen {
254
+ inline-size: 100vw;
255
+ max-inline-size: 100vw;
256
+ }
257
+ .cdk-overlay-pane.kt-dialog--fullscreen .cdk-dialog-container {
258
+ inline-size: 100vw;
259
+ max-inline-size: 100vw;
260
+ block-size: 100dvh;
261
+ max-block-size: 100dvh;
262
+ border: 0;
263
+ border-radius: 0;
264
+ /* fondu simple : on neutralise la transition centrée et on joue la keyframe d'opacité. */
265
+ transform: none;
266
+ transition: none;
267
+ animation: kt-dialog-fullscreen-in var(--dialog-anim-duration, 160ms) var(--dialog-anim-easing, ease);
268
+ }
269
+ @starting-style {
270
+ .cdk-overlay-pane.kt-dialog--fullscreen .cdk-dialog-container {
271
+ transform: none;
272
+ }
273
+ }
274
+ /* [ktDialogHeader] = top app bar collée en haut (✕ + action), safe-area pour les encoches. */
275
+ .cdk-overlay-pane.kt-dialog--fullscreen [ktDialogHeader] {
276
+ position: sticky;
277
+ inset-block-start: 0;
278
+ z-index: 1;
279
+ background: var(--dialog-bg);
280
+ border-block-end: var(--dialog-border-width) solid var(--dialog-border-color);
281
+ padding-block-start: max(var(--dialog-pad), env(safe-area-inset-top, 0px));
282
+ }
283
+ .cdk-overlay-pane.kt-dialog--fullscreen [ktDialogActions] {
284
+ padding-block-end: max(var(--dialog-pad), env(safe-area-inset-bottom, 0px));
285
+ }
286
+
287
+ /* ===================== BOTTOM-SHEET (kt-dialog--sheet) =====================
288
+ Feuille glissant du bas, tokens --kt-sheet-* (partagés avec le Select). On positionne+translate
289
+ le CONTENEUR ; la directive [ktDialogSheetHandle] le drag via .cdk-dialog-container. */
290
+ .cdk-overlay-pane.kt-dialog--sheet .cdk-dialog-container {
291
+ position: fixed;
292
+ inset: auto 0 0 0;
293
+ inline-size: 100%;
294
+ max-inline-size: 100%;
295
+ max-block-size: var(--kt-sheet-max-block-size, 85svh);
296
+ border-block-end: 0;
297
+ border-start-start-radius: var(--kt-sheet-radius, 16px);
298
+ border-start-end-radius: var(--kt-sheet-radius, 16px);
299
+ border-end-start-radius: 0;
300
+ border-end-end-radius: 0;
301
+ box-shadow: var(--kt-sheet-shadow, 0 -4px 16px rgb(0 0 0 / 12%));
302
+ transform: none;
303
+ translate: 0 0;
304
+ /* Entrée : glissement du bas (keyframe PARTAGÉE kt-sheet-in, cf. foundation.css). */
305
+ animation: kt-sheet-in var(--kt-sheet-anim-duration, 120ms) ease;
306
+ transition: translate var(--kt-sheet-anim-duration, 120ms) ease;
307
+ }
308
+ .cdk-overlay-pane.kt-dialog--sheet .cdk-dialog-container.kt-dialog-sheet--dragging,
309
+ .cdk-overlay-pane.kt-dialog--sheet .cdk-dialog-container.kt-dialog-container--dragging {
310
+ transition: none;
311
+ }
312
+ .cdk-overlay-pane.kt-dialog--sheet .cdk-dialog-container.kt-dialog-container--closing {
313
+ translate: 0 100%;
314
+ transition: translate var(--kt-sheet-exit-duration, 90ms) cubic-bezier(0.4, 0, 0.2, 1);
315
+ }
316
+
317
+ /* Poignée de préhension ([ktDialogSheetHandle] ou .kt-dialog-container__sheet-handle) — visible dès que le dialog est en mode sheet.
318
+ Hors mode sheet, l'élément reste un div vide de hauteur nulle (aucune règle ne s'applique). */
319
+ .cdk-overlay-pane.kt-dialog--sheet [ktDialogSheetHandle],
320
+ .cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__sheet-handle {
321
+ display: flex;
322
+ align-items: center;
323
+ justify-content: center;
324
+ flex: none;
325
+ block-size: 44px; /* Cible tactile minimale WCAG 2.5.5 AAA (44px) */
326
+ cursor: grab;
327
+ touch-action: none; /* le geste ne déclenche pas le scroll de la page */
328
+ }
329
+ .cdk-overlay-pane.kt-dialog--sheet [ktDialogSheetHandle]:active,
330
+ .cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__sheet-handle:active {
331
+ cursor: grabbing;
332
+ }
333
+ .cdk-overlay-pane.kt-dialog--sheet [ktDialogSheetHandle]::before,
334
+ .cdk-overlay-pane.kt-dialog--sheet .kt-dialog-container__sheet-handle::before {
335
+ content: '';
336
+ inline-size: 2.25rem;
337
+ block-size: 0.25rem;
338
+ border-radius: 999px;
339
+ background: var(--kt-sheet-grab-color, var(--kt-outline, #c4c7c5));
340
+ }
341
+
342
+ /* Mouvement réduit : pas de glissement ni de fondu (WCAG 2.3.3). */
343
+ @media (prefers-reduced-motion: reduce) {
344
+ .cdk-overlay-pane.kt-dialog--sheet .cdk-dialog-container,
345
+ .cdk-overlay-pane.kt-dialog--fullscreen .cdk-dialog-container {
346
+ transition: none;
347
+ translate: 0 0;
348
+ }
349
+ }
350
+ }
@@ -0,0 +1,128 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, ElementRef, PLATFORM_ID, signal, input, booleanAttribute, computed, effect, isDevMode, Directive } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+
5
+ const KT_BUTTON_CONFIG = new InjectionToken('KT_BUTTON_CONFIG');
6
+ /**
7
+ * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`
8
+ * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement
9
+ * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).
10
+ *
11
+ * @example
12
+ * ```html
13
+ * <button ktButton mode="filled" color="primary">Enregistrer</button>
14
+ * <a ktButton mode="text" href="/aide">Aide</a>
15
+ * <button ktButton iconOnly icon="close" ariaLabel="Fermer"></button>
16
+ * ```
17
+ */
18
+ class KtButton {
19
+ config = inject(KT_BUTTON_CONFIG, { optional: true });
20
+ host = inject(ElementRef).nativeElement;
21
+ platformId = inject(PLATFORM_ID);
22
+ viewInitialized = signal(false, /* @ts-ignore */
23
+ ...(ngDevMode ? [{ debugName: "viewInitialized" }] : /* istanbul ignore next */ []));
24
+ // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.
25
+ isLink = this.host.tagName === 'A';
26
+ // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).
27
+ nativeAriaLabel = this.host.getAttribute('aria-label');
28
+ /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */
29
+ mode = input(this.config?.mode ?? 'filled', /* @ts-ignore */
30
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
31
+ /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */
32
+ color = input(this.config?.color ?? 'primary', /* @ts-ignore */
33
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
34
+ /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */
35
+ size = input(this.config?.size ?? 'md', /* @ts-ignore */
36
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
37
+ /** Étire le bouton sur toute la largeur disponible. @default false */
38
+ fullWidth = input(false, /* @ts-ignore */
39
+ ...(ngDevMode ? [{ debugName: "fullWidth" }] : /* istanbul ignore next */ []));
40
+ /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */
41
+ iconOnly = input(false, { ...(ngDevMode ? { debugName: "iconOnly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
42
+ /** Nom accessible. Obligatoire si `iconOnly` ; à défaut, l'`aria-label` natif est préservé. */
43
+ ariaLabel = input(/* @ts-ignore */
44
+ ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
45
+ /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */
46
+ type = input('button', /* @ts-ignore */
47
+ ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
48
+ /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */
49
+ loading = input(false, /* @ts-ignore */
50
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
51
+ /** Nom de l'icône (rendu via CSS `data-icon`). */
52
+ icon = input(/* @ts-ignore */
53
+ ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
54
+ /** Position de l'icône relative au texte. @default 'start' */
55
+ iconPosition = input('start', /* @ts-ignore */
56
+ ...(ngDevMode ? [{ debugName: "iconPosition" }] : /* istanbul ignore next */ []));
57
+ /** Désactive le bouton. @default false */
58
+ disabled = input(false, /* @ts-ignore */
59
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
60
+ /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */
61
+ disabledInteractive = input(this.config?.disabledInteractive ?? false, /* @ts-ignore */
62
+ ...(ngDevMode ? [{ debugName: "disabledInteractive" }] : /* istanbul ignore next */ []));
63
+ // loading implique un bouton inerte
64
+ isDisabled = computed(() => this.disabled() || this.loading(), /* @ts-ignore */
65
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
66
+ // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.
67
+ resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null, /* @ts-ignore */
68
+ ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : /* istanbul ignore next */ []));
69
+ constructor() {
70
+ effect(() => {
71
+ if (!isPlatformBrowser(this.platformId))
72
+ return;
73
+ if (!this.viewInitialized())
74
+ return;
75
+ if (!isDevMode() || !this.iconOnly())
76
+ return;
77
+ if (!this.icon()) {
78
+ console.warn('[ktButton] iconOnly attend une icône via [icon].');
79
+ }
80
+ if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {
81
+ console.warn('[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +
82
+ 'le bouton serait annoncé sans libellé.');
83
+ }
84
+ });
85
+ }
86
+ ngAfterViewInit() {
87
+ this.viewInitialized.set(true);
88
+ }
89
+ haltDisabledEvents(event) {
90
+ if (!this.isDisabled())
91
+ return;
92
+ event.preventDefault();
93
+ event.stopImmediatePropagation();
94
+ }
95
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtButton, deps: [], target: i0.ɵɵFactoryTarget.Directive });
96
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: KtButton, isStandalone: true, selector: "button[ktButton], a[ktButton]", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconPosition: { classPropertyName: "iconPosition", publicName: "iconPosition", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, disabledInteractive: { classPropertyName: "disabledInteractive", publicName: "disabledInteractive", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "haltDisabledEvents($event)" }, properties: { "attr.data-mode": "mode()", "attr.data-color": "color()", "attr.data-size": "size()", "attr.data-full-width": "fullWidth() ? \"\" : null", "attr.data-icon-only": "iconOnly() ? \"\" : null", "attr.type": "!isLink ? type() : null", "attr.aria-label": "resolvedAriaLabel()", "class.loading": "loading()", "class.disabled": "isDisabled()", "attr.data-icon": "icon()", "attr.data-icon-position": "iconPosition()", "attr.disabled": "isDisabled() && !disabledInteractive() && !isLink ? \"\" : null", "attr.aria-disabled": "isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null", "attr.tabindex": "isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null", "attr.aria-busy": "loading() ? \"true\" : null" } }, ngImport: i0 });
97
+ }
98
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: KtButton, decorators: [{
99
+ type: Directive,
100
+ args: [{
101
+ selector: 'button[ktButton], a[ktButton]',
102
+ host: {
103
+ '[attr.data-mode]': 'mode()',
104
+ '[attr.data-color]': 'color()',
105
+ '[attr.data-size]': 'size()',
106
+ '[attr.data-full-width]': 'fullWidth() ? "" : null',
107
+ '[attr.data-icon-only]': 'iconOnly() ? "" : null',
108
+ '[attr.type]': '!isLink ? type() : null',
109
+ '[attr.aria-label]': 'resolvedAriaLabel()',
110
+ '[class.loading]': 'loading()',
111
+ '[class.disabled]': 'isDisabled()',
112
+ '[attr.data-icon]': 'icon()',
113
+ '[attr.data-icon-position]': 'iconPosition()',
114
+ '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? "" : null',
115
+ '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? "true" : null',
116
+ '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? "-1" : null',
117
+ '[attr.aria-busy]': 'loading() ? "true" : null',
118
+ '(click)': 'haltDisabledEvents($event)',
119
+ },
120
+ }]
121
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconPosition", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], disabledInteractive: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledInteractive", required: false }] }] } });
122
+
123
+ /**
124
+ * Generated bundle index. Do not edit.
125
+ */
126
+
127
+ export { KT_BUTTON_CONFIG, KtButton };
128
+ //# sourceMappingURL=ktortu-aaa-button.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ktortu-aaa-button.mjs","sources":["../../../../projects/ktortu/aaa/button/button.ts","../../../../projects/ktortu/aaa/button/ktortu-aaa-button.ts"],"sourcesContent":["import {\n Directive,\n ElementRef,\n InjectionToken,\n booleanAttribute,\n computed,\n inject,\n input,\n isDevMode,\n AfterViewInit,\n PLATFORM_ID,\n effect,\n signal,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\n\n/** Axe \"emphase\" (apparence). */\nexport type KtButtonMode = 'filled' | 'tonal' | 'outlined' | 'text';\n/** Axe \"intention\" (couleur sémantique). */\nexport type KtButtonColor = 'primary' | 'neutral' | 'danger';\n/** Taille (la cible md ~44-48px vise le confort tactile / AAA). */\nexport type KtButtonSize = 'sm' | 'md' | 'lg';\n\nexport interface KtButtonConfig {\n disabledInteractive: boolean;\n mode: KtButtonMode;\n color: KtButtonColor;\n size: KtButtonSize;\n}\n\nexport const KT_BUTTON_CONFIG = new InjectionToken<Partial<KtButtonConfig>>('KT_BUTTON_CONFIG');\n\n/**\n * Bouton ou lien stylé par la directive `ktButton`. À poser sur un `<button>`\n * (action) ou un `<a>` (navigation) — l'accessibilité s'adapte automatiquement\n * (un `<a>` désactivé bascule sur `aria-disabled` plutôt que `disabled`).\n *\n * @example\n * ```html\n * <button ktButton mode=\"filled\" color=\"primary\">Enregistrer</button>\n * <a ktButton mode=\"text\" href=\"/aide\">Aide</a>\n * <button ktButton iconOnly icon=\"close\" ariaLabel=\"Fermer\"></button>\n * ```\n */\n@Directive({\n selector: 'button[ktButton], a[ktButton]',\n host: {\n '[attr.data-mode]': 'mode()',\n '[attr.data-color]': 'color()',\n '[attr.data-size]': 'size()',\n '[attr.data-full-width]': 'fullWidth() ? \"\" : null',\n '[attr.data-icon-only]': 'iconOnly() ? \"\" : null',\n '[attr.type]': '!isLink ? type() : null',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[class.loading]': 'loading()',\n '[class.disabled]': 'isDisabled()',\n '[attr.data-icon]': 'icon()',\n '[attr.data-icon-position]': 'iconPosition()',\n '[attr.disabled]': 'isDisabled() && !disabledInteractive() && !isLink ? \"\" : null',\n '[attr.aria-disabled]': 'isDisabled() && (disabledInteractive() || isLink) ? \"true\" : null',\n '[attr.tabindex]': 'isDisabled() && !disabledInteractive() && isLink ? \"-1\" : null',\n '[attr.aria-busy]': 'loading() ? \"true\" : null',\n '(click)': 'haltDisabledEvents($event)',\n },\n})\nexport class KtButton implements AfterViewInit {\n private readonly config = inject(KT_BUTTON_CONFIG, { optional: true });\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n private readonly platformId = inject(PLATFORM_ID);\n private readonly viewInitialized = signal(false);\n\n // Un <a> reste toujours focusable : on bascule sur aria-disabled au lieu du disabled natif.\n protected readonly isLink = this.host.tagName === 'A';\n // aria-label natif posé par le consommateur, capté à la construction (préservé si l'input n'est pas fourni).\n private readonly nativeAriaLabel = this.host.getAttribute('aria-label');\n\n /** Emphase visuelle du bouton (apparence, ≠ `type`). @default 'filled' (ou KT_BUTTON_CONFIG.mode) */\n readonly mode = input<KtButtonMode>(this.config?.mode ?? 'filled');\n\n /** Intention sémantique (couleur). @default 'primary' (ou KT_BUTTON_CONFIG.color) */\n readonly color = input<KtButtonColor>(this.config?.color ?? 'primary');\n\n /** Taille ; `md` vise une cible tactile ~44-48px. @default 'md' (ou KT_BUTTON_CONFIG.size) */\n readonly size = input<KtButtonSize>(this.config?.size ?? 'md');\n\n /** Étire le bouton sur toute la largeur disponible. @default false */\n readonly fullWidth = input<boolean>(false);\n\n /** Bouton carré sans texte ; impose un nom accessible via `ariaLabel`. @default false */\n readonly iconOnly = input<boolean, unknown>(false, { transform: booleanAttribute });\n\n /** Nom accessible. Obligatoire si `iconOnly` ; à défaut, l'`aria-label` natif est préservé. */\n readonly ariaLabel = input<string>();\n\n /** Attribut HTML natif (comportement formulaire), distinct de `mode`. @default 'button' */\n readonly type = input<'button' | 'submit' | 'reset'>('button');\n\n /** Affiche l'état de chargement et rend le bouton inerte (`aria-busy`). @default false */\n readonly loading = input<boolean>(false);\n\n /** Nom de l'icône (rendu via CSS `data-icon`). */\n readonly icon = input<string>();\n\n /** Position de l'icône relative au texte. @default 'start' */\n readonly iconPosition = input<'start' | 'end'>('start');\n\n /** Désactive le bouton. @default false */\n readonly disabled = input<boolean>(false);\n\n /** Garde le bouton focusable même désactivé (via `aria-disabled`). @default false (ou KT_BUTTON_CONFIG.disabledInteractive) */\n readonly disabledInteractive = input<boolean>(this.config?.disabledInteractive ?? false);\n\n // loading implique un bouton inerte\n protected readonly isDisabled = computed(() => this.disabled() || this.loading());\n\n // input prioritaire, sinon aria-label natif préservé (non destructif), sinon rien.\n protected readonly resolvedAriaLabel = computed(() => this.ariaLabel()?.trim() || this.nativeAriaLabel || null);\n\n constructor() {\n effect(() => {\n if (!isPlatformBrowser(this.platformId)) return;\n if (!this.viewInitialized()) return;\n if (!isDevMode() || !this.iconOnly()) return;\n\n if (!this.icon()) {\n console.warn('[ktButton] iconOnly attend une icône via [icon].');\n }\n if (!this.resolvedAriaLabel() && !this.host.hasAttribute('aria-labelledby')) {\n console.warn(\n '[ktButton] iconOnly sans nom accessible : ajoutez [ariaLabel] (WCAG 4.1.2) — ' +\n 'le bouton serait annoncé sans libellé.',\n );\n }\n });\n }\n\n ngAfterViewInit(): void {\n this.viewInitialized.set(true);\n }\n\n protected haltDisabledEvents(event: Event): void {\n if (!this.isDisabled()) return;\n\n event.preventDefault();\n event.stopImmediatePropagation();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;MA8Ba,gBAAgB,GAAG,IAAI,cAAc,CAA0B,kBAAkB;AAE9F;;;;;;;;;;;AAWG;MAsBU,QAAQ,CAAA;IACF,MAAM,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AACrD,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC,CAAC,aAAa;AAChE,IAAA,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC;IAChC,eAAe,GAAG,MAAM,CAAC,KAAK;wFAAC;;IAG7B,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,KAAK,GAAG;;IAEpC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC;;IAG9D,IAAI,GAAG,KAAK,CAAe,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,QAAQ;6EAAC;;IAGzD,KAAK,GAAG,KAAK,CAAgB,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,SAAS;8EAAC;;IAG7D,IAAI,GAAG,KAAK,CAAe,IAAI,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;6EAAC;;IAGrD,SAAS,GAAG,KAAK,CAAU,KAAK;kFAAC;;IAGjC,QAAQ,GAAG,KAAK,CAAmB,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;;AAG1E,IAAA,SAAS,GAAG,KAAK;6FAAU;;IAG3B,IAAI,GAAG,KAAK,CAAgC,QAAQ;6EAAC;;IAGrD,OAAO,GAAG,KAAK,CAAU,KAAK;gFAAC;;AAG/B,IAAA,IAAI,GAAG,KAAK;wFAAU;;IAGtB,YAAY,GAAG,KAAK,CAAkB,OAAO;qFAAC;;IAG9C,QAAQ,GAAG,KAAK,CAAU,KAAK;iFAAC;;IAGhC,mBAAmB,GAAG,KAAK,CAAU,IAAI,CAAC,MAAM,EAAE,mBAAmB,IAAI,KAAK;4FAAC;;AAGrE,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE;mFAAC;;AAG9D,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI;0FAAC;AAE/G,IAAA,WAAA,GAAA;QACE,MAAM,CAAC,MAAK;AACV,YAAA,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC;gBAAE;AACzC,YAAA,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE;gBAAE;YAC7B,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAAE;AAEtC,YAAA,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE;AAChB,gBAAA,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC;YAClE;AACA,YAAA,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE;gBAC3E,OAAO,CAAC,IAAI,CACV,+EAA+E;AAC7E,oBAAA,wCAAwC,CAC3C;YACH;AACF,QAAA,CAAC,CAAC;IACJ;IAEA,eAAe,GAAA;AACb,QAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC;AAEU,IAAA,kBAAkB,CAAC,KAAY,EAAA;AACvC,QAAA,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE;QAExB,KAAK,CAAC,cAAc,EAAE;QACtB,KAAK,CAAC,wBAAwB,EAAE;IAClC;uGAhFW,QAAQ,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAR,QAAQ,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,+BAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,mBAAA,EAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,UAAA,EAAA,qBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,OAAA,EAAA,4BAAA,EAAA,EAAA,UAAA,EAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,sBAAA,EAAA,2BAAA,EAAA,qBAAA,EAAA,0BAAA,EAAA,WAAA,EAAA,yBAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,eAAA,EAAA,WAAA,EAAA,gBAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,gBAAA,EAAA,eAAA,EAAA,iEAAA,EAAA,oBAAA,EAAA,qEAAA,EAAA,eAAA,EAAA,kEAAA,EAAA,gBAAA,EAAA,6BAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAR,QAAQ,EAAA,UAAA,EAAA,CAAA;kBArBpB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,+BAA+B;AACzC,oBAAA,IAAI,EAAE;AACJ,wBAAA,kBAAkB,EAAE,QAAQ;AAC5B,wBAAA,mBAAmB,EAAE,SAAS;AAC9B,wBAAA,kBAAkB,EAAE,QAAQ;AAC5B,wBAAA,wBAAwB,EAAE,yBAAyB;AACnD,wBAAA,uBAAuB,EAAE,wBAAwB;AACjD,wBAAA,aAAa,EAAE,yBAAyB;AACxC,wBAAA,mBAAmB,EAAE,qBAAqB;AAC1C,wBAAA,iBAAiB,EAAE,WAAW;AAC9B,wBAAA,kBAAkB,EAAE,cAAc;AAClC,wBAAA,kBAAkB,EAAE,QAAQ;AAC5B,wBAAA,2BAA2B,EAAE,gBAAgB;AAC7C,wBAAA,iBAAiB,EAAE,+DAA+D;AAClF,wBAAA,sBAAsB,EAAE,mEAAmE;AAC3F,wBAAA,iBAAiB,EAAE,gEAAgE;AACnF,wBAAA,kBAAkB,EAAE,2BAA2B;AAC/C,wBAAA,SAAS,EAAE,4BAA4B;AACxC,qBAAA;AACF,iBAAA;;;AChED;;AAEG;;;;"}