fold-ng 0.16.0 → 0.17.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 +219 -1
- package/README.md +1 -0
- package/fesm2022/fold-ng.mjs +334 -25
- package/fesm2022/fold-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/tmp-esm2022/tsconfig.lib.tsbuildinfo +1 -1
- package/tokens/scales.css +14 -0
- package/types/fold-ng.d.ts +346 -12
- package/types/fold-ng.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,223 @@ All notable changes to **fold-ng** are documented here. The format follows
|
|
|
8
8
|
|
|
9
9
|
_Nothing yet._
|
|
10
10
|
|
|
11
|
+
## [0.17.0] - 2026-08-26
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`fold-fieldset`** — le groupe NOMMÉ de contrôles : la paire
|
|
16
|
+
`<fieldset>`/`<legend>`, son habillage navigateur neutralisé et son rythme sur
|
|
17
|
+
l'échelle des tokens.
|
|
18
|
+
|
|
19
|
+
Il existe parce que la paire native est inutilisable telle quelle : chaque
|
|
20
|
+
appelant réécrit les six mêmes lignes pour la défaire (`margin: 0; padding: 0;
|
|
21
|
+
border: 0`, puis flex + gap, puis `legend { padding: 0 }`). Ce reset avait été
|
|
22
|
+
écrit **cinq fois, au caractère près**, dans deux applications. Une répétition
|
|
23
|
+
aussi exacte n'est pas une coïncidence, c'est un composant manquant.
|
|
24
|
+
|
|
25
|
+
Ce n'est ni une carte ni une section : `fold-card` est une surface,
|
|
26
|
+
`fold-page-section` un chapitre de page ; ceci est le **groupement
|
|
27
|
+
accessible** des contrôles qui répondent à une seule question. C'est le
|
|
28
|
+
`<legend>` qui nomme le groupe à un lecteur d'écran — et c'est toute la raison
|
|
29
|
+
de prendre un `fieldset` plutôt qu'un `div` : un titre au-dessus d'un `div`
|
|
30
|
+
aurait exactement la même allure et n'annoncerait rien.
|
|
31
|
+
|
|
32
|
+
**Ce que l'élément natif achète, et que le composant garde** :
|
|
33
|
+
|
|
34
|
+
- `disabled` — le **super-pouvoir** du `fieldset`, que rien d'autre en HTML
|
|
35
|
+
n'a : il désactive TOUS les contrôles qu'il contient, en un attribut, sans
|
|
36
|
+
câblage par contrôle. (La première `<legend>` en est exemptée par la spec.)
|
|
37
|
+
- `hint` — l'instruction du groupe (« au moins un jour »), pointée par
|
|
38
|
+
`aria-describedby`. Un indice seulement peint est un indice que la moitié
|
|
39
|
+
des lecteurs n'aura jamais. `hintPosition` la place `under` (défaut, sa
|
|
40
|
+
propre ligne : de la place pour une phrase) ou `inline` (sur la ligne de la
|
|
41
|
+
légende, pour l'incise courte qui qualifie le nom au lieu d'instruire).
|
|
42
|
+
L'annonce ne dépend pas de la position — seul l'œil change de chemin. Sans
|
|
43
|
+
légende, `inline` retombe sur `under` : il n'y a pas de ligne où se poser.
|
|
44
|
+
- `ariaLabel` — nomme un groupe qui ne doit pas afficher de légende visible.
|
|
45
|
+
Ignoré si `legend` est renseigné : deux noms pour un groupe, c'est ainsi
|
|
46
|
+
qu'ils divergent.
|
|
47
|
+
- `legend` vide **et** `ariaLabel` vide = groupe volontairement sans nom, et
|
|
48
|
+
sans `aria-label` vide non plus — un groupe qui réclame un nom et n'en donne
|
|
49
|
+
aucun est pire qu'un groupe muet. C'est le cas imbriqué, déjà nommé par son
|
|
50
|
+
parent.
|
|
51
|
+
|
|
52
|
+
`legendVariant` porte les **deux registres** que les formulaires réels
|
|
53
|
+
emploient — les mêmes mots que le `titleVariant` d'un `fold-page-section`, et
|
|
54
|
+
délibérément : on rencontre UN vocabulaire pour « petit et au-dessus » contre
|
|
55
|
+
« se lit comme de la prose », pas un par composant.
|
|
56
|
+
|
|
57
|
+
- `eyebrow` (défaut) — petit, capitales, tracé, atténué : le groupe est une
|
|
58
|
+
**partie d'un formulaire**, son nom se pose au-dessus sans disputer
|
|
59
|
+
l'attention aux contrôles.
|
|
60
|
+
- `heading` — au poids du libellé d'un champ : le groupe **est** une seule
|
|
61
|
+
chose aux yeux du formulaire (« Heures de retrait »), son nom se lit donc
|
|
62
|
+
comme les libellés qui l'entourent.
|
|
63
|
+
|
|
64
|
+
Ce n'est pas un réglage cosmétique : un eyebrow au-dessus d'un groupe qui est
|
|
65
|
+
en réalité UN champ fait paraître au formulaire plus de sections qu'il n'a de
|
|
66
|
+
questions.
|
|
67
|
+
|
|
68
|
+
`optional` marque le groupe ENTIER — même parenthèse, même mot et même
|
|
69
|
+
fournisseur (`FOLD_COMMON_LABELS`) que le marqueur d'un libellé de champ. Il
|
|
70
|
+
existe parce qu'un groupe peut être facultatif quand aucun de ses membres ne
|
|
71
|
+
l'est : un point GPS, ce sont deux champs qu'on donne ensemble ou pas du
|
|
72
|
+
tout, et écrire « optionnel » sur chacun dirait autre chose — que l'un
|
|
73
|
+
pourrait manquer sans l'autre.
|
|
74
|
+
|
|
75
|
+
Plus `direction` (`vertical` par défaut, `horizontal`) et `appearance`
|
|
76
|
+
(`plain` par défaut, `border` pour le groupe encadré qui doit se distinguer de
|
|
77
|
+
ses pairs d'un coup d'œil). `--fold-fieldset-gap` thème l'écart entre membres. Il vaut par défaut le
|
|
78
|
+
**rythme d'un formulaire** (`--fold-space-md`) : un champ, c'est un libellé,
|
|
79
|
+
une boîte et parfois un indice — il lui faut l'air que trois cases à cocher
|
|
80
|
+
n'exigent pas. Un groupe qu'on n'a pas réglé empile des champs ; les groupes
|
|
81
|
+
compacts (cases, lignes horaires, boutons radio) resserrent.
|
|
82
|
+
|
|
83
|
+
⚠️ Une finesse que les cinq versions manuscrites avaient toutes redécouverte à
|
|
84
|
+
leurs dépens : **la légende n'est pas un élément flex**. La boîte flex d'un
|
|
85
|
+
`fieldset` est sa boîte de contenu anonyme, et la légende rendue vit en
|
|
86
|
+
dehors — le `gap` ne l'atteint donc jamais, et son espace en dessous doit être
|
|
87
|
+
sa propre marge.
|
|
88
|
+
|
|
89
|
+
⚠️ Deuxième finesse, trouvée en écrivant le test : **`input.disabled` ne dit
|
|
90
|
+
pas la vérité** dans un `fieldset` désactivé. La propriété IDL ne reflète que
|
|
91
|
+
l'attribut PROPRE du contrôle et reste `false` ; seule la pseudo-classe
|
|
92
|
+
`:disabled` connaît l'ancêtre. Un test écrit sur la propriété serait passé au
|
|
93
|
+
vert sur un composant qui ne désactivait plus rien.
|
|
94
|
+
|
|
95
|
+
- **`--fold-font-label`** — la face du registre **micro-libellé** : les libellés
|
|
96
|
+
2xs / gras / capitales / tracés qui titrent une section, coiffent une colonne
|
|
97
|
+
de tableau ou servent d'eyebrow. Elle vaut `inherit` par défaut, donc rien ne
|
|
98
|
+
change tant qu'un hôte ne la nomme pas — la règle « un composant porte la face
|
|
99
|
+
de son hôte » tient toujours. Elle existe parce que c'est précisément le rôle
|
|
100
|
+
où un hôte veut souvent une AUTRE face que son texte courant (un libellé
|
|
101
|
+
monospace se lit comme une parole du système, pas comme de la prose), et que
|
|
102
|
+
le dire sans elle obligeait à entrer dans les entrailles de trois composants —
|
|
103
|
+
avec la dérive garantie le jour où un quatrième rejoint le registre.
|
|
104
|
+
|
|
105
|
+
- **`fold-page-section` gagne `collapsible` + `[(open)]`** — replier le CORPS
|
|
106
|
+
d'une section, et rien d'autre. Le titre, son sous-titre, sa description et
|
|
107
|
+
ses `[sectionActions]` restent en place, et c'est toute la différence entre
|
|
108
|
+
replier et **cacher** : un onglet cachait l'ÉTAT avec les champs, donc on ne
|
|
109
|
+
pouvait pas savoir ce qui manquait sans tout ouvrir. Replié, la section dit
|
|
110
|
+
encore ce qu'elle est et ce qu'elle fait — et ses actions restent cliquables,
|
|
111
|
+
donc elle s'enregistre sans se déplier.
|
|
112
|
+
|
|
113
|
+
Deux conséquences de cette règle, toutes deux voulues : le bouton est le
|
|
114
|
+
TITRE et non l'en-tête (un bouton autour de l'en-tête aurait imbriqué
|
|
115
|
+
« Enregistrer » dans un `<button>` — inerte, et invalide), et l'état par
|
|
116
|
+
défaut est **ouvert**, parce qu'une section qui démarre repliée est une
|
|
117
|
+
section qu'il faut découvrir.
|
|
118
|
+
|
|
119
|
+
- **`fold-page-section` gagne `[sectionSubtitle]`, `titleVariant` et
|
|
120
|
+
`separator`** — les pendants exacts de ce que `fold-page-layout` a reçu, pour
|
|
121
|
+
la même raison : une section d'écran dense est une petite page.
|
|
122
|
+
|
|
123
|
+
- **`fold-page-layout` sépare la PORTÉE de l'en-tête de son SOL** :
|
|
124
|
+
`headerBleed` (aller bord à bord) et `headerBand` (peindre la bande) sont deux
|
|
125
|
+
entrées, plus une seule. `headerBand` faisait les deux, donc un simple filet
|
|
126
|
+
pleine largeur sous l'en-tête — traitement courant et discret — obligeait à
|
|
127
|
+
peindre une bande. On ne pouvait pas demander la portée sans l'encre.
|
|
128
|
+
|
|
129
|
+
- **Les bandes montent d'un cran : `fold-page-layout[headerBand]` et
|
|
130
|
+
`fold-aside-layout[band]`.** Une en-tête de page et un rail collant peuvent
|
|
131
|
+
enfin se poser sur `--fold-color-surface-band` — le rôle qui existait déjà et
|
|
132
|
+
que `fold-card` consomme par `raisedBands` : « un pas À L'ÉCART de son
|
|
133
|
+
conteneur, dans le sens que la polarité du thème impose ». Clair il fonce,
|
|
134
|
+
sombre il éclaircit ; le composant nomme le rôle et n'a jamais à savoir dans
|
|
135
|
+
quel sens.
|
|
136
|
+
|
|
137
|
+
**Ce n'est pas une `surface`, et c'est le point.** L'axe `surface` est une
|
|
138
|
+
IDENTITÉ (`chrome`, `accent`) et il repointe l'encre sans jamais peindre de
|
|
139
|
+
fond (`docs/surfaces.md`) ; « l'en-tête est du mobilier, pas du contenu » n'est
|
|
140
|
+
ni l'un ni l'autre — c'est une élévation. Un `surface="raised"` aurait mélangé
|
|
141
|
+
les deux axes et aurait menti sur ce qu'il fait.
|
|
142
|
+
|
|
143
|
+
Deux détails qui ne sont pas de la décoration : la bande d'en-tête **annule
|
|
144
|
+
exactement** ce que la page marge (les deux mêmes tokens, moitié à l'étroit),
|
|
145
|
+
puis rembourse ce padding à l'intérieur — la colonne de texte ne bouge pas
|
|
146
|
+
quand on l'allume. Et un rail bandé **ferme la gouttière de colonne** au profit
|
|
147
|
+
d'un filet : un fond tenu à 28px du contenu qu'il accompagne ne se lit pas
|
|
148
|
+
comme une bande mais comme une carte flottante. L'espace passe à l'intérieur,
|
|
149
|
+
donc le contenu se lit à la même largeur dans les deux cas.
|
|
150
|
+
|
|
151
|
+
- **`fold-aside-layout[bleed]`** — sortir de la gouttière de page et atteindre
|
|
152
|
+
le bord, même mécanisme et même variable que `fold-page-section[bleed]`. C'est
|
|
153
|
+
le compagnon de `band` : un rail bandé tenu à distance du bord par une
|
|
154
|
+
gouttière est exactement la carte flottante que la bande sert à remplacer.
|
|
155
|
+
|
|
156
|
+
- **`fold-view-toggle` : un segment peut porter un point d'état** (`dot` +
|
|
157
|
+
`dotLabel` sur une option). Un point dit « regarde ici », jamais _quoi_ — il
|
|
158
|
+
est donc `aria-hidden`, et son sens rejoint le **nom accessible** du segment
|
|
159
|
+
au lieu de disparaître. Sans ça, un lecteur d'écran entendait « EN » là où un
|
|
160
|
+
œil voyait « EN, il manque quelque chose ».
|
|
161
|
+
|
|
162
|
+
### Changed
|
|
163
|
+
|
|
164
|
+
- **BREAKING (visuel) — le segment choisi d'un `fold-view-toggle` est PLEIN**
|
|
165
|
+
(`activeStyle` passe de `raised` à `solid`, et gagne au passage la valeur
|
|
166
|
+
`accent`). Même raison que le bouton solide : un contrôle segmenté existe pour
|
|
167
|
+
montrer **lequel est choisi**, et le choisi doit être la chose la plus forte
|
|
168
|
+
du contrôle.
|
|
169
|
+
|
|
170
|
+
Trois registres, parce qu'aucun ne convient partout : `solid` (rempli),
|
|
171
|
+
`accent` (teinté — présent, plus discret, et il garde l'encre du segment
|
|
172
|
+
lisible au milieu de beaucoup de couleur), `raised` (puce neutre). La puce
|
|
173
|
+
confiait la distinction à une élévation qui disparaît entièrement sous
|
|
174
|
+
`forced-colors`. Rien ne verrouillait ce défaut non plus.
|
|
175
|
+
|
|
176
|
+
Un détail que le plein impose : sur un fond d'accent, un point d'état peint
|
|
177
|
+
dans sa propre teinte peut tomber à un cheveu du fond — un point ambre sur un
|
|
178
|
+
accent chaud s'efface. Sur `solid`, le point prend donc l'encre du segment,
|
|
179
|
+
la seule garantie d'y être lisible.
|
|
180
|
+
|
|
181
|
+
- **BREAKING (visuel) — un `foldButton` est SOLIDE par défaut** (`emphasis`
|
|
182
|
+
passe de `soft` à `solid` ; `intent` reste `primary`). Le défaut, c'est ce
|
|
183
|
+
qu'une app écrit quand elle n'écrit rien — et ce qu'elle écrit le plus, c'est
|
|
184
|
+
**l'action principale** de l'écran, celle qu'elle veut qu'on presse. Avec un
|
|
185
|
+
défaut teinté, le bouton le plus fort d'une page était celui que quelqu'un
|
|
186
|
+
avait pensé à baliser : l'emphase suivait l'effort de rédaction plutôt que
|
|
187
|
+
l'importance. `soft` et `outline` se demandent maintenant exprès, pour les
|
|
188
|
+
actions qui accompagnent celle-là.
|
|
189
|
+
|
|
190
|
+
Rien ne verrouillait ce défaut : les vingt tests du bouton passaient à
|
|
191
|
+
l'identique avant et après le basculement. Un test le tient désormais.
|
|
192
|
+
|
|
193
|
+
- **BREAKING (visuel) — un titre de `fold-page-section` porte le registre
|
|
194
|
+
MICRO-LIBELLÉ par défaut** : 2xs, gras, capitales, tracé, dans
|
|
195
|
+
`--fold-font-label`. Un titre de section est un **libellé du bloc qui le
|
|
196
|
+
suit** ; à l'échelle d'une page, une pile de titres en taille de corps entre en
|
|
197
|
+
concurrence avec le contenu même qu'elle est censée étiqueter. C'est le
|
|
198
|
+
registre que portent déjà une en-tête de colonne de tableau et un eyebrow de
|
|
199
|
+
`fold-element-title`, donc les trois s'accordent.
|
|
200
|
+
|
|
201
|
+
C'est une **peau**, jamais une sémantique : le titre reste le même `h2`, avec
|
|
202
|
+
le même `aria-level` et le même nom de région dans les deux registres — un
|
|
203
|
+
test le vérifie explicitement, parce qu'un titre qui cesserait d'être un titre
|
|
204
|
+
pour ressembler à un libellé coûterait son plan à la page sans que rien ne le
|
|
205
|
+
dise.
|
|
206
|
+
|
|
207
|
+
Pour retrouver l'ancien rendu : `titleVariant="heading"`.
|
|
208
|
+
|
|
209
|
+
### Fixed
|
|
210
|
+
|
|
211
|
+
- **Un `bleed` annule désormais la gouttière que la page PAYE, pas celle qu'on
|
|
212
|
+
lui a demandée.** Sous 640px, `fold-page-layout` réduit son inset de moitié —
|
|
213
|
+
et `fold-page-section[bleed]`, épinglé au token écrit, continuait d'annuler la
|
|
214
|
+
valeur entière : la section débordait d'une demi-gouttière de chaque côté,
|
|
215
|
+
précisément là où il y avait le moins de place. La page publie maintenant
|
|
216
|
+
`--fold-page-gutter-effective` et tout ce qui annule lit celle-là. Le bug était
|
|
217
|
+
invisible partout : aucune requête média ne s'évalue en test unitaire, et le
|
|
218
|
+
symptôme est un débordement horizontal. Un test de contrat sur les sources
|
|
219
|
+
tient l'invariant.
|
|
220
|
+
|
|
221
|
+
- **`fold-page-layout` : les actions s'alignent sur la rangée du TITRE.** Elles
|
|
222
|
+
se calaient en haut de la colonne de texte ; depuis que `[pageEyebrow]`
|
|
223
|
+
existe, cette colonne commence par le fil d'Ariane — et les actions
|
|
224
|
+
remontaient se coller à lui. L'eyebrow sort donc de la colonne et coiffe
|
|
225
|
+
l'en-tête entier : c'est sa place logique (il désigne la page, pas le titre) et
|
|
226
|
+
la rangée titre + actions redevient une vraie rangée.
|
|
227
|
+
|
|
11
228
|
## [0.16.0] - 2026-08-24
|
|
12
229
|
|
|
13
230
|
### Added
|
|
@@ -1955,7 +2172,8 @@ design-token stylesheet.
|
|
|
1955
2172
|
`currentColor`; `prefers-reduced-motion` + `forced-colors` are respected;
|
|
1956
2173
|
strings localise via inputs / providers (`provideFoldPanelLabels`).
|
|
1957
2174
|
|
|
1958
|
-
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.
|
|
2175
|
+
[unreleased]: https://github.com/hugoheynard/fold-ng/compare/v0.17.0...HEAD
|
|
2176
|
+
[0.17.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.17.0
|
|
1959
2177
|
[0.16.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.16.0
|
|
1960
2178
|
[0.15.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.15.0
|
|
1961
2179
|
[0.14.0]: https://github.com/hugoheynard/fold-ng/releases/tag/v0.14.0
|
package/README.md
CHANGED
|
@@ -350,6 +350,7 @@ ships it.
|
|
|
350
350
|
| `FoldDateComponent` | `fold-date` | Native calendar-date wrapper (`type` = date · datetime-local · month · week) — keeps the OS picker, hands back a typed `[(value)]` string (`YYYY-MM-DD`). `min`/`max`/`step` pass through. Time-of-day → `fold-time`. |
|
|
351
351
|
| `FoldTimeComponent` | `fold-time` | Native time-of-day wrapper (`<input type="time">`) — typed `[(value)]` string (`HH:mm`), `min`/`max`/`step`. The sibling of `fold-date`. |
|
|
352
352
|
| `FoldCheckboxComponent` | `fold-checkbox` | Boolean control — a native `<input type="checkbox">` (keyboard, `indeterminate`, forms) restyled to tokens. Signal Forms (`[formField]`, a `FormCheckboxControl`) or standalone `[(checked)]`; `indeterminate`, `label`/`ariaLabel`, `hint`/`errors`, `size`. |
|
|
353
|
+
| `FoldFieldsetComponent` | `fold-fieldset` | Named group of controls — a real `<fieldset>`/`<legend>` with the browser's margin/padding/3D border undone and the rhythm on tokens. `disabled` disables every control inside (the element's unique power); `hint` wired through `aria-describedby` (`hintPosition` `under`/`inline`); `legend` (empty renders none) or `ariaLabel`; `optional` marks the whole group (not each member); `legendVariant` (`eyebrow`/`heading` — the same two registers as a page-section's title), `direction` (`vertical`/`horizontal`), `appearance` (`plain`/`border`); `--fold-fieldset-gap`. |
|
|
353
354
|
| `FoldPasswordFieldComponent` | `fold-password-field` | Password input + a live requirements checklist (a dot/tick per rule). Rules injected via `FoldPasswordRule` (`{ label, test }` — regex/zod/anything); `revealable` eye (a `fold-input` capability); `marker` dot/check; `[rules]` slot to redesign the list; `validChange`; Signal Forms. |
|
|
354
355
|
| `FoldViewToggleComponent` | `fold-view-toggle` | Segmented single-select (Cards/Table, density, chart-mode…). Generic `options` (`{ value, icon?, label?, ariaLabel?, disabled? }`) + `[(value)]`; a real `role="radiogroup"` — roving tabindex, arrow keys, Home/End, disabled-skip; `size`, `iconOnly`, `activeStyle` (raised / accent). |
|
|
355
356
|
| `FoldSearchComponent` | `fold-search` | Debounced search box — an `fold-input` that emits `searchChange` once typing settles (`delayMs`), trimmed + de-duplicated. |
|