@starterlib/ui 0.2.0 → 0.4.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.
@@ -0,0 +1,536 @@
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 } 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 { ST_LIBELLES, ControleDeSaisie, AnnonceLocale, filtrerOptions, StFormField, StControleDeChamp, StIcon, ContratDeChamp, StIconButton } from '@starterlib/ui';
8
+ import { StDialog } from '@starterlib/ui/surfaces';
9
+ import { Combobox, ComboboxPopup, ComboboxWidget } from '@angular/aria/combobox';
10
+ import { Listbox, Option } from '@angular/aria/listbox';
11
+ import { CdkConnectedOverlay } from '@angular/cdk/overlay';
12
+
13
+ /**
14
+ * Onglets, sur les primitives `@angular/aria/tabs`.
15
+ *
16
+ * ```html
17
+ * <st-tabs etiquette="Compte" [onglets]="onglets" [(selection)]="onglet">
18
+ * <st-tab-panel valeur="operations">
19
+ * <ng-template stTabContent><app-operations /></ng-template>
20
+ * </st-tab-panel>
21
+ * <st-tab-panel valeur="virements">
22
+ * <ng-template stTabContent><app-virements /></ng-template>
23
+ * </st-tab-panel>
24
+ * </st-tabs>
25
+ * ```
26
+ *
27
+ * ⚠️ **La liste d'onglets est rendue par le composant, a partir de donnees**, et
28
+ * non projetee. Les primitives d'Aria relient un onglet a sa liste par
29
+ * INJECTION, et un contenu projete garde l'injecteur de l'endroit ou il est
30
+ * ecrit : c'est la lecon de `st-menu`, ou des elements projetes ne
31
+ * s'enregistraient jamais. Rendue ici, la liste est a coup sur sous la bonne
32
+ * primitive — et ses valeurs peuvent etre dites en francais.
33
+ *
34
+ * Les PANNEAUX, eux, sont projetes : ecrits directement sous `st-tabs`, leur
35
+ * injecteur parent est celui de l'hote, qui porte la primitive `Tabs`.
36
+ *
37
+ * ⚠️ **Activation manuelle par defaut.** En activation automatique, chaque
38
+ * fleche SELECTIONNE l'onglet atteint ; son contenu differe se rend, et charge
39
+ * ses donnees. Parcourir cinq onglets au clavier lancerait cinq requetes pour en
40
+ * lire un. En manuelle, les fleches deplacent le focus et Entree ou Espace
41
+ * selectionne. `automatique` reste le bon choix quand les panneaux sont deja la
42
+ * et s'affichent sans delai.
43
+ */
44
+ class StTabs {
45
+ onglets = input.required(/* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "onglets" }] : /* istanbul ignore next */ []));
47
+ /** Nom accessible de la liste : sans lui, plusieurs listes d'onglets se confondent. */
48
+ etiquette = input.required(/* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
50
+ activation = input('manuelle', /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "activation" }] : /* istanbul ignore next */ []));
52
+ orientation = input('horizontale', /* @ts-ignore */
53
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
54
+ /** Valeur de l'onglet selectionne. `undefined` : le premier onglet disponible. */
55
+ selection = model(undefined, /* @ts-ignore */
56
+ ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
57
+ /**
58
+ * ⚠️ Toujours un onglet selectionne. Le motif ARIA l'exige : une liste sans
59
+ * selection n'affiche aucun panneau, et la page parait vide sans raison.
60
+ */
61
+ selectionEffective = computed(() => this.selection() ?? this.onglets().find((onglet) => onglet.desactive !== true)?.valeur, /* @ts-ignore */
62
+ ...(ngDevMode ? [{ debugName: "selectionEffective" }] : /* istanbul ignore next */ []));
63
+ modeAria = computed(() => this.activation() === 'automatique' ? 'follow' : 'explicit', /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "modeAria" }] : /* istanbul ignore next */ []));
65
+ orientationAria = computed(() => this.orientation() === 'verticale' ? 'vertical' : 'horizontal', /* @ts-ignore */
66
+ ...(ngDevMode ? [{ debugName: "orientationAria" }] : /* istanbul ignore next */ []));
67
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
68
+ 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}.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 });
69
+ }
70
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabs, decorators: [{
71
+ type: Component,
72
+ args: [{ selector: 'st-tabs', changeDetection: ChangeDetectionStrategy.OnPush, imports: [TabList, Tab], hostDirectives: [Tabs], host: {
73
+ class: 'st-tabs',
74
+ '[attr.data-orientation]': 'orientation()',
75
+ }, 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}.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"] }]
76
+ }], 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"] }] } });
77
+
78
+ /**
79
+ * Panneau d'un onglet, relie par sa `valeur`.
80
+ *
81
+ * ⚠️ La primitive d'Aria ne masque RIEN : elle pose `inert` sur le panneau
82
+ * inactif, ce qui le retire de l'arbre d'accessibilite et du clavier, mais le
83
+ * laisse a l'ecran. Sa documentation le dit en une ligne. Sans le style de ce
84
+ * composant, tous les panneaux s'affichaient empiles, muets et intouchables.
85
+ */
86
+ /* eslint-disable-next-line @typescript-eslint/no-extraneous-class --
87
+ Vide par construction : tout le comportement vient de la primitive d'Aria
88
+ composee en directive d'hote. La classe porte le selecteur et le style du
89
+ socle, rien d'autre ; y ajouter un membre pour satisfaire la regle serait
90
+ inventer de l'API. */
91
+ class StTabPanel {
92
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
93
+ 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 });
94
+ }
95
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabPanel, decorators: [{
96
+ type: Component,
97
+ 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"] }]
98
+ }] });
99
+
100
+ /**
101
+ * Contenu differe d'un panneau : rendu a la premiere selection de son onglet.
102
+ *
103
+ * Un ecran a cinq onglets ne charge ainsi que celui qu'on regarde. Voir
104
+ * l'activation manuelle de `st-tabs` : c'est ce qui empeche qu'un simple
105
+ * parcours au clavier les rende tous.
106
+ */
107
+ /* eslint-disable-next-line @typescript-eslint/no-extraneous-class --
108
+ Vide par construction : tout le comportement vient de la primitive d'Aria
109
+ composee en directive d'hote. La classe porte le selecteur et le style du
110
+ socle, rien d'autre ; y ajouter un membre pour satisfaire la regle serait
111
+ inventer de l'API. */
112
+ class StTabContent {
113
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
114
+ 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 });
115
+ }
116
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StTabContent, decorators: [{
117
+ type: Directive,
118
+ args: [{
119
+ selector: 'ng-template[stTabContent]',
120
+ hostDirectives: [TabContent],
121
+ }]
122
+ }] });
123
+
124
+ /** Au-dela, la navigation tient a cote du contenu. Meme seuil que `st-app-shell`. */
125
+ const ECRAN_LARGE = '(min-width: 64rem)';
126
+ let compteur = 0;
127
+ /**
128
+ * Navigation laterale : une region a cote du contenu sur grand ecran, un tiroir
129
+ * sur petit ecran.
130
+ *
131
+ * ```html
132
+ * <st-app-shell>
133
+ * <button entete stButton [stSidenavToggle]="navigation">Menu</button>
134
+ * <st-sidenav navigation #navigation etiquette="Navigation principale">
135
+ * <a routerLink="/comptes">Comptes</a>
136
+ * </st-sidenav>
137
+ * …
138
+ * </st-app-shell>
139
+ * ```
140
+ *
141
+ * ⚠️ **Deux modes, jamais confondus.** Persistante, c'est une REGION de la page :
142
+ * aucun piege, rien a ouvrir. Modale, c'est un TIROIR, donc un `StDialog` — role,
143
+ * piegeage du focus, Echap, retour du focus au bouton — et non une region
144
+ * glissee par-dessus la page avec un peu de CSS, qui laisserait le clavier
145
+ * parcourir la page cachee dessous.
146
+ *
147
+ * ⚠️ **Elle porte son propre `<nav>`, et un seul a la fois.** Sur petit ecran,
148
+ * la region en ligne DISPARAIT quand le tiroir prend le relais : laissee en
149
+ * place, elle resterait un repere de navigation vide, que le lecteur d'ecran
150
+ * proposerait sans rien y trouver.
151
+ *
152
+ * Un lien suivi dans le tiroir le ferme : `StDialog` se ferme a toute navigation
153
+ * du routeur.
154
+ */
155
+ class StSidenav {
156
+ dialogues = inject(StDialog);
157
+ libelles = inject(ST_LIBELLES);
158
+ /**
159
+ * Nom du repere. `null` : « Navigation principale », du dictionnaire. A
160
+ * renseigner des qu'une page compte deux navigations, qui doivent se
161
+ * distinguer.
162
+ */
163
+ etiquette = input(null, /* @ts-ignore */
164
+ ...(ngDevMode ? [{ debugName: "etiquette" }] : /* istanbul ignore next */ []));
165
+ etiquetteLue = computed(() => this.etiquette() ?? this.libelles().navigationPrincipale, /* @ts-ignore */
166
+ ...(ngDevMode ? [{ debugName: "etiquetteLue" }] : /* istanbul ignore next */ []));
167
+ /** `auto` : persistante sur grand ecran, tiroir en dessous. */
168
+ mode = input('auto', /* @ts-ignore */
169
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
170
+ identifiant = `st-sidenav-${(compteur += 1).toString()}`;
171
+ ecranLarge = signal(false, /* @ts-ignore */
172
+ ...(ngDevMode ? [{ debugName: "ecranLarge" }] : /* istanbul ignore next */ []));
173
+ /** Mode reellement applique. Lu par le bouton, qui ne s'affiche qu'en tiroir. */
174
+ modeEffectif = computed(() => {
175
+ const mode = this.mode();
176
+ if (mode !== 'auto')
177
+ return mode;
178
+ return this.ecranLarge() ? 'persistant' : 'modal';
179
+ }, /* @ts-ignore */
180
+ ...(ngDevMode ? [{ debugName: "modeEffectif" }] : /* istanbul ignore next */ []));
181
+ ouvertInterne = signal(false, /* @ts-ignore */
182
+ ...(ngDevMode ? [{ debugName: "ouvertInterne" }] : /* istanbul ignore next */ []));
183
+ ouvert = this.ouvertInterne.asReadonly();
184
+ // Nomme autrement que la variable `#contenu` du gabarit, qui la masquerait.
185
+ gabarit = viewChild.required('contenu', /* @ts-ignore */
186
+ ...(ngDevMode ? [{ debugName: "gabarit" }] : /* istanbul ignore next */ []));
187
+ reference = null;
188
+ constructor() {
189
+ const abonnement = inject(BreakpointObserver)
190
+ .observe(ECRAN_LARGE)
191
+ .subscribe((etat) => {
192
+ this.ecranLarge.set(etat.matches);
193
+ });
194
+ // Un ecran qui s'elargit pendant que le tiroir est ouvert : la navigation
195
+ // redevient une region, et le tiroir n'a plus de raison d'etre.
196
+ effect(() => {
197
+ if (this.modeEffectif() === 'persistant')
198
+ this.fermer();
199
+ });
200
+ inject(DestroyRef).onDestroy(() => {
201
+ abonnement.unsubscribe();
202
+ this.fermer();
203
+ });
204
+ }
205
+ ouvrir() {
206
+ if (this.modeEffectif() !== 'modal' || this.reference !== null)
207
+ return;
208
+ const reference = this.dialogues.ouvrir(this.gabarit(), {
209
+ cote: 'debut',
210
+ etiquette: this.etiquetteLue(),
211
+ });
212
+ this.reference = reference;
213
+ this.ouvertInterne.set(true);
214
+ void reference.ferme.then(() => {
215
+ if (this.reference !== reference)
216
+ return;
217
+ this.reference = null;
218
+ this.ouvertInterne.set(false);
219
+ });
220
+ }
221
+ fermer() {
222
+ const reference = this.reference;
223
+ if (reference === null)
224
+ return;
225
+ this.reference = null;
226
+ this.ouvertInterne.set(false);
227
+ reference.fermer();
228
+ }
229
+ basculer() {
230
+ if (this.ouvertInterne())
231
+ this.fermer();
232
+ else
233
+ this.ouvrir();
234
+ }
235
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenav, deps: [], target: i0.ɵɵFactoryTarget.Component });
236
+ 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 });
237
+ }
238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenav, decorators: [{
239
+ type: Component,
240
+ args: [{ selector: 'st-sidenav', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], host: {
241
+ class: 'st-sidenav',
242
+ '[attr.data-mode]': 'modeEffectif()',
243
+ }, 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"] }]
244
+ }], 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 }] }] } });
245
+
246
+ /**
247
+ * Bouton qui ouvre la navigation en tiroir.
248
+ *
249
+ * ⚠️ **Masque quand la navigation est persistante.** Un bouton « Menu » qui ne
250
+ * fait rien, parce que la navigation est deja affichee a cote, est un arret de
251
+ * tabulation inutile et une promesse non tenue.
252
+ *
253
+ * ⚠️ `aria-expanded` suit l'ouverture du tiroir : sans lui, rien ne dit a qui
254
+ * n'en voit pas l'effet que le bouton a agi.
255
+ */
256
+ class StSidenavToggle {
257
+ stSidenavToggle = input.required(/* @ts-ignore */
258
+ ...(ngDevMode ? [{ debugName: "stSidenavToggle" }] : /* istanbul ignore next */ []));
259
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenavToggle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
260
+ 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 });
261
+ }
262
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StSidenavToggle, decorators: [{
263
+ type: Directive,
264
+ args: [{
265
+ selector: '[stSidenavToggle]',
266
+ host: {
267
+ 'aria-haspopup': 'dialog',
268
+ '[attr.aria-expanded]': 'stSidenavToggle().ouvert()',
269
+ '[attr.hidden]': 'stSidenavToggle().modeEffectif() === "modal" ? null : ""',
270
+ '(click)': 'stSidenavToggle().basculer()',
271
+ },
272
+ }]
273
+ }], propDecorators: { stSidenavToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "stSidenavToggle", required: true }] }] } });
274
+
275
+ /**
276
+ * Liste de choix a filtrer en tapant, sur `@angular/aria/combobox` et
277
+ * `@angular/aria/listbox`.
278
+ *
279
+ * ```html
280
+ * <st-combobox libelle="Beneficiaire" [options]="beneficiaires" [formField]="virement.beneficiaire" />
281
+ * <!-- liste servie par le serveur : -->
282
+ * <st-combobox libelle="Commune" filtrage="distant" [options]="communes()"
283
+ * (recherche)="terme.set($event)" [formField]="adresse.commune" />
284
+ * ```
285
+ *
286
+ * Le modele est la VALEUR de l'option choisie, `''` sans choix. Le texte tape ne
287
+ * part jamais dans le modele : « Pari » n'est pas une commune.
288
+ *
289
+ * ⚠️ **Pour une longue liste seulement.** Sous une vingtaine d'options,
290
+ * `st-select` — natif — est plus sur, et plus rapide sur mobile.
291
+ *
292
+ * - La liste s'ouvre a la frappe et a la fleche bas ; Entree choisit, Echap
293
+ * ferme. Le focus reste dans le champ (`aria-activedescendant`).
294
+ * - Le nombre de resultats s'annonce apres la frappe, une fois : le lecteur
295
+ * d'ecran ne voit pas la liste se reduire.
296
+ * - ⚠️ **Quitter le champ sans choisir rend le texte du choix precedent.** Un
297
+ * texte qui ne correspond a aucune option ne reste pas affiche comme s'il etait
298
+ * retenu ; un champ vide, lui, retire le choix.
299
+ * - La liste est posee dans la couche superieure du navigateur (`popover`) par le
300
+ * CDK : un dialogue ou un tableau defilant ne la rogne pas.
301
+ *
302
+ * ⚠️ En `distant`, `recherche` est emis a chaque frappe : a l'application de
303
+ * temporiser, et de ne pas mettre le terme dans l'adresse (SEC-6).
304
+ */
305
+ class StCombobox extends ControleDeSaisie {
306
+ libelles = inject(ST_LIBELLES);
307
+ element = inject(ElementRef);
308
+ injecteur = inject(Injector);
309
+ options = input.required(/* @ts-ignore */
310
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
311
+ filtrage = input('local', /* @ts-ignore */
312
+ ...(ngDevMode ? [{ debugName: "filtrage" }] : /* istanbul ignore next */ []));
313
+ /** Texte tape, a chaque frappe. Sert au filtrage `distant`. */
314
+ recherche = output();
315
+ annonce = new AnnonceLocale(inject(DestroyRef));
316
+ ouvert = signal(false, /* @ts-ignore */
317
+ ...(ngDevMode ? [{ debugName: "ouvert" }] : /* istanbul ignore next */ []));
318
+ /** Texte du champ : le libelle du choix, tant que la personne ne tape pas. */
319
+ texte = linkedSignal(() => this.libelleDe(this.value()), /* @ts-ignore */
320
+ ...(ngDevMode ? [{ debugName: "texte" }] : /* istanbul ignore next */ []));
321
+ /** Ce qui filtre : vide a l'ouverture par la fleche, pour montrer toute la liste. */
322
+ filtre = signal('', /* @ts-ignore */
323
+ ...(ngDevMode ? [{ debugName: "filtre" }] : /* istanbul ignore next */ []));
324
+ /** Option active dans la liste ; le choix n'est retenu qu'a la validation. */
325
+ surlignee = linkedSignal(() => this.value() === '' ? [] : [this.value()], /* @ts-ignore */
326
+ ...(ngDevMode ? [{ debugName: "surlignee" }] : /* istanbul ignore next */ []));
327
+ visibles = computed(() => this.filtrage() === 'local' ? filtrerOptions(this.options(), this.filtre()) : this.options(), /* @ts-ignore */
328
+ ...(ngDevMode ? [{ debugName: "visibles" }] : /* istanbul ignore next */ []));
329
+ liste = viewChild(Listbox, /* @ts-ignore */
330
+ ...(ngDevMode ? [{ debugName: "liste" }] : /* istanbul ignore next */ []));
331
+ constructor() {
332
+ super();
333
+ afterRenderEffect(() => {
334
+ if (this.ouvert())
335
+ this.liste()?.scrollActiveItemIntoView({ block: 'nearest' });
336
+ });
337
+ }
338
+ surSaisie(evenement) {
339
+ const saisie = this.texteSaisi(evenement);
340
+ this.filtre.set(saisie);
341
+ this.recherche.emit(saisie);
342
+ if (this.filtrage() === 'local') {
343
+ this.annonce.annoncer(this.libelles().nombreDeResultats(this.visibles().length));
344
+ }
345
+ }
346
+ basculer() {
347
+ if (this.disabled() || this.readonly())
348
+ return;
349
+ this.filtre.set('');
350
+ this.ouvert.update((ouvert) => !ouvert);
351
+ this.focus();
352
+ }
353
+ valider() {
354
+ const choisie = this.surlignee()[0];
355
+ if (choisie === undefined)
356
+ return;
357
+ this.value.set(choisie);
358
+ this.texte.set(this.libelleDe(choisie));
359
+ this.filtre.set('');
360
+ this.ouvert.set(false);
361
+ afterNextRender(() => {
362
+ this.focus();
363
+ }, { injector: this.injecteur });
364
+ }
365
+ aLaSortie(evenement) {
366
+ const suivant = evenement.relatedTarget;
367
+ if (suivant !== null && this.element.nativeElement.contains(suivant))
368
+ return;
369
+ if (this.texte().trim() === '')
370
+ this.value.set('');
371
+ this.texte.set(this.libelleDe(this.value()));
372
+ this.filtre.set('');
373
+ this.touch.emit();
374
+ }
375
+ libelleDe(valeur) {
376
+ if (valeur === '')
377
+ return '';
378
+ return this.options().find((option) => option.valeur === valeur)?.libelle ?? '';
379
+ }
380
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCombobox, deps: [], target: i0.ɵɵFactoryTarget.Component });
381
+ 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-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-rayon-sm)}.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", "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 });
382
+ }
383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StCombobox, decorators: [{
384
+ type: Component,
385
+ args: [{ selector: 'st-combobox', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
386
+ StFormField,
387
+ StControleDeChamp,
388
+ StIcon,
389
+ Combobox,
390
+ ComboboxPopup,
391
+ ComboboxWidget,
392
+ Listbox,
393
+ Option,
394
+ CdkConnectedOverlay,
395
+ ], host: {
396
+ class: 'st-combobox',
397
+ '(focusout)': 'aLaSortie($event)',
398
+ }, 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-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-rayon-sm)}.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"] }]
399
+ }], 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 }] }] } });
400
+
401
+ /**
402
+ * Choix de plusieurs valeurs dans une liste, sur `@angular/aria/combobox` et
403
+ * `@angular/aria/listbox`.
404
+ *
405
+ * ```html
406
+ * <st-multiselect libelle="Comptes a inclure" [options]="comptes" [formField]="releve.comptes" />
407
+ * ```
408
+ *
409
+ * Le modele est la liste des VALEURS choisies, dans l'ordre des options.
410
+ *
411
+ * - Fleche bas, Entree ou Espace ouvrent la liste ; Espace coche ou decoche
412
+ * l'option active sans fermer ; Echap ferme.
413
+ * - ⚠️ **Chaque choix reste visible et retirable** sous le champ, par un bouton
414
+ * « Retirer … ». Un resume « 3 choix » dans le declencheur ne dit pas lesquels,
415
+ * et obligerait a rouvrir la liste pour en retirer un.
416
+ * - Un choix retire rend le focus au retrait suivant, ou au champ : le focus ne
417
+ * tombe jamais sur le document.
418
+ *
419
+ * ⚠️ Sous une dizaine d'options, un groupe de cases a cocher (`st-fieldset` et
420
+ * `st-checkbox`) est plus simple : tout est visible, rien a ouvrir.
421
+ */
422
+ class StMultiselect extends ContratDeChamp {
423
+ libelles = inject(ST_LIBELLES);
424
+ element = inject(ElementRef);
425
+ injecteur = inject(Injector);
426
+ value = model([], /* @ts-ignore */
427
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
428
+ options = input.required(/* @ts-ignore */
429
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
430
+ annonce = new AnnonceLocale(inject(DestroyRef));
431
+ ouvert = signal(false, /* @ts-ignore */
432
+ ...(ngDevMode ? [{ debugName: "ouvert" }] : /* istanbul ignore next */ []));
433
+ liste = viewChild(Listbox, /* @ts-ignore */
434
+ ...(ngDevMode ? [{ debugName: "liste" }] : /* istanbul ignore next */ []));
435
+ /** La liste d'Aria attend un tableau modifiable ; le modele, lui, est en lecture seule. */
436
+ valeursDeListe = computed(() => [...this.value()], /* @ts-ignore */
437
+ ...(ngDevMode ? [{ debugName: "valeursDeListe" }] : /* istanbul ignore next */ []));
438
+ choisies = computed(() => {
439
+ const valeurs = new Set(this.value());
440
+ return this.options().filter((option) => valeurs.has(option.valeur));
441
+ }, /* @ts-ignore */
442
+ ...(ngDevMode ? [{ debugName: "choisies" }] : /* istanbul ignore next */ []));
443
+ resume = computed(() => {
444
+ const choisies = this.choisies();
445
+ return choisies.length === 0
446
+ ? this.libelles().choisir
447
+ : choisies.map((option) => option.libelle).join(', ');
448
+ }, /* @ts-ignore */
449
+ ...(ngDevMode ? [{ debugName: "resume" }] : /* istanbul ignore next */ []));
450
+ constructor() {
451
+ super();
452
+ afterRenderEffect(() => {
453
+ if (this.ouvert())
454
+ this.liste()?.scrollActiveItemIntoView({ block: 'nearest' });
455
+ });
456
+ }
457
+ /** Le declencheur n'est pas un champ natif : la base ne le trouve pas par `#champ`. */
458
+ focus(options) {
459
+ this.element.nativeElement.querySelector('[ngCombobox]')?.focus(options);
460
+ }
461
+ surChoix(valeurs) {
462
+ if (this.readonly())
463
+ return;
464
+ // Dans l'ordre des options, et non dans l'ordre des clics : un modele stable
465
+ // ne change pas de forme selon le chemin pris pour le remplir.
466
+ const retenues = new Set(valeurs);
467
+ this.value.set(this.options()
468
+ .filter((option) => retenues.has(option.valeur))
469
+ .map((option) => option.valeur));
470
+ }
471
+ retirer(valeur, indice) {
472
+ const restantes = this.value().filter((choisie) => choisie !== valeur);
473
+ this.value.set(restantes);
474
+ this.annonce.annoncer(this.libelles().nombreDeSelections(restantes.length));
475
+ afterNextRender(() => {
476
+ const boutons = this.element.nativeElement.querySelectorAll('.st-multiselect__retrait');
477
+ const suivant = boutons[Math.min(indice, boutons.length - 1)];
478
+ if (suivant !== undefined)
479
+ suivant.focus();
480
+ else
481
+ this.focus();
482
+ }, { injector: this.injecteur });
483
+ }
484
+ aLaSortie(evenement) {
485
+ const suivant = evenement.relatedTarget;
486
+ if (suivant !== null && this.element.nativeElement.contains(suivant))
487
+ return;
488
+ this.touch.emit();
489
+ }
490
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMultiselect, deps: [], target: i0.ɵɵFactoryTarget.Component });
491
+ 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-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-rayon-sm)}.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", "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 });
492
+ }
493
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: StMultiselect, decorators: [{
494
+ type: Component,
495
+ args: [{ selector: 'st-multiselect', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
496
+ StFormField,
497
+ StControleDeChamp,
498
+ StIcon,
499
+ StIconButton,
500
+ Combobox,
501
+ ComboboxPopup,
502
+ ComboboxWidget,
503
+ Listbox,
504
+ Option,
505
+ CdkConnectedOverlay,
506
+ ], host: {
507
+ class: 'st-multiselect',
508
+ '(focusout)': 'aLaSortie($event)',
509
+ }, 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-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-rayon-sm)}.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"] }]
510
+ }], 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 }] }] } });
511
+
512
+ /**
513
+ * @starterlib/ui/interactions — les composants montes sur Angular Aria qui ne
514
+ * se posent pas au-dessus de la page.
515
+ *
516
+ * ⚠️ Un point d'entree separe pour la meme raison que `ui/surfaces` : ce qui vit
517
+ * ici traine `@angular/aria`, pair optionnel. Laisse dans le tronc, il entrerait
518
+ * dans le train de singletons de chaque application. Mais ce n'est pas une
519
+ * surface : des onglets font partie de la page. Le ranger sous `ui/surfaces`
520
+ * aurait fait mentir le nom, et l'ADR 0003 aurait perdu sa lisibilite (audit de
521
+ * la vague 2, decision 5.1).
522
+ *
523
+ * ⚠️ En federation, ce point d'entree se partage SEPAREMENT : c'est un
524
+ * specificateur de module distinct.
525
+ *
526
+ * ```ts
527
+ * import { StTabs, StTabPanel, StTabContent } from '@starterlib/ui/interactions';
528
+ * ```
529
+ */
530
+
531
+ /**
532
+ * Generated bundle index. Do not edit.
533
+ */
534
+
535
+ export { StCombobox, StMultiselect, StSidenav, StSidenavToggle, StTabContent, StTabPanel, StTabs };
536
+ //# sourceMappingURL=starterlib-ui-interactions.mjs.map