origam 2.13.0 → 2.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 ADDED
@@ -0,0 +1,1975 @@
1
+ # origam — Changelog
2
+
3
+ This changelog tracks releases of the `origam` package itself. Token-value
4
+ churn (Tokens Studio sync from Figma, semantic primitive shifts) lives in
5
+ [`tokens/CHANGELOG.md`](./tokens/CHANGELOG.md) — keep them separate so
6
+ "the design moved" never blocks a release of "the code shipped a new
7
+ component".
8
+
9
+ Format inspired by [Keep a Changelog](https://keepachangelog.com).
10
+ This project follows [Semantic Versioning](https://semver.org).
11
+
12
+ ---
13
+
14
+ ## [Unreleased]
15
+
16
+ ## [2.14.0] - 2026-08-07
17
+
18
+ Reported from a real integration, not from a code review. Each item was
19
+ reproduced before being fixed, and the fix verified against the running
20
+ component.
21
+
22
+ ### ⚠️ BREAKING — `<origam-avatar>` is now a circle by default
23
+
24
+ The base theme sized avatar corners with `rounded: 'lg'` — a 16px rounded
25
+ square. Every avatar that does not set `rounded` explicitly now renders as a
26
+ circle (`rounded: 'full'`).
27
+
28
+ **Why**: an avatar is round in the overwhelming majority of interfaces, and
29
+ until now obtaining one required knowing that a second, undocumented radius
30
+ scale existed (see the `rounded` fix below). The mismatch showed: a
31
+ rounded-square avatar dropped into a circular trigger left a visible ring
32
+ around it — reported from the field as *"a square inside a circle"*.
33
+
34
+ **Migration** — restore the previous shape app-wide in one line, through the
35
+ theme layer:
36
+
37
+ ```ts
38
+ createOrigam({ theme: { components: { 'origam-avatar': { rounded: 'lg' } } } })
39
+ ```
40
+
41
+ or per instance with `<origam-avatar rounded="lg">`.
42
+
43
+ ### Fixed — dead `aria-describedby` on input fields (accessibility)
44
+
45
+ A screen reader announced **no validation message at all**. The defect was
46
+ invisible to the eye: the message rendered correctly.
47
+
48
+ `<origam-text-field>` filtered `id` out of the props it forwarded, so
49
+ `<origam-input>` never received it and invented its own. It then derived
50
+ `<id>-messages` and pointed `aria-describedby` at it — but `<origam-messages>`
51
+ declared an `id` prop and applied it to **no element**, giving each message a
52
+ different id instead. The reference resolved to nothing.
53
+
54
+ `<origam-messages>` now applies the received `id` to its root, and
55
+ `<origam-text-field>` no longer filters `id` out. Verified in a browser:
56
+ `aria-describedby` resolves to an element that exists.
57
+
58
+ **Second effect of the same fix**: an `id` passed to a text field now actually
59
+ reaches its `<input>`. `document.getElementById(...)` finds it, and an
60
+ external `<label for>` associates correctly — both previously failed silently.
61
+
62
+ ### Fixed — `<origam-avatar size>` ignored outside a flex container
63
+
64
+ The root element declared no `display`, so with `tag="span"` it inherited the
65
+ browser's `inline` — and an inline box ignores `width`/`height`. The avatar
66
+ collapsed to its content and `size` did nothing, with no warning.
67
+
68
+ It appeared to work only when the parent happened to be a flex container,
69
+ which blockifies its children. Measured on one real page: the same component
70
+ rendered 32×32 under `inline-flex` but **30×30 under `position: relative`**
71
+ for a requested size of 72.
72
+
73
+ The root now declares `display: inline-flex` — `inline-flex` rather than
74
+ `block` because `tag="span"` exists so an avatar can sit in phrasing content,
75
+ inside a `<button>` whose content model rejects block-level elements. Consumers
76
+ who worked around this with `.origam-avatar { display: inline-flex }` can drop
77
+ the override; it remains harmless.
78
+
79
+ ### Fixed — `rounded` rejected the token scale, hiding the circular variant
80
+
81
+ `TRounded` only described the component scale (`x-small` … `x-large`,
82
+ `shaped`), none of which produces a circle. The radius rungs — `none`, `xs`,
83
+ `sm`, `md`, `lg`, `xl` and **`full`**, the one that yields a circle — were
84
+ already honoured at runtime (the brand themes have shipped `rounded: 'md'` for
85
+ a while) but failed to type-check and never appeared in autocompletion.
86
+
87
+ A round avatar was therefore undiscoverable: it required knowing that a second,
88
+ undocumented scale existed. Reported from the field as *"a square inside a
89
+ circle"* — a rounded-square avatar in a circular trigger leaves a visible ring.
90
+
91
+ The rungs are now a first-class `ROUNDED_TOKEN` enum and part of `TRounded`.
92
+ `rounded="full"` type-checks. The two scales stay separate on purpose: one
93
+ names an intent (`shaped`), the other a radius rung — `x-small` and `xs` are
94
+ not interchangeable.
95
+
96
+ ### Fixed — the published package shipped no changelog
97
+
98
+ `files` listed `CHANGELOG.md`, but the file lives at the monorepo root and had
99
+ no counterpart in `packages/ds/`, so npm silently packed nothing. Consumers had
100
+ no way to tell what a release contained. A `prepack` step now copies it in —
101
+ verified: 96 kB of changelog in the tarball.
102
+
103
+ ## [2.13.0] - 2026-08-05
104
+
105
+ ### ⚠️ BREAKING — `<origam-select>` dropdown rows now follow the control
106
+
107
+ A `<origam-select>` opened a menu whose rows measured 48px whatever the
108
+ control was set to. The most common case — a select with **no** `size` prop —
109
+ was the worst: a 48px row under a 36px control. Rows now match the control
110
+ exactly at every size (36/36 unsized, 28/28 small, 44/44 large, 60/60
111
+ x-large + comfortable).
112
+
113
+ **Why this is breaking**: the fix corrects how `density` is applied to
114
+ **every** list, not only dropdowns. Density becomes a coherent ±8px offset
115
+ aligned on the field model, where it previously amounted to −24px in
116
+ `compact` and did nothing at all in `comfortable`:
117
+
118
+ | standalone list row, no `size` | before | after |
119
+ |---|---|---|
120
+ | `default` | 56px | 56px |
121
+ | `compact` | 32px | **48px** |
122
+ | `comfortable` | 56px | **64px** |
123
+
124
+ The old 32px came from counting density three times (−8 on `min-height`,
125
+ −8 on each block padding). `comfortable` had no rule at all and was a no-op.
126
+ Anything relying on `density="compact"` on an `<origam-list>` will render
127
+ 8–16px taller.
128
+
129
+ `ISizeProps` is added to `IListProps` (a forwarding prop — the list paints
130
+ nothing from it) and to `IListItemProps` (which paints the row height).
131
+
132
+ ### ⚠️ BREAKING — `useStyle()` no longer overwrites a consumer `id`
133
+
134
+ `useStyle()` generated its own id and the component put it on its root,
135
+ silently overwriting an `id` passed as a prop. On `<origam-btn>` this broke
136
+ every `<label for>`, `aria-labelledby`, `aria-controls` and
137
+ `aria-describedby` pointing at a button. The consumer's `id` now wins and the
138
+ generated rule follows it.
139
+
140
+ **Why this is breaking**: components previously always carried a generated
141
+ id. Anything keyed on that generated id (a CSS selector, a test locator)
142
+ will no longer match when the consumer supplies an `id`.
143
+
144
+ ### Fixed
145
+
146
+ - **The generated stylesheet was being discarded entirely.** `StyleValue`
147
+ includes `false`, and Vue resolves an unpassed prop whose declared type
148
+ contains Boolean to `false`, never `undefined` — so every component
149
+ exposing the shared `style` prop emitted `#id {false}`, which is not a
150
+ declaration. Browsers dropped it through error recovery; jsdom dropped the
151
+ **whole** sheet. Nested style arrays were also flattened one level only,
152
+ landing an object in the sheet as `[object Object]`.
153
+ - **CSS injection through the `id` prop.** Now that the id comes from the
154
+ consumer and is interpolated verbatim into a `<style>` element, an id such
155
+ as `a { } body { display: none }` appended arbitrary rules to the document.
156
+ A new `escapeCssIdent()` utility delegates to native `CSS.escape`, with a
157
+ spec-conformant fallback for jsdom and SSR, which expose no `CSS` global.
158
+ - **Interpolation placeholders silently rendered empty.** `t()` is variadic
159
+ — `t(key, ...params)` — but five call sites passed it an **array**, so the
160
+ array landed in `params[0]` where the template expected the value itself.
161
+ This stayed invisible while `stringifyParam` accepted any value (`[3]`
162
+ stringified to `"3"`); once it was narrowed to primitives, an array began
163
+ rendering as an **empty string** and the placeholder vanished. Visible
164
+ effects: `<origam-file-field display="counter">` displayed `" files"`
165
+ instead of `"3 files"`, and the `max_length` validation message of
166
+ `<origam-text-field>` / `<origam-textarea-field>` showed an empty limit.
167
+
168
+ ### Security
169
+
170
+ `brace-expansion` overrides were pinned to the **vulnerable** versions
171
+ (`^2.1.2` / `^5.0.8`) — set for an earlier advisory and never moved since.
172
+ They are raised to the patched floors (`^2.1.4` / `^5.0.9`), which clears
173
+ the three `high` DoS advisories (unbounded expansion) that `pnpm audit`
174
+ reported through `minimatch`.
175
+
176
+ Nuxt advisories published during this release cycle are cleared too:
177
+ `@nuxt/devtools` (**critical**), and five `high` on `nuxt` itself
178
+ (server-side RCE via Runtime, unauthenticated OOM crash, CPU exhaustion
179
+ while parsing). `nuxt` moves to `^4.5.1` and `typeorm` to `^0.3.31`,
180
+ staying on the `0.3.x` line — the fix landed there, and `1.x` is a
181
+ breaking change. `pnpm audit --prod` now reports **no known
182
+ vulnerabilities**.
183
+
184
+ Scope note for consumers: the published `origam` package depends only on
185
+ `@mdi/font` and `qrcode-generator`, with `shiki` / `vue` / `vue-i18n` /
186
+ `vue-router` as peers. None of the packages above ship with it — they all
187
+ come from `@origam/marketing`, which is `private` and never published. You
188
+ were never exposed; the deployed site was.
189
+
190
+ One change does reach the published package. Nuxt 4.5 made TypeScript
191
+ unable to name the type `defineNuxtModule` returns, so the default export
192
+ of the `origam/nuxt` sub-export is now explicitly annotated as
193
+ `NuxtModule`. Behaviour is unchanged — it is a declaration-emit fix.
194
+
195
+ ### ⚠️ BREAKING — i18n locale keys migrated to `snake_case`
196
+
197
+ All locale keys shipped by the DS (`packages/ds/src/assets/locales/en.json`,
198
+ `fr.json`) now follow the project-wide i18n convention: every key **segment**
199
+ is `snake_case` (`a-z`, `0-9`, `_`). Nested structure is unchanged — only the
200
+ casing of each segment changes (e.g. `dataTable.ariaLabel.sortBy` →
201
+ `data_table.aria_label.sort_by`).
202
+
203
+ **Why this is breaking**: `createOrigam({ messages })` lets a consumer
204
+ override any locale message by key path. Any override keyed on an old
205
+ camelCase path (e.g. `'origam.dataTable.sortBy'`) will silently stop applying
206
+ — the DS falls back to its own bundled (English) string with **no error**,
207
+ which is easy to miss until a French (or other locale) screen renders in
208
+ English.
209
+
210
+ **All 65 `t()` call sites and their indirect prop-default equivalents were
211
+ updated in the same commit** — verified with a cross-reference script (every
212
+ literal `origam.*` key still referenced in `packages/ds/src` was checked
213
+ against both locale files; zero leftover old-casing key strings remain
214
+ anywhere in `packages/ds/src`). Fallback strings (the second argument to
215
+ `t(key, fallback)`) are unchanged — only the key paths moved.
216
+
217
+ If you override any DS locale key, update the key path using the table below.
218
+
219
+ | Old key | New key |
220
+ |---|---|
221
+ | `origam.bottomNav.ariaLabel` | `origam.bottom_nav.aria_label` |
222
+ | `origam.calendar.ariaLabel` | `origam.calendar.aria_label` |
223
+ | `origam.calendar.monthGrid` | `origam.calendar.month_grid` |
224
+ | `origam.calendar.moreEvents` | `origam.calendar.more_events` |
225
+ | `origam.calendar.viewSwitcher` | `origam.calendar.view_switcher` |
226
+ | `origam.carousel.ariaLabel.delimiter` | `origam.carousel.aria_label.delimiter` |
227
+ | `origam.clipboard.copiedAriaLabel` | `origam.clipboard.copied_aria_label` |
228
+ | `origam.clipboard.copyAriaLabel` | `origam.clipboard.copy_aria_label` |
229
+ | `origam.code.copiedAriaLabel` | `origam.code.copied_aria_label` |
230
+ | `origam.code.copyAriaLabel` | `origam.code.copy_aria_label` |
231
+ | `origam.colorPicker.canvas.ariaLabel` | `origam.color_picker.canvas.aria_label` |
232
+ | `origam.colorPicker.canvas.value` | `origam.color_picker.canvas.value` |
233
+ | `origam.confirmEdit.cancel` | `origam.confirm_edit.cancel` |
234
+ | `origam.confirmEdit.ok` | `origam.confirm_edit.ok` |
235
+ | `origam.dataFooter.firstPage` | `origam.data_footer.first_page` |
236
+ | `origam.dataFooter.itemsPerPageAll` | `origam.data_footer.items_per_page_all` |
237
+ | `origam.dataFooter.itemsPerPageText` | `origam.data_footer.items_per_page_text` |
238
+ | `origam.dataFooter.lastPage` | `origam.data_footer.last_page` |
239
+ | `origam.dataFooter.nextPage` | `origam.data_footer.next_page` |
240
+ | `origam.dataFooter.pageText` | `origam.data_footer.page_text` |
241
+ | `origam.dataFooter.prevPage` | `origam.data_footer.prev_page` |
242
+ | `origam.dataIterator.loadingText` | `origam.data_iterator.loading_text` |
243
+ | `origam.dataIterator.noResultsText` | `origam.data_iterator.no_results_text` |
244
+ | `origam.dataTable.ariaLabel.activateAscending` | `origam.data_table.aria_label.activate_ascending` |
245
+ | `origam.dataTable.ariaLabel.activateDescending` | `origam.data_table.aria_label.activate_descending` |
246
+ | `origam.dataTable.ariaLabel.activateNone` | `origam.data_table.aria_label.activate_none` |
247
+ | `origam.dataTable.ariaLabel.sortAscending` | `origam.data_table.aria_label.sort_ascending` |
248
+ | `origam.dataTable.ariaLabel.sortDescending` | `origam.data_table.aria_label.sort_descending` |
249
+ | `origam.dataTable.ariaLabel.sortNone` | `origam.data_table.aria_label.sort_none` |
250
+ | `origam.dataTable.itemsPerPageText` | `origam.data_table.items_per_page_text` |
251
+ | `origam.dataTable.sortBy` | `origam.data_table.sort_by` |
252
+ | `origam.dataTableRow.collapseRow` | `origam.data_table_row.collapse_row` |
253
+ | `origam.dataTableRow.expandRow` | `origam.data_table_row.expand_row` |
254
+ | `origam.datePicker.header` | `origam.date_picker.header` |
255
+ | `origam.datePicker.input.placeholder` | `origam.date_picker.input.placeholder` |
256
+ | `origam.datePicker.itemsSelected` | `origam.date_picker.items_selected` |
257
+ | `origam.datePicker.range.header` | `origam.date_picker.range.header` |
258
+ | `origam.datePicker.range.title` | `origam.date_picker.range.title` |
259
+ | `origam.datePicker.title` | `origam.date_picker.title` |
260
+ | `origam.datePickerRangeField.text` | `origam.date_picker_range_field.text` |
261
+ | `origam.fileField.browse` | `origam.file_field.browse` |
262
+ | `origam.fileField.counter` | `origam.file_field.counter` |
263
+ | `origam.fileField.counterSize` | `origam.file_field.counter_size` |
264
+ | `origam.fileField.dropzoneSubtitle` | `origam.file_field.dropzone_subtitle` |
265
+ | `origam.fileField.dropzoneTitle` | `origam.file_field.dropzone_title` |
266
+ | `origam.fileInput.counter` | `origam.file_input.counter` |
267
+ | `origam.fileInput.counterSize` | `origam.file_input.counter_size` |
268
+ | `origam.fileUpload.browse` | `origam.file_upload.browse` |
269
+ | `origam.fileUpload.divider` | `origam.file_upload.divider` |
270
+ | `origam.fileUpload.title` | `origam.file_upload.title` |
271
+ | `origam.infiniteScroll.empty` | `origam.infinite_scroll.empty` |
272
+ | `origam.infiniteScroll.loadMore` | `origam.infinite_scroll.load_more` |
273
+ | `origam.input.appendAction` | `origam.input.append_action` |
274
+ | `origam.input.prependAction` | `origam.input.prepend_action` |
275
+ | `origam.media.castToDevice` | `origam.media.cast_to_device` |
276
+ | `origam.media.nextTrack` | `origam.media.next_track` |
277
+ | `origam.media.normalSpeed` | `origam.media.normal_speed` |
278
+ | `origam.media.playbackSpeed` | `origam.media.playback_speed` |
279
+ | `origam.media.previousTrack` | `origam.media.previous_track` |
280
+ | `origam.media.stopCasting` | `origam.media.stop_casting` |
281
+ | `origam.noDataText` | `origam.no_data_text` |
282
+ | `origam.pagination.ariaLabel.currentPage` | `origam.pagination.aria_label.current_page` |
283
+ | `origam.pagination.ariaLabel.first` | `origam.pagination.aria_label.first` |
284
+ | `origam.pagination.ariaLabel.last` | `origam.pagination.aria_label.last` |
285
+ | `origam.pagination.ariaLabel.next` | `origam.pagination.aria_label.next` |
286
+ | `origam.pagination.ariaLabel.page` | `origam.pagination.aria_label.page` |
287
+ | `origam.pagination.ariaLabel.previous` | `origam.pagination.aria_label.previous` |
288
+ | `origam.pagination.ariaLabel.root` | `origam.pagination.aria_label.root` |
289
+ | `origam.rating.ariaLabel.item` | `origam.rating.aria_label.item` |
290
+ | `origam.stepper.progressSteps` | `origam.stepper.progress_steps` |
291
+ | `origam.stepper.stepAriaLabel` | `origam.stepper.step_aria_label` |
292
+ | `origam.timePicker.am` | `origam.time_picker.am` |
293
+ | `origam.timePicker.pm` | `origam.time_picker.pm` |
294
+ | `origam.timePicker.title` | `origam.time_picker.title` |
295
+ | `origam.video.disableCaptions` | `origam.video.disable_captions` |
296
+ | `origam.video.enableCaptions` | `origam.video.enable_captions` |
297
+ | `origam.video.enterFullscreen` | `origam.video.enter_fullscreen` |
298
+ | `origam.video.enterPip` | `origam.video.enter_pip` |
299
+ | `origam.video.exitFullscreen` | `origam.video.exit_fullscreen` |
300
+ | `origam.video.exitPip` | `origam.video.exit_pip` |
301
+
302
+ ### ⚠️ BREAKING — `<origam-title>` now defaults to `h2`, not `h1`
303
+
304
+ Every `<origam-title>` written **without** an explicit `tag` renders an
305
+ `<h2>` instead of an `<h1>`. This changes the rendered DOM, the heading
306
+ outline, any CSS keyed on `h1`, and the result of automated a11y audits.
307
+
308
+ **Why**: a document carries exactly one `h1`, so `h1` is the one level a
309
+ shared component must never emit by default — two untagged titles on a page
310
+ produced two `h1`s and broke both the heading order and the a11y audit. `h2`
311
+ is the deepest level that is always valid under a page-owned `h1`, and
312
+ repeating it is legal. The correct level depends on document position, which
313
+ the component cannot know: pass `tag` explicitly whenever it matters.
314
+
315
+ **Migration** — restore the old behaviour app-wide in one line, through the
316
+ defaults layer:
317
+
318
+ ```ts
319
+ createOrigam({ theme: { components: { 'origam-title': { tag: 'h1' } } } })
320
+ ```
321
+
322
+ ⚠️ **This migration only works from this release onwards.** On any earlier
323
+ version it silently does nothing: `OrigamTitle`'s template bound `:is="tag"`,
324
+ and in `<script setup>` a bare prop name in the template resolves against the
325
+ raw `$props`, never against the `useDefaults()` proxy — so the theme value
326
+ was read and then ignored. Fixed here by binding `:is="props.tag"`, the same
327
+ correction as the seven components in #250. Do not conclude the snippet is
328
+ wrong if you try it on 2.12.x.
329
+
330
+ ### Fixed
331
+
332
+ - **`useStyle()` no longer overwrites a consumer-supplied `id`.** The
333
+ composable returns the id a component puts on its root element *and* the
334
+ selector of the `#<id> { … }` rule it injects — the two must be the same
335
+ value or the rule stops matching. It now accepts that id from the caller
336
+ (`useStyle(styles, () => props.id)`), reactively, so an `id` that only
337
+ appears on a later render is still picked up. Applied to `OrigamBtn`, where
338
+ the generated id silently replaced the consumer's and broke every
339
+ `<label for>` / `aria-labelledby` / `aria-controls` / `aria-describedby`
340
+ association targeting a button, and to `OrigamTitle`, where element and
341
+ rule had diverged. ⚠️ **138 of the 140 components calling `useStyle` are
342
+ still affected**, in three shapes: 14 swallow a consumer `id` outright
343
+ (same defect as Btn); 114 bind no id to their root at all, so where the
344
+ component also declares an `id` prop — 192 of 217 do — that prop is inert
345
+ and a consumer simply cannot set an id (measured on `OrigamCard` and
346
+ `OrigamKbd`, which render no `id` attribute even when one is passed); and
347
+ 10 bind some other id while the rule keeps targeting the generated one,
348
+ the same divergence just fixed on Title. Tracked separately; a blanket
349
+ default was deliberately rejected because `OrigamSnackbarGroup` uses
350
+ `props.id` as a group *name*, not a DOM id.
351
+ - **The injected stylesheet is valid CSS again.** Every rule carried a bare
352
+ `false` in its body (`#id {false}`). Vue's `StyleValue` type includes
353
+ `false`, so the shared `style` prop compiles to a runtime type containing
354
+ `Boolean`, and Vue resolves an unpassed boolean-typed prop to the concrete
355
+ value `false` — which was serialised verbatim. 192 of 217 components
356
+ declare that prop. No visual impact: Chromium, Firefox and WebKit all
357
+ discard the invalid declaration by CSS error recovery and apply the rest
358
+ unchanged. The cost was invalid CSS shipped to consumers, and jsdom
359
+ rejecting every generated sheet under test ("Could not parse CSS
360
+ stylesheet"). Numbers are dropped for the same reason, and an object nested
361
+ inside an array is now expanded instead of reaching the sheet as
362
+ `[object Object]`.
363
+ - **`<origam-title>` reads its theme defaults.** It never called
364
+ `useDefaults()`, so `theme.components['origam-title']` was inert.
365
+
366
+ ### Added
367
+
368
+ - **`escapeCssIdent()`** (`origam/utils`) — escapes an arbitrary string for
369
+ interpolation as a CSS ident. Required now that `useStyle()` builds a real
370
+ stylesheet rule out of a consumer-supplied id: without it an id such as
371
+ `a { } body { display: none }` appends attacker- or typo-controlled rules
372
+ to the document, and ids that are legal in HTML but illegal as a CSS ident
373
+ (leading digit, dots, colons) silently kill their own rule. Delegates to
374
+ the native `CSS.escape` when present, with a spec-equivalent fallback for
375
+ jsdom and SSR — the two were compared over 34 inputs in Chromium and are
376
+ byte-identical.
377
+
378
+ ### Changed
379
+
380
+ - **`useStyle(styles, uniq)`** — `uniq` widened from `string | undefined` to
381
+ `MaybeRefOrGetter<string | undefined>`. Backward compatible: a plain string
382
+ still behaves exactly as before, and an empty value falls back to the
383
+ generated `<name>-<uid>` id rather than emitting the invalid selector `#`.
384
+
385
+ ---
386
+
387
+ ## [2.12.1] — 2026-07-31
388
+
389
+ **Hotfix — `vue-router` 5 is now accepted.** A consuming project could not
390
+ upgrade to Nuxt 4.5.1: Nuxt pulls `vue-router` 5, while origam declared an
391
+ optional peer of `vue-router: "^4.5.0"`. The peer range is widened to
392
+ `"^4.5.0 || ^5.0.0"`.
393
+
394
+ Nothing else changed — no runtime code, no public API, no dependency added.
395
+ The widening is strictly additive: projects on `vue-router` 4 are unaffected,
396
+ projects on 5 stop being blocked.
397
+
398
+ Vue Router 5 is, in the maintainers' own words, "a *boring* release, it
399
+ merges unplugin-vue-router into the core package with no breaking changes",
400
+ and the official migration guide states: "If you're using Vue Router 4
401
+ without unplugin-vue-router, there are no breaking changes". origam does not
402
+ use unplugin-vue-router, nor the iife build (the single documented breaking
403
+ change is that the iife bundle no longer ships `@vue/devtools-api`).
404
+
405
+ Verified on both majors before shipping: every symbol origam imports
406
+ (`useLink`, `RouterLink.useLink`, `RouteLocationRaw`, `Router`,
407
+ `RouteLocationNormalizedLoaded`, `NavigationGuardNext`, `UseLinkOptions`)
408
+ still exists in 5.2.0; type-check of the design system is clean against
409
+ 4.6.4 and 5.2.0 alike; and the built `dist` was exercised at runtime under
410
+ both — href resolution, named routes, click navigation, external hrefs,
411
+ `isActive`, and the tag fallback when no `to` is passed all behave
412
+ identically.
413
+
414
+ **Consumer note.** `vue-router` 5 requires `vue` `^3.5.34 || ^4.0.0`, while
415
+ origam's peer on `vue` remains `^3.5.0`. A project pinned between Vue 3.5.0
416
+ and 3.5.33 that adopts `vue-router` 5 will hit a conflict — between `vue`
417
+ and `vue-router` directly, not with origam.
418
+
419
+ ---
420
+
421
+ ## [2.12.0] — 2026-07-29
422
+
423
+ **Hotfix — `vue-i18n` is optional again.** A consuming project reported that
424
+ origam could not be enabled at all: `createOrigam()` reached `createLocale()`,
425
+ which imported `vue-i18n` at module level, even though the package declares it
426
+ as an **optional** peer. Any project without `vue-i18n` failed at import time.
427
+
428
+ ### Fixed
429
+
430
+ - **`createLocale()` no longer imports `vue-i18n`.** The default locale is now
431
+ backed by a built-in adapter shipped with the DS. Runtime *and* type
432
+ dependencies are gone — `grep vue-i18n` over the published `dist/` returns
433
+ only a string literal (the vue-i18n adapter's own `name` field) and a
434
+ comment. (Reported by a consumer.)
435
+ - The `vue-i18n` **type** imports in `locale.interface.ts` / `locale.util.ts`
436
+ are replaced by locally-declared structural types. An `import type` costs
437
+ nothing at runtime but still breaks `tsc` for a project that does not install
438
+ the package once `skipLibCheck` is `false` — the same class of bug, one step
439
+ further.
440
+
441
+ ### Added
442
+
443
+ - **`createBuiltinAdapter()`**, exported from `origam/utils`. Resolves dotted
444
+ keys, falls back to the fallback locale then to the key itself, and
445
+ interpolates both positional (`{0}`) and named (`{value}`) params — the only
446
+ two forms used by the DS's own messages.
447
+
448
+ It deliberately does **not** reimplement `vue-i18n`: no plurals, no linked
449
+ messages, no date formatting. A consumer needing those keeps `vue-i18n` and
450
+ passes their own adapter.
451
+
452
+ ### Changed — read this if you use `vue-i18n`
453
+
454
+ **The default adapter is no longer `vue-i18n`.** If you relied on
455
+ `createOrigam()` building a `vue-i18n` instance for you, pass it explicitly:
456
+
457
+ ```ts
458
+ import { createOrigam } from 'origam'
459
+ import { createVueI18nAdapter } from 'origam/utils'
460
+ import { createI18n, useI18n } from 'vue-i18n'
461
+
462
+ const i18n = createI18n({ legacy: false, locale: 'en' })
463
+
464
+ createOrigam({
465
+ locale: { adapter: createVueI18nAdapter({ i18n, useI18n }) }
466
+ })
467
+ ```
468
+
469
+ Nothing changes for the DS's own strings — they resolve identically through
470
+ either adapter. This is a **minor** rather than a patch precisely because that
471
+ default changed.
472
+
473
+ ---
474
+
475
+ ## [2.11.0] — 2026-07-28
476
+
477
+ Theming-resolution release. Every fix here has the same root cause: a
478
+ component **declared** theme-able props but never **consumed** them, so an
479
+ `IOrigamTheme.components` block was a silent no-op. Minor rather than patch
480
+ because the public prop surface grew (see *Added*), even though every commit
481
+ is labelled `fix`.
482
+
483
+ ### Added
484
+
485
+ - **`ISelectionControlProps` now extends `IBorderProps` / `IRoundedProps` /
486
+ `IElevationProps`**, inherited by `ICheckboxBtnProps` and `IRadioBtnProps`.
487
+ `OrigamSelectionControl` consumes them through the standard
488
+ `useBorder` / `useRounded` / `useElevation` composables, on the
489
+ state-layer box that owns the control's visible surface — same relay point
490
+ as `OrigamSwitchTrack`. Checkbox and Radio therefore accept `border`,
491
+ `rounded` and `elevation`, from a prop or from a theme. (#241)
492
+
493
+ ::: warning Known limit — `rounded` alone paints nothing at rest
494
+ The state-layer box has no background and no border by default, so a
495
+ `rounded` passed on its own is measurable in the computed style but
496
+ produces no visible difference at rest. It shows on hover (the state layer
497
+ now follows the radius), or combined with `border` / `elevation` / a
498
+ custom background. The glyph itself (`mdi-checkbox-*`, `mdi-radiobox-*`)
499
+ is an icon-font character and keeps its own shape — reshaping the mark
500
+ would be a rendering change, not a prop. Documented in the Checkbox and
501
+ Radio pages.
502
+ :::
503
+
504
+ ### Fixed
505
+
506
+ - **`OrigamBtnGroup`** forwarded `density`/`color`/`bgColor`/`size`/`hover`/
507
+ `active` to its children unconditionally, overwriting theme defaults with
508
+ `undefined`. Now filtered through `usePassedProps()` — the helper that
509
+ detects whether the *consumer* actually passed a prop, since Vue coerces an
510
+ unpassed boolean to `false`, never `undefined`. (#274)
511
+ - **`:is="tag"` bypassed theme resolution** on 7 components — `OrigamAvatar`,
512
+ `OrigamBtnGroup`, `OrigamExpansionPanel`, `OrigamLabel`, `OrigamMessages`,
513
+ `OrigamTab`, `OrigamTabs`. In `<script setup>` the template binds each prop
514
+ from the *raw* `$props`, independently of the local
515
+ `props = useDefaults(_props)`, so a bare `:is="tag"` never saw the theme's
516
+ value. The audit the ticket asked for widened the scope from 1 file to 7.
517
+ (#250)
518
+ - **`OrigamSelectionControl` declared `grid-area: control` unconditionally**,
519
+ assuming it always sits inside a Field/Input named grid. Any consumer
520
+ placing a bare control in its own grid saw every instance collapse onto one
521
+ cell. The declaration was in fact **dead code** on the Field/Input path —
522
+ `.origam-input__control` already owns `grid-area: control` *and* is
523
+ `display: flex`, and a `grid-area` on a flex child has no effect — so it is
524
+ removed rather than scoped. (#247)
525
+ - **The Checkbox/Radio hover halo hardcoded `border-radius: 100%`**, staying
526
+ circular even under `rounded="md"` — while being the only surface the prop
527
+ could paint. Now `inherit`. (#241)
528
+
529
+ ### Internal
530
+
531
+ - Marketing i18n: locale files split one per top-level namespace, an
532
+ `i18n:check` script wired into CI, and 195 missing translation keys filled
533
+ (400 → 205). EN/FR parity reached **0 gap** — the admin backoffice went
534
+ from 0 % to fully translated. No change to the published package.
535
+
536
+ ---
537
+
538
+ ## [2.10.0] — 2026-07-27
539
+
540
+ Field-rendering release. No new API — no component, prop, emit or slot was
541
+ added, and no source file was created. The minor bump is there to flag one
542
+ **visible rendering change** for consumers of `origam/styles` (see *Changed*);
543
+ everything else is a fix.
544
+
545
+ ### Changed
546
+
547
+ - **`--origam-field__input---padding-start` / `-end` now resolve to `0`**
548
+ (was `var(--origam-space---4)`, i.e. 16px) for consumers of the bundled
549
+ `origam/styles` (`main.css`). The inline spacing of a field is owned by the
550
+ **root** token `--origam-field---padding-start`, which is unchanged at
551
+ `{space.4}`; the input wrapper sits inside it, so a second inline padding
552
+ doubled it — and it truncated the value text of narrow controls
553
+ (`elevated` → `ele…`). Consumers importing `origam/tokens/css/{light,dark}`
554
+ are **unaffected**: those sheets already emitted `0` since 2026-07-24.
555
+ Fields keep their inline spacing, supplied by the root token. (#295)
556
+
557
+ ### Fixed
558
+
559
+ - **Tokens** — `field.input.padding-start/end` was `0` in the generated sheets
560
+ but `{space.4}` in the token source, so every `tokens:build` silently
561
+ reverted the fix, and `main.css` (produced by the separate `styles:build`
562
+ step) had drifted to a third value. The source now carries the intent and
563
+ the pipeline is idempotent — a second full build produces no diff. (#295)
564
+ - **`OrigamSliderField`** — the `rounded` prop was inert: radii were
565
+ hardcoded instead of going through `useRounded()`. (#283)
566
+ - **`OrigamApp`** — did not call `useDefaults()`, so
567
+ `theme.components['origam-app']` (`bgColor`, `fullHeight`) was ignored.
568
+ - **`OrigamField`** — inline padding is floored at the corner radius so
569
+ content clears the rounded outline, and the outline's start leg is widened
570
+ to match, the left corner no longer rendering flatter than the right.
571
+ Prepended fields are excluded to avoid a swatch/icon box artifact.
572
+ - **`OrigamField`** — the outlined floating label was pinned to the notch
573
+ corner by a hard `margin: 0 4px` while the input text and the resting label
574
+ both sit at `--origam-field__input---padding-start`, leaving the label
575
+ misaligned with its own value — visibly so on large-radius themes.
576
+ - **`OrigamColorPickerField`** — the swatch sat inside the field's inline
577
+ padding, so any consumer raising `--origam-field---padding-start` opened a
578
+ gap between the colour and the left border. Also fixed: the field wrapping
579
+ onto two lines when given content, and the swatch's square outer corner.
580
+ - **`OrigamColorPicker`** — a class typo on the hue/alpha slider fill
581
+ occluded both gradients.
582
+ - **`OrigamDatePickerField`** — wrapped onto two lines when given content in
583
+ `range` mode.
584
+
585
+ ### Internal
586
+
587
+ - Quality Gate unblocked: the two SonarQube new-code violations that had kept
588
+ the `Build` workflow red on every `develop` push since 2026-07-22 are
589
+ resolved — a nested ternary in `OrigamSliderField` and a duplicate
590
+ `:deep()` selector in `OrigamColorPickerField`. Both were pure
591
+ maintainability findings, no behaviour change. (#296)
592
+
593
+ ---
594
+
595
+ ## [2.9.0] — 2026-07-22
596
+
597
+ Theming-enablement release. This ships the design-token **hooks** and the
598
+ `useDefaults` **wiring** that let a consumer configure a component's
599
+ appearance entirely from an `IOrigamTheme` object (props-first), plus the
600
+ CSS hooks brand themes need for translucency and separation. No breaking
601
+ changes — every addition is opt-in via a new component token with an inert
602
+ (`none`) fallback, so existing renders are byte-identical until a theme
603
+ sets a value.
604
+
605
+ ### Added
606
+
607
+ - **`useDefaults()` now resolves per-component theme defaults on 11 more
608
+ components** that declared theme-configurable props but never read
609
+ `theme.components['origam-<name>']` — their theme config was silently
610
+ inert before this. This is the mechanism that makes props-first theming
611
+ actually apply. (#242)
612
+ - **Translucency & focus hooks on 7 components.** New component tokens,
613
+ each consumed with an inert fallback (`var(--…, none)`):
614
+ `--origam-chip---backdrop-filter`, `--origam-snackbar-item---backdrop-filter`,
615
+ `--origam-field---backdrop-filter` (+ `--origam-field---focus-ring-*`),
616
+ `--origam-switch__track---backdrop-filter`,
617
+ `--origam-selection-control__input---backdrop-filter`,
618
+ `--origam-tooltip---backdrop-filter`, `--origam-overlay-scrim---backdrop-filter`.
619
+ These give brand themes real `backdrop-filter` translucency (glass, frosted
620
+ surfaces) without per-instance `:style` overrides. (#253)
621
+ - **`OrigamAvatarGroup` separation ring.** Overlapping avatars now get a
622
+ legible seam via a dedicated `outline` — a CSS property distinct from
623
+ `box-shadow`, so it coexists with each theme's own avatar shadow with no
624
+ specificity or ordering tricks. Driven by new
625
+ `--origam-avatar-group__item---outline-{color,width,style}` tokens
626
+ (defaults `color.surface.default` / `2px` / `solid`). Only avatars rendered
627
+ inside an `<origam-avatar-group>` get it; a standalone `<origam-avatar>`
628
+ never does. (#263)
629
+ - **`usePassedProps()` composable helper.** Reports which props a consumer
630
+ actually passed by reading `vnode.props`, distinguishing a real value from
631
+ Vue 3 coercing an unpassed boolean prop to `false`. Used so a
632
+ theme-provided value is no longer overwritten by an `undefined → false`
633
+ default. (#263)
634
+
635
+ ### Fixed
636
+
637
+ - **Live theme switching now updates component default *props*, not just CSS
638
+ variables.** The Nuxt module's theme singleton could be duplicated when the
639
+ host app resolved `origam/nuxt` (source) and `origam/composables` (compiled
640
+ `dist`) to two physical module files — two independent `_theme` singletons,
641
+ so the plugin's watcher (which reassigns the resolved per-component
642
+ defaults) listened to a different instance than the one `setTheme()`
643
+ mutated. Props stayed frozen on a live switch while cssVars followed (pure
644
+ CSS cascade). The singleton is now anchored on `globalThis` on the client
645
+ (server stays module-level to avoid leaking state across concurrent SSR
646
+ requests). (#275)
647
+ - **`OrigamRadio`, `OrigamTabs` and `OrigamSliderField` now call
648
+ `useDefaults()`.** They declared theme-configurable props (`activeBgColor`,
649
+ `variant`, `color`/`bgColor`) but never resolved
650
+ `theme.components['origam-<name>']`, so that theme config was silently inert.
651
+ (#279)
652
+ - **`OrigamDialog` `scrim` now renders.** `scrim` was missing from the
653
+ component's `withDefaults(...)`, so Vue 3 coerced the unpassed boolean to
654
+ `false` and forwarded it to `OrigamOverlay`, overriding the overlay's own
655
+ `scrim: true` default — the backdrop never showed. Anchored `scrim: true` in
656
+ `withDefaults` (same pattern as the existing `openOnClick` default). (#279)
657
+ - **`useDefaults` wiring no longer collapses `<origam-code>`'s header zone.**
658
+ The origam base theme carried a `compact: true` default (authored in a bulk
659
+ pass before `useDefaults` was wired, never visually validated); once the
660
+ wiring landed it forced every code block without an explicit `compact` prop
661
+ into the condensed single-line pill layout, dropping the
662
+ header/filename/line-numbers/lang-badge. Removed from the base theme. (#249)
663
+ - **`OrigamBtnGroup` renders as a single rounded surface.** The group applies
664
+ its `rounded`/`elevation` on the outer wrapper with `overflow: hidden`
665
+ clipping children to the inner curvature — no forced child heights, no
666
+ inner-radius tokens; group height derives from `OrigamBtn`'s own border-box
667
+ height calc. (#239)
668
+ - **`OrigamSwitchTrack.error` widened from `boolean` to `string | boolean`**
669
+ to match the canonical Commons `IValidationProps`, so the parent's
670
+ validation surface is type-assignable again (was `vue-tsc` TS2345, CI red).
671
+ Consumed by truthiness only — behaviour unchanged. (#239)
672
+ - **`OrigamLayout` / `OrigamApp` `full-height` no longer clamps content
673
+ taller than one screen.** The full-height wrapper was `height: 100vh`
674
+ (a hard clamp) instead of `min-height: 100vh` ("occupy at least the
675
+ viewport"). Content overflowing past one screen still rendered (nothing
676
+ was clipped — no `overflow: hidden` on that element), but the wrapper's
677
+ own box never grew to match, so any ancestor sizing itself off that box
678
+ (e.g. a themed page background) stopped short of the actual page height.
679
+ `full-height` pages now grow naturally past one screen and the
680
+ background/box always matches the true content height.
681
+ - Fixed a handful of pre-existing `$type: "dimension"` component tokens
682
+ with non-numeric values (`auto`, `calc(...)`) that crashed the
683
+ `size/rem` Style Dictionary transform and silently blocked
684
+ `tokens:build` / the full package build (`btn.width`, `dialog.max-width`,
685
+ `dialog.max-height`, `contextual-menu.max-height`, `menu.max-height`,
686
+ `expansion-panel.header.append.margin-inline-start`,
687
+ `expansion-panel.popout.max-width(-active)`,
688
+ `expansion-panel.inset.max-width-active`) — retyped to `"other"`,
689
+ matching the established pattern already used for equivalent tokens
690
+ elsewhere in the same files. No visual/behavioural change; this only
691
+ unblocks the build pipeline.
692
+
693
+ > The 8 marketing brand themes (glass · cartoon · geek · apple · editorial ·
694
+ > material · ecom) that these hooks enable live in the private
695
+ > `@origam/marketing` package and are **not** part of the npm `origam`
696
+ > release — this changelog tracks the published library only.
697
+
698
+ ---
699
+
700
+ ## [2.8.1] — 2026-07-15
701
+
702
+ ### Fixed
703
+
704
+ - **The npm tarball now ships `README.md` and `LICENSE`.** The publish runs
705
+ from `packages/ds/` exclusively, but the package dir contained neither
706
+ file — both listed in the `files` array yet silently absent from every
707
+ published version (the npm page showed no readme at all). The new
708
+ npm-facing README covers install, quick start (plugin + token sheets +
709
+ Nuxt module), the theming model and the granular exports, and links to
710
+ the upcoming documentation site (https://origam.dev).
711
+
712
+ ---
713
+
714
+ ## [2.8.0] — 2026-07-15
715
+
716
+ ### Added
717
+
718
+ - **Border per-side props are now wired** (`useBorder`). The discrete
719
+ `borderTop` / `borderRight` / `borderBottom` / `borderLeft` props on
720
+ `IBorderProps` — declared but never read until now — each accept a
721
+ number (px), a boolean (legacy thin opt-in) or a free-form
722
+ `"width style color"` string (same grammar as the global `border`
723
+ shorthand). New per-side color overrides ship alongside:
724
+ `borderTopColor` / `borderRightColor` / `borderBottomColor` /
725
+ `borderLeftColor` (`TColor` — semantic intent or raw CSS color).
726
+ Precedence is specific-over-global (global `border`, then standalone
727
+ `borderColor` / `borderStyle`, then per-side width/style, then per-side
728
+ color) and documented on `useBorder`. Emitted declarations are physical
729
+ (`border-top-width`, …). `useStateEffect` forwards the 8 new props, so
730
+ Card, Sheet and every other consumer gets them for free. Pilot
731
+ component: Card (story + doc + e2e).
732
+ - **`useElevation` accepts a free-form custom `box-shadow` value.**
733
+ `elevation` (new `TElevation` type) still takes a named origam rung or
734
+ a Material 0–24 number, and now also any shadow-like CSS string
735
+ (`var()` / `calc()` / `rgba()` / hex / length / `inset` signals),
736
+ emitted verbatim as `box-shadow`. Previously a custom string silently
737
+ produced NO shadow (`parseInt` read a leading `0` and resolved to the
738
+ `none` rung). Pilot component: Card (story + doc + e2e).
739
+
740
+ ### Changed
741
+
742
+ - **`OrigamBlockquote`: `bgColor` renamed to `accentColor` (non-breaking).**
743
+ `bgColor` never painted a surface fill on Blockquote — it drove the
744
+ decorative accent (bar, background quote glyph, author label), which the
745
+ `bgColor` name misrepresented. `accentColor` is now the canonical prop;
746
+ `bgColor` keeps working as a deprecated alias (`accentColor` wins when
747
+ both are set) and logs a console warning once per session. Scope: this
748
+ pass only touches Blockquote — `bgColor` stays canonical and
749
+ non-deprecated on surface-fill components (Btn, Card, Chip, Badge,
750
+ Alert, Pagination, …). Removal of the `bgColor` alias on Blockquote is
751
+ targeted for v3.0.0. See `ROADMAP.md` — "Renommer `bgColor` →
752
+ `accentColor`".
753
+
754
+ ### Fixed
755
+
756
+ - **`useMargin`: 2-value strings work again.** `margin="8px 16px"` silently
757
+ produced no styles (the 2-value case was missing from
758
+ `formatMarginStylesVar`; padding already had it). Both utils now document
759
+ the intentional 4-value order convention: values are grouped by logical
760
+ axis — `block-start`, `inline-start`, `block-end`, `inline-end`
761
+ (top / left / bottom / right) — NOT the native CSS clockwise shorthand.
762
+ RTL-safe by design, arbitrated in #216.
763
+
764
+ ### Internal
765
+
766
+ - Marketing `/theming` — the Theme Builder ships six rich, validated
767
+ controls built from origam components (color/accentColor with intents +
768
+ custom picker, density, rounded with per-corner link/unlink, elevation
769
+ presets + full custom box-shadow, border with per-side width AND color
770
+ link/unlink, padding/margin devtools-style box-model with axis linking).
771
+ - Marketing playground isolation: the `origam` preview theme now carries a
772
+ complete GENERATED reset of every component var (~2700/mode, derived from
773
+ the DS baseline sheets) — ambient brand themes can no longer leak into
774
+ the playground preview.
775
+ - Marketing package restructured to the project architecture (`src/`
776
+ srcDir, locales under `src/assets/locales`, wireframes under
777
+ `wireframes/`).
778
+
779
+ ---
780
+
781
+ ## [2.7.3] — 2026-07-10
782
+
783
+ ### Fixed
784
+
785
+ - **`OrigamField`: the `rounded` prop now drives the whole field chrome.**
786
+ The prop used to round the outer box only (inline `border-radius`), while
787
+ the inner chrome (outline legs, per-corner radii) reads
788
+ `--origam-field---border-radius` — which the prop never touched, so the
789
+ outline corners stayed at the default and mismatched the box (themes had
790
+ to force the var with `!important` hacks). The field now mirrors the
791
+ radius resolved from the `rounded` prop into its component var, keeping
792
+ box and outline in sync for every field type (text, select, textarea,
793
+ number, password, date, file, color). `shaped` / `shaped-invert` are
794
+ unaffected and stay SCSS-owned.
795
+
796
+ ---
797
+
798
+ ## [2.7.2] — 2026-07-09
799
+
800
+ ### Fixed
801
+
802
+ - **Default text (and every `currentColor` icon) rendered browser-black in
803
+ dark mode.** The semantic text token flips correctly
804
+ (`neutral-900 → neutral-50`) and `app.json` has declared
805
+ `app.color` / `app.background-color` all along — but nothing consumed
806
+ them. `OrigamApp` now paints the base pair
807
+ (`color: var(--origam-app---color)`,
808
+ `background-color: var(--origam-app---background-color)`), and
809
+ `OrigamThemeProvider` (`display: contents`) sets the inherited `color`
810
+ so a local `data-mode="dark"` sub-tree gets readable defaults too.
811
+ Verified in both modes (computed styles) with no light-mode change.
812
+
813
+ ---
814
+
815
+ ## [2.7.1] — 2026-07-09
816
+
817
+ ### Fixed
818
+
819
+ - **`origam/styles` CSS entry shipped empty.** `src/assets/css/main.css` was
820
+ 0 bytes in every published version (2.6.x, 2.7.0), so
821
+ `import 'origam/styles'` (the `style`/`import`/`require` conditions)
822
+ delivered no tokens and no utility classes — only the SCSS path
823
+ (`@use 'origam/styles'`) worked. `main.css` is now compiled from
824
+ `main.scss` at build time (`styles:build`): the full aggregate
825
+ (primitives + light + dark + utilities + reset, ~366 KB) ships in the
826
+ package.
827
+
828
+ ### Internal
829
+
830
+ - New brand identity: the "cube sonobe" logo (modular origami — identical
831
+ folded units assembling into a structure) replaces the legacy mark across
832
+ the repo (docs/stories/marketing assets; not part of the npm tarball).
833
+ - npm publishing is handled by CI again (release workflow auth fixed).
834
+
835
+ ---
836
+
837
+ ## [2.7.0] — 2026-07-08
838
+
839
+ ### Added
840
+
841
+ - `OrigamThemeProvider` now applies the active theme's **component default
842
+ props** to its sub-tree (props-first theming cascades into sub-trees via
843
+ `provideDefaults`), not just the `data-theme` / `data-mode` attributes. A
844
+ `<origam-theme-provider theme="brand-x">` wrapper therefore gives every
845
+ descendant that brand's per-component defaults.
846
+ - Glass theme: `OrigamAlert` glassmorphism (transparency + blur).
847
+
848
+ ### Changed
849
+
850
+ - **`shiki` is now an OPTIONAL peer dependency** (removed from `dependencies`).
851
+ `OrigamCode` degrades gracefully to plain, un-highlighted code when `shiki`
852
+ is not installed (a one-time console warning is emitted; line numbers,
853
+ highlight-lines and copy keep working). **Migration:** add `shiki` (`^4.3.1`)
854
+ to your app's dependencies to keep syntax highlighting. This drops a heavy
855
+ (~3 MB) hard dependency from apps that don't render code blocks.
856
+
857
+ ### Fixed
858
+
859
+ - `OrigamPagination` colored mode (`color` / `bgColor`) now fills the page
860
+ buttons. The theme's default `text` btn variant
861
+ (`background-color: transparent !important`) was swallowing the intent fill,
862
+ leaving resting buttons transparent; the row now forces the `flat` variant
863
+ when a colour is set.
864
+ - Cartoon dark `/theming`: fields adopt the theme and the primary btn contrast
865
+ is corrected.
866
+
867
+ ### Internal
868
+
869
+ - Restored the green `vue-tsc` type-check gate under TypeScript 6.
870
+ - E2E suite stabilised: sharded CI plus deterministic fixture-drift fixes
871
+ (variant-index / navigation realignment) — the "flaky" bucket was
872
+ deterministic drift, not timing.
873
+
874
+ ---
875
+
876
+ ## [2.6.3] — 2026-07-03
877
+
878
+ ### Changed
879
+
880
+ - Internal code-quality cleanup to green the SonarQube quality gate
881
+ (`new_violations`): removed redundant type assertions, merged duplicate
882
+ imports, extracted nested template literals, `||` → `??`, dropped a redundant
883
+ `| undefined`, converted a call-signature interface to a function type, and
884
+ removed a leftover CSS comment. **No API or runtime behaviour change** — pure
885
+ refactor (PR #87).
886
+ - Marketing site (private, not part of the published `origam` package):
887
+ `@nuxtjs/i18n` 9 → 10 and `@nuxtjs/seo` 2 → 5 (locales moved to `i18n/locales/`;
888
+ v10 config breaking changes handled) — PR #88.
889
+
890
+ ---
891
+
892
+ ## [2.6.2] — 2026-07-03
893
+
894
+ ### Changed
895
+
896
+ - Dependency maintenance (Dependabot): TypeScript 5.8 → 6.0, ESLint 9 → 10
897
+ (+ `typescript-eslint`, `eslint-plugin-vue`), Style Dictionary 4 → 5
898
+ (+ `@tokens-studio/sd-transforms` 1 → 2), Shiki 3 → 4 (VitePress docs), and a
899
+ dev-tooling batch (`jsdom` 25 → 29, `happy-dom`, `sass`, `@types/node`,
900
+ `unplugin-vue-components`, `lint-staged`, `globals`…). **Vite and Vue stay
901
+ pinned** (`vite ~7.3.6`, `vue 3.5.35`) — their bumps were declined (Vite ≥ 7.3.5
902
+ broke the build; the Vue override neutralises the bump). `react-dom` bump
903
+ closed (not a dependency of a Vue design system).
904
+
905
+ ### Fixed
906
+
907
+ - CI publish (`release.yml`): the npm publish step failed with **E404** because
908
+ `pnpm publish` did not expand the `${NODE_AUTH_TOKEN}` placeholder from
909
+ setup-node's `.npmrc` → the PUT went out unauthenticated. The resolved token is
910
+ now written to the `.npmrc` pnpm reads before publish (PR #82). 2.5.0/2.5.1/2.6.0
911
+ had failed here; 2.6.1 was published manually as a stop-gap.
912
+ - Test robustness: `transition.composable.spec.ts` used an invalid CSS sentinel
913
+ (`transformOrigin: 'original'`) that jsdom 29 now rejects; switched to a valid
914
+ value `top left` (PR #84).
915
+
916
+ ---
917
+
918
+ ## [2.6.1] — 2026-07-02
919
+
920
+ ### Added
921
+
922
+ - `OrigamCode` — `compact` and `prompt` display modes (commit `5ebc6702`).
923
+ Compact mode collapses the component to a single line (no gutter, no
924
+ filename bar); `prompt` mode renders a shell-style `$` prefix. Two-axis
925
+ theming support: the component now tracks both `data-theme` and
926
+ `data-mode` on the host `<html>` element so it correctly switches in
927
+ dark/light sub-trees (`fix(ds): OrigamCode two-axis theming` — commit
928
+ `e2e716f2`). Scroller fill repaired in the same pass.
929
+ - `backdrop-filter` glassmorphism tokens on `Card` / `Sheet` / `Toolbar` /
930
+ `Menu` — new token group `component.{card,sheet,toolbar,menu}.backdrop-filter`
931
+ emitted as `--origam-{cmp}---backdrop-filter` CSS vars. Allows consumers
932
+ to build frosted-glass surfaces without overrides (commit `71647594`).
933
+
934
+ ### Changed
935
+
936
+ - Brand themes removed from the DS (ADR-004) — the `origam` package now
937
+ ships only the `light` and `dark` base token sets. Consumer themes
938
+ (`sobre`, `geek`, `glass`, `cartoon`, `apple`, `ecom`, `editorial`,
939
+ `material`) are now authored in semantic JSON and installed via
940
+ `createOrigam({ themes: [...] })` rather than bundled into the lib.
941
+ The DS stays unopinionated; themes travel with the consumer
942
+ (commits `fbea1e3f`, `5f1bf51c`, `e0a47050`, `edc30e80`, `f7c56f06`,
943
+ `3b9f8510`).
944
+
945
+ ### Fixed
946
+
947
+ - `OrigamImg` — `markBooted` `requestAnimationFrame` callback was called
948
+ during SSR, crashing server-render (`fix(ds): guard OrigamImg markBooted
949
+ rAF behind IN_BROWSER` — commit `6b09c5df`).
950
+ - Six SSR/theming gaps surfaced by the marketing site refactor: theme cookie
951
+ injection race, `data-mode` missing on first SSR render, component-level
952
+ token var scope, active-theme guard in `useTheme`, Menu/Tooltip token
953
+ resolution under sub-tree providers, Nuxt hydration mismatch on theme
954
+ toggle (commit `b9fe3219`).
955
+ - `OrigamSelect` — the chevron icon opened then instantly re-closed the menu
956
+ on the very first click (field not yet focused): the opening `mousedown`
957
+ bubbled to the control handler and double-toggled. The toggle now only
958
+ runs when the field is already focused; otherwise the event bubbles to the
959
+ single control handler that opens + focuses in one pass (PR #70).
960
+ - Sass `mixed-decls` deprecation warnings eliminated in `OrigamBtn` and
961
+ `OrigamSwitchTrack` — declarations that followed a nested rule (`@media` /
962
+ `&:focus-visible`) are moved above it. Pure reorder, identical compiled CSS
963
+ (PR #73).
964
+ - Layout offset counted twice in SSR / production builds (invisible in dev):
965
+ Vue's hydration-mismatch recovery could abandon a layout item mid-`setup()`
966
+ after it registered, leaving an orphan in `registered` so a 240 px drawer
967
+ reserved 480 px. `useLayoutItem`'s `register()` now evicts stale entries
968
+ occupying the same `(order, position)` slot (PR #76).
969
+
970
+ ---
971
+
972
+ ## [2.6.0] — 2026-06-11
973
+
974
+ > **The bracket + a11y + theming release.** `OrigamBracket` ships as a
975
+ > production-ready e-sport tournament component (double-elimination, Grand
976
+ > Final, statuses, full cross-cutting prop surface). `v-contrast` becomes
977
+ > the DS-wide WCAG text-legibility guard applied to every colour-bearing
978
+ > component. A two-axis theming engine (`data-theme` × `data-mode`) replaces
979
+ > the previous single-axis approach and enables brand themes to be installed
980
+ > at runtime via `createOrigam()`. Multiple composables and layout components
981
+ > also land fixes. 170 commits total in this range (DS-relevant subset
982
+ > documented below).
983
+
984
+ ### Added
985
+
986
+ - `v-contrast` directive — runtime WCAG 2.1 AA text-legibility guard
987
+ (`feat(ds): v-contrast directive` — commit `8a80787a`). Applied
988
+ automatically to every colour-bearing component in the same cycle
989
+ (`feat(ds/a11y): apply v-contrast to all colour-bearing components` —
990
+ commit `4a4d4473`). Two correctness fixes landed before release: correct
991
+ WCAG luminance maths for composite translucent backgrounds (commit
992
+ `96ff45c4`), and `color(srgb …)` token format support + guard against
993
+ clobbering an explicit `color` prop (commits `34786ed5`, `12a3e378`).
994
+ - `OrigamBracket` — **major enrichment** on top of the 2.3.0 initial
995
+ component. Full double-elimination layout with two independent trees +
996
+ a Grand Final match (`feat(ds/bracket): real two-tree double-elimination
997
+ layout` — commit `dee1c23d`; Grand Final — commit `560eaf5b`).
998
+ Full cross-cutting prop surface on both `OrigamBracketMatch`
999
+ (`rounded` / `elevation` / `border` / `color` / `bgColor` — commit
1000
+ `8998f0e3`) and `OrigamBracketCompetitor` (commit `3088ad31`).
1001
+ Distinct status indicators (live, forfeit) + live-link prop (commit
1002
+ `4d38c65c`). Connector trait inherits match `border-width` / `style` /
1003
+ `color` (commit `94242ab3`). State variant (hover / active) wired
1004
+ (commits `0f4d5924`, `5edeca52`, `92edcd1b`). Auto-contrast on match
1005
+ text and seed numbers via `v-contrast` (commit `4cd5bc44`).
1006
+ `OrigamDivider` used internally for the match divider (commit `c472bb71`).
1007
+ - `OrigamBlockquote` — **major enrichment** from the initial Wave 4 stub.
1008
+ Two-axis colour model: `color` (foreground / accent) and `bgColor`
1009
+ (surface / left-bar background) applied on all 5 variants
1010
+ (`feat(ds/blockquote): two-axis colour model` — commit `ccfe8c82`).
1011
+ `bgColor` accent on every variant including `elegant` and `minimal`
1012
+ (commit `b2f5c1f3`). `color` also drives the source label (commit
1013
+ `d80e88ca`). Accent pseudo-element decoupled from the `border` prop
1014
+ (commit `c5843ee1`, revert + re-land in same release).
1015
+ - `OrigamBottomNav` — three additions: dimension props (`width`, `height`,
1016
+ etc.) now apply (fix — commit `21d44f63`); `position` prop
1017
+ (`start | center | end`) controls item distribution (commit `395a797e`);
1018
+ `active` state exposed and diffused to the child buttons (commit
1019
+ `5bdb3e85`).
1020
+ - `OrigamAvatarGroup` — three additions: `rounded`, `elevation`, `border`
1021
+ propagated to all child avatars (commit `725303bd`); `hover` / `active`
1022
+ state fan-out made reactive (commit `e163cfba`); click-outside collapses
1023
+ an expanded group (commit `c364aae3`).
1024
+ - `OrigamAppbar` — `scroll-behavior="active"` engages the active surface
1025
+ state on scroll (commit `07feb981`). `hide` and `inverted` scroll
1026
+ behaviours repaired; `scrollBehavior` now accepts combined tokens
1027
+ (commits `01912f4c`, `b625ea1e`). Removed dead props: `absolute`
1028
+ (commit `50c5ea32`), `floating` (commit `3d707ef5`), `width` /
1029
+ `minWidth` / `maxWidth` (commit `c5e7a6b9` — layout owns the
1030
+ cross-axis).
1031
+ - Two-axis theming engine — `data-theme` (brand identity) × `data-mode`
1032
+ (light/dark) replace the previous single `data-theme` approach. Shadow
1033
+ tokens gain per-theme variants (commit `f7c56f06`). `createOrigam()`
1034
+ gains a `theme` option to install one or multiple presets at init time,
1035
+ SSR no-flash (commits `edc30e80`, `5f1bf51c`, `3b9f8510`). Token
1036
+ authoring via semantic JSON (colors, radius, typography, shadow, spacing,
1037
+ animation groups) — `feat(ds): semantic JSON theme authoring` (commit
1038
+ `e0a47050`).
1039
+ - Field validation surface extended to 5 additional field components
1040
+ (`InlineEdit`, `OtpInputField`, `Clipboard`, `NumberField`,
1041
+ `SliderField`) via unified `useValidation` wiring (commit `27977831`).
1042
+ - `OrigamStatus` — forces its intent onto `color` / `bgColor` (non-
1043
+ overridable) so status badges are always correctly painted regardless of
1044
+ surrounding theme (commit `8a72740a`).
1045
+
1046
+ ### Changed
1047
+
1048
+ - `OrigamAudio` — dimension props (`width`, `height`) now apply (commit
1049
+ `adaf4395`); content fills the available height with controls pushed
1050
+ to the bottom (commit `97207e26`); progress row grows when the bar is
1051
+ taller (commit `8f8f1c1f`); `rounded="none"` and shaped variants added
1052
+ (commit `073d0513`). Compact disc rendering fixed — oversized with
1053
+ floating grooves (commit `bef36e20`). Reactive `autoplay` (commit
1054
+ `0dc90fea`). Play-button focus tint lingering + playlist active item
1055
+ blinking fixed (commit `6e6042bb`).
1056
+ - `OrigamToolbar` — `hover` / `active` now drive the surface colour
1057
+ (state-aware) (commit `c966e9c6`).
1058
+ - `OrigamAlert` — removed redundant `prominent` prop (commit `f32af0a8`).
1059
+ - `OrigamAvatar` — `color` prop now applies; `v-contrast` was previously
1060
+ overriding an explicit `color` (commit `369e0afa`). Dead `start` / `end`
1061
+ props removed (commit `84f324fe`).
1062
+ - `OrigamApp` — now exposes only `color` / `bgColor` and forwards them to
1063
+ the layout (commit `111f6596`).
1064
+ - DS-wide: `useStateEffect` repaired — runtime prop changes for
1065
+ `color` / `bgColor` and similar were silently ignored after initial
1066
+ mount (commit `a1a66120`). Reactive-update loops in `AvatarGroup` and
1067
+ `RadioGroup` killed (commit `4c5eb18d`).
1068
+ - Monorepo migration — `packages/ds`, `packages/marketing`,
1069
+ `packages/stories`, `packages/docs`, `packages/tests`,
1070
+ `packages/figma-plugin` are now first-class pnpm workspace packages
1071
+ (commits `4d53558b`, `70f1819f`, `fa0ed997`).
1072
+
1073
+ ### Fixed
1074
+
1075
+ - `OrigamBtn` — `border-color` and `border-style` are now customisable
1076
+ props (commit `b44add69`).
1077
+ - `OrigamField` — themed `border-radius` now resolves via the real
1078
+ component token (commit `851b2a2f`).
1079
+ - `OrigamExpansionPanels` — `useElevation` import missing, causing
1080
+ `ReferenceError` at mount (commit `2eef9288`).
1081
+ - `OrigamDatePicker` — crash guard for empty `daysInMonth` when reading
1082
+ `date` prop (commit `896b9eec`).
1083
+ - `OrigamBorder` — numeric `border-width` values were invisible (missing
1084
+ `border-style` and `border-color` defaults) (commit `e9a151ec`).
1085
+ - `rounded="none"` — did not actually remove the border radius; affected
1086
+ `OrigamAudio` and other consumers (commit `073d0513`).
1087
+ - `fix(ds): /* @vite-ignore */ on shiki dynamic import` — defensive guard
1088
+ to silence the Vite bundler warning on the `OrigamCode` lazy highlighter
1089
+ import (commit `f6bbc494`).
1090
+ - DS/marketing integration — MDI class prefix resolution, search hotkey
1091
+ double-fire, Menu anchor positioning (commit `d113a245`); lib dist
1092
+ missing CSS in first production deploy (commit `62667fbc`).
1093
+
1094
+ ---
1095
+
1096
+ ## [2.5.1] — 2026-05-27
1097
+
1098
+ > **Patch.** Two housekeeping commits with no public API impact.
1099
+
1100
+ ### Changed
1101
+
1102
+ - Repository structure cleaned up: unit specs moved to `tests/TU/`,
1103
+ the dev playground dropped (commit `52f7dc70`).
1104
+ - `.gitignore` updated to exclude `tests/a11y/.report` and
1105
+ `tests/a11y/.results` artefacts (commit `6c2ef153`).
1106
+
1107
+ ---
1108
+
1109
+ ## [2.5.0] — 2026-05-24
1110
+
1111
+ > **The accessibility release.** WCAG 2.1 AA pass across the entire
1112
+ > component catalogue, critical backlog items (Select combobox pattern,
1113
+ > DataTable caption, ColorPicker keyboard), and a VitePress sidebar
1114
+ > reorganised by UI taxonomy.
1115
+
1116
+ ### Fixed
1117
+
1118
+ - WCAG 2.1 AA pass — 35 targeted fixes across 36 components covering:
1119
+ missing `aria-label` on icon-only buttons, incorrect `role` assignments,
1120
+ focus management gaps, colour-contrast warnings on default token values,
1121
+ and keyboard-navigation holes in Slider / Rating / Switch / Radio
1122
+ (commit `1ad0aeaf`).
1123
+ - Critical a11y backlog:
1124
+ - `OrigamSelect` — full combobox ARIA pattern (`role="combobox"` +
1125
+ `aria-expanded` + `aria-activedescendant` + `aria-autocomplete`).
1126
+ - `OrigamDataTable` — `<caption>` element added so screen readers
1127
+ announce the table purpose.
1128
+ - `OrigamColorPicker` — keyboard navigation restored; tab order and
1129
+ focus ring corrected.
1130
+ - A11y test infrastructure wired (commit `b3dd8552`).
1131
+ - VitePress docs — SSR crash in the build resolved; chart legend guard
1132
+ against undefined series data (commit `6cfb0029`).
1133
+
1134
+ ### Changed
1135
+
1136
+ - VitePress component sidebar reorganised by UI taxonomy (layout /
1137
+ navigation / data-display / forms / feedback / utility) instead of
1138
+ flat alphabetical order (commit `0bd5e2f2`).
1139
+
1140
+ ---
1141
+
1142
+ ## [2.4.0] — 2026-05-23
1143
+
1144
+ > **The chart engine + media kit + Wave 4 release.** In-house chart
1145
+ > engine (27 primitives, 19 families, pure SVG, zero external dep),
1146
+ > atomic media kit (`OrigamAudio` with waveform / vinyl / stem-tracks /
1147
+ > playlist, `OrigamVideo` YouTube-style player, `OrigamMediaScrubber`
1148
+ > and `OrigamMediaController` primitives), native `SliderField`, six
1149
+ > utility / content components (`OrigamGrid`, `OrigamMasonry`,
1150
+ > `OrigamEmptyState`, `OrigamClipboard`, `OrigamInlineEdit`,
1151
+ > `OrigamNumberFormat`), `OrigamWatermark`, `OrigamQRCode`,
1152
+ > `OrigamCalendar`, `OrigamTextMask`, gradient support on color props,
1153
+ > and a DS-wide reuse-interfaces audit. 149 commits.
1154
+
1155
+ ### Added
1156
+
1157
+ #### Chart engine
1158
+
1159
+ - `OrigamChart` + `useChart` — in-house chart component. 8 base types
1160
+ (`line` / `area` / `bar` / `column` / `pie` / `donut` / `scatter` /
1161
+ `radar`). Pure SVG rendering (no canvas, no `d3`, no `chart.js`, no
1162
+ `echarts`). Tooltip inline-positioned via mouse move; legend as
1163
+ `<ul role="list">` with click-to-toggle series visibility. Animated
1164
+ entrance with `prefers-reduced-motion` respect. Custom slots for
1165
+ `tooltip` / `legend-item` / `title` / `empty`. ARIA `role="img"` with
1166
+ `<title>` + `<desc>`; every data point is `tabindex="0"` +
1167
+ `role="button"` with a descriptive `aria-label`. Pure-function SVG
1168
+ path utilities — SSR-safe. `tokens/component/chart.json` (title,
1169
+ subtitle, axis, grid, tooltip, legend, point, bar, pie, radar,
1170
+ animation groups). (commit `b46541ce`)
1171
+ - Family split into 9 per-type components (`OrigamChartCartesian`,
1172
+ `OrigamChartPolar`, `OrigamChartRadar`, `OrigamChartGauge`, …) sharing
1173
+ the engine via `OrigamChartAxis` and `OrigamChartLegend` sub-components
1174
+ (commits `4da143c9`, `c950c364`, `ef4ab360`).
1175
+ - 15 additional chart families extending the base engine:
1176
+ - **Honeycomb / Treemap / Sankey / Word-cloud** (commit `94902edc`)
1177
+ - **Heatmap / Sunburst / Box-plot / Pictorial** (commit `c1e69d60`)
1178
+ - **Candlestick / Streamgraph** (commit `c5edc9b0`)
1179
+ - **Pyramid / Funnel** family with outside labels + leader lines on
1180
+ narrow bands (commits `445acb05`, `f4af7a27`, `293905bc`)
1181
+ - **Polar-bar / Variwide** (commit `26380205`)
1182
+ - **Pareto / Bullet** (commit `39f141a3`)
1183
+ - **Combination chart** (line + column / area + line) (commit
1184
+ `4406e92d` area, `10d012ad` bar-axis swap)
1185
+ - **Map / Choropleth / Flight-routes / Plot bands** (commit `c728bd94`,
1186
+ `6e72e7f4`)
1187
+ - **Stacked percent / Multi-axis Y** (commit `6e72e7f4`)
1188
+ - **Drilldown** (commit `8f827e7d`)
1189
+ - **Sparkline / Zoom-pan / RangeSelector** (commit `23d97e29`)
1190
+ - **Annotations** (arrows, label callouts, circle highlights, brackets)
1191
+ (commit `e1e41b89`)
1192
+ - Chart composables wired to full DS cross-cutting prop surface
1193
+ (`IDimensionProps` via `useDimension`, `margin`, `padding`, `rounded`,
1194
+ `elevation`, `bgColor`) (commits `7179cd6c`, `e16742e0`).
1195
+ - Scatter chart: `z` dimension drives bubble radius (commit `2eb6c868`).
1196
+ - Pie / donut: multi-series concentric rings; per-slice legend toggle
1197
+ (commit `ba51275b`).
1198
+ - DS intent colours and custom CSS colours resolve to the correct token
1199
+ namespace in charts (commit `8166cd1e`).
1200
+ - 7 canonical transverse emit interfaces (`IChartEmits`, etc.) added
1201
+ (commit `e1c7b890`).
1202
+ - DS-wide typed `defineEmits` migration — 7 batches covering 54+
1203
+ components (commits `01381e41`, `a03627a8`, `ce40e686`, `2ce1ccd2`,
1204
+ `3347bc58`, `279fb2f0`, `e91c9518`).
1205
+
1206
+ #### Media kit
1207
+
1208
+ - `OrigamAudio` + `useAudioPlayer` + `useWaveform` — in-house audio
1209
+ player. Custom UI (play/pause, scrubber, volume, waveform via
1210
+ `OfflineAudioContext`). Media Session API for lock-screen controls +
1211
+ metadata. Cover image. 3 controls modes. Vinyl disc animation. Stem-
1212
+ tracks (multi-channel mute/solo). Playlist with tri-state loop and
1213
+ random shuffle. Zero external dep. ARIA: dynamic `aria-label`,
1214
+ `role="slider"` on scrubber, `role="img"` on waveform. SSR-safe.
1215
+ `tokens/component/sound.json` (commits `293efc06`, `d3781288`,
1216
+ `9c12e283`, `f3d3c797`). Waveform + album ported from `OrigamSound`
1217
+ (commit `d5a0246b`). `useWaveform` moved to Audio namespace (commit
1218
+ `d4822f26`). `<OrigamSound>` namespace retired — superseded by
1219
+ `<OrigamAudio>` (commit `a77911bc`).
1220
+ - `OrigamVideo` + `useVideoPlayer` — in-house video player. YouTube-style
1221
+ restructure: custom UI (play/pause, scrubber, volume, fullscreen, PIP,
1222
+ captions), 3 controls modes, WebVTT captions with language switcher,
1223
+ `aspect-ratio` preset, download prop + quality switcher in cog menu,
1224
+ YouTube-style skip ripple (half-disk + chevrons stagger). Zero external
1225
+ dep. ARIA: `role="slider"` on scrubber, `role="status"` / `role="alert"`
1226
+ on state overlays. SSR-safe. `tokens/component/video.json` (commits
1227
+ `8a37102e`, `97420a99`, `31dcfdae`, `f50ccbdb`, `dcccb4dd`, `0208b2f3`,
1228
+ `9bbbea96`, `75b0779f`, `80e2a9c0`).
1229
+ - `OrigamMediaScrubber` — reusable horizontal + vertical media scrubber
1230
+ primitive (thin track, buffer indicator, hover thumb + tooltip). Used
1231
+ by both `OrigamAudio` and `OrigamVideo` for their respective scrubber
1232
+ and volume controls (commits `9f87c917`, `9e050390`, `15d53094`).
1233
+ - `OrigamMediaController` — shared media-control shell extracted from
1234
+ `useVideoPlayer` (commit `2cbb9618`). `useMediaPlayer` base composable
1235
+ split out (commit `c2a9f282`).
1236
+
1237
+ #### New utility / content components
1238
+
1239
+ - `OrigamGrid` + `OrigamGridItem` — declarative CSS Grid wrapper. Props
1240
+ for `columns` / `rows` / `areas` / `gap` (token or raw CSS) /
1241
+ `autoFlow` / `align*` / `justify*` and item-level `column` / `row` /
1242
+ `area` / `*Self` shorthands. Object syntax (`{ start, end }`) or raw
1243
+ CSS strings (`'1 / 5'`, `'span 2'`) for span control. Token group
1244
+ `--origam-grid---gap-{xs,sm,md,lg,xl}` (commit `0ebb1cd4`).
1245
+ - `OrigamMasonry` + `useMasonry` — Pinterest-style masonry layout.
1246
+ CSS-first via `grid-template-rows: masonry` (detected via `useCssSupport`
1247
+ — new `masonry` flag). JS bucket-fill fallback with `ResizeObserver`.
1248
+ Responsive columns via container-query breakpoints. `animated` prop.
1249
+ `tokens/component/masonry.json` (`animation-duration`,
1250
+ `animation-easing`) (commit `2691c4e5`).
1251
+ - `OrigamEmptyState` — placeholder for absent data. 5 presets
1252
+ (`no-data` / `no-results` / `error` / `offline` / `locked`) with auto
1253
+ icon + intent mapping. Three sizes, two alignments. ARIA `role="status"`
1254
+ + `aria-live="polite"`. `tokens/component/empty-state.json` (commit
1255
+ `abaca302`).
1256
+ - `OrigamClipboard` + `useClipboard` — copy-to-clipboard helper.
1257
+ `navigator.clipboard.writeText` + `execCommand` fallback. Scoped
1258
+ `#default` slot exposes `{ copy, copied, error }`. Auto-resetting
1259
+ feedback state. ARIA `aria-live="polite"`. SSR-safe.
1260
+ `tokens/component/clipboard.json` (commit `d93c2957`).
1261
+ - `OrigamInlineEdit` + `useInlineEdit` — edit-in-place pattern. Click
1262
+ display → input prefilled → Enter confirms / Escape cancels. Async
1263
+ `validate` callback. Multiline textarea mode. Custom slots
1264
+ `#display` / `#edit` / `#actions`. ARIA `aria-invalid` +
1265
+ `aria-describedby`. `tokens/component/inline-edit.json` (commit
1266
+ `d4a95557`).
1267
+ - `OrigamNumberFormat` + `useNumberFormat` — i18n number formatting via
1268
+ `Intl.NumberFormat`. 7 formats, full locale support with auto-resolution
1269
+ chain, LRU-cached `Intl` instances. Scoped `#default` slot exposes
1270
+ `{ formatted, parts, value }`. ARIA `aria-label` expansion for compact
1271
+ notation (commit `dd8bfe3c`).
1272
+ - `OrigamWatermark` + `useWatermark` — diagonal repeating overlay. SVG
1273
+ data-URL (no canvas). Text or image mode. Anti-tamper MutationObserver.
1274
+ `pointer-events: none`. `tokens/component/watermark.json`. SSR-safe
1275
+ (commit `cc715c38`).
1276
+ - `OrigamQRCode` + `useQRCode` — SVG QR code rendering via
1277
+ `qrcode-generator` (~5 kB). 4 ECC levels. Optional logo overlay.
1278
+ Rounded modules. LRU cache. ARIA `role="img"`. Renamed from
1279
+ `OrigamQRCode` → `OrigamQrCode` (Vue style guide — commits `9f5a4eea`,
1280
+ `eabb92a3`, `1ee2acf6`, `da34f20d`, multiple QrCode fixes).
1281
+ - `OrigamCalendar` + `useCalendar` — full calendar. 4 views
1282
+ (month / week / day / agenda), navigation, events with
1283
+ start / end / color / category, range select with drag-to-create,
1284
+ RRULE recurring events (`DAILY | WEEKLY | MONTHLY` + `INTERVAL` /
1285
+ `COUNT` / `UNTIL` / `BYDAY`). Toolbar with view switcher. Locale-aware
1286
+ via `Intl.DateTimeFormat`. Zero external dep. ARIA
1287
+ `application / gridcell / toolbar` + keyboard navigation. SSR-safe
1288
+ (commit `8b5d7f0a`).
1289
+ - `OrigamTextMask` + gradient color props — text reveals an animated
1290
+ background via `background-clip: text`. 4 animation types
1291
+ (`pan` / `rotate` / `pulse` / `zoom`) with `prefers-reduced-motion`.
1292
+ Zero JS (pure CSS keyframes). SSR-safe (commit `31f67d37`).
1293
+ - Gradient support for `color` / `bgColor` / `textColor` props. Three
1294
+ input formats: raw CSS gradient string, `IGradient` structured object
1295
+ (`{ from, to, via?, direction?, type? }` or `{ stops: [...] }`), or
1296
+ preset name (`color="gradient-sunset"`). 5 built-in semantic presets
1297
+ (`sunset`, `ocean`, `forest`, `fire`, `midnight`) with light + dark
1298
+ variants. 100 % backward-compatible (commit `c922fbff`).
1299
+ - `SliderField` — native HTML `<input type="range">` implementation
1300
+ (commit `d3781288`).
1301
+
1302
+ ### Changed
1303
+
1304
+ - DS-wide reuse-interfaces audit — 9 batches (commits `d0afd731`,
1305
+ `2c6e3aab`, `325c4df1`, `5f1abaf8`, and earlier in the cycle).
1306
+ Every component that declared `height` / `width` / `margin` / etc.
1307
+ inline now `extends IDimensionProps` / `IMarginProps` / etc. and
1308
+ consumes the matching composable. Eliminates half-implemented
1309
+ surfaces and drift from the standard `convertToUnit` helper.
1310
+ - All ~28 inline composable constants extracted to `src/consts/`
1311
+ (commits `7cd6ff81`, `de48f7da`).
1312
+ - Story Playground variants universally renamed to `Default` across all
1313
+ 177 stories (commits `cfeaa682`, `ace53342`, `f2e89284`).
1314
+
1315
+ ### Fixed
1316
+
1317
+ - `OrigamChart` — area fill previously overridden by the SCSS
1318
+ `fill: none` cascade (commit `ed1c4610`); scatter points overflowing
1319
+ the plot zone (commit `ba51275b`); horizontal bar — category/value
1320
+ axis swapped (commit `f8ec65db`); legend click filtered wrong series
1321
+ index (commit `d25eae00`); `legendPosition` not moving the legend
1322
+ (commit `082d053b`); column/bar slots overlapping value axis on first
1323
+ index (commit `4406e92d`); stepped-line tail cut off (commit
1324
+ `ee45357a`); pie/donut legend showing wrong labels (commit `4406e92d`).
1325
+ - `OrigamVideo` — double-click skip on desktop (`f50ccbdb`); controls bar
1326
+ clicks restored after overlay removal (`4fd5f375`); skip ripple sizing
1327
+ cascade (`80e2a9c0`, `9bbbea96`, `75b0779f`); cross-origin download
1328
+ via blob (`73c2e2a4`, `d84fa9bc`).
1329
+ - `OrigamCalendar` — Calendar loading shape + active toolbar button
1330
+ contrast (`55e58b3d`).
1331
+ - `OrigamQrCode` — intent-to-paint mapping, SVG host sizing, title
1332
+ centring (commits `c1823057`, `dceb4cfe`).
1333
+ - Link composable — `ComputedRef` for `tag` and `href` unwrapped in
1334
+ templates; previously caused a `[object Object]` render (commit
1335
+ `9822c47b`, reverted and re-landed as `3f79d0c0`).
1336
+ - `withDefaults()` inline literal rule enforced — `Grid`, `Masonry`,
1337
+ `Blockquote` were referencing `XXX_DEFAULTS` objects statically
1338
+ unresolvable by the SFC compiler, causing `undefined` prop crashes
1339
+ (commit `276fff1e`).
1340
+ - `OrigamSnackbar` — `roundedClasses` `ReferenceError` at mount (commit
1341
+ `aa86f93c`).
1342
+
1343
+ ---
1344
+
1345
+ ## [2.3.0] — 2026-05-15
1346
+
1347
+ > **The features release.** Four new components, four major
1348
+ > enrichments, an official Nuxt module, and a comprehensive SSR
1349
+ > safety audit. All additions are backward-compatible — drop-in
1350
+ > upgrade from 2.2.x. 378 unit tests (+158 vs 2.2.1), 0 lint errors.
1351
+
1352
+ ### Added
1353
+
1354
+ - `origam/nuxt` sub-export — official Nuxt 3 / Nuxt 4 module. Auto-imports
1355
+ components and composables. SSR-safe theme resolution via cookie +
1356
+ `Sec-CH-Prefers-Color-Scheme` header (no FOUC, no hydration mismatch).
1357
+ Auto-injects token CSS files (primitive + selected themes + utilities).
1358
+ Configurable via `origam: {}` in `nuxt.config.ts`. Resolves through
1359
+ `modules: ['origam/nuxt']`. New `IOrigamNuxtModuleOptions` and
1360
+ `IOrigamNuxtRuntimeConfig` interfaces; new theme constants
1361
+ (`ORIGAM_THEME_AUTO`, `ORIGAM_THEME_LIGHT`, `ORIGAM_THEME_DARK`,
1362
+ `ORIGAM_THEME_ATTR`, `ORIGAM_THEME_STORAGE_KEY`). Reference
1363
+ documentation at `docs/integrations/nuxt.md`.
1364
+
1365
+ ### Changed
1366
+
1367
+ - `OrigamTextField` — new `mask` prop with built-in patterns
1368
+ (`phone:fr`, `phone:us`, `phone:international`, `iban`, `siret`,
1369
+ `creditcard`, `date:iso`, `date:fr`, `date:us`, `time`, `time:12h`,
1370
+ `postcode:fr`, `postcode:us`) plus a custom pattern syntax
1371
+ (`#` = digit, `A` = letter, `*` = any, anything else is a literal).
1372
+ In-house mask engine — zero external dependency (no `imask.js` /
1373
+ `cleave.js` / `vue-the-mask`). Reactive validation pipeline with
1374
+ built-in `luhn` (credit card), `iban` (mod-97) and date parsers
1375
+ (`date:iso` / `date:fr` / `date:us`); custom validators accepted as
1376
+ `(unmasked) => boolean`. New emits `@valid(boolean)` and
1377
+ `@complete({ complete, unmasked })` fire on every value change. The
1378
+ v-model exposes the **unmasked** value while the DOM input displays
1379
+ the formatted (masked) one — paste handling strips literals and
1380
+ reformats, `aria-invalid` toggles on touched fields, and the engine
1381
+ auto-promotes phone-shaped patterns to `type="tel"` for mobile
1382
+ keyboard hints. New `useMask` composable, `applyMask` / `unmaskValue`
1383
+ / `resolveMaskConfig` / `validatePattern` utils, new `IMaskOptions`
1384
+ interface and `TMask` / `TBuiltInPattern` / `TPatternValidator`
1385
+ types.
1386
+ - `OrigamTextareaField` — new `mode="rich"` enabling a lightweight
1387
+ HTML / Markdown editor based on `contenteditable`. Built in-house
1388
+ with zero external dependencies (no TipTap, ProseMirror, Quill).
1389
+ 9 toolbar commands (`bold`, `italic`, `underline`, `link`,
1390
+ `list-bullet`, `list-ordered`, `heading`, `code-inline`,
1391
+ `clear-format`), customisable via the new `toolbar` prop (or
1392
+ `toolbar: false` to hide). Keyboard shortcuts (Cmd/Ctrl+B/I/U/K/E
1393
+ and Cmd/Ctrl+Shift+7/8 for the two list modes). New `output` prop
1394
+ switches the v-model serialisation between `'html'` (sanitised) and
1395
+ `'markdown'` (CommonMark-flavoured subset). New `toolbarPosition`
1396
+ prop (`'top' | 'bottom' | 'floating'`). New `format` emit fired on
1397
+ every toolbar click or keyboard shortcut with the command id (and
1398
+ the URL for link insertion). New slots `#toolbar` and
1399
+ `#toolbar-item` allow replacing the default UI entirely. Internal
1400
+ sub-component `OrigamRichToolbar.vue` + composable
1401
+ `useTextareaRich` own the contenteditable contract. In-house HTML
1402
+ sanitiser with allowlist on tags (`p`, `br`, `strong`, `b`, `em`,
1403
+ `i`, `u`, `a`, `ul`, `ol`, `li`, `h1`, `h2`, `h3`, `code`),
1404
+ attributes (`href` restricted to `http:` / `https:` / `mailto:` /
1405
+ `tel:` plus relative URLs; `class` restricted to the
1406
+ `origam-rich--*` prefix), and stripping every `on*` event handler
1407
+ before per-tag filtering. External links are auto-hardened with
1408
+ `rel="noopener noreferrer nofollow" target="_blank"`. ARIA:
1409
+ `role="toolbar" + aria-label` on the toolbar, real `<button>` per
1410
+ command with `aria-label` + `aria-pressed`, the editing surface
1411
+ carries `role="textbox" + aria-multiline="true"`. New tokens under
1412
+ `component/textarea-field/rich-*` (toolbar surface, button states,
1413
+ content padding, inline-code colors, link color, heading sizes).
1414
+ The plain-mode (`mode="plain"`, default) API is fully
1415
+ backward-compatible — passing nothing keeps the previous
1416
+ `<textarea>` behaviour.
1417
+
1418
+ - `OrigamCode` — major enrichment via shiki integration. Syntax
1419
+ highlighting for 13 languages (vue, ts, js, tsx, jsx, scss, css, json,
1420
+ bash, html, xml, yaml, md) plus `plaintext`. New props: `lineNumbers`,
1421
+ `highlightLines` (accepts both `number[]` and the range syntax
1422
+ `'2,5-7'`), `copyable`, `maxHeight`, `theme` (`'auto' | 'light' | 'dark'`),
1423
+ `wrap`, `filename`. Copy button with `navigator.clipboard` and an
1424
+ `execCommand('copy')` fallback for legacy WebViews. Lazy-init shiki
1425
+ highlighter cached as a module-level singleton across instances; an
1426
+ LRU (max 64 entries) caches highlighted HTML by `(code, lang, theme)`
1427
+ so re-renders never re-tokenise. Theme defaults to `auto` and tracks
1428
+ the host `<html data-theme>` attribute. New `useCode` composable
1429
+ exposing `{ highlight, prime, isReady, resetCacheForTesting }`. New
1430
+ utility `parseHighlightLines()` shared with stories/tests. Pure-CSS
1431
+ line-numbers gutter (CSS counter, no JS layout) and line-highlight
1432
+ swap (class toggle on already-rendered rows, no re-tokenisation).
1433
+ ARIA: `role="region"` on the surface, `aria-live="polite"` on the
1434
+ copy feedback, hidden line numbers. `shiki` (`^3.8.1`) promoted from
1435
+ `devDependencies` to `dependencies` (it's a runtime dep now); the
1436
+ bundle adds ~3 MB to installed `node_modules` for the curated subset.
1437
+ New tokens under `component/code` (35 vars: surface, header, filename,
1438
+ copy button, line-number gutter, line-highlight, scrollbar). The v2.x
1439
+ plain-text `<pre>` API is fully backward-compatible — passing just
1440
+ `code` and `lang` keeps the previous behaviour.
1441
+
1442
+ - `OrigamParallax` — major enrichment. Multi-layer support via new
1443
+ `OrigamParallaxLayer` subcomponent (`speed`, `offsetX`, `offsetY`,
1444
+ `zIndex` props; layers register themselves into the host runtime and
1445
+ receive direct DOM mutations of `transform` outside Vue reactivity).
1446
+ Host gains `direction` (`'vertical' | 'horizontal' | 'both'`),
1447
+ `easing` (`'linear' | 'ease-out' | 'spring'`, in addition to the
1448
+ legacy raw CSS timing-function string), `disabled`, `speed`,
1449
+ `threshold` props. New emits: `@enter`, `@leave`,
1450
+ `@scroll-progress(0→1)` driven by `IntersectionObserver` +
1451
+ `requestAnimationFrame`. `prefers-reduced-motion: reduce` is honoured
1452
+ natively — layers stay at `translate3d(offsetX, offsetY, 0)` and the
1453
+ rAF loop short-circuits. CSS-first scroll-driven animations
1454
+ (`animation-timeline: view()`) when `view-timeline` is supported
1455
+ (Chrome 115+, Edge 115+) AND `easing === 'linear'`; JS fallback
1456
+ otherwise. Spring easing implemented as a damped lerp in the JS path.
1457
+ Existing single-layer / `<OrigamParallaxElement>` API preserved (the
1458
+ two layer kinds use independent injection contexts and can coexist
1459
+ inside the same host). New tokens: `parallax.transition-duration-spring`,
1460
+ `parallax.transition-easing-default` / `-spring`,
1461
+ `parallax.layer.will-change` / `.transform-origin`.
1462
+
1463
+ ### Added
1464
+
1465
+ - `OrigamCommandPalette` — ⌘K command launcher. Built on a teleported
1466
+ dialog with focus trap + focus restoration. Custom subsequence-based
1467
+ fuzzy-match algorithm (no external dep) ranks results by
1468
+ consecutive-run + label-prefix + first-position bonuses. Composable
1469
+ `useCommand` exposes a process-wide command registry — entries
1470
+ registered inside a Vue effect scope auto-unregister on dispose.
1471
+ Reuses `OrigamKbd` for inline shortcut display. Hotkey listener
1472
+ built on `useHotkey` (default `⌘+K` on macOS, `Ctrl+K` on
1473
+ Windows / Linux, configurable per combination or disabled with
1474
+ `:hotkey="null"`). ARIA combobox pattern (`role="combobox"` on the
1475
+ input, `role="listbox"` on the result list, `role="option"` per
1476
+ item, `aria-activedescendant` tracking the keyboard cursor) +
1477
+ `role="dialog"` + `aria-modal="true"` on the surface. Tokens exposed
1478
+ under `tokens/component/command-palette.json`
1479
+ (`--origam-command-palette---*`,
1480
+ `--origam-command-palette__input---*`,
1481
+ `--origam-command-palette__item---*`,
1482
+ `--origam-command-palette__group-title---*`,
1483
+ `--origam-command-palette__empty---*`,
1484
+ `--origam-command-palette__footer---*`,
1485
+ `--origam-command-palette--backdrop---*`).
1486
+
1487
+ - `OrigamBracket` — e-sport tournament tree. Supports single-elimination,
1488
+ double-elimination and round-robin variants. SVG connectors
1489
+ auto-computed from `nextMatchId` (with positional fallback). Slots for
1490
+ custom match / competitor / round-title / connector rendering. ARIA
1491
+ `role="region"` with per-round `role="group"` + `aria-labelledby`
1492
+ pointing at title headings. Tokens exposed under
1493
+ `tokens/component/bracket.json` (`--origam-bracket---*`,
1494
+ `--origam-bracket-match---*`, `--origam-bracket-competitor---*`,
1495
+ `--origam-bracket-connector---*`, `--origam-bracket-round-robin---*`).
1496
+
1497
+ - `OrigamSnackbarStack` — multi-toast notification system. Reuses
1498
+ `OrigamSnackbar` styling vocabulary for rendering. Composable
1499
+ `useSnackbarStack({ id })` exposes `notify` / `dismiss` /
1500
+ `dismissAll`. 8 anchor locations, max stack size with FIFO eviction,
1501
+ per-item auto-dismiss (or `0` for sticky), action buttons with
1502
+ optional `keepOpen`, ARIA `role="region"` on the stack root +
1503
+ `role="status"` / `"alert"` per intent on each item with matching
1504
+ `aria-live`. Slide-in / slide-out transitions degrade to a fade under
1505
+ `prefers-reduced-motion`. Tokens exposed under
1506
+ `tokens/component/snackbar-stack.json`
1507
+ (`--origam-snackbar-stack---*`, `--origam-snackbar-stack__item---*`).
1508
+
1509
+ - `OrigamTabs` / `OrigamTab` / `OrigamTabPanels` / `OrigamTabPanel` —
1510
+ full tab system with horizontal / vertical orientation, three visual
1511
+ variants (`default` / `pills` / `underline`), ARIA `role="tablist"` +
1512
+ `role="tab"` + `role="tabpanel"` wiring, full keyboard navigation
1513
+ (`←`/`→`/`↑`/`↓`/`Home`/`End`/`Enter`/`Space`), lazy or eager panel
1514
+ mounting, optional touch-swipeable panels. Reuses the shared
1515
+ `useGroup` orchestration so the selection state, mandatory behaviour
1516
+ and disabled-tab semantics align with the rest of the system
1517
+ (`OrigamBtnToggle`, `OrigamCarousel`, `OrigamBottomNav`). Tokens
1518
+ exposed under `tokens/component/tabs.json` (item color, indicator
1519
+ color, panel padding, transition duration).
1520
+
1521
+ - `OrigamClientOnly` — SSR helper component that renders its default
1522
+ slot only after `onMounted`. Optional `#fallback` slot (or
1523
+ `placeholder-tag` / `placeholder-class` props) reserves layout space
1524
+ during SSR to avoid CLS. Use to wrap fragments whose markup truly must
1525
+ differ between server and client (audio, deviceorientation,
1526
+ IntersectionObserver-driven features, …) without triggering hydration
1527
+ mismatches.
1528
+
1529
+ - `useCssSupportClient(feature, { defaultValue })` — hydration-safe
1530
+ feature-gate helper. Returns a `Ref<boolean>` that starts at
1531
+ `defaultValue` (default `false`) on both SSR and first client render,
1532
+ then flips to the real `CSS.supports()` result inside `onMounted`.
1533
+ Use to gate **markup** branches (template `v-if`) when the regular
1534
+ `useCssSupport().css.value.X` would produce a hydration mismatch
1535
+ (class-only branches keep using the existing API — Vue 3 reconciles
1536
+ class diffs fine).
1537
+
1538
+ ### Fixed
1539
+
1540
+ - SSR safety — comprehensive audit of all composables and components
1541
+ that previously could crash on server render (`window is not defined`,
1542
+ `document is not defined`). `useCssSupport` already returned all-false
1543
+ flags during SSR; the rest of the surface (`useTheme`, `useCommand`,
1544
+ `useSnackbarStack`, `useCode`, `useMask`, `useTouch`, `useHotkey`,
1545
+ `useSticky`, `useSheetSwipe`, `useScroll`, `useParallax`, `useStyleTag`,
1546
+ `useTeleport`, `useLocationStrategies`, `useScrollStrategies`,
1547
+ `useDisplay`) was confirmed safe via the audit and patched where a
1548
+ composable's public method or a `computed` could be evaluated during
1549
+ SSR. Specifically: `useAspectRatio` no longer dereferences
1550
+ `window.innerWidth/Height` when no explicit `aspectRatio` prop is
1551
+ given; `useVirtual`'s `viewportHeight` computed guards
1552
+ `document.documentElement`; `useSnackbarStack.dismiss()` guards
1553
+ `window.clearTimeout`. Overlay components (Dialog, Drawer, Menu,
1554
+ Tooltip, Snackbar, ContextualMenu, SnackbarStack, CommandPalette)
1555
+ confirmed SSR-safe via `<Teleport>` (Vue defers the mount until the
1556
+ client). New guide `docs/guide/ssr.md`. New `src/__tests__/ssr-smoke.spec.ts`
1557
+ exercises every refactored composable in a simulated SSR environment
1558
+ (window/document/CSS stripped) **and** through `@vue/server-renderer`'s
1559
+ real `renderToString()`.
1560
+
1561
+ ---
1562
+
1563
+ ## [2.2.1] — 2026-05-14
1564
+
1565
+ ### Fixed
1566
+
1567
+ - Expose `./package.json` explicitly in the `exports` map. The catch-all
1568
+ `"./*": "./dist/src/*"` previously intercepted `import 'origam/package.json'`
1569
+ and re-routed it to a non-existent file inside `dist/`. Standard
1570
+ consumer pattern (reading the version from the package metadata) was
1571
+ broken — fixed by adding `"./package.json": "./package.json"` ahead
1572
+ of the catch-all so it matches first.
1573
+ - 2.2.0 was tagged but never published to npm. 2.2.1 is the first
1574
+ version that lands on the registry.
1575
+
1576
+ ---
1577
+
1578
+ ## [2.2.0] — 2026-05-14
1579
+
1580
+ > **The "ready for npm" release.** Package metadata, peer dependencies,
1581
+ > tree-shaking signals and a real README are all in. Tarball preview
1582
+ > shrunk from 5.6 MB to **867.9 kB** (2 343 files, zero suspect entry).
1583
+ > No public API change vs 2.1.0 — every consumer upgrade is a
1584
+ > drop-in.
1585
+
1586
+ ### Added — stories, slots, emits coverage
1587
+
1588
+ - 525+ missing `Slot` / `Emit` Variants across 113 component stories
1589
+ (`feat(stories)`). Every documented slot now has a dedicated Variant
1590
+ with a custom-content example, and every emit a Variant that logs
1591
+ to a live counter.
1592
+ - `OrigamSwitchTrack` extracted as a standalone primitive (track-only)
1593
+ so consumers can build switch-like compounds without rewriting the
1594
+ thumb logic.
1595
+ - Tooling: `useStateEffect` now reacts to every per-axis composable
1596
+ (`useHover` / `useActive` / `useFocus`) in a single call — collapses
1597
+ the 14 components that historically wired them axis by axis.
1598
+
1599
+ ### Fixed
1600
+
1601
+ - `useStyle` now instrumented across the 124 components that
1602
+ previously inlined `*Styles` arrays — single source of truth for the
1603
+ inline-style escape hatch.
1604
+ - Carousel — `progress` prop renders a real-time progress bar wired to
1605
+ the cycle timer (was rendering a static "step / total" bar).
1606
+ - Carousel — `hideDelimiterBackground` finally has a background to
1607
+ hide (default `__controls` bg = `rgba(0, 0, 0, 0.4)`).
1608
+ - Histoire sandbox/panel sash now resizes the iframe in **both
1609
+ directions** (was capped at the 720 px responsive-preset width when
1610
+ dragging right).
1611
+ - `useRounded` — utility variants (`xs|sm|md|lg|xl|none|full`) now
1612
+ emit BOTH the `.origam--rounded-*` class AND an inline
1613
+ `border-radius` declaration as a specificity escape hatch (Strategy
1614
+ A documented in `CLAUDE.md`).
1615
+ - 130 unused-vars cleared from `src/`, 39 ESLint warnings fixed.
1616
+
1617
+ ### Changed — package for npm
1618
+
1619
+ - `peerDependencies` introduced: `vue ^3.5`, `vue-i18n ^11`,
1620
+ `vue-router ^4.5` (last two `optional` via `peerDependenciesMeta`).
1621
+ They are no longer auto-installed as `dependencies` — consumers
1622
+ bring their own versions.
1623
+ - `sideEffects: ["**/*.css", "**/*.scss", "**/*.vue"]` for downstream
1624
+ tree-shaking.
1625
+ - `engines.node: ">=22"` (matches `.nvmrc`).
1626
+ - `prepublishOnly` script chains `tokens:build` → `server:build` →
1627
+ unit tests. Every `npm publish` rebuilds from scratch with green
1628
+ tests.
1629
+ - `files` whitelist tightened to `dist/src/`, `dist/tokens/`, LICENSE,
1630
+ README, CHANGELOG. The Histoire bundle (`dist/stories/`, ~13 MB) is
1631
+ no longer shipped to consumers.
1632
+ - `build.config.ts` `externals` extended to `['vue', 'vue-i18n',
1633
+ 'vue-router', '@mdi/font']` so peer deps don't get re-bundled.
1634
+ - mkdist exclusion patterns expanded: `.spec.ts`, `__tests__/**`,
1635
+ `.cy.ts`, `.story.vue` are stripped from `dist/`.
1636
+ - ESLint config ignores `docs/.vitepress/cache/**` and
1637
+ `figma-plugin/**` (these aren't part of the published library).
1638
+
1639
+ ### Documentation
1640
+
1641
+ - Full `README.md` written from scratch (253 lines) — install,
1642
+ peer deps, plugin registration, theming (`data-theme` +
1643
+ `useTheme()` + `<OrigamThemeProvider>`), token tiers, component
1644
+ families, composable table, CSS-first principle.
1645
+
1646
+ ### Internal
1647
+
1648
+ - ESLint rule `no-restricted-imports` blocks `@origam` /
1649
+ `@stories` / `@docs` / `@cypress` aliases inside `src/` — once
1650
+ published, those aliases can't be resolved by consumers.
1651
+ - 124 components instrumented with `useStyle` following the
1652
+ canonical pattern.
1653
+ - `.tsbuildinfo` and other transient artefacts removed from the tree.
1654
+
1655
+ ---
1656
+
1657
+ ## [2.1.0] — 2026-05-07
1658
+
1659
+ > **The classes-first release.** The 13 transversal composables
1660
+ > (`useColor`, `useBackgroundColor`, `useTextColor`, `useColorEffect`,
1661
+ > `useElevation`, `useRounded`, `useBorder`, `useMargin`, `usePadding`,
1662
+ > `useSize`) now emit utility classes (e.g. `.origam--bg-primary`,
1663
+ > `.origam--shadow-md`) when the consumer passes a tokenised value. The
1664
+ > existing `*Styles` outputs stay populated only when the value is
1665
+ > non-tokenisable (legacy hex, custom dimensions) — the inline style
1666
+ > stack on a typical button drops from ~12 to ~6 declarations.
1667
+ >
1668
+ > **Non-breaking.** Every composable keeps its previous return shape;
1669
+ > the new `*Classes` keys are additive. Components have been migrated
1670
+ > to read both shapes in parallel (transition strategy A).
1671
+
1672
+ ### Migrating from v2.0 to v2.1
1673
+
1674
+ Nothing is required. The release is additive; existing consumers keep
1675
+ working unchanged. To start using the new path:
1676
+
1677
+ ```vue
1678
+ <!-- v2.0 — still works, still emits inline style -->
1679
+ <OrigamBtn color="primary">Click</OrigamBtn>
1680
+
1681
+ <!-- v2.1 — same code, but the emitted DOM now also carries the
1682
+ `.origam--bg-primary` utility class instead of inline
1683
+ `background-color: var(...)`. Inspecting the DOM is much cleaner. -->
1684
+ ```
1685
+
1686
+ If you write your own components on top of `useColor*` / `useElevation`
1687
+ / `useRounded` / etc., you can now destructure the new `*Classes`
1688
+ return:
1689
+
1690
+ ```ts
1691
+ const { colorClasses, colorStyles } = useColorEffect(props, isHover, isActive, isDisabled)
1692
+ // colorClasses: ['origam--bg-primary'] when intent
1693
+ // colorStyles : [] when intent
1694
+ // colorClasses: [] when '#7c3aed'
1695
+ // colorStyles : ['background-color: ...']when '#7c3aed'
1696
+ ```
1697
+
1698
+ Bind both — `:class="[..., colorClasses]"` AND `:style="[..., colorStyles]"`.
1699
+ The hover/active/disabled state still flows through `colorStyles` (utility
1700
+ classes are static, by design).
1701
+
1702
+ ### Added
1703
+
1704
+ - **66 utility classes** generated by Style Dictionary, exposed at
1705
+ `origam/tokens/css/utilities` and forwarded by `origam/styles`:
1706
+ - `.origam--color-{primary|secondary|success|warning|danger|info|neutral}`
1707
+ - `.origam--bg-{primary|secondary|success|warning|danger|info|neutral}`
1708
+ - `.origam--shadow-{none|xs|sm|md|lg|xl}`
1709
+ - `.origam--rounded-{none|xs|sm|md|lg|xl|full}`
1710
+ - `.origam--border-{none|thin|thick}`
1711
+ - `.origam--p-{0..12}`, `.origam--m-{0..12}`, `.origam--gap-{0..12}`
1712
+ - `.origam--text-{xs|sm|md|lg|xl|2xl}`
1713
+ - New return keys on transversal composables:
1714
+ `colorClasses`, `backgroundColorClasses`, `textColorClasses`,
1715
+ `elevationClasses` (extended), `roundedClasses` (extended),
1716
+ `borderClasses` (extended), `marginClasses`, `paddingClasses`,
1717
+ `sizeClasses`.
1718
+ - `tests/e2e/utilities.spec.ts` — 66 Playwright tests, one per
1719
+ utility class, asserting both class presence and `getComputedStyle`
1720
+ resolution against the CSS var pipeline.
1721
+ - `tests/e2e/prop-audit-phase4.spec.ts` — DOM audit on representative
1722
+ components confirming the inline-style pile reduction.
1723
+ - 79 new Vitest unit specs in `src/composables/Commons/__tests__/`
1724
+ covering the classes-first branch of every refactored composable.
1725
+
1726
+ ### Changed
1727
+
1728
+ - ~54 components migrated to consume `*Classes` in parallel with
1729
+ `*Styles`. Notable surface-bearing children: Menu/Tooltip/Picker/
1730
+ Snackbar/Badge — utility class lands on the `__content` / `__pill` /
1731
+ `__wrapper` BEM child, never on the teleport root.
1732
+ - `OrigamSelectionControl.__wrapper` now carries
1733
+ `useTextColor(color)`'s class + style on its root element. This
1734
+ re-instates the Switch thumb tinting (`currentColor`) for tokenised
1735
+ intents, which had silently broken when `OrigamSwitchTrack` was
1736
+ extracted earlier in the cycle.
1737
+ - `OrigamSwitch` SCSS — the legacy `[style*="color:"]` selector is
1738
+ joined by a class-driven set
1739
+ (`.origam-selection-control__wrapper.origam--color-{intent} &__thumb`)
1740
+ so both the new tokenised path and the legacy hex path tint the thumb
1741
+ via `currentColor`.
1742
+
1743
+ ### Deprecated
1744
+
1745
+ - `*Styles` returns on the 10 refactored composables — prefer the
1746
+ matching `*Classes` for tokenised values. Both are kept for one
1747
+ major cycle; removal scheduled for v3.0.0.
1748
+
1749
+ ### Fixed
1750
+
1751
+ - `OrigamSwitch` thumb stayed white when `color="primary"` (or any
1752
+ tokenised intent) — regression from the `OrigamSwitchTrack`
1753
+ extraction. Re-introduced via classes-first.
1754
+ - `OrigamSnackbar` `__wrapper` no longer duplicates the
1755
+ `roundedClasses`/`borderClasses` already applied at the root.
1756
+
1757
+ ### Known limits — planned for v2.2 (Phase 1.5)
1758
+
1759
+ - No utility for margin / padding axes (`mx`, `my`, `mt`, `mb`, `ml`,
1760
+ `mr`, `px`, `py`, `pt`, `pb`, `pl`, `pr`). Components passing
1761
+ axis-specific values fall back to inline styles for now.
1762
+ - No `2xl` / `3xl` shadow utilities (the rungs exist as primitives but
1763
+ are off the manifest).
1764
+ - Legacy `ROUNDED` enum (`x-small | small | default | medium | large
1765
+ | x-large | shaped | shaped-invert`) does not bridge to the
1766
+ `none|xs|sm|md|lg|xl|full` utility taxonomy — components passing a
1767
+ legacy value get their existing component-scoped BEM modifier and an
1768
+ inline style fallback.
1769
+ - `useColorEffect` does NOT emit a `*Classes` value when `isHover` /
1770
+ `isActive` / `isDisabled` is true (states are inline-only by design).
1771
+ Components that bind `useActive(props, 'modelValue')` (Alert, Badge,
1772
+ BottomNav) therefore never carry a utility class while visible — the
1773
+ inline style still paints them correctly. This is intentional; do not
1774
+ rely on the utility class for state-dependent styling.
1775
+
1776
+ ---
1777
+
1778
+ ## [2.0.0] — 2026-04-26
1779
+
1780
+ > **The design tokens release.** Every component now resolves its colors,
1781
+ > spacing, typography, and motion through a centralised, theme-aware
1782
+ > token pipeline (Style Dictionary v4 + Tokens Studio for Figma). 50+
1783
+ > components migrated; 18 of them also picked up real bug fixes from the
1784
+ > companion `optimus-design-system` codebase. Multi-theme (light / dark)
1785
+ > works out of the box; `brand-X` themes are an additive layer.
1786
+
1787
+ ### Migrating from v0.x to v2.0.0
1788
+
1789
+ This is a major bump because three things change:
1790
+
1791
+ 1. **The CSS variables that components read are now generated.** The old
1792
+ `<style>:root{}` blocks that each component shipped have been removed.
1793
+ Consumers MUST load the generated token CSS — either:
1794
+
1795
+ ```ts
1796
+ import 'origam/styles' // primitive + light + dark + helpers
1797
+ ```
1798
+ or pick the layers separately:
1799
+ ```ts
1800
+ import 'origam/tokens/css/primitive'
1801
+ import 'origam/tokens/css/light'
1802
+ import 'origam/tokens/css/dark' // applied via [data-theme="dark"]
1803
+ ```
1804
+
1805
+ If you were overriding origam vars at the document root (e.g.
1806
+ `:root { --origam-btn---background-color: red }`), that still works —
1807
+ the generated CSS is just a default. But if you depended on the
1808
+ per-component `:root{}` block being injected when the component
1809
+ mounted, that's gone. The vars are now set once globally.
1810
+
1811
+ 2. **`useColorEffect` is intent-aware.** The `color` / `bgColor` props on
1812
+ `<OrigamBtn>`, `<OrigamChip>`, `<OrigamAvatar>`, etc. now expect a
1813
+ semantic intent (`'primary'`, `'success'`, `'danger'`, …) rather than
1814
+ a raw hex. Raw hex still works but emits a one-shot `console.warn`
1815
+ per `(prop, value)` pair (full removal in v3.0.0).
1816
+
1817
+ ```vue
1818
+ <!-- v0.x -->
1819
+ <OrigamBtn color="#7c3aed">Click</OrigamBtn>
1820
+
1821
+ <!-- v2.0 -->
1822
+ <OrigamBtn color="primary">Click</OrigamBtn>
1823
+
1824
+ <!-- One-off custom color -->
1825
+ <OrigamBtn :style="{ '--origam-btn---background-color': '#7c3aed' }">…</OrigamBtn>
1826
+ ```
1827
+
1828
+ 3. **`useElevation` no longer computes shadows in JS.** The `bgColor`
1829
+ parameter is accepted for signature compat but ignored. The composable
1830
+ now emits `box-shadow: var(--origam-shadow-{rung})` and the rung is
1831
+ themed at the token level (different shadow recipes for light vs dark).
1832
+
1833
+ The legacy `formatElevationStyle(level, bgColor)` utility is still
1834
+ exported but `@deprecated` — it will be removed in v3.0.0.
1835
+
1836
+ ### Added
1837
+
1838
+ - **Multi-tier design tokens** (`tokens/`):
1839
+ - Primitive: 12 color ramps, 12 spacing steps, full font / radius /
1840
+ shadow / motion / zIndex / opacity / border ladders.
1841
+ - Semantic: `surface`, `text`, `border`, `action.{primary,secondary,
1842
+ ghost}`, `feedback.{success,warning,danger,info}`, `overlay`. Per
1843
+ theme (light + dark), with hooks for brand-X.
1844
+ - Component: ~60 files, one per component, in DTCG / Tokens Studio
1845
+ format — editable from the Figma plugin.
1846
+ - **Pipeline**: Style Dictionary v4 + sd-transforms. Outputs CSS,
1847
+ SCSS partials, and TypeScript union types. `npm run tokens:build`,
1848
+ `tokens:watch`, `tokens:lint`.
1849
+ - **GitHub Action** (`.github/workflows/tokens-sync.yml`): consumes
1850
+ Figma → Tokens Studio → `tokens-sync` branch, rebuilds artifacts,
1851
+ opens a PR to `develop` automatically.
1852
+ - **Multi-theme runtime**:
1853
+ - `useTheme()` composable — singleton ref, persistence, prefers-
1854
+ color-scheme fallback, toggle helper.
1855
+ - `<OrigamThemeProvider theme="dark">` for sub-tree overrides.
1856
+ - **CSS-first / JS-fallback principle**:
1857
+ - `useCssSupport()` composable — feature detection layer wrapping
1858
+ `CSS.supports()` with caching for 20 modern features (subgrid,
1859
+ container queries, `:has()`, aspect-ratio, color-mix, anchor
1860
+ positioning, view transitions, …). Components now branch via
1861
+ this composable instead of calling `CSS.supports()` directly.
1862
+ - **Defaults system** (ported from optimus):
1863
+ - `useDefaults()` composable + `<OrigamDefaultsProvider>` for
1864
+ cascading default props (mirrors Vuetify's `<v-defaults-provider>`).
1865
+ - `IDefault`, `IDefaultProviderProps`, `IDefaultProviderSlots`.
1866
+ - **`<OrigamConfirmWrapper>`** — type-it-twice form helper with
1867
+ auto-injected validation, two render modes (slot-based / `field=`
1868
+ shortcut), and bidirectional defaults forwarding.
1869
+ - **`SCSS helpers`** (`src/assets/scss/_helpers.scss`): `ds-intent`,
1870
+ `ds-elevation`, `ds-text-style`, `ds-space`, `ds-focus-ring`,
1871
+ `ds-visually-hidden`.
1872
+ - **VARIANT / VARIANT_INPUT enums** + `TVariant` / `TVariantInput`
1873
+ types.
1874
+ - Common emit / slot interfaces: `IFieldEmits`, `IFieldSlots`,
1875
+ `IFieldDefaultSlotProps`, `IInputEmits`, `IInputSlots`,
1876
+ `IAdjacentEmits`, `IAdjacentSlots`, `IAdjacentInnerEmits`,
1877
+ `IAdjacentInnerSlots`, `IActiveEmits`, `IFocusEmits`,
1878
+ `ICommonsComponentEmits`, `ICommonsComponentSlots`.
1879
+
1880
+ ### Changed
1881
+
1882
+ - **Every component** with chrome (~50) now consumes design tokens
1883
+ rather than hardcoded values. The per-component `<style>:root{}`
1884
+ blocks are gone.
1885
+ - **`OrigamPasswordField`** rebuilt from the optimus version (629
1886
+ vs 399 lines). New features: strength-requirements popup
1887
+ (`requirements` + `need*` flags + `minLength`), auto-injected
1888
+ validation rules, show/hide toggle, intersect-driven autofocus,
1889
+ click:control / mousedown:control emits.
1890
+ - **`OrigamFileField`** split into 3 files (FileField + DragNDropItem
1891
+ + ListItem) for clearer responsibility boundaries; drag-and-drop
1892
+ mode reworked, `maxFileSize` validation added, typed emits.
1893
+ - **`OrigamForm`** grew form-level validation, `scrollToError`,
1894
+ global `messages` rendering through OrigamMessages.
1895
+ - **`OrigamField`** now exposes the `outline__notch` BEM child for
1896
+ the floating-label rendering, slot-based prefix/suffix detection,
1897
+ and proper focus↔active synchronisation.
1898
+ - **Snackbar z-index** normalised from `10000` to `1060` via
1899
+ `{zIndex.toast}`. Consumers stacking custom overlays on top must
1900
+ verify their stacking context.
1901
+ - **`color.action.primary.bgSubtle / fgSubtle`** added to the
1902
+ semantic layer (was missing — Select selected-item background and
1903
+ Slider thumb focus ring needed it).
1904
+
1905
+ ### Fixed
1906
+
1907
+ - **OrigamMessages**: `:id` was bound to the entire messages array
1908
+ instead of the current message. Fixed by using a kebab-cased
1909
+ per-message key.
1910
+ - **OrigamNumberField**: `watchEffect` → `watch(props.modelValue)` —
1911
+ the eager mode was causing reactive write loops while the user
1912
+ typed. Increment / decrement handlers split.
1913
+ - **OrigamOtpInputField**: undefined-current crash guard;
1914
+ `isValidNumber` → `isInvalidValue` (semantic was inverted);
1915
+ double `update:focused` emit prevented; handleClear added.
1916
+ - **OrigamFileField**: `@clik:append` and `@lick:append-inner` typo
1917
+ fixes; drag&drop append mode no longer overwrites existing files;
1918
+ locale arg shape `t(key, [arg])` corrected.
1919
+ - **OrigamMenu**: stray `console.log()` removed.
1920
+ - **OrigamOverlayScrim**: `<style scoped>` block was missing
1921
+ entirely — scrim wasn't rendering.
1922
+ - **OrigamProgressCircular**: `backgroundColorClasses` /
1923
+ `loaderColorClasses` were missing on the SVG `<circle>` elements.
1924
+ - **OrigamProgressLinear**: `useBackgroundColor` → `useTextColor`
1925
+ (the bar's color was being applied as background, washing it out).
1926
+ - **OrigamListSubheader**: vuetify-specific
1927
+ `rgba(var(--v-theme-on-surface), …)` references replaced with
1928
+ origam-native tokens. Style block converted from `lang="css"` to
1929
+ `lang="scss"`.
1930
+ - **OrigamExpansionPanel**: vuetify shadow var leak
1931
+ (`var(--v-shadow-key-umbra-opacity, …)`) removed; selector
1932
+ `:not(.v-expansion-panel-title--static)` matched a vuetify class
1933
+ that never renders in origam — corrected to the origam class.
1934
+ - **OrigamSlideGroup**: `<style scoped>` block was missing entirely.
1935
+ - **OrigamCheckbox / OrigamCheckboxBtn / OrigamSelectionControl**:
1936
+ `handleClickLabel` argument typed `MouseEvent` (was `Event` —
1937
+ caused TS strict failures).
1938
+ - **OrigamSelectionControlGroup**: `item` slot now exposes
1939
+ `{ item, index }` (was `{ item }` only).
1940
+
1941
+ ### Deprecated
1942
+
1943
+ - `formatElevationStyle(level, bgColor)` — replaced by the
1944
+ `--origam-shadow-{rung}` token ladder. Kept exported for one
1945
+ major version; full removal in v3.0.0.
1946
+ - Raw hex / rgb passed to `useColorEffect` color props — pass a
1947
+ `TIntent` value or use `:style` for one-off custom colors. Removal
1948
+ in v3.0.0.
1949
+
1950
+ ### Outstanding (deferred to v2.x or v3.0.0)
1951
+
1952
+ - Several form components still carry hardcoded hex inside their
1953
+ scoped styles (Switch 11, Select 8, DatePickerField 5,
1954
+ ColorPickerField 2, Img 2, Highlight 3). The token JSONs are in
1955
+ place; the `var(--origam-…)` references need to be wired in a
1956
+ follow-up. Fallback-only behaviour ensures consumers see the
1957
+ intended design today.
1958
+ - Outstanding token-naming gaps: `color.surface.chrome` (SystemBar),
1959
+ `color.overlay.backdrop` (Drawer / Overlay scrim), `color.surface
1960
+ .inverse` (Tooltip), `opacity.20` / `opacity.30`, `radius.xs2: 6px`,
1961
+ `font.size.6xl: 45px`, `font.letterSpacing.widish`. Promote at
1962
+ next ui-designer review.
1963
+ - `OrigamMain` uses `--origam-main--{prop}` (double-tiret) instead
1964
+ of the canonical `---` convention. Cosmetic, breaks consumer
1965
+ overrides. To be aligned at v3.
1966
+
1967
+ ---
1968
+
1969
+ ## Pre-2.0 history
1970
+
1971
+ The `[WIP]` commits before this release were the foundation work
1972
+ (audit, Figma integration spike, package layout). The design-token
1973
+ migration started at commit `2124ab8` (Lot 0) and shipped over
1974
+ ten incremental commits visible in `git log --oneline main..HEAD`.
1975
+