@starterlib/ui 0.7.2 → 0.8.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 (35) hide show
  1. package/README.md +95 -42
  2. package/fesm2022/starterlib-ui-arbre.mjs +82 -0
  3. package/fesm2022/starterlib-ui-arbre.mjs.map +1 -0
  4. package/fesm2022/starterlib-ui-donnees.mjs +532 -0
  5. package/fesm2022/starterlib-ui-donnees.mjs.map +1 -0
  6. package/fesm2022/starterlib-ui-formulaires.mjs +2 -1
  7. package/fesm2022/starterlib-ui-formulaires.mjs.map +1 -1
  8. package/fesm2022/starterlib-ui-interactions.mjs +34 -589
  9. package/fesm2022/starterlib-ui-interactions.mjs.map +1 -1
  10. package/fesm2022/starterlib-ui-libelles-en.mjs +142 -0
  11. package/fesm2022/starterlib-ui-libelles-en.mjs.map +1 -0
  12. package/fesm2022/starterlib-ui-listes.mjs +671 -0
  13. package/fesm2022/starterlib-ui-listes.mjs.map +1 -0
  14. package/fesm2022/starterlib-ui-navigation.mjs +246 -0
  15. package/fesm2022/starterlib-ui-navigation.mjs.map +1 -0
  16. package/fesm2022/starterlib-ui-onglets.mjs +137 -0
  17. package/fesm2022/starterlib-ui-onglets.mjs.map +1 -0
  18. package/fesm2022/starterlib-ui-saisie.mjs +1632 -0
  19. package/fesm2022/starterlib-ui-saisie.mjs.map +1 -0
  20. package/fesm2022/starterlib-ui-selection.mjs +269 -0
  21. package/fesm2022/starterlib-ui-selection.mjs.map +1 -0
  22. package/fesm2022/starterlib-ui.mjs +105 -2931
  23. package/fesm2022/starterlib-ui.mjs.map +1 -1
  24. package/package.json +35 -3
  25. package/types/starterlib-ui-arbre.d.ts +51 -0
  26. package/types/starterlib-ui-donnees.d.ts +228 -0
  27. package/types/starterlib-ui-formulaires.d.ts +5 -4
  28. package/types/starterlib-ui-interactions.d.ts +4 -316
  29. package/types/starterlib-ui-libelles-en.d.ts +25 -0
  30. package/types/starterlib-ui-listes.d.ts +432 -0
  31. package/types/starterlib-ui-navigation.d.ts +104 -0
  32. package/types/starterlib-ui-onglets.d.ts +89 -0
  33. package/types/starterlib-ui-saisie.d.ts +924 -0
  34. package/types/starterlib-ui-selection.d.ts +112 -0
  35. package/types/starterlib-ui.d.ts +9 -1575
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, LOCALE_ID, signal, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, ElementRef, DestroyRef, output, model, afterNextRender, viewChild, effect, Directive, Injector, linkedSignal, TemplateRef, contentChildren, contentChild, isDevMode } from '@angular/core';
3
- import { masquerIban, masquerTelephone, masquerCarte, quatreDerniers, formaterIban, formaterMontant, formaterTaille, formaterDateLongue, montant, decimalesDe, motifDeDate, formaterDateSaisie, ibanEstValide, normaliserTelephone, typeDeFichierAccepte, normaliserIban, bornerDate, premierJourDeLaSemaine, ajouterJours, semainesDuMois, ajouterMois, jourDeLaSemaine, CHOIX_DE_THEME, LANGUES } from '@starterlib/util';
2
+ import { InjectionToken, inject, LOCALE_ID, signal, input, computed, ChangeDetectionStrategy, Component, booleanAttribute, ElementRef, DestroyRef, output, viewChild, afterNextRender, effect, TemplateRef, Directive, contentChild, linkedSignal, isDevMode } from '@angular/core';
3
+ import { CHOIX_DE_THEME, LANGUES } from '@starterlib/util';
4
4
  export { ETATS_DE_BLOC } from '@starterlib/util';
5
5
  import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
6
6
  import * as i1 from '@angular/router';
@@ -158,134 +158,6 @@ const LIBELLES_FR = {
158
158
  recopierPourConfirmer: (texte) => `Pour confirmer, recopiez \u00ab\u202f${texte}\u202f\u00bb`,
159
159
  aLInstant: "a l'instant",
160
160
  };
161
- /** Traduction anglaise du meme dictionnaire. La paire est obligatoire. */
162
- const LIBELLES_EN = {
163
- chargementEnCours: 'Loading',
164
- erreurSurvenue: 'Something went wrong',
165
- aucunResultat: 'No results',
166
- horsConnexion: 'Offline',
167
- reessayer: 'Try again',
168
- identifiantDeTrace: 'Trace identifier',
169
- codeTechnique: 'Code',
170
- copier: 'Copy',
171
- copieEffectuee: 'Copied',
172
- copieImpossible: 'Could not copy. Select the text to copy it.',
173
- afficher: 'Show',
174
- masquer: 'Hide',
175
- valeurMasqueeANouveau: 'Value hidden again',
176
- natureIban: 'IBAN',
177
- natureCarte: 'card',
178
- natureTelephone: 'number',
179
- seTerminantPar: (nature, quatreDerniers) => `${nature} ending in ${quatreDerniers}`,
180
- nouvelleFenetre: '(opens in a new window)',
181
- retirer: 'Remove',
182
- filDAriane: 'Breadcrumb',
183
- allerAuContenu: 'Skip to content',
184
- navigationPrincipale: 'Main navigation',
185
- liensDuPied: 'Footer links',
186
- chargement: 'Loading',
187
- plus: 'More',
188
- plusDActions: 'More actions',
189
- etapes: 'Steps',
190
- etapeTerminee: 'completed',
191
- etapeEnCours: 'current',
192
- etapeAVenir: 'upcoming',
193
- tendanceEnHausse: 'up',
194
- tendanceEnBaisse: 'down',
195
- tendanceStable: 'unchanged',
196
- theme: 'Theme',
197
- themeSysteme: 'System',
198
- themeClair: 'Light',
199
- themeSombre: 'Dark',
200
- langue: 'Language',
201
- reessayerDans: (duree) => `You can try again in ${duree}`,
202
- reessayerMaintenant: 'You can try again now.',
203
- sessionBientotExpiree: 'Your session is about to expire',
204
- sessionExpireDans: (duree) => `Without any activity, you will be signed out in ${duree}.`,
205
- resterConnecte: 'Stay signed in',
206
- seDeconnecter: 'Sign out',
207
- obligatoire: '(required)',
208
- champObligatoire: 'This field is required.',
209
- valeurInvalide: 'This value is not valid.',
210
- adresseInvalide: 'Enter an email address, like name@example.com.',
211
- formatInvalide: 'The format is not valid.',
212
- longueurMinimale: (minimum) => `Enter at least ${String(minimum)} characters.`,
213
- longueurMaximale: (maximum) => `Enter no more than ${String(maximum)} characters.`,
214
- valeurMinimale: (minimum) => `The value must be at least ${minimum}.`,
215
- valeurMaximale: (maximum) => `The value must be at most ${maximum}.`,
216
- caracteresRestants: (restants) => restants === 1 ? '1 character remaining' : `${String(restants)} characters remaining`,
217
- caracteresEnTrop: (enTrop) => enTrop === 1 ? '1 character too many' : `${String(enTrop)} characters too many`,
218
- erreursACorriger: (nombre) => nombre === 1 ? 'There is 1 problem to fix' : `There are ${String(nombre)} problems to fix`,
219
- afficherLeMotDePasse: 'Show password',
220
- codeAChiffres: (longueur) => `${String(longueur)}-digit code.`,
221
- ibanInvalide: 'Enter a valid IBAN.',
222
- telephoneInvalide: 'Enter a phone number using digits, with the country code for a number abroad.',
223
- exempleDIban: 'For example FR76 3000 6000 0112 3456 7890 189.',
224
- rechercher: 'Search',
225
- fermerLeMessage: 'Dismiss message',
226
- montantIllisible: (exemple) => `Enter an amount in figures, like ${exemple}.`,
227
- tropDeDecimales: (decimales) => decimales === 0
228
- ? 'Enter an amount without decimals.'
229
- : `Enter no more than ${String(decimales)} decimal place${decimales > 1 ? 's' : ''}.`,
230
- deviseDuMontant: (devise) => `Currency: ${devise}.`,
231
- lettreDuJour: 'dd',
232
- lettreDuMois: 'mm',
233
- lettreDeLAnnee: 'yyyy',
234
- formatDeDate: (motif, exemple) => `Use the format ${motif}, like ${exemple}.`,
235
- dateIllisible: (motif, exemple) => `Enter a real date in the format ${motif}, like ${exemple}.`,
236
- dateTropTot: (date) => `Enter a date on or after ${date}.`,
237
- dateTropTard: (date) => `Enter a date on or before ${date}.`,
238
- finAvantDebut: 'The end date cannot be before the start date.',
239
- dateDeDebut: 'Start date',
240
- dateDeFin: 'End date',
241
- choisirDansLeCalendrier: (champ) => `${champ}: choose from calendar`,
242
- moisPrecedent: 'Previous month',
243
- moisSuivant: 'Next month',
244
- aideDuCalendrier: 'Arrow keys to change day, Page Up and Page Down to change month, Escape to close.',
245
- choisir: 'Choose\u2026',
246
- nombreDeResultats: (nombre) => nombre === 0 ? 'No results' : `${String(nombre)} result${nombre > 1 ? 's' : ''}`,
247
- nombreDeSelections: (nombre) => nombre === 0 ? 'Nothing selected' : `${String(nombre)} selected`,
248
- retirerLeChoix: (choix) => `Remove ${choix}`,
249
- afficherLesChoix: (champ) => `${champ}: show options`,
250
- choisirDesFichiers: 'Choose files',
251
- choisirUnFichier: 'Choose a file',
252
- ouDeposerIci: 'or drop them here',
253
- aucunFichier: 'No file chosen.',
254
- nombreDeFichiers: (nombre) => nombre === 0 ? 'No file chosen.' : `${String(nombre)} file${nombre > 1 ? 's' : ''} chosen.`,
255
- retirerLeFichier: (fichier) => `Remove ${fichier}`,
256
- fichierTropVolumineux: (fichier, taille) => `${fichier} is larger than ${taille}.`,
257
- typeDeFichierRefuse: (fichier) => `${fichier} is not an accepted file type.`,
258
- tropDeFichiers: (nombre) => `Choose no more than ${String(nombre)} file${nombre > 1 ? 's' : ''}.`,
259
- typesAcceptes: (types) => `Accepted types: ${types}.`,
260
- tailleMaximale: (taille) => `${taille} maximum per file.`,
261
- televersementDe: (fichier) => `Uploading ${fichier}`,
262
- annulerLeTeleversement: (fichier) => `Cancel upload of ${fichier}`,
263
- fichierDepose: (fichier) => `${fichier} uploaded`,
264
- fichierRefuse: (fichier) => `${fichier} rejected`,
265
- pagination: 'Pagination',
266
- pagePrecedente: 'Previous page',
267
- pageSuivante: 'Next page',
268
- pageNumero: (numero) => `Page ${String(numero)}`,
269
- afficherPlus: 'Show more',
270
- lignesParPage: 'Rows per page',
271
- selectionnerLaPage: (nombre) => `Select the ${String(nombre)} rows on this page`,
272
- selectionnerLaLigne: (ligne) => `Select ${ligne}`,
273
- lignesSelectionnees: (nombre) => nombre === 0 ? 'No rows selected' : `${String(nombre)} row${nombre > 1 ? 's' : ''} selected`,
274
- trieCroissant: (colonne) => `Sorted by ${colonne}, ascending`,
275
- trieDecroissant: (colonne) => `Sorted by ${colonne}, descending`,
276
- tableauVide: 'No rows to display.',
277
- nonRenseigne: 'Not provided',
278
- filtres: 'Filters',
279
- filtresActifs: 'Active filters',
280
- retirerLeFiltre: (filtre) => `Remove filter ${filtre}`,
281
- effacerLesFiltres: 'Clear filters',
282
- filtreRetire: (filtre) => `Filter ${filtre} removed`,
283
- filtresEffaces: 'Filters cleared',
284
- confirmer: 'Confirm',
285
- annuler: 'Cancel',
286
- recopierPourConfirmer: (texte) => `To confirm, type \u201c${texte}\u201d`,
287
- aLInstant: 'just now',
288
- };
289
161
 
