origam 2.18.3 → 2.18.4

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
@@ -18,6 +18,71 @@ This project follows [Semantic Versioning](https://semver.org).
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ### Fixed — #818 baseline shrink : `aria-allowed-attr`/`aria-prohibited-attr`/`aria-valid-attr-value` (12 des 25 entrées)
22
+
23
+ Recompté sur `a11y-violations.baseline.json` (pas repris d'un chiffre cité
24
+ ailleurs) : la baseline #818 portait **25 clés / 33 violations** sur 24
25
+ composants, dont **12 `aria-allowed-attr`** concentrées sur la famille
26
+ `OrigamDataTable*`. Cause commune, pas 12 défauts distincts :
27
+ `OrigamTextField` ne posait pas `inheritAttrs: false` alors qu'il
28
+ redistribue déjà explicitement chaque attr de fall-through
29
+ (`filterInputAttrs` → `rootAttrs` sur `<origam-input>`, `inputAttrs` sur le
30
+ vrai `<input>`). Sans ce flag, Vue appliquait EN PLUS le `$attrs` brut sur
31
+ la racine `<origam-input>`, qui ne s'y soustrayait pas non plus — doublant
32
+ `aria-haspopup`/`aria-expanded`/`aria-controls`/`aria-valuenow`/… sur le
33
+ `<div>` wrapper, qui ne porte aucun rôle les autorisant. `role` (prop
34
+ déclarée sur `OrigamTextField`) n'atteignait par ailleurs que
35
+ `<origam-field>`, jamais le vrai `<input>` — c'est ce défaut exact que le
36
+ commentaire `KNOWN_FAILURES` documentait déjà pour `OrigamSelect` (test
37
+ `fixme`).
38
+
39
+ **Correctif à la source** (`packages/ds/src/components/TextField/OrigamTextField.vue`) :
40
+ `defineOptions({ inheritAttrs: false })` + `role` désormais aussi lié sur le
41
+ `<input>` réel. Élimine, mesuré axe-core en navigateur réel (Playwright +
42
+ Histoire statique) :
43
+ - `OrigamSelect` — sort de `KNOWN_FAILURES` (`components.spec.ts`), test
44
+ `fixme` → vert.
45
+ - `OrigamDataTable`, `OrigamDataTableFooter`, `OrigamDataTableHeaderCell`,
46
+ `OrigamDataTableHeaders`, `OrigamDataTableHeadersCell`,
47
+ `OrigamDataTableRow` — `aria-allowed-attr` disparaît, entrée de baseline
48
+ retirée entièrement.
49
+ - `OrigamDataTableGroupHeaderRow`, `OrigamDataTableRows`, `OrigamNumberField`
50
+ — `aria-allowed-attr` disparaît, l'entrée reste pour leur AUTRE violation
51
+ (`button-name` / `color-contrast`), non touchée par ce lot.
52
+ - `OrigamDataTableHeadersCellMobile` — `aria-allowed-attr` ET
53
+ `aria-prohibited-attr` disparaissent (même fuite, deux règles axe
54
+ différentes), entrée retirée entièrement.
55
+
56
+ **Deuxième cause, distincte, deux composants** —
57
+ `packages/ds/src/components/ColorPickerField/OrigamColorPickerField.vue` et
58
+ `.../DatePickerField/OrigamDatePickerField.vue` posaient un
59
+ `aria-haspopup` avec une valeur inventée (`'colorpickerbox'` /
60
+ `'datepickerbox'`, jamais un token ARIA valide) sur l'élément
61
+ `.origam-field` (via `activator="parent"` d'`OrigamMenu`), sans `role` le
62
+ justifiant. Remplacé par `aria-haspopup: 'dialog'` (motif WAI-ARIA « Date
63
+ Picker Dialog ») + `role: 'combobox'` sur le même élément — même principe
64
+ que `comboboxAriaAttrs` d'`OrigamSelect`. `aria-allowed-attr` ET
65
+ `aria-valid-attr-value` disparaissent pour les deux ; `OrigamColorPickerField`
66
+ est intégralement retiré de la baseline, `OrigamDatePickerField` y reste
67
+ pour son `color-contrast` (non touché).
68
+
69
+ **Bilan mesuré** : baseline #818 25 clés/33 violations → **17 clés/18
70
+ violations**. Suite `pnpm -F @origam/tests test:a11y` complète (226 tests,
71
+ catalogue 218/218) : **226 passed**, aucune régression sur les 36
72
+ composants à baseline vide. Contrôle positif exécuté : réintroduire l'ancien
73
+ comportement fait échouer `a11y — OrigamSelect Default Variant` avec
74
+ `aria-allowed-attr`/`aria-prohibited-attr` NON baselinés — la porte capte
75
+ la régression, pas seulement l'édition du JSON.
76
+
77
+ **Laissé de côté, délibérément** (17 clés / 18 violations restent dans
78
+ `a11y-violations.baseline.json`, cf. #818) : `color-contrast` (8 instances —
79
+ plusieurs agents dédiés au contraste travaillent déjà sur ce périmètre),
80
+ `button-name` (3, boutons icône sans libellé — famille distincte),
81
+ `scrollable-region-focusable` (3), `aria-required-parent`/`aria-required-children`/
82
+ `listitem`/`aria-prohibited-attr` (`OrigamChartMap`, sans lien avec ce
83
+ correctif) — un défaut chacun, cause non partagée avec ce lot, non
84
+ instruit ici.
85
+
21
86
  Récolte de deux dépréciations posées "pour la prochaine majeure" avant que le
22
87
  `CLAUDE.md` n'acte que `3.0.0` est réservé à la séparation en modules et que
23
88
  les ruptures, elles, ne le sont pas — voir "Work priorities and versioning"
@@ -156,6 +221,105 @@ conclure qu'elle ne l'est plus par défaut.
156
221
 
157
222
  ### Fixed
158
223
 
224
+ - **#871 (clôture) — les 7 dernières violations AA étaient UN défaut de
225
+ palette répété, pas sept défauts de composant.** Mesuré avec
226
+ `packages/tests/audit/dark-contrast.audit.mjs` : **7 / 1 664 → 0 / 1 664**,
227
+ les 8 identités × 2 modes × 2 portées, quatre contrôles verts des deux
228
+ côtés. Exactement **7 lignes changent, aucune ne se dégrade**, et le ratio
229
+ minimum de toute la surface passe de 3.65 à **4.70**.
230
+
231
+ Les 7 se répartissaient en deux causes, toutes deux dans
232
+ `packages/marketing/src/themes` — **aucune dans le DS**, qui était déjà à
233
+ zéro :
234
+
235
+ | cause | marque · mode | couple | avant | après |
236
+ |---|---|---|---|---|
237
+ | encre héritée du mode clair | `apple` sombre | `#ffffff` → `#000000` sur `#0a84ff` | 3.65 | **5.76** |
238
+ | encre héritée du mode clair | `ecom` sombre | `#ffffff` → `#1a0f0a` sur `#f43f5e` | 3.67 | **5.12** |
239
+ | jeton de rôle inversé | `ecom` clair | `action.primary.bg` → `.fgSubtle` sur la crème | 4.43 | **5.93** |
240
+
241
+ **La cause commune.** Le mode sombre ÉCLAIRCIT l'accent de marque — c'est
242
+ la bonne pratique pour une grande surface (`#0071e3` → `#0a84ff` chez
243
+ `apple`, rose-600 → rose-500 chez `ecom`) — mais les deux palettes gardaient
244
+ l'encre `#ffffff` de leur jumeau clair. Or chacune applique **déjà** la règle
245
+ inverse à ses intentions `feedback` : les 4 d'`ecom` et 3 des 4 d'`apple`
246
+ encrent leur accent éclairci avec le `text.inverse` de la palette.
247
+ `action.primary` était la dernière exception. Le badge n'était pas en cause :
248
+ `--origam-badge__badge---{background-color,color}` lit exactement ce couple —
249
+ tout comme `--origam-btn--primary---*`, qui n'est pas dans la sonde et était
250
+ donc en défaut sans être mesuré.
251
+
252
+ ⚠️ **On n'a PAS foncé l'accent**, bien que ce soit l'autre levier : chez
253
+ `apple`, `action.primary.bg` sert aussi d'ENCRE (le bloc `components` passe
254
+ ce `var()` en `color` sur `origam-breadcrumb-item`). Le ramener à `#0071e3`
255
+ donnait 4.70 sous du blanc mais faisait tomber le fil d'Ariane à **4.47** sur
256
+ le fond noir — un défaut échangé contre un autre. Vérifié par calcul avant
257
+ d'écrire la moindre ligne.
258
+
259
+ La 3ᵉ cause est une erreur de RÔLE : `bg` est un jeton de surface, `fgSubtle`
260
+ la teinte prévue pour être LUE sur une surface neutre. `ecomDarkTheme` posait
261
+ déjà `fgSubtle` ; seul le bloc clair se trompait de jeton. Les deux modes sont
262
+ désormais symétriques.
263
+
264
+ - **#871 — une 8ᵉ violation, que la sonde ne pouvait pas voir** :
265
+ `material` sombre, `feedback.success`, `#1b5e20` sur `#81c784` = **3.91**
266
+ → `#0d3b10` = **6.32**. La sonde de `dark-contrast.audit.mjs` ne rend qu'UNE
267
+ intention par famille de composant, donc elle ne peint jamais
268
+ `feedback.success`. Trouvée par la spec statique ajoutée ci-dessous. C'est
269
+ l'encre qui était l'intruse, pas le conteneur : les trois autres intentions
270
+ sombres de `material` tiennent 5.79 / 7.70 / 7.71 avec des encres bespoke
271
+ très profondes, `success` était la seule à se contenter du green-900 de la
272
+ rampe.
273
+
274
+ - **#871 — l'audit lui-même ne démarrait plus.** Son `npx vite build` résolvait
275
+ vite par le PATH, via un lien `node_modules/.bin` qu'un peer auto-installé
276
+ ne garantit pas : sur un `pnpm install --frozen-lockfile` propre,
277
+ `packages/tests/node_modules/.bin/` ne contient que `playwright` et `vitest`,
278
+ et la commande mourait en `127 — sh: vite: command not found`. **L'audit ne
279
+ rendait plus aucun chiffre**, et rien ne le signalait puisque rien ne le
280
+ rejoue en CI. `resolveViteBin()` passe désormais par le realpath de
281
+ `@vitejs/plugin-vue`, ce qui ne dépend ni du PATH ni du hoisting et
282
+ sélectionne la copie de vite que le plugin utilise réellement (deux majeures
283
+ coexistent dans le magasin). Les commentaires qui affirmaient le contraire
284
+ dans les deux fichiers ont été corrigés.
285
+
286
+ - **#871 — l'état SURVOL était en défaut plus grave encore, et le correctif
287
+ d'encre l'a réglé sans qu'on y touche.** Le survol n'a pas d'encre à lui :
288
+ `--origam-btn--primary---background-color-hover` bascule la surface, mais il
289
+ n'existe **aucun** `--origam-btn--primary---color-hover` (ni de
290
+ `--origam-badge__badge---color-hover`) — l'encre reste `action.primary.fg`
291
+ dans les deux états. Sous l'encre blanche, `apple` sombre y tombait à
292
+ **2.19:1** et `ecom` sombre à **2.69:1** : *pires que n'importe laquelle des
293
+ 7 violations comptées*, et invisibles parce que la sonde de
294
+ `dark-contrast.audit.mjs` ne rend que l'état au repos.
295
+
296
+ **Aucun `bgHover` n'a été modifié.** La direction du survol n'était pas le
297
+ problème — l'encre l'était. Mesuré sur les deux marques :
298
+
299
+ | mode · encre | direction du survol | ratio repos → survol |
300
+ |---|---|---|
301
+ | clair · blanche | fonce | 4.70 → **6.95** / **6.29** ↑ |
302
+ | sombre · blanche *(avant)* | éclaircit | 3.65 → **2.19** / 3.67 → **2.69** ↓ |
303
+ | sombre · foncée *(après)* | éclaircit | 5.76 → **9.60** / 5.12 → **6.99** ↑ |
304
+
305
+ La règle n'est donc pas *« le survol doit foncer »* mais **« le survol doit
306
+ déplacer l'accent à l'opposé de la clarté de son encre »**. Les palettes la
307
+ respectaient déjà dans 3 quadrants sur 4 ; le seul cassé l'était par son
308
+ encre. L'idiome « le survol éclaircit », qui est le bon en mode sombre, est
309
+ conservé dans les deux marques.
310
+
311
+ - **#871 — non-régression : `packages/tests/TU/marketing/brand-palette-contrast.spec.ts`.**
312
+ Les couples `{bg, fg}` littéraux des 7 palettes de marque (14 thèmes) doivent
313
+ tenir AA. L'audit de #871 est un script qu'on lance à la main : rien ne le
314
+ rejoue, donc rien n'empêchait une retouche de palette de ramener ces couples
315
+ en silence. La spec lit les hex **dans l'objet `IOrigamTheme`**, avant toute
316
+ indirection CSS — la règle jsdom/`var()` du `CLAUDE.md` ne s'y applique pas,
317
+ et le navigateur reste l'arbitre de ce qui est peint. Elle embarque ses
318
+ propres garde-fous : les 3 témoins de la formule, un contrôle négatif sur les
319
+ 3 couples corrigés, et un plancher de couverture qui rend bruyante une dérive
320
+ du filtre. **Vérifiée ROUGE sur `origin/develop`** : 3 échecs / 14 succès.
321
+ C'est elle qui a trouvé la 8ᵉ violation ci-dessus.
322
+
159
323
  - **#871 — le mode sombre ne peignait qu'à moitié dans un sous-arbre thémé.**
