@starterlib/ui
v0.8.0
Published
Bibliotheque de composants st-* du socle frontal starter, sur CDK et Aria.
Readme
@starterlib/ui
Composants st-* du socle frontal starter. Zoneless, signal-first, OnPush,
autonomes. Ils consomment les jetons de @starterlib/tokens.
Installation
pnpm add @starterlib/ui @starterlib/tokens @starterlib/utilLes versions d'Angular, de @starterlib/tokens et de @starterlib/util sont
des dependances de pair : c'est l'application qui les installe, et elles doivent
etre identiques dans l'hote et dans chaque remote. Deux versions du socle
dans le meme navigateur, ce sont deux jeux de jetons d'injection.
Onze points d'entree
Deux criteres, dans cet ordre.
- La dependance (ADR 0003) : un composant sort du tronc quand il traine un pair lourd, et le point d'entree ou il va se choisit par ce qu'il EST pour qui l'emploie.
- Le moment de peinture (ADR 0031, 0.8.0) : a dependance egale, ce que l'hote ne peint pas sort aussi. Un paquet partage en singleton part ENTIER — la federation annule l'elagage —, donc ce que la coquille n'affiche jamais lui coute plein tarif au premier ecran, et ne coute RIEN au remote qui l'affiche, puisque le tronc y est deja charge.
| Point d'entree | Contenu | Pairs lourds importes statiquement |
| ---------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| @starterlib/ui | le TRONC : noyau, icone, texte, actions, etats, retour, indicateurs, structure, transverses, st-code | aucun |
| @starterlib/ui/saisie | les champs de formulaire, le calendrier, le televersement | aucun (des TYPES d'@angular/forms) |
| @starterlib/ui/listes | liste, liste de description, table de donnees, pagination, tri, filtres | aucun |
| @starterlib/ui/donnees | montant, date, date relative, nombre, valeur masquee, indicateur chiffre | aucun |
| @starterlib/ui/libelles-en | le catalogue anglais | aucun |
| @starterlib/ui/surfaces | ce qui se pose AU-DESSUS de la page : menu, dialogues, tiroirs, messages ephemeres | @angular/cdk/{a11y,dialog,keycodes,overlay,portal}, @angular/aria/menu |
| @starterlib/ui/navigation | la navigation laterale | @angular/cdk/layout — et rien d'autre |
| @starterlib/ui/onglets | les onglets et leurs panneaux | @angular/aria/tabs |
| @starterlib/ui/selection | liste deroulante filtrante, choix multiple | @angular/aria/{combobox,listbox}, @angular/cdk/overlay |
| @starterlib/ui/arbre | l'arborescence | @angular/aria/tree |
| @starterlib/ui/formulaires | les champs qui LISENT leur saisie : montant, date, plage de dates | le code d'@angular/forms (transformedValue) |
import { StButton, StCard, StError } from '@starterlib/ui';
import { StFormField, StInput, StSelect } from '@starterlib/ui/saisie';
import { StDataTable, StPagination } from '@starterlib/ui/listes';
import { StMoney, StDate } from '@starterlib/ui/donnees';
import { StDialog, StToast } from '@starterlib/ui/surfaces';
import { StSidenav } from '@starterlib/ui/navigation';
import { StTabs } from '@starterlib/ui/onglets';
import { StCombobox, StMultiselect } from '@starterlib/ui/selection';
import { StTree } from '@starterlib/ui/arbre';
import { StAmountField, StDateField } from '@starterlib/ui/formulaires';Les aretes internes admises, et elles seules : saisie, listes, donnees,
surfaces, navigation et arbre importent le tronc ; formulaires et
selection importent en plus saisie, parce qu'une liste deroulante et un
champ de montant sont d'abord des CHAMPS. navigation atteint surfaces par
import() seulement : le dialogue du tiroir ne pese pas sur le premier rendu.
Ce qui reste des anciens specificateurs
@starterlib/ui/interactions survit, deprecie, comme baril de
re-exports vers navigation, onglets, selection et arbre : un remote
reste en 0.7.x recoit la MEME CLASSE que l'hote, et la migration se fait
service par service. ⚠️ Ce qui s'importe du baril pese les quatre familles.
Le tronc, lui, ne re-exporte pas saisie, listes, donnees ni
libelles-en : ces points d'entree importent le tronc, et un tronc qui les
re-exporterait fermerait un cycle entre points d'entree que ng-packagr refuse.
Monter en 0.8.0 demande donc de deplacer ces imports-la, et rien d'autre.
⚠️ Un paquet partage en federation ne s'elague pas : declare singleton, il est charge en entier des qu'un remote le reference. Le decoupage ne vise donc pas l'elagage, mais l'unite de CHARGEMENT — et le train de singletons : une application qui n'affiche ni menu, ni dialogue, ni onglets ne charge ni le CDK ni Aria, et n'a pas a se redeployer quand ils montent de version.
@angular/cdk, @angular/aria et @angular/forms sont des pairs
optionnels : le tronc n'en importe que des TYPES. Ils deviennent obligatoires
des le premier import d'un point d'entree secondaire qui les emploie, et leur
absence se voit alors a la resolution, pas a l'execution. ⚠️ @angular/aria
exige @angular/cdk a la version EXACTE : les deux montent ensemble.
Ce que chaque point d'entree importe a l'execution est verifie sur le paquet
construit (pnpm paquets:check), au specificateur exact : navigation a
droit a @angular/cdk/layout, et un @angular/cdk/overlay qui s'y glisserait
echoue avant la publication. Une regle non outillee est une opinion.
⚠️ En federation, chaque point d'entree se partage SEPAREMENT. Ce sont onze
specificateurs de module distincts : partager @starterlib/ui ne couvre aucun
des dix autres. Omis de la configuration, chaque remote en embarque sa propre
copie — et la regle singleton: true, strictVersion: true qui protege le tronc
ne protege plus rien au-dessus. Le decoupage achete l'independance de
CHARGEMENT, jamais celle de VERSION : les onze portent le numero de leur paquet.
// federation.config.mjs de l'hote ET de chaque remote
import { partageDuSocle } from '@starterlib/core/federation';
shared: { ...share({ ...ANGULAR_ET_RXJS, ...partageDuSocle('0.8') }) }⚠️ Cette liste ne se recopie plus. Depuis la 0.8.0 elle est publiee par
@starterlib/core/federation : partageDuSocle(train) rend les seize
specificateurs du socle — les onze de ui, le baril deprecie, tokens, util,
core et core/federation —, chacun en singleton: true, strictVersion: true,
includeSecondaries: false. La raison n'est pas la commodite : une ligne oubliee
n'est pas une erreur de construction, le specificateur se duplique simplement
dans le remote, avec son etat. Passer de quatre a douze specificateurs ui en
recopiant aurait multiplie par trois les occasions de cet oubli.
⚠️ Partager un point d'entree que personne n'importe coute zero octet : il n'entre dans aucune carte d'importation. La liste est donc exhaustive sans arbitrage — c'est l'IMPORT qui coute, jamais le partage.
Voir docs/adr/0003-points-d-entree-decoupes-par-dependance.md,
docs/adr/0004-le-tronc-est-dimensionne-par-le-premier-ecran-de-l-hote.md,
docs/recettes/federation.md, et l'ADR 0031 du socle backend.
Configuration — une fois, dans l'hote
import { provideStarterUi, LIBELLES_FR } from '@starterlib/ui';
export const appConfig: ApplicationConfig = {
providers: [
provideStarterUi({
cheminDuSprite: '/ui/socle/assets/icones/sprite.svg',
locale, // Signal<string> : les formats suivent la langue a chaud
libelles, // Signal<LibellesUi> : LIBELLES_FR, LIBELLES_EN, ou le votre
fuseau: 'Europe/Paris',
}),
],
};⚠️ Dans l'hote uniquement. Un remote qui la rappellerait poserait un second jeu de valeurs dans son injecteur de route : deux locales, deux dictionnaires, et la moitie de l'ecran resterait dans l'ancienne langue apres une bascule.
Sans configuration, les composants prennent LOCALE_ID et le francais. C'est un
depannage, pas une i18n : une application bilingue doit fournir des signaux,
sinon la bascule de langue ne touche pas les composants du socle.
Sprite d'icones — a cabler avant le premier rendu
st-icon lit un sprite SVG servi en meme origine. Le paquet livre l'actif,
l'application le sert et en donne le chemin. Aucun chemin par defaut : une
reference use qui ne resout pas ne leve rien, et toutes les icones resteraient
vides en silence.
angular.json, cible build :
"assets": [
{
"glob": "sprite.svg",
"input": "node_modules/@starterlib/ui/assets/icones",
"output": "assets/icones",
},
]app.config.ts :
import { fournirCheminDuSprite } from '@starterlib/ui';
export const appConfig: ApplicationConfig = {
providers: [fournirCheminDuSprite('/ui/socle/assets/icones/sprite.svg')],
};Le prefixe est celui sous lequel l'interface est servie (/ui/<interface>/).
Un chemin empreinte (sprite.<hash>.svg) donne un cache immuable.
Feuille de jetons
Les composants ne portent aucune couleur en dur : ils lisent les variables CSS
de @starterlib/tokens. Sans la feuille, tout s'affiche sans style.
@import '@starterlib/tokens/styles/starter-tokens.css';Ce qui est livre
Vague 1 : etats (st-spinner, st-skeleton, st-empty, st-error,
st-offline-banner, st-progress, st-state), texte (st-text,
st-heading), donnees (st-money, st-date, st-relative-time, st-masked,
st-count — passes sous ui/donnees en 0.8.0 ; st-code reste au tronc), actions (st-button, st-icon-button, st-link,
st-button-group), indicateurs (st-badge, st-status-pill, st-tag,
st-avatar), iconographie (st-icon). st-menu est passe sous ui/surfaces.
Vague 2 :
- Tronc — structure (
st-app-shell,st-header,st-skip-link,st-page-title,st-breadcrumb,st-section,st-card,st-divider), retour (st-banner,st-inline-message), transverses (st-theme-toggle,st-lang-switch,st-copy-button,st-timer,st-logo). ui/surfaces—StDialog(dialogue, tiroir, feuille inferieure),StConfirmDialog,StToast,st-idle-warning,stTooltip,st-popover.ui/onglets—st-tabs,st-tab-panel,stTabContent;ui/navigation—st-sidenav,stSidenavToggle(tous trois sousui/interactionsavant la 0.8.0).
Vague 3 — la saisie, sur Signal Forms :
ui/saisie(le tronc avant la 0.8.0) —st-form-field,st-input,st-textarea,st-fieldset,st-checkbox,st-radio-group,st-switch,st-error-summary,st-password-field,st-otp-field,st-search-field,st-iban-field,st-phone-field,st-select(natif),st-file-upload,st-calendar; messages d'erreur traduits et fonctions de schema (erreurIban,erreurTelephone,erreurDate,erreurPlageDeDates,erreursDeFichiers).ui/selection—st-combobox,st-multiselect.ui/formulaires—st-amount-field,st-date-field,st-date-range.
Un champ s'emploie avec un formulaire, par [formField], ou seul, par
[(value)]. Les regles d'un schema sont des fonctions PURES du socle, a brancher
par validate : les fournir toutes faites importerait le code d'@angular/forms
a l'execution dans ui/saisie.
readonly virement = form(this.modele, (chemin) => {
required(chemin.beneficiaire);
validate(chemin.iban, ({ value }) => erreurIban(value()));
validate(chemin.execution, ({ value }) => erreurDate(value(), { min: aujourdHui }));
});Vague 4 — listes et tableaux, au contrat du backend (curseur vers l'avant, sans total ni tri serveur) :
ui/listes(le tronc avant la 0.8.0) —st-data-table(stCellule,stTableauVide,stActionsDeTableau),st-sort-header,st-pagination,st-list(stElementDeListe),st-description-list(stDescription),st-filter-bar,st-filter-chip.ui/arbre—st-tree.@starterlib/core—NavigationParCurseur;@starterlib/util—PageParCurseur<T>.
readonly navigation = new NavigationParCurseur();
readonly page = httpResource<PageParCurseur<Expedition>>(() => ({
url: '/api/v1/expeditions',
params: this.navigation.parametres(),
}));<st-data-table
#tableau
legende="Expeditions"
cle="id"
[colonnes]="colonnes"
[lignes]="page.value()?.content ?? []"
[chargement]="page.isLoading()"
>
<ng-template stCellule="reference" [stCelluleDe]="page.value()?.content" let-ligne>
<a [routerLink]="['/expeditions', ligne.id]">{{ ligne.reference }}</a>
</ng-template>
</st-data-table>
<st-pagination
[cible]="tableau"
[numero]="navigation.numeroDePage()"
[precedenteDisponible]="navigation.precedenteDisponible()"
[suivanteDisponible]="page.value()?.hasMore ?? false"
[chargement]="page.isLoading()"
(precedente)="navigation.precedente()"
(suivante)="navigation.suivante(page.value())"
/>Phase 0 de la coquille (plan de la phase 2 des interfaces, lots S05, S13, S14, S17) — ce que la coquille et les remotes assemblent :
- Tronc, structure —
st-app-shell(emplacementbannieres, hauteur de l'en-tete mesuree et posee sur<html>pourscroll-padding, impression, zone sure),st-footer(mentions, version, statut),stNavLink(routerLink+aria-current="page", icone et pastille),st-page-header(fil,h1, actions, onglets),st-route-progress(barre fine, rien avant 200 ms),st-auth-shell(gabarit pleine page),st-stepper(ol,aria-current="step"),st-settings-row(groupe nomme et decrit). - Tronc, actions et donnees —
st-form-actions(barre collee en bas, jamais fixee),st-stat(tendance par forme et texte). ui/surfaces—st-toolbar(une action principale, les secondaires repliees dans un menu « Plus » sousmd).st-icon—spriteetnomMetierpour les icones d'un remote (docs/recettes/sprite-d-icones.md, section 7) ;st-progressprend uneepaisseurfine.- Feuilles — chaque
:hoversous@media (hover: hover), les points de rupture lus dans le partiel_ruptures.scssde@starterlib/tokens(@include st.depuis('lg')), gardes dansstyles/gardes-de-feuille.spec.ts.
Inventaire et regles de la suite : docs/plan-librairie.md et les audits de
vague (docs/audit-vague-2-…, -3-…, -4-2026-09-17.md) du depot.
Ce que le banc en vrai navigateur verifie
Le depot mesure ses composants dans Chromium (pnpm test:navigateur), parce que
jsdom ne calcule ni mise en page ni cascade :
- contraste reel de chaque texte, dans les DEUX themes ;
- taille de chaque cible de pointage (24 px au moins, WCAG 2.5.8) ;
- anneau de focus a chaque arret de tabulation, au VRAI clavier, et son contraste contre ce qui l'entoure ;
- encre de chaque icone, lue par le moteur de rendu, dans une zone sure commune.
Voir docs/audit-design-2026-09-17.md pour ce que ces mesures ont corrige.
Regles tenues par les composants
- Un statut se lit par une icone et un texte, jamais par la couleur seule.
- Aucun acces a
windownidocumenthorsafterNextRender: le rendu serveur reste possible sans reecriture. - Aucun style en ligne : la politique de securite du contenu a nonce l'interdit.
- Aucun stockage navigateur.