290
162
  /**
291
163
  * Locale de formatage des montants, des dates et des nombres, **sous forme de
@@ -1159,504 +1031,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
1159
1031
  args: [{ selector: 'st-code', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIconButton], host: { class: 'st-code' }, template: "<code aria-hidden=\"true\">{{ valeur() }}</code>\n<span class=\"st-vh\">{{ etiquetteLue() + ' ' + valeur() }}</span>\n\n@if (copiePossible()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton. Le nom accessible est\n impose par le type (`etiquette` est requis) et verifie par axe. -->\n <button\n stIconButton\n type=\"button\"\n icone=\"copie\"\n taille=\"sm\"\n [etiquette]=\"libelleCopierLu()\"\n (click)=\"auClicDeCopie()\"\n ></button>\n}\n\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [":host([hidden]){display:none!important}:host{display:inline-flex;gap:var(--st-espacement-1);align-items:center}code{padding-block:var(--st-espacement-1);padding-inline:var(--st-espacement-2);font-family:var(--st-police-mono);font-size:var(--st-taille-sm);color:var(--st-couleur-texte);background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure);border-radius:var(--st-rayon-sm);overflow-wrap:anywhere;-webkit-user-select:all;user-select:all}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1160
1032
  }], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], copiable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copiable", required: false }] }], etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }], libelleCopier: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelleCopier", required: false }] }], messageDeConfirmation: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageDeConfirmation", required: false }] }], copie: [{ type: i0.Output, args: ["copie"] }] } });
1161
1033
 
1162
- /**
1163
- * Identifiant sensible, masque par defaut.
1164
- *
1165
- * ⚠️ La valeur masquee porte un `aria-label` en toutes lettres (« IBAN se
1166
- * terminant par 2606 ») : plusieurs lecteurs d'ecran epellent une suite de
1167
- * puces caractere par caractere, ce qui est a la fois illisible et interminable.
1168
- *
1169
- * La revelation est un EVENEMENT METIER : elle se journalise cote appelant par
1170
- * la sortie `revelation`. La valeur revelee, elle, ne part jamais dans un
1171
- * journal.
1172
- */
1173
- class StMasked {
1174
- libelles = inject(ST_LIBELLES);
1175
- valeur = input.required(/* @ts-ignore */
1176
- ...(ngDevMode ? [{ debugName: "valeur" }] : /* istanbul ignore next */ []));
1177
- type = input('iban', /* @ts-ignore */
1178
- ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
1179
- revele = model(false, /* @ts-ignore */
1180
- ...(ngDevMode ? [{ debugName: "revele" }] : /* istanbul ignore next */ []));
1181
- revelationAutorisee = input(false, { ...(ngDevMode ? { debugName: "revelationAutorisee" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1182
- dureeDeRevelationEnSecondes = input(30, /* @ts-ignore */
1183
- ...(ngDevMode ? [{ debugName: "dureeDeRevelationEnSecondes" }] : /* istanbul ignore next */ []));
1184
- /** `null` : les textes viennent du dictionnaire de l'application. */
1185
- libelleAfficher = input(null, /* @ts-ignore */
1186
- ...(ngDevMode ? [{ debugName: "libelleAfficher" }] : /* istanbul ignore next */ []));
1187
- libelleMasquer = input(null, /* @ts-ignore */
1188
- ...(ngDevMode ? [{ debugName: "libelleMasquer" }] : /* istanbul ignore next */ []));
1189
- revelation = output();
1190
- /**
1191
- * ⚠️ Par `AnnonceLocale`, et non par un signal pose directement. La 0.2.0
1192
- * reposait « Valeur masquee a nouveau » a l'identique : aucune mutation, donc
1193
- * une seconde remise en masque jamais annoncee.
1194
- */
1195
- annonce = new AnnonceLocale(inject(DestroyRef));
1196
- minuterie = null;
1197
- constructor() {
1198
- // Enregistre UNE fois. La 0.2.0 l'enregistrait a chaque revelation, et
1199
- // chaque clic empilait un rappel de plus.
1200
- inject(DestroyRef).onDestroy(() => {
1201
- if (this.minuterie !== null)
1202
- clearTimeout(this.minuterie);
1203
- });
1204
- }
1205
- valeurMasquee = computed(() => {
1206
- const valeur = this.valeur();
1207
- switch (this.type()) {
1208
- case 'carte':
1209
- return masquerCarte(valeur);
1210
- case 'telephone':
1211
- return masquerTelephone(valeur);
1212
- default:
1213
- return masquerIban(valeur);
1214
- }
1215
- }, /* @ts-ignore */
1216
- ...(ngDevMode ? [{ debugName: "valeurMasquee" }] : /* istanbul ignore next */ []));
1217
- valeurAffichee = computed(() => this.revele() ? this.valeurLisible() : this.valeurMasquee(), /* @ts-ignore */
1218
- ...(ngDevMode ? [{ debugName: "valeurAffichee" }] : /* istanbul ignore next */ []));
1219
- /**
1220
- * La nature et la tournure viennent du dictionnaire : l'ordre des mots change
1221
- * d'une langue a l'autre, « IBAN se terminant par 2606 » contre « IBAN ending
1222
- * in 2606 ». Une chaine a trous ne suffirait pas.
1223
- */
1224
- nomAccessible = computed(() => {
1225
- const libelles = this.libelles();
1226
- const nature = {
1227
- iban: libelles.natureIban,
1228
- carte: libelles.natureCarte,
1229
- telephone: libelles.natureTelephone,
1230
- }[this.type()];
1231
- if (this.revele())
1232
- return `${nature} ${this.valeurLisible()}`;
1233
- return libelles.seTerminantPar(nature, quatreDerniers(this.valeur()));
1234
- }, /* @ts-ignore */
1235
- ...(ngDevMode ? [{ debugName: "nomAccessible" }] : /* istanbul ignore next */ []));
1236
- libelleDuBouton = computed(() => this.revele()
1237
- ? (this.libelleMasquer() ?? this.libelles().masquer)
1238
- : (this.libelleAfficher() ?? this.libelles().afficher), /* @ts-ignore */
1239
- ...(ngDevMode ? [{ debugName: "libelleDuBouton" }] : /* istanbul ignore next */ []));
1240
- valeurLisible() {
1241
- return this.type() === 'iban' ? formaterIban(this.valeur()) : this.valeur();
1242
- }
1243
- basculer() {
1244
- if (this.revele()) {
1245
- this.masquer();
1246
- return;
1247
- }
1248
- this.revele.set(true);
1249
- this.revelation.emit({ type: this.type() });
1250
- // Re-masquage automatique : une valeur revelee puis oubliee a l'ecran est
1251
- // lisible par quiconque passe derriere.
1252
- if (this.minuterie !== null)
1253
- clearTimeout(this.minuterie);
1254
- this.minuterie = setTimeout(() => {
1255
- this.masquer();
1256
- }, this.dureeDeRevelationEnSecondes() * 1000);
1257
- }
1258
- masquer() {
1259
- if (this.minuterie !== null) {
1260
- clearTimeout(this.minuterie);
1261
- this.minuterie = null;
1262
- }
1263
- if (!this.revele())
1264
- return;
1265
- this.revele.set(false);
1266
- this.annonce.annoncer(this.libelles().valeurMasqueeANouveau);
1267
- }
1268
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMasked, deps: [], target: i0.ɵɵFactoryTarget.Component });
1269
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StMasked, isStandalone: true, selector: "st-masked", inputs: { valeur: { classPropertyName: "valeur", publicName: "valeur", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, revele: { classPropertyName: "revele", publicName: "revele", isSignal: true, isRequired: false, transformFunction: null }, revelationAutorisee: { classPropertyName: "revelationAutorisee", publicName: "revelationAutorisee", isSignal: true, isRequired: false, transformFunction: null }, dureeDeRevelationEnSecondes: { classPropertyName: "dureeDeRevelationEnSecondes", publicName: "dureeDeRevelationEnSecondes", isSignal: true, isRequired: false, transformFunction: null }, libelleAfficher: { classPropertyName: "libelleAfficher", publicName: "libelleAfficher", isSignal: true, isRequired: false, transformFunction: null }, libelleMasquer: { classPropertyName: "libelleMasquer", publicName: "libelleMasquer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { revele: "reveleChange", revelation: "revelation" }, host: { properties: { "attr.data-type": "type()" }, classAttribute: "st-masked" }, ngImport: i0, template: "<span class=\"st-masked__valeur\" aria-hidden=\"true\">{{ valeurAffichee() }}</span>\n<span class=\"st-vh\">{{ nomAccessible() }}</span>\n\n@if (revelationAutorisee()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton. Le nom accessible est\n impose par le type (`etiquette` est requis) et verifie par axe. -->\n <button\n stIconButton\n type=\"button\"\n taille=\"sm\"\n [icone]=\"revele() ? 'oeil-barre' : 'oeil'\"\n [etiquette]=\"libelleDuBouton()\"\n [enfonce]=\"revele()\"\n (click)=\"basculer()\"\n ></button>\n}\n\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [":host([hidden]){display:none!important}:host{display:inline-flex;gap:var(--st-espacement-2);align-items:center}.st-masked__valeur{font-family:var(--st-police-mono);color:var(--st-couleur-texte);letter-spacing:.02em}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: StIconButton, selector: "button[stIconButton]", inputs: ["icone", "etiquette", "variante", "taille", "enfonce"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1270
- }
1271
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMasked, decorators: [{
1272
- type: Component,
1273
- args: [{ selector: 'st-masked', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIconButton], host: { class: 'st-masked', '[attr.data-type]': 'type()' }, template: "<span class=\"st-masked__valeur\" aria-hidden=\"true\">{{ valeurAffichee() }}</span>\n<span class=\"st-vh\">{{ nomAccessible() }}</span>\n\n@if (revelationAutorisee()) {\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton. Le nom accessible est\n impose par le type (`etiquette` est requis) et verifie par axe. -->\n <button\n stIconButton\n type=\"button\"\n taille=\"sm\"\n [icone]=\"revele() ? 'oeil-barre' : 'oeil'\"\n [etiquette]=\"libelleDuBouton()\"\n [enfonce]=\"revele()\"\n (click)=\"basculer()\"\n ></button>\n}\n\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [":host([hidden]){display:none!important}:host{display:inline-flex;gap:var(--st-espacement-2);align-items:center}.st-masked__valeur{font-family:var(--st-police-mono);color:var(--st-couleur-texte);letter-spacing:.02em}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1274
- }], ctorParameters: () => [], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], revele: [{ type: i0.Input, args: [{ isSignal: true, alias: "revele", required: false }] }, { type: i0.Output, args: ["reveleChange"] }], revelationAutorisee: [{ type: i0.Input, args: [{ isSignal: true, alias: "revelationAutorisee", required: false }] }], dureeDeRevelationEnSecondes: [{ type: i0.Input, args: [{ isSignal: true, alias: "dureeDeRevelationEnSecondes", required: false }] }], libelleAfficher: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelleAfficher", required: false }] }], libelleMasquer: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelleMasquer", required: false }] }], revelation: [{ type: i0.Output, args: ["revelation"] }] } });
1275
-
1276
- /**
1277
- * Somme d'argent.
1278
- *
1279
- * Le montant entre sous forme d'unites mineures entieres : un nombre a virgule
1280
- * flottante ne represente pas exactement 0,10 EUR, et les erreurs s'accumulent.
1281
- * La conversion n'a lieu qu'a l'instant du rendu.
1282
- *
1283
- * ⚠️ Le signe negatif est TOUJOURS rendu en texte. La couleur ne fait que le
1284
- * renforcer : un daltonien, une impression en noir et blanc ou un theme a fort
1285
- * contraste ne doivent jamais transformer un debit en credit.
1286
- */
1287
- class StMoney {
1288
- locale = inject(ST_LOCALE);
1289
- valeur = input.required(/* @ts-ignore */
1290
- ...(ngDevMode ? [{ debugName: "valeur" }] : /* istanbul ignore next */ []));
1291
- signeExplicite = input(false, { ...(ngDevMode ? { debugName: "signeExplicite" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1292
- sansDevise = input(false, { ...(ngDevMode ? { debugName: "sansDevise" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1293
- taille = input('md', /* @ts-ignore */
1294
- ...(ngDevMode ? [{ debugName: "taille" }] : /* istanbul ignore next */ []));
1295
- signe = computed(() => {
1296
- const unites = this.valeur().unitesMineures;
1297
- if (unites < 0n)
1298
- return 'negatif';
1299
- if (unites > 0n)
1300
- return 'positif';
1301
- return 'nul';
1302
- }, /* @ts-ignore */
1303
- ...(ngDevMode ? [{ debugName: "signe" }] : /* istanbul ignore next */ []));
1304
- texte = computed(() => formaterMontant(this.valeur(), {
1305
- locale: this.locale(),
1306
- signeExplicite: this.signeExplicite(),
1307
- sansDevise: this.sansDevise(),
1308
- }), /* @ts-ignore */
1309
- ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
1310
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMoney, deps: [], target: i0.ɵɵFactoryTarget.Component });
1311
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StMoney, isStandalone: true, selector: "st-money", inputs: { valeur: { classPropertyName: "valeur", publicName: "valeur", isSignal: true, isRequired: true, transformFunction: null }, signeExplicite: { classPropertyName: "signeExplicite", publicName: "signeExplicite", isSignal: true, isRequired: false, transformFunction: null }, sansDevise: { classPropertyName: "sansDevise", publicName: "sansDevise", isSignal: true, isRequired: false, transformFunction: null }, taille: { classPropertyName: "taille", publicName: "taille", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-taille": "taille()", "attr.data-signe": "signe()" }, classAttribute: "st-money" }, ngImport: i0, template: "{{ texte() }}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-moyenne);color:var(--st-couleur-texte);white-space:nowrap}:host([data-signe=negatif]){color:var(--st-couleur-danger)}:host([data-taille=sm]){font-size:var(--st-taille-sm)}:host([data-taille=lg]){font-size:var(--st-taille-lg)}:host([data-taille=xl]){font-size:var(--st-taille-xl)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1312
- }
1313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMoney, decorators: [{
1314
- type: Component,
1315
- args: [{ selector: 'st-money', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1316
- class: 'st-money',
1317
- '[attr.data-taille]': 'taille()',
1318
- '[attr.data-signe]': 'signe()',
1319
- }, template: "{{ texte() }}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-moyenne);color:var(--st-couleur-texte);white-space:nowrap}:host([data-signe=negatif]){color:var(--st-couleur-danger)}:host([data-taille=sm]){font-size:var(--st-taille-sm)}:host([data-taille=lg]){font-size:var(--st-taille-lg)}:host([data-taille=xl]){font-size:var(--st-taille-xl)}\n"] }]
1320
- }], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], signeExplicite: [{ type: i0.Input, args: [{ isSignal: true, alias: "signeExplicite", required: false }] }], sansDevise: [{ type: i0.Input, args: [{ isSignal: true, alias: "sansDevise", required: false }] }], taille: [{ type: i0.Input, args: [{ isSignal: true, alias: "taille", required: false }] }] } });
1321
-
1322
- const OPTIONS = {
1323
- court: { day: '2-digit', month: '2-digit', year: 'numeric' },
1324
- long: { day: 'numeric', month: 'long', year: 'numeric' },
1325
- 'date-heure': {
1326
- day: '2-digit',
1327
- month: '2-digit',
1328
- year: 'numeric',
1329
- hour: '2-digit',
1330
- minute: '2-digit',
1331
- },
1332
- heure: { hour: '2-digit', minute: '2-digit' },
1333
- };
1334
- /**
1335
- * Date rendue dans la locale et le fuseau de l'application.
1336
- *
1337
- * ⚠️ La locale et le fuseau sont EXPLICITES, jamais deduits de l'environnement
1338
- * d'execution. Un rendu serveur et un rendu navigateur qui ne s'accordent pas
1339
- * produisent deux textes differents, et l'hydratation casse. Le rendu serveur
1340
- * est ecarte en version 1, mais la compatibilite reste gardee a cout nul.
1341
- *
1342
- * Le format court garde un nom accessible en toutes lettres : « 12/09/26 » est
1343
- * ambigu a l'oral, et l'ordre jour-mois n'est pas universel.
1344
- */
1345
- class StDate {
1346
- locale = inject(ST_LOCALE);
1347
- fuseauParDefaut = inject(FUSEAU_PAR_DEFAUT);
1348
- valeur = input.required(/* @ts-ignore */
1349
- ...(ngDevMode ? [{ debugName: "valeur" }] : /* istanbul ignore next */ []));
1350
- format = input('court', /* @ts-ignore */
1351
- ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
1352
- fuseau = input(null, /* @ts-ignore */
1353
- ...(ngDevMode ? [{ debugName: "fuseau" }] : /* istanbul ignore next */ []));
1354
- /**
1355
- * Une DATE DE CALENDRIER (`2026-09-12`) n'est pas un instant : une date de
1356
- * naissance, une echeance ou un jour de valeur n'ont pas d'heure, et ne
1357
- * doivent pas changer de jour selon le fuseau de qui les regarde.
1358
- *
1359
- * ⚠️ Le piege est dans la norme : `new Date('2026-09-12')` vaut minuit UTC,
1360
- * donc le 11 septembre partout a l'ouest de Greenwich. Une echeance affichee
1361
- * la veille est un defaut que personne ne voit depuis l'Europe.
1362
- */
1363
- estDateDeCalendrier = computed(() => {
1364
- const valeur = this.valeur();
1365
- return typeof valeur === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(valeur);
1366
- }, /* @ts-ignore */
1367
- ...(ngDevMode ? [{ debugName: "estDateDeCalendrier" }] : /* istanbul ignore next */ []));
1368
- date = computed(() => {
1369
- const valeur = this.valeur();
1370
- return valeur instanceof Date ? valeur : new Date(valeur);
1371
- }, /* @ts-ignore */
1372
- ...(ngDevMode ? [{ debugName: "date" }] : /* istanbul ignore next */ []));
1373
- estValide = computed(() => !Number.isNaN(this.date().getTime()), /* @ts-ignore */
1374
- ...(ngDevMode ? [{ debugName: "estValide" }] : /* istanbul ignore next */ []));
1375
- /**
1376
- * Le fuseau de rendu. Pour une date de calendrier, c'est UTC : la valeur a
1377
- * ete construite a minuit UTC, la relire en UTC rend donc le jour ecrit.
1378
- */
1379
- fuseauDeRendu = computed(() => this.estDateDeCalendrier() ? 'UTC' : (this.fuseau() ?? this.fuseauParDefaut), /* @ts-ignore */
1380
- ...(ngDevMode ? [{ debugName: "fuseauDeRendu" }] : /* istanbul ignore next */ []));
1381
- /**
1382
- * Une date de calendrier porte `2026-09-12`, sans heure ni decalage : c'est
1383
- * la forme que la norme HTML attend, et la seule qui ne mente pas.
1384
- */
1385
- iso = computed(() => {
1386
- if (!this.estValide())
1387
- return null;
1388
- if (this.estDateDeCalendrier())
1389
- return this.date().toISOString().slice(0, 10);
1390
- return this.date().toISOString();
1391
- }, /* @ts-ignore */
1392
- ...(ngDevMode ? [{ debugName: "iso" }] : /* istanbul ignore next */ []));
1393
- /** Une date sans heure ne peut pas en afficher une. */
1394
- optionsDeFormat = computed(() => {
1395
- const options = OPTIONS[this.format()];
1396
- if (!this.estDateDeCalendrier())
1397
- return options;
1398
- // Le format « heure » n'a aucun sens ici : il ne resterait rien a afficher.
1399
- if (this.format() === 'heure')
1400
- return OPTIONS.court;
1401
- const sansHeure = { ...options };
1402
- delete sansHeure.hour;
1403
- delete sansHeure.minute;
1404
- return sansHeure;
1405
- }, /* @ts-ignore */
1406
- ...(ngDevMode ? [{ debugName: "optionsDeFormat" }] : /* istanbul ignore next */ []));
1407
- texte = computed(() => {
1408
- if (!this.estValide())
1409
- return '';
1410
- return new Intl.DateTimeFormat(this.locale(), {
1411
- ...this.optionsDeFormat(),
1412
- timeZone: this.fuseauDeRendu(),
1413
- }).format(this.date());
1414
- }, /* @ts-ignore */
1415
- ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
1416
- /** Forme longue, lue par les technologies d'assistance. */
1417
- texteAccessible = computed(() => {
1418
- if (!this.estValide())
1419
- return '';
1420
- if (this.format() === 'court') {
1421
- return new Intl.DateTimeFormat(this.locale(), {
1422
- ...OPTIONS.long,
1423
- timeZone: this.fuseauDeRendu(),
1424
- }).format(this.date());
1425
- }
1426
- return null;
1427
- }, /* @ts-ignore */
1428
- ...(ngDevMode ? [{ debugName: "texteAccessible" }] : /* istanbul ignore next */ []));
1429
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDate, deps: [], target: i0.ɵɵFactoryTarget.Component });
1430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StDate, isStandalone: true, selector: "st-date", inputs: { valeur: { classPropertyName: "valeur", publicName: "valeur", isSignal: true, isRequired: true, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, fuseau: { classPropertyName: "fuseau", publicName: "fuseau", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-date" }, ngImport: i0, template: "@if (estValide()) {\n <time [attr.datetime]=\"iso()\" aria-hidden=\"true\">{{ texte() }}</time>\n @if (texteAccessible(); as lu) {\n <!-- Rien quand le format affiche se lit deja : un noeud vide ferait\n croire a un texte de remplacement absent. -->\n <span class=\"st-vh\">{{ lu }}</span>\n }\n}\n", styles: [":host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;color:var(--st-couleur-texte);white-space:nowrap}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1431
- }
1432
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDate, decorators: [{
1433
- type: Component,
1434
- args: [{ selector: 'st-date', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'st-date' }, template: "@if (estValide()) {\n <time [attr.datetime]=\"iso()\" aria-hidden=\"true\">{{ texte() }}</time>\n @if (texteAccessible(); as lu) {\n <!-- Rien quand le format affiche se lit deja : un noeud vide ferait\n croire a un texte de remplacement absent. -->\n <span class=\"st-vh\">{{ lu }}</span>\n }\n}\n", styles: [":host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;color:var(--st-couleur-texte);white-space:nowrap}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1435
- }], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], fuseau: [{ type: i0.Input, args: [{ isSignal: true, alias: "fuseau", required: false }] }] } });
1436
-
1437
- const MINUTE = 60_000;
1438
- const HEURE = 60 * MINUTE;
1439
- const JOUR = 24 * HEURE;
1440
- /**
1441
- * Temps ecoule, exprime relativement (« il y a 5 minutes »).
1442
- *
1443
- * ⚠️ Ce composant ne doit JAMAIS etre place dans une region vivante : son texte
1444
- * se reecrit tout seul, et une region vivante le ferait annoncer en boucle.
1445
- * Son nom accessible porte la date absolue, qui elle ne bouge pas.
1446
- *
1447
- * La cadence de rafraichissement s'adapte : rafraichir chaque seconde une date
1448
- * vieille de trois jours reveille l'onglet pour rien.
1449
- */
1450
- class StRelativeTime {
1451
- locale = inject(ST_LOCALE);
1452
- libelles = inject(ST_LIBELLES);
1453
- fuseau = inject(FUSEAU_PAR_DEFAUT);
1454
- valeur = input.required(/* @ts-ignore */
1455
- ...(ngDevMode ? [{ debugName: "valeur" }] : /* istanbul ignore next */ []));
1456
- rafraichir = input(true, { ...(ngDevMode ? { debugName: "rafraichir" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1457
- /** Au-dela de ce nombre de jours, la date absolue est plus parlante. */
1458
- seuilAbsoluEnJours = input(7, /* @ts-ignore */
1459
- ...(ngDevMode ? [{ debugName: "seuilAbsoluEnJours" }] : /* istanbul ignore next */ []));
1460
- destruction = inject(DestroyRef);
1461
- maintenant = signal(Date.now(), /* @ts-ignore */
1462
- ...(ngDevMode ? [{ debugName: "maintenant" }] : /* istanbul ignore next */ []));
1463
- constructor() {
1464
- // `afterNextRender` : aucune minuterie ne demarre a la construction, ce qui
1465
- // garde le composant compatible avec un rendu serveur.
1466
- afterNextRender(() => {
1467
- if (!this.rafraichir())
1468
- return;
1469
- let minuterie;
1470
- const planifier = () => {
1471
- const ecart = Math.abs(Date.now() - this.dateEnMillisecondes());
1472
- // Sous une minute, la valeur bouge vite ; au-dela, une fois par minute
1473
- // suffit et l'onglet se rendort.
1474
- const cadence = ecart < MINUTE ? 10_000 : MINUTE;
1475
- minuterie = setTimeout(() => {
1476
- this.maintenant.set(Date.now());
1477
- planifier();
1478
- }, cadence);
1479
- };
1480
- planifier();
1481
- this.destruction.onDestroy(() => {
1482
- clearTimeout(minuterie);
1483
- });
1484
- });
1485
- }
1486
- dateEnMillisecondes() {
1487
- const valeur = this.valeur();
1488
- return (valeur instanceof Date ? valeur : new Date(valeur)).getTime();
1489
- }
1490
- estValide = computed(() => !Number.isNaN(this.dateEnMillisecondes()), /* @ts-ignore */
1491
- ...(ngDevMode ? [{ debugName: "estValide" }] : /* istanbul ignore next */ []));
1492
- iso = computed(() => this.estValide() ? new Date(this.dateEnMillisecondes()).toISOString() : null, /* @ts-ignore */
1493
- ...(ngDevMode ? [{ debugName: "iso" }] : /* istanbul ignore next */ []));
1494
- depasseLeSeuil = computed(() => {
1495
- const ecart = Math.abs(this.maintenant() - this.dateEnMillisecondes());
1496
- return ecart > this.seuilAbsoluEnJours() * JOUR;
1497
- }, /* @ts-ignore */
1498
- ...(ngDevMode ? [{ debugName: "depasseLeSeuil" }] : /* istanbul ignore next */ []));
1499
- texte = computed(() => {
1500
- if (!this.estValide())
1501
- return '';
1502
- const cible = this.dateEnMillisecondes();
1503
- if (this.depasseLeSeuil()) {
1504
- return new Intl.DateTimeFormat(this.locale(), {
1505
- day: 'numeric',
1506
- month: 'long',
1507
- year: 'numeric',
1508
- timeZone: this.fuseau,
1509
- }).format(new Date(cible));
1510
- }
1511
- const ecart = cible - this.maintenant();
1512
- const absolu = Math.abs(ecart);
1513
- // Sous dix secondes, « il y a 0 seconde » est plus deroutant qu'utile.
1514
- if (absolu < 10_000)
1515
- return this.libelles().aLInstant;
1516
- const format = new Intl.RelativeTimeFormat(this.locale(), { numeric: 'auto' });
1517
- if (absolu < MINUTE)
1518
- return format.format(Math.round(ecart / 1000), 'second');
1519
- if (absolu < HEURE)
1520
- return format.format(Math.round(ecart / MINUTE), 'minute');
1521
- if (absolu < JOUR)
1522
- return format.format(Math.round(ecart / HEURE), 'hour');
1523
- return format.format(Math.round(ecart / JOUR), 'day');
1524
- }, /* @ts-ignore */
1525
- ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
1526
- texteAccessible = computed(() => {
1527
- if (!this.estValide())
1528
- return '';
1529
- return new Intl.DateTimeFormat(this.locale(), {
1530
- day: 'numeric',
1531
- month: 'long',
1532
- year: 'numeric',
1533
- hour: '2-digit',
1534
- minute: '2-digit',
1535
- timeZone: this.fuseau,
1536
- }).format(new Date(this.dateEnMillisecondes()));
1537
- }, /* @ts-ignore */
1538
- ...(ngDevMode ? [{ debugName: "texteAccessible" }] : /* istanbul ignore next */ []));
1539
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StRelativeTime, deps: [], target: i0.ɵɵFactoryTarget.Component });
1540
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StRelativeTime, isStandalone: true, selector: "st-relative-time", inputs: { valeur: { classPropertyName: "valeur", publicName: "valeur", isSignal: true, isRequired: true, transformFunction: null }, rafraichir: { classPropertyName: "rafraichir", publicName: "rafraichir", isSignal: true, isRequired: false, transformFunction: null }, seuilAbsoluEnJours: { classPropertyName: "seuilAbsoluEnJours", publicName: "seuilAbsoluEnJours", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-relative-time" }, ngImport: i0, template: "@if (estValide()) {\n <time [attr.datetime]=\"iso()\" aria-hidden=\"true\">{{ texte() }}</time>\n @if (texteAccessible(); as lu) {\n <!-- Rien quand le format affiche se lit deja : un noeud vide ferait\n croire a un texte de remplacement absent. -->\n <span class=\"st-vh\">{{ lu }}</span>\n }\n}\n", styles: [":host([hidden]){display:none!important}:host{font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1541
- }
1542
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StRelativeTime, decorators: [{
1543
- type: Component,
1544
- args: [{ selector: 'st-relative-time', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'st-relative-time' }, template: "@if (estValide()) {\n <time [attr.datetime]=\"iso()\" aria-hidden=\"true\">{{ texte() }}</time>\n @if (texteAccessible(); as lu) {\n <!-- Rien quand le format affiche se lit deja : un noeud vide ferait\n croire a un texte de remplacement absent. -->\n <span class=\"st-vh\">{{ lu }}</span>\n }\n}\n", styles: [":host([hidden]){display:none!important}:host{font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1545
- }], ctorParameters: () => [], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], rafraichir: [{ type: i0.Input, args: [{ isSignal: true, alias: "rafraichir", required: false }] }], seuilAbsoluEnJours: [{ type: i0.Input, args: [{ isSignal: true, alias: "seuilAbsoluEnJours", required: false }] }] } });
1546
-
1547
- /**
1548
- * Nombre dans le flux du texte.
1549
- *
1550
- * Distinct de `st-badge`, qui est une pastille superposee a un autre element.
1551
- *
1552
- * ⚠️ Quand la valeur est abregee (« 1,2 k »), le nom accessible porte le nombre
1553
- * COMPLET : l'abreviation est une commodite visuelle, pas une perte
1554
- * d'information pour qui ecoute la page.
1555
- */
1556
- class StCount {
1557
- locale = inject(ST_LOCALE);
1558
- valeur = input.required(/* @ts-ignore */
1559
- ...(ngDevMode ? [{ debugName: "valeur" }] : /* istanbul ignore next */ []));
1560
- ton = input('neutre', /* @ts-ignore */
1561
- ...(ngDevMode ? [{ debugName: "ton" }] : /* istanbul ignore next */ []));
1562
- /** `null` : jamais d'abreviation. */
1563
- abregerAuDela = input(1000, /* @ts-ignore */
1564
- ...(ngDevMode ? [{ debugName: "abregerAuDela" }] : /* istanbul ignore next */ []));
1565
- estAbrege = computed(() => {
1566
- const seuil = this.abregerAuDela();
1567
- return seuil !== null && Math.abs(this.valeur()) >= seuil;
1568
- }, /* @ts-ignore */
1569
- ...(ngDevMode ? [{ debugName: "estAbrege" }] : /* istanbul ignore next */ []));
1570
- texteComplet = computed(() => new Intl.NumberFormat(this.locale()).format(this.valeur()), /* @ts-ignore */
1571
- ...(ngDevMode ? [{ debugName: "texteComplet" }] : /* istanbul ignore next */ []));
1572
- texte = computed(() => {
1573
- if (!this.estAbrege())
1574
- return this.texteComplet();
1575
- return new Intl.NumberFormat(this.locale(), {
1576
- notation: 'compact',
1577
- maximumFractionDigits: 1,
1578
- }).format(this.valeur());
1579
- }, /* @ts-ignore */
1580
- ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
1581
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCount, deps: [], target: i0.ɵɵFactoryTarget.Component });
1582
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StCount, isStandalone: true, selector: "st-count", inputs: { valeur: { classPropertyName: "valeur", publicName: "valeur", isSignal: true, isRequired: true, transformFunction: null }, ton: { classPropertyName: "ton", publicName: "ton", isSignal: true, isRequired: false, transformFunction: null }, abregerAuDela: { classPropertyName: "abregerAuDela", publicName: "abregerAuDela", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-ton": "ton()" }, classAttribute: "st-count" }, ngImport: i0, template: "<span aria-hidden=\"true\">{{ texte() }}</span>\n\n@if (estAbrege()) {\n <!-- Abrege a l'ecran, entier a l'oral : \u00AB 1,2 k \u00BB ne se lit pas. -->\n <span class=\"st-vh\">{{ texteComplet() }}</span>\n}\n", styles: [":host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-moyenne);color:var(--st-couleur-texte);white-space:nowrap}:host([data-ton=discret]){color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1583
- }
1584
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCount, decorators: [{
1585
- type: Component,
1586
- args: [{ selector: 'st-count', changeDetection: ChangeDetectionStrategy.OnPush, host: {
1587
- class: 'st-count',
1588
- '[attr.data-ton]': 'ton()',
1589
- }, template: "<span aria-hidden=\"true\">{{ texte() }}</span>\n\n@if (estAbrege()) {\n <!-- Abrege a l'ecran, entier a l'oral : \u00AB 1,2 k \u00BB ne se lit pas. -->\n <span class=\"st-vh\">{{ texteComplet() }}</span>\n}\n", styles: [":host([hidden]){display:none!important}:host{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-moyenne);color:var(--st-couleur-texte);white-space:nowrap}:host([data-ton=discret]){color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1590
- }], propDecorators: { valeur: [{ type: i0.Input, args: [{ isSignal: true, alias: "valeur", required: true }] }], ton: [{ type: i0.Input, args: [{ isSignal: true, alias: "ton", required: false }] }], abregerAuDela: [{ type: i0.Input, args: [{ isSignal: true, alias: "abregerAuDela", required: false }] }] } });
1591
-
1592
- /**
1593
- * ⚠️ Une forme par sens, et non une couleur : un chevron vers le haut, un vers
1594
- * le bas, un point pour ce qui ne bouge pas. Sous contraste force ou a
1595
- * l'impression, il ne reste que la forme et le texte.
1596
- */
1597
- const ICONE_DE_TENDANCE = {
1598
- hausse: 'chevron-haut',
1599
- baisse: 'chevron-bas',
1600
- stable: 'cercle',
1601
- };
1602
- /**
1603
- * Indicateur chiffre : un libelle, une valeur, et sa tendance.
1604
- *
1605
- * ```html
1606
- * <st-stat libelle="Solde disponible" [tendance]="{ sens: 'hausse', texte: '+2,4 % sur un mois', ton: 'succes' }">
1607
- * <st-money [valeur]="solde" />
1608
- * </st-stat>
1609
- * ```
1610
- *
1611
- * La valeur est PROJETEE : c'est `st-money`, `st-count` ou `st-date` qui la
1612
- * formate dans la langue courante, et le socle n'a pas a redire comment.
1613
- *
1614
- * ⚠️ **La tendance se lit par un texte ET une icone, jamais par la couleur
1615
- * seule.** Le texte de l'appelant porte deja le signe ; l'icone porte le sens ;
1616
- * et un mot lisible par lecteur seulement — « en hausse », « en baisse »,
1617
- * « stable » — precede le texte, parce qu'un lecteur d'ecran ne lit pas un
1618
- * chevron.
1619
- *
1620
- * Le libelle vient AVANT la valeur dans le document, comme dans une phrase :
1621
- * « Solde disponible, 1 234,50 € ». A l'ecran, il se pose au-dessus, en petit.
1622
- */
1623
- class StStat {
1624
- libelles = inject(ST_LIBELLES);
1625
- /** Texte visible, deja traduit. */
1626
- libelle = input.required(/* @ts-ignore */
1627
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
1628
- tendance = input(null, /* @ts-ignore */
1629
- ...(ngDevMode ? [{ debugName: "tendance" }] : /* istanbul ignore next */ []));
1630
- /** Precision sous la valeur : la periode, la source. Jamais une information critique. */
1631
- precision = input(null, /* @ts-ignore */
1632
- ...(ngDevMode ? [{ debugName: "precision" }] : /* istanbul ignore next */ []));
1633
- icone = computed(() => {
1634
- const tendance = this.tendance();
1635
- return tendance === null ? null : ICONE_DE_TENDANCE[tendance.sens];
1636
- }, /* @ts-ignore */
1637
- ...(ngDevMode ? [{ debugName: "icone" }] : /* istanbul ignore next */ []));
1638
- tonDeTendance = computed(() => this.tendance()?.ton ?? 'neutre', /* @ts-ignore */
1639
- ...(ngDevMode ? [{ debugName: "tonDeTendance" }] : /* istanbul ignore next */ []));
1640
- sensLu = computed(() => {
1641
- const tendance = this.tendance();
1642
- if (tendance === null)
1643
- return '';
1644
- const libelles = this.libelles();
1645
- if (tendance.sens === 'hausse')
1646
- return libelles.tendanceEnHausse;
1647
- if (tendance.sens === 'baisse')
1648
- return libelles.tendanceEnBaisse;
1649
- return libelles.tendanceStable;
1650
- }, /* @ts-ignore */
1651
- ...(ngDevMode ? [{ debugName: "sensLu" }] : /* istanbul ignore next */ []));
1652
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StStat, deps: [], target: i0.ɵɵFactoryTarget.Component });
1653
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StStat, isStandalone: true, selector: "st-stat", inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null }, tendance: { classPropertyName: "tendance", publicName: "tendance", isSignal: true, isRequired: false, transformFunction: null }, precision: { classPropertyName: "precision", publicName: "precision", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-stat" }, ngImport: i0, template: "<span class=\"st-stat__libelle\">{{ libelle() }}</span>\n<span class=\"st-stat__valeur\"><ng-content /></span>\n@if (tendance(); as tendance) {\n <span class=\"st-stat__tendance\" [attr.data-ton]=\"tonDeTendance()\">\n @if (icone(); as nom) {\n <st-icon [nom]=\"nom\" taille=\"sm\" />\n }\n <span class=\"st-vh\">{{ sensLu() }}, </span>{{ tendance.texte }}\n </span>\n}\n@if (precision(); as texte) {\n <span class=\"st-stat__precision\">{{ texte }}</span>\n}\n", styles: [":host([hidden]){display:none!important}:host{display:flex;flex-direction:column;gap:var(--st-espacement-1);min-inline-size:0}.st-stat__libelle{font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-stat__valeur{font-size:var(--st-taille-2xl);font-weight:var(--st-graisse-semi);line-height:var(--st-interligne-serre);color:var(--st-couleur-texte);overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.st-stat__tendance{display:inline-flex;gap:var(--st-espacement-1);align-items:center;font-size:var(--st-taille-sm);color:var(--st-couleur-texte)}.st-stat__tendance[data-ton=discret]{color:var(--st-couleur-texte-discret)}.st-stat__tendance[data-ton=primaire]{color:var(--st-couleur-primaire)}.st-stat__tendance[data-ton=succes]{color:var(--st-couleur-succes)}.st-stat__tendance[data-ton=avertissement]{color:var(--st-couleur-avertissement)}.st-stat__tendance[data-ton=danger]{color:var(--st-couleur-danger)}.st-stat__precision{font-size:var(--st-taille-xs);color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1654
- }
1655
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StStat, decorators: [{
1656
- type: Component,
1657
- args: [{ selector: 'st-stat', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIcon], host: { class: 'st-stat' }, template: "<span class=\"st-stat__libelle\">{{ libelle() }}</span>\n<span class=\"st-stat__valeur\"><ng-content /></span>\n@if (tendance(); as tendance) {\n <span class=\"st-stat__tendance\" [attr.data-ton]=\"tonDeTendance()\">\n @if (icone(); as nom) {\n <st-icon [nom]=\"nom\" taille=\"sm\" />\n }\n <span class=\"st-vh\">{{ sensLu() }}, </span>{{ tendance.texte }}\n </span>\n}\n@if (precision(); as texte) {\n <span class=\"st-stat__precision\">{{ texte }}</span>\n}\n", styles: [":host([hidden]){display:none!important}:host{display:flex;flex-direction:column;gap:var(--st-espacement-1);min-inline-size:0}.st-stat__libelle{font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-stat__valeur{font-size:var(--st-taille-2xl);font-weight:var(--st-graisse-semi);line-height:var(--st-interligne-serre);color:var(--st-couleur-texte);overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.st-stat__tendance{display:inline-flex;gap:var(--st-espacement-1);align-items:center;font-size:var(--st-taille-sm);color:var(--st-couleur-texte)}.st-stat__tendance[data-ton=discret]{color:var(--st-couleur-texte-discret)}.st-stat__tendance[data-ton=primaire]{color:var(--st-couleur-primaire)}.st-stat__tendance[data-ton=succes]{color:var(--st-couleur-succes)}.st-stat__tendance[data-ton=avertissement]{color:var(--st-couleur-avertissement)}.st-stat__tendance[data-ton=danger]{color:var(--st-couleur-danger)}.st-stat__precision{font-size:var(--st-taille-xs);color:var(--st-couleur-texte-discret)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n"] }]
1658
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }], tendance: [{ type: i0.Input, args: [{ isSignal: true, alias: "tendance", required: false }] }], precision: [{ type: i0.Input, args: [{ isSignal: true, alias: "precision", required: false }] }] } });
1659
-
1660
1034
  /**
1661
1035
  * Pastille de statut.
1662
1036
  *
@@ -2418,10 +1792,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
2418
1792
  * ```
2419
1793
  *
2420
1794
  * **Ou il vit, et pourquoi.** Dans le tronc, a cote de `st-breadcrumb` et non
2421
- * dans `ui/interactions` avec `st-sidenav` : les points d'entree se decoupent
2422
- * par pair lourd, et ce lien n'en traine aucun — `@angular/router` est deja
2423
- * exige par le tronc. Une SPA qui rend sa navigation sans tiroir n'a pas a
2424
- * charger Aria pour un lien.
1795
+ * dans `ui/navigation` avec `st-sidenav` : les points d'entree se decoupent par
1796
+ * pair lourd, et ce lien n'en traine aucun — `@angular/router` est deja exige
1797
+ * par le tronc. Une SPA qui rend sa navigation sans tiroir n'a pas a charger
1798
+ * l'observateur de rupture pour un lien. Et la coquille le PEINT : le second
1799
+ * critere de l'ADR 0031 le garde au tronc pour la meme raison.
2425
1800
  *
2426
1801
  * ⚠️ **L'etat actif est porte par `aria-current="page"`**, pose par le routeur,
2427
1802
  * et c'est sur lui que la feuille se branche — jamais sur la seule couleur : le
@@ -2674,7 +2049,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
2674
2049
  args: [{ selector: 'st-stepper', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIcon], host: { class: 'st-stepper' }, template: "<ol class=\"st-stepper__liste\" [attr.aria-label]=\"etiquetteLue()\">\n @for (ligne of lignes(); track $index) {\n <li\n class=\"st-stepper__etape\"\n [attr.data-etat]=\"ligne.etat\"\n [attr.aria-current]=\"ligne.etat === 'courante' ? 'step' : null\"\n >\n <span class=\"st-stepper__repere\" aria-hidden=\"true\">\n @if (ligne.etat === 'faite') {\n <st-icon nom=\"coche\" taille=\"sm\" />\n } @else {\n {{ ligne.numero }}\n }\n </span>\n <span class=\"st-stepper__libelle\">{{ ligne.libelle }}</span>\n <span class=\"st-vh\">, {{ ligne.texteDEtat }}</span>\n </li>\n }\n</ol>\n", styles: [":host([hidden]){display:none!important}:host{display:block}.st-stepper__liste{display:flex;flex-direction:column;gap:var(--st-espacement-3);padding:0;margin:0;list-style:none}.st-stepper__etape{display:flex;gap:var(--st-espacement-3);align-items:center;min-block-size:var(--st-cible-min);color:var(--st-couleur-texte-discret)}.st-stepper__repere{display:inline-flex;flex:none;align-items:center;justify-content:center;inline-size:2rem;block-size:2rem;font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi);font-variant-numeric:tabular-nums;border:var(--st-epaisseur-moyenne) solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-pleine)}.st-stepper__etape[data-etat=faite]{color:var(--st-couleur-texte)}.st-stepper__etape[data-etat=faite] .st-stepper__repere{color:var(--st-couleur-sur-succes);background:var(--st-couleur-succes);border-color:var(--st-couleur-succes)}.st-stepper__etape[data-etat=courante]{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte)}.st-stepper__etape[data-etat=courante] .st-stepper__repere{color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(min-width:48rem){.st-stepper__liste{flex-direction:row;flex-wrap:wrap;gap:var(--st-espacement-6)}}@media(forced-colors:active){.st-stepper__etape[data-etat=courante] .st-stepper__repere,.st-stepper__etape[data-etat=faite] .st-stepper__repere{forced-color-adjust:none;color:HighlightText;background:Highlight;border-color:Highlight}}\n"] }]
2675
2050
  }], propDecorators: { etapes: [{ type: i0.Input, args: [{ isSignal: true, alias: "etapes", required: true }] }], etape: [{ type: i0.Input, args: [{ isSignal: true, alias: "etape", required: true }] }], etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }] } });
2676
2051
 
2677
- let compteur$9 = 0;
2052
+ let compteur$2 = 0;
2678
2053
  /**
2679
2054
  * Ligne de reglage : un libelle, une description, et le controle qui les suit.
2680
2055
  *
@@ -2709,7 +2084,7 @@ class StSettingsRow {
2709
2084
  ...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
2710
2085
  /** Identifiants du libelle et de la description, cibles des attributs ARIA. */
2711
2086
  identifiants = (() => {
2712
- const base = `st-reglage-${(compteur$9 += 1).toString()}`;
2087
+ const base = `st-reglage-${(compteur$2 += 1).toString()}`;
2713
2088
  return { libelle: `${base}-libelle`, description: `${base}-description` };
2714
2089
  })();
2715
2090
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSettingsRow, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2882,2315 +2257,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
2882
2257
  }, template: "<st-icon [nom]=\"icone()\" taille=\"xs\" />\n<span class=\"st-inline-message__texte\"><ng-content /></span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:flex;gap:var(--st-espacement-2);align-items:flex-start;font-size:var(--st-taille-sm);line-height:var(--st-interligne-normal)}:host([data-ton=neutre]){color:var(--st-couleur-texte-discret)}:host([data-ton=primaire]){color:var(--st-couleur-primaire)}:host([data-ton=succes]){color:var(--st-couleur-succes)}:host([data-ton=avertissement]){color:var(--st-couleur-avertissement)}:host([data-ton=danger]){color:var(--st-couleur-danger)}\n"] }]
2883
2258
  }], propDecorators: { ton: [{ type: i0.Input, args: [{ isSignal: true, alias: "ton", required: false }] }], identifiant: [{ type: i0.Input, args: [{ isSignal: true, alias: "identifiant", required: false }] }] } });
2884
2259
 
2260
+ let compteur$1 = 0;
2885
2261
  /**
2886
- * Le texte d'une erreur, dans la langue courante.
2887
- *
2888
- * ⚠️ **Un `message` fourni l'emporte toujours.** Un validateur metier, ou une
2889
- * erreur du serveur deja traduite, en sait plus que le genre : « IBAN d'un pays
2890
- * non pris en charge » vaut mieux que « Le format n'est pas valide ».
2262
+ * Choix du theme : systeme, clair, sombre.
2891
2263
  *
2892
- * ⚠️ **Un genre inconnu rend un message generique, jamais le genre brut.**
2893
- * Afficher `ibanChecksum` a une personne qui saisit un IBAN, c'est lui montrer le
2894
- * code source.
2264
+ * ⚠️ **Trois etats, donc PAS un interrupteur.** `role="switch"` et
2265
+ * `aria-pressed` sont binaires : poses sur un choix a trois valeurs, ils
2266
+ * annoncent « active » ou « desactive » sans jamais dire lequel des trois est
2267
+ * retenu. Un groupe de boutons radio dit exactement « Sombre, bouton radio,
2268
+ * coche, 3 sur 3 ».
2895
2269
  *
2896
- * La locale sert aux messages qui citent un format, une date ou une taille : « au
2897
- * format jj/mm/aaaa » n'est pas « mm/dd/yyyy ».
2898
- */
2899
- function messageDErreur(erreur, libelles, locale = 'fr-FR') {
2900
- const fourni = erreur.message?.trim();
2901
- if (fourni !== undefined && fourni !== '')
2902
- return fourni;
2903
- const propriete = (nom) => erreur[nom];
2904
- const nombre = (nom) => {
2905
- const valeur = propriete(nom);
2906
- return typeof valeur === 'number' ? valeur : undefined;
2907
- };
2908
- const texte = (nom) => {
2909
- const valeur = propriete(nom);
2910
- return typeof valeur === 'string' ? valeur : undefined;
2911
- };
2912
- switch (erreur.kind) {
2913
- case 'required':
2914
- return libelles.champObligatoire;
2915
- case 'email':
2916
- return libelles.adresseInvalide;
2917
- case 'pattern':
2918
- return libelles.formatInvalide;
2919
- case 'iban':
2920
- return libelles.ibanInvalide;
2921
- case 'telephone':
2922
- return libelles.telephoneInvalide;
2923
- case 'minLength': {
2924
- const minimum = nombre('minLength');
2925
- return minimum === undefined ? libelles.valeurInvalide : libelles.longueurMinimale(minimum);
2926
- }
2927
- case 'maxLength': {
2928
- const maximum = nombre('maxLength');
2929
- return maximum === undefined ? libelles.valeurInvalide : libelles.longueurMaximale(maximum);
2930
- }
2931
- case 'min': {
2932
- const minimum = nombre('min');
2933
- return minimum === undefined
2934
- ? libelles.valeurInvalide
2935
- : libelles.valeurMinimale(String(minimum));
2936
- }
2937
- case 'max': {
2938
- const maximum = nombre('max');
2939
- return maximum === undefined
2940
- ? libelles.valeurInvalide
2941
- : libelles.valeurMaximale(String(maximum));
2942
- }
2943
- case 'montant': {
2944
- const devise = texte('devise') ?? 'EUR';
2945
- const exemple = formaterMontant(montant((123456n * 10n ** BigInt(decimalesDe(devise))) / 100n, devise), {
2946
- locale,
2947
- sansDevise: true,
2948
- });
2949
- return libelles.montantIllisible(exemple);
2950
- }
2951
- case 'decimales': {
2952
- const decimales = nombre('decimales');
2953
- return decimales === undefined
2954
- ? libelles.valeurInvalide
2955
- : libelles.tropDeDecimales(decimales);
2956
- }
2957
- case 'date':
2958
- return libelles.dateIllisible(motifLocal(libelles, locale), exempleDeDate(locale));
2959
- case 'dateMin': {
2960
- const minimum = texte('min');
2961
- return minimum === undefined
2962
- ? libelles.valeurInvalide
2963
- : libelles.dateTropTot(formaterDateLongue(minimum, locale));
2964
- }
2965
- case 'dateMax': {
2966
- const maximum = texte('max');
2967
- return maximum === undefined
2968
- ? libelles.valeurInvalide
2969
- : libelles.dateTropTard(formaterDateLongue(maximum, locale));
2970
- }
2971
- case 'plageDeDates':
2972
- return libelles.finAvantDebut;
2973
- case 'fichierTaille': {
2974
- const fichier = texte('fichier');
2975
- const maximum = nombre('max');
2976
- return fichier === undefined || maximum === undefined
2977
- ? libelles.valeurInvalide
2978
- : libelles.fichierTropVolumineux(fichier, formaterTaille(maximum, locale));
2979
- }
2980
- case 'fichierType': {
2981
- const fichier = texte('fichier');
2982
- return fichier === undefined
2983
- ? libelles.valeurInvalide
2984
- : libelles.typeDeFichierRefuse(fichier);
2985
- }
2986
- case 'fichiersNombre': {
2987
- const maximum = nombre('max');
2988
- return maximum === undefined ? libelles.valeurInvalide : libelles.tropDeFichiers(maximum);
2989
- }
2990
- default:
2991
- return libelles.valeurInvalide;
2992
- }
2993
- }
2994
- /** Le motif a taper dans la langue courante : `jj/mm/aaaa`. */
2995
- function motifLocal(libelles, locale) {
2996
- return motifDeDate(locale, {
2997
- jour: libelles.lettreDuJour,
2998
- mois: libelles.lettreDuMois,
2999
- annee: libelles.lettreDeLAnnee,
3000
- });
3001
- }
3002
- /**
3003
- * Un jour superieur a 12 : « 31/03/2026 » montre sans ambiguite ou va le jour,
3004
- * ce que « 01/02/2026 » ne dit pas.
3005
- */
3006
- function exempleDeDate(locale) {
3007
- return formaterDateSaisie('2026-03-31', locale);
3008
- }
3009
- /**
3010
- * Erreur d'IBAN, a brancher sur un schema :
2270
+ * ⚠️ **Composant de PRESENTATION.** Il n'injecte pas `StTheme` : `ui` ne depend
2271
+ * pas de `@starterlib/core` (ADR 0003). La coquille le relie au noyau :
3011
2272
  *
3012
- * ```ts
3013
- * validate(chemin.iban, ({ value }) => erreurIban(value()));
2273
+ * ```html
2274
+ * <st-theme-toggle [choix]="theme.choix()" (choisi)="theme.choisir($event)" />
3014
2275
  * ```
3015
- *
3016
- * Une fonction pure plutot qu'une regle toute faite : fournir la regle
3017
- * importerait `validate` d'`@angular/forms` A L'EXECUTION, et le socle ne
3018
- * l'importe que pour ses types (audit de la vague 3, V3-1).
3019
- *
3020
- * ⚠️ Confort de saisie, jamais controle (SEC-5) : le serveur revalide. Un champ
3021
- * vide n'est pas « invalide » ici — c'est `required` qui le dit, avec le bon
3022
- * message.
3023
2276
  */
3024
- function erreurIban(valeur) {
3025
- if (valeur.trim() === '')
3026
- return null;
3027
- return ibanEstValide(valeur) ? null : { kind: 'iban' };
2277
+ class StThemeToggle {
2278
+ libelles = inject(ST_LIBELLES);
2279
+ choix = input.required(/* @ts-ignore */
2280
+ ...(ngDevMode ? [{ debugName: "choix" }] : /* istanbul ignore next */ []));
2281
+ /** Legende visible. Masquee par defaut, mais toujours lue : elle nomme le groupe. */
2282
+ legendeVisible = input(false, { ...(ngDevMode ? { debugName: "legendeVisible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2283
+ choisi = output();
2284
+ /** Unique par instance : deux bascules sur la meme page partageraient leur groupe radio. */
2285
+ nom = `st-theme-${(compteur$1 += 1).toString()}`;
2286
+ legende = computed(() => this.libelles().theme, /* @ts-ignore */
2287
+ ...(ngDevMode ? [{ debugName: "legende" }] : /* istanbul ignore next */ []));
2288
+ options = computed(() => {
2289
+ const libelles = this.libelles();
2290
+ const texte = {
2291
+ systeme: libelles.themeSysteme,
2292
+ clair: libelles.themeClair,
2293
+ sombre: libelles.themeSombre,
2294
+ };
2295
+ return CHOIX_DE_THEME.map((valeur) => ({ valeur, libelle: texte[valeur] }));
2296
+ }, /* @ts-ignore */
2297
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2298
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StThemeToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
2299
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StThemeToggle, isStandalone: true, selector: "st-theme-toggle", inputs: { choix: { classPropertyName: "choix", publicName: "choix", isSignal: true, isRequired: true, transformFunction: null }, legendeVisible: { classPropertyName: "legendeVisible", publicName: "legendeVisible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { choisi: "choisi" }, host: { classAttribute: "st-theme-toggle" }, ngImport: i0, template: "<fieldset class=\"st-theme-toggle__groupe\">\n <legend [class.st-vh]=\"!legendeVisible()\">{{ legende() }}</legend>\n @for (option of options(); track option.valeur) {\n <label>\n <input\n type=\"radio\"\n [name]=\"nom\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === choix()\"\n (change)=\"choisi.emit(option.valeur)\"\n />\n <span>{{ option.libelle }}</span>\n </label>\n }\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block}.st-theme-toggle__groupe{display:inline-flex;flex-wrap:wrap;gap:0;padding:0;margin:0;border:0}.st-theme-toggle__groupe legend{padding:0;margin-block-end:var(--st-espacement-2);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi)}.st-theme-toggle__groupe input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-theme-toggle__groupe label{display:inline-flex;align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-1) var(--st-espacement-3);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte)}.st-theme-toggle__groupe label+label{margin-inline-start:-1px}.st-theme-toggle__groupe label:first-of-type{border-start-start-radius:var(--st-rayon-md);border-end-start-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:last-of-type{border-start-end-radius:var(--st-rayon-md);border-end-end-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:has(input:checked){position:relative;font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-theme-toggle__groupe label:has(input:focus-visible){position:relative;z-index:1;outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}@media(forced-colors:active){.st-theme-toggle__groupe label:has(input:checked){forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3028
2300
  }
2301
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StThemeToggle, decorators: [{
2302
+ type: Component,
2303
+ args: [{ selector: 'st-theme-toggle', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'st-theme-toggle' }, template: "<fieldset class=\"st-theme-toggle__groupe\">\n <legend [class.st-vh]=\"!legendeVisible()\">{{ legende() }}</legend>\n @for (option of options(); track option.valeur) {\n <label>\n <input\n type=\"radio\"\n [name]=\"nom\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === choix()\"\n (change)=\"choisi.emit(option.valeur)\"\n />\n <span>{{ option.libelle }}</span>\n </label>\n }\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block}.st-theme-toggle__groupe{display:inline-flex;flex-wrap:wrap;gap:0;padding:0;margin:0;border:0}.st-theme-toggle__groupe legend{padding:0;margin-block-end:var(--st-espacement-2);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi)}.st-theme-toggle__groupe input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-theme-toggle__groupe label{display:inline-flex;align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-1) var(--st-espacement-3);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte)}.st-theme-toggle__groupe label+label{margin-inline-start:-1px}.st-theme-toggle__groupe label:first-of-type{border-start-start-radius:var(--st-rayon-md);border-end-start-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:last-of-type{border-start-end-radius:var(--st-rayon-md);border-end-end-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:has(input:checked){position:relative;font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-theme-toggle__groupe label:has(input:focus-visible){position:relative;z-index:1;outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}@media(forced-colors:active){.st-theme-toggle__groupe label:has(input:checked){forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"] }]
2304
+ }], propDecorators: { choix: [{ type: i0.Input, args: [{ isSignal: true, alias: "choix", required: true }] }], legendeVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendeVisible", required: false }] }], choisi: [{ type: i0.Output, args: ["choisi"] }] } });
2305
+
3029
2306
  /**
3030
- * Erreur de FORME d'un numero de telephone : chiffres, `+` de tete facultatif,
3031
- * de 4 a 15 chiffres — la longueur maximale d'un numero international (E.164).
3032
- *
3033
- * ```ts
3034
- * validate(chemin.telephone, ({ value }) => erreurTelephone(value()));
3035
- * ```
2307
+ * Nom de chaque langue ECRIT DANS CETTE LANGUE, et jamais traduit.
3036
2308
  *
3037
- * ⚠️ Une forme valide ne dit pas que le numero existe : seul le serveur le sait.
3038
- * Un champ vide n'est pas une erreur de forme : l'obligation est une autre regle.
2309
+ * ⚠️ Une personne qui ne lit pas le francais ne cherche pas « Anglais » : elle
2310
+ * cherche « English ». Traduire le nom des langues dans la langue courante rend
2311
+ * le selecteur illisible precisement pour qui en a besoin.
3039
2312
  */
3040
- function erreurTelephone(valeur) {
3041
- const numero = normaliserTelephone(valeur);
3042
- if (numero === '')
3043
- return null;
3044
- return /^\+?[0-9]{4,15}$/.test(numero) ? null : { kind: 'telephone' };
3045
- }
2313
+ const ENDONYMES = {
2314
+ fr: 'Français',
2315
+ en: 'English',
2316
+ };
2317
+ let compteur = 0;
3046
2318
  /**
3047
- * Erreur de bornes d'une date, a brancher sur un schema :
2319
+ * Choix de la langue de l'interface.
3048
2320
  *
3049
- * ```ts
3050
- * validate(chemin.naissance, ({ value }) => erreurDate(value(), { max: aujourdHui }));
3051
- * ```
2321
+ * ⚠️ **Chaque option porte son propre `lang`.** Sans lui, un lecteur d'ecran
2322
+ * regle en francais lit « English » avec une voix francaise, et le mot devient
2323
+ * incomprehensible pour la personne anglophone qui cherchait justement a
2324
+ * l'entendre.
3052
2325
  *
3053
- * Les bornes du schema et celles passees au calendrier doivent etre les memes :
3054
- * le calendrier grise, le schema refuse une date tapee a la main.
3055
- */
3056
- function erreurDate(valeur, bornes) {
3057
- if (valeur === null || valeur === '')
3058
- return null;
3059
- const { min, max } = bornes;
3060
- if (min !== undefined && min !== null && valeur < min) {
3061
- return { kind: 'dateMin', min };
3062
- }
3063
- if (max !== undefined && max !== null && valeur > max) {
3064
- return { kind: 'dateMax', max };
3065
- }
3066
- return null;
3067
- }
3068
- /** Une fin anterieure au debut. Une borne manquante n'est pas une erreur de plage. */
3069
- function erreurPlageDeDates(plage) {
3070
- const { debut, fin } = plage;
3071
- if (debut === null || fin === null)
3072
- return null;
3073
- return fin < debut ? { kind: 'plageDeDates' } : null;
3074
- }
3075
- /**
3076
- * Erreurs d'une liste de fichiers, une par fichier refuse, pour dire LEQUEL :
2326
+ * ⚠️ **Composant de PRESENTATION.** Il n'injecte pas `StLangue` : `ui` ne depend
2327
+ * pas de `@starterlib/core` (ADR 0003). La coquille le relie au noyau :
3077
2328
  *
3078
- * ```ts
3079
- * validate(chemin.pieces, ({ value }) => erreursDeFichiers(value(), regles));
3080
- * ```
3081
- *
3082
- * ⚠️ Confort, jamais securite (SEC-5) : le type se deduit de l'extension, et le
3083
- * serveur relit le contenu.
3084
- */
3085
- function erreursDeFichiers(fichiers, regles) {
3086
- const erreurs = [];
3087
- const { accept = '', tailleMax, nombreMax } = regles;
3088
- if (nombreMax !== undefined && fichiers.length > nombreMax) {
3089
- erreurs.push({ kind: 'fichiersNombre', max: nombreMax });
3090
- }
3091
- for (const fichier of fichiers) {
3092
- if (!typeDeFichierAccepte(fichier, accept)) {
3093
- erreurs.push({ kind: 'fichierType', fichier: fichier.name });
3094
- }
3095
- else if (tailleMax !== undefined && fichier.size > tailleMax) {
3096
- erreurs.push({
3097
- kind: 'fichierTaille',
3098
- fichier: fichier.name,
3099
- max: tailleMax,
3100
- });
3101
- }
3102
- }
3103
- return erreurs;
3104
- }
3105
- /**
3106
- * Erreurs a afficher par un champ qui LIT sa saisie (`transformedValue`) : celles
3107
- * du formulaire, plus les erreurs de lecture qu'il ne connait pas encore.
3108
- *
3109
- * ⚠️ Relie a un formulaire, un champ recoit ses erreurs de lecture DANS `errors`,
3110
- * par Signal Forms. Employe seul, par `[(value)]`, il ne les recoit nulle part :
3111
- * sans cette fusion, « 12,5,3 » s'afficherait sans le moindre message. Une erreur
3112
- * deja presente — meme genre, meme partie — n'est pas repetee.
3113
- */
3114
- function fusionnerErreurs(formulaire, lecture) {
3115
- const cle = (erreur) => `${erreur.kind}|${erreur.partie ?? ''}`;
3116
- const connues = new Set(formulaire.map(cle));
3117
- return [...formulaire, ...lecture.filter((erreur) => !connues.has(cle(erreur)))];
3118
- }
3119
-
3120
- /**
3121
- * Base des champs du socle : le contrat Signal Forms, ecrit une fois — tout sauf
3122
- * `value`, dont le type depend du champ (texte, montant, date).
3123
- *
3124
- * ⚠️ **Une base, et non une copie par champ.** Neuf membres de contrat recopies
3125
- * dans six composants, c'est six occasions d'en oublier un — et l'oubli ne se
3126
- * voit pas : un champ sans `touched` n'affiche jamais ses erreurs, un champ sans
3127
- * reflet de `hidden` ne se masque pas, et rien n'echoue.
3128
- *
3129
- * Les noms restent anglais : voir le glossaire, § 8.
3130
- *
3131
- * Le gabarit du champ doit nommer son element natif `#champ`, et le brancher sur
3132
- * `touch` a la perte du focus.
3133
- */
3134
- class ContratDeChamp {
3135
- libelle = input.required(/* @ts-ignore */
3136
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
3137
- aide = input(null, /* @ts-ignore */
3138
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3139
- errors = input([], /* @ts-ignore */
3140
- ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3141
- touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3142
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3143
- readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3144
- required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3145
- hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3146
- name = input('', /* @ts-ignore */
3147
- ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3148
- touch = output();
3149
- champ = viewChild('champ', /* @ts-ignore */
3150
- ...(ngDevMode ? [{ debugName: "champ" }] : /* istanbul ignore next */ []));
3151
- /** Appele par Signal Forms pour amener le focus sur un champ en erreur. */
3152
- focus(options) {
3153
- this.champ()?.nativeElement.focus(options);
3154
- }
3155
- texteSaisi(evenement) {
3156
- return evenement.target.value;
3157
- }
3158
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContratDeChamp, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3159
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.1.6", type: ContratDeChamp, isStandalone: true, inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touch: "touch" }, host: { properties: { "attr.hidden": "hidden() ? \"\" : null" } }, viewQueries: [{ propertyName: "champ", first: true, predicate: ["champ"], descendants: true, isSignal: true }], ngImport: i0 });
3160
- }
3161
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ContratDeChamp, decorators: [{
3162
- type: Directive,
3163
- args: [{
3164
- host: {
3165
- // L'entree `hidden` du contrat n'ecrit pas l'attribut : on le reflete, sans
3166
- // quoi la regle `:host([hidden])` du socle ne s'applique pas.
3167
- '[attr.hidden]': 'hidden() ? "" : null',
3168
- },
3169
- }]
3170
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], champ: [{ type: i0.ViewChild, args: ['champ', { isSignal: true }] }] } });
3171
- /** Base des champs dont la valeur est le texte saisi. */
3172
- class ControleDeSaisie extends ContratDeChamp {
3173
- value = model('', /* @ts-ignore */
3174
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
3175
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ControleDeSaisie, deps: null, target: i0.ɵɵFactoryTarget.Directive });
3176
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: ControleDeSaisie, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, usesInheritance: true, ngImport: i0 });
3177
- }
3178
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: ControleDeSaisie, decorators: [{
3179
- type: Directive
3180
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }] } });
3181
-
3182
- let compteur$8 = 0;
3183
- /** Elements qu'un `label for` etiquette. */
3184
- const ELEMENTS_ETIQUETABLES = new Set([
3185
- 'INPUT',
3186
- 'SELECT',
3187
- 'TEXTAREA',
3188
- 'BUTTON',
3189
- 'METER',
3190
- 'OUTPUT',
3191
- 'PROGRESS',
3192
- ]);
3193
- /**
3194
- * Etiquette, aide, erreur et controle d'un champ de saisie — et le cablage qui
3195
- * les relie.
3196
- *
3197
- * ⚠️ **Le SEUL endroit ou ce cablage existe** (audit de la vague 3, V3-2). Chaque
3198
- * champ du socle l'emploie au lieu de refaire identifiants, `aria-describedby` et
3199
- * `aria-invalid` : c'est precisement le code que l'on croit fini et qui se perd,
3200
- * un champ specialise a la fois.
3201
- *
3202
- * L'ordre visuel suit celui de la lecture : etiquette, aide, erreur, controle.
3203
- * L'erreur est AU-DESSUS du controle, pour etre vue avant de retaper.
3204
- *
3205
- * ⚠️ **L'erreur n'est pas une region vivante.** Annoncee a la perte du focus, elle
3206
- * couperait la lecture du champ suivant, ou la personne est deja passee. Elle est
3207
- * reliee au controle, donc lue quand on y revient ; a l'envoi, c'est le
3208
- * recapitulatif d'erreurs qui prend le focus et les enumere.
3209
- */
3210
- class StFormField {
3211
- libelles = inject(ST_LIBELLES);
3212
- locale = inject(ST_LOCALE);
3213
- element = inject(ElementRef);
3214
- /**
3215
- * ⚠️ OBLIGATOIRE et toujours visible (UX-1). Un texte indicatif ne le remplace
3216
- * jamais : il disparait a la premiere frappe.
3217
- */
3218
- libelle = input.required(/* @ts-ignore */
3219
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
3220
- /** Aide permanente : format attendu, exemple. Lue apres l'etiquette. */
3221
- aide = input(null, /* @ts-ignore */
3222
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3223
- /** Texte complementaire tenu a jour par le controle, comme un compteur. */
3224
- complement = input(null, /* @ts-ignore */
3225
- ...(ngDevMode ? [{ debugName: "complement" }] : /* istanbul ignore next */ []));
3226
- erreurs = input([], /* @ts-ignore */
3227
- ...(ngDevMode ? [{ debugName: "erreurs" }] : /* istanbul ignore next */ []));
3228
- /**
3229
- * ⚠️ Faux tant que la personne n'a pas quitte le champ ou tente l'envoi (UX-3) :
3230
- * un formulaire vierge couvert d'erreurs accuse avant qu'on ait rien fait.
3231
- */
3232
- afficherErreurs = input(false, { ...(ngDevMode ? { debugName: "afficherErreurs" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3233
- obligatoire = input(false, { ...(ngDevMode ? { debugName: "obligatoire" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3234
- /**
3235
- * ⚠️ Change l'ordre VISUEL, jamais celui du document : l'etiquette reste
3236
- * reliee par `for`, et l'ordre de tabulation ne passe que par le controle.
3237
- */
3238
- disposition = input('empilee', /* @ts-ignore */
3239
- ...(ngDevMode ? [{ debugName: "disposition" }] : /* istanbul ignore next */ []));
3240
- /**
3241
- * Etiquette lue mais non affichee.
3242
- *
3243
- * ⚠️ Reservee a la RECHERCHE, ou l'icone, le bouton et la position en tete de
3244
- * page disent deja le but du champ. Partout ailleurs, une etiquette masquee est
3245
- * l'etiquette qui manque au moment ou l'on tape (UX-1).
3246
- */
3247
- libelleMasque = input(false, { ...(ngDevMode ? { debugName: "libelleMasque" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3248
- /** Identifiants du champ, lus par `stControleDeChamp` et par le gabarit. */
3249
- identifiants = (() => {
3250
- const base = `st-champ-${(compteur$8 += 1).toString()}`;
3251
- return {
3252
- controle: base,
3253
- libelle: `${base}-libelle`,
3254
- aide: `${base}-aide`,
3255
- complement: `${base}-complement`,
3256
- erreur: `${base}-erreur`,
3257
- };
3258
- })();
3259
- libelleObligatoire = computed(() => this.libelles().obligatoire, /* @ts-ignore */
3260
- ...(ngDevMode ? [{ debugName: "libelleObligatoire" }] : /* istanbul ignore next */ []));
3261
- /** Messages a afficher, dans la langue courante. Vide tant qu'ils ne doivent pas l'etre. */
3262
- messages = computed(() => {
3263
- if (!this.afficherErreurs())
3264
- return [];
3265
- const libelles = this.libelles();
3266
- const locale = this.locale();
3267
- return this.erreurs().map((erreur) => messageDErreur(erreur, libelles, locale));
3268
- }, /* @ts-ignore */
3269
- ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
3270
- /**
3271
- * Un clic sur l'etiquette d'un controle non natif n'y mene pas de lui-meme :
3272
- * on y amene le focus, comme le navigateur le fait pour un champ natif.
3273
- */
3274
- focaliserLeControle() {
3275
- const controle = this.element.nativeElement.querySelector(`#${this.identifiants.controle}`);
3276
- if (controle !== null && !ELEMENTS_ETIQUETABLES.has(controle.tagName))
3277
- controle.focus();
3278
- }
3279
- /** Lu par le controle pour `aria-invalid`. */
3280
- enErreur = computed(() => this.messages().length > 0, /* @ts-ignore */
3281
- ...(ngDevMode ? [{ debugName: "enErreur" }] : /* istanbul ignore next */ []));
3282
- /**
3283
- * ⚠️ Seuls les elements PRESENTS sont references : un `aria-describedby` qui
3284
- * vise un identifiant absent est ignore par certains lecteurs d'ecran — tout
3285
- * entier, y compris les references valides qui l'accompagnent.
3286
- */
3287
- descriptions = computed(() => {
3288
- const ids = [];
3289
- if (this.aide() !== null)
3290
- ids.push(this.identifiants.aide);
3291
- if (this.complement() !== null)
3292
- ids.push(this.identifiants.complement);
3293
- if (this.enErreur())
3294
- ids.push(this.identifiants.erreur);
3295
- return ids.length === 0 ? null : ids.join(' ');
3296
- }, /* @ts-ignore */
3297
- ...(ngDevMode ? [{ debugName: "descriptions" }] : /* istanbul ignore next */ []));
3298
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
3299
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StFormField, isStandalone: true, selector: "st-form-field", inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, complement: { classPropertyName: "complement", publicName: "complement", isSignal: true, isRequired: false, transformFunction: null }, erreurs: { classPropertyName: "erreurs", publicName: "erreurs", isSignal: true, isRequired: false, transformFunction: null }, afficherErreurs: { classPropertyName: "afficherErreurs", publicName: "afficherErreurs", isSignal: true, isRequired: false, transformFunction: null }, obligatoire: { classPropertyName: "obligatoire", publicName: "obligatoire", isSignal: true, isRequired: false, transformFunction: null }, disposition: { classPropertyName: "disposition", publicName: "disposition", isSignal: true, isRequired: false, transformFunction: null }, libelleMasque: { classPropertyName: "libelleMasque", publicName: "libelleMasque", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-en-erreur": "enErreur() ? \"\" : null", "attr.data-disposition": "disposition()" }, classAttribute: "st-form-field" }, ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus --\n Le clic sur une etiquette est une commodite de souris, comme pour tout\n champ natif : au clavier, le controle se rejoint par tabulation, et\n l'etiquette n'a pas a recevoir le focus. -->\n<label\n class=\"st-form-field__libelle\"\n [class.st-vh]=\"libelleMasque()\"\n [id]=\"identifiants.libelle\"\n [for]=\"identifiants.controle\"\n [attr.data-libelle]=\"libelle()\"\n (click)=\"focaliserLeControle()\"\n>\n {{ libelle() }}\n @if (obligatoire()) {\n <span class=\"st-form-field__obligatoire\">{{ libelleObligatoire() }}</span>\n }\n</label>\n\n@if (aide(); as texte) {\n <p class=\"st-form-field__aide\" [id]=\"identifiants.aide\">{{ texte }}</p>\n}\n\n@if (enErreur()) {\n <div class=\"st-form-field__erreurs\" [id]=\"identifiants.erreur\">\n @for (message of messages(); track $index) {\n <st-inline-message ton=\"danger\">{{ message }}</st-inline-message>\n }\n </div>\n}\n\n<div class=\"st-form-field__controle\">\n <ng-content />\n</div>\n\n@if (complement(); as texte) {\n <p class=\"st-form-field__complement\" [id]=\"identifiants.complement\">{{ texte }}</p>\n}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:grid;grid-template-areas:\"libelle\" \"aide\" \"erreur\" \"controle\" \"complement\";grid-template-columns:minmax(0,1fr);gap:var(--st-espacement-1);margin-block-end:var(--st-espacement-5)}:host([data-disposition=case]){grid-template-areas:\"erreur erreur\" \"controle libelle\" \". aide\" \". complement\";grid-template-columns:auto minmax(0,1fr);column-gap:var(--st-espacement-2);align-items:center}.st-form-field__libelle{grid-area:libelle}.st-form-field__aide{grid-area:aide}.st-form-field__erreurs{grid-area:erreur}.st-form-field__controle{grid-area:controle}.st-form-field__complement{grid-area:complement}:host([data-disposition=case]) .st-form-field__libelle{font-weight:var(--st-graisse-normale)}.st-form-field__libelle{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte)}.st-form-field__obligatoire{margin-inline-start:var(--st-espacement-1);font-weight:var(--st-graisse-normale);color:var(--st-couleur-texte-discret)}.st-form-field__aide,.st-form-field__complement{margin:0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}:host([data-en-erreur]){padding-inline-start:var(--st-espacement-3);border-inline-start:3px solid var(--st-couleur-danger)}\n"], dependencies: [{ kind: "component", type: StInlineMessage, selector: "st-inline-message", inputs: ["ton", "identifiant"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3300
- }
3301
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFormField, decorators: [{
3302
- type: Component,
3303
- args: [{ selector: 'st-form-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StInlineMessage], host: {
3304
- class: 'st-form-field',
3305
- '[attr.data-en-erreur]': 'enErreur() ? "" : null',
3306
- '[attr.data-disposition]': 'disposition()',
3307
- }, template: "<!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus --\n Le clic sur une etiquette est une commodite de souris, comme pour tout\n champ natif : au clavier, le controle se rejoint par tabulation, et\n l'etiquette n'a pas a recevoir le focus. -->\n<label\n class=\"st-form-field__libelle\"\n [class.st-vh]=\"libelleMasque()\"\n [id]=\"identifiants.libelle\"\n [for]=\"identifiants.controle\"\n [attr.data-libelle]=\"libelle()\"\n (click)=\"focaliserLeControle()\"\n>\n {{ libelle() }}\n @if (obligatoire()) {\n <span class=\"st-form-field__obligatoire\">{{ libelleObligatoire() }}</span>\n }\n</label>\n\n@if (aide(); as texte) {\n <p class=\"st-form-field__aide\" [id]=\"identifiants.aide\">{{ texte }}</p>\n}\n\n@if (enErreur()) {\n <div class=\"st-form-field__erreurs\" [id]=\"identifiants.erreur\">\n @for (message of messages(); track $index) {\n <st-inline-message ton=\"danger\">{{ message }}</st-inline-message>\n }\n </div>\n}\n\n<div class=\"st-form-field__controle\">\n <ng-content />\n</div>\n\n@if (complement(); as texte) {\n <p class=\"st-form-field__complement\" [id]=\"identifiants.complement\">{{ texte }}</p>\n}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:grid;grid-template-areas:\"libelle\" \"aide\" \"erreur\" \"controle\" \"complement\";grid-template-columns:minmax(0,1fr);gap:var(--st-espacement-1);margin-block-end:var(--st-espacement-5)}:host([data-disposition=case]){grid-template-areas:\"erreur erreur\" \"controle libelle\" \". aide\" \". complement\";grid-template-columns:auto minmax(0,1fr);column-gap:var(--st-espacement-2);align-items:center}.st-form-field__libelle{grid-area:libelle}.st-form-field__aide{grid-area:aide}.st-form-field__erreurs{grid-area:erreur}.st-form-field__controle{grid-area:controle}.st-form-field__complement{grid-area:complement}:host([data-disposition=case]) .st-form-field__libelle{font-weight:var(--st-graisse-normale)}.st-form-field__libelle{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte)}.st-form-field__obligatoire{margin-inline-start:var(--st-espacement-1);font-weight:var(--st-graisse-normale);color:var(--st-couleur-texte-discret)}.st-form-field__aide,.st-form-field__complement{margin:0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}:host([data-en-erreur]){padding-inline-start:var(--st-espacement-3);border-inline-start:3px solid var(--st-couleur-danger)}\n"] }]
3308
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], complement: [{ type: i0.Input, args: [{ isSignal: true, alias: "complement", required: false }] }], erreurs: [{ type: i0.Input, args: [{ isSignal: true, alias: "erreurs", required: false }] }], afficherErreurs: [{ type: i0.Input, args: [{ isSignal: true, alias: "afficherErreurs", required: false }] }], obligatoire: [{ type: i0.Input, args: [{ isSignal: true, alias: "obligatoire", required: false }] }], disposition: [{ type: i0.Input, args: [{ isSignal: true, alias: "disposition", required: false }] }], libelleMasque: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelleMasque", required: false }] }] } });
3309
- /**
3310
- * Pose sur l'element natif d'un champ le cablage de son `st-form-field` :
3311
- * identifiant, description, invalidite, obligation.
3312
- *
3313
- * ⚠️ `aria-required` et NON l'attribut `required` natif. Le natif declenche la
3314
- * validation du navigateur a l'envoi : une bulle « Veuillez renseigner ce champ »
3315
- * dans la langue du NAVIGATEUR, pas dans celle de l'application, et qui double
3316
- * le message du socle.
3317
- */
3318
- class StControleDeChamp {
3319
- champ = inject(StFormField);
3320
- /**
3321
- * ⚠️ `label for` n'etiquette que les elements natifs de formulaire. Le
3322
- * declencheur d'une selection multiple est un `div` de role `combobox` : sans
3323
- * `aria-labelledby`, il n'aurait aucun nom, et l'etiquette visible ne dirait
3324
- * rien au lecteur d'ecran.
3325
- */
3326
- etiquetable = ELEMENTS_ETIQUETABLES.has(inject(ElementRef).nativeElement.tagName);
3327
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StControleDeChamp, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3328
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: StControleDeChamp, isStandalone: true, selector: "[stControleDeChamp]", host: { properties: { "id": "champ.identifiants.controle", "attr.aria-labelledby": "etiquetable ? null : champ.identifiants.libelle", "attr.aria-describedby": "champ.descriptions()", "attr.aria-invalid": "champ.enErreur() ? \"true\" : null", "attr.aria-required": "champ.obligatoire() ? \"true\" : null" } }, ngImport: i0 });
3329
- }
3330
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StControleDeChamp, decorators: [{
3331
- type: Directive,
3332
- args: [{
3333
- selector: '[stControleDeChamp]',
3334
- host: {
3335
- '[id]': 'champ.identifiants.controle',
3336
- '[attr.aria-labelledby]': 'etiquetable ? null : champ.identifiants.libelle',
3337
- '[attr.aria-describedby]': 'champ.descriptions()',
3338
- '[attr.aria-invalid]': 'champ.enErreur() ? "true" : null',
3339
- '[attr.aria-required]': 'champ.obligatoire() ? "true" : null',
3340
- },
3341
- }]
3342
- }] });
3343
-
3344
- /**
3345
- * Champ de saisie d'une ligne, integre a Signal Forms.
3346
- *
3347
- * ```html
3348
- * <st-input libelle="Nom du beneficiaire" autocomplete="name" [formField]="virement.beneficiaire" />
3349
- * <!-- ou, sans formulaire : -->
3350
- * <st-input libelle="Reference" [(value)]="reference" />
3351
- * ```
3352
- *
3353
- * ⚠️ **Contrat Signal Forms, noms anglais.** `value`, `errors`, `touched`,
3354
- * `disabled`… sont cherches PAR LEUR NOM par la directive `[formField]` : les
3355
- * traduire romprait l'integration sans la moindre erreur (audit de la vague 3,
3356
- * V3-1). L'import d'`@angular/forms` est un import de TYPE : aucun code n'entre
3357
- * dans le paquet.
3358
- *
3359
- * ⚠️ **Pas d'attribut `maxlength` natif.** Il TRONQUE en silence un texte colle
3360
- * trop long : la personne croit avoir saisi sa reference entiere, et n'en envoie
3361
- * que le debut. La limite passe par la validation, qui DIT le depassement.
3362
- */
3363
- class StInput extends ControleDeSaisie {
3364
- type = input('text', /* @ts-ignore */
3365
- ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
3366
- /**
3367
- * WCAG 1.3.5 : identifie le but du champ, pour le remplissage automatique et les
3368
- * aides techniques. `null` : laisse le navigateur decider.
3369
- */
3370
- autocomplete = input(null, /* @ts-ignore */
3371
- ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
3372
- /** Clavier virtuel adapte : `numeric`, `decimal`, `email`… */
3373
- inputmode = input(null, /* @ts-ignore */
3374
- ...(ngDevMode ? [{ debugName: "inputmode" }] : /* istanbul ignore next */ []));
3375
- surSaisie(evenement) {
3376
- this.value.set(this.texteSaisi(evenement));
3377
- }
3378
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StInput, deps: null, target: i0.ɵɵFactoryTarget.Component });
3379
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StInput, isStandalone: true, selector: "st-input", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, inputmode: { classPropertyName: "inputmode", publicName: "inputmode", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-input" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-input__champ\"\n stControleDeChamp\n [type]=\"type()\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.inputmode]=\"inputmode()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-input__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-input__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-input__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-input__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-input__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3380
- }
3381
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StInput, decorators: [{
3382
- type: Component,
3383
- args: [{ selector: 'st-input', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: { class: 'st-input' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-input__champ\"\n stControleDeChamp\n [type]=\"type()\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.inputmode]=\"inputmode()\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-input__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-input__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-input__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-input__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-input__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}\n"] }]
3384
- }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], inputmode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputmode", required: false }] }] } });
3385
-
3386
- /** Restant a partir duquel le compteur se signale. */
3387
- const SEUILS_D_ANNONCE = [20, 10, 0];
3388
- /**
3389
- * Zone de texte, integree a Signal Forms, avec compteur quand une longueur
3390
- * maximale est fixee.
3391
- *
3392
- * ⚠️ **Le compteur n'est pas annonce a chaque frappe** : une region vivante qui
3393
- * change a chaque caractere recouvre ce que la personne tape. Il est RELIE a la
3394
- * zone — donc lu quand on y revient — et seuls les seuils (20, 10, 0 restants)
3395
- * et le depassement s'annoncent (UX-11).
3396
- *
3397
- * ⚠️ **Pas de `maxlength` natif**, meme raison que `st-input` : un texte colle
3398
- * serait tronque en silence. Le compteur affiche le depassement, la validation
3399
- * le refuse.
3400
- */
3401
- class StTextarea extends ControleDeSaisie {
3402
- libelles = inject(ST_LIBELLES);
3403
- lignes = input(4, /* @ts-ignore */
3404
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
3405
- /** Contrat Signal Forms : la longueur maximale fixee par le schema. */
3406
- maxLength = input(undefined, /* @ts-ignore */
3407
- ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
3408
- annonce = new AnnonceLocale(inject(DestroyRef));
3409
- restants = computed(() => {
3410
- const maximum = this.maxLength();
3411
- return maximum === undefined ? null : maximum - this.value().length;
3412
- }, /* @ts-ignore */
3413
- ...(ngDevMode ? [{ debugName: "restants" }] : /* istanbul ignore next */ []));
3414
- compteur = computed(() => {
3415
- const restants = this.restants();
3416
- if (restants === null)
3417
- return null;
3418
- const libelles = this.libelles();
3419
- return restants >= 0
3420
- ? libelles.caracteresRestants(restants)
3421
- : libelles.caracteresEnTrop(-restants);
3422
- }, /* @ts-ignore */
3423
- ...(ngDevMode ? [{ debugName: "compteur" }] : /* istanbul ignore next */ []));
3424
- constructor() {
3425
- super();
3426
- let precedent = null;
3427
- effect(() => {
3428
- const restants = this.restants();
3429
- if (restants === null)
3430
- return;
3431
- const avant = precedent;
3432
- precedent = restants;
3433
- if (avant === null)
3434
- return;
3435
- const franchi = SEUILS_D_ANNONCE.some((seuil) => avant > seuil && restants <= seuil) ||
3436
- (avant >= 0 && restants < 0);
3437
- const texte = this.compteur();
3438
- if (franchi && texte !== null)
3439
- this.annonce.annoncer(texte);
3440
- });
3441
- }
3442
- surSaisie(evenement) {
3443
- this.value.set(this.texteSaisi(evenement));
3444
- }
3445
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
3446
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StTextarea, isStandalone: true, selector: "st-textarea", inputs: { lignes: { classPropertyName: "lignes", publicName: "lignes", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-textarea" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [complement]=\"compteur()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <textarea\n #champ\n class=\"st-textarea__champ\"\n stControleDeChamp\n [rows]=\"lignes()\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n ></textarea>\n</st-form-field>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-textarea__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-textarea__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-textarea__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-textarea__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-textarea__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-textarea__champ{resize:vertical}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3447
- }
3448
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTextarea, decorators: [{
3449
- type: Component,
3450
- args: [{ selector: 'st-textarea', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: { class: 'st-textarea' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [complement]=\"compteur()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <textarea\n #champ\n class=\"st-textarea__champ\"\n stControleDeChamp\n [rows]=\"lignes()\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n ></textarea>\n</st-form-field>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-textarea__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-textarea__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-textarea__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-textarea__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-textarea__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-textarea__champ{resize:vertical}\n"] }]
3451
- }], ctorParameters: () => [], propDecorators: { lignes: [{ type: i0.Input, args: [{ isSignal: true, alias: "lignes", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }] } });
3452
-
3453
- let compteur$7 = 0;
3454
- /**
3455
- * Groupe de controles, nomme par sa QUESTION.
3456
- *
3457
- * ⚠️ `fieldset` et `legend` natifs (UX-7). Sans legende, un lecteur d'ecran
3458
- * annonce « Oui, bouton radio » et « Non, bouton radio » : la reponse, jamais la
3459
- * question. La legende est lue a l'entree dans le groupe.
3460
- *
3461
- * C'est le pendant de `st-form-field` pour un GROUPE : l'aide et l'erreur sont
3462
- * reliees au `fieldset` par `aria-describedby`, et l'erreur porte sur le groupe
3463
- * entier — « Choisissez un mode de reglement » — et non sur chaque option.
3464
- *
3465
- * ⚠️ L'obligation se dit dans la legende, en texte : `aria-required` n'est pas
3466
- * valide sur un groupe, et `required` natif ferait parler le navigateur dans sa
3467
- * propre langue.
3468
- */
3469
- class StFieldset {
3470
- libelles = inject(ST_LIBELLES);
3471
- locale = inject(ST_LOCALE);
3472
- /** La question posee par le groupe. Obligatoire. */
3473
- legende = input.required(/* @ts-ignore */
3474
- ...(ngDevMode ? [{ debugName: "legende" }] : /* istanbul ignore next */ []));
3475
- aide = input(null, /* @ts-ignore */
3476
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3477
- erreurs = input([], /* @ts-ignore */
3478
- ...(ngDevMode ? [{ debugName: "erreurs" }] : /* istanbul ignore next */ []));
3479
- afficherErreurs = input(false, { ...(ngDevMode ? { debugName: "afficherErreurs" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3480
- obligatoire = input(false, { ...(ngDevMode ? { debugName: "obligatoire" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3481
- base = `st-groupe-${(compteur$7 += 1).toString()}`;
3482
- identifiantAide = `${this.base}-aide`;
3483
- identifiantErreur = `${this.base}-erreur`;
3484
- libelleObligatoire = computed(() => this.libelles().obligatoire, /* @ts-ignore */
3485
- ...(ngDevMode ? [{ debugName: "libelleObligatoire" }] : /* istanbul ignore next */ []));
3486
- messages = computed(() => {
3487
- if (!this.afficherErreurs())
3488
- return [];
3489
- const libelles = this.libelles();
3490
- const locale = this.locale();
3491
- return this.erreurs().map((erreur) => messageDErreur(erreur, libelles, locale));
3492
- }, /* @ts-ignore */
3493
- ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
3494
- enErreur = computed(() => this.messages().length > 0, /* @ts-ignore */
3495
- ...(ngDevMode ? [{ debugName: "enErreur" }] : /* istanbul ignore next */ []));
3496
- descriptions = computed(() => {
3497
- const ids = [];
3498
- if (this.aide() !== null)
3499
- ids.push(this.identifiantAide);
3500
- if (this.enErreur())
3501
- ids.push(this.identifiantErreur);
3502
- return ids.length === 0 ? null : ids.join(' ');
3503
- }, /* @ts-ignore */
3504
- ...(ngDevMode ? [{ debugName: "descriptions" }] : /* istanbul ignore next */ []));
3505
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFieldset, deps: [], target: i0.ɵɵFactoryTarget.Component });
3506
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StFieldset, isStandalone: true, selector: "st-fieldset", inputs: { legende: { classPropertyName: "legende", publicName: "legende", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, erreurs: { classPropertyName: "erreurs", publicName: "erreurs", isSignal: true, isRequired: false, transformFunction: null }, afficherErreurs: { classPropertyName: "afficherErreurs", publicName: "afficherErreurs", isSignal: true, isRequired: false, transformFunction: null }, obligatoire: { classPropertyName: "obligatoire", publicName: "obligatoire", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-en-erreur": "enErreur() ? \"\" : null" }, classAttribute: "st-fieldset" }, ngImport: i0, template: "<fieldset class=\"st-fieldset__groupe\" [attr.aria-describedby]=\"descriptions()\">\n <legend class=\"st-fieldset__legende\" [attr.data-libelle]=\"legende()\">\n {{ legende() }}\n @if (obligatoire()) {\n <span class=\"st-fieldset__obligatoire\">{{ libelleObligatoire() }}</span>\n }\n </legend>\n\n @if (aide(); as texte) {\n <p class=\"st-fieldset__aide\" [id]=\"identifiantAide\">{{ texte }}</p>\n }\n\n @if (enErreur()) {\n <div class=\"st-fieldset__erreurs\" [id]=\"identifiantErreur\">\n @for (message of messages(); track $index) {\n <st-inline-message ton=\"danger\">{{ message }}</st-inline-message>\n }\n </div>\n }\n\n <div class=\"st-fieldset__controles\">\n <ng-content />\n </div>\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;margin-block-end:var(--st-espacement-5)}.st-fieldset__groupe{min-inline-size:0;padding:0;margin:0;border:0}.st-fieldset__legende{padding:0;margin-block-end:var(--st-espacement-2);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte)}.st-fieldset__obligatoire{margin-inline-start:var(--st-espacement-1);font-weight:var(--st-graisse-normale);color:var(--st-couleur-texte-discret)}.st-fieldset__aide{margin:0 0 var(--st-espacement-2);font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-fieldset__erreurs{margin-block-end:var(--st-espacement-2)}.st-fieldset__controles{display:flex;flex-direction:column;gap:var(--st-espacement-2)}:host([data-en-erreur]) .st-fieldset__groupe{padding-inline-start:var(--st-espacement-3);border-inline-start:3px solid var(--st-couleur-danger)}\n"], dependencies: [{ kind: "component", type: StInlineMessage, selector: "st-inline-message", inputs: ["ton", "identifiant"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3507
- }
3508
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFieldset, decorators: [{
3509
- type: Component,
3510
- args: [{ selector: 'st-fieldset', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StInlineMessage], host: {
3511
- class: 'st-fieldset',
3512
- '[attr.data-en-erreur]': 'enErreur() ? "" : null',
3513
- }, template: "<fieldset class=\"st-fieldset__groupe\" [attr.aria-describedby]=\"descriptions()\">\n <legend class=\"st-fieldset__legende\" [attr.data-libelle]=\"legende()\">\n {{ legende() }}\n @if (obligatoire()) {\n <span class=\"st-fieldset__obligatoire\">{{ libelleObligatoire() }}</span>\n }\n </legend>\n\n @if (aide(); as texte) {\n <p class=\"st-fieldset__aide\" [id]=\"identifiantAide\">{{ texte }}</p>\n }\n\n @if (enErreur()) {\n <div class=\"st-fieldset__erreurs\" [id]=\"identifiantErreur\">\n @for (message of messages(); track $index) {\n <st-inline-message ton=\"danger\">{{ message }}</st-inline-message>\n }\n </div>\n }\n\n <div class=\"st-fieldset__controles\">\n <ng-content />\n </div>\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;margin-block-end:var(--st-espacement-5)}.st-fieldset__groupe{min-inline-size:0;padding:0;margin:0;border:0}.st-fieldset__legende{padding:0;margin-block-end:var(--st-espacement-2);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte)}.st-fieldset__obligatoire{margin-inline-start:var(--st-espacement-1);font-weight:var(--st-graisse-normale);color:var(--st-couleur-texte-discret)}.st-fieldset__aide{margin:0 0 var(--st-espacement-2);font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}.st-fieldset__erreurs{margin-block-end:var(--st-espacement-2)}.st-fieldset__controles{display:flex;flex-direction:column;gap:var(--st-espacement-2)}:host([data-en-erreur]) .st-fieldset__groupe{padding-inline-start:var(--st-espacement-3);border-inline-start:3px solid var(--st-couleur-danger)}\n"] }]
3514
- }], propDecorators: { legende: [{ type: i0.Input, args: [{ isSignal: true, alias: "legende", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], erreurs: [{ type: i0.Input, args: [{ isSignal: true, alias: "erreurs", required: false }] }], afficherErreurs: [{ type: i0.Input, args: [{ isSignal: true, alias: "afficherErreurs", required: false }] }], obligatoire: [{ type: i0.Input, args: [{ isSignal: true, alias: "obligatoire", required: false }] }] } });
3515
-
3516
- /**
3517
- * Case a cocher, integree a Signal Forms.
3518
- *
3519
- * ```html
3520
- * <st-checkbox libelle="J'accepte les conditions generales" [formField]="inscription.conditions" />
3521
- * ```
3522
- *
3523
- * Un element `input type="checkbox"` natif : Espace coche, l'etat est annonce,
3524
- * et la case suit le reglage de contraste du systeme sans rien ecrire.
3525
- *
3526
- * ⚠️ **Pour un choix a envoyer avec un formulaire.** Un reglage qui s'applique
3527
- * IMMEDIATEMENT est un `st-switch` : la case promet un effet a l'envoi, et
3528
- * l'interrupteur un effet tout de suite (UX-8).
3529
- */
3530
- class StCheckbox {
3531
- libelle = input.required(/* @ts-ignore */
3532
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
3533
- aide = input(null, /* @ts-ignore */
3534
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3535
- /** Contrat Signal Forms. */
3536
- checked = model(false, /* @ts-ignore */
3537
- ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
3538
- errors = input([], /* @ts-ignore */
3539
- ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3540
- touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3541
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3542
- required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3543
- hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3544
- name = input('', /* @ts-ignore */
3545
- ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3546
- touch = output();
3547
- champ = viewChild.required('champ', /* @ts-ignore */
3548
- ...(ngDevMode ? [{ debugName: "champ" }] : /* istanbul ignore next */ []));
3549
- focus(options) {
3550
- this.champ().nativeElement.focus(options);
3551
- }
3552
- surChangement(evenement) {
3553
- this.checked.set(evenement.target.checked);
3554
- }
3555
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCheckbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
3556
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.6", type: StCheckbox, isStandalone: true, selector: "st-checkbox", inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touch: "touch" }, host: { properties: { "attr.hidden": "hidden() ? \"\" : null" }, classAttribute: "st-checkbox" }, viewQueries: [{ propertyName: "champ", first: true, predicate: ["champ"], descendants: true, isSignal: true }], ngImport: i0, template: "<st-form-field\n disposition=\"case\"\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-checkbox__case\"\n type=\"checkbox\"\n stControleDeChamp\n [checked]=\"checked()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (change)=\"surChangement($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-checkbox__case:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-checkbox__case{inline-size:1.5rem;block-size:1.5rem;margin:0;cursor:pointer;accent-color:var(--st-couleur-primaire)}.st-checkbox__case:disabled{cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3557
- }
3558
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCheckbox, decorators: [{
3559
- type: Component,
3560
- args: [{ selector: 'st-checkbox', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: {
3561
- class: 'st-checkbox',
3562
- '[attr.hidden]': 'hidden() ? "" : null',
3563
- }, template: "<st-form-field\n disposition=\"case\"\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-checkbox__case\"\n type=\"checkbox\"\n stControleDeChamp\n [checked]=\"checked()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (change)=\"surChangement($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-checkbox__case:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-checkbox__case{inline-size:1.5rem;block-size:1.5rem;margin:0;cursor:pointer;accent-color:var(--st-couleur-primaire)}.st-checkbox__case:disabled{cursor:not-allowed}\n"] }]
3564
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], champ: [{ type: i0.ViewChild, args: ['champ', { isSignal: true }] }] } });
3565
-
3566
- let compteur$6 = 0;
3567
- /**
3568
- * Groupe de boutons radio, integre a Signal Forms.
3569
- *
3570
- * ```html
3571
- * <st-radio-group legende="Mode de reglement" [options]="modes" [formField]="commande.mode" />
3572
- * ```
3573
- *
3574
- * ⚠️ **De vrais boutons radio natifs** : un seul arret de tabulation pour le
3575
- * groupe, deplacement aux fleches, « 2 sur 3 » annonce. Les recrire en ARIA,
3576
- * c'est cent lignes pour obtenir moins bien — meme raison que `st-theme-toggle`.
3577
- *
3578
- * ⚠️ **L'erreur porte sur le groupe**, par `st-fieldset`, et non sur chaque option :
3579
- * « Choisissez un mode de reglement », une fois.
3580
- *
3581
- * `touch` n'est emis que lorsque le focus QUITTE le groupe : passer d'une option a
3582
- * l'autre aux fleches n'est pas une fin de saisie, et l'erreur n'a pas a surgir
3583
- * pendant qu'on choisit.
3584
- */
3585
- class StRadioGroup {
3586
- element = inject(ElementRef);
3587
- legende = input.required(/* @ts-ignore */
3588
- ...(ngDevMode ? [{ debugName: "legende" }] : /* istanbul ignore next */ []));
3589
- aide = input(null, /* @ts-ignore */
3590
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3591
- options = input.required(/* @ts-ignore */
3592
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
3593
- /** Contrat Signal Forms. */
3594
- value = model('', /* @ts-ignore */
3595
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
3596
- errors = input([], /* @ts-ignore */
3597
- ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
3598
- touched = input(false, { ...(ngDevMode ? { debugName: "touched" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3599
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3600
- required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3601
- hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3602
- name = input('', /* @ts-ignore */
3603
- ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3604
- touch = output();
3605
- /** Nom partage par les options : sans lui, chaque radio forme son propre groupe. */
3606
- nomParDefaut = `st-radio-${(compteur$6 += 1).toString()}`;
3607
- nomDuGroupe() {
3608
- return this.name() === '' ? this.nomParDefaut : this.name();
3609
- }
3610
- identifiantAide(indice) {
3611
- return `${this.nomParDefaut}-aide-${indice.toString()}`;
3612
- }
3613
- /** Focus sur l'option cochee, sinon la premiere disponible : c'est l'arret du groupe. */
3614
- focus(options) {
3615
- const hote = this.element.nativeElement;
3616
- const cible = hote.querySelector('input:checked') ??
3617
- hote.querySelector('input:not(:disabled)');
3618
- cible?.focus(options);
3619
- }
3620
- choisir(valeur) {
3621
- this.value.set(valeur);
3622
- }
3623
- aLaSortie(evenement) {
3624
- const suivant = evenement.relatedTarget;
3625
- if (suivant !== null && this.element.nativeElement.contains(suivant))
3626
- return;
3627
- this.touch.emit();
3628
- }
3629
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
3630
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StRadioGroup, isStandalone: true, selector: "st-radio-group", inputs: { legende: { classPropertyName: "legende", publicName: "legende", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { listeners: { "focusout": "aLaSortie($event)" }, properties: { "attr.hidden": "hidden() ? \"\" : null" }, classAttribute: "st-radio-group" }, ngImport: i0, template: "<st-fieldset\n [legende]=\"legende()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n @for (option of options(); track option.valeur; let indice = $index) {\n <div class=\"st-radio-group__option\">\n <input\n class=\"st-radio-group__radio\"\n type=\"radio\"\n [id]=\"nomDuGroupe() + '-' + indice\"\n [name]=\"nomDuGroupe()\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === value()\"\n [disabled]=\"disabled() || option.desactivee === true\"\n [attr.aria-describedby]=\"option.aide ? identifiantAide(indice) : null\"\n (change)=\"choisir(option.valeur)\"\n />\n <label class=\"st-radio-group__libelle\" [for]=\"nomDuGroupe() + '-' + indice\">\n {{ option.libelle }}\n </label>\n @if (option.aide; as aide) {\n <p class=\"st-radio-group__aide\" [id]=\"identifiantAide(indice)\">{{ aide }}</p>\n }\n </div>\n }\n</st-fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-radio-group__option{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:var(--st-espacement-2);align-items:center}.st-radio-group__radio:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-radio-group__radio{inline-size:1.5rem;block-size:1.5rem;margin:0;cursor:pointer;accent-color:var(--st-couleur-primaire)}.st-radio-group__radio:disabled{cursor:not-allowed}.st-radio-group__libelle{cursor:pointer}.st-radio-group__aide{grid-column:2;margin:0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}\n"], dependencies: [{ kind: "component", type: StFieldset, selector: "st-fieldset", inputs: ["legende", "aide", "erreurs", "afficherErreurs", "obligatoire"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3631
- }
3632
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StRadioGroup, decorators: [{
3633
- type: Component,
3634
- args: [{ selector: 'st-radio-group', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFieldset], host: {
3635
- class: 'st-radio-group',
3636
- '[attr.hidden]': 'hidden() ? "" : null',
3637
- '(focusout)': 'aLaSortie($event)',
3638
- }, template: "<st-fieldset\n [legende]=\"legende()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n @for (option of options(); track option.valeur; let indice = $index) {\n <div class=\"st-radio-group__option\">\n <input\n class=\"st-radio-group__radio\"\n type=\"radio\"\n [id]=\"nomDuGroupe() + '-' + indice\"\n [name]=\"nomDuGroupe()\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === value()\"\n [disabled]=\"disabled() || option.desactivee === true\"\n [attr.aria-describedby]=\"option.aide ? identifiantAide(indice) : null\"\n (change)=\"choisir(option.valeur)\"\n />\n <label class=\"st-radio-group__libelle\" [for]=\"nomDuGroupe() + '-' + indice\">\n {{ option.libelle }}\n </label>\n @if (option.aide; as aide) {\n <p class=\"st-radio-group__aide\" [id]=\"identifiantAide(indice)\">{{ aide }}</p>\n }\n </div>\n }\n</st-fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-radio-group__option{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:var(--st-espacement-2);align-items:center}.st-radio-group__radio:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-radio-group__radio{inline-size:1.5rem;block-size:1.5rem;margin:0;cursor:pointer;accent-color:var(--st-couleur-primaire)}.st-radio-group__radio:disabled{cursor:not-allowed}.st-radio-group__libelle{cursor:pointer}.st-radio-group__aide{grid-column:2;margin:0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}\n"] }]
3639
- }], propDecorators: { legende: [{ type: i0.Input, args: [{ isSignal: true, alias: "legende", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
3640
-
3641
- /**
3642
- * Interrupteur : un reglage qui s'applique IMMEDIATEMENT.
3643
- *
3644
- * ```html
3645
- * <st-switch libelle="Notifications par SMS" [(checked)]="sms" (checkedChange)="enregistrer($event)" />
3646
- * ```
3647
- *
3648
- * ⚠️ **Jamais dans un formulaire a envoyer** (UX-8). Un interrupteur promet que
3649
- * l'effet a lieu au basculement ; pose au-dessus d'un bouton « Enregistrer », il
3650
- * ment — la personne croit le reglage applique, quitte la page, et le perd. Pour
3651
- * un choix envoye plus tard, c'est `st-checkbox`.
3652
- *
3653
- * C'est pourquoi il n'affiche aucune erreur de validation : un reglage immediat
3654
- * ne se valide pas a l'envoi. Un refus du serveur se dit par un message, et
3655
- * l'interrupteur revient a son etat precedent.
3656
- *
3657
- * ⚠️ Une case a cocher native avec `role="switch"` : Espace bascule, l'etiquette
3658
- * est reliee par `for`, et le lecteur d'ecran annonce « active » ou
3659
- * « desactive » — mieux qu'un `div` qui imiterait tout cela.
3660
- */
3661
- class StSwitch {
3662
- libelle = input.required(/* @ts-ignore */
3663
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
3664
- aide = input(null, /* @ts-ignore */
3665
- ...(ngDevMode ? [{ debugName: "aide" }] : /* istanbul ignore next */ []));
3666
- /** Contrat Signal Forms. */
3667
- checked = model(false, /* @ts-ignore */
3668
- ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
3669
- disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3670
- hidden = input(false, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3671
- name = input('', /* @ts-ignore */
3672
- ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
3673
- touch = output();
3674
- champ = viewChild.required('champ', /* @ts-ignore */
3675
- ...(ngDevMode ? [{ debugName: "champ" }] : /* istanbul ignore next */ []));
3676
- focus(options) {
3677
- this.champ().nativeElement.focus(options);
3678
- }
3679
- surChangement(evenement) {
3680
- this.checked.set(evenement.target.checked);
3681
- }
3682
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
3683
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.6", type: StSwitch, isStandalone: true, selector: "st-switch", inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null }, aide: { classPropertyName: "aide", publicName: "aide", isSignal: true, isRequired: false, transformFunction: null }, checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touch: "touch" }, host: { properties: { "attr.hidden": "hidden() ? \"\" : null" }, classAttribute: "st-switch" }, viewQueries: [{ propertyName: "champ", first: true, predicate: ["champ"], descendants: true, isSignal: true }], ngImport: i0, template: "<st-form-field disposition=\"case\" [libelle]=\"libelle()\" [aide]=\"aide()\">\n <input\n #champ\n class=\"st-switch__interrupteur\"\n type=\"checkbox\"\n role=\"switch\"\n stControleDeChamp\n [checked]=\"checked()\"\n [attr.aria-checked]=\"checked()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (change)=\"surChangement($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-switch__interrupteur:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-switch__interrupteur{position:relative;inline-size:2.75rem;block-size:1.5rem;margin:0;cursor:pointer;background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-pleine);appearance:none}.st-switch__interrupteur:checked:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px;box-shadow:0 0 0 2px var(--st-couleur-surface)}.st-switch__interrupteur:after{position:absolute;inset-block-start:50%;inset-inline-start:.125rem;inline-size:1.125rem;block-size:1.125rem;content:\"\";background:var(--st-couleur-texte-discret);border-radius:var(--st-rayon-pleine);transition:inset-inline-start var(--st-duree-rapide) ease;transform:translateY(-50%)}.st-switch__interrupteur:checked{background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-switch__interrupteur:checked:after{inset-inline-start:calc(100% - 1.25rem);background:var(--st-couleur-sur-primaire)}.st-switch__interrupteur:disabled{cursor:not-allowed;opacity:.6}@media(forced-colors:active){.st-switch__interrupteur{border-color:CanvasText}.st-switch__interrupteur:after{background:CanvasText}.st-switch__interrupteur:checked:after{background:Highlight}}@media(prefers-reduced-motion:reduce){.st-switch__interrupteur:after{transition:none}}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3684
- }
3685
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSwitch, decorators: [{
3686
- type: Component,
3687
- args: [{ selector: 'st-switch', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: {
3688
- class: 'st-switch',
3689
- '[attr.hidden]': 'hidden() ? "" : null',
3690
- }, template: "<st-form-field disposition=\"case\" [libelle]=\"libelle()\" [aide]=\"aide()\">\n <input\n #champ\n class=\"st-switch__interrupteur\"\n type=\"checkbox\"\n role=\"switch\"\n stControleDeChamp\n [checked]=\"checked()\"\n [attr.aria-checked]=\"checked()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (change)=\"surChangement($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-switch__interrupteur:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-switch__interrupteur{position:relative;inline-size:2.75rem;block-size:1.5rem;margin:0;cursor:pointer;background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-pleine);appearance:none}.st-switch__interrupteur:checked:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px;box-shadow:0 0 0 2px var(--st-couleur-surface)}.st-switch__interrupteur:after{position:absolute;inset-block-start:50%;inset-inline-start:.125rem;inline-size:1.125rem;block-size:1.125rem;content:\"\";background:var(--st-couleur-texte-discret);border-radius:var(--st-rayon-pleine);transition:inset-inline-start var(--st-duree-rapide) ease;transform:translateY(-50%)}.st-switch__interrupteur:checked{background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-switch__interrupteur:checked:after{inset-inline-start:calc(100% - 1.25rem);background:var(--st-couleur-sur-primaire)}.st-switch__interrupteur:disabled{cursor:not-allowed;opacity:.6}@media(forced-colors:active){.st-switch__interrupteur{border-color:CanvasText}.st-switch__interrupteur:after{background:CanvasText}.st-switch__interrupteur:checked:after{background:Highlight}}@media(prefers-reduced-motion:reduce){.st-switch__interrupteur:after{transition:none}}\n"] }]
3691
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }], aide: [{ type: i0.Input, args: [{ isSignal: true, alias: "aide", required: false }] }], checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }], champ: [{ type: i0.ViewChild, args: ['champ', { isSignal: true }] }] } });
3692
-
3693
- let compteur$5 = 0;
3694
- /**
3695
- * Recapitulatif des erreurs d'un formulaire, a l'envoi.
3696
- *
3697
- * ```html
3698
- * <st-error-summary [erreurs]="tentative() ? virement().errorSummary() : []" />
3699
- * ```
3700
- *
3701
- * ⚠️ **Il prend le focus en apparaissant** (UX-5). Sans lui, une personne au
3702
- * clavier ou au lecteur d'ecran appuie sur « Envoyer », et rien ne se passe : le
3703
- * champ refuse est en haut de la page, hors de sa vue, et personne ne le lui dit.
3704
- *
3705
- * ⚠️ **Chaque ligne nomme son champ.** « Ce champ est obligatoire. » repete cinq
3706
- * fois n'apprend rien ; « Nom du beneficiaire : ce champ est obligatoire. », si.
3707
- * Le libelle est lu sur l'element lie au champ, par Signal Forms — sans registre,
3708
- * et quel que soit le composant.
3709
- *
3710
- * ⚠️ **Une ligne par champ**, la premiere erreur seulement : trois erreurs sur le
3711
- * meme IBAN font trois lignes pour une seule correction.
3712
- *
3713
- * ⚠️ **Une region nommee par son titre.** Un `div` sans role ne PEUT pas porter
3714
- * `aria-labelledby` : le nom serait ignore, et le focus n'annoncerait rien. En
3715
- * region, il annonce « 2 erreurs a corriger » a l'arrivee du focus, et se
3716
- * retrouve dans la liste des reperes.
3717
- *
3718
- * Chaque ligne amene le focus sur son champ : c'est le champ, et non la ligne,
3719
- * qu'il faut corriger. Une erreur rattachee au formulaire entier — un refus du
3720
- * serveur sur un champ inconnu — s'affiche sans lien.
3721
- */
3722
- class StErrorSummary {
3723
- libelles = inject(ST_LIBELLES);
3724
- locale = inject(ST_LOCALE);
3725
- element = inject(ElementRef);
3726
- injecteur = inject(Injector);
3727
- erreurs = input.required(/* @ts-ignore */
3728
- ...(ngDevMode ? [{ debugName: "erreurs" }] : /* istanbul ignore next */ []));
3729
- /** `null` : « N erreurs a corriger », du dictionnaire. */
3730
- titre = input(null, /* @ts-ignore */
3731
- ...(ngDevMode ? [{ debugName: "titre" }] : /* istanbul ignore next */ []));
3732
- /** Unique par instance : deux recapitulatifs sur une page partageraient leur titre. */
3733
- identifiantDuTitre = `st-recapitulatif-${(compteur$5 += 1).toString()}`;
3734
- lignes = computed(() => {
3735
- const libelles = this.libelles();
3736
- const locale = this.locale();
3737
- const vus = new Set();
3738
- const lignes = [];
3739
- for (const erreur of this.erreurs()) {
3740
- const champ = erreur.fieldTree;
3741
- if (champ !== undefined) {
3742
- if (vus.has(champ))
3743
- continue;
3744
- vus.add(champ);
3745
- }
3746
- const etat = champ?.();
3747
- const liaison = etat?.formFieldBindings()[0];
3748
- lignes.push({
3749
- libelle: liaison === undefined ? null : libelleDe(liaison.element),
3750
- message: messageDErreur(erreur, libelles, locale),
3751
- aller: () => {
3752
- etat?.focusBoundControl();
3753
- },
3754
- });
3755
- }
3756
- return lignes;
3757
- }, /* @ts-ignore */
3758
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
3759
- titreLu = computed(() => this.titre() ?? this.libelles().erreursACorriger(this.lignes().length), /* @ts-ignore */
3760
- ...(ngDevMode ? [{ debugName: "titreLu" }] : /* istanbul ignore next */ []));
3761
- constructor() {
3762
- // Le focus a l'APPARITION seulement : le reprendre a chaque correction
3763
- // arracherait la personne au champ qu'elle est en train de corriger.
3764
- let visible = false;
3765
- effect(() => {
3766
- const maintenant = this.lignes().length > 0;
3767
- if (maintenant && !visible) {
3768
- afterNextRender(() => {
3769
- this.focus();
3770
- }, { injector: this.injecteur });
3771
- }
3772
- visible = maintenant;
3773
- });
3774
- }
3775
- /** A appeler apres un envoi refuse, quand le recapitulatif etait deja visible. */
3776
- focus() {
3777
- this.element.nativeElement.querySelector('.st-error-summary__cadre')?.focus();
3778
- }
3779
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StErrorSummary, deps: [], target: i0.ɵɵFactoryTarget.Component });
3780
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StErrorSummary, isStandalone: true, selector: "st-error-summary", inputs: { erreurs: { classPropertyName: "erreurs", publicName: "erreurs", isSignal: true, isRequired: true, transformFunction: null }, titre: { classPropertyName: "titre", publicName: "titre", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.hidden": "lignes().length === 0 ? \"\" : null" }, classAttribute: "st-error-summary" }, ngImport: i0, template: "@if (lignes().length > 0) {\n <div\n class=\"st-error-summary__cadre\"\n role=\"region\"\n tabindex=\"-1\"\n [attr.aria-labelledby]=\"identifiantDuTitre\"\n >\n <h2 class=\"st-error-summary__titre\" [id]=\"identifiantDuTitre\">\n <st-icon nom=\"cercle-croix\" taille=\"sm\" />\n {{ titreLu() }}\n </h2>\n <ul class=\"st-error-summary__liste\">\n @for (ligne of lignes(); track $index) {\n <li>\n @if (ligne.libelle !== null) {\n <button type=\"button\" class=\"st-error-summary__lien\" (click)=\"ligne.aller()\">\n {{ ligne.libelle }} : {{ ligne.message }}\n </button>\n } @else {\n {{ ligne.message }}\n }\n </li>\n }\n </ul>\n </div>\n}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;margin-block-end:var(--st-espacement-5)}.st-error-summary__cadre:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-error-summary__cadre{padding:var(--st-espacement-4);color:var(--st-couleur-sur-danger-discret);background:var(--st-couleur-danger-discret);border:2px solid var(--st-couleur-danger);border-radius:var(--st-rayon-md)}.st-error-summary__titre{display:flex;gap:var(--st-espacement-2);align-items:center;margin:0 0 var(--st-espacement-2);font-size:var(--st-taille-lg)}.st-error-summary__liste{padding-inline-start:var(--st-espacement-5);margin:0}.st-error-summary__lien:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-error-summary__lien{padding:0;font:inherit;color:inherit;text-align:start;text-decoration:underline;cursor:pointer;background:none;border:0}@media(forced-colors:active){.st-error-summary__cadre{border-color:CanvasText}}\n"], dependencies: [{ kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3781
- }
3782
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StErrorSummary, decorators: [{
3783
- type: Component,
3784
- args: [{ selector: 'st-error-summary', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIcon], host: {
3785
- class: 'st-error-summary',
3786
- '[attr.hidden]': 'lignes().length === 0 ? "" : null',
3787
- }, template: "@if (lignes().length > 0) {\n <div\n class=\"st-error-summary__cadre\"\n role=\"region\"\n tabindex=\"-1\"\n [attr.aria-labelledby]=\"identifiantDuTitre\"\n >\n <h2 class=\"st-error-summary__titre\" [id]=\"identifiantDuTitre\">\n <st-icon nom=\"cercle-croix\" taille=\"sm\" />\n {{ titreLu() }}\n </h2>\n <ul class=\"st-error-summary__liste\">\n @for (ligne of lignes(); track $index) {\n <li>\n @if (ligne.libelle !== null) {\n <button type=\"button\" class=\"st-error-summary__lien\" (click)=\"ligne.aller()\">\n {{ ligne.libelle }} : {{ ligne.message }}\n </button>\n } @else {\n {{ ligne.message }}\n }\n </li>\n }\n </ul>\n </div>\n}\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;margin-block-end:var(--st-espacement-5)}.st-error-summary__cadre:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-error-summary__cadre{padding:var(--st-espacement-4);color:var(--st-couleur-sur-danger-discret);background:var(--st-couleur-danger-discret);border:2px solid var(--st-couleur-danger);border-radius:var(--st-rayon-md)}.st-error-summary__titre{display:flex;gap:var(--st-espacement-2);align-items:center;margin:0 0 var(--st-espacement-2);font-size:var(--st-taille-lg)}.st-error-summary__liste{padding-inline-start:var(--st-espacement-5);margin:0}.st-error-summary__lien:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-error-summary__lien{padding:0;font:inherit;color:inherit;text-align:start;text-decoration:underline;cursor:pointer;background:none;border:0}@media(forced-colors:active){.st-error-summary__cadre{border-color:CanvasText}}\n"] }]
3788
- }], ctorParameters: () => [], propDecorators: { erreurs: [{ type: i0.Input, args: [{ isSignal: true, alias: "erreurs", required: true }] }], titre: [{ type: i0.Input, args: [{ isSignal: true, alias: "titre", required: false }] }] } });
3789
- /**
3790
- * Libelle d'un champ, lu sur l'element lie : l'attribut pose par le socle, sinon
3791
- * l'etiquette native, sinon `aria-label`.
3792
- */
3793
- function libelleDe(element) {
3794
- const socle = element.querySelector('[data-libelle]')?.getAttribute('data-libelle');
3795
- if (socle !== null && socle !== undefined)
3796
- return socle;
3797
- const etiquette = element.labels?.[0]?.textContent.trim();
3798
- if (etiquette !== undefined && etiquette !== '')
3799
- return etiquette;
3800
- return element.getAttribute('aria-label');
3801
- }
3802
-
3803
- /**
3804
- * Mot de passe, avec revelation.
3805
- *
3806
- * ```html
3807
- * <st-password-field libelle="Mot de passe" usage="creation" [regles]="regles" [formField]="compte.motDePasse" />
3808
- * ```
3809
- *
3810
- * ⚠️ **`autocomplete` selon l'usage** (SEC-2) : `current-password` a la connexion,
3811
- * `new-password` a la creation — c'est ce qui fait proposer par le gestionnaire
3812
- * un mot de passe FORT au bon endroit, et non l'ancien.
3813
- *
3814
- * ⚠️ **Le collage n'est jamais bloque** (SEC-1, NIST SP 800-63B) : l'interdire
3815
- * pousse vers des mots de passe courts, retenus de tete, et casse les
3816
- * gestionnaires de mots de passe.
3817
- *
3818
- * ⚠️ **Revele, il se remasque des que le focus quitte le champ, et a l'envoi du
3819
- * formulaire** (SEC-4) : un mot de passe laisse en clair reste lisible par-dessus
3820
- * l'epaule, sur l'ecran suivant comme sur celui-ci. L'envoi par Entree ne fait
3821
- * pas quitter le champ : d'ou l'ecoute de `submit`, qui remonte jusqu'au document.
3822
- *
3823
- * Le bouton de revelation est un bouton bascule : `aria-pressed` porte l'etat, et
3824
- * l'etiquette ne change PAS — « Masquer le mot de passe, enfonce » se lirait
3825
- * comme une double negation.
3826
- *
3827
- * ⚠️ **Aucun indicateur de « force »**, et c'est un refus. Une estimation faite
3828
- * dans le navigateur donne « fort » a `Motdepasse1!` : elle rassure a tort. Les
3829
- * regles sont affichees AVANT la saisie (UX-10), et c'est le serveur qui verifie —
3830
- * y compris contre les mots de passe deja divulgues, qu'aucune regle locale ne
3831
- * voit.
3832
- */
3833
- class StPasswordField extends ControleDeSaisie {
3834
- libelles = inject(ST_LIBELLES);
3835
- element = inject(ElementRef);
3836
- usage = input('connexion', /* @ts-ignore */
3837
- ...(ngDevMode ? [{ debugName: "usage" }] : /* istanbul ignore next */ []));
3838
- /** Regles affichees avant la saisie, comme aide du champ. */
3839
- regles = input([], /* @ts-ignore */
3840
- ...(ngDevMode ? [{ debugName: "regles" }] : /* istanbul ignore next */ []));
3841
- revele = signal(false, /* @ts-ignore */
3842
- ...(ngDevMode ? [{ debugName: "revele" }] : /* istanbul ignore next */ []));
3843
- autocomplete = computed(() => this.usage() === 'creation' ? 'new-password' : 'current-password', /* @ts-ignore */
3844
- ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
3845
- aideLue = computed(() => {
3846
- const regles = this.regles();
3847
- const aide = this.aide();
3848
- if (regles.length === 0)
3849
- return aide;
3850
- return [aide, ...regles].filter((texte) => texte !== null).join(' ');
3851
- }, /* @ts-ignore */
3852
- ...(ngDevMode ? [{ debugName: "aideLue" }] : /* istanbul ignore next */ []));
3853
- basculer() {
3854
- this.revele.update((revele) => !revele);
3855
- // Revele alors que le focus est ailleurs — clic direct sur le bouton, champ
3856
- // rempli par le gestionnaire —, il ne pourrait plus quitter le champ : rien ne
3857
- // le remasquerait. Au clavier, le focus est deja sur le bouton, et y reste.
3858
- const hote = this.element.nativeElement;
3859
- if (this.revele() && !hote.contains(hote.ownerDocument.activeElement))
3860
- this.focus();
3861
- }
3862
- surSaisie(evenement) {
3863
- this.value.set(this.texteSaisi(evenement));
3864
- }
3865
- aLEnvoi(evenement) {
3866
- const formulaire = evenement.target;
3867
- if (formulaire?.contains(this.element.nativeElement) === true)
3868
- this.revele.set(false);
3869
- }
3870
- aLaSortie(evenement) {
3871
- const suivant = evenement.relatedTarget;
3872
- if (suivant !== null && this.element.nativeElement.contains(suivant))
3873
- return;
3874
- this.revele.set(false);
3875
- }
3876
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPasswordField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3877
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StPasswordField, isStandalone: true, selector: "st-password-field", inputs: { usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, regles: { classPropertyName: "regles", publicName: "regles", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focusout": "aLaSortie($event)", "document:submit": "aLEnvoi($event)" }, classAttribute: "st-password-field" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div class=\"st-password-field__ligne\">\n <input\n #champ\n class=\"st-password-field__champ\"\n stControleDeChamp\n spellcheck=\"false\"\n autocapitalize=\"none\"\n autocorrect=\"off\"\n [type]=\"revele() ? 'text' : 'password'\"\n [value]=\"value()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton. Le nom accessible est\n impose par le type (`etiquette` est requis) et verifie par axe.\n `mousedown` sans effet par defaut : le focus reste dans le champ. Safari\n ne donne pas le focus a un bouton clique, et la sortie du champ\n remasquerait juste avant que le clic ne revele a nouveau. -->\n <button\n stIconButton\n type=\"button\"\n [icone]=\"revele() ? 'oeil-barre' : 'oeil'\"\n [etiquette]=\"libelles().afficherLeMotDePasse\"\n [enfonce]=\"revele()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"basculer()\"\n ></button>\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-password-field__ligne{display:flex;gap:var(--st-espacement-1);align-items:center}.st-password-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-password-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-password-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-password-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-password-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-password-field__champ{flex:1 1 auto}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }, { kind: "component", type: StIconButton, selector: "button[stIconButton]", inputs: ["icone", "etiquette", "variante", "taille", "enfonce"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3878
- }
3879
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPasswordField, decorators: [{
3880
- type: Component,
3881
- args: [{ selector: 'st-password-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp, StIconButton], host: {
3882
- class: 'st-password-field',
3883
- '(focusout)': 'aLaSortie($event)',
3884
- '(document:submit)': 'aLEnvoi($event)',
3885
- }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div class=\"st-password-field__ligne\">\n <input\n #champ\n class=\"st-password-field__champ\"\n stControleDeChamp\n spellcheck=\"false\"\n autocapitalize=\"none\"\n autocorrect=\"off\"\n [type]=\"revele() ? 'text' : 'password'\"\n [value]=\"value()\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton. Le nom accessible est\n impose par le type (`etiquette` est requis) et verifie par axe.\n `mousedown` sans effet par defaut : le focus reste dans le champ. Safari\n ne donne pas le focus a un bouton clique, et la sortie du champ\n remasquerait juste avant que le clic ne revele a nouveau. -->\n <button\n stIconButton\n type=\"button\"\n [icone]=\"revele() ? 'oeil-barre' : 'oeil'\"\n [etiquette]=\"libelles().afficherLeMotDePasse\"\n [enfonce]=\"revele()\"\n [disabled]=\"disabled()\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"basculer()\"\n ></button>\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-password-field__ligne{display:flex;gap:var(--st-espacement-1);align-items:center}.st-password-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-password-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-password-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-password-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-password-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-password-field__champ{flex:1 1 auto}\n"] }]
3886
- }], propDecorators: { usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], regles: [{ type: i0.Input, args: [{ isSignal: true, alias: "regles", required: false }] }] } });
3887
-
3888
- /**
3889
- * Code a usage unique : second facteur, code recu par SMS ou par courriel.
3890
- *
3891
- * ⚠️ **UN seul champ, pas six cases** (UX-9). Six champs cassent le collage, le
3892
- * remplissage automatique du code recu par SMS, et la lecture au lecteur d'ecran,
3893
- * qui annonce six champs sans nom au lieu d'un seul.
3894
- *
3895
- * ⚠️ `autocomplete="one-time-code"` : iOS et Android proposent alors le code recu,
3896
- * d'un toucher. Clavier numerique par `inputmode`, et NON par `type="number"`,
3897
- * qui retire les zeros de tete et accepte « 1e5 ».
3898
- *
3899
- * ⚠️ **Aucun envoi automatique** a la sixieme saisie : un formulaire qui part de
3900
- * lui-meme surprend (WCAG 3.2.2), et part aussi avec un code mal colle. Le code
3901
- * colle est nettoye de ses espaces et tirets — « 123 456 » —, jamais tronque.
3902
- */
3903
- class StOtpField extends ControleDeSaisie {
3904
- libelles = inject(ST_LIBELLES);
3905
- longueur = input(6, /* @ts-ignore */
3906
- ...(ngDevMode ? [{ debugName: "longueur" }] : /* istanbul ignore next */ []));
3907
- aideLue = computed(() => this.aide() ?? this.libelles().codeAChiffres(this.longueur()), /* @ts-ignore */
3908
- ...(ngDevMode ? [{ debugName: "aideLue" }] : /* istanbul ignore next */ []));
3909
- surSaisie(evenement) {
3910
- this.value.set(this.texteSaisi(evenement).replace(/[\s-]/g, ''));
3911
- }
3912
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StOtpField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3913
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StOtpField, isStandalone: true, selector: "st-otp-field", inputs: { longueur: { classPropertyName: "longueur", publicName: "longueur", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-otp-field" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-otp-field__champ\"\n stControleDeChamp\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"one-time-code\"\n spellcheck=\"false\"\n autocapitalize=\"none\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-otp-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-otp-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-otp-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-otp-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-otp-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-otp-field__champ{max-inline-size:12rem;font-family:var(--st-police-mono);font-variant-numeric:tabular-nums;letter-spacing:.3em}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3914
- }
3915
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StOtpField, decorators: [{
3916
- type: Component,
3917
- args: [{ selector: 'st-otp-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: { class: 'st-otp-field' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-otp-field__champ\"\n stControleDeChamp\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"one-time-code\"\n spellcheck=\"false\"\n autocapitalize=\"none\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-otp-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-otp-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-otp-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-otp-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-otp-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-otp-field__champ{max-inline-size:12rem;font-family:var(--st-police-mono);font-variant-numeric:tabular-nums;letter-spacing:.3em}\n"] }]
3918
- }], propDecorators: { longueur: [{ type: i0.Input, args: [{ isSignal: true, alias: "longueur", required: false }] }] } });
3919
-
3920
- /**
3921
- * Champ de recherche : un repere, un champ, un bouton.
3922
- *
3923
- * ```html
3924
- * <st-search-field libelle="Rechercher un beneficiaire" (recherche)="filtrer($event)" />
3925
- * ```
3926
- *
3927
- * ⚠️ **Un repere `search`** : la recherche se retrouve dans la liste des reperes du
3928
- * lecteur d'ecran, a cote de la navigation et du contenu.
3929
- *
3930
- * L'etiquette est lue sans etre affichee — seule exception au socle (UX-1) : la
3931
- * loupe, le bouton et la position disent deja le but du champ.
3932
- *
3933
- * ⚠️ **Jamais dans l'adresse quand le terme est personnel** (SEC-6). Chercher un
3934
- * nom, un IBAN ou un numero en `?q=` inscrit la donnee dans l'historique, dans les
3935
- * journaux des intermediaires, et dans l'en-tete `Referer` des pages suivantes.
3936
- * `recherche` emet le terme : c'est a l'appelant de ne pas le mettre dans l'URL.
3937
- */
3938
- class StSearchField extends ControleDeSaisie {
3939
- libelles = inject(ST_LIBELLES);
3940
- /** Emis a la validation — Entree ou bouton —, et non a chaque frappe. */
3941
- recherche = output();
3942
- surSaisie(evenement) {
3943
- this.value.set(this.texteSaisi(evenement));
3944
- }
3945
- lancer(evenement) {
3946
- evenement?.preventDefault();
3947
- this.recherche.emit(this.value().trim());
3948
- }
3949
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSearchField, deps: null, target: i0.ɵɵFactoryTarget.Component });
3950
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: StSearchField, isStandalone: true, selector: "st-search-field", outputs: { recherche: "recherche" }, host: { attributes: { "role": "search" }, classAttribute: "st-search-field" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n libelleMasque\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n>\n <div class=\"st-search-field__ligne\">\n <st-icon class=\"st-search-field__loupe\" nom=\"recherche\" taille=\"sm\" />\n <input\n #champ\n class=\"st-search-field__champ\"\n stControleDeChamp\n type=\"search\"\n enterkeyhint=\"search\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (input)=\"surSaisie($event)\"\n (keydown.enter)=\"lancer($event)\"\n (blur)=\"touch.emit()\"\n />\n <button stButton type=\"button\" [disabled]=\"disabled()\" (click)=\"lancer()\">\n {{ libelles().rechercher }}\n </button>\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-search-field__ligne{position:relative;display:flex;gap:var(--st-espacement-2);align-items:center}.st-search-field__loupe{position:absolute;inset-inline-start:var(--st-espacement-3);color:var(--st-couleur-texte-discret);pointer-events:none}.st-search-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-search-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-search-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-search-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-search-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-search-field__champ{flex:1 1 auto;padding-inline-start:calc(var(--st-espacement-3) * 2 + 1rem)}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }, { kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }, { kind: "component", type: StButton, selector: "button[stButton]", inputs: ["variante", "taille", "pleineLargeur", "iconeAvant", "iconeApres", "chargement", "indisponible"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3951
- }
3952
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSearchField, decorators: [{
3953
- type: Component,
3954
- args: [{ selector: 'st-search-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp, StIcon, StButton], host: { class: 'st-search-field', role: 'search' }, template: "<st-form-field\n libelleMasque\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n>\n <div class=\"st-search-field__ligne\">\n <st-icon class=\"st-search-field__loupe\" nom=\"recherche\" taille=\"sm\" />\n <input\n #champ\n class=\"st-search-field__champ\"\n stControleDeChamp\n type=\"search\"\n enterkeyhint=\"search\"\n [value]=\"value()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n (input)=\"surSaisie($event)\"\n (keydown.enter)=\"lancer($event)\"\n (blur)=\"touch.emit()\"\n />\n <button stButton type=\"button\" [disabled]=\"disabled()\" (click)=\"lancer()\">\n {{ libelles().rechercher }}\n </button>\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-search-field__ligne{position:relative;display:flex;gap:var(--st-espacement-2);align-items:center}.st-search-field__loupe{position:absolute;inset-inline-start:var(--st-espacement-3);color:var(--st-couleur-texte-discret);pointer-events:none}.st-search-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-search-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-search-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-search-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-search-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-search-field__champ{flex:1 1 auto;padding-inline-start:calc(var(--st-espacement-3) * 2 + 1rem)}\n"] }]
3955
- }], propDecorators: { recherche: [{ type: i0.Output, args: ["recherche"] }] } });
3956
-
3957
- /**
3958
- * IBAN. La valeur du modele est NORMALISEE — sans espaces, en majuscules —, et
3959
- * l'affichage groupe par quatre.
3960
- *
3961
- * ```html
3962
- * <st-iban-field libelle="IBAN du beneficiaire" [formField]="virement.iban" />
3963
- * <!-- schema : validate(chemin.iban, ({ value }) => erreurIban(value())) -->
3964
- * ```
3965
- *
3966
- * ⚠️ **Le groupement se fait a la sortie du champ, jamais pendant la frappe.**
3967
- * Reformater a chaque caractere deplace le curseur a la fin : corriger une
3968
- * erreur au milieu de 27 caracteres devient impossible, chaque frappe renvoyant a
3969
- * la fin de la ligne.
3970
- *
3971
- * ⚠️ **Aucune verification de validite ici** : elle appartient au schema, par
3972
- * `erreurIban`, pour s'afficher au bon moment et par le bon canal. Et elle reste un
3973
- * confort (SEC-5) : le serveur revalide.
3974
- *
3975
- * `autocapitalize="characters"` et correction orthographique coupee : un IBAN
3976
- * n'est pas un mot, et une correction automatique le fausserait.
3977
- */
3978
- class StIbanField extends ControleDeSaisie {
3979
- libelles = inject(ST_LIBELLES);
3980
- enSaisie = signal(false, /* @ts-ignore */
3981
- ...(ngDevMode ? [{ debugName: "enSaisie" }] : /* istanbul ignore next */ []));
3982
- brut = signal('', /* @ts-ignore */
3983
- ...(ngDevMode ? [{ debugName: "brut" }] : /* istanbul ignore next */ []));
3984
- aideLue = computed(() => this.aide() ?? this.libelles().exempleDIban, /* @ts-ignore */
3985
- ...(ngDevMode ? [{ debugName: "aideLue" }] : /* istanbul ignore next */ []));
3986
- /**
3987
- * Brut pendant la saisie ; groupe par quatre au repos. Une valeur changee de
3988
- * l'exterieur pendant la saisie — remise a zero du formulaire — l'emporte sur
3989
- * le brut, qui ne la represente plus.
3990
- */
3991
- affichage = computed(() => {
3992
- const valeur = this.value();
3993
- const brut = this.brut();
3994
- return this.enSaisie() && normaliserIban(brut) === valeur ? brut : formaterIban(valeur);
3995
- }, /* @ts-ignore */
3996
- ...(ngDevMode ? [{ debugName: "affichage" }] : /* istanbul ignore next */ []));
3997
- auFocus() {
3998
- this.brut.set(formaterIban(this.value()));
3999
- this.enSaisie.set(true);
4000
- }
4001
- surSaisie(evenement) {
4002
- const saisie = this.texteSaisi(evenement);
4003
- this.brut.set(saisie);
4004
- this.value.set(normaliserIban(saisie));
4005
- }
4006
- aLaSortie() {
4007
- this.enSaisie.set(false);
4008
- this.touch.emit();
4009
- }
4010
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StIbanField, deps: null, target: i0.ɵɵFactoryTarget.Component });
4011
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: StIbanField, isStandalone: true, selector: "st-iban-field", host: { classAttribute: "st-iban-field" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-iban-field__champ\"\n stControleDeChamp\n type=\"text\"\n autocomplete=\"off\"\n autocapitalize=\"characters\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n [value]=\"affichage()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (focus)=\"auFocus()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"aLaSortie()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-iban-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-iban-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-iban-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-iban-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-iban-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-iban-field__champ{font-family:var(--st-police-mono);font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4012
- }
4013
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StIbanField, decorators: [{
4014
- type: Component,
4015
- args: [{ selector: 'st-iban-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: { class: 'st-iban-field' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-iban-field__champ\"\n stControleDeChamp\n type=\"text\"\n autocomplete=\"off\"\n autocapitalize=\"characters\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n [value]=\"affichage()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (focus)=\"auFocus()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"aLaSortie()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-iban-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-iban-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-iban-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-iban-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-iban-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-iban-field__champ{font-family:var(--st-police-mono);font-variant-numeric:tabular-nums}\n"] }]
4016
- }] });
4017
-
4018
- /**
4019
- * Numero de telephone. La valeur du modele ne garde que les chiffres et le `+` de
4020
- * tete ; la saisie s'affiche telle qu'elle a ete tapee.
4021
- *
4022
- * ```html
4023
- * <st-phone-field libelle="Telephone portable" [formField]="contact.telephone" />
4024
- * <!-- schema : validate(chemin.telephone, ({ value }) => erreurTelephone(value())) -->
4025
- * ```
4026
- *
4027
- * ⚠️ **Aucun formatage automatique.** Le groupement des chiffres depend du pays,
4028
- * et reformater pendant la frappe renvoie le curseur en fin de ligne. La saisie
4029
- * reste celle de la personne, espaces et points compris.
4030
- *
4031
- * ⚠️ **Pas de selecteur de pays, pas de verification par pays.** Il faudrait une
4032
- * table des plans de numerotation, lourde et souvent perimee : c'est au serveur
4033
- * de dire si le numero existe (SEC-5). `erreurTelephone` ne verifie que la forme.
4034
- *
4035
- * `type="tel"`, et jamais `type="number"` : un numero n'est pas une quantite, et le
4036
- * champ numerique retire le zero de tete et le `+`.
4037
- */
4038
- class StPhoneField extends ControleDeSaisie {
4039
- autocomplete = input('tel', /* @ts-ignore */
4040
- ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
4041
- brut = signal(null, /* @ts-ignore */
4042
- ...(ngDevMode ? [{ debugName: "brut" }] : /* istanbul ignore next */ []));
4043
- /**
4044
- * La saisie telle que tapee, tant qu'elle represente encore la valeur ; une
4045
- * valeur changee de l'exterieur — remise a zero, pre-remplissage — l'emporte.
4046
- */
4047
- affichage = computed(() => {
4048
- const valeur = this.value();
4049
- const brut = this.brut();
4050
- return brut !== null && normaliserTelephone(brut) === valeur ? brut : valeur;
4051
- }, /* @ts-ignore */
4052
- ...(ngDevMode ? [{ debugName: "affichage" }] : /* istanbul ignore next */ []));
4053
- surSaisie(evenement) {
4054
- const saisie = this.texteSaisi(evenement);
4055
- this.brut.set(saisie);
4056
- this.value.set(normaliserTelephone(saisie));
4057
- }
4058
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPhoneField, deps: null, target: i0.ɵɵFactoryTarget.Component });
4059
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StPhoneField, isStandalone: true, selector: "st-phone-field", inputs: { autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-phone-field" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-phone-field__champ\"\n stControleDeChamp\n type=\"tel\"\n [attr.autocomplete]=\"autocomplete()\"\n [value]=\"affichage()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-phone-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-phone-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-phone-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-phone-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-phone-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-phone-field__champ{font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4060
- }
4061
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPhoneField, decorators: [{
4062
- type: Component,
4063
- args: [{ selector: 'st-phone-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp], host: { class: 'st-phone-field' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <input\n #champ\n class=\"st-phone-field__champ\"\n stControleDeChamp\n type=\"tel\"\n [attr.autocomplete]=\"autocomplete()\"\n [value]=\"affichage()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n (blur)=\"touch.emit()\"\n />\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-phone-field__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-phone-field__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-phone-field__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-phone-field__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-phone-field__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-phone-field__champ{font-variant-numeric:tabular-nums}\n"] }]
4064
- }], propDecorators: { autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }] } });
4065
-
4066
- /** Sans accents ni casse : « eleve » trouve « Élève ». */
4067
- function normaliserPourRecherche(texte) {
4068
- return texte
4069
- .normalize('NFD')
4070
- .replace(/[\u0300-\u036f]/g, '')
4071
- .toLocaleLowerCase()
4072
- .trim();
4073
- }
4074
- /** Options dont le libelle CONTIENT le texte cherche, sans accents ni casse. */
4075
- function filtrerOptions(options, recherche) {
4076
- const cherche = normaliserPourRecherche(recherche);
4077
- if (cherche === '')
4078
- return options;
4079
- return options.filter((option) => normaliserPourRecherche(option.libelle).includes(cherche));
4080
- }
4081
-
4082
- /**
4083
- * Liste deroulante, sur un `select` NATIF.
4084
- *
4085
- * ```html
4086
- * <st-select libelle="Pays" [options]="pays" autocomplete="country" [formField]="adresse.pays" />
4087
- * ```
4088
- *
4089
- * ⚠️ **Natif, et non une liste ARIA** (FE-4). Le `select` natif ouvre le selecteur
4090
- * du systeme sur mobile, se remplit automatiquement, se parcourt a la lettre, et
4091
- * se lit partout. Une liste recomposee en ARIA ne fait jamais mieux sur ces
4092
- * points ; elle ne se justifie que pour filtrer une longue liste — c'est
4093
- * `st-combobox` — ou choisir plusieurs valeurs — `st-multiselect`.
4094
- *
4095
- * La premiere option, vide, dit « Choisir… » : sans elle, la premiere vraie
4096
- * option paraitrait choisie alors que personne ne l'a fait. Le modele vaut
4097
- * alors `''`, que `required` refuse.
4098
- *
4099
- * `readonly` n'existe pas sur un `select` : le champ annonce `aria-readonly` et
4100
- * ignore le changement, plutot que d'etre desactive — un champ desactive sort de
4101
- * l'ordre de tabulation, et sa valeur ne se lit plus.
4102
- */
4103
- class StSelect extends ControleDeSaisie {
4104
- libelles = inject(ST_LIBELLES);
4105
- options = input.required(/* @ts-ignore */
4106
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
4107
- /** Texte de l'option vide. Par defaut : « Choisir… ». */
4108
- texteVide = input(null, /* @ts-ignore */
4109
- ...(ngDevMode ? [{ debugName: "texteVide" }] : /* istanbul ignore next */ []));
4110
- /** WCAG 1.3.5 : `country`, `sex`, `honorific-prefix`… */
4111
- autocomplete = input(null, /* @ts-ignore */
4112
- ...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
4113
- /** Options consecutives d'un meme groupe, dans l'ordre fourni. */
4114
- blocs = computed(() => {
4115
- const blocs = [];
4116
- for (const option of this.options()) {
4117
- const groupe = option.groupe ?? null;
4118
- const dernier = blocs.at(-1);
4119
- if (dernier?.groupe === groupe)
4120
- dernier.options.push(option);
4121
- else
4122
- blocs.push({ groupe, options: [option] });
4123
- }
4124
- return blocs;
4125
- }, /* @ts-ignore */
4126
- ...(ngDevMode ? [{ debugName: "blocs" }] : /* istanbul ignore next */ []));
4127
- surChoix(evenement) {
4128
- const element = evenement.target;
4129
- if (this.readonly()) {
4130
- element.value = this.value();
4131
- return;
4132
- }
4133
- this.value.set(element.value);
4134
- }
4135
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSelect, deps: null, target: i0.ɵɵFactoryTarget.Component });
4136
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StSelect, isStandalone: true, selector: "st-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, texteVide: { classPropertyName: "texteVide", publicName: "texteVide", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-select" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div class=\"st-select__cadre\">\n <select\n #champ\n class=\"st-select__champ\"\n stControleDeChamp\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\n [disabled]=\"disabled()\"\n (change)=\"surChoix($event)\"\n (blur)=\"touch.emit()\"\n >\n <option value=\"\" [selected]=\"value() === ''\">{{ texteVide() ?? libelles().choisir }}</option>\n @for (bloc of blocs(); track $index) {\n @if (bloc.groupe === null) {\n @for (option of bloc.options; track option.valeur) {\n <option\n [value]=\"option.valeur\"\n [selected]=\"option.valeur === value()\"\n [disabled]=\"option.desactivee === true\"\n >\n {{ option.libelle }}\n </option>\n }\n } @else {\n <optgroup [label]=\"bloc.groupe\">\n @for (option of bloc.options; track option.valeur) {\n <option\n [value]=\"option.valeur\"\n [selected]=\"option.valeur === value()\"\n [disabled]=\"option.desactivee === true\"\n >\n {{ option.libelle }}\n </option>\n }\n </optgroup>\n }\n }\n </select>\n <st-icon class=\"st-select__chevron\" nom=\"chevron-bas\" taille=\"sm\" />\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-select__cadre{position:relative}.st-select__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-select__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-select__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-select__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-select__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-select__champ{padding-inline-end:calc(var(--st-espacement-3) * 2 + 1rem);cursor:pointer;appearance:none}.st-select__chevron{position:absolute;inset-block-start:50%;inset-inline-end:var(--st-espacement-3);color:var(--st-couleur-texte-discret);pointer-events:none;transform:translateY(-50%)}@media(forced-colors:active){.st-select__chevron{color:CanvasText}}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }, { kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4137
- }
4138
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSelect, decorators: [{
4139
- type: Component,
4140
- args: [{ selector: 'st-select', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp, StIcon], host: { class: 'st-select' }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div class=\"st-select__cadre\">\n <select\n #champ\n class=\"st-select__champ\"\n stControleDeChamp\n [attr.name]=\"name() || null\"\n [attr.autocomplete]=\"autocomplete()\"\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\n [disabled]=\"disabled()\"\n (change)=\"surChoix($event)\"\n (blur)=\"touch.emit()\"\n >\n <option value=\"\" [selected]=\"value() === ''\">{{ texteVide() ?? libelles().choisir }}</option>\n @for (bloc of blocs(); track $index) {\n @if (bloc.groupe === null) {\n @for (option of bloc.options; track option.valeur) {\n <option\n [value]=\"option.valeur\"\n [selected]=\"option.valeur === value()\"\n [disabled]=\"option.desactivee === true\"\n >\n {{ option.libelle }}\n </option>\n }\n } @else {\n <optgroup [label]=\"bloc.groupe\">\n @for (option of bloc.options; track option.valeur) {\n <option\n [value]=\"option.valeur\"\n [selected]=\"option.valeur === value()\"\n [disabled]=\"option.desactivee === true\"\n >\n {{ option.libelle }}\n </option>\n }\n </optgroup>\n }\n }\n </select>\n <st-icon class=\"st-select__chevron\" nom=\"chevron-bas\" taille=\"sm\" />\n </div>\n</st-form-field>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-select__cadre{position:relative}.st-select__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-select__champ{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-select__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-select__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-select__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-select__champ{padding-inline-end:calc(var(--st-espacement-3) * 2 + 1rem);cursor:pointer;appearance:none}.st-select__chevron{position:absolute;inset-block-start:50%;inset-inline-end:var(--st-espacement-3);color:var(--st-couleur-texte-discret);pointer-events:none;transform:translateY(-50%)}@media(forced-colors:active){.st-select__chevron{color:CanvasText}}\n"] }]
4141
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], texteVide: [{ type: i0.Input, args: [{ isSignal: true, alias: "texteVide", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }] } });
4142
-
4143
- let compteur$4 = 0;
4144
- /** Un lundi connu, pour nommer les jours de la semaine sans horloge. */
4145
- const LUNDI_DE_REFERENCE = '2006-01-02';
4146
- /** « Aujourd'hui » dans un fuseau : la date du poste n'est pas celle de la personne. */
4147
- function dateDuJour(fuseau) {
4148
- const parties = new Intl.DateTimeFormat('en-CA', {
4149
- timeZone: fuseau,
4150
- year: 'numeric',
4151
- month: '2-digit',
4152
- day: '2-digit',
4153
- }).formatToParts(new Date());
4154
- const partie = (type) => parties.find((p) => p.type === type)?.value ?? '';
4155
- return `${partie('year')}-${partie('month')}-${partie('day')}`;
4156
- }
4157
- /**
4158
- * Calendrier d'un mois, pour choisir une date.
4159
- *
4160
- * ```html
4161
- * <st-calendar [selection]="depart()" dateMin="2026-09-17" (choix)="depart.set($event)" />
4162
- * ```
4163
- *
4164
- * ⚠️ **Une aide, jamais la seule voie.** Dans `st-date-field`, le calendrier
4165
- * complete un champ texte : une date de naissance se tape plus vite qu'elle ne se
4166
- * cherche mois par mois, et un calendrier reste penible au lecteur d'ecran.
4167
- *
4168
- * ⚠️ **Le clavier navigue de DATE en date, pas de cellule en cellule** (decision
4169
- * D7 de l'audit de la vague 3). Fleche droite le 30 septembre mene au 1er
4170
- * octobre ; Page suivante change de mois, Maj + Page d'annee ; Debut et Fin vont
4171
- * aux bords de la semaine de la LOCALE. La grille d'Angular Aria deplace un focus
4172
- * entre cellules affichees : il aurait fallu la contourner sur chaque touche qui
4173
- * compte, et elle aurait fait entrer le CDK dans les formulaires.
4174
- *
4175
- * Semaine commencant au jour de la locale, noms de jours et de mois dans la
4176
- * langue courante, « aujourd'hui » dans le fuseau de la personne
4177
- * (`FUSEAU_PAR_DEFAUT`) et non celui du poste.
4178
- */
4179
- class StCalendar {
4180
- libelles = inject(ST_LIBELLES);
4181
- locale = inject(ST_LOCALE);
4182
- element = inject(ElementRef);
4183
- injecteur = inject(Injector);
4184
- aujourdHui = dateDuJour(inject(FUSEAU_PAR_DEFAUT));
4185
- selection = input(null, /* @ts-ignore */
4186
- ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
4187
- dateMin = input(null, /* @ts-ignore */
4188
- ...(ngDevMode ? [{ debugName: "dateMin" }] : /* istanbul ignore next */ []));
4189
- dateMax = input(null, /* @ts-ignore */
4190
- ...(ngDevMode ? [{ debugName: "dateMax" }] : /* istanbul ignore next */ []));
4191
- /** Periode a faire ressortir, pour un choix de plage. */
4192
- plage = input(null, /* @ts-ignore */
4193
- ...(ngDevMode ? [{ debugName: "plage" }] : /* istanbul ignore next */ []));
4194
- choix = output();
4195
- /** Echap : le conteneur decide de fermer, et ou rendre le focus. */
4196
- fermeture = output();
4197
- identifiants = (() => {
4198
- const base = `st-calendrier-${(compteur$4 += 1).toString()}`;
4199
- return { titre: `${base}-titre`, aide: `${base}-aide` };
4200
- })();
4201
- /** La date qui porte le focus. Suit la selection, bornee. */
4202
- active = linkedSignal(() => bornerDate(this.selection() ?? this.aujourdHui, this.dateMin(), this.dateMax()), /* @ts-ignore */
4203
- ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
4204
- premierJour = computed(() => premierJourDeLaSemaine(this.locale()), /* @ts-ignore */
4205
- ...(ngDevMode ? [{ debugName: "premierJour" }] : /* istanbul ignore next */ []));
4206
- titre = computed(() => {
4207
- const [annee, mois] = this.active().split('-').map(Number);
4208
- return new Intl.DateTimeFormat(this.locale(), {
4209
- month: 'long',
4210
- year: 'numeric',
4211
- timeZone: 'UTC',
4212
- }).format(Date.UTC(annee ?? 2000, (mois ?? 1) - 1, 1));
4213
- }, /* @ts-ignore */
4214
- ...(ngDevMode ? [{ debugName: "titre" }] : /* istanbul ignore next */ []));
4215
- joursDeLaSemaine = computed(() => {
4216
- const locale = this.locale();
4217
- const court = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'UTC' });
4218
- const long = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'UTC' });
4219
- return Array.from({ length: 7 }, (_, index) => {
4220
- const date = ajouterJours(LUNDI_DE_REFERENCE, (this.premierJour() - 1 + index) % 7);
4221
- const instant = Date.parse(`${date}T00:00:00Z`);
4222
- return { court: court.format(instant), long: long.format(instant) };
4223
- });
4224
- }, /* @ts-ignore */
4225
- ...(ngDevMode ? [{ debugName: "joursDeLaSemaine" }] : /* istanbul ignore next */ []));
4226
- semaines = computed(() => {
4227
- const active = this.active();
4228
- const [annee, mois] = active.split('-').map(Number);
4229
- const locale = this.locale();
4230
- const lecture = new Intl.DateTimeFormat(locale, { dateStyle: 'full', timeZone: 'UTC' });
4231
- const numero = new Intl.NumberFormat(locale);
4232
- const selection = this.selection();
4233
- const debut = this.plage()?.debut ?? null;
4234
- const fin = this.plage()?.fin ?? null;
4235
- const min = this.dateMin();
4236
- const max = this.dateMax();
4237
- const prefixe = active.slice(0, 7);
4238
- return semainesDuMois(annee ?? 2000, mois ?? 1, this.premierJour()).map((semaine) => semaine.map((date) => ({
4239
- date,
4240
- horsDuMois: !date.startsWith(prefixe),
4241
- numero: numero.format(Number(date.slice(8))),
4242
- nom: lecture.format(Date.parse(`${date}T00:00:00Z`)),
4243
- horsBornes: (min !== null && date < min) || (max !== null && date > max),
4244
- choisi: date === selection || date === debut || date === fin,
4245
- dansLaPlage: debut !== null && fin !== null && date > debut && date < fin,
4246
- aujourdHui: date === this.aujourdHui,
4247
- })));
4248
- }, /* @ts-ignore */
4249
- ...(ngDevMode ? [{ debugName: "semaines" }] : /* istanbul ignore next */ []));
4250
- moisPrecedentPossible = computed(() => {
4251
- const min = this.dateMin();
4252
- return min === null || this.active().slice(0, 7) > min.slice(0, 7);
4253
- }, /* @ts-ignore */
4254
- ...(ngDevMode ? [{ debugName: "moisPrecedentPossible" }] : /* istanbul ignore next */ []));
4255
- moisSuivantPossible = computed(() => {
4256
- const max = this.dateMax();
4257
- return max === null || this.active().slice(0, 7) < max.slice(0, 7);
4258
- }, /* @ts-ignore */
4259
- ...(ngDevMode ? [{ debugName: "moisSuivantPossible" }] : /* istanbul ignore next */ []));
4260
- estActive(date) {
4261
- return date === this.active();
4262
- }
4263
- /** Amene le focus sur la date active. */
4264
- focus() {
4265
- this.element.nativeElement
4266
- .querySelector('button[data-date][tabindex="0"]')
4267
- ?.focus();
4268
- }
4269
- changerDeMois(ecart) {
4270
- this.active.set(bornerDate(ajouterMois(this.active(), ecart), this.dateMin(), this.dateMax()));
4271
- }
4272
- choisir(jour) {
4273
- if (jour.horsBornes)
4274
- return;
4275
- this.active.set(jour.date);
4276
- this.choix.emit(jour.date);
4277
- }
4278
- surTouche(evenement) {
4279
- const active = this.active();
4280
- const droiteAGauche = getComputedStyle(this.element.nativeElement).direction === 'rtl';
4281
- const recul = (jourDeLaSemaine(active) - this.premierJour() + 7) % 7;
4282
- const mois = evenement.shiftKey ? 12 : 1;
4283
- const cibles = {
4284
- ArrowRight: () => ajouterJours(active, droiteAGauche ? -1 : 1),
4285
- ArrowLeft: () => ajouterJours(active, droiteAGauche ? 1 : -1),
4286
- ArrowDown: () => ajouterJours(active, 7),
4287
- ArrowUp: () => ajouterJours(active, -7),
4288
- Home: () => ajouterJours(active, -recul),
4289
- End: () => ajouterJours(active, 6 - recul),
4290
- PageDown: () => ajouterMois(active, mois),
4291
- PageUp: () => ajouterMois(active, -mois),
4292
- };
4293
- if (evenement.key === 'Escape') {
4294
- evenement.preventDefault();
4295
- this.fermeture.emit();
4296
- return;
4297
- }
4298
- // Les fleches ne deplacent la date que depuis la grille : sur les boutons de
4299
- // mois, elles gardent leur sens pour le navigateur et le lecteur d'ecran.
4300
- const depuisLaGrille = evenement.target.hasAttribute('data-date');
4301
- const cible = cibles[evenement.key];
4302
- if (cible === undefined || !depuisLaGrille)
4303
- return;
4304
- evenement.preventDefault();
4305
- this.active.set(bornerDate(cible(), this.dateMin(), this.dateMax()));
4306
- afterNextRender(() => {
4307
- this.focus();
4308
- }, { injector: this.injecteur });
4309
- }
4310
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
4311
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StCalendar, isStandalone: true, selector: "st-calendar", inputs: { selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, dateMin: { classPropertyName: "dateMin", publicName: "dateMin", isSignal: true, isRequired: false, transformFunction: null }, dateMax: { classPropertyName: "dateMax", publicName: "dateMax", isSignal: true, isRequired: false, transformFunction: null }, plage: { classPropertyName: "plage", publicName: "plage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { choix: "choix", fermeture: "fermeture" }, host: { listeners: { "keydown": "surTouche($event)" }, classAttribute: "st-calendar" }, ngImport: i0, template: "<div class=\"st-calendar__entete\">\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n stIconButton\n type=\"button\"\n icone=\"chevron-gauche\"\n [etiquette]=\"libelles().moisPrecedent\"\n [disabled]=\"!moisPrecedentPossible()\"\n (click)=\"changerDeMois(-1)\"\n ></button>\n <!-- Region vivante : changer de mois a la souris ne deplace pas le focus, et le\n nouveau mois doit se dire. -->\n <p class=\"st-calendar__titre\" aria-live=\"polite\" [id]=\"identifiants.titre\">{{ titre() }}</p>\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n stIconButton\n type=\"button\"\n icone=\"chevron-droite\"\n [etiquette]=\"libelles().moisSuivant\"\n [disabled]=\"!moisSuivantPossible()\"\n (click)=\"changerDeMois(1)\"\n ></button>\n</div>\n\n<table\n class=\"st-calendar__grille\"\n role=\"grid\"\n [attr.aria-labelledby]=\"identifiants.titre\"\n [attr.aria-describedby]=\"identifiants.aide\"\n>\n <thead>\n <tr>\n @for (jour of joursDeLaSemaine(); track $index) {\n <th class=\"st-calendar__jour-de-semaine\" scope=\"col\" [attr.abbr]=\"jour.long\">\n {{ jour.court }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (semaine of semaines(); track semaine[0]?.date) {\n <tr>\n @for (jour of semaine; track jour.date) {\n @if (jour.horsDuMois) {\n <td class=\"st-calendar__vide\"></td>\n } @else {\n <td [attr.aria-selected]=\"jour.choisi ? 'true' : null\">\n <button\n class=\"st-calendar__jour\"\n type=\"button\"\n [attr.data-date]=\"jour.date\"\n [attr.data-plage]=\"jour.dansLaPlage ? '' : null\"\n [attr.tabindex]=\"estActive(jour.date) ? 0 : -1\"\n [attr.aria-label]=\"jour.nom\"\n [attr.aria-current]=\"jour.aujourdHui ? 'date' : null\"\n [attr.aria-disabled]=\"jour.horsBornes ? 'true' : null\"\n (click)=\"choisir(jour)\"\n >\n {{ jour.numero }}\n </button>\n </td>\n }\n }\n </tr>\n }\n </tbody>\n</table>\n\n<p class=\"st-calendar__aide\" [id]=\"identifiants.aide\">{{ libelles().aideDuCalendrier }}</p>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block;padding:var(--st-espacement-3);color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-md)}.st-calendar__entete{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;margin-block-end:var(--st-espacement-2)}.st-calendar__titre{margin:0;font-weight:var(--st-graisse-semi);text-transform:capitalize}.st-calendar__grille{border-collapse:collapse}.st-calendar__jour-de-semaine{padding-block-end:var(--st-espacement-1);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret);text-align:center}.st-calendar__jour:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-calendar__jour{inline-size:max(var(--st-calendrier-jour-taille, var(--st-cible-tactile-min)),var(--st-cible-min));block-size:max(var(--st-calendrier-jour-taille, var(--st-cible-tactile-min)),var(--st-cible-min));font:inherit;font-variant-numeric:tabular-nums;color:inherit;cursor:pointer;background:transparent;border:1px solid transparent;border-radius:var(--st-rayon-sm)}@media(hover:hover){.st-calendar__jour:hover:not([aria-disabled=true]){background:var(--st-couleur-surface-enfoncee)}}.st-calendar__jour[aria-current=date]{font-weight:var(--st-graisse-semi);border-color:var(--st-couleur-bordure-forte)}.st-calendar__jour[data-plage]{background:var(--st-couleur-surface-enfoncee)}.st-calendar__jour[aria-disabled=true]{color:var(--st-couleur-texte-discret);text-decoration:line-through;cursor:not-allowed}[aria-selected=true]>.st-calendar__jour:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px;box-shadow:0 0 0 2px var(--st-couleur-surface)}[aria-selected=true]>.st-calendar__jour{font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire)}.st-calendar__aide{max-inline-size:20rem;margin:var(--st-espacement-2) 0 0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}@media(forced-colors:active){[aria-selected=true]>.st-calendar__jour{forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"], dependencies: [{ kind: "component", type: StIconButton, selector: "button[stIconButton]", inputs: ["icone", "etiquette", "variante", "taille", "enfonce"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4312
- }
4313
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCalendar, decorators: [{
4314
- type: Component,
4315
- args: [{ selector: 'st-calendar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIconButton], host: {
4316
- class: 'st-calendar',
4317
- '(keydown)': 'surTouche($event)',
4318
- }, template: "<div class=\"st-calendar__entete\">\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n stIconButton\n type=\"button\"\n icone=\"chevron-gauche\"\n [etiquette]=\"libelles().moisPrecedent\"\n [disabled]=\"!moisPrecedentPossible()\"\n (click)=\"changerDeMois(-1)\"\n ></button>\n <!-- Region vivante : changer de mois a la souris ne deplace pas le focus, et le\n nouveau mois doit se dire. -->\n <p class=\"st-calendar__titre\" aria-live=\"polite\" [id]=\"identifiants.titre\">{{ titre() }}</p>\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n stIconButton\n type=\"button\"\n icone=\"chevron-droite\"\n [etiquette]=\"libelles().moisSuivant\"\n [disabled]=\"!moisSuivantPossible()\"\n (click)=\"changerDeMois(1)\"\n ></button>\n</div>\n\n<table\n class=\"st-calendar__grille\"\n role=\"grid\"\n [attr.aria-labelledby]=\"identifiants.titre\"\n [attr.aria-describedby]=\"identifiants.aide\"\n>\n <thead>\n <tr>\n @for (jour of joursDeLaSemaine(); track $index) {\n <th class=\"st-calendar__jour-de-semaine\" scope=\"col\" [attr.abbr]=\"jour.long\">\n {{ jour.court }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (semaine of semaines(); track semaine[0]?.date) {\n <tr>\n @for (jour of semaine; track jour.date) {\n @if (jour.horsDuMois) {\n <td class=\"st-calendar__vide\"></td>\n } @else {\n <td [attr.aria-selected]=\"jour.choisi ? 'true' : null\">\n <button\n class=\"st-calendar__jour\"\n type=\"button\"\n [attr.data-date]=\"jour.date\"\n [attr.data-plage]=\"jour.dansLaPlage ? '' : null\"\n [attr.tabindex]=\"estActive(jour.date) ? 0 : -1\"\n [attr.aria-label]=\"jour.nom\"\n [attr.aria-current]=\"jour.aujourdHui ? 'date' : null\"\n [attr.aria-disabled]=\"jour.horsBornes ? 'true' : null\"\n (click)=\"choisir(jour)\"\n >\n {{ jour.numero }}\n </button>\n </td>\n }\n }\n </tr>\n }\n </tbody>\n</table>\n\n<p class=\"st-calendar__aide\" [id]=\"identifiants.aide\">{{ libelles().aideDuCalendrier }}</p>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block;padding:var(--st-espacement-3);color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-md)}.st-calendar__entete{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;margin-block-end:var(--st-espacement-2)}.st-calendar__titre{margin:0;font-weight:var(--st-graisse-semi);text-transform:capitalize}.st-calendar__grille{border-collapse:collapse}.st-calendar__jour-de-semaine{padding-block-end:var(--st-espacement-1);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret);text-align:center}.st-calendar__jour:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-calendar__jour{inline-size:max(var(--st-calendrier-jour-taille, var(--st-cible-tactile-min)),var(--st-cible-min));block-size:max(var(--st-calendrier-jour-taille, var(--st-cible-tactile-min)),var(--st-cible-min));font:inherit;font-variant-numeric:tabular-nums;color:inherit;cursor:pointer;background:transparent;border:1px solid transparent;border-radius:var(--st-rayon-sm)}@media(hover:hover){.st-calendar__jour:hover:not([aria-disabled=true]){background:var(--st-couleur-surface-enfoncee)}}.st-calendar__jour[aria-current=date]{font-weight:var(--st-graisse-semi);border-color:var(--st-couleur-bordure-forte)}.st-calendar__jour[data-plage]{background:var(--st-couleur-surface-enfoncee)}.st-calendar__jour[aria-disabled=true]{color:var(--st-couleur-texte-discret);text-decoration:line-through;cursor:not-allowed}[aria-selected=true]>.st-calendar__jour:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px;box-shadow:0 0 0 2px var(--st-couleur-surface)}[aria-selected=true]>.st-calendar__jour{font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire)}.st-calendar__aide{max-inline-size:20rem;margin:var(--st-espacement-2) 0 0;font-size:var(--st-taille-sm);color:var(--st-couleur-texte-discret)}@media(forced-colors:active){[aria-selected=true]>.st-calendar__jour{forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"] }]
4319
- }], propDecorators: { selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], dateMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateMin", required: false }] }], dateMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateMax", required: false }] }], plage: [{ type: i0.Input, args: [{ isSignal: true, alias: "plage", required: false }] }], choix: [{ type: i0.Output, args: ["choix"] }], fermeture: [{ type: i0.Output, args: ["fermeture"] }] } });
4320
-
4321
- /**
4322
- * Cle d'un fichier choisi : deux choix du meme fichier ne l'ajoutent pas deux
4323
- * fois, et l'application s'en sert pour dire ou en est chaque televersement.
4324
- */
4325
- function cleDeFichier(fichier) {
4326
- return `${fichier.name}|${fichier.size.toString()}|${fichier.lastModified.toString()}`;
4327
- }
4328
- /**
4329
- * Choix de fichiers, sur un `input type="file"` natif.
4330
- *
4331
- * ```html
4332
- * <st-file-upload libelle="Justificatifs" accept=".pdf, image/*" [tailleMax]="5_000_000" multiple
4333
- * [formField]="dossier.pieces" />
4334
- * <!-- schema : validate(chemin.pieces, ({ value }) => erreursDeFichiers(value(), regles)) -->
4335
- * ```
4336
- *
4337
- * Le modele est la liste des `File` choisis. L'envoi appartient a l'application,
4338
- * par `@starterlib/core` : le composant choisit, il ne televerse pas.
4339
- *
4340
- * ⚠️ **L'element natif reste la voie principale** (FE-4) : il ouvre le selecteur
4341
- * du systeme, l'appareil photo sur mobile, et se commande au clavier. Il est
4342
- * masque visuellement, pas retire : le bouton affiche est son etiquette. Le
4343
- * depot par glisser est un complement a la souris, jamais la seule voie.
4344
- *
4345
- * ⚠️ **Aucun apercu d'image** (SEC-7). Un apercu passe par une adresse `blob:`
4346
- * que la politique de securite du contenu doit autoriser, et un SVG affiche peut
4347
- * porter du script. Le nom et la taille suffisent a reconnaitre un fichier.
4348
- *
4349
- * ⚠️ **`accept`, `tailleMax` et `nombreMax` sont un confort** (SEC-5) : ils
4350
- * s'affichent en aide et filtrent le selecteur du systeme, mais la verification
4351
- * est dans le schema — `erreursDeFichiers` —, et le serveur relit le contenu.
4352
- *
4353
- * Chaque changement de la liste s'annonce : « 2 fichiers choisis ». Un fichier
4354
- * retire rend le focus au bouton de retrait suivant, ou au choix de fichiers : le
4355
- * focus ne tombe jamais sur le document.
4356
- *
4357
- * ⚠️ **Il ne televerse pas.** `etats` dit ou en est chaque fichier, et le bouton
4358
- * de la ligne devient « Annuler » pendant l'envoi : le trajet — declaration, depot
4359
- * pre-signe, confirmation antivirus — vit dans `StTeleversement`
4360
- * (`@starterlib/core`), seul a parler au reseau.
4361
- */
4362
- class StFileUpload extends ContratDeChamp {
4363
- libelles = inject(ST_LIBELLES);
4364
- locale = inject(ST_LOCALE);
4365
- element = inject(ElementRef);
4366
- injecteur = inject(Injector);
4367
- value = model([], /* @ts-ignore */
4368
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4369
- /** Attribut `accept` natif : `.pdf, image/*`. */
4370
- accept = input('', /* @ts-ignore */
4371
- ...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
4372
- multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4373
- /** En octets. Affiche en aide ; la verification est dans le schema. */
4374
- tailleMax = input(null, /* @ts-ignore */
4375
- ...(ngDevMode ? [{ debugName: "tailleMax" }] : /* istanbul ignore next */ []));
4376
- /**
4377
- * Ou en est chaque fichier, par `cleDeFichier`. Le composant AFFICHE ce trajet,
4378
- * il ne le fait pas : `StTeleversement` de `@starterlib/core` s'en charge.
4379
- */
4380
- etats = input(null, /* @ts-ignore */
4381
- ...(ngDevMode ? [{ debugName: "etats" }] : /* istanbul ignore next */ []));
4382
- annonce = new AnnonceLocale(inject(DestroyRef));
4383
- survol = signal(false, /* @ts-ignore */
4384
- ...(ngDevMode ? [{ debugName: "survol" }] : /* istanbul ignore next */ []));
4385
- aideLue = computed(() => {
4386
- const libelles = this.libelles();
4387
- const textes = [this.aide()];
4388
- if (this.accept().trim() !== '')
4389
- textes.push(libelles.typesAcceptes(this.accept()));
4390
- const taille = this.tailleMax();
4391
- if (taille !== null)
4392
- textes.push(libelles.tailleMaximale(formaterTaille(taille, this.locale())));
4393
- const aide = textes.filter((texte) => texte !== null).join(' ');
4394
- return aide === '' ? null : aide;
4395
- }, /* @ts-ignore */
4396
- ...(ngDevMode ? [{ debugName: "aideLue" }] : /* istanbul ignore next */ []));
4397
- lignes = computed(() => {
4398
- const locale = this.locale();
4399
- const etats = this.etats();
4400
- return this.value().map((fichier) => {
4401
- const cle = cleDeFichier(fichier);
4402
- return {
4403
- fichier,
4404
- cle,
4405
- taille: formaterTaille(fichier.size, locale),
4406
- etat: etats?.get(cle) ?? null,
4407
- };
4408
- });
4409
- }, /* @ts-ignore */
4410
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
4411
- surChoix(evenement) {
4412
- const element = evenement.target;
4413
- this.ajouter(Array.from(element.files ?? []));
4414
- // Vide, pour qu'un second choix du meme fichier declenche encore `change`.
4415
- element.value = '';
4416
- }
4417
- surSurvol(evenement) {
4418
- if (this.disabled() || this.readonly())
4419
- return;
4420
- evenement.preventDefault();
4421
- this.survol.set(true);
4422
- }
4423
- surDepot(evenement) {
4424
- evenement.preventDefault();
4425
- this.survol.set(false);
4426
- if (this.disabled() || this.readonly())
4427
- return;
4428
- this.ajouter(Array.from(evenement.dataTransfer?.files ?? []));
4429
- }
4430
- /** Emis quand la personne interrompt un envoi : c'est l'application qui annule. */
4431
- annulation = output();
4432
- retirer(indice) {
4433
- const restants = this.value().filter((_, position) => position !== indice);
4434
- this.value.set(restants);
4435
- this.annonce.annoncer(this.libelles().nombreDeFichiers(restants.length));
4436
- afterNextRender(() => {
4437
- const boutons = this.element.nativeElement.querySelectorAll('.st-file-upload__retrait');
4438
- const suivant = boutons[Math.min(indice, boutons.length - 1)];
4439
- if (suivant !== undefined)
4440
- suivant.focus();
4441
- else
4442
- this.focus();
4443
- }, { injector: this.injecteur });
4444
- }
4445
- aLaSortie(evenement) {
4446
- const suivant = evenement.relatedTarget;
4447
- if (suivant !== null && this.element.nativeElement.contains(suivant))
4448
- return;
4449
- this.touch.emit();
4450
- }
4451
- ajouter(fichiers) {
4452
- if (fichiers.length === 0 || this.disabled() || this.readonly())
4453
- return;
4454
- let liste;
4455
- if (this.multiple()) {
4456
- const connus = new Set(this.value().map(cleDeFichier));
4457
- liste = [
4458
- ...this.value(),
4459
- ...fichiers.filter((fichier) => !connus.has(cleDeFichier(fichier))),
4460
- ];
4461
- }
4462
- else {
4463
- liste = fichiers.slice(0, 1);
4464
- }
4465
- this.value.set(liste);
4466
- // Le choix est complet : ses erreurs — taille, type — se montrent tout de suite.
4467
- this.touch.emit();
4468
- this.annonce.annoncer(this.libelles().nombreDeFichiers(liste.length));
4469
- }
4470
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFileUpload, deps: null, target: i0.ɵɵFactoryTarget.Component });
4471
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StFileUpload, isStandalone: true, selector: "st-file-upload", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, tailleMax: { classPropertyName: "tailleMax", publicName: "tailleMax", isSignal: true, isRequired: false, transformFunction: null }, etats: { classPropertyName: "etats", publicName: "etats", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", annulation: "annulation" }, host: { listeners: { "focusout": "aLaSortie($event)" }, classAttribute: "st-file-upload" }, usesInheritance: true, ngImport: i0, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div\n class=\"st-file-upload__zone\"\n [attr.data-survol]=\"survol() ? '' : null\"\n [attr.data-desactive]=\"disabled() ? '' : null\"\n (dragover)=\"surSurvol($event)\"\n (dragleave)=\"survol.set(false)\"\n (drop)=\"surDepot($event)\"\n >\n <input\n #champ\n class=\"st-file-upload__natif\"\n stControleDeChamp\n type=\"file\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"surChoix($event)\"\n />\n <!-- Voie de la souris vers le champ natif. Masque au lecteur d'ecran et hors\n tabulation : le clavier et le lecteur d'ecran passent par le champ natif,\n deja nomme par l'etiquette du champ. -->\n <span class=\"st-file-upload__bouton\" aria-hidden=\"true\" (click)=\"champ.click()\">\n <st-icon nom=\"televerser\" taille=\"sm\" />\n {{ multiple() ? libelles().choisirDesFichiers : libelles().choisirUnFichier }}\n </span>\n @if (multiple()) {\n <span class=\"st-file-upload__depot\" aria-hidden=\"true\">{{ libelles().ouDeposerIci }}</span>\n }\n </div>\n\n @if (lignes().length > 0) {\n <ul class=\"st-file-upload__liste\">\n @for (ligne of lignes(); track ligne.cle; let indice = $index) {\n <li class=\"st-file-upload__fichier\" [attr.data-etape]=\"ligne.etat?.etape\">\n <st-icon nom=\"fichier\" taille=\"sm\" />\n <span class=\"st-file-upload__nom\">{{ ligne.fichier.name }}</span>\n <span class=\"st-file-upload__taille\">{{ ligne.taille }}</span>\n\n @if (ligne.etat; as etat) {\n @switch (etat.etape) {\n @case ('en-cours') {\n <st-progress\n class=\"st-file-upload__progression\"\n [valeur]=\"etat.progression ?? null\"\n [etiquette]=\"libelles().televersementDe(ligne.fichier.name)\"\n />\n }\n @case ('depose') {\n <span class=\"st-file-upload__etat\">\n <st-icon nom=\"cercle-coche\" taille=\"sm\" ton=\"succes\" />\n {{ libelles().fichierDepose(ligne.fichier.name) }}\n </span>\n }\n @case ('refuse') {\n <span class=\"st-file-upload__etat\" data-refuse>\n <st-icon nom=\"triangle-alerte\" taille=\"sm\" ton=\"danger\" />\n {{ etat.message ?? libelles().fichierRefuse(ligne.fichier.name) }}\n </span>\n }\n }\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n class=\"st-file-upload__retrait\"\n stIconButton\n type=\"button\"\n taille=\"sm\"\n [icone]=\"ligne.etat?.etape === 'en-cours' ? 'croix' : 'corbeille'\"\n [etiquette]=\"\n ligne.etat?.etape === 'en-cours'\n ? libelles().annulerLeTeleversement(ligne.fichier.name)\n : libelles().retirerLeFichier(ligne.fichier.name)\n \"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"\n ligne.etat?.etape === 'en-cours' ? annulation.emit(ligne.fichier) : retirer(indice)\n \"\n ></button>\n </li>\n }\n </ul>\n }\n</st-form-field>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-file-upload__zone{position:relative;display:flex;flex-wrap:wrap;gap:var(--st-espacement-2) var(--st-espacement-3);align-items:center;padding:var(--st-espacement-3);border:1px dashed var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-md)}.st-file-upload__zone[data-survol]{background:var(--st-couleur-surface-enfoncee);border-style:solid}.st-file-upload__natif{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-file-upload__bouton{display:inline-flex;gap:var(--st-espacement-2);align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-4);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm)}.st-file-upload__natif:focus-visible+.st-file-upload__bouton{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}[aria-invalid=true]+.st-file-upload__bouton{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-file-upload__natif:disabled+.st-file-upload__bouton{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-file-upload__depot{color:var(--st-couleur-texte-discret)}.st-file-upload__liste{display:grid;gap:var(--st-espacement-1);padding:0;margin:var(--st-espacement-2) 0 0;list-style:none}.st-file-upload__fichier{display:flex;gap:var(--st-espacement-2);align-items:center}.st-file-upload__nom{min-inline-size:0;overflow-wrap:anywhere}.st-file-upload__progression{flex:1 1 8rem;max-inline-size:12rem}.st-file-upload__etat{display:inline-flex;gap:var(--st-espacement-1);align-items:center;color:var(--st-couleur-texte-discret)}.st-file-upload__etat[data-refuse]{color:var(--st-couleur-danger)}.st-file-upload__taille{flex:none;color:var(--st-couleur-texte-discret);font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: StFormField, selector: "st-form-field", inputs: ["libelle", "aide", "complement", "erreurs", "afficherErreurs", "obligatoire", "disposition", "libelleMasque"] }, { kind: "directive", type: StControleDeChamp, selector: "[stControleDeChamp]" }, { kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }, { kind: "component", type: StIconButton, selector: "button[stIconButton]", inputs: ["icone", "etiquette", "variante", "taille", "enfonce"] }, { kind: "component", type: StProgress, selector: "st-progress", inputs: ["valeur", "max", "etiquette", "ton", "epaisseur", "texteDeValeur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4472
- }
4473
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFileUpload, decorators: [{
4474
- type: Component,
4475
- args: [{ selector: 'st-file-upload', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StFormField, StControleDeChamp, StIcon, StIconButton, StProgress], host: {
4476
- class: 'st-file-upload',
4477
- '(focusout)': 'aLaSortie($event)',
4478
- }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aideLue()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div\n class=\"st-file-upload__zone\"\n [attr.data-survol]=\"survol() ? '' : null\"\n [attr.data-desactive]=\"disabled() ? '' : null\"\n (dragover)=\"surSurvol($event)\"\n (dragleave)=\"survol.set(false)\"\n (drop)=\"surDepot($event)\"\n >\n <input\n #champ\n class=\"st-file-upload__natif\"\n stControleDeChamp\n type=\"file\"\n [accept]=\"accept()\"\n [multiple]=\"multiple()\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"surChoix($event)\"\n />\n <!-- Voie de la souris vers le champ natif. Masque au lecteur d'ecran et hors\n tabulation : le clavier et le lecteur d'ecran passent par le champ natif,\n deja nomme par l'etiquette du champ. -->\n <span class=\"st-file-upload__bouton\" aria-hidden=\"true\" (click)=\"champ.click()\">\n <st-icon nom=\"televerser\" taille=\"sm\" />\n {{ multiple() ? libelles().choisirDesFichiers : libelles().choisirUnFichier }}\n </span>\n @if (multiple()) {\n <span class=\"st-file-upload__depot\" aria-hidden=\"true\">{{ libelles().ouDeposerIci }}</span>\n }\n </div>\n\n @if (lignes().length > 0) {\n <ul class=\"st-file-upload__liste\">\n @for (ligne of lignes(); track ligne.cle; let indice = $index) {\n <li class=\"st-file-upload__fichier\" [attr.data-etape]=\"ligne.etat?.etape\">\n <st-icon nom=\"fichier\" taille=\"sm\" />\n <span class=\"st-file-upload__nom\">{{ ligne.fichier.name }}</span>\n <span class=\"st-file-upload__taille\">{{ ligne.taille }}</span>\n\n @if (ligne.etat; as etat) {\n @switch (etat.etape) {\n @case ('en-cours') {\n <st-progress\n class=\"st-file-upload__progression\"\n [valeur]=\"etat.progression ?? null\"\n [etiquette]=\"libelles().televersementDe(ligne.fichier.name)\"\n />\n }\n @case ('depose') {\n <span class=\"st-file-upload__etat\">\n <st-icon nom=\"cercle-coche\" taille=\"sm\" ton=\"succes\" />\n {{ libelles().fichierDepose(ligne.fichier.name) }}\n </span>\n }\n @case ('refuse') {\n <span class=\"st-file-upload__etat\" data-refuse>\n <st-icon nom=\"triangle-alerte\" taille=\"sm\" ton=\"danger\" />\n {{ etat.message ?? libelles().fichierRefuse(ligne.fichier.name) }}\n </span>\n }\n }\n }\n\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stIconButton, et l'etiquette est requise. -->\n <button\n class=\"st-file-upload__retrait\"\n stIconButton\n type=\"button\"\n taille=\"sm\"\n [icone]=\"ligne.etat?.etape === 'en-cours' ? 'croix' : 'corbeille'\"\n [etiquette]=\"\n ligne.etat?.etape === 'en-cours'\n ? libelles().annulerLeTeleversement(ligne.fichier.name)\n : libelles().retirerLeFichier(ligne.fichier.name)\n \"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"\n ligne.etat?.etape === 'en-cours' ? annulation.emit(ligne.fichier) : retirer(indice)\n \"\n ></button>\n </li>\n }\n </ul>\n }\n</st-form-field>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-file-upload__zone{position:relative;display:flex;flex-wrap:wrap;gap:var(--st-espacement-2) var(--st-espacement-3);align-items:center;padding:var(--st-espacement-3);border:1px dashed var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-md)}.st-file-upload__zone[data-survol]{background:var(--st-couleur-surface-enfoncee);border-style:solid}.st-file-upload__natif{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-file-upload__bouton{display:inline-flex;gap:var(--st-espacement-2);align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-4);font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm)}.st-file-upload__natif:focus-visible+.st-file-upload__bouton{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}[aria-invalid=true]+.st-file-upload__bouton{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-file-upload__natif:disabled+.st-file-upload__bouton{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-file-upload__depot{color:var(--st-couleur-texte-discret)}.st-file-upload__liste{display:grid;gap:var(--st-espacement-1);padding:0;margin:var(--st-espacement-2) 0 0;list-style:none}.st-file-upload__fichier{display:flex;gap:var(--st-espacement-2);align-items:center}.st-file-upload__nom{min-inline-size:0;overflow-wrap:anywhere}.st-file-upload__progression{flex:1 1 8rem;max-inline-size:12rem}.st-file-upload__etat{display:inline-flex;gap:var(--st-espacement-1);align-items:center;color:var(--st-couleur-texte-discret)}.st-file-upload__etat[data-refuse]{color:var(--st-couleur-danger)}.st-file-upload__taille{flex:none;color:var(--st-couleur-texte-discret);font-variant-numeric:tabular-nums}\n"] }]
4479
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], tailleMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "tailleMax", required: false }] }], etats: [{ type: i0.Input, args: [{ isSignal: true, alias: "etats", required: false }] }], annulation: [{ type: i0.Output, args: ["annulation"] }] } });
4480
-
4481
- /** Plafond du backend : au-dela, la requete est refusee (SEC-7 de la vague 4). */
4482
- const TAILLE_MAXIMALE = 100;
4483
- /**
4484
- * Pagination d'une liste par curseur.
4485
- *
4486
- * ```html
4487
- * <st-data-table #tableau … />
4488
- * <st-pagination
4489
- * [numero]="navigation.numeroDePage()"
4490
- * [precedenteDisponible]="navigation.precedenteDisponible()"
4491
- * [suivanteDisponible]="page.value()?.hasMore ?? false"
4492
- * [chargement]="page.isLoading()"
4493
- * [cible]="tableau"
4494
- * (precedente)="navigation.precedente()"
4495
- * (suivante)="navigation.suivante(page.value())"
4496
- * />
4497
- * ```
4498
- *
4499
- * ⚠️ **Ni total, ni numeros cliquables** : le backend ne compte pas et ne va que
4500
- * vers l'avant (audit de la vague 4, V4-2). « Page 3 » se dit ; « page 3 sur 12 »
4501
- * serait invente.
4502
- *
4503
- * ⚠️ **Boutons indisponibles, jamais desactives** : sur la derniere page, « Page
4504
- * suivante » garde le focus qu'il avait. Desactive, il le perdrait, et le lecteur
4505
- * d'ecran retomberait sur le debut du document.
4506
- *
4507
- * ⚠️ **Apres un changement de page, le focus va a la `cible`** — la legende du
4508
- * tableau — et « Page 2 » s'annonce (UX-13). Sans cela, la personne au clavier
4509
- * reste en bas, sur « Page suivante », sans savoir que le contenu au-dessus a
4510
- * change.
4511
- */
4512
- class StPagination {
4513
- libelles = inject(ST_LIBELLES);
4514
- injecteur = inject(Injector);
4515
- mode = input('pages', /* @ts-ignore */
4516
- ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
4517
- /** A partir de 1. */
4518
- numero = input(1, /* @ts-ignore */
4519
- ...(ngDevMode ? [{ debugName: "numero" }] : /* istanbul ignore next */ []));
4520
- precedenteDisponible = input(false, { ...(ngDevMode ? { debugName: "precedenteDisponible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4521
- suivanteDisponible = input(false, { ...(ngDevMode ? { debugName: "suivanteDisponible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4522
- chargement = input(false, { ...(ngDevMode ? { debugName: "chargement" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4523
- /** Nom de la navigation, quand une page en porte plusieurs. */
4524
- etiquette = input(null, /* @ts-ignore */
4525
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
4526
- /** Tailles proposees ; vide, pas de choix. Plafonnees a 100. */
4527
- taillesDePage = input([], /* @ts-ignore */
4528
- ...(ngDevMode ? [{ debugName: "taillesDePage" }] : /* istanbul ignore next */ []));
4529
- taille = model(20, /* @ts-ignore */
4530
- ...(ngDevMode ? [{ debugName: "taille" }] : /* istanbul ignore next */ []));
4531
- /** Recoit le focus apres un changement de page. */
4532
- cible = input(null, /* @ts-ignore */
4533
- ...(ngDevMode ? [{ debugName: "cible" }] : /* istanbul ignore next */ []));
4534
- precedente = output();
4535
- suivante = output();
4536
- annonce = new AnnonceLocale(inject(DestroyRef));
4537
- tailles = computed(() => this.taillesDePage().filter((taille) => taille >= 1 && taille <= TAILLE_MAXIMALE), /* @ts-ignore */
4538
- ...(ngDevMode ? [{ debugName: "tailles" }] : /* istanbul ignore next */ []));
4539
- etiquetteLue = computed(() => this.etiquette() ?? this.libelles().pagination, /* @ts-ignore */
4540
- ...(ngDevMode ? [{ debugName: "etiquetteLue" }] : /* istanbul ignore next */ []));
4541
- constructor() {
4542
- // Le focus attend la FIN du chargement : amene sur la cible pendant que les
4543
- // anciennes lignes sont encore la, il ferait relire la page quittee.
4544
- let dernier = null;
4545
- let enAttente = false;
4546
- effect(() => {
4547
- const numero = this.numero();
4548
- const chargement = this.chargement();
4549
- if (dernier === null) {
4550
- dernier = numero;
4551
- return;
4552
- }
4553
- if (numero !== dernier) {
4554
- dernier = numero;
4555
- enAttente = true;
4556
- if (this.mode() === 'pages')
4557
- this.annonce.annoncer(this.libelles().pageNumero(numero));
4558
- }
4559
- if (!enAttente || chargement)
4560
- return;
4561
- enAttente = false;
4562
- afterNextRender(() => {
4563
- const cible = this.cible();
4564
- const element = typeof cible === 'function' ? cible() : cible;
4565
- element?.focus();
4566
- }, { injector: this.injecteur });
4567
- });
4568
- }
4569
- surTaille(evenement) {
4570
- this.taille.set(Number(evenement.target.value));
4571
- }
4572
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
4573
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StPagination, isStandalone: true, selector: "st-pagination", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, numero: { classPropertyName: "numero", publicName: "numero", isSignal: true, isRequired: false, transformFunction: null }, precedenteDisponible: { classPropertyName: "precedenteDisponible", publicName: "precedenteDisponible", isSignal: true, isRequired: false, transformFunction: null }, suivanteDisponible: { classPropertyName: "suivanteDisponible", publicName: "suivanteDisponible", isSignal: true, isRequired: false, transformFunction: null }, chargement: { classPropertyName: "chargement", publicName: "chargement", isSignal: true, isRequired: false, transformFunction: null }, etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: false, transformFunction: null }, taillesDePage: { classPropertyName: "taillesDePage", publicName: "taillesDePage", isSignal: true, isRequired: false, transformFunction: null }, taille: { classPropertyName: "taille", publicName: "taille", isSignal: true, isRequired: false, transformFunction: null }, cible: { classPropertyName: "cible", publicName: "cible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { taille: "tailleChange", precedente: "precedente", suivante: "suivante" }, host: { classAttribute: "st-pagination" }, ngImport: i0, template: "<nav class=\"st-pagination__navigation\" [attr.aria-label]=\"etiquetteLue()\">\n @if (mode() === 'pages') {\n <button\n stButton\n type=\"button\"\n iconeAvant=\"chevron-gauche\"\n [indisponible]=\"!precedenteDisponible() || chargement()\"\n (click)=\"precedente.emit()\"\n >\n {{ libelles().pagePrecedente }}\n </button>\n <span class=\"st-pagination__numero\" aria-current=\"page\">\n {{ libelles().pageNumero(numero()) }}\n </span>\n <button\n stButton\n type=\"button\"\n iconeApres=\"chevron-droite\"\n [indisponible]=\"!suivanteDisponible()\"\n [chargement]=\"chargement()\"\n (click)=\"suivante.emit()\"\n >\n {{ libelles().pageSuivante }}\n </button>\n } @else if (suivanteDisponible()) {\n <button stButton type=\"button\" [chargement]=\"chargement()\" (click)=\"suivante.emit()\">\n {{ libelles().afficherPlus }}\n </button>\n }\n\n @if (tailles().length > 0) {\n <label class=\"st-pagination__taille\">\n <span>{{ libelles().lignesParPage }}</span>\n <select class=\"st-pagination__choix\" (change)=\"surTaille($event)\">\n @for (proposee of tailles(); track proposee) {\n <option [value]=\"proposee\" [selected]=\"proposee === taille()\">{{ proposee }}</option>\n }\n </select>\n </label>\n }\n</nav>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-pagination__navigation{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2) var(--st-espacement-3);align-items:center}.st-pagination__numero{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-semi)}.st-pagination__taille{display:inline-flex;gap:var(--st-espacement-2);align-items:center;margin-inline-start:auto}.st-pagination__choix:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-pagination__choix{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-pagination__choix[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-pagination__choix:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-pagination__choix:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-pagination__choix{inline-size:auto}\n"], dependencies: [{ kind: "component", type: StButton, selector: "button[stButton]", inputs: ["variante", "taille", "pleineLargeur", "iconeAvant", "iconeApres", "chargement", "indisponible"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4574
- }
4575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StPagination, decorators: [{
4576
- type: Component,
4577
- args: [{ selector: 'st-pagination', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StButton], host: { class: 'st-pagination' }, template: "<nav class=\"st-pagination__navigation\" [attr.aria-label]=\"etiquetteLue()\">\n @if (mode() === 'pages') {\n <button\n stButton\n type=\"button\"\n iconeAvant=\"chevron-gauche\"\n [indisponible]=\"!precedenteDisponible() || chargement()\"\n (click)=\"precedente.emit()\"\n >\n {{ libelles().pagePrecedente }}\n </button>\n <span class=\"st-pagination__numero\" aria-current=\"page\">\n {{ libelles().pageNumero(numero()) }}\n </span>\n <button\n stButton\n type=\"button\"\n iconeApres=\"chevron-droite\"\n [indisponible]=\"!suivanteDisponible()\"\n [chargement]=\"chargement()\"\n (click)=\"suivante.emit()\"\n >\n {{ libelles().pageSuivante }}\n </button>\n } @else if (suivanteDisponible()) {\n <button stButton type=\"button\" [chargement]=\"chargement()\" (click)=\"suivante.emit()\">\n {{ libelles().afficherPlus }}\n </button>\n }\n\n @if (tailles().length > 0) {\n <label class=\"st-pagination__taille\">\n <span>{{ libelles().lignesParPage }}</span>\n <select class=\"st-pagination__choix\" (change)=\"surTaille($event)\">\n @for (proposee of tailles(); track proposee) {\n <option [value]=\"proposee\" [selected]=\"proposee === taille()\">{{ proposee }}</option>\n }\n </select>\n </label>\n }\n</nav>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-pagination__navigation{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2) var(--st-espacement-3);align-items:center}.st-pagination__numero{font-variant-numeric:tabular-nums;font-weight:var(--st-graisse-semi)}.st-pagination__taille{display:inline-flex;gap:var(--st-espacement-2);align-items:center;margin-inline-start:auto}.st-pagination__choix:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-pagination__choix{box-sizing:border-box;inline-size:100%;min-block-size:var(--st-commande-hauteur, var(--st-cible-tactile-min));padding:var(--st-espacement-2) var(--st-espacement-3);font:inherit;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-commande-rayon, var(--st-rayon-md))}.st-pagination__choix[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-pagination__choix:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-pagination__choix:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-pagination__choix{inline-size:auto}\n"] }]
4578
- }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], numero: [{ type: i0.Input, args: [{ isSignal: true, alias: "numero", required: false }] }], precedenteDisponible: [{ type: i0.Input, args: [{ isSignal: true, alias: "precedenteDisponible", required: false }] }], suivanteDisponible: [{ type: i0.Input, args: [{ isSignal: true, alias: "suivanteDisponible", required: false }] }], chargement: [{ type: i0.Input, args: [{ isSignal: true, alias: "chargement", required: false }] }], etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }], taillesDePage: [{ type: i0.Input, args: [{ isSignal: true, alias: "taillesDePage", required: false }] }], taille: [{ type: i0.Input, args: [{ isSignal: true, alias: "taille", required: false }] }, { type: i0.Output, args: ["tailleChange"] }], cible: [{ type: i0.Input, args: [{ isSignal: true, alias: "cible", required: false }] }], precedente: [{ type: i0.Output, args: ["precedente"] }], suivante: [{ type: i0.Output, args: ["suivante"] }] } });
4579
-
4580
- /**
4581
- * Valeur riche d'un element : montant, date, valeur masquee.
4582
- *
4583
- * ```html
4584
- * <ng-template stDescription="iban" let-element>
4585
- * <st-masked nature="iban" [valeur]="compte().iban" />
4586
- * </ng-template>
4587
- * ```
4588
- */
4589
- class StDescription {
4590
- cle = input.required({ ...(ngDevMode ? { debugName: "cle" } : /* istanbul ignore next */ {}), alias: 'stDescription' });
4591
- gabarit = inject(TemplateRef);
4592
- static ngTemplateContextGuard(_directive, _contexte) {
4593
- return true;
4594
- }
4595
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4596
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: StDescription, isStandalone: true, selector: "ng-template[stDescription]", inputs: { cle: { classPropertyName: "cle", publicName: "stDescription", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
4597
- }
4598
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDescription, decorators: [{
4599
- type: Directive,
4600
- args: [{ selector: 'ng-template[stDescription]' }]
4601
- }], propDecorators: { cle: [{ type: i0.Input, args: [{ isSignal: true, alias: "stDescription", required: true }] }] } });
4602
- /**
4603
- * Liste de descriptions : les proprietes d'un objet, sur une page de detail.
4604
- *
4605
- * ```html
4606
- * <st-description-list [elements]="[
4607
- * { terme: 'Reference', description: commande().reference },
4608
- * { cle: 'montant', terme: 'Montant', description: null },
4609
- * ]">
4610
- * <ng-template stDescription="montant"><st-money [valeur]="commande().montant" /></ng-template>
4611
- * </st-description-list>
4612
- * ```
4613
- *
4614
- * ⚠️ **Un vrai `dl`, rendu par le composant.** Le lecteur d'ecran annonce « liste de
4615
- * descriptions, 4 elements » et lit chaque terme avec sa valeur. Ecrits par
4616
- * l'application et projetes, `dt` et `dd` echapperaient aux styles du composant,
4617
- * et un `div` mal place rendrait la liste invalide.
4618
- *
4619
- * ⚠️ **Une valeur absente se dit** : « — » a l'ecran, « Non renseigne » au lecteur
4620
- * d'ecran. Une cellule vide ne distingue pas « aucun » de « pas encore charge ».
4621
- *
4622
- * En colonnes quand la place le permet, empile en dessous : la largeur est celle
4623
- * du CONTENEUR, pas de l'ecran — la meme liste dans une carte etroite s'empile
4624
- * aussi sur un grand ecran (UX-18).
4625
- */
4626
- class StDescriptionList {
4627
- libelles = inject(ST_LIBELLES);
4628
- elements = input.required(/* @ts-ignore */
4629
- ...(ngDevMode ? [{ debugName: "elements" }] : /* istanbul ignore next */ []));
4630
- gabarits = contentChildren(StDescription, /* @ts-ignore */
4631
- ...(ngDevMode ? [{ debugName: "gabarits" }] : /* istanbul ignore next */ []));
4632
- lignes = computed(() => {
4633
- const gabarits = new Map(this.gabarits().map((gabarit) => [gabarit.cle(), gabarit.gabarit]));
4634
- return this.elements().map((element) => ({
4635
- element,
4636
- gabarit: gabarits.get(element.cle ?? element.terme) ?? null,
4637
- }));
4638
- }, /* @ts-ignore */
4639
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
4640
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDescriptionList, deps: [], target: i0.ɵɵFactoryTarget.Component });
4641
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StDescriptionList, isStandalone: true, selector: "st-description-list", inputs: { elements: { classPropertyName: "elements", publicName: "elements", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "st-description-list" }, queries: [{ propertyName: "gabarits", predicate: StDescription, isSignal: true }], ngImport: i0, template: "<dl class=\"st-description-list__liste\">\n @for (ligne of lignes(); track ligne.element.cle ?? ligne.element.terme) {\n <div class=\"st-description-list__groupe\">\n <dt class=\"st-description-list__terme\">{{ ligne.element.terme }}</dt>\n <dd class=\"st-description-list__valeur\">\n @if (ligne.gabarit; as gabarit) {\n <ng-container *ngTemplateOutlet=\"gabarit; context: { $implicit: ligne.element }\" />\n } @else if (ligne.element.description === null) {\n <span aria-hidden=\"true\">&#8212;</span>\n <span class=\"st-vh\">{{ libelles().nonRenseigne }}</span>\n } @else {\n {{ ligne.element.description }}\n }\n </dd>\n </div>\n }\n</dl>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;container-type:inline-size}.st-description-list__liste{margin:0}.st-description-list__groupe{display:grid;gap:var(--st-espacement-1);padding-block:var(--st-espacement-2);border-block-end:1px solid var(--st-couleur-bordure)}.st-description-list__terme{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret)}.st-description-list__valeur{margin:0;overflow-wrap:anywhere}@container (min-width: st.$rupture-sm){.st-description-list__groupe{grid-template-columns:minmax(10rem,1fr) 2fr;gap:var(--st-espacement-4)}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4642
- }
4643
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDescriptionList, decorators: [{
4644
- type: Component,
4645
- args: [{ selector: 'st-description-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: { class: 'st-description-list' }, template: "<dl class=\"st-description-list__liste\">\n @for (ligne of lignes(); track ligne.element.cle ?? ligne.element.terme) {\n <div class=\"st-description-list__groupe\">\n <dt class=\"st-description-list__terme\">{{ ligne.element.terme }}</dt>\n <dd class=\"st-description-list__valeur\">\n @if (ligne.gabarit; as gabarit) {\n <ng-container *ngTemplateOutlet=\"gabarit; context: { $implicit: ligne.element }\" />\n } @else if (ligne.element.description === null) {\n <span aria-hidden=\"true\">&#8212;</span>\n <span class=\"st-vh\">{{ libelles().nonRenseigne }}</span>\n } @else {\n {{ ligne.element.description }}\n }\n </dd>\n </div>\n }\n</dl>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block;container-type:inline-size}.st-description-list__liste{margin:0}.st-description-list__groupe{display:grid;gap:var(--st-espacement-1);padding-block:var(--st-espacement-2);border-block-end:1px solid var(--st-couleur-bordure)}.st-description-list__terme{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret)}.st-description-list__valeur{margin:0;overflow-wrap:anywhere}@container (min-width: st.$rupture-sm){.st-description-list__groupe{grid-template-columns:minmax(10rem,1fr) 2fr;gap:var(--st-espacement-4)}}\n"] }]
4646
- }], propDecorators: { elements: [{ type: i0.Input, args: [{ isSignal: true, alias: "elements", required: true }] }], gabarits: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StDescription), { isSignal: true }] }] } });
4647
-
4648
- /**
4649
- * Rendu d'un element, type par `[stElementDeListeDe]` :
4650
- *
4651
- * ```html
4652
- * <ng-template stElementDeListe [stElementDeListeDe]="expeditions()" let-expedition>
4653
- * <a [routerLink]="['/expeditions', expedition.id]">{{ expedition.reference }}</a>
4654
- * </ng-template>
4655
- * ```
4656
- */
4657
- class StElementDeListe {
4658
- /** Sert seulement au typage du contexte ; nom de l'attribut, d'ou l'absence d'alias. */
4659
- stElementDeListeDe = input(undefined, /* @ts-ignore */
4660
- ...(ngDevMode ? [{ debugName: "stElementDeListeDe" }] : /* istanbul ignore next */ []));
4661
- gabarit = inject(TemplateRef);
4662
- static ngTemplateContextGuard(_directive, _contexte) {
4663
- return true;
4664
- }
4665
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StElementDeListe, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4666
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: StElementDeListe, isStandalone: true, selector: "ng-template[stElementDeListe]", inputs: { stElementDeListeDe: { classPropertyName: "stElementDeListeDe", publicName: "stElementDeListeDe", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4667
- }
4668
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StElementDeListe, decorators: [{
4669
- type: Directive,
4670
- args: [{ selector: 'ng-template[stElementDeListe]' }]
4671
- }], propDecorators: { stElementDeListeDe: [{ type: i0.Input, args: [{ isSignal: true, alias: "stElementDeListeDe", required: false }] }] } });
4672
- /**
4673
- * Liste d'objets : expeditions, notifications, documents. La forme mobile d'un
4674
- * tableau (UX-5).
4675
- *
4676
- * ```html
4677
- * <st-list #liste etiquette="Mes expeditions" [elements]="expeditions()" cle="id">
4678
- * <ng-template stElementDeListe [stElementDeListeDe]="expeditions()" let-expedition>…</ng-template>
4679
- * </st-list>
4680
- * <st-pagination mode="suite" [cible]="liste" … />
4681
- * ```
4682
- *
4683
- * ⚠️ **Un lien DANS l'element, jamais un element cliquable** (UX-3) : le lien a un
4684
- * nom, s'ouvre dans un onglet, et laisse les autres boutons de l'element
4685
- * atteignables.
4686
- *
4687
- * ⚠️ **`focus()` va au premier element AJOUTE** depuis le dernier affichage : apres
4688
- * « Afficher plus », la lecture reprend la ou la liste s'est allongee, et non en
4689
- * tete. Apres un filtre, la liste raccourcit, et le focus va au premier element.
4690
- *
4691
- * `track` par la cle : un rafraichissement garde le focus sur le bon element.
4692
- */
4693
- class StList {
4694
- element = inject(ElementRef);
4695
- elements = input.required(/* @ts-ignore */
4696
- ...(ngDevMode ? [{ debugName: "elements" }] : /* istanbul ignore next */ []));
4697
- /** Propriete qui identifie un element. */
4698
- cle = input.required(/* @ts-ignore */
4699
- ...(ngDevMode ? [{ debugName: "cle" }] : /* istanbul ignore next */ []));
4700
- /** Nom de la liste, lu a l'entree. */
4701
- etiquette = input(null, /* @ts-ignore */
4702
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
4703
- /** `ol` quand l'ordre a un sens : etapes, classement. */
4704
- ordonnee = input(false, { ...(ngDevMode ? { debugName: "ordonnee" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4705
- /** Contenu en cours de remplacement : `aria-busy`. */
4706
- chargement = input(false, { ...(ngDevMode ? { debugName: "chargement" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4707
- gabarit = contentChild(StElementDeListe, /* @ts-ignore */
4708
- ...(ngDevMode ? [{ debugName: "gabarit" }] : /* istanbul ignore next */ []));
4709
- indiceDuPremierAjout = 0;
4710
- lignes = computed(() => {
4711
- const cle = this.cle();
4712
- return this.elements().map((valeur, indice) => ({
4713
- valeur,
4714
- indice,
4715
- identifiant: String(valeur[cle]),
4716
- }));
4717
- }, /* @ts-ignore */
4718
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
4719
- constructor() {
4720
- let longueur = 0;
4721
- effect(() => {
4722
- const nouvelle = this.elements().length;
4723
- this.indiceDuPremierAjout = nouvelle > longueur && longueur > 0 ? longueur : 0;
4724
- longueur = nouvelle;
4725
- });
4726
- }
4727
- /** Focus sur le premier element ajoute, ou sur le premier. */
4728
- focus(options) {
4729
- const elements = this.element.nativeElement.querySelectorAll('.st-list__element');
4730
- (elements[this.indiceDuPremierAjout] ?? elements[0])?.focus(options);
4731
- }
4732
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StList, deps: [], target: i0.ɵɵFactoryTarget.Component });
4733
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StList, isStandalone: true, selector: "st-list", inputs: { elements: { classPropertyName: "elements", publicName: "elements", isSignal: true, isRequired: true, transformFunction: null }, cle: { classPropertyName: "cle", publicName: "cle", isSignal: true, isRequired: true, transformFunction: null }, etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: false, transformFunction: null }, ordonnee: { classPropertyName: "ordonnee", publicName: "ordonnee", isSignal: true, isRequired: false, transformFunction: null }, chargement: { classPropertyName: "chargement", publicName: "chargement", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "st-list" }, queries: [{ propertyName: "gabarit", first: true, predicate: StElementDeListe, descendants: true, isSignal: true }], ngImport: i0, template: "@if (ordonnee()) {\n <ol\n class=\"st-list__liste\"\n [attr.aria-label]=\"etiquette()\"\n [attr.aria-busy]=\"chargement() ? 'true' : null\"\n >\n <ng-container *ngTemplateOutlet=\"contenu\" />\n </ol>\n} @else {\n <ul\n class=\"st-list__liste\"\n [attr.aria-label]=\"etiquette()\"\n [attr.aria-busy]=\"chargement() ? 'true' : null\"\n >\n <ng-container *ngTemplateOutlet=\"contenu\" />\n </ul>\n}\n\n<ng-template #contenu>\n @for (ligne of lignes(); track ligne.identifiant) {\n <!-- tabindex -1 : cible du focus apres \u00AB Afficher plus \u00BB, hors tabulation. -->\n <li class=\"st-list__element\" tabindex=\"-1\">\n @if (gabarit(); as gabarit) {\n <ng-container\n *ngTemplateOutlet=\"\n gabarit.gabarit;\n context: { $implicit: ligne.valeur, indice: ligne.indice }\n \"\n />\n } @else {\n {{ ligne.valeur }}\n }\n </li>\n }\n</ng-template>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-list__liste{padding:0;margin:0;list-style:none}.st-list__liste[aria-busy=true]{cursor:progress}.st-list__element{padding-block:var(--st-espacement-3);border-block-end:1px solid var(--st-couleur-bordure)}.st-list__element:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4734
- }
4735
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StList, decorators: [{
4736
- type: Component,
4737
- args: [{ selector: 'st-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: { class: 'st-list' }, template: "@if (ordonnee()) {\n <ol\n class=\"st-list__liste\"\n [attr.aria-label]=\"etiquette()\"\n [attr.aria-busy]=\"chargement() ? 'true' : null\"\n >\n <ng-container *ngTemplateOutlet=\"contenu\" />\n </ol>\n} @else {\n <ul\n class=\"st-list__liste\"\n [attr.aria-label]=\"etiquette()\"\n [attr.aria-busy]=\"chargement() ? 'true' : null\"\n >\n <ng-container *ngTemplateOutlet=\"contenu\" />\n </ul>\n}\n\n<ng-template #contenu>\n @for (ligne of lignes(); track ligne.identifiant) {\n <!-- tabindex -1 : cible du focus apres \u00AB Afficher plus \u00BB, hors tabulation. -->\n <li class=\"st-list__element\" tabindex=\"-1\">\n @if (gabarit(); as gabarit) {\n <ng-container\n *ngTemplateOutlet=\"\n gabarit.gabarit;\n context: { $implicit: ligne.valeur, indice: ligne.indice }\n \"\n />\n } @else {\n {{ ligne.valeur }}\n }\n </li>\n }\n</ng-template>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-list__liste{padding:0;margin:0;list-style:none}.st-list__liste[aria-busy=true]{cursor:progress}.st-list__element{padding-block:var(--st-espacement-3);border-block-end:1px solid var(--st-couleur-bordure)}.st-list__element:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}\n"] }]
4738
- }], ctorParameters: () => [], propDecorators: { elements: [{ type: i0.Input, args: [{ isSignal: true, alias: "elements", required: true }] }], cle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cle", required: true }] }], etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }], ordonnee: [{ type: i0.Input, args: [{ isSignal: true, alias: "ordonnee", required: false }] }], chargement: [{ type: i0.Input, args: [{ isSignal: true, alias: "chargement", required: false }] }], gabarit: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StElementDeListe), { isSignal: true }] }] } });
4739
-
4740
- /**
4741
- * Bouton de tri d'une colonne, pose DANS l'en-tete : `<th><button stSortHeader>`.
4742
- *
4743
- * ⚠️ **Le bouton, pas l'en-tete cliquable** : un `th` n'est ni focalisable ni
4744
- * annonce comme actionnable. Et l'etat se porte par `aria-sort` sur le `th`, que
4745
- * `st-data-table` pose sur la SEULE colonne triee (FE-3).
4746
- *
4747
- * ⚠️ **Ne trie rien.** Il dit l'intention ; c'est le serveur qui trie, et le
4748
- * contrat actuel ne le fait pas (audit de la vague 4, V4-3). Trier la page
4749
- * affichee ferait croire a un tri de toute la liste.
4750
- *
4751
- * Une colonne non triee montre deux chevrons, pour se signaler triable sans
4752
- * attendre le survol ; l'icone est decorative, l'etat est lu par `aria-sort`.
4753
- */
4754
- class StSortHeader {
4755
- sens = input(null, /* @ts-ignore */
4756
- ...(ngDevMode ? [{ debugName: "sens" }] : /* istanbul ignore next */ []));
4757
- icone = computed(() => {
4758
- const sens = this.sens();
4759
- if (sens === 'croissant')
4760
- return 'chevron-haut';
4761
- if (sens === 'decroissant')
4762
- return 'chevron-bas';
4763
- return 'tri';
4764
- }, /* @ts-ignore */
4765
- ...(ngDevMode ? [{ debugName: "icone" }] : /* istanbul ignore next */ []));
4766
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSortHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
4767
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StSortHeader, isStandalone: true, selector: "button[stSortHeader]", inputs: { sens: { classPropertyName: "sens", publicName: "sens", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, properties: { "attr.data-sens": "sens()" }, classAttribute: "st-sort-header" }, ngImport: i0, template: "<span class=\"st-sort-header__libelle\"><ng-content /></span>\n<st-icon class=\"st-sort-header__icone\" taille=\"sm\" [nom]=\"icone()\" />\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}:host{display:inline-flex;gap:var(--st-espacement-1);align-items:center;min-block-size:var(--st-cible-min);padding:0;font:inherit;font-weight:inherit;color:inherit;text-align:inherit;cursor:pointer;background:none;border:0}.st-sort-header__icone{flex:none;color:var(--st-couleur-texte-discret)}:host([data-sens]) .st-sort-header__icone{color:inherit}\n"], dependencies: [{ kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4768
- }
4769
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSortHeader, decorators: [{
4770
- type: Component,
4771
- args: [{ selector: 'button[stSortHeader]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIcon], host: {
4772
- class: 'st-sort-header',
4773
- type: 'button',
4774
- '[attr.data-sens]': 'sens()',
4775
- }, template: "<span class=\"st-sort-header__libelle\"><ng-content /></span>\n<st-icon class=\"st-sort-header__icone\" taille=\"sm\" [nom]=\"icone()\" />\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}:host{display:inline-flex;gap:var(--st-espacement-1);align-items:center;min-block-size:var(--st-cible-min);padding:0;font:inherit;font-weight:inherit;color:inherit;text-align:inherit;cursor:pointer;background:none;border:0}.st-sort-header__icone{flex:none;color:var(--st-couleur-texte-discret)}:host([data-sens]) .st-sort-header__icone{color:inherit}\n"] }]
4776
- }], propDecorators: { sens: [{ type: i0.Input, args: [{ isSignal: true, alias: "sens", required: false }] }] } });
4777
-
4778
- let compteur$3 = 0;
4779
- /**
4780
- * Rendu d'une cellule, type par `[stCelluleDe]` :
4781
- *
4782
- * ```html
4783
- * <ng-template stCellule="montant" [stCelluleDe]="lignes()" let-ligne>
4784
- * <st-money [valeur]="ligne.montant" />
4785
- * </ng-template>
4786
- * ```
4787
- */
4788
- class StCellule {
4789
- cle = input.required({ ...(ngDevMode ? { debugName: "cle" } : /* istanbul ignore next */ {}), alias: 'stCellule' });
4790
- /** Sert seulement au typage du contexte ; nom de l'attribut, d'ou l'absence d'alias. */
4791
- stCelluleDe = input(undefined, /* @ts-ignore */
4792
- ...(ngDevMode ? [{ debugName: "stCelluleDe" }] : /* istanbul ignore next */ []));
4793
- gabarit = inject(TemplateRef);
4794
- static ngTemplateContextGuard(_directive, _contexte) {
4795
- return true;
4796
- }
4797
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCellule, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4798
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: StCellule, isStandalone: true, selector: "ng-template[stCellule]", inputs: { cle: { classPropertyName: "cle", publicName: "stCellule", isSignal: true, isRequired: true, transformFunction: null }, stCelluleDe: { classPropertyName: "stCelluleDe", publicName: "stCelluleDe", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
4799
- }
4800
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCellule, decorators: [{
4801
- type: Directive,
4802
- args: [{ selector: 'ng-template[stCellule]' }]
4803
- }], propDecorators: { cle: [{ type: i0.Input, args: [{ isSignal: true, alias: "stCellule", required: true }] }], stCelluleDe: [{ type: i0.Input, args: [{ isSignal: true, alias: "stCelluleDe", required: false }] }] } });
4804
- /** Contenu du tableau vide : un `st-empty`, avec son action. */
4805
- class StTableauVide {
4806
- gabarit = inject(TemplateRef);
4807
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTableauVide, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4808
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: StTableauVide, isStandalone: true, selector: "ng-template[stTableauVide]", ngImport: i0 });
4809
- }
4810
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTableauVide, decorators: [{
4811
- type: Directive,
4812
- args: [{ selector: 'ng-template[stTableauVide]' }]
4813
- }] });
4814
- /** Lignes de squelette au premier chargement. */
4815
- const LIGNES_DE_SQUELETTE = 5;
4816
- /**
4817
- * Tableau de donnees, sur un `<table>` NATIF.
4818
- *
4819
- * ```html
4820
- * <st-data-table #tableau legende="Expeditions" [colonnes]="colonnes" [lignes]="lignes()"
4821
- * cle="id" selectionnable [(selection)]="choisies" [chargement]="page.isLoading()">
4822
- * <ng-template stCellule="reference" [stCelluleDe]="lignes()" let-ligne>
4823
- * <a [routerLink]="['/expeditions', ligne.id]">{{ ligne.reference }}</a>
4824
- * </ng-template>
4825
- * <button stButton stActionsDeTableau (click)="exporter()">Exporter</button>
4826
- * </st-data-table>
4827
- * ```
4828
- *
4829
- * ⚠️ **Un tableau, pas une grille** (audit de la vague 4, V4-1). Le lecteur d'ecran
4830
- * sait parcourir un `<table>` de cellule en cellule ; une grille ARIA remplacerait
4831
- * ce parcours par les fleches, et rendrait les liens des cellules plus durs a
4832
- * atteindre. Ni Material, ni `CdkTable` : pas de pair dans le tronc.
4833
- *
4834
- * - **Legende obligatoire** (UX-1), et une colonne d'en-tete de ligne — la premiere
4835
- * par defaut — pour que chaque cellule se lise avec sa ligne (UX-2).
4836
- * - **Zone de defilement nommee** : focalisable quand le tableau deborde (UX-6),
4837
- * colonne figee sans masquer le focus (UX-7), en-tete fige seulement avec
4838
- * `hauteurMax` (UX-8).
4839
- * - **Selection de la PAGE, par cle** (V4-4) : elle ne garde que les lignes encore
4840
- * affichees, et se vide donc au changement de page.
4841
- * - **Tri PILOTE** : le tableau dit la demande et l'annonce, il ne reordonne
4842
- * jamais les lignes (V4-3).
4843
- * - **Chargement** : squelette au premier, lignes gardees et `aria-busy` ensuite
4844
- * (UX-11).
4845
- * - Export, actions de masse : zone `stActionsDeTableau`, et le SERVEUR fait le
4846
- * travail (V4-5, SEC-4, SEC-5).
4847
- */
4848
- class StDataTable {
4849
- libelles = inject(ST_LIBELLES);
4850
- legende = input.required(/* @ts-ignore */
4851
- ...(ngDevMode ? [{ debugName: "legende" }] : /* istanbul ignore next */ []));
4852
- /** Lue mais non affichee, quand un titre visible nomme deja le tableau. */
4853
- legendeMasquee = input(false, { ...(ngDevMode ? { debugName: "legendeMasquee" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4854
- colonnes = input.required(/* @ts-ignore */
4855
- ...(ngDevMode ? [{ debugName: "colonnes" }] : /* istanbul ignore next */ []));
4856
- lignes = input.required(/* @ts-ignore */
4857
- ...(ngDevMode ? [{ debugName: "lignes" }] : /* istanbul ignore next */ []));
4858
- /** Propriete qui identifie une ligne. Jamais l'indice (FE-1). */
4859
- cle = input.required(/* @ts-ignore */
4860
- ...(ngDevMode ? [{ debugName: "cle" }] : /* istanbul ignore next */ []));
4861
- /** Colonne lue comme en-tete de chaque ligne. Par defaut : la premiere. */
4862
- enTeteDeLigne = input(null, /* @ts-ignore */
4863
- ...(ngDevMode ? [{ debugName: "enTeteDeLigne" }] : /* istanbul ignore next */ []));
4864
- premiereColonneFigee = input(false, { ...(ngDevMode ? { debugName: "premiereColonneFigee" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4865
- /** Hauteur maximale, `32rem` : l'en-tete se fige, le corps defile. */
4866
- hauteurMax = input(null, /* @ts-ignore */
4867
- ...(ngDevMode ? [{ debugName: "hauteurMax" }] : /* istanbul ignore next */ []));
4868
- densite = input('confortable', /* @ts-ignore */
4869
- ...(ngDevMode ? [{ debugName: "densite" }] : /* istanbul ignore next */ []));
4870
- chargement = input(false, { ...(ngDevMode ? { debugName: "chargement" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4871
- selectionnable = input(false, { ...(ngDevMode ? { debugName: "selectionnable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4872
- selection = model([], /* @ts-ignore */
4873
- ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
4874
- tri = model(null, /* @ts-ignore */
4875
- ...(ngDevMode ? [{ debugName: "tri" }] : /* istanbul ignore next */ []));
4876
- /** Texte du tableau vide, sans gabarit `stTableauVide`. */
4877
- texteVide = input(null, /* @ts-ignore */
4878
- ...(ngDevMode ? [{ debugName: "texteVide" }] : /* istanbul ignore next */ []));
4879
- gabarits = contentChildren(StCellule, /* @ts-ignore */
4880
- ...(ngDevMode ? [{ debugName: "gabarits" }] : /* istanbul ignore next */ []));
4881
- gabaritVide = contentChild(StTableauVide, /* @ts-ignore */
4882
- ...(ngDevMode ? [{ debugName: "gabaritVide" }] : /* istanbul ignore next */ []));
4883
- zone = viewChild.required('zone', /* @ts-ignore */
4884
- ...(ngDevMode ? [{ debugName: "zone" }] : /* istanbul ignore next */ []));
4885
- annonce = new AnnonceLocale(inject(DestroyRef));
4886
- deborde = signal(false, /* @ts-ignore */
4887
- ...(ngDevMode ? [{ debugName: "deborde" }] : /* istanbul ignore next */ []));
4888
- largeurFigee = signal(0, /* @ts-ignore */
4889
- ...(ngDevMode ? [{ debugName: "largeurFigee" }] : /* istanbul ignore next */ []));
4890
- identifiants = (() => {
4891
- const base = `st-tableau-${(compteur$3 += 1).toString()}`;
4892
- return { legende: `${base}-legende`, selection: `${base}-selection` };
4893
- })();
4894
- cleDEnTete = computed(() => this.enTeteDeLigne() ?? this.colonnes()[0]?.cle ?? '', /* @ts-ignore */
4895
- ...(ngDevMode ? [{ debugName: "cleDEnTete" }] : /* istanbul ignore next */ []));
4896
- cellules = computed(() => {
4897
- const gabarits = new Map(this.gabarits().map((gabarit) => [gabarit.cle(), gabarit.gabarit]));
4898
- return this.colonnes().map((colonne) => ({
4899
- colonne,
4900
- gabarit: gabarits.get(colonne.cle) ?? null,
4901
- enTete: colonne.cle === this.cleDEnTete(),
4902
- }));
4903
- }, /* @ts-ignore */
4904
- ...(ngDevMode ? [{ debugName: "cellules" }] : /* istanbul ignore next */ []));
4905
- rangees = computed(() => {
4906
- const cle = this.cle();
4907
- const entete = this.cleDEnTete();
4908
- const choisies = new Set(this.selection());
4909
- return this.lignes().map((valeur) => {
4910
- const identifiant = String(valeur[cle]);
4911
- return {
4912
- valeur,
4913
- identifiant,
4914
- nom: texteDe(valeur[entete]),
4915
- choisie: choisies.has(identifiant),
4916
- };
4917
- });
4918
- }, /* @ts-ignore */
4919
- ...(ngDevMode ? [{ debugName: "rangees" }] : /* istanbul ignore next */ []));
4920
- squelette = computed(() => this.chargement() && this.lignes().length === 0
4921
- ? Array.from({ length: LIGNES_DE_SQUELETTE }, (_, indice) => indice)
4922
- : [], /* @ts-ignore */
4923
- ...(ngDevMode ? [{ debugName: "squelette" }] : /* istanbul ignore next */ []));
4924
- nombreDeColonnes = computed(() => this.colonnes().length + (this.selectionnable() ? 1 : 0), /* @ts-ignore */
4925
- ...(ngDevMode ? [{ debugName: "nombreDeColonnes" }] : /* istanbul ignore next */ []));
4926
- colonnesDeSquelette = computed(() => Array.from({ length: this.nombreDeColonnes() }, (_, indice) => indice), /* @ts-ignore */
4927
- ...(ngDevMode ? [{ debugName: "colonnesDeSquelette" }] : /* istanbul ignore next */ []));
4928
- toutesChoisies = computed(() => {
4929
- const rangees = this.rangees();
4930
- return rangees.length > 0 && rangees.every((rangee) => rangee.choisie);
4931
- }, /* @ts-ignore */
4932
- ...(ngDevMode ? [{ debugName: "toutesChoisies" }] : /* istanbul ignore next */ []));
4933
- quelquesChoisies = computed(() => !this.toutesChoisies() && this.rangees().some((rangee) => rangee.choisie), /* @ts-ignore */
4934
- ...(ngDevMode ? [{ debugName: "quelquesChoisies" }] : /* istanbul ignore next */ []));
4935
- constructor() {
4936
- // V4-4 : une selection ne vise que ce qui est affiche. Nouvelle page, nouvelles
4937
- // cles : les anciennes tombent.
4938
- effect(() => {
4939
- const presentes = new Set(this.rangees().map((rangee) => rangee.identifiant));
4940
- const selection = this.selection();
4941
- const gardees = selection.filter((identifiant) => presentes.has(identifiant));
4942
- if (gardees.length !== selection.length)
4943
- this.selection.set(gardees);
4944
- });
4945
- // Mesures du rendu, jamais sur le serveur (FE-5).
4946
- const destruction = inject(DestroyRef);
4947
- afterNextRender(() => {
4948
- const zone = this.zone().nativeElement;
4949
- if (typeof ResizeObserver === 'undefined')
4950
- return;
4951
- const observateur = new ResizeObserver(() => {
4952
- this.deborde.set(zone.scrollWidth > zone.clientWidth + 1 || zone.scrollHeight > zone.clientHeight + 1);
4953
- // UX-7 : la largeur des colonnes figees devient la marge de defilement,
4954
- // pour que la cellule focalisee s'arrete A COTE d'elles et non dessous.
4955
- const figees = zone.querySelectorAll('tr:first-child > [data-figee]');
4956
- let largeur = 0;
4957
- figees.forEach((cellule) => (largeur += cellule.getBoundingClientRect().width));
4958
- this.largeurFigee.set(Math.ceil(largeur));
4959
- });
4960
- observateur.observe(zone);
4961
- const tableau = zone.querySelector('table');
4962
- if (tableau !== null)
4963
- observateur.observe(tableau);
4964
- destruction.onDestroy(() => {
4965
- observateur.disconnect();
4966
- });
4967
- });
4968
- }
4969
- /** Amene le focus sur le tableau : sa legende est lue (UX-13). */
4970
- focus(options) {
4971
- this.zone().nativeElement.focus(options);
4972
- }
4973
- sensDe(colonne) {
4974
- const tri = this.tri();
4975
- return tri?.cle === colonne.cle ? tri.sens : null;
4976
- }
4977
- ariaSort(colonne) {
4978
- const sens = this.sensDe(colonne);
4979
- if (sens === null)
4980
- return null;
4981
- return sens === 'croissant' ? 'ascending' : 'descending';
4982
- }
4983
- trier(colonne) {
4984
- const sens = this.sensDe(colonne) === 'croissant' ? 'decroissant' : 'croissant';
4985
- this.tri.set({ cle: colonne.cle, sens });
4986
- const libelles = this.libelles();
4987
- this.annonce.annoncer(sens === 'croissant'
4988
- ? libelles.trieCroissant(colonne.libelle)
4989
- : libelles.trieDecroissant(colonne.libelle));
4990
- }
4991
- basculerTout() {
4992
- this.selection.set(this.toutesChoisies() ? [] : this.rangees().map((rangee) => rangee.identifiant));
4993
- }
4994
- basculer(identifiant) {
4995
- const selection = this.selection();
4996
- this.selection.set(selection.includes(identifiant)
4997
- ? selection.filter((choisie) => choisie !== identifiant)
4998
- : [...selection, identifiant]);
4999
- }
5000
- texteDe(ligne, colonne) {
5001
- return texteDe(ligne[colonne.cle]);
5002
- }
5003
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
5004
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StDataTable, isStandalone: true, selector: "st-data-table", inputs: { legende: { classPropertyName: "legende", publicName: "legende", isSignal: true, isRequired: true, transformFunction: null }, legendeMasquee: { classPropertyName: "legendeMasquee", publicName: "legendeMasquee", isSignal: true, isRequired: false, transformFunction: null }, colonnes: { classPropertyName: "colonnes", publicName: "colonnes", isSignal: true, isRequired: true, transformFunction: null }, lignes: { classPropertyName: "lignes", publicName: "lignes", isSignal: true, isRequired: true, transformFunction: null }, cle: { classPropertyName: "cle", publicName: "cle", isSignal: true, isRequired: true, transformFunction: null }, enTeteDeLigne: { classPropertyName: "enTeteDeLigne", publicName: "enTeteDeLigne", isSignal: true, isRequired: false, transformFunction: null }, premiereColonneFigee: { classPropertyName: "premiereColonneFigee", publicName: "premiereColonneFigee", isSignal: true, isRequired: false, transformFunction: null }, hauteurMax: { classPropertyName: "hauteurMax", publicName: "hauteurMax", isSignal: true, isRequired: false, transformFunction: null }, densite: { classPropertyName: "densite", publicName: "densite", isSignal: true, isRequired: false, transformFunction: null }, chargement: { classPropertyName: "chargement", publicName: "chargement", isSignal: true, isRequired: false, transformFunction: null }, selectionnable: { classPropertyName: "selectionnable", publicName: "selectionnable", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, tri: { classPropertyName: "tri", publicName: "tri", isSignal: true, isRequired: false, transformFunction: null }, texteVide: { classPropertyName: "texteVide", publicName: "texteVide", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", tri: "triChange" }, host: { properties: { "attr.data-densite": "densite()", "attr.data-figee": "premiereColonneFigee() ? \"\" : null" }, classAttribute: "st-data-table" }, queries: [{ propertyName: "gabarits", predicate: StCellule, isSignal: true }, { propertyName: "gabaritVide", first: true, predicate: StTableauVide, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "zone", first: true, predicate: ["zone"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- UN seul ng-content : la projection se decide a la compilation, et un second\n emplacement de meme selecteur, dans une autre branche, ne recevrait jamais\n rien. -->\n<div class=\"st-data-table__barre\">\n @if (selectionnable()) {\n <p class=\"st-data-table__selection\" role=\"status\" [id]=\"identifiants.selection\">\n {{ selection().length > 0 ? libelles().lignesSelectionnees(selection().length) : '' }}\n </p>\n }\n <div class=\"st-data-table__actions\">\n <ng-content select=\"[stActionsDeTableau]\" />\n </div>\n</div>\n\n<!-- UX-6 : zone nommee par la legende ; focalisable quand elle deborde, et par\n programme toujours, pour recevoir le focus apres un changement de page. -->\n<div\n #zone\n class=\"st-data-table__zone\"\n role=\"region\"\n [attr.aria-labelledby]=\"identifiants.legende\"\n [attr.tabindex]=\"deborde() ? 0 : -1\"\n [attr.data-deborde]=\"deborde() ? '' : null\"\n [style.max-block-size]=\"hauteurMax()\"\n [style.scroll-padding-inline-start.px]=\"largeurFigee()\"\n>\n <table class=\"st-data-table__tableau\" [attr.aria-busy]=\"chargement() ? 'true' : null\">\n <caption\n class=\"st-data-table__legende\"\n [id]=\"identifiants.legende\"\n [class.st-vh]=\"legendeMasquee()\"\n >\n {{\n legende()\n }}\n </caption>\n <thead>\n <tr>\n @if (selectionnable()) {\n <th class=\"st-data-table__case\" scope=\"col\" data-figee>\n <input\n class=\"st-data-table__cocher\"\n type=\"checkbox\"\n [checked]=\"toutesChoisies()\"\n [indeterminate]=\"quelquesChoisies()\"\n [disabled]=\"rangees().length === 0\"\n [attr.aria-label]=\"libelles().selectionnerLaPage(rangees().length)\"\n (change)=\"basculerTout()\"\n />\n </th>\n }\n @for (cellule of cellules(); track cellule.colonne.cle; let premiere = $first) {\n <th\n scope=\"col\"\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n [attr.aria-sort]=\"ariaSort(cellule.colonne)\"\n >\n @if (cellule.colonne.triable) {\n <button\n stSortHeader\n type=\"button\"\n [sens]=\"sensDe(cellule.colonne)\"\n (click)=\"trier(cellule.colonne)\"\n >\n {{ cellule.colonne.libelle }}\n </button>\n } @else if (cellule.colonne.libelleMasque) {\n <span class=\"st-vh\">{{ cellule.colonne.libelle }}</span>\n } @else {\n {{ cellule.colonne.libelle }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (rang of squelette(); track rang) {\n <tr class=\"st-data-table__squelette\" aria-hidden=\"true\">\n @for (colonne of colonnesDeSquelette(); track colonne) {\n <td><span class=\"st-data-table__barre-de-squelette\"></span></td>\n }\n </tr>\n }\n @for (rangee of rangees(); track rangee.identifiant) {\n <tr [attr.data-choisie]=\"rangee.choisie ? '' : null\">\n @if (selectionnable()) {\n <td class=\"st-data-table__case\" data-figee>\n <input\n class=\"st-data-table__cocher\"\n type=\"checkbox\"\n [checked]=\"rangee.choisie\"\n [attr.aria-label]=\"libelles().selectionnerLaLigne(rangee.nom ?? rangee.identifiant)\"\n (change)=\"basculer(rangee.identifiant)\"\n />\n </td>\n }\n @for (cellule of cellules(); track cellule.colonne.cle; let premiere = $first) {\n @if (cellule.enTete) {\n <th\n scope=\"row\"\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valeur;\n context: { $implicit: rangee.valeur, cellule: cellule }\n \"\n />\n </th>\n } @else {\n <td\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valeur;\n context: { $implicit: rangee.valeur, cellule: cellule }\n \"\n />\n </td>\n }\n }\n </tr>\n } @empty {\n @if (!chargement()) {\n <tr>\n <td class=\"st-data-table__vide\" [attr.colspan]=\"nombreDeColonnes()\">\n @if (gabaritVide(); as vide) {\n <ng-container *ngTemplateOutlet=\"vide.gabarit\" />\n } @else {\n {{ texteVide() ?? libelles().tableauVide }}\n }\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n\n<ng-template #valeur let-ligne let-cellule=\"cellule\">\n @if (cellule.gabarit; as gabarit) {\n <ng-container\n *ngTemplateOutlet=\"gabarit; context: { $implicit: ligne, colonne: cellule.colonne }\"\n />\n } @else if (texteDe(ligne, cellule.colonne); as texte) {\n {{ texte }}\n } @else {\n <span aria-hidden=\"true\">&#8212;</span><span class=\"st-vh\">{{ libelles().nonRenseigne }}</span>\n }\n</ng-template>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{--st-tableau-plan-figee: 1;--st-tableau-plan-entete: 2;--st-tableau-plan-coin: 3;display:block}.st-data-table__barre,.st-data-table__actions{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);align-items:center}.st-data-table__barre{justify-content:space-between}.st-data-table__barre:has(.st-data-table__selection,.st-data-table__actions:not(:empty)){min-block-size:var(--st-cible-tactile-min);margin-block-end:var(--st-espacement-2)}.st-data-table__actions{margin-inline-start:auto}.st-data-table__actions:empty{display:none}.st-data-table__selection{margin:0;font-weight:var(--st-graisse-semi)}.st-data-table__zone:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-data-table__zone{position:relative;overflow:auto;border:1px solid var(--st-couleur-bordure);border-radius:var(--st-rayon-md);background:linear-gradient(to right,var(--st-couleur-surface) 30%,transparent) left/2rem 100% no-repeat local,linear-gradient(to left,var(--st-couleur-surface) 30%,transparent) right/2rem 100% no-repeat local,radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.2),transparent) left/.75rem 100% no-repeat scroll,radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.2),transparent) right/.75rem 100% no-repeat scroll,var(--st-couleur-surface)}.st-data-table__tableau{inline-size:100%;font-variant-numeric:tabular-nums;border-collapse:separate;border-spacing:0}.st-data-table__legende{padding:var(--st-espacement-3);font-weight:var(--st-graisse-semi);text-align:start}th,td{padding-block:var(--st-tableau-cellule-bloc, var(--st-espacement-3));padding-inline:var(--st-tableau-cellule-ligne, var(--st-espacement-3));text-align:start;vertical-align:top;overflow-wrap:anywhere;border-block-end:1px solid var(--st-couleur-bordure)}thead th{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret);white-space:nowrap;background:var(--st-couleur-surface-enfoncee)}thead th{position:sticky;inset-block-start:0;z-index:var(--st-tableau-plan-entete)}tbody th{font-weight:var(--st-graisse-semi)}tbody tr:last-child>*{border-block-end:0}@media(hover:hover){tbody tr:hover>*{background:var(--st-couleur-surface-survol)}}tbody tr[data-choisie]>*{background:var(--st-couleur-primaire-discret)}tbody tr[data-choisie]>:first-child{box-shadow:inset 3px 0 0 var(--st-couleur-primaire)}:host-context([dir=rtl]) tbody tr[data-choisie]>:first-child{box-shadow:inset -3px 0 0 var(--st-couleur-primaire)}[data-alignement=fin]{text-align:end}.st-data-table__case{inline-size:var(--st-tableau-largeur-case, 3rem);min-inline-size:var(--st-tableau-largeur-case, 3rem);text-align:center;vertical-align:middle}:host([data-densite=compacte]) th,:host([data-densite=compacte]) td{padding-block:var(--st-tableau-cellule-bloc, var(--st-espacement-1));padding-inline:var(--st-tableau-cellule-ligne, var(--st-espacement-2))}.st-data-table__cocher:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-data-table__cocher{inline-size:var(--st-cible-min);block-size:var(--st-cible-min);margin:0;accent-color:var(--st-couleur-primaire);cursor:pointer}:host([data-figee]) [data-figee]{position:sticky;inset-inline-start:0;z-index:var(--st-tableau-plan-figee);background:var(--st-couleur-surface)}:host([data-figee]) .st-data-table__case+[data-figee]{inset-inline-start:var(--st-tableau-largeur-case, 3rem)}:host([data-figee]) thead [data-figee]{z-index:var(--st-tableau-plan-coin);background:var(--st-couleur-surface-enfoncee)}.st-data-table__vide{padding:var(--st-espacement-5);color:var(--st-couleur-texte-discret);text-align:center}.st-data-table__barre-de-squelette{display:block;block-size:1em;background:var(--st-couleur-surface-enfoncee);border-radius:var(--st-rayon-sm)}@media(prefers-reduced-motion:no-preference){.st-data-table__barre-de-squelette{animation:st-tableau-pulsation var(--st-anim-respiration, 1.6s) ease-in-out infinite}}@keyframes st-tableau-pulsation{50%{opacity:.5}}.st-data-table__tableau[aria-busy=true]{cursor:progress}@media(forced-colors:active){tbody tr[data-choisie]>:first-child{box-shadow:none;border-inline-start:3px solid Highlight}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StSortHeader, selector: "button[stSortHeader]", inputs: ["sens"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5005
- }
5006
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StDataTable, decorators: [{
5007
- type: Component,
5008
- args: [{ selector: 'st-data-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, StSortHeader], host: {
5009
- class: 'st-data-table',
5010
- '[attr.data-densite]': 'densite()',
5011
- '[attr.data-figee]': 'premiereColonneFigee() ? "" : null',
5012
- }, template: "<!-- UN seul ng-content : la projection se decide a la compilation, et un second\n emplacement de meme selecteur, dans une autre branche, ne recevrait jamais\n rien. -->\n<div class=\"st-data-table__barre\">\n @if (selectionnable()) {\n <p class=\"st-data-table__selection\" role=\"status\" [id]=\"identifiants.selection\">\n {{ selection().length > 0 ? libelles().lignesSelectionnees(selection().length) : '' }}\n </p>\n }\n <div class=\"st-data-table__actions\">\n <ng-content select=\"[stActionsDeTableau]\" />\n </div>\n</div>\n\n<!-- UX-6 : zone nommee par la legende ; focalisable quand elle deborde, et par\n programme toujours, pour recevoir le focus apres un changement de page. -->\n<div\n #zone\n class=\"st-data-table__zone\"\n role=\"region\"\n [attr.aria-labelledby]=\"identifiants.legende\"\n [attr.tabindex]=\"deborde() ? 0 : -1\"\n [attr.data-deborde]=\"deborde() ? '' : null\"\n [style.max-block-size]=\"hauteurMax()\"\n [style.scroll-padding-inline-start.px]=\"largeurFigee()\"\n>\n <table class=\"st-data-table__tableau\" [attr.aria-busy]=\"chargement() ? 'true' : null\">\n <caption\n class=\"st-data-table__legende\"\n [id]=\"identifiants.legende\"\n [class.st-vh]=\"legendeMasquee()\"\n >\n {{\n legende()\n }}\n </caption>\n <thead>\n <tr>\n @if (selectionnable()) {\n <th class=\"st-data-table__case\" scope=\"col\" data-figee>\n <input\n class=\"st-data-table__cocher\"\n type=\"checkbox\"\n [checked]=\"toutesChoisies()\"\n [indeterminate]=\"quelquesChoisies()\"\n [disabled]=\"rangees().length === 0\"\n [attr.aria-label]=\"libelles().selectionnerLaPage(rangees().length)\"\n (change)=\"basculerTout()\"\n />\n </th>\n }\n @for (cellule of cellules(); track cellule.colonne.cle; let premiere = $first) {\n <th\n scope=\"col\"\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n [attr.aria-sort]=\"ariaSort(cellule.colonne)\"\n >\n @if (cellule.colonne.triable) {\n <button\n stSortHeader\n type=\"button\"\n [sens]=\"sensDe(cellule.colonne)\"\n (click)=\"trier(cellule.colonne)\"\n >\n {{ cellule.colonne.libelle }}\n </button>\n } @else if (cellule.colonne.libelleMasque) {\n <span class=\"st-vh\">{{ cellule.colonne.libelle }}</span>\n } @else {\n {{ cellule.colonne.libelle }}\n }\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (rang of squelette(); track rang) {\n <tr class=\"st-data-table__squelette\" aria-hidden=\"true\">\n @for (colonne of colonnesDeSquelette(); track colonne) {\n <td><span class=\"st-data-table__barre-de-squelette\"></span></td>\n }\n </tr>\n }\n @for (rangee of rangees(); track rangee.identifiant) {\n <tr [attr.data-choisie]=\"rangee.choisie ? '' : null\">\n @if (selectionnable()) {\n <td class=\"st-data-table__case\" data-figee>\n <input\n class=\"st-data-table__cocher\"\n type=\"checkbox\"\n [checked]=\"rangee.choisie\"\n [attr.aria-label]=\"libelles().selectionnerLaLigne(rangee.nom ?? rangee.identifiant)\"\n (change)=\"basculer(rangee.identifiant)\"\n />\n </td>\n }\n @for (cellule of cellules(); track cellule.colonne.cle; let premiere = $first) {\n @if (cellule.enTete) {\n <th\n scope=\"row\"\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valeur;\n context: { $implicit: rangee.valeur, cellule: cellule }\n \"\n />\n </th>\n } @else {\n <td\n [attr.data-alignement]=\"cellule.colonne.alignement ?? 'debut'\"\n [attr.data-figee]=\"premiere && premiereColonneFigee() ? '' : null\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n valeur;\n context: { $implicit: rangee.valeur, cellule: cellule }\n \"\n />\n </td>\n }\n }\n </tr>\n } @empty {\n @if (!chargement()) {\n <tr>\n <td class=\"st-data-table__vide\" [attr.colspan]=\"nombreDeColonnes()\">\n @if (gabaritVide(); as vide) {\n <ng-container *ngTemplateOutlet=\"vide.gabarit\" />\n } @else {\n {{ texteVide() ?? libelles().tableauVide }}\n }\n </td>\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n\n<ng-template #valeur let-ligne let-cellule=\"cellule\">\n @if (cellule.gabarit; as gabarit) {\n <ng-container\n *ngTemplateOutlet=\"gabarit; context: { $implicit: ligne, colonne: cellule.colonne }\"\n />\n } @else if (texteDe(ligne, cellule.colonne); as texte) {\n {{ texte }}\n } @else {\n <span aria-hidden=\"true\">&#8212;</span><span class=\"st-vh\">{{ libelles().nonRenseigne }}</span>\n }\n</ng-template>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{--st-tableau-plan-figee: 1;--st-tableau-plan-entete: 2;--st-tableau-plan-coin: 3;display:block}.st-data-table__barre,.st-data-table__actions{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);align-items:center}.st-data-table__barre{justify-content:space-between}.st-data-table__barre:has(.st-data-table__selection,.st-data-table__actions:not(:empty)){min-block-size:var(--st-cible-tactile-min);margin-block-end:var(--st-espacement-2)}.st-data-table__actions{margin-inline-start:auto}.st-data-table__actions:empty{display:none}.st-data-table__selection{margin:0;font-weight:var(--st-graisse-semi)}.st-data-table__zone:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-data-table__zone{position:relative;overflow:auto;border:1px solid var(--st-couleur-bordure);border-radius:var(--st-rayon-md);background:linear-gradient(to right,var(--st-couleur-surface) 30%,transparent) left/2rem 100% no-repeat local,linear-gradient(to left,var(--st-couleur-surface) 30%,transparent) right/2rem 100% no-repeat local,radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.2),transparent) left/.75rem 100% no-repeat scroll,radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.2),transparent) right/.75rem 100% no-repeat scroll,var(--st-couleur-surface)}.st-data-table__tableau{inline-size:100%;font-variant-numeric:tabular-nums;border-collapse:separate;border-spacing:0}.st-data-table__legende{padding:var(--st-espacement-3);font-weight:var(--st-graisse-semi);text-align:start}th,td{padding-block:var(--st-tableau-cellule-bloc, var(--st-espacement-3));padding-inline:var(--st-tableau-cellule-ligne, var(--st-espacement-3));text-align:start;vertical-align:top;overflow-wrap:anywhere;border-block-end:1px solid var(--st-couleur-bordure)}thead th{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte-discret);white-space:nowrap;background:var(--st-couleur-surface-enfoncee)}thead th{position:sticky;inset-block-start:0;z-index:var(--st-tableau-plan-entete)}tbody th{font-weight:var(--st-graisse-semi)}tbody tr:last-child>*{border-block-end:0}@media(hover:hover){tbody tr:hover>*{background:var(--st-couleur-surface-survol)}}tbody tr[data-choisie]>*{background:var(--st-couleur-primaire-discret)}tbody tr[data-choisie]>:first-child{box-shadow:inset 3px 0 0 var(--st-couleur-primaire)}:host-context([dir=rtl]) tbody tr[data-choisie]>:first-child{box-shadow:inset -3px 0 0 var(--st-couleur-primaire)}[data-alignement=fin]{text-align:end}.st-data-table__case{inline-size:var(--st-tableau-largeur-case, 3rem);min-inline-size:var(--st-tableau-largeur-case, 3rem);text-align:center;vertical-align:middle}:host([data-densite=compacte]) th,:host([data-densite=compacte]) td{padding-block:var(--st-tableau-cellule-bloc, var(--st-espacement-1));padding-inline:var(--st-tableau-cellule-ligne, var(--st-espacement-2))}.st-data-table__cocher:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-data-table__cocher{inline-size:var(--st-cible-min);block-size:var(--st-cible-min);margin:0;accent-color:var(--st-couleur-primaire);cursor:pointer}:host([data-figee]) [data-figee]{position:sticky;inset-inline-start:0;z-index:var(--st-tableau-plan-figee);background:var(--st-couleur-surface)}:host([data-figee]) .st-data-table__case+[data-figee]{inset-inline-start:var(--st-tableau-largeur-case, 3rem)}:host([data-figee]) thead [data-figee]{z-index:var(--st-tableau-plan-coin);background:var(--st-couleur-surface-enfoncee)}.st-data-table__vide{padding:var(--st-espacement-5);color:var(--st-couleur-texte-discret);text-align:center}.st-data-table__barre-de-squelette{display:block;block-size:1em;background:var(--st-couleur-surface-enfoncee);border-radius:var(--st-rayon-sm)}@media(prefers-reduced-motion:no-preference){.st-data-table__barre-de-squelette{animation:st-tableau-pulsation var(--st-anim-respiration, 1.6s) ease-in-out infinite}}@keyframes st-tableau-pulsation{50%{opacity:.5}}.st-data-table__tableau[aria-busy=true]{cursor:progress}@media(forced-colors:active){tbody tr[data-choisie]>:first-child{box-shadow:none;border-inline-start:3px solid Highlight}}\n"] }]
5013
- }], ctorParameters: () => [], propDecorators: { legende: [{ type: i0.Input, args: [{ isSignal: true, alias: "legende", required: true }] }], legendeMasquee: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendeMasquee", required: false }] }], colonnes: [{ type: i0.Input, args: [{ isSignal: true, alias: "colonnes", required: true }] }], lignes: [{ type: i0.Input, args: [{ isSignal: true, alias: "lignes", required: true }] }], cle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cle", required: true }] }], enTeteDeLigne: [{ type: i0.Input, args: [{ isSignal: true, alias: "enTeteDeLigne", required: false }] }], premiereColonneFigee: [{ type: i0.Input, args: [{ isSignal: true, alias: "premiereColonneFigee", required: false }] }], hauteurMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "hauteurMax", required: false }] }], densite: [{ type: i0.Input, args: [{ isSignal: true, alias: "densite", required: false }] }], chargement: [{ type: i0.Input, args: [{ isSignal: true, alias: "chargement", required: false }] }], selectionnable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionnable", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], tri: [{ type: i0.Input, args: [{ isSignal: true, alias: "tri", required: false }] }, { type: i0.Output, args: ["triChange"] }], texteVide: [{ type: i0.Input, args: [{ isSignal: true, alias: "texteVide", required: false }] }], gabarits: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StCellule), { isSignal: true }] }], gabaritVide: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StTableauVide), { isSignal: true }] }], zone: [{ type: i0.ViewChild, args: ['zone', { isSignal: true }] }] } });
5014
- /** Texte d'une valeur ; `null` quand elle est absente, pour le dire (UX de la vague 4). */
5015
- function texteDe(valeur) {
5016
- if (valeur === null || valeur === undefined || valeur === '')
5017
- return null;
5018
- if (typeof valeur === 'string')
5019
- return valeur;
5020
- if (typeof valeur === 'number' || typeof valeur === 'boolean' || typeof valeur === 'bigint') {
5021
- return String(valeur);
5022
- }
5023
- return null;
5024
- }
5025
-
5026
- /**
5027
- * Filtre actif, retirable : « Statut : Expediee ✕ ».
5028
- *
5029
- * ```html
5030
- * <button stFilterChip libelle="Statut : Expediee" (click)="retirer('statut')"></button>
5031
- * ```
5032
- *
5033
- * ⚠️ **Toute la puce est le bouton**, et son nom CONTIENT le texte visible —
5034
- * « Retirer le filtre Statut : Expediee » (WCAG 2.5.3) : une commande vocale
5035
- * « cliquer Statut » l'atteint. Une croix seule, a cote d'un texte inerte, serait
5036
- * une cible de quelques pixels sans nom.
5037
- */
5038
- class StFilterChip {
5039
- libelles = inject(ST_LIBELLES);
5040
- libelle = input.required(/* @ts-ignore */
5041
- ...(ngDevMode ? [{ debugName: "libelle" }] : /* istanbul ignore next */ []));
5042
- nom = computed(() => this.libelles().retirerLeFiltre(this.libelle()), /* @ts-ignore */
5043
- ...(ngDevMode ? [{ debugName: "nom" }] : /* istanbul ignore next */ []));
5044
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFilterChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
5045
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: StFilterChip, isStandalone: true, selector: "button[stFilterChip]", inputs: { libelle: { classPropertyName: "libelle", publicName: "libelle", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "type": "button" }, properties: { "attr.aria-label": "nom()" }, classAttribute: "st-filter-chip" }, ngImport: i0, template: "<span class=\"st-filter-chip__libelle\">{{ libelle() }}</span>\n<st-icon class=\"st-filter-chip__croix\" nom=\"croix\" taille=\"sm\" />\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}:host{display:inline-flex;gap:var(--st-espacement-1);align-items:center;min-block-size:calc(var(--st-cible-min) + var(--st-espacement-2));padding-block:var(--st-espacement-1);padding-inline:var(--st-espacement-3) var(--st-espacement-2);font:inherit;color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-pleine)}@media(hover:hover){:host:hover{background:var(--st-couleur-surface-survol)}}.st-filter-chip__croix{flex:none}\n"], dependencies: [{ kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5046
- }
5047
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFilterChip, decorators: [{
5048
- type: Component,
5049
- args: [{ selector: 'button[stFilterChip]', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StIcon], host: {
5050
- class: 'st-filter-chip',
5051
- type: 'button',
5052
- '[attr.aria-label]': 'nom()',
5053
- }, template: "<span class=\"st-filter-chip__libelle\">{{ libelle() }}</span>\n<st-icon class=\"st-filter-chip__croix\" nom=\"croix\" taille=\"sm\" />\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}:host{display:inline-flex;gap:var(--st-espacement-1);align-items:center;min-block-size:calc(var(--st-cible-min) + var(--st-espacement-2));padding-block:var(--st-espacement-1);padding-inline:var(--st-espacement-3) var(--st-espacement-2);font:inherit;color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-pleine)}@media(hover:hover){:host:hover{background:var(--st-couleur-surface-survol)}}.st-filter-chip__croix{flex:none}\n"] }]
5054
- }], propDecorators: { libelle: [{ type: i0.Input, args: [{ isSignal: true, alias: "libelle", required: true }] }] } });
5055
-
5056
- let compteur$2 = 0;
5057
- /**
5058
- * Barre de filtres : les champs, puis les filtres en vigueur, retirables.
5059
- *
5060
- * ```html
5061
- * <st-filter-bar [filtresActifs]="actifs()" (retrait)="retirer($event)" (effacement)="effacer()">
5062
- * <st-select libelle="Statut" [options]="statuts" [(value)]="statut" />
5063
- * </st-filter-bar>
5064
- * ```
5065
- *
5066
- * Un formulaire NOMME : repere « Filtres » dans la liste des reperes du lecteur
5067
- * d'ecran. Pas un repere `search` : un `st-search-field` pose dedans en porte deja
5068
- * un, et deux reperes imbriques se liraient en double.
5069
- *
5070
- * ⚠️ **Les filtres s'appliquent au changement**, par l'application : Entree dans
5071
- * un champ n'envoie rien, et ne recharge pas la page.
5072
- *
5073
- * ⚠️ **Retirer un filtre rend le focus a la puce suivante**, sinon a la
5074
- * precedente, sinon au formulaire ; « Effacer les filtres » le rend au formulaire
5075
- * (UX-14). Le focus ne tombe jamais sur le document, et le retrait s'annonce.
5076
- *
5077
- * ⚠️ **Dans l'adresse, des valeurs d'enumeration seulement** (SEC-2) : un texte
5078
- * cherche, un nom, un IBAN n'y vont pas. Le composant ne touche pas a l'adresse ;
5079
- * la regle est pour l'application qui le ferait.
5080
- */
5081
- class StFilterBar {
5082
- libelles = inject(ST_LIBELLES);
5083
- element = inject(ElementRef);
5084
- injecteur = inject(Injector);
5085
- /** Nom du formulaire. Par defaut : « Filtres ». */
5086
- etiquette = input(null, /* @ts-ignore */
5087
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
5088
- filtresActifs = input([], /* @ts-ignore */
5089
- ...(ngDevMode ? [{ debugName: "filtresActifs" }] : /* istanbul ignore next */ []));
5090
- retrait = output();
5091
- effacement = output();
5092
- annonce = new AnnonceLocale(inject(DestroyRef));
5093
- identifiantActifs = `st-filtres-${(compteur$2 += 1).toString()}-actifs`;
5094
- etiquetteLue = computed(() => this.etiquette() ?? this.libelles().filtres, /* @ts-ignore */
5095
- ...(ngDevMode ? [{ debugName: "etiquetteLue" }] : /* istanbul ignore next */ []));
5096
- retirer(filtre, indice) {
5097
- this.retrait.emit(filtre.cle);
5098
- this.annonce.annoncer(this.libelles().filtreRetire(filtre.libelle));
5099
- this.apresLeRendu(() => {
5100
- const puces = this.element.nativeElement.querySelectorAll('.st-filter-chip');
5101
- return puces[Math.min(indice, puces.length - 1)] ?? null;
5102
- });
5103
- }
5104
- effacer() {
5105
- this.effacement.emit();
5106
- this.annonce.annoncer(this.libelles().filtresEffaces);
5107
- this.apresLeRendu(() => null);
5108
- }
5109
- apresLeRendu(cible) {
5110
- afterNextRender(() => {
5111
- const formulaire = this.element.nativeElement.querySelector('form');
5112
- (cible() ?? formulaire)?.focus();
5113
- }, { injector: this.injecteur });
5114
- }
5115
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFilterBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
5116
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StFilterBar, isStandalone: true, selector: "st-filter-bar", inputs: { etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: false, transformFunction: null }, filtresActifs: { classPropertyName: "filtresActifs", publicName: "filtresActifs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retrait: "retrait", effacement: "effacement" }, host: { classAttribute: "st-filter-bar" }, ngImport: i0, template: "<!-- tabindex -1 : cible du focus quand la derniere puce disparait. -->\n<form\n class=\"st-filter-bar__formulaire\"\n tabindex=\"-1\"\n [attr.aria-label]=\"etiquetteLue()\"\n (submit)=\"$event.preventDefault()\"\n>\n <div class=\"st-filter-bar__champs\">\n <ng-content />\n </div>\n\n @if (filtresActifs().length > 0) {\n <div class=\"st-filter-bar__actifs\">\n <p class=\"st-filter-bar__titre\" [id]=\"identifiantActifs\">{{ libelles().filtresActifs }}</p>\n <ul class=\"st-filter-bar__puces\" [attr.aria-labelledby]=\"identifiantActifs\">\n @for (filtre of filtresActifs(); track filtre.cle; let indice = $index) {\n <li>\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stFilterChip, et le libelle est requis. -->\n <button\n stFilterChip\n type=\"button\"\n [libelle]=\"filtre.libelle\"\n (click)=\"retirer(filtre, indice)\"\n ></button>\n </li>\n }\n </ul>\n <button stButton type=\"button\" variante=\"discret\" (click)=\"effacer()\">\n {{ libelles().effacerLesFiltres }}\n </button>\n </div>\n }\n</form>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-filter-bar__formulaire:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-filter-bar__formulaire{display:grid;gap:var(--st-espacement-3)}.st-filter-bar__champs{display:flex;flex-wrap:wrap;gap:var(--st-espacement-3) var(--st-espacement-4);align-items:flex-end}.st-filter-bar__actifs{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);align-items:center}.st-filter-bar__titre{margin:0;font-weight:var(--st-graisse-semi)}.st-filter-bar__puces{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);padding:0;margin:0;list-style:none}\n"], dependencies: [{ kind: "component", type: StButton, selector: "button[stButton]", inputs: ["variante", "taille", "pleineLargeur", "iconeAvant", "iconeApres", "chargement", "indisponible"] }, { kind: "component", type: StFilterChip, selector: "button[stFilterChip]", inputs: ["libelle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5117
- }
5118
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StFilterBar, decorators: [{
5119
- type: Component,
5120
- args: [{ selector: 'st-filter-bar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StButton, StFilterChip], host: { class: 'st-filter-bar' }, template: "<!-- tabindex -1 : cible du focus quand la derniere puce disparait. -->\n<form\n class=\"st-filter-bar__formulaire\"\n tabindex=\"-1\"\n [attr.aria-label]=\"etiquetteLue()\"\n (submit)=\"$event.preventDefault()\"\n>\n <div class=\"st-filter-bar__champs\">\n <ng-content />\n </div>\n\n @if (filtresActifs().length > 0) {\n <div class=\"st-filter-bar__actifs\">\n <p class=\"st-filter-bar__titre\" [id]=\"identifiantActifs\">{{ libelles().filtresActifs }}</p>\n <ul class=\"st-filter-bar__puces\" [attr.aria-labelledby]=\"identifiantActifs\">\n @for (filtre of filtresActifs(); track filtre.cle; let indice = $index) {\n <li>\n <!-- eslint-disable-next-line @angular-eslint/template/elements-content --\n Le contenu vient du gabarit de stFilterChip, et le libelle est requis. -->\n <button\n stFilterChip\n type=\"button\"\n [libelle]=\"filtre.libelle\"\n (click)=\"retirer(filtre, indice)\"\n ></button>\n </li>\n }\n </ul>\n <button stButton type=\"button\" variante=\"discret\" (click)=\"effacer()\">\n {{ libelles().effacerLesFiltres }}\n </button>\n </div>\n }\n</form>\n<span class=\"st-vh\" aria-live=\"polite\">{{ annonce.texte() }}</span>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:block}.st-filter-bar__formulaire:focus{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-filter-bar__formulaire{display:grid;gap:var(--st-espacement-3)}.st-filter-bar__champs{display:flex;flex-wrap:wrap;gap:var(--st-espacement-3) var(--st-espacement-4);align-items:flex-end}.st-filter-bar__actifs{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);align-items:center}.st-filter-bar__titre{margin:0;font-weight:var(--st-graisse-semi)}.st-filter-bar__puces{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);padding:0;margin:0;list-style:none}\n"] }]
5121
- }], propDecorators: { etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }], filtresActifs: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtresActifs", required: false }] }], retrait: [{ type: i0.Output, args: ["retrait"] }], effacement: [{ type: i0.Output, args: ["effacement"] }] } });
5122
-
5123
- let compteur$1 = 0;
5124
- /**
5125
- * Choix du theme : systeme, clair, sombre.
5126
- *
5127
- * ⚠️ **Trois etats, donc PAS un interrupteur.** `role="switch"` et
5128
- * `aria-pressed` sont binaires : poses sur un choix a trois valeurs, ils
5129
- * annoncent « active » ou « desactive » sans jamais dire lequel des trois est
5130
- * retenu. Un groupe de boutons radio dit exactement « Sombre, bouton radio,
5131
- * coche, 3 sur 3 ».
5132
- *
5133
- * ⚠️ **Composant de PRESENTATION.** Il n'injecte pas `StTheme` : `ui` ne depend
5134
- * pas de `@starterlib/core` (ADR 0003). La coquille le relie au noyau :
5135
- *
5136
- * ```html
5137
- * <st-theme-toggle [choix]="theme.choix()" (choisi)="theme.choisir($event)" />
5138
- * ```
5139
- */
5140
- class StThemeToggle {
5141
- libelles = inject(ST_LIBELLES);
5142
- choix = input.required(/* @ts-ignore */
5143
- ...(ngDevMode ? [{ debugName: "choix" }] : /* istanbul ignore next */ []));
5144
- /** Legende visible. Masquee par defaut, mais toujours lue : elle nomme le groupe. */
5145
- legendeVisible = input(false, { ...(ngDevMode ? { debugName: "legendeVisible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
5146
- choisi = output();
5147
- /** Unique par instance : deux bascules sur la meme page partageraient leur groupe radio. */
5148
- nom = `st-theme-${(compteur$1 += 1).toString()}`;
5149
- legende = computed(() => this.libelles().theme, /* @ts-ignore */
5150
- ...(ngDevMode ? [{ debugName: "legende" }] : /* istanbul ignore next */ []));
5151
- options = computed(() => {
5152
- const libelles = this.libelles();
5153
- const texte = {
5154
- systeme: libelles.themeSysteme,
5155
- clair: libelles.themeClair,
5156
- sombre: libelles.themeSombre,
5157
- };
5158
- return CHOIX_DE_THEME.map((valeur) => ({ valeur, libelle: texte[valeur] }));
5159
- }, /* @ts-ignore */
5160
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
5161
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StThemeToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
5162
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StThemeToggle, isStandalone: true, selector: "st-theme-toggle", inputs: { choix: { classPropertyName: "choix", publicName: "choix", isSignal: true, isRequired: true, transformFunction: null }, legendeVisible: { classPropertyName: "legendeVisible", publicName: "legendeVisible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { choisi: "choisi" }, host: { classAttribute: "st-theme-toggle" }, ngImport: i0, template: "<fieldset class=\"st-theme-toggle__groupe\">\n <legend [class.st-vh]=\"!legendeVisible()\">{{ legende() }}</legend>\n @for (option of options(); track option.valeur) {\n <label>\n <input\n type=\"radio\"\n [name]=\"nom\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === choix()\"\n (change)=\"choisi.emit(option.valeur)\"\n />\n <span>{{ option.libelle }}</span>\n </label>\n }\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block}.st-theme-toggle__groupe{display:inline-flex;flex-wrap:wrap;gap:0;padding:0;margin:0;border:0}.st-theme-toggle__groupe legend{padding:0;margin-block-end:var(--st-espacement-2);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi)}.st-theme-toggle__groupe input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-theme-toggle__groupe label{display:inline-flex;align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-1) var(--st-espacement-3);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte)}.st-theme-toggle__groupe label+label{margin-inline-start:-1px}.st-theme-toggle__groupe label:first-of-type{border-start-start-radius:var(--st-rayon-md);border-end-start-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:last-of-type{border-start-end-radius:var(--st-rayon-md);border-end-end-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:has(input:checked){position:relative;font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-theme-toggle__groupe label:has(input:focus-visible){position:relative;z-index:1;outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}@media(forced-colors:active){.st-theme-toggle__groupe label:has(input:checked){forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5163
- }
5164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StThemeToggle, decorators: [{
5165
- type: Component,
5166
- args: [{ selector: 'st-theme-toggle', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'st-theme-toggle' }, template: "<fieldset class=\"st-theme-toggle__groupe\">\n <legend [class.st-vh]=\"!legendeVisible()\">{{ legende() }}</legend>\n @for (option of options(); track option.valeur) {\n <label>\n <input\n type=\"radio\"\n [name]=\"nom\"\n [value]=\"option.valeur\"\n [checked]=\"option.valeur === choix()\"\n (change)=\"choisi.emit(option.valeur)\"\n />\n <span>{{ option.libelle }}</span>\n </label>\n }\n</fieldset>\n", styles: [".st-vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}:host([hidden]){display:none!important}:host{display:inline-block}.st-theme-toggle__groupe{display:inline-flex;flex-wrap:wrap;gap:0;padding:0;margin:0;border:0}.st-theme-toggle__groupe legend{padding:0;margin-block-end:var(--st-espacement-2);font-size:var(--st-taille-sm);font-weight:var(--st-graisse-semi)}.st-theme-toggle__groupe input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.st-theme-toggle__groupe label{display:inline-flex;align-items:center;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-1) var(--st-espacement-3);color:var(--st-couleur-texte);cursor:pointer;background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte)}.st-theme-toggle__groupe label+label{margin-inline-start:-1px}.st-theme-toggle__groupe label:first-of-type{border-start-start-radius:var(--st-rayon-md);border-end-start-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:last-of-type{border-start-end-radius:var(--st-rayon-md);border-end-end-radius:var(--st-rayon-md)}.st-theme-toggle__groupe label:has(input:checked){position:relative;font-weight:var(--st-graisse-semi);color:var(--st-couleur-sur-primaire);background:var(--st-couleur-primaire);border-color:var(--st-couleur-primaire)}.st-theme-toggle__groupe label:has(input:focus-visible){position:relative;z-index:1;outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}@media(forced-colors:active){.st-theme-toggle__groupe label:has(input:checked){forced-color-adjust:none;color:HighlightText;background:Highlight}}\n"] }]
5167
- }], propDecorators: { choix: [{ type: i0.Input, args: [{ isSignal: true, alias: "choix", required: true }] }], legendeVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendeVisible", required: false }] }], choisi: [{ type: i0.Output, args: ["choisi"] }] } });
5168
-
5169
- /**
5170
- * Nom de chaque langue ECRIT DANS CETTE LANGUE, et jamais traduit.
5171
- *
5172
- * ⚠️ Une personne qui ne lit pas le francais ne cherche pas « Anglais » : elle
5173
- * cherche « English ». Traduire le nom des langues dans la langue courante rend
5174
- * le selecteur illisible precisement pour qui en a besoin.
5175
- */
5176
- const ENDONYMES = {
5177
- fr: 'Français',
5178
- en: 'English',
5179
- };
5180
- let compteur = 0;
5181
- /**
5182
- * Choix de la langue de l'interface.
5183
- *
5184
- * ⚠️ **Chaque option porte son propre `lang`.** Sans lui, un lecteur d'ecran
5185
- * regle en francais lit « English » avec une voix francaise, et le mot devient
5186
- * incomprehensible pour la personne anglophone qui cherchait justement a
5187
- * l'entendre.
5188
- *
5189
- * ⚠️ **Composant de PRESENTATION.** Il n'injecte pas `StLangue` : `ui` ne depend
5190
- * pas de `@starterlib/core` (ADR 0003). La coquille le relie au noyau :
5191
- *
5192
- * ```html
5193
- * <st-lang-switch [courante]="langue.courante()" (choisie)="langue.changer($event)" />
2329
+ * ```html
2330
+ * <st-lang-switch [courante]="langue.courante()" (choisie)="langue.changer($event)" />
5194
2331
  * ```
5195
2332
  */
5196
2333
  class StLangSwitch {
@@ -5746,16 +2883,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
5746
2883
  }], ctorParameters: () => [], propDecorators: { ressource: [{ type: i0.Input, args: [{ isSignal: true, alias: "ressource", required: false }] }], etat: [{ type: i0.Input, args: [{ isSignal: true, alias: "etat", required: false }] }], estVide: [{ type: i0.Input, args: [{ isSignal: true, alias: "estVide", required: false }] }], squelette: [{ type: i0.Input, args: [{ isSignal: true, alias: "squelette", required: false }] }], lignesDeSquelette: [{ type: i0.Input, args: [{ isSignal: true, alias: "lignesDeSquelette", required: false }] }], annonces: [{ type: i0.Input, args: [{ isSignal: true, alias: "annonces", required: false }] }], gabaritSucces: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StSucces), { isSignal: true }] }], gabaritErreur: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StErreur), { isSignal: true }] }], gabaritVide: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StVide), { isSignal: true }] }], gabaritHorsLigne: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StHorsLigne), { isSignal: true }] }], gabaritChargement: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StChargement), { isSignal: true }] }] } });
5747
2884
 
5748
2885
  /**
5749
- * @starterlib/ui — bibliotheque de composants du socle frontal.
2886
+ * @starterlib/ui — le TRONC de la bibliotheque de composants du socle frontal.
5750
2887
  *
5751
2888
  * Tout composant s'emploie sans enveloppe : les elements interactifs sont des
5752
2889
  * directives posees sur les elements natifs (`button[stButton]`), jamais des
5753
2890
  * balises qui en enrobent un.
2891
+ *
2892
+ * ⚠️ **Ce qui vit ici est dimensionne par le premier ecran de l'HOTE**, et non
2893
+ * par l'union des consommateurs (ADR 0031, decision 2). Un paquet partage en
2894
+ * singleton part ENTIER : la federation annule l'elagage, donc tout ce que la
2895
+ * coquille ne peint pas lui coute plein tarif au premier ecran, et ne coute
2896
+ * RIEN au remote qui l'affiche — le tronc y est deja charge. Restent donc ici
2897
+ * le noyau (les jetons fournis a la racine, le contrat d'injection) et les
2898
+ * familles que la coquille peint : icone, texte, actions, etats, retour,
2899
+ * indicateurs, structure, transverses.
2900
+ *
2901
+ * Sont sortis en 0.8.0, chacun derriere son propre specificateur :
2902
+ *
2903
+ * | Point d'entree | Ce qu'il porte |
2904
+ * |---|---|
2905
+ * | `@starterlib/ui/saisie` | les champs de formulaire, le calendrier, le televersement |
2906
+ * | `@starterlib/ui/listes` | liste, table de donnees, pagination, tri, filtres |
2907
+ * | `@starterlib/ui/donnees` | montant, date, date relative, nombre, valeur masquee, indicateur |
2908
+ * | `@starterlib/ui/libelles-en` | le catalogue anglais |
2909
+ * | `@starterlib/ui/navigation` | le panneau lateral |
2910
+ * | `@starterlib/ui/onglets` | les onglets |
2911
+ * | `@starterlib/ui/selection` | liste deroulante filtrante et choix multiple |
2912
+ * | `@starterlib/ui/arbre` | l'arborescence |
2913
+ * | `@starterlib/ui/surfaces` | ce qui se pose au-dessus de la page |
2914
+ * | `@starterlib/ui/formulaires` | les champs qui LISENT leur saisie |
2915
+ *
2916
+ * ⚠️ **Le tronc ne re-exporte pas ce qui est parti**, et ce n'est pas un oubli :
2917
+ * `@starterlib/ui/saisie` importe le tronc, donc un tronc qui re-exporterait
2918
+ * `@starterlib/ui/saisie` fermerait un cycle entre points d'entree, que
2919
+ * ng-packagr refuse. Le baril de compatibilite n'existe que la ou il ne cree
2920
+ * aucun cycle : `@starterlib/ui/interactions`, qu'aucun des quatre points
2921
+ * d'entree qu'il re-exporte n'importe. Un consommateur qui monte en 0.8.0
2922
+ * deplace donc ses imports de saisie, de listes et de donnees ; il n'a rien a
2923
+ * changer pour ce qui est reste.
2924
+ *
2925
+ * `st-code` reste ici, bien qu'il appartienne a la famille « donnees » :
2926
+ * `etats/st-error.ts` l'importe pour afficher un identifiant de trace, et cet
2927
+ * import ferait sortir le tronc vers un point d'entree secondaire.
5754
2928
  */
5755
2929
 
5756
2930
  /**
5757
2931
  * Generated bundle index. Do not edit.
5758
2932
  */
5759
2933
 
5760
- export { AnnonceLocale, CHEMIN_DU_SPRITE, ContratDeChamp, ControleDeSaisie, FUSEAU_PAR_DEFAUT, GABARITS_D_ETAT, IDENTIFIANT_DU_CONTENU_PRINCIPAL, LIBELLES_EN, LIBELLES_FR, NOMS_DICONE, PROPRIETE_HAUTEUR_ENTETE, REGION_VIVANTE_PARENTE, ST_LIBELLES, ST_LOCALE, StAppShell, StAuthShell, StAvatar, StBadge, StBanner, StBreadcrumb, StButton, StButtonGroup, StCalendar, StCard, StCellule, StChargement, StCheckbox, StCode, StControleDeChamp, StCopyButton, StCount, StDataTable, StDate, StDescription, StDescriptionList, StDivider, StElementDeListe, StEmpty, StErreur, StError, StErrorSummary, StFieldset, StFileUpload, StFilterBar, StFilterChip, StFooter, StFormActions, StFormField, StHeader, StHeading, StHorsLigne, StIbanField, StIcon, StIconButton, StInlineMessage, StInput, StLangSwitch, StLink, StList, StLogo, StMasked, StMoney, StNavLink, StOfflineBanner, StOtpField, StPageHeader, StPageTitle, StPagination, StPasswordField, StPhoneField, StProgress, StRadioGroup, StRelativeTime, StRouteProgress, StSearchField, StSection, StSelect, StSettingsRow, StSkeleton, StSkipLink, StSortHeader, StSpinner, StStat, StState, StStatusPill, StStepper, StSucces, StSwitch, StTableauVide, StTag, StText, StTextarea, StThemeToggle, StTimer, StVide, cleDeFichier, copierDansLePressePapiers, erreurDate, erreurIban, erreurPlageDeDates, erreurTelephone, erreursDeFichiers, exempleDeDate, filtrerOptions, formaterDuree, fournirCheminDuSprite, fusionnerErreurs, messageDErreur, motifLocal, normaliserPourRecherche, pressePapiersDisponible, provideStarterUi };
2934
+ export { AnnonceLocale, CHEMIN_DU_SPRITE, FUSEAU_PAR_DEFAUT, GABARITS_D_ETAT, IDENTIFIANT_DU_CONTENU_PRINCIPAL, LIBELLES_FR, NOMS_DICONE, PROPRIETE_HAUTEUR_ENTETE, REGION_VIVANTE_PARENTE, ST_LIBELLES, ST_LOCALE, StAppShell, StAuthShell, StAvatar, StBadge, StBanner, StBreadcrumb, StButton, StButtonGroup, StCard, StChargement, StCode, StCopyButton, StDivider, StEmpty, StErreur, StError, StFooter, StFormActions, StHeader, StHeading, StHorsLigne, StIcon, StIconButton, StInlineMessage, StLangSwitch, StLink, StLogo, StNavLink, StOfflineBanner, StPageHeader, StPageTitle, StProgress, StRouteProgress, StSection, StSettingsRow, StSkeleton, StSkipLink, StSpinner, StState, StStatusPill, StStepper, StSucces, StTag, StText, StThemeToggle, StTimer, StVide, copierDansLePressePapiers, formaterDuree, fournirCheminDuSprite, pressePapiersDisponible, provideStarterUi };
5761
2935
  //# sourceMappingURL=starterlib-ui.mjs.map