fold-ng 0.12.1 → 0.14.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,283 @@ All notable changes to **fold-ng** are documented here. The format follows
8
8
 
9
9
  _Nothing yet._
10
10
 
11
+ ## [0.14.0] - 2026-08-23
12
+
13
+ ### Changed
14
+
15
+ - **BREAKING — `fold-nav-launcher` a DEUX niveaux.** Une tuile qui contient des
16
+ tuiles est un **groupe** (`fold-nav-group`) : aucun `level` à piloter, aucun
17
+ mode à basculer — le lanceur trouve la profondeur par requête de contenu,
18
+ exactement comme `fold-multiselect` trouve ses `fold-optgroup`. Deux niveaux
19
+ et pas plus : au troisième ce n'est plus un lanceur, c'est une arborescence.
20
+
21
+ Ce qui casse : `icon` devient **facultatif** sur `fold-nav-tile` (une entrée
22
+ de second niveau n'en a pas toujours ; sans icône, la ligne retombe sur un
23
+ point de statut plutôt que de décaler son libellé). La cascade d'entrée n'est
24
+ plus indexée sur `:nth-child` mais sur la **distance à la tuile touchée** —
25
+ c'est le lanceur qui la calcule, parce qu'aucune tuile ne peut connaître
26
+ seule sa distance à un doigt posé sur une autre. `variant` gagne `accent`.
27
+
28
+ Le lanceur se déclare enfin **surface de chrome** : il peignait le sol du
29
+ rail tout en lisant l'encre de la PAGE, ce qui donnait du texte sombre sur
30
+ sombre et des tuiles claires au milieu d'un panneau noir sous tout thème
31
+ mixte. Peindre le sol d'une famille sans en rejoindre la polarité, c'est en
32
+ revendiquer la moitié.
33
+
34
+ Nouveaux réglages : `eyebrow`, `heading`, `glass`, `closeLabel`, `backLabel`,
35
+ et un emplacement `footer` pour ce que l'application possède (le compte, la
36
+ déconnexion) et que la bibliothèque n'a pas à deviner.
37
+
38
+ Ce que la transition doit aux technologies d'assistance, parce que le
39
+ mouvement ne leur dit rien : le **focus suit le niveau** (et ne bouge qu'une
40
+ fois le niveau peint — pris une frame trop tôt il échouait en silence et le
41
+ piège retombait sur le bouton de fermeture, donc le curseur atterrissait sur
42
+ « renvoyer » à chaque descente) ; **Échap devient contextuel** (niveau 2 il
43
+ remonte, niveau 1 il ferme) ; le **niveau est annoncé** par une région vive.
44
+ Le balayage double la flèche, il ne la remplace pas.
45
+
46
+ ### Added
47
+
48
+ - **`fold-menu` — `navLabel`**, le nom du repère `<nav>` du rail. Facultatif
49
+ pour un rail seul : la seule navigation d'une page n'a rien à distinguer.
50
+ Il cesse de l'être dès que la coquille en porte **deux** — un rail
51
+ d'application et un rail d'espace de travail s'annoncent alors tous les deux
52
+ « navigation », et plus rien ne les sépare. La galerie, qui a exactement ces
53
+ deux rails, les nomme désormais.
54
+
55
+ - **`fold-meter`** — une mesure en LECTURE SEULE dans une plage connue (une
56
+ complétude, un quota, un score). Ce n'est ni une barre de progression ni un
57
+ `fold-slider` : ce dernier est un `input type="range"`, focalisable,
58
+ déplaçable, et il s'annonce comme quelque chose qu'on peut changer. Un
59
+ `role="meter"` annonce une valeur déjà décidée. Le raccourci habituel — un
60
+ `div` de 4px — ne donne rien du tout à un lecteur d'écran, et c'est la raison
61
+ d'être du composant. La valeur est **bornée** plutôt que crue : au-delà du max
62
+ elle peindrait hors de la piste, et une plage inversée diviserait par un
63
+ négatif.
64
+
65
+ - **`fold-checklist`** — une liste de conditions et leur état. Troisième
66
+ apparition du motif (les règles d'un champ mot de passe, les préconditions
67
+ d'une zone dangereuse, puis une liste de publication), ce qui est le moment où
68
+ une forme cesse d'être une coïncidence. Chaque état est porté par un **glyphe
69
+ autant que par une couleur** : un point vert et un point ambre sont le même
70
+ point en niveaux de gris, en `forced-colors`, et pour une large part des
71
+ lecteurs. L'état est aussi dit **en toutes lettres** pour un lecteur d'écran,
72
+ qui ne voit aucun glyphe.
73
+
74
+ ## [0.13.0] - 2026-08-23
75
+
76
+ ### Changed
77
+
78
+ - **BREAKING — `brand` / `on-brand` entrent au catalogue (48 rôles).** La
79
+ marque IDENTIFIE, donc contrairement à `primary` elle ne doit pas basculer de
80
+ polarité : une marque teal sur la page et bleu pâle sur le rail a cessé
81
+ d'être la marque. `primary` faisait deux métiers contradictoires — porter la
82
+ marque ET les affordances de premier plan, dont la seconde doit s'éclaircir
83
+ sur un chrome sombre. Un sous-bloc scopé ne peut **jamais** les redéclarer, et
84
+ un test le vérifie.
85
+
86
+ - **BREAKING — `fold-data-table` : `narrowLayout` / `cardsAt` /
87
+ `rowCardChrome`, et `mobileLayout` déprécié** (retiré avant la 1.0). La
88
+ bascule se décide sur la largeur du **conteneur** (600px par défaut), pas sur
89
+ la fenêtre : une table dans un panneau de 480px sur un écran de 1920 restait
90
+ une table illisible. `custom` était une redondance — projeter un
91
+ `foldRowCard` disait déjà tout.
92
+
93
+ - **BREAKING — `surface-band` entre au catalogue (46 rôles).** Une bande ne
94
+ monte pas, elle **s'écarte**, et la direction dépend de la polarité du thème.
95
+ `fold-card` la codait en dur (`surface-hover`, et `surface-card` en sunken) —
96
+ une hypothèse de polarité posée dans un composant. Sur navi la bande valait
97
+ la couleur de la page sur une carte blanche (1,09:1) ; sur le chrome navi
98
+ elle valait **exactement** le corps de la carte (1,00:1). Les quatre thèmes
99
+ non modifiés reprennent la valeur qu'ils avaient déjà : le rendu ne bouge
100
+ pas, seul le rôle devient explicite. Un thème maison doit le déclarer.
101
+
102
+ - **Les bordures de navi descendent d'un cran** (`paper-300` / `paper-200`).
103
+ Sur un fond clair la profondeur vient du **contour**, pas du remplissage : il
104
+ reste moins de 10% de luminance entre le blanc et une page claire, et trois
105
+ niveaux n'y tiennent pas. `surface-sunken` ne bouge pas.
106
+
107
+ - **BREAKING — `justify` sépare la répartition de la densité.** `size` se
108
+ documentait « pure padding/typography » et décidait en douce du modèle de
109
+ répartition : `compact` justifiait les onglets, `comfortable` les calait à
110
+ gauche. Une barre `compact` épouse maintenant son contenu ;
111
+ `justify="stretch"` restaure l'ancien comportement.
112
+
113
+ - **BREAKING — `FoldNavLayoutContext` gagne `barCollapsed`.** Le contexte ne
114
+ parlait que dans un sens : la barre savait tout du layout, le layout rien de
115
+ la barre. `collapsed` appartient à la barre et la largeur de piste au layout,
116
+ et le JSDoc disait d'aller poser `--fold-nav-layout-rail-width` à la main, à
117
+ chaque usage. Une implémentation maison du token doit exposer `barCollapsed`.
118
+
119
+ - **BREAKING — quatre rôles s'ajoutent au catalogue de couleurs** :
120
+ `on-info` · `on-warning` · `on-alert` · `on-success` (41 → 45). Un thème
121
+ maison doit les déclarer, sinon le test de parité échoue.
122
+
123
+ Ils ferment un **P0** indépendant de navi : `emphasis="solid"` +
124
+ `intent="warning"` peignait `on-primary` — du blanc — sur de l'ambre, à
125
+ **2,4:1 sur umbra et 3,3:1 sur les quatre autres**. Même défaut sur la bulle
126
+ compteur de `fold-nav-tile`. La combinaison est exprimable depuis que
127
+ emphasis et intent sont deux axes indépendants ; rien ne la testait, parce
128
+ que le catalogue n'avait pas d'encre pour un remplissage de statut.
129
+
130
+ - **BREAKING — les primitives `--fold-ref-navy-*` (9) et `--fold-ref-ivory-50`
131
+ sont supprimées**, remplacées par les familles `graphite` / `paper` /
132
+ `signal` / `navyink`. Une app qui référençait une primitive navi directement
133
+ doit migrer.
134
+
135
+ - **BREAKING — `--fold-shadow-*` peut être redéclarée par un thème.**
136
+ L'invariant disait « seul le rayon varie » et rangeait l'ombre avec les
137
+ mesures. La vraie ligne : un thème peut changer ce à quoi une surface
138
+ **ressemble**, jamais où elle **se trouve**. Une ombre ne déplace pas une
139
+ boîte. Un thème maison qui héritait des ombres de base les garde.
140
+
141
+ - **navi 2 — « Graphite & Signal ».** navi avait deux polarités et un seul jeu
142
+ de rôles : son sous-bloc chrome en déclarait **17 sur 45** et héritait les 28
143
+ autres de la page. Le givre blanc de la page posé sur le rail sombre rendait
144
+ une infobulle à **1,9:1**. Huit échecs WCAG mesurés sont fermés ; `card`,
145
+ `sunken` et `page` valaient blanc, blanc et ivoire — une table imbriquée dans
146
+ une carte n'avait aucun bord — et font maintenant trois marches distinctes.
147
+ Le rayon passe de 1/2/3/4px à 2/4/6/8px : à 1px un coin est un artefact de
148
+ rendu, pas une intention.
149
+
150
+ - **BREAKING — `--fold-text-md` vaut 13px ; le corps de texte s'appelle
151
+ `--fold-text-base`.** Un consommateur non migré rétrécit d'un cran **en
152
+ silence** : c'est un rechercher/remplacer, `--fold-text-md` →
153
+ `--fold-text-base`, à faire avant de monter de version.
154
+
155
+ - **Le bouton `lg` cesse d'être un `md` déguisé.** Les deux préréglages
156
+ nommaient littéralement la même taille — l'échelle n'avait aucune marche
157
+ entre le corps et 20px à saisir. `lg` rend maintenant en `--fold-text-lg`.
158
+
159
+ ### Added
160
+
161
+ - **Un système typographique, au contrat.** La typo était la moitié manquante
162
+ du design system : cinq tailles, et graisse, interligne, interlettrage et
163
+ famille écrits en dur composant par composant — 74, 25, 22 déclarations,
164
+ pour 4, 9 et 11 valeurs distinctes. Personne n'avait décidé qu'il y aurait
165
+ onze interlettrages ; ils se sont accumulés.
166
+
167
+ Quatre échelles nouvelles, toutes dans le test de contrat au même titre que
168
+ la couleur, donc une marche ne peut plus naître ou disparaître sans que le
169
+ catalogue le dise :
170
+
171
+ - `--fold-font-sans` · `--fold-font-mono` — deux fontes, pas de `display` :
172
+ rien n'en rend une. Elles restent **hors de l'axe de thème** : changer de
173
+ fonte re-flow une page, et seul le radius peut varier par thème.
174
+ - `--fold-text-2xs … 2xl` — l'échelle s'étend vers le bas et vers
175
+ l'intérieur (10 · 11 · 12 · 13 · 14 · 16 · 20 · 24), là où le code avait
176
+ des littéraux. Elle s'arrête à 24px parce que rien, ni dans la librairie
177
+ ni dans la galerie, ne rend plus gros.
178
+ - `--fold-weight-regular|medium|semibold|bold|extrabold`
179
+ - `--fold-leading-none|tight|snug|normal|relaxed`
180
+ - `--fold-tracking-tightest|tighter|tight|normal|wide|caps`
181
+
182
+ `extrabold` et `tightest` viennent de la galerie : c'est elle, pas la
183
+ librairie, qui rend des titres hero — 800 sur treize sites, -0.04em sur
184
+ cinq, toujours ensemble avec une taille fluide. Un registre d'affichage
185
+ cohérent que personne n'avait nommé.
186
+
187
+ - **Un rôle sémantique que rien ne peint est un rôle mort.** Le contrat
188
+ vérifiait l'orphelinat dans un seul sens — une primitive que rien ne pointe
189
+ échoue depuis longtemps, mais un rôle que les cinq thèmes déclarent et
190
+ qu'aucun composant ne peint ne regardait personne. Sur 45 rôles, il y en
191
+ avait exactement un.
192
+
193
+ - **`depth-contrast.spec.ts`** — 4,5:1 entre deux **surfaces** ne veut rien
194
+ dire : personne ne lit un contour. Deux planchers, par le travail que fait la
195
+ séparation — **structure** (contour de carte, cadre de panneau) ≥ 1,25:1 et
196
+ **subdivision** (bande, séparateur) ≥ 1,15:1. `lumen` est exempté avec sa
197
+ raison, et sa valeur épinglée plutôt que passée sous silence.
198
+
199
+ - **`chrome-contrast.spec.ts`** — douze paires de contraste, chacune un échec
200
+ mesuré avant la refonte, plus deux invariants structurels. Les alphas sont
201
+ compositées sur le fond qu'elles rencontrent vraiment : mesurer un
202
+ `color-mix(…, transparent)` contre rien, c'est comment une surface
203
+ translucide passe un test qu'elle devrait échouer. Rejoué sur l'ancien
204
+ navi : 10 échecs sur 14.
205
+
206
+ - **`status-ink-contrast.spec.ts`** — chaque encre de statut sur son propre
207
+ fond, à 4,5:1, sur les cinq thèmes. Mesurer plutôt que décréter a payé :
208
+ `bubbly` type `info` en azure et non en violet, donc le blanc y tombe à
209
+ 2,8:1 et il prend l'encre sombre là où les quatre autres prennent le blanc.
210
+
211
+ - **Un sous-bloc de thème scopé doit être CLOS.** Le test de parité
212
+ dédupliquait les sélecteurs `[data-theme]` et ne gardait que le bloc de
213
+ tête — le sous-bloc chrome de navi n'était vérifié par rien, et son
214
+ commentaire l'assumait. Une famille se redéclare désormais entière : prendre
215
+ `primary` sans `on-primary`, c'est poser sur le nouveau remplissage l'encre
216
+ prévue pour l'autre polarité.
217
+
218
+ - **`pnpm run lint:typography`** — jumeau de `lint:spacing`, branché en
219
+ pre-push et dans les deux workflows. Aucun `font-size`, `font-weight`,
220
+ `line-height` ou `letter-spacing` littéral sous `src/components`. Porte dure
221
+ d'emblée : le solde est à zéro, **galerie comprise** — elle en portait 276
222
+ à elle seule, plus que la librairie entière — et le garde-fou lit aussi les
223
+ styles inline des templates.
224
+
225
+ Trois exceptions, chacune pour une raison : les huit tailles qui restent des
226
+ littéraux sont de la **géométrie de composant**, pas du texte (les initiales
227
+ d'un avatar suivent son diamètre) ; `clamp()` est du **texte fluide**, une
228
+ expression qui balaie l'échelle plutôt qu'une marche dessus ; et un
229
+ commentaire n'est pas du code.
230
+
231
+ Les indirections Sass sont **suivies, pas crues** : `_field-box.scss` route
232
+ ses tailles par une map, donc `font-size: map.get($s, font)` ne porte aucun
233
+ littéral — il vit à l'entrée de map, qu'aucun motif `font-size:` n'aurait
234
+ jamais vue. Chaque `map.get(…, clé)` et chaque `$variable` est résolu contre
235
+ les liaisons du fichier et vérifié là-bas, et une indirection qui ne résout
236
+ nulle part est elle-même un signalement : une valeur qu'on ne peut pas
237
+ suivre, c'est exactement le trou.
238
+
239
+ ### Fixed
240
+
241
+ - **Un clic hors tuile ferme le `fold-nav-launcher`.** Le scrim portait bien
242
+ `(click)="close()"` et ne pouvait jamais se déclencher : `.nl-dialog` est
243
+ `position: fixed; inset: 0` par-dessus lui. Le test qui le couvrait cliquait
244
+ le scrim directement et passait au vert depuis toujours — jsdom n'a pas de
245
+ mise en page, il n'a jamais vu le recouvrement.
246
+
247
+ - **Le mode cartes de `fold-data-table` rend une vraie liste.** `auto-cards`
248
+ était une réécriture CSS du tableau (`display: block` sur le `<tr>`, `flex`
249
+ sur les cellules) — ce qui **retire le rôle implicite** des éléments de
250
+ tableau : la table cessait d'être une table sans devenir une liste, et le
251
+ `<thead>` restait en en-têtes orphelins. Une `<ul>/<li>` maintenant, avec une
252
+ coquille commune (contour, ton, sélection) et un gabarit par défaut en `<dl>`.
253
+
254
+ - **Le zébrage se voit sur navi** — 1,07 → 1,15:1. Et les diviseurs de ligne
255
+ disparaissent dès que le zébrage est actif : la rayure porte déjà la
256
+ séparation.
257
+
258
+ - **Une barre d'onglets repliée défile, et son libellé actif n'est plus rogné.**
259
+ Le repli et le défilement s'excluaient : passé une douzaine d'items, le
260
+ premier écrasé était l'item actif, le seul qui garde son libellé. L'infobulle
261
+ d'un item icône passe dans le **top layer** (`popover`), donc elle échappe au
262
+ débordement du scroller sans qu'il faille lui réserver de la place.
263
+
264
+ - **La typographie de la barre répond à `size` seul.** L'orientation pilotait
265
+ taille ET graisse, donc franchir `foldAt` avec `direction="auto"` recomposait
266
+ les libellés au lieu de déplacer la barre.
267
+
268
+ - **`background="surface"` + `activeStyle="fill"` : la pilule active n'est plus
269
+ rognée** contre l'arête basse du bandeau.
270
+
271
+ - **Le gap d'un `fold-nav-layout` suit sa propre largeur, pas la fenêtre.** Il
272
+ tenait sur `@media (max-width: 640px)` pendant que le pli tenait sur la
273
+ largeur du conteneur : un layout étroit sur grand écran gardait 16px alors
274
+ qu'il était replié.
275
+
276
+ - **Le header et les rails du `fold-app-shell` peignent enfin leur fond.**
277
+ `--fold-color-bg-header` était déclaré par les cinq thèmes et consommé par
278
+ **zéro composant** : le header rendait transparent sur le `:host` du shell,
279
+ qui peint `bg-page` — résolu **hors** de la région chrome. Sous navi, du
280
+ texte clair de chrome sur un fond paper ; idem pour toute bande de rail que
281
+ le `fold-menu` ne recouvre pas. Antérieur à navi 2 — invisible tant que la
282
+ page et le chrome partageaient la même polarité.
283
+
284
+ - **`--fold-font-mono` existe.** Neuf déclarations de la galerie le lisaient,
285
+ aucune ne le déclarait : il ne rendait que par son fallback. Quatorze autres
286
+ recopiaient la pile à la main.
287
+
11
288
  ## [0.12.1] - 2026-08-22
12
289
 
13
290
  ### Fixed
@@ -1635,7 +1912,9 @@ design-token stylesheet.
1635
1912
  `currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
1636
1913
  strings localise via inputs / providers (`provideFoldPanelLabels`).
1637
1914
 
1638
- [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.12.1...HEAD
1915
+ [unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.14.0...HEAD
1916
+ [0.14.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.14.0
1917
+ [0.13.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.13.0
1639
1918
  [0.12.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.12.1
1640
1919
  [0.12.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.12.0
1641
1920
  [0.11.1]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.11.1
package/README.md CHANGED
@@ -99,11 +99,14 @@ is the umbra or lumen block with its primitive families re-pointed. Adding a
99
99
  sixth is a new `[data-theme]` block in `semantic.css` plus the primitives it
100
100
  names.
101
101
 
102
- `bubbly` and `navi` also change their corners **radius is the one scale a theme may
103
- re-declare**, because corner softness is a brand axis (friendly vs
104
- institutional) and it is the only scale that changes nothing about where a box
105
- sits. Type, space, motion and elevation stay theme-invariant: retheming must
106
- never re-flow a page. The contract test enforces that split.
102
+ `bubbly` and `navi` also change their corners, and `navi` its depth **radius
103
+ and elevation are the two scales a theme may re-declare**. The line is not that
104
+ one scale is special: a theme may change what a surface **looks like**, never
105
+ where it **sits**. Corner softness is a brand axis (friendly vs institutional);
106
+ depth is calibrated against a ground, and a shadow tuned for near-black ink is
107
+ a grey smear on a light page. Neither moves a box by one pixel. Type, space and
108
+ motion stay theme-invariant — those re-flow or re-time a page. The contract
109
+ test enforces that split.
107
110
 
108
111
  `navi` is the interesting one: mixing chrome and page means one theme needs the
109
112
  `text`/`border`/`surface` roles to differ **per region**, which a single set of