160
324
  Les feuilles de tokens portent ~1 761 déclarations **dérivées**
161
325
  (`--origam-title---color: var(--origam-color__text---primary)`). Une custom
@@ -331,9 +331,31 @@ const menuProps = computed(() => {
331
331
  return {
332
332
  ...props.menuProps,
333
333
  activatorProps: {
334
+ /*********************************************************
335
+ * role + aria-haspopup — #818
336
+ *
337
+ * @description
338
+ * `'colorpickerbox'` was never a valid `aria-haspopup` token
339
+ * (spec allows `false|true|menu|listbox|tree|grid|dialog`) —
340
+ * axe-core's `aria-valid-attr-value` flagged it, measured
341
+ * against a real browser. The popup is a free-form panel
342
+ * (a color canvas + swatches), not a list of options, so
343
+ * `'dialog'` is the correct token — same family as the
344
+ * WAI-ARIA "Date Picker Dialog" pattern `OrigamDatePickerField`
345
+ * uses right below.
346
+ *
347
+ * @description
348
+ * `role: 'combobox'` is ALSO required here: `activator="parent"`
349
+ * (below) lands these ARIA attrs on the `.origam-field` wrapper
350
+ * `<div>`, which carries no role by default — `aria-allowed-attr`
351
+ * rejects `aria-expanded` on an element whose role doesn't
352
+ * support it. Same fix, same rationale as
353
+ * `OrigamSelect.comboboxAriaAttrs` (`role` MUST sit on the same
354
+ * element as `aria-haspopup`/`aria-expanded`).
355
+ ********************************************************/
356
+ role: "combobox",
334
357
  ...props.menuProps?.activatorProps || {},
335
- "aria-haspopup": "colorpickerbox"
336
- // Set aria-haspopup to 'listbox'
358
+ "aria-haspopup": "dialog"
337
359
  },
338
360
  contentProps: {
339
361
  ...consumerContentProps,
@@ -405,9 +405,29 @@ const menuProps = computed(() => {
405
405
  return {
406
406
  ...props.menuProps,
407
407
  activatorProps: {
408
+ /*********************************************************
409
+ * role + aria-haspopup — #818
410
+ *
411
+ * @description
412
+ * `'datepickerbox'` was never a valid `aria-haspopup` token
413
+ * (spec allows `false|true|menu|listbox|tree|grid|dialog`) —
414
+ * axe-core's `aria-valid-attr-value` flagged it, measured
415
+ * against a real browser. `'dialog'` matches the WAI-ARIA
416
+ * "Date Picker Dialog" pattern: a button/combobox opening a
417
+ * calendar dialog.
418
+ *
419
+ * @description
420
+ * `role: 'combobox'` is ALSO required here: `activator="parent"`
421
+ * (below) lands these ARIA attrs on the `.origam-field` wrapper
422
+ * `<div>`, which carries no role by default — `aria-allowed-attr`
423
+ * rejects `aria-expanded` on an element whose role doesn't
424
+ * support it. Same fix, same rationale as
425
+ * `OrigamSelect.comboboxAriaAttrs` (`role` MUST sit on the same
426
+ * element as `aria-haspopup`/`aria-expanded`).
427
+ ********************************************************/
428
+ role: "combobox",
408
429
  ...props.menuProps?.activatorProps || {},
409
- "aria-haspopup": "datepickerbox"
410
- // Set aria-haspopup to 'listbox'
430
+ "aria-haspopup": "dialog"
411
431
  },
412
432
  contentProps: {
413
433
  ...consumerContentProps,
@@ -91,6 +91,7 @@
91
91
  :readonly="isReadonly"
92
92
  :required="required || undefined"
93
93
  :aria-required="required ? 'true' : undefined"
94
+ :role="role"
94
95
  :size="1"
95
96
  :type="resolvedInputType"
96
97
  :value="displayValue"
@@ -208,6 +209,7 @@ import { applyMask } from "../../utils/Commons/apply-mask.util";
208
209
  import { filterInputAttrs } from "../../utils/Input/input.util";
209
210
  import { forwardRefs } from "../../utils/Commons/forwardRefs.util";
210
211
  import { resolveMaskConfig } from "../../utils/Commons/resolve-mask-config.util";
212
+ defineOptions({ inheritAttrs: false });
211
213
  const props = defineProps({
212
214
  autofocus: { type: Boolean, required: false },
213
215
  counter: { type: [Boolean, Number, String], required: false },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "origam",
3
- "version": "2.18.3",
3
+ "version": "2.18.4",
4
4
  "type": "module",
5
5
  "description": "origam — a Vue 3 design system with multi-theme design tokens, CSS-first components and TypeScript types.",
6
6
  "keywords": [