@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,598 +1,43 @@
1
- import * as i0 from '@angular/core';
2
- import { input, model, computed, ChangeDetectionStrategy, Component, Directive, inject, signal, viewChild, effect, DestroyRef, ElementRef, Injector, output, linkedSignal, afterRenderEffect, afterNextRender, booleanAttribute } from '@angular/core';
3
- import * as i1 from '@angular/aria/tabs';
4
- import { TabList, Tab, Tabs, TabPanel, TabContent } from '@angular/aria/tabs';
5
- import { BreakpointObserver } from '@angular/cdk/layout';
6
- import { NgTemplateOutlet } from '@angular/common';
7
- import { RUPTURES } from '@starterlib/tokens';
8
- import { ST_LIBELLES, ControleDeSaisie, AnnonceLocale, filtrerOptions, StFormField, StControleDeChamp, StIcon, ContratDeChamp, StIconButton } from '@starterlib/ui';
9
- import { StDialog } from '@starterlib/ui/surfaces';
10
- import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox';
11
- import { Listbox, Option } from '@angular/aria/listbox';
12
- import { CdkConnectedOverlay } from '@angular/cdk/overlay';
13
- import { Tree, TreeItem, TreeItemGroup } from '@angular/aria/tree';
14
-
15
- /**
16
- * Onglets, sur les primitives `@angular/aria/tabs`.
17
- *
18
- * ```html
19
- * <st-tabs etiquette="Compte" [onglets]="onglets" [(selection)]="onglet">
20
- * <st-tab-panel valeur="operations">
21
- * <ng-template stTabContent><app-operations /></ng-template>
22
- * </st-tab-panel>
23
- * <st-tab-panel valeur="virements">
24
- * <ng-template stTabContent><app-virements /></ng-template>
25
- * </st-tab-panel>
26
- * </st-tabs>
27
- * ```
28
- *
29
- * ⚠️ **La liste d'onglets est rendue par le composant, a partir de donnees**, et
30
- * non projetee. Les primitives d'Aria relient un onglet a sa liste par
31
- * INJECTION, et un contenu projete garde l'injecteur de l'endroit ou il est
32
- * ecrit : c'est la lecon de `st-menu`, ou des elements projetes ne
33
- * s'enregistraient jamais. Rendue ici, la liste est a coup sur sous la bonne
34
- * primitive — et ses valeurs peuvent etre dites en francais.
35
- *
36
- * Les PANNEAUX, eux, sont projetes : ecrits directement sous `st-tabs`, leur
37
- * injecteur parent est celui de l'hote, qui porte la primitive `Tabs`.
38
- *
39
- * ⚠️ **Activation manuelle par defaut.** En activation automatique, chaque
40
- * fleche SELECTIONNE l'onglet atteint ; son contenu differe se rend, et charge
41
- * ses donnees. Parcourir cinq onglets au clavier lancerait cinq requetes pour en
42
- * lire un. En manuelle, les fleches deplacent le focus et Entree ou Espace
43
- * selectionne. `automatique` reste le bon choix quand les panneaux sont deja la
44
- * et s'affichent sans delai.
45
- */
46
- class StTabs {
47
- onglets = input.required(/* @ts-ignore */
48
- ...(ngDevMode ? [{ debugName: "onglets" }] : /* istanbul ignore next */ []));
49
- /** Nom accessible de la liste : sans lui, plusieurs listes d'onglets se confondent. */
50
- etiquette = input.required(/* @ts-ignore */
51
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
52
- activation = input('manuelle', /* @ts-ignore */
53
- ...(ngDevMode ? [{ debugName: "activation" }] : /* istanbul ignore next */ []));
54
- orientation = input('horizontale', /* @ts-ignore */
55
- ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
56
- /** Valeur de l'onglet selectionne. `undefined` : le premier onglet disponible. */
57
- selection = model(undefined, /* @ts-ignore */
58
- ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
59
- /**
60
- * ⚠️ Toujours un onglet selectionne. Le motif ARIA l'exige : une liste sans
61
- * selection n'affiche aucun panneau, et la page parait vide sans raison.
62
- */
63
- selectionEffective = computed(() => this.selection() ?? this.onglets().find((onglet) => onglet.desactive !== true)?.valeur, /* @ts-ignore */
64
- ...(ngDevMode ? [{ debugName: "selectionEffective" }] : /* istanbul ignore next */ []));
65
- modeAria = computed(() => this.activation() === 'automatique' ? 'follow' : 'explicit', /* @ts-ignore */
66
- ...(ngDevMode ? [{ debugName: "modeAria" }] : /* istanbul ignore next */ []));
67
- orientationAria = computed(() => this.orientation() === 'verticale' ? 'vertical' : 'horizontal', /* @ts-ignore */
68
- ...(ngDevMode ? [{ debugName: "orientationAria" }] : /* istanbul ignore next */ []));
69
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
70
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StTabs, isStandalone: true, selector: "st-tabs", inputs: { onglets: { classPropertyName: "onglets", publicName: "onglets", isSignal: true, isRequired: true, transformFunction: null }, etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: true, transformFunction: null }, activation: { classPropertyName: "activation", publicName: "activation", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange" }, host: { properties: { "attr.data-orientation": "orientation()" }, classAttribute: "st-tabs" }, hostDirectives: [{ directive: i1.Tabs }], ngImport: i0, template: "<div\n class=\"st-tabs__liste\"\n ngTabList\n [attr.aria-label]=\"etiquette()\"\n [selectionMode]=\"modeAria()\"\n [orientation]=\"orientationAria()\"\n [selectedTab]=\"selectionEffective()\"\n (selectedTabChange)=\"selection.set($event)\"\n>\n @for (onglet of onglets(); track onglet.valeur) {\n <button\n class=\"st-tabs__onglet\"\n type=\"button\"\n ngTab\n [value]=\"onglet.valeur\"\n [disabled]=\"onglet.desactive === true\"\n >\n {{ onglet.libelle }}\n </button>\n }\n</div>\n\n<div class=\"st-tabs__panneaux\">\n <ng-content />\n</div>\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}:host([data-orientation=verticale]){display:flex;gap:var(--st-espacement-4)}.st-tabs__liste{display:flex;gap:var(--st-espacement-1);border-block-end:1px solid var(--st-couleur-bordure)}:host([data-orientation=verticale]) .st-tabs__liste{flex-direction:column;border-block-end:0;border-inline-end:1px solid var(--st-couleur-bordure)}.st-tabs__panneaux{flex:1 1 auto;min-inline-size:0}.st-tabs__onglet:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-tabs__onglet{min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-4);font:inherit;color:var(--st-couleur-texte-discret);cursor:pointer;background:none;border:0;border-block-end:3px solid transparent}@media(hover:hover){.st-tabs__onglet:hover{color:var(--st-couleur-texte)}}.st-tabs__onglet[aria-selected=true]{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte);border-block-end-color:var(--st-couleur-primaire)}.st-tabs__onglet[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}@media(forced-colors:active){.st-tabs__onglet[aria-selected=true]{border-block-end-color:Highlight}}\n"], dependencies: [{ kind: "directive", type: TabList, selector: "[ngTabList]", inputs: ["orientation", "wrap", "softDisabled", "focusMode", "selectionMode", "selectedTab", "disabled"], outputs: ["selectedTabChange"], exportAs: ["ngTabList"] }, { kind: "directive", type: Tab, selector: "[ngTab]", inputs: ["id", "disabled", "value"], exportAs: ["ngTab"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
71
- }
72
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabs, decorators: [{
73
- type: Component,
74
- args: [{ selector: 'st-tabs', changeDetection: ChangeDetectionStrategy.OnPush, imports: [TabList, Tab], hostDirectives: [Tabs], host: {
75
- class: 'st-tabs',
76
- '[attr.data-orientation]': 'orientation()',
77
- }, template: "<div\n class=\"st-tabs__liste\"\n ngTabList\n [attr.aria-label]=\"etiquette()\"\n [selectionMode]=\"modeAria()\"\n [orientation]=\"orientationAria()\"\n [selectedTab]=\"selectionEffective()\"\n (selectedTabChange)=\"selection.set($event)\"\n>\n @for (onglet of onglets(); track onglet.valeur) {\n <button\n class=\"st-tabs__onglet\"\n type=\"button\"\n ngTab\n [value]=\"onglet.valeur\"\n [disabled]=\"onglet.desactive === true\"\n >\n {{ onglet.libelle }}\n </button>\n }\n</div>\n\n<div class=\"st-tabs__panneaux\">\n <ng-content />\n</div>\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}:host([data-orientation=verticale]){display:flex;gap:var(--st-espacement-4)}.st-tabs__liste{display:flex;gap:var(--st-espacement-1);border-block-end:1px solid var(--st-couleur-bordure)}:host([data-orientation=verticale]) .st-tabs__liste{flex-direction:column;border-block-end:0;border-inline-end:1px solid var(--st-couleur-bordure)}.st-tabs__panneaux{flex:1 1 auto;min-inline-size:0}.st-tabs__onglet:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-tabs__onglet{min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-4);font:inherit;color:var(--st-couleur-texte-discret);cursor:pointer;background:none;border:0;border-block-end:3px solid transparent}@media(hover:hover){.st-tabs__onglet:hover{color:var(--st-couleur-texte)}}.st-tabs__onglet[aria-selected=true]{font-weight:var(--st-graisse-semi);color:var(--st-couleur-texte);border-block-end-color:var(--st-couleur-primaire)}.st-tabs__onglet[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}@media(forced-colors:active){.st-tabs__onglet[aria-selected=true]{border-block-end-color:Highlight}}\n"] }]
78
- }], propDecorators: { onglets: [{ type: i0.Input, args: [{ isSignal: true, alias: "onglets", required: true }] }], etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: true }] }], activation: [{ type: i0.Input, args: [{ isSignal: true, alias: "activation", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }] } });
79
-
80
- /**
81
- * Panneau d'un onglet, relie par sa `valeur`.
82
- *
83
- * ⚠️ La primitive d'Aria ne masque RIEN : elle pose `inert` sur le panneau
84
- * inactif, ce qui le retire de l'arbre d'accessibilite et du clavier, mais le
85
- * laisse a l'ecran. Sa documentation le dit en une ligne. Sans le style de ce
86
- * composant, tous les panneaux s'affichaient empiles, muets et intouchables.
87
- */
88
- /* eslint-disable-next-line @typescript-eslint/no-extraneous-class --
89
- Vide par construction : tout le comportement vient de la primitive d'Aria
90
- composee en directive d'hote. La classe porte le selecteur et le style du
91
- socle, rien d'autre ; y ajouter un membre pour satisfaire la regle serait
92
- inventer de l'API. */
93
- class StTabPanel {
94
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
95
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.6", type: StTabPanel, isStandalone: true, selector: "st-tab-panel", host: { classAttribute: "st-tab-panel" }, hostDirectives: [{ directive: i1.TabPanel, inputs: ["value", "valeur"] }], ngImport: i0, template: "<ng-content />\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:block;padding-block:var(--st-espacement-4)}:host([inert]){display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
96
- }
97
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabPanel, decorators: [{
98
- type: Component,
99
- args: [{ selector: 'st-tab-panel', changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [{ directive: TabPanel, inputs: ['value: valeur'] }], host: { class: 'st-tab-panel' }, template: "<ng-content />\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:block;padding-block:var(--st-espacement-4)}:host([inert]){display:none}\n"] }]
100
- }] });
101
-
102
- /**
103
- * Contenu differe d'un panneau : rendu a la premiere selection de son onglet.
104
- *
105
- * Un ecran a cinq onglets ne charge ainsi que celui qu'on regarde. Voir
106
- * l'activation manuelle de `st-tabs` : c'est ce qui empeche qu'un simple
107
- * parcours au clavier les rende tous.
108
- */
109
- /* eslint-disable-next-line @typescript-eslint/no-extraneous-class --
110
- Vide par construction : tout le comportement vient de la primitive d'Aria
111
- composee en directive d'hote. La classe porte le selecteur et le style du
112
- socle, rien d'autre ; y ajouter un membre pour satisfaire la regle serait
113
- inventer de l'API. */
114
- class StTabContent {
115
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
116
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.6", type: StTabContent, isStandalone: true, selector: "ng-template[stTabContent]", hostDirectives: [{ directive: i1.TabContent }], ngImport: i0 });
117
- }
118
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabContent, decorators: [{
119
- type: Directive,
120
- args: [{
121
- selector: 'ng-template[stTabContent]',
122
- hostDirectives: [TabContent],
123
- }]
124
- }] });
125
-
126
- /**
127
- * Au-dela, la navigation tient a cote du contenu. Meme seuil que `st-app-shell`,
128
- * lu dans les jetons : ecrit en dur ici, il avait deja diverge une fois de la
129
- * feuille (F7 du plan de la coquille).
130
- */
131
- const ECRAN_LARGE = RUPTURES.lg.requete;
132
- let compteur = 0;
133
- /**
134
- * Navigation laterale : une region a cote du contenu sur grand ecran, un tiroir
135
- * sur petit ecran.
136
- *
137
- * ```html
138
- * <st-app-shell>
139
- * <button entete stButton [stSidenavToggle]="navigation">Menu</button>
140
- * <st-sidenav navigation #navigation etiquette="Navigation principale">
141
- * <a routerLink="/comptes">Comptes</a>
142
- * </st-sidenav>
143
- * …
144
- * </st-app-shell>
145
- * ```
146
- *
147
- * ⚠️ **Deux modes, jamais confondus.** Persistante, c'est une REGION de la page :
148
- * aucun piege, rien a ouvrir. Modale, c'est un TIROIR, donc un `StDialog` — role,
149
- * piegeage du focus, Echap, retour du focus au bouton — et non une region
150
- * glissee par-dessus la page avec un peu de CSS, qui laisserait le clavier
151
- * parcourir la page cachee dessous.
152
- *
153
- * ⚠️ **Elle porte son propre `<nav>`, et un seul a la fois.** Sur petit ecran,
154
- * la region en ligne DISPARAIT quand le tiroir prend le relais : laissee en
155
- * place, elle resterait un repere de navigation vide, que le lecteur d'ecran
156
- * proposerait sans rien y trouver.
157
- *
158
- * Un lien suivi dans le tiroir le ferme : `StDialog` se ferme a toute navigation
159
- * du routeur.
160
- */
161
- class StSidenav {
162
- dialogues = inject(StDialog);
163
- libelles = inject(ST_LIBELLES);
164
- /**
165
- * Nom du repere. `null` : « Navigation principale », du dictionnaire. A
166
- * renseigner des qu'une page compte deux navigations, qui doivent se
167
- * distinguer.
168
- */
169
- etiquette = input(null, /* @ts-ignore */
170
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
171
- etiquetteLue = computed(() => this.etiquette() ?? this.libelles().navigationPrincipale, /* @ts-ignore */
172
- ...(ngDevMode ? [{ debugName: "etiquetteLue" }] : /* istanbul ignore next */ []));
173
- /** `auto` : persistante sur grand ecran, tiroir en dessous. */
174
- mode = input('auto', /* @ts-ignore */
175
- ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
176
- identifiant = `st-sidenav-${(compteur += 1).toString()}`;
177
- ecranLarge = signal(false, /* @ts-ignore */
178
- ...(ngDevMode ? [{ debugName: "ecranLarge" }] : /* istanbul ignore next */ []));
179
- /** Mode reellement applique. Lu par le bouton, qui ne s'affiche qu'en tiroir. */
180
- modeEffectif = computed(() => {
181
- const mode = this.mode();
182
- if (mode !== 'auto')
183
- return mode;
184
- return this.ecranLarge() ? 'persistant' : 'modal';
185
- }, /* @ts-ignore */
186
- ...(ngDevMode ? [{ debugName: "modeEffectif" }] : /* istanbul ignore next */ []));
187
- ouvertInterne = signal(false, /* @ts-ignore */
188
- ...(ngDevMode ? [{ debugName: "ouvertInterne" }] : /* istanbul ignore next */ []));
189
- ouvert = this.ouvertInterne.asReadonly();
190
- // Nomme autrement que la variable `#contenu` du gabarit, qui la masquerait.
191
- gabarit = viewChild.required('contenu', /* @ts-ignore */
192
- ...(ngDevMode ? [{ debugName: "gabarit" }] : /* istanbul ignore next */ []));
193
- reference = null;
194
- constructor() {
195
- const abonnement = inject(BreakpointObserver)
196
- .observe(ECRAN_LARGE)
197
- .subscribe((etat) => {
198
- this.ecranLarge.set(etat.matches);
199
- });
200
- // Un ecran qui s'elargit pendant que le tiroir est ouvert : la navigation
201
- // redevient une region, et le tiroir n'a plus de raison d'etre.
202
- effect(() => {
203
- if (this.modeEffectif() === 'persistant')
204
- this.fermer();
205
- });
206
- inject(DestroyRef).onDestroy(() => {
207
- abonnement.unsubscribe();
208
- this.fermer();
209
- });
210
- }
211
- ouvrir() {
212
- if (this.modeEffectif() !== 'modal' || this.reference !== null)
213
- return;
214
- const reference = this.dialogues.ouvrir(this.gabarit(), {
215
- cote: 'debut',
216
- etiquette: this.etiquetteLue(),
217
- });
218
- this.reference = reference;
219
- this.ouvertInterne.set(true);
220
- void reference.ferme.then(() => {
221
- if (this.reference !== reference)
222
- return;
223
- this.reference = null;
224
- this.ouvertInterne.set(false);
225
- });
226
- }
227
- fermer() {
228
- const reference = this.reference;
229
- if (reference === null)
230
- return;
231
- this.reference = null;
232
- this.ouvertInterne.set(false);
233
- reference.fermer();
234
- }
235
- basculer() {
236
- if (this.ouvertInterne())
237
- this.fermer();
238
- else
239
- this.ouvrir();
240
- }
241
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenav, deps: [], target: i0.ɵɵFactoryTarget.Component });
242
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StSidenav, isStandalone: true, selector: "st-sidenav", inputs: { etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-mode": "modeEffectif()" }, classAttribute: "st-sidenav" }, viewQueries: [{ propertyName: "gabarit", first: true, predicate: ["contenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #contenu>\n <nav class=\"st-sidenav__navigation\" [attr.id]=\"identifiant\" [attr.aria-label]=\"etiquetteLue()\">\n <ng-content />\n </nav>\n</ng-template>\n\n@if (modeEffectif() === 'persistant') {\n <ng-container [ngTemplateOutlet]=\"contenu\" />\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}:host([data-mode=modal]){display:none}.st-sidenav__navigation{display:flex;flex-direction:column;gap:var(--st-espacement-1);padding:var(--st-espacement-4)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
243
- }
244
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenav, decorators: [{
245
- type: Component,
246
- args: [{ selector: 'st-sidenav', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
247
- class: 'st-sidenav',
248
- '[attr.data-mode]': 'modeEffectif()',
249
- }, template: "<ng-template #contenu>\n <nav class=\"st-sidenav__navigation\" [attr.id]=\"identifiant\" [attr.aria-label]=\"etiquetteLue()\">\n <ng-content />\n </nav>\n</ng-template>\n\n@if (modeEffectif() === 'persistant') {\n <ng-container [ngTemplateOutlet]=\"contenu\" />\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}:host([data-mode=modal]){display:none}.st-sidenav__navigation{display:flex;flex-direction:column;gap:var(--st-espacement-1);padding:var(--st-espacement-4)}\n"] }]
250
- }], ctorParameters: () => [], propDecorators: { etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], gabarit: [{ type: i0.ViewChild, args: ['contenu', { isSignal: true }] }] } });
251
-
252
- /**
253
- * Bouton qui ouvre la navigation en tiroir.
254
- *
255
- * ⚠️ **Masque quand la navigation est persistante.** Un bouton « Menu » qui ne
256
- * fait rien, parce que la navigation est deja affichee a cote, est un arret de
257
- * tabulation inutile et une promesse non tenue.
258
- *
259
- * ⚠️ `aria-expanded` suit l'ouverture du tiroir : sans lui, rien ne dit a qui
260
- * n'en voit pas l'effet que le bouton a agi.
261
- */
262
- class StSidenavToggle {
263
- stSidenavToggle = input.required(/* @ts-ignore */
264
- ...(ngDevMode ? [{ debugName: "stSidenavToggle" }] : /* istanbul ignore next */ []));
265
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenavToggle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
266
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.6", type: StSidenavToggle, isStandalone: true, selector: "[stSidenavToggle]", inputs: { stSidenavToggle: { classPropertyName: "stSidenavToggle", publicName: "stSidenavToggle", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-haspopup": "dialog" }, listeners: { "click": "stSidenavToggle().basculer()" }, properties: { "attr.aria-expanded": "stSidenavToggle().ouvert()", "attr.hidden": "stSidenavToggle().modeEffectif() === \"modal\" ? null : \"\"" } }, ngImport: i0 });
267
- }
268
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenavToggle, decorators: [{
269
- type: Directive,
270
- args: [{
271
- selector: '[stSidenavToggle]',
272
- host: {
273
- 'aria-haspopup': 'dialog',
274
- '[attr.aria-expanded]': 'stSidenavToggle().ouvert()',
275
- '[attr.hidden]': 'stSidenavToggle().modeEffectif() === "modal" ? null : ""',
276
- '(click)': 'stSidenavToggle().basculer()',
277
- },
278
- }]
279
- }], propDecorators: { stSidenavToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "stSidenavToggle", required: true }] }] } });
280
-
281
- /**
282
- * Liste de choix a filtrer en tapant, sur `@angular/aria/combobox` et
283
- * `@angular/aria/listbox`.
284
- *
285
- * ```html
286
- * <st-combobox libelle="Beneficiaire" [options]="beneficiaires" [formField]="virement.beneficiaire" />
287
- * <!-- liste servie par le serveur : -->
288
- * <st-combobox libelle="Commune" filtrage="distant" [options]="communes()"
289
- * (recherche)="terme.set($event)" [formField]="adresse.commune" />
290
- * ```
291
- *
292
- * Le modele est la VALEUR de l'option choisie, `''` sans choix. Le texte tape ne
293
- * part jamais dans le modele : « Pari » n'est pas une commune.
294
- *
295
- * ⚠️ **Pour une longue liste seulement.** Sous une vingtaine d'options,
296
- * `st-select` — natif — est plus sur, et plus rapide sur mobile.
297
- *
298
- * - La liste s'ouvre a la frappe et a la fleche bas ; Entree choisit, Echap
299
- * ferme. Le focus reste dans le champ (`aria-activedescendant`).
300
- * - Le nombre de resultats s'annonce apres la frappe, une fois : le lecteur
301
- * d'ecran ne voit pas la liste se reduire.
302
- * - ⚠️ **Quitter le champ sans choisir rend le texte du choix precedent.** Un
303
- * texte qui ne correspond a aucune option ne reste pas affiche comme s'il etait
304
- * retenu ; un champ vide, lui, retire le choix.
305
- * - La liste est posee dans la couche superieure du navigateur (`popover`) par le
306
- * CDK : un dialogue ou un tableau defilant ne la rogne pas.
307
- *
308
- * ⚠️ En `distant`, `recherche` est emis a chaque frappe : a l'application de
309
- * temporiser, et de ne pas mettre le terme dans l'adresse (SEC-6).
310
- */
311
- class StCombobox extends ControleDeSaisie {
312
- libelles = inject(ST_LIBELLES);
313
- element = inject(ElementRef);
314
- injecteur = inject(Injector);
315
- options = input.required(/* @ts-ignore */
316
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
317
- filtrage = input('local', /* @ts-ignore */
318
- ...(ngDevMode ? [{ debugName: "filtrage" }] : /* istanbul ignore next */ []));
319
- /** Texte tape, a chaque frappe. Sert au filtrage `distant`. */
320
- recherche = output();
321
- annonce = new AnnonceLocale(inject(DestroyRef));
322
- ouvert = signal(false, /* @ts-ignore */
323
- ...(ngDevMode ? [{ debugName: "ouvert" }] : /* istanbul ignore next */ []));
324
- /** Texte du champ : le libelle du choix, tant que la personne ne tape pas. */
325
- texte = linkedSignal(() => this.libelleDe(this.value()), /* @ts-ignore */
326
- ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
327
- /** Ce qui filtre : vide a l'ouverture par la fleche, pour montrer toute la liste. */
328
- filtre = signal('', /* @ts-ignore */
329
- ...(ngDevMode ? [{ debugName: "filtre" }] : /* istanbul ignore next */ []));
330
- /** Option active dans la liste ; le choix n'est retenu qu'a la validation. */
331
- surlignee = linkedSignal(() => this.value() === '' ? [] : [this.value()], /* @ts-ignore */
332
- ...(ngDevMode ? [{ debugName: "surlignee" }] : /* istanbul ignore next */ []));
333
- visibles = computed(() => this.filtrage() === 'local' ? filtrerOptions(this.options(), this.filtre()) : this.options(), /* @ts-ignore */
334
- ...(ngDevMode ? [{ debugName: "visibles" }] : /* istanbul ignore next */ []));
335
- liste = viewChild(Listbox, /* @ts-ignore */
336
- ...(ngDevMode ? [{ debugName: "liste" }] : /* istanbul ignore next */ []));
337
- constructor() {
338
- super();
339
- afterRenderEffect(() => {
340
- if (this.ouvert())
341
- this.liste()?.scrollActiveItemIntoView({ block: 'nearest' });
342
- });
343
- }
344
- surSaisie(evenement) {
345
- const saisie = this.texteSaisi(evenement);
346
- this.filtre.set(saisie);
347
- this.recherche.emit(saisie);
348
- if (this.filtrage() === 'local') {
349
- this.annonce.annoncer(this.libelles().nombreDeResultats(this.visibles().length));
350
- }
351
- }
352
- basculer() {
353
- if (this.disabled() || this.readonly())
354
- return;
355
- this.filtre.set('');
356
- this.ouvert.update((ouvert) => !ouvert);
357
- this.focus();
358
- }
359
- valider() {
360
- const choisie = this.surlignee()[0];
361
- if (choisie === undefined)
362
- return;
363
- this.value.set(choisie);
364
- this.texte.set(this.libelleDe(choisie));
365
- this.filtre.set('');
366
- this.ouvert.set(false);
367
- afterNextRender(() => {
368
- this.focus();
369
- }, { injector: this.injecteur });
370
- }
371
- aLaSortie(evenement) {
372
- const suivant = evenement.relatedTarget;
373
- if (suivant !== null && this.element.nativeElement.contains(suivant))
374
- return;
375
- if (this.texte().trim() === '')
376
- this.value.set('');
377
- this.texte.set(this.libelleDe(this.value()));
378
- this.filtre.set('');
379
- this.touch.emit();
380
- }
381
- libelleDe(valeur) {
382
- if (valeur === '')
383
- return '';
384
- return this.options().find((option) => option.valeur === valeur)?.libelle ?? '';
385
- }
386
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCombobox, deps: [], target: i0.ɵɵFactoryTarget.Component });
387
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StCombobox, isStandalone: true, selector: "st-combobox", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, filtrage: { classPropertyName: "filtrage", publicName: "filtrage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recherche: "recherche" }, host: { listeners: { "focusout": "aLaSortie($event)" }, classAttribute: "st-combobox" }, viewQueries: [{ propertyName: "liste", first: true, predicate: Listbox, descendants: true, isSignal: true }], 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 #cadre class=\"st-combobox__cadre\">\n <input\n #champ\n #combobox=\"ngCombobox\"\n class=\"st-combobox__champ\"\n stControleDeChamp\n ngCombobox\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [(value)]=\"texte\"\n [(expanded)]=\"ouvert\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n />\n <!-- Voie de la souris vers la liste. Hors tabulation et masque au lecteur\n d'ecran : au clavier, la fleche bas ouvre la liste depuis le champ. -->\n <span\n class=\"st-combobox__ouvrir\"\n aria-hidden=\"true\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"basculer()\"\n >\n <st-icon nom=\"chevron-bas\" taille=\"sm\" />\n </span>\n </div>\n\n <!-- \u26A0\uFE0F La primitive de popup ENVELOPPE la surcouche, et non l'inverse (exemple\n de la documentation d'Aria). Enfermee dans la surcouche, elle ne\n s'enregistre qu'a l'ouverture : liste fermee, le champ annonce\n aria-autocomplete=\"none\" et aucun aria-haspopup, et le lecteur d'ecran\n ignore qu'une liste existe. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\">\n <ng-template\n [cdkConnectedOverlay]=\"{ origin: cadre, usePopover: 'inline', matchWidth: true }\"\n [cdkConnectedOverlayOpen]=\"ouvert()\"\n >\n <div class=\"st-combobox__popup\">\n @if (visibles().length === 0) {\n <p class=\"st-combobox__vide\">{{ libelles().aucunResultat }}</p>\n }\n <div\n #liste=\"ngListbox\"\n class=\"st-combobox__liste\"\n ngListbox\n ngComboboxWidget\n focusMode=\"activedescendant\"\n [tabindex]=\"-1\"\n [attr.aria-label]=\"libelle()\"\n [activeDescendant]=\"liste.activeDescendant()\"\n [(value)]=\"surlignee\"\n (click)=\"valider()\"\n (keydown.enter)=\"valider()\"\n >\n @for (option of visibles(); track option.valeur) {\n <div\n class=\"st-combobox__option\"\n ngOption\n [value]=\"option.valeur\"\n [label]=\"option.libelle\"\n [disabled]=\"option.desactivee === true\"\n >\n <span>{{ option.libelle }}</span>\n <st-icon class=\"st-combobox__coche\" nom=\"coche\" taille=\"sm\" />\n </div>\n }\n </div>\n </div>\n </ng-template>\n </ng-template>\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-combobox__cadre{position:relative}.st-combobox__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-combobox__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-combobox__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-combobox__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-combobox__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-combobox__champ{padding-inline-end:calc(var(--st-espacement-3) * 2 + 1rem)}.st-combobox__ouvrir{position:absolute;inset-block:0;inset-inline-end:0;display:flex;align-items:center;padding-inline:var(--st-espacement-3);color:var(--st-couleur-texte-discret);cursor:pointer}.st-combobox__popup{max-block-size:18rem;overflow-y:auto;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm);box-shadow:var(--st-ombre-md)}.st-combobox__vide{padding:var(--st-espacement-2) var(--st-espacement-3);margin:0;color:var(--st-couleur-texte-discret)}.st-combobox__option{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-3);cursor:pointer}.st-combobox__option[data-active=true]{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px;background:var(--st-couleur-surface-enfoncee)}.st-combobox__option[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-combobox__option[aria-selected=true]{font-weight:var(--st-graisse-semi)}.st-combobox__coche{visibility:hidden}[aria-selected=true]>.st-combobox__coche{visibility:visible}\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: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "readonly", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
388
- }
389
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCombobox, decorators: [{
390
- type: Component,
391
- args: [{ selector: 'st-combobox', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
392
- StFormField,
393
- StControleDeChamp,
394
- StIcon,
395
- Combobox,
396
- ComboboxPopup,
397
- ComboboxWidget,
398
- Listbox,
399
- Option,
400
- CdkConnectedOverlay,
401
- ], host: {
402
- class: 'st-combobox',
403
- '(focusout)': 'aLaSortie($event)',
404
- }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div #cadre class=\"st-combobox__cadre\">\n <input\n #champ\n #combobox=\"ngCombobox\"\n class=\"st-combobox__champ\"\n stControleDeChamp\n ngCombobox\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [(value)]=\"texte\"\n [(expanded)]=\"ouvert\"\n [attr.name]=\"name() || null\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n (input)=\"surSaisie($event)\"\n />\n <!-- Voie de la souris vers la liste. Hors tabulation et masque au lecteur\n d'ecran : au clavier, la fleche bas ouvre la liste depuis le champ. -->\n <span\n class=\"st-combobox__ouvrir\"\n aria-hidden=\"true\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"basculer()\"\n >\n <st-icon nom=\"chevron-bas\" taille=\"sm\" />\n </span>\n </div>\n\n <!-- \u26A0\uFE0F La primitive de popup ENVELOPPE la surcouche, et non l'inverse (exemple\n de la documentation d'Aria). Enfermee dans la surcouche, elle ne\n s'enregistre qu'a l'ouverture : liste fermee, le champ annonce\n aria-autocomplete=\"none\" et aucun aria-haspopup, et le lecteur d'ecran\n ignore qu'une liste existe. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\">\n <ng-template\n [cdkConnectedOverlay]=\"{ origin: cadre, usePopover: 'inline', matchWidth: true }\"\n [cdkConnectedOverlayOpen]=\"ouvert()\"\n >\n <div class=\"st-combobox__popup\">\n @if (visibles().length === 0) {\n <p class=\"st-combobox__vide\">{{ libelles().aucunResultat }}</p>\n }\n <div\n #liste=\"ngListbox\"\n class=\"st-combobox__liste\"\n ngListbox\n ngComboboxWidget\n focusMode=\"activedescendant\"\n [tabindex]=\"-1\"\n [attr.aria-label]=\"libelle()\"\n [activeDescendant]=\"liste.activeDescendant()\"\n [(value)]=\"surlignee\"\n (click)=\"valider()\"\n (keydown.enter)=\"valider()\"\n >\n @for (option of visibles(); track option.valeur) {\n <div\n class=\"st-combobox__option\"\n ngOption\n [value]=\"option.valeur\"\n [label]=\"option.libelle\"\n [disabled]=\"option.desactivee === true\"\n >\n <span>{{ option.libelle }}</span>\n <st-icon class=\"st-combobox__coche\" nom=\"coche\" taille=\"sm\" />\n </div>\n }\n </div>\n </div>\n </ng-template>\n </ng-template>\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-combobox__cadre{position:relative}.st-combobox__champ:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-combobox__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-combobox__champ[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-combobox__champ:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-combobox__champ:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-combobox__champ{padding-inline-end:calc(var(--st-espacement-3) * 2 + 1rem)}.st-combobox__ouvrir{position:absolute;inset-block:0;inset-inline-end:0;display:flex;align-items:center;padding-inline:var(--st-espacement-3);color:var(--st-couleur-texte-discret);cursor:pointer}.st-combobox__popup{max-block-size:18rem;overflow-y:auto;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm);box-shadow:var(--st-ombre-md)}.st-combobox__vide{padding:var(--st-espacement-2) var(--st-espacement-3);margin:0;color:var(--st-couleur-texte-discret)}.st-combobox__option{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-3);cursor:pointer}.st-combobox__option[data-active=true]{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px;background:var(--st-couleur-surface-enfoncee)}.st-combobox__option[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-combobox__option[aria-selected=true]{font-weight:var(--st-graisse-semi)}.st-combobox__coche{visibility:hidden}[aria-selected=true]>.st-combobox__coche{visibility:visible}\n"] }]
405
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], filtrage: [{ type: i0.Input, args: [{ isSignal: true, alias: "filtrage", required: false }] }], recherche: [{ type: i0.Output, args: ["recherche"] }], liste: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Listbox), { isSignal: true }] }] } });
406
-
407
- /**
408
- * Choix de plusieurs valeurs dans une liste, sur `@angular/aria/combobox` et
409
- * `@angular/aria/listbox`.
410
- *
411
- * ```html
412
- * <st-multiselect libelle="Comptes a inclure" [options]="comptes" [formField]="releve.comptes" />
413
- * ```
414
- *
415
- * Le modele est la liste des VALEURS choisies, dans l'ordre des options.
416
- *
417
- * - Fleche bas, Entree ou Espace ouvrent la liste ; Espace coche ou decoche
418
- * l'option active sans fermer ; Echap ferme.
419
- * - ⚠️ **Chaque choix reste visible et retirable** sous le champ, par un bouton
420
- * « Retirer … ». Un resume « 3 choix » dans le declencheur ne dit pas lesquels,
421
- * et obligerait a rouvrir la liste pour en retirer un.
422
- * - Un choix retire rend le focus au retrait suivant, ou au champ : le focus ne
423
- * tombe jamais sur le document.
424
- *
425
- * ⚠️ Sous une dizaine d'options, un groupe de cases a cocher (`st-fieldset` et
426
- * `st-checkbox`) est plus simple : tout est visible, rien a ouvrir.
427
- */
428
- class StMultiselect extends ContratDeChamp {
429
- libelles = inject(ST_LIBELLES);
430
- element = inject(ElementRef);
431
- injecteur = inject(Injector);
432
- value = model([], /* @ts-ignore */
433
- ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
434
- options = input.required(/* @ts-ignore */
435
- ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
436
- annonce = new AnnonceLocale(inject(DestroyRef));
437
- ouvert = signal(false, /* @ts-ignore */
438
- ...(ngDevMode ? [{ debugName: "ouvert" }] : /* istanbul ignore next */ []));
439
- liste = viewChild(Listbox, /* @ts-ignore */
440
- ...(ngDevMode ? [{ debugName: "liste" }] : /* istanbul ignore next */ []));
441
- /** La liste d'Aria attend un tableau modifiable ; le modele, lui, est en lecture seule. */
442
- valeursDeListe = computed(() => [...this.value()], /* @ts-ignore */
443
- ...(ngDevMode ? [{ debugName: "valeursDeListe" }] : /* istanbul ignore next */ []));
444
- choisies = computed(() => {
445
- const valeurs = new Set(this.value());
446
- return this.options().filter((option) => valeurs.has(option.valeur));
447
- }, /* @ts-ignore */
448
- ...(ngDevMode ? [{ debugName: "choisies" }] : /* istanbul ignore next */ []));
449
- resume = computed(() => {
450
- const choisies = this.choisies();
451
- return choisies.length === 0
452
- ? this.libelles().choisir
453
- : choisies.map((option) => option.libelle).join(', ');
454
- }, /* @ts-ignore */
455
- ...(ngDevMode ? [{ debugName: "resume" }] : /* istanbul ignore next */ []));
456
- constructor() {
457
- super();
458
- afterRenderEffect(() => {
459
- if (this.ouvert())
460
- this.liste()?.scrollActiveItemIntoView({ block: 'nearest' });
461
- });
462
- }
463
- /** Le declencheur n'est pas un champ natif : la base ne le trouve pas par `#champ`. */
464
- focus(options) {
465
- this.element.nativeElement.querySelector('[ngCombobox]')?.focus(options);
466
- }
467
- surChoix(valeurs) {
468
- if (this.readonly())
469
- return;
470
- // Dans l'ordre des options, et non dans l'ordre des clics : un modele stable
471
- // ne change pas de forme selon le chemin pris pour le remplir.
472
- const retenues = new Set(valeurs);
473
- this.value.set(this.options()
474
- .filter((option) => retenues.has(option.valeur))
475
- .map((option) => option.valeur));
476
- }
477
- retirer(valeur, indice) {
478
- const restantes = this.value().filter((choisie) => choisie !== valeur);
479
- this.value.set(restantes);
480
- this.annonce.annoncer(this.libelles().nombreDeSelections(restantes.length));
481
- afterNextRender(() => {
482
- const boutons = this.element.nativeElement.querySelectorAll('.st-multiselect__retrait');
483
- const suivant = boutons[Math.min(indice, boutons.length - 1)];
484
- if (suivant !== undefined)
485
- suivant.focus();
486
- else
487
- this.focus();
488
- }, { injector: this.injecteur });
489
- }
490
- aLaSortie(evenement) {
491
- const suivant = evenement.relatedTarget;
492
- if (suivant !== null && this.element.nativeElement.contains(suivant))
493
- return;
494
- this.touch.emit();
495
- }
496
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMultiselect, deps: [], target: i0.ɵɵFactoryTarget.Component });
497
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StMultiselect, isStandalone: true, selector: "st-multiselect", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "focusout": "aLaSortie($event)" }, classAttribute: "st-multiselect" }, viewQueries: [{ propertyName: "liste", first: true, predicate: Listbox, descendants: true, isSignal: true }], 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\n #combobox=\"ngCombobox\"\n class=\"st-multiselect__declencheur\"\n stControleDeChamp\n ngCombobox\n [attr.data-vide]=\"choisies().length === 0 ? '' : null\"\n [(expanded)]=\"ouvert\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n <span class=\"st-multiselect__resume\">{{ resume() }}</span>\n <st-icon class=\"st-multiselect__chevron\" nom=\"chevron-bas\" taille=\"sm\" />\n </div>\n\n <!-- \u26A0\uFE0F La primitive de popup ENVELOPPE la surcouche, et non l'inverse (exemple\n de la documentation d'Aria). Enfermee dans la surcouche, elle ne\n s'enregistre qu'a l'ouverture : liste fermee, le champ annonce\n aria-autocomplete=\"none\" et aucun aria-haspopup, et le lecteur d'ecran\n ignore qu'une liste existe. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\">\n <ng-template\n [cdkConnectedOverlay]=\"{ origin: combobox.element, usePopover: 'inline', matchWidth: true }\"\n [cdkConnectedOverlayOpen]=\"ouvert()\"\n >\n <div class=\"st-multiselect__popup\">\n <div\n #liste=\"ngListbox\"\n class=\"st-multiselect__liste\"\n ngListbox\n ngComboboxWidget\n focusMode=\"activedescendant\"\n selectionMode=\"explicit\"\n [multi]=\"true\"\n [tabindex]=\"-1\"\n [attr.aria-label]=\"libelle()\"\n [activeDescendant]=\"liste.activeDescendant()\"\n [value]=\"valeursDeListe()\"\n (valueChange)=\"surChoix($event)\"\n >\n @for (option of options(); track option.valeur) {\n <div\n class=\"st-multiselect__option\"\n ngOption\n [value]=\"option.valeur\"\n [label]=\"option.libelle\"\n [disabled]=\"option.desactivee === true\"\n >\n <span>{{ option.libelle }}</span>\n <st-icon class=\"st-multiselect__coche\" nom=\"coche\" taille=\"sm\" />\n </div>\n }\n </div>\n </div>\n </ng-template>\n </ng-template>\n\n @if (choisies().length > 0) {\n <ul class=\"st-multiselect__choix\">\n @for (option of choisies(); track option.valeur; let indice = $index) {\n <li class=\"st-multiselect__puce\">\n <span>{{ option.libelle }}</span>\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-multiselect__retrait\"\n stIconButton\n type=\"button\"\n icone=\"croix\"\n taille=\"sm\"\n [etiquette]=\"libelles().retirerLeChoix(option.libelle)\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"retirer(option.valeur, indice)\"\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-multiselect__declencheur:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-multiselect__declencheur{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-multiselect__declencheur[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-multiselect__declencheur:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__declencheur:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-multiselect__declencheur{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;cursor:pointer}.st-multiselect__declencheur[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__resume{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}[data-vide]>.st-multiselect__resume{color:var(--st-couleur-texte-discret)}.st-multiselect__chevron{flex:none;color:var(--st-couleur-texte-discret)}.st-multiselect__popup{max-block-size:18rem;overflow-y:auto;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm);box-shadow:var(--st-ombre-md)}.st-multiselect__option{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-3);cursor:pointer}.st-multiselect__option[data-active=true]{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__option[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-multiselect__option[aria-selected=true]{font-weight:var(--st-graisse-semi)}.st-multiselect__coche{visibility:hidden}[aria-selected=true]>.st-multiselect__coche{visibility:visible}.st-multiselect__choix{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);padding:0;margin:var(--st-espacement-2) 0 0;list-style:none}.st-multiselect__puce{display:inline-flex;gap:var(--st-espacement-1);align-items:center;padding-inline-start:var(--st-espacement-3);background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure);border-radius:var(--st-rayon-pleine)}\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: "directive", type: Combobox, selector: "[ngCombobox]", inputs: ["disabled", "readonly", "softDisabled", "alwaysExpanded", "tabindex", "expanded", "value", "inlineSuggestion"], outputs: ["expandedChange", "valueChange"], exportAs: ["ngCombobox"] }, { kind: "directive", type: ComboboxPopup, selector: "ng-template[ngComboboxPopup]", inputs: ["combobox", "popupType"], exportAs: ["ngComboboxPopup"] }, { kind: "directive", type: ComboboxWidget, selector: "[ngComboboxWidget]", inputs: ["activeDescendant"], exportAs: ["ngComboboxWidget"] }, { kind: "directive", type: Listbox, selector: "[ngListbox]", inputs: ["id", "orientation", "multi", "wrap", "softDisabled", "focusMode", "selectionMode", "typeaheadDelay", "disabled", "readonly", "tabindex", "value"], outputs: ["valueChange"], exportAs: ["ngListbox"] }, { kind: "directive", type: Option, selector: "[ngOption]", inputs: ["id", "value", "disabled", "label"], exportAs: ["ngOption"] }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
498
- }
499
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMultiselect, decorators: [{
500
- type: Component,
501
- args: [{ selector: 'st-multiselect', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
502
- StFormField,
503
- StControleDeChamp,
504
- StIcon,
505
- StIconButton,
506
- Combobox,
507
- ComboboxPopup,
508
- ComboboxWidget,
509
- Listbox,
510
- Option,
511
- CdkConnectedOverlay,
512
- ], host: {
513
- class: 'st-multiselect',
514
- '(focusout)': 'aLaSortie($event)',
515
- }, template: "<st-form-field\n [libelle]=\"libelle()\"\n [aide]=\"aide()\"\n [erreurs]=\"errors()\"\n [afficherErreurs]=\"touched()\"\n [obligatoire]=\"required()\"\n>\n <div\n #combobox=\"ngCombobox\"\n class=\"st-multiselect__declencheur\"\n stControleDeChamp\n ngCombobox\n [attr.data-vide]=\"choisies().length === 0 ? '' : null\"\n [(expanded)]=\"ouvert\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n >\n <span class=\"st-multiselect__resume\">{{ resume() }}</span>\n <st-icon class=\"st-multiselect__chevron\" nom=\"chevron-bas\" taille=\"sm\" />\n </div>\n\n <!-- \u26A0\uFE0F La primitive de popup ENVELOPPE la surcouche, et non l'inverse (exemple\n de la documentation d'Aria). Enfermee dans la surcouche, elle ne\n s'enregistre qu'a l'ouverture : liste fermee, le champ annonce\n aria-autocomplete=\"none\" et aucun aria-haspopup, et le lecteur d'ecran\n ignore qu'une liste existe. -->\n <ng-template ngComboboxPopup [combobox]=\"combobox\">\n <ng-template\n [cdkConnectedOverlay]=\"{ origin: combobox.element, usePopover: 'inline', matchWidth: true }\"\n [cdkConnectedOverlayOpen]=\"ouvert()\"\n >\n <div class=\"st-multiselect__popup\">\n <div\n #liste=\"ngListbox\"\n class=\"st-multiselect__liste\"\n ngListbox\n ngComboboxWidget\n focusMode=\"activedescendant\"\n selectionMode=\"explicit\"\n [multi]=\"true\"\n [tabindex]=\"-1\"\n [attr.aria-label]=\"libelle()\"\n [activeDescendant]=\"liste.activeDescendant()\"\n [value]=\"valeursDeListe()\"\n (valueChange)=\"surChoix($event)\"\n >\n @for (option of options(); track option.valeur) {\n <div\n class=\"st-multiselect__option\"\n ngOption\n [value]=\"option.valeur\"\n [label]=\"option.libelle\"\n [disabled]=\"option.desactivee === true\"\n >\n <span>{{ option.libelle }}</span>\n <st-icon class=\"st-multiselect__coche\" nom=\"coche\" taille=\"sm\" />\n </div>\n }\n </div>\n </div>\n </ng-template>\n </ng-template>\n\n @if (choisies().length > 0) {\n <ul class=\"st-multiselect__choix\">\n @for (option of choisies(); track option.valeur; let indice = $index) {\n <li class=\"st-multiselect__puce\">\n <span>{{ option.libelle }}</span>\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-multiselect__retrait\"\n stIconButton\n type=\"button\"\n icone=\"croix\"\n taille=\"sm\"\n [etiquette]=\"libelles().retirerLeChoix(option.libelle)\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"retirer(option.valeur, indice)\"\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-multiselect__declencheur:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:2px}.st-multiselect__declencheur{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-multiselect__declencheur[aria-invalid=true]{border-color:var(--st-couleur-danger);box-shadow:inset 0 0 0 1px var(--st-couleur-danger)}.st-multiselect__declencheur:disabled{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__declencheur:read-only:not(:disabled){background:var(--st-couleur-surface-enfoncee)}.st-multiselect__declencheur{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;cursor:pointer}.st-multiselect__declencheur[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__resume{min-inline-size:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}[data-vide]>.st-multiselect__resume{color:var(--st-couleur-texte-discret)}.st-multiselect__chevron{flex:none;color:var(--st-couleur-texte-discret)}.st-multiselect__popup{max-block-size:18rem;overflow-y:auto;color:var(--st-couleur-texte);background:var(--st-couleur-surface);border:1px solid var(--st-couleur-bordure-forte);border-radius:var(--st-rayon-sm);box-shadow:var(--st-ombre-md)}.st-multiselect__option{display:flex;gap:var(--st-espacement-2);align-items:center;justify-content:space-between;min-block-size:var(--st-cible-tactile-min);padding:var(--st-espacement-2) var(--st-espacement-3);cursor:pointer}.st-multiselect__option[data-active=true]{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px;background:var(--st-couleur-surface-enfoncee)}.st-multiselect__option[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-multiselect__option[aria-selected=true]{font-weight:var(--st-graisse-semi)}.st-multiselect__coche{visibility:hidden}[aria-selected=true]>.st-multiselect__coche{visibility:visible}.st-multiselect__choix{display:flex;flex-wrap:wrap;gap:var(--st-espacement-2);padding:0;margin:var(--st-espacement-2) 0 0;list-style:none}.st-multiselect__puce{display:inline-flex;gap:var(--st-espacement-1);align-items:center;padding-inline-start:var(--st-espacement-3);background:var(--st-couleur-surface-enfoncee);border:1px solid var(--st-couleur-bordure);border-radius:var(--st-rayon-pleine)}\n"] }]
516
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], liste: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Listbox), { isSignal: true }] }] } });
517
-
518
- /**
519
- * Arbre de donnees hierarchiques, sur `@angular/aria/tree` : dossiers, plan
520
- * comptable, organisation.
521
- *
522
- * ```html
523
- * <st-tree etiquette="Plan comptable" [noeuds]="comptes" [(selection)]="compte" [(deplies)]="ouverts" />
524
- * ```
525
- *
526
- * ⚠️ **Jamais pour la navigation du site** (UX-17) : une navigation est une liste
527
- * de liens, que le lecteur d'ecran liste et parcourt comme telle. Un arbre se
528
- * parcourt aux fleches, un element a la fois, et ses elements ne sont pas des
529
- * liens. Le mode « navigation » de la primitive n'est donc pas expose.
530
- *
531
- * ⚠️ **Les noeuds sont rendus par le composant, a partir de donnees** (FE-6) : la
532
- * primitive relie un element a son arbre par injection, et un contenu projete
533
- * garde l'injecteur de l'endroit ou il est ecrit — lecon de `st-menu`.
534
- *
535
- * Clavier de la primitive : fleches haut et bas entre les elements visibles,
536
- * droite pour deplier ou entrer, gauche pour replier ou remonter, Debut et Fin,
537
- * recherche a la frappe ; Espace ou Entree choisit. Selection et depliage sont des
538
- * listes de VALEURS, lisibles et restaurables par l'application.
539
- */
540
- class StTree {
541
- /** Nom de l'arbre, lu a l'entree. Obligatoire. */
542
- etiquette = input.required(/* @ts-ignore */
543
- ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
544
- noeuds = input.required(/* @ts-ignore */
545
- ...(ngDevMode ? [{ debugName: "noeuds" }] : /* istanbul ignore next */ []));
546
- multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
547
- selection = model([], /* @ts-ignore */
548
- ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
549
- deplies = model([], /* @ts-ignore */
550
- ...(ngDevMode ? [{ debugName: "deplies" }] : /* istanbul ignore next */ []));
551
- /** La primitive attend un tableau modifiable ; le modele est en lecture seule. */
552
- valeursChoisies = computed(() => [...this.selection()], /* @ts-ignore */
553
- ...(ngDevMode ? [{ debugName: "valeursChoisies" }] : /* istanbul ignore next */ []));
554
- ensembleDeplie = computed(() => new Set(this.deplies()), /* @ts-ignore */
555
- ...(ngDevMode ? [{ debugName: "ensembleDeplie" }] : /* istanbul ignore next */ []));
556
- estDeplie(noeud) {
557
- return this.ensembleDeplie().has(noeud.valeur);
558
- }
559
- surDepliage(noeud, deplie) {
560
- const deplies = this.deplies().filter((valeur) => valeur !== noeud.valeur);
561
- this.deplies.set(deplie ? [...deplies, noeud.valeur] : deplies);
562
- }
563
- surSelection(valeurs) {
564
- this.selection.set([...valeurs]);
565
- }
566
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
567
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: StTree, isStandalone: true, selector: "st-tree", inputs: { etiquette: { classPropertyName: "etiquette", publicName: "etiquette", isSignal: true, isRequired: true, transformFunction: null }, noeuds: { classPropertyName: "noeuds", publicName: "noeuds", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, deplies: { classPropertyName: "deplies", publicName: "deplies", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selection: "selectionChange", deplies: "depliesChange" }, host: { classAttribute: "st-tree" }, ngImport: i0, template: "<ul\n #arbre=\"ngTree\"\n class=\"st-tree__racine\"\n ngTree\n selectionMode=\"explicit\"\n [attr.aria-label]=\"etiquette()\"\n [multi]=\"multiple()\"\n [value]=\"valeursChoisies()\"\n (valueChange)=\"surSelection($event)\"\n>\n <ng-container\n *ngTemplateOutlet=\"niveau; context: { noeuds: noeuds(), parent: arbre, niveau: 1 }\"\n />\n</ul>\n\n<ng-template #niveau let-noeuds=\"noeuds\" let-parent=\"parent\" let-profondeur=\"niveau\">\n @for (noeud of noeuds; track noeud.valeur) {\n <li\n #element=\"ngTreeItem\"\n class=\"st-tree__element\"\n ngTreeItem\n [parent]=\"parent\"\n [value]=\"noeud.valeur\"\n [label]=\"noeud.libelle\"\n [disabled]=\"noeud.desactive === true\"\n [expanded]=\"estDeplie(noeud)\"\n [style.--st-arbre-niveau]=\"profondeur\"\n (expandedChange)=\"surDepliage(noeud, $event)\"\n >\n @if (noeud.enfants?.length) {\n <st-icon\n class=\"st-tree__chevron\"\n taille=\"sm\"\n [nom]=\"estDeplie(noeud) ? 'chevron-bas' : 'chevron-droite'\"\n />\n } @else {\n <span class=\"st-tree__espace\"></span>\n }\n <span class=\"st-tree__libelle\">{{ noeud.libelle }}</span>\n <st-icon class=\"st-tree__coche\" nom=\"coche\" taille=\"sm\" />\n </li>\n @if (noeud.enfants?.length) {\n <ul class=\"st-tree__groupe\" role=\"group\">\n <ng-template ngTreeItemGroup [ownedBy]=\"element\" #groupe=\"ngTreeItemGroup\">\n <ng-container\n *ngTemplateOutlet=\"\n niveau;\n context: { noeuds: noeud.enfants, parent: groupe, niveau: profondeur + 1 }\n \"\n />\n </ng-template>\n </ul>\n }\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-tree__racine,.st-tree__groupe{padding:0;margin:0;list-style:none}.st-tree__element{display:flex;gap:var(--st-espacement-1);align-items:center;min-block-size:var(--st-cible-tactile-min);padding-inline:calc((var(--st-arbre-niveau, 1) - 1) * 1.5rem + var(--st-espacement-2)) var(--st-espacement-2);cursor:pointer;border-radius:var(--st-rayon-sm)}@media(hover:hover){.st-tree__element:hover{background:var(--st-couleur-surface-survol)}}.st-tree__element:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px}.st-tree__element[aria-selected=true]{font-weight:var(--st-graisse-semi);background:var(--st-couleur-primaire-discret)}.st-tree__element[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-tree__chevron,.st-tree__espace{flex:none;inline-size:1rem}.st-tree__coche{margin-inline-start:auto;visibility:hidden}[aria-selected=true]>.st-tree__coche{visibility:visible}.st-tree__groupe:empty{display:none}\n"], dependencies: [{ kind: "directive", type: Tree, selector: "[ngTree]", inputs: ["id", "orientation", "multi", "disabled", "selectionMode", "focusMode", "wrap", "softDisabled", "typeaheadDelay", "tabindex", "value", "nav", "currentType"], outputs: ["valueChange"], exportAs: ["ngTree"] }, { kind: "directive", type: TreeItem, selector: "[ngTreeItem]", inputs: ["id", "value", "parent", "disabled", "selectable", "expanded", "label"], outputs: ["expandedChange"], exportAs: ["ngTreeItem"] }, { kind: "directive", type: TreeItemGroup, selector: "ng-template[ngTreeItemGroup]", inputs: ["ownedBy"], exportAs: ["ngTreeItemGroup"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StIcon, selector: "st-icon", inputs: ["nom", "sprite", "nomMetier", "taille", "ton", "etiquette"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
568
- }
569
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTree, decorators: [{
570
- type: Component,
571
- args: [{ selector: 'st-tree', changeDetection: ChangeDetectionStrategy.OnPush, imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet, StIcon], host: { class: 'st-tree' }, template: "<ul\n #arbre=\"ngTree\"\n class=\"st-tree__racine\"\n ngTree\n selectionMode=\"explicit\"\n [attr.aria-label]=\"etiquette()\"\n [multi]=\"multiple()\"\n [value]=\"valeursChoisies()\"\n (valueChange)=\"surSelection($event)\"\n>\n <ng-container\n *ngTemplateOutlet=\"niveau; context: { noeuds: noeuds(), parent: arbre, niveau: 1 }\"\n />\n</ul>\n\n<ng-template #niveau let-noeuds=\"noeuds\" let-parent=\"parent\" let-profondeur=\"niveau\">\n @for (noeud of noeuds; track noeud.valeur) {\n <li\n #element=\"ngTreeItem\"\n class=\"st-tree__element\"\n ngTreeItem\n [parent]=\"parent\"\n [value]=\"noeud.valeur\"\n [label]=\"noeud.libelle\"\n [disabled]=\"noeud.desactive === true\"\n [expanded]=\"estDeplie(noeud)\"\n [style.--st-arbre-niveau]=\"profondeur\"\n (expandedChange)=\"surDepliage(noeud, $event)\"\n >\n @if (noeud.enfants?.length) {\n <st-icon\n class=\"st-tree__chevron\"\n taille=\"sm\"\n [nom]=\"estDeplie(noeud) ? 'chevron-bas' : 'chevron-droite'\"\n />\n } @else {\n <span class=\"st-tree__espace\"></span>\n }\n <span class=\"st-tree__libelle\">{{ noeud.libelle }}</span>\n <st-icon class=\"st-tree__coche\" nom=\"coche\" taille=\"sm\" />\n </li>\n @if (noeud.enfants?.length) {\n <ul class=\"st-tree__groupe\" role=\"group\">\n <ng-template ngTreeItemGroup [ownedBy]=\"element\" #groupe=\"ngTreeItemGroup\">\n <ng-container\n *ngTemplateOutlet=\"\n niveau;\n context: { noeuds: noeud.enfants, parent: groupe, niveau: profondeur + 1 }\n \"\n />\n </ng-template>\n </ul>\n }\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-tree__racine,.st-tree__groupe{padding:0;margin:0;list-style:none}.st-tree__element{display:flex;gap:var(--st-espacement-1);align-items:center;min-block-size:var(--st-cible-tactile-min);padding-inline:calc((var(--st-arbre-niveau, 1) - 1) * 1.5rem + var(--st-espacement-2)) var(--st-espacement-2);cursor:pointer;border-radius:var(--st-rayon-sm)}@media(hover:hover){.st-tree__element:hover{background:var(--st-couleur-surface-survol)}}.st-tree__element:focus-visible{outline:2px solid var(--st-couleur-anneau-focus);outline-offset:-2px}.st-tree__element[aria-selected=true]{font-weight:var(--st-graisse-semi);background:var(--st-couleur-primaire-discret)}.st-tree__element[aria-disabled=true]{color:var(--st-couleur-texte-desactive);cursor:not-allowed}.st-tree__chevron,.st-tree__espace{flex:none;inline-size:1rem}.st-tree__coche{margin-inline-start:auto;visibility:hidden}[aria-selected=true]>.st-tree__coche{visibility:visible}.st-tree__groupe:empty{display:none}\n"] }]
572
- }], propDecorators: { etiquette: [{ type: i0.Input, args: [{ isSignal: true, alias: "etiquette", required: true }] }], noeuds: [{ type: i0.Input, args: [{ isSignal: true, alias: "noeuds", required: true }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }, { type: i0.Output, args: ["selectionChange"] }], deplies: [{ type: i0.Input, args: [{ isSignal: true, alias: "deplies", required: false }] }, { type: i0.Output, args: ["depliesChange"] }] } });
573
-
574
- /**
575
- * @starterlib/ui/interactions — les composants montes sur Angular Aria qui ne
576
- * se posent pas au-dessus de la page.
577
- *
578
- * ⚠️ Un point d'entree separe pour la meme raison que `ui/surfaces` : ce qui vit
579
- * ici traine `@angular/aria`, pair optionnel. Laisse dans le tronc, il entrerait
580
- * dans le train de singletons de chaque application. Mais ce n'est pas une
581
- * surface : des onglets font partie de la page. Le ranger sous `ui/surfaces`
582
- * aurait fait mentir le nom, et l'ADR 0003 aurait perdu sa lisibilite (audit de
583
- * la vague 2, decision 5.1).
584
- *
585
- * ⚠️ En federation, ce point d'entree se partage SEPAREMENT : c'est un
586
- * specificateur de module distinct.
1
+ export * from '@starterlib/ui/onglets';
2
+ export * from '@starterlib/ui/navigation';
3
+ export * from '@starterlib/ui/selection';
4
+ export * from '@starterlib/ui/arbre';
5
+
6
+ /**
7
+ * @starterlib/ui/interactions — **DEPRECIE depuis la 0.8.0**. Baril de
8
+ * compatibilite : il ne contient plus une ligne de code, il re-exporte les
9
+ * quatre points d'entree qui l'ont remplace.
10
+ *
11
+ * | Ce qu'on importait ici | Ou il vit desormais |
12
+ * |---|---|
13
+ * | `StSidenav`, `StSidenavToggle` | `@starterlib/ui/navigation` |
14
+ * | `StTabs`, `StTabPanel`, `StTabContent` | `@starterlib/ui/onglets` |
15
+ * | `StCombobox`, `StMultiselect` | `@starterlib/ui/selection` |
16
+ * | `StTree` | `@starterlib/ui/arbre` |
17
+ *
18
+ * ⚠️ **Ce qui s'importe d'ici pese les QUATRE familles.** Le baril agrege
19
+ * `@angular/aria/tabs`, `/combobox`, `/listbox`, `/tree`, `@angular/cdk/overlay`
20
+ * et `@angular/cdk/layout` : une page qui ne veut qu'un panneau lateral paie
21
+ * tout le reste (ADR 0031, decision 2). C'est exactement le defaut mesure sur
22
+ * la coquille — 15,2 Ko d'`@angular/aria` pour un seul `StSidenav`.
23
+ *
24
+ * Il survit pour que la migration soit PROGRESSIVE : un remote reste en
25
+ * 0.7.x qui importe `StTabs` d'ici recoit la MEME CLASSE que l'hote qui
26
+ * l'importe de `@starterlib/ui/onglets`, puisque les deux specificateurs sont
27
+ * partages en singleton et que le baril ne fait que re-exporter. A retirer
28
+ * quand plus aucun consommateur ne l'importe.
587
29
  *
588
30
  * ```ts
589
- * import { StTabs, StTabPanel, StTabContent } from '@starterlib/ui/interactions';
31
+ * // Ne plus ecrire :
32
+ * import { StSidenav } from '@starterlib/ui/interactions';
33
+ * // Ecrire :
34
+ * import { StSidenav } from '@starterlib/ui/navigation';
590
35
  * ```
36
+ *
37
+ * @deprecated Importer le point d'entree etroit correspondant.
591
38
  */
592
39
 
593
40
  /**
594
41
  * Generated bundle index. Do not edit.
595
42
  */
596
-
597
- export { StCombobox, StMultiselect, StSidenav, StSidenavToggle, StTabContent, StTabPanel, StTabs, StTree };
598
43
  //# sourceMappingURL=starterlib-ui-interactions.mjs.map