@uni-design-system/uni-core 8.1.0 → 8.3.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,726 @@
1
+ # @uni-design-system/uni-core
2
+
3
+ ## 8.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`eb8b7ae`](https://github.com/uni-design-system/uni/commit/eb8b7aebd900e647a8b35da791fce88a8c9a1217) Thanks [@gaenglish](https://github.com/gaenglish)! - `'combobox'` joins `ComponentName`, with a base-theme entry: `toggleIcon: 'chevronDown'`, `clearIcon: 'close'`, `selectedIcon: 'check'` (BaseIcons names rendered by `uni-icon` — not Material Symbol ligatures), the listbox surface trio (`listColor`/`listShadow`/`listBorderRadius`), `activeColor`, `descriptionColor`, and `maxVisibleOptions: 8` — a scroll height, never a cap. Field chrome comes from the `input` entry via `uni-input-box`, as everywhere.
8
+
9
+ - [`eb8b7ae`](https://github.com/uni-design-system/uni/commit/eb8b7aebd900e647a8b35da791fce88a8c9a1217) Thanks [@gaenglish](https://github.com/gaenglish)! - Listbox popups (`uni-search-input`, `uni-tag-input`, `uni-time-input`, `uni-combobox`): the active/hover option fill is now the themable `activeColor` option (default `'primary-container'`, on-color derived) instead of a hardcoded token pair. Set it when your theme maps `primary-container` and `primary-surface` to the same color — the keyboard highlight is otherwise invisible; a canvas/hover tint like the one your menus use is usually right (`searchInput: { options: { activeColor: 'tertiary-surface' } }`, and likewise `tagInput`/`timeInput`/`combobox`). The base theme carries the default explicitly so the option is discoverable in each component's Theme options table.
10
+
11
+ The four popups now share one style source, the exported `listboxPopupStyles(theme, options, { maxHeight? })` helper (`UniListboxPopupOptions`), so their surface trio and highlight can no longer drift apart. Rendering is unchanged under existing themes; the active row's text color now derives from `listColor`'s on-pair rather than assuming `on-primary-surface`.
12
+
13
+ - [`6f87212`](https://github.com/uni-design-system/uni/commit/6f87212da2ed421a8a4f2e57047ebcba485c0fd3) Thanks [@gaenglish](https://github.com/gaenglish)! - `'popover'`, `'callout'`, and `'tour'` join `ComponentName`, with base-theme entries. The popover defaults reproduce the previously hardcoded look (`primary-surface`, `quaternary` border, `xs` radius, `raised` shadow, `6px 12px` padding, 7px offset) plus the tooltip-mode options; the callout entry carries the scrim/spotlight geometry (`scrimColor`, `spotlightPadding`, `spotlightRadius`, `ringWidth` — the ring color is deliberately the `variant` role, not an option); the tour entry is just `progressStyle` and `footerGap`, since its skin is the callout's.
14
+
15
+ ## 8.2.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [`57a8c4c`](https://github.com/uni-design-system/uni/commit/57a8c4c73c852a6b14c2e2916cad9bd0a1566787) Thanks [@gaenglish](https://github.com/gaenglish)! - `createTheme` accepts a sparse `typography` override, deep-merged over the base type scale — restate only the roles (or the individual `TextStyle` fields within a role) that change, and add product-specific roles under any name. Closes the gap where a derived theme (e.g. the Carbon experiment) had to spread `typography` over the created theme by hand.
20
+
21
+ - [`57a8c4c`](https://github.com/uni-design-system/uni/commit/57a8c4c73c852a6b14c2e2916cad9bd0a1566787) Thanks [@gaenglish](https://github.com/gaenglish)! - Input options: `typeFace` → `typeface`, matching the tooltip/button/tabs casing. The base theme now writes `typeface`, and the input box reads the new key with the old one as a deprecated fallback, so themes that still set `typeFace` render unchanged. The `typeFace` key is deprecated and will be removed in the next major.
22
+
23
+ ### Patch Changes
24
+
25
+ - [`e706e38`](https://github.com/uni-design-system/uni/commit/e706e3887f47d8821cc1652410ad37a43d52a428) Thanks [@gaenglish](https://github.com/gaenglish)! - Ship `CHANGELOG.md` in the published packages. The release notes existed only in the repo; an installed package carried no record of what changed, so upgrade questions couldn't be answered from `node_modules`. uni-angular copies it into the ng-packagr `dist` via `assets`; the rest add it to `files`.
26
+
27
+ ## 8.1.0
28
+
29
+ ### Minor Changes
30
+
31
+ - [`71bc74c`](https://github.com/uni-design-system/uni/commit/71bc74c0441d4170dc38312967610eac46788326) Thanks [@gaenglish](https://github.com/gaenglish)! - Calendar, date & time entry: `uni-calendar`, `uni-date-input`, `uni-time-input` and `uni-date-time-input`, plus a pure datetime helper layer in the cdk.
32
+
33
+ The most-requested form control family anywhere, built native-platform-first with zero runtime dependencies: `Intl` does all formatting and parsing (no date library), the popup rides the native popover top layer with CSS anchor positioning, and every day in the grid is a real `<button>`.
34
+ - **Values are plain ISO strings, never `Date`s** — `UniDate` (`'YYYY-MM-DD'`), `UniTime` (`'HH:mm'`, always 24-hour), `UniDateTime` (`'YYYY-MM-DDTHH:mm'`), `UniDateRange` (`{ start, end }`). A `Date` is a timestamp with a timezone problem; a calendar date is a label on a wall calendar. Strings are timezone-free, JSON-serializable and sortable with `<`, and `[value]="'2026-08-20'"` is a complete, correct binding.
35
+ - **`uni-calendar`** is an inline month grid and a form control in its own right: single and range modes in one component (the value shape switches with `mode`), availability **markers** with screen-reader labels, `minDate`/`maxDate` fences, `disabledDates` as a list or predicate, and a two-way `month` model so an app can drive "jump to June". One tab stop with a roving-tabindex `role="grid"`; the full APG keyboard map (arrows, week `Home`/`End`, `PageUp`/`PageDown` with `Shift` for years) crosses month edges, skips disabled days directionally, and stops at fences without wrapping. Range selection paints a live preview band, swaps a backwards commit instead of erroring, and narrates every step through a `role="status"` region.
36
+ - **`uni-date-input`** is free-typed date entry — no input mask (masks fight paste, IMEs and screen-reader echo). The parser accepts ISO, locale-numeric text with the digit order read from `Intl.DateTimeFormat(locale).formatToParts()`, and the locale's own month names; a missing year resolves to the next occurrence and two-digit years are refused rather than guessed. Unreadable, fenced or unavailable text **stays in the field**, flagged, with a `rejected` event. The popup is the same `uni-calendar`, hosted in a `role="dialog"` popover via the shared `uni-dropdown`; `↑`/`↓` on a committed value step ±1 day.
37
+ - **`uni-time-input`** is a combobox over time slots — byte-for-byte the search-input/tag-input listbox contract via the CDK's `ListboxNavigation`. `9` → 09:00, `930` → 09:30, `3p` → 15:00, and a bare `3` leans PM in a 12-hour field (the bias yields when `minTime`/`maxTime` say otherwise). `slots` pins the choices for scheduling, refusing a typed `5pm` as `'unavailable'`; `↑`/`↓` on a committed value step ±`minuteStep`.
38
+ - **`uni-date-time-input`** seats both parts in one `uni-input-box` chrome under one `role="group"` label, emitting one combined value only when both parts are set. `slotsFor` is the scheduling flow in one attribute: the time part stays disabled until a day is chosen, then offers exactly that day's slots, and changing the day clears a slot that no longer exists. The parts gained an `embedded` input so the composer renders one shared box instead of nested chrome.
39
+ - **cdk `datetime` module** (public export): the string-math and `Intl` layer — `addDays`/`addMonths`/`buildMonthGrid`/`parseDateText`/`parseTimeText`/`timeSlots`/`localeWeekStart` and friends — pure functions, unit-tested hard, usable by apps directly.
40
+ - **uni-core**: `'calendar'`, `'dateInput'`, `'timeInput'`, `'dateTimeInput'` join `ComponentName` with base-theme entries (day geometry per size token, `dayBorderRadius: 'xxs'` for the square look, toggle glyphs, popup/list chrome). Selection, range and today colours are deliberately not options — they are the `primary` role pair, so themes restyle them by restyling the palette.
41
+ - **Naming note:** the fences are `minDate`/`maxDate`/`minTime`/`maxTime`/`minDateTime`/`maxDateTime` rather than the platform's `min`/`max`, because Signal Forms' `FormUiControl` reserves `min`/`max` as numeric signals on form controls.
42
+
43
+ - [`1a6b382`](https://github.com/uni-design-system/uni/commit/1a6b38273e5c6daaea6355e1ba8cf01d7e851100) Thanks [@gaenglish](https://github.com/gaenglish)! - Themable focus chrome: a shared `focusRing` primitive for every control, plus `focusBorder`/`focusShadow`/`focusColor` options for input boxes.
44
+
45
+ **Shared focus ring.** A theme can now restyle the keyboard-focus indicator across the whole library by defining `focusRing` **border** and/or **shadow** primitives: `ThemeService.focusRing()` (and the new selector-less `focusRingStyle()`) replaces its default 2px outline with that border — drawn as an outline hugging the control — plus the ring shadow. A `focusRing` **thickness** primitive sets the ring's outline offset (negative values overlay the control's resting border, reading as a border-color change). Checkbox, radio, toggle and slider now route their hand-rolled focus styles through the shared helper (calendar days and tag chips already did), so one primitive trio gives every control the same focus language. Themes without the primitives render exactly as before.
46
+
47
+ **uni-core:** `Thicknesses` is now an open record like `Borders`/`Shadows` (extra named primitives allowed), and `createTheme` accepts a sparse `thicknesses` override merged over the base scale.
48
+
49
+ **Also in this release:**
50
+ - Radio and toggle scope their transitions (border/background/transform) instead of `all`, so the focus ring's outline and shadow apply instantly — `transition: all` interpolated the outline from a stale color, flashing a dark ring before the themed ring color landed.
51
+ - The radio's dot grow/retract animation is a token — `radio.options.transitionSpeed` (seconds, default 0.3 preserving the current feel; 0 switches instantly).
52
+ - The checkbox focus ring rounds proportionally again (box radius plus its gap) and the gap itself is a new `checkbox.options.focusRingGap` option; a `focusRingStyle` call's explicit gap wins over the theme's `focusRing` thickness, then the branch defaults.
53
+
54
+ The `input` component options could previously restyle focus only through `focusOutline`/`focusOutlineOffset`. The new optional trio mirrors the error-state trio (`errorBorder`/`errorShadow`/`errorColor`) and applies while any projected control has focus: `focusBorder` swaps the border primitive, `focusShadow` draws a ring (e.g. a soft `0 0 0 3px` spread), and `focusColor` swaps the background. All three default to `undefined`, so existing themes render exactly as before, and they yield to the error state so a flagged field stays visibly flagged while being corrected.
55
+
56
+ The Wellsourced showcase theme now defines the `focusRing` pair for its app's `.search-input:focus` look — an ochre (`secondary`) 1px border with a 10% ring of the same hue, tinted per palette in light and dark — so text fields (which rest on a canvas tint and snap to the clean surface on focus), checkboxes, radios, toggles, sliders, calendar days and tag chips all share one focus treatment.
57
+
58
+ ## 8.0.0
59
+
60
+ ### Minor Changes
61
+
62
+ - [`3279186`](https://github.com/uni-design-system/uni/commit/3279186e2cee806056cfb446699ab108d30fb608) Thanks [@gaenglish](https://github.com/gaenglish)! - Menu items no longer look preselected when opened with the mouse
63
+
64
+ Opening a `uni-menu` by click left the first item highlighted before the
65
+ pointer had touched it, reading as a preselected default. Two correct
66
+ behaviours combined badly: `onOpened()` implements ARIA roving focus by calling
67
+ `.focus()` on an item every open, and the item styles deliberately painted
68
+ `:focus` the same as `:hover`. Nothing was wrong with the focus itself — only
69
+ with painting it after a pointer open.
70
+ - **The highlight now keys on `:focus-visible`.** Programmatic focus following a
71
+ click doesn't match it, while keyboard-driven focus does — so mouse users get
72
+ no phantom highlight, and keyboard users keep the focus cursor. Roving-focus
73
+ bookkeeping is untouched: a mouse open still moves focus to the first item, so
74
+ screen readers announce it exactly as before.
75
+ - **New `HOVER_OR_KEYBOARD_FOCUS` constant exported from uni-core**, holding
76
+ that selector. Emotion merges styles by _exact selector text_, so a component's
77
+ base rule and any theme variant restyling it have to agree character for
78
+ character — a variant keyed `'&:hover, &:focus'` would both fail to override
79
+ and reintroduce the phantom highlight. Naming the selector once removes the
80
+ trap; the base theme's `menuItem.warn` tone and the Carbon/Wellsourced
81
+ showcase themes now use it.
82
+
83
+ Themes with their own `menuItem` variants should key the highlight with
84
+ `HOVER_OR_KEYBOARD_FOCUS`. Note that `:focus-visible` is a browser heuristic:
85
+ after a user has been navigating by keyboard, a subsequent click may still show
86
+ the highlight, which is the intended "this person is using the keyboard"
87
+ behaviour rather than a regression.
88
+
89
+ - [`de83fe4`](https://github.com/uni-design-system/uni/commit/de83fe497a2f48569a7c49eea2e7ca640d931254) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-tag-input`, plus a shared listbox contract in the CDK
90
+
91
+ The type-to-add chip field — recipients, filters, labels. It is the **open-set**
92
+ control: anything typed can become a token, whether or not it matches a
93
+ suggestion. Closed-set picking stays with `uni-select` /
94
+ `uni-multi-select-dropdown`, which is the deliberate split — the two look alike
95
+ but differ on the one thing that decides the value type, so a single component
96
+ would force one of them to carry an API it can never use.
97
+ - **`FormValueControl<UniTagItem[]>`.** `{ value: 'a@b.com' }` is a complete
98
+ item; `label`, `avatarSrc`, `invalid` and `disabled` are optional enrichment.
99
+ - **Invalid entries stay in the value, flagged**, rather than being dropped — a
100
+ field that silently swallows a typo'd address is worse than one that shows it
101
+ in red, and Enter on the chip lifts it back into the input to fix. Duplicates
102
+ and over-`max` entries are refused through `(rejected)` with a reason.
103
+ - **Full keyboard contract**: separators commit, Tab commits without trapping,
104
+ Backspace on an empty field _focuses_ the last chip rather than deleting it
105
+ blind, and on a chip Backspace and Delete both remove but move focus in
106
+ opposite directions — what makes pruning a list feel right.
107
+ - **The whole field is one tab stop.** Chips carry `tabindex="-1"`, so Tab never
108
+ walks through eight recipients to reach the next control.
109
+ - **Announces through a `role="status"` live region** on every add, remove and
110
+ rejection; the removal route is described once per field rather than by every
111
+ chip.
112
+ - **`preset="email"`** wires an address validator, a paste parser that unwraps
113
+ `Name <a@b.com>` and keeps an unterminated tail in the field, and Space as a
114
+ separator.
115
+ - **`'tagInput'` joins core's `ComponentName`** with a theme entry for the chip
116
+ field's own tokens. Field chrome is not duplicated — it comes from `input` via
117
+ `uni-input-box`, so a tag input restyles with every other field.
118
+
119
+ **Also in this release**
120
+ - **`ListboxNavigation` in the CDK** (`createListboxNavigation`) owns the
121
+ combobox bookkeeping every type-ahead control needs: open state, active
122
+ option, wrap-around arithmetic, Home/End, and `aria-activedescendant` wiring
123
+ that can never point at an option a narrowing filter has removed.
124
+ `uni-search-input` now uses it — behaviour unchanged, and it gains Home/End
125
+ for free — and the multi-select combobox upgrade will too. Three hand-rolled
126
+ copies of one keyboard contract is how they drift.
127
+ - **`uni-tag` gains `controlTabIndex`**, so a composite that owns its own roving
128
+ focus can take its chips out of the tab order, and **`selected` is now
129
+ optional**: an interactive chip carries `aria-pressed` only when it is
130
+ genuinely a toggle. Announcing "not pressed" on a recipient chip is worse than
131
+ announcing nothing.
132
+
133
+ - [`760b761`](https://github.com/uni-design-system/uni/commit/760b761ad77533e7e01f7a56731f74e470bbd948) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-tag` v2: themable, opt-in removal, two style axes
134
+
135
+ v1 was a single hardcoded look with the palette welded into its template, an
136
+ unconditional remove button, and no theme entry — a filter chip, a status pill
137
+ and a recipient token all rendered identically. v2 makes the chip a real themed
138
+ component and the building block `uni-tag-input` composes.
139
+
140
+ **Breaking (uni-angular)**
141
+ - **`(close)` is now `(removed)`.** The old name shadowed the native `close`
142
+ event and needed an eslint suppression to compile.
143
+ - **The remove button is opt-in** — set `[removable]="true"`. Previously every
144
+ tag, including a plain category label, shipped a "Remove …" button into the
145
+ accessibility tree.
146
+ - Codemod: rename `(close)` → `(removed)` and add `[removable]="true"` wherever
147
+ a `(close)` handler exists.
148
+
149
+ **Fixed**
150
+ - **A falsy value can now be removed.** `handleClose` guarded with `if (v)`, so
151
+ a tag keyed `''` or `0` silently could not be dismissed. Every defined value
152
+ emits, and a tag with no value emits `undefined`.
153
+ - The remove control now sizes from the chip instead of the icon-button's own
154
+ `sm` size — it was 22px inside a 24px chip, and taller than an `sm` chip
155
+ entirely.
156
+
157
+ **New**
158
+ - **Two orthogonal style axes**: `variant` picks the colour role, `tone` picks
159
+ the archetype (`soft` / `solid` / `outline`). Both resolve from the new `tag`
160
+ theme entry, with tones as nested `&.tone-*` selectors inside each variant so
161
+ a theme author restyles both axes in one place.
162
+ - **`'tag'` joins core's `ComponentName`**, with a `TagTone` type and a full
163
+ theme entry (`options` for radius/typeface/gap/symbols, `variants` per colour
164
+ role, `sizes` carrying geometry only).
165
+ - **`interactive`** turns the chip body into a `<button>` with `selected` mapped
166
+ to `aria-pressed`, keeping the remove control a sibling — nesting them would
167
+ be invalid HTML and would strand the inner control for keyboard users.
168
+ - **Lead slot**: `avatarSrc`, `avatarName` (initials fallback), `iconName`,
169
+ `symbolName` and `dot` convenience inputs, plus a `[tag-lead]` slot for
170
+ anything richer. Lead elements size from the chip height, and all are
171
+ `aria-hidden` so the chip's text stays its accessible name.
172
+ - **Glyphs are theme icon primitives, not Material ligatures.** The remove and
173
+ selected affordances resolve through the new `removeIcon` / `selectedIcon`
174
+ theme options (defaulting to the built-in `close` and `check`), so they mask
175
+ `currentColor`, recolour with the chip's tone, and can be swapped per theme.
176
+ They also contribute no text to the DOM, which a ligature does — one less way
177
+ for an accessible name to be polluted. `symbolName` remains as the escape
178
+ hatch for glyphs the theme's icon set doesn't carry.
179
+ - **`invalid`** sets `aria-invalid` and a dashed underline, so the state does
180
+ not rely on colour alone (WCAG 1.4.1); `disabled`, `maxWidth` truncation with
181
+ a `title`, and `removeLabel` for overriding the remove button's name.
182
+ - The component ships with 21 specs, having previously had none.
183
+
184
+ - [`6a4c7da`](https://github.com/uni-design-system/uni/commit/6a4c7da4c7c3f5758d8c66d1be714319c983e39e) Thanks [@gaenglish](https://github.com/gaenglish)! - Theme validation contract: `parseTheme` / `assertTheme` / `isUniTheme`
185
+
186
+ Every theme scale is `Partial<Record<…>>`, so a malformed theme — hand
187
+ written, generated, or fetched as JSON — rendered as silent `undefined` CSS
188
+ with no diagnosis. Core now ships a **dependency-free** structural validator
189
+ (core stays zero-dep; no zod outside the MCP):
190
+ - **`parseTheme(input): ThemeParseResult`** collects every issue rather than
191
+ failing fast: `{ success, theme?, issues: [{ path, message }] }` with dotted
192
+ paths like `colors.primary` or `typography.label.fontSize`. Acceptance /
193
+ rejection **with reasons** is the API.
194
+ - **`assertTheme(input)`** throws with all reasons; **`isUniTheme`** is the
195
+ type-guard form; **`formatThemeIssues`** renders issues for logs.
196
+ - The contract requires what components can't render without: 16
197
+ `REQUIRED_COLOR_TOKENS`, all 22 canonical `REQUIRED_TEXT_ROLES` (with
198
+ `fontFamily`/`fontSize`/`lineHeight`), the spacing/radii/shadow/thickness
199
+ scale keys, and recursively-valid component style expressions. All
200
+ `REQUIRED_*` sets are exported for tooling (MCP, docs, registries). Themes
201
+ remain free to add arbitrary named primitives on every scale.
202
+ - Because a `UniTheme` is plain serializable data, the same contract
203
+ validates JSON round-trips — the foundation for theme distribution and
204
+ runtime theme registries (ROADMAP Track 1).
205
+
206
+ - [`5414517`](https://github.com/uni-design-system/uni/commit/5414517d52d17943a4730752ab5d90c304c37062) Thanks [@gaenglish](https://github.com/gaenglish)! - A wire format for themes: `hydrateTheme` / `dehydrateTheme`
207
+
208
+ A `UniTheme` is plain serializable data, which makes JSON the natural transport
209
+ for distributing themes — but a serialized theme is ~50 KB and **~71% of that is
210
+ the 61 built-in icon data URIs that every uni-core consumer already ships**.
211
+ - **`dehydrateTheme(theme)`** returns the wire form: icons identical to
212
+ `BaseIcons` are dropped, genuine overrides kept. A theme goes from ~50 KB to
213
+ ~14 KB, and still passes `parseTheme`.
214
+ - **`hydrateTheme(theme)`** restores the built-in set on the way in, applying
215
+ the same `{...BaseIcons, ...icons}` contract `createTheme` applies at
216
+ construction — the theme's own icons win, built-ins fill the rest.
217
+ - **`generateUniThemes` now returns the `ContrastReport`** alongside the
218
+ light/dark pair, so a caller can generate registration-ready themes and their
219
+ WCAG audit in one call instead of falling back to `emitThemeFile` (which
220
+ returns TypeScript source) just to see the report.
221
+ - **`summarizeContrast(report)`** is extracted and exported, so the generated
222
+ theme file's header and every other consumer word the audit identically.
223
+
224
+ ## 7.3.0
225
+
226
+ ### Minor Changes
227
+
228
+ - [`c828982`](https://github.com/uni-design-system/uni/commit/c828982442e5bdc1d6884551160bbceac8ecf8f7) Thanks [@gaenglish](https://github.com/gaenglish)! - `expand` gains a themable, size-aware speed
229
+
230
+ The reveal/collapse duration was hardcoded at 350ms, so a consumer wanting a
231
+ snappier disclosure — or wanting adjacent styling to fade on the same clock —
232
+ had nothing to reference. A fixed duration also reads differently at different
233
+ sizes: sluggish on a two-line region, rushed on a full-page one.
234
+ - **New `transitionSpeed` option in the `expand` theme options** (seconds,
235
+ default `0.35`, matching alert/card `transitionSpeed`) sets the base
236
+ duration. `'expand'` joins core's `ComponentName` union, so custom and
237
+ derived themes can type an `expand` entry.
238
+ - **Duration scales with content height.** The actual duration is
239
+ `transitionSpeed × √(height ÷ 240px)`, clamped to ~0.15–0.6s at the default
240
+ speed (the envelope scales proportionally with a themed speed), so perceived
241
+ speed stays steady across region sizes. The curve and its constants
242
+ (`expandDuration`, `EXPAND_DEFAULT_SPEED`, `EXPAND_MIN_DURATION`,
243
+ `EXPAND_MAX_DURATION`, `EXPAND_REFERENCE_HEIGHT`) are exported from
244
+ uni-core.
245
+ - **New per-instance `transitionSpeed` input** sets an exact duration for one
246
+ region, bypassing the scaling: `<uni-expand [transitionSpeed]="0.15">`.
247
+ - **The resolved duration is exposed as the public `duration` signal** on
248
+ `uni-expand`, so adjacent styling can move on the reveal's clock with a
249
+ plain binding — `[style.transition-duration]="expand.duration() + 's'"` —
250
+ keeping all timing in the theme/signal pipeline, with no custom CSS.
251
+ - **`expand-toggle` gains a matching `transitionSpeed` input** and otherwise
252
+ follows the theme token instead of its own hardcoded 350ms. Expand Area
253
+ binds the region's resolved `duration` to its toggle, keeping chevron and
254
+ reveal on one clock even when size-scaled or overridden.
255
+ - **Speed is theme-reactive**: swapping themes at runtime retimes regions and
256
+ chevrons live.
257
+ - **Enter/leave easing is now `ease-in-out`** (was `ease-in`), matching the
258
+ chevron rotation so trigger and region decelerate together.
259
+ - **`UniExpandOptions` is exported** from uni-angular for
260
+ `getComponentOptions<UniExpandOptions>('expand')` consumers.
261
+
262
+ - [`6e8b429`](https://github.com/uni-design-system/uni/commit/6e8b4297190ce6974114d7fc6c52e37866902633) Thanks [@gaenglish](https://github.com/gaenglish)! - Menus join the theme model: `menu` + `menuItem` component options, item tones, and dividers
263
+
264
+ The menu was the only composite component with no theme surface of its own —
265
+ panel chrome came from the shared `dropdown` entry and every item-level knob
266
+ (height 38, `primary-container` hover, `check` active symbol, `label` type
267
+ role) was hardcoded. Every Uni menu therefore looked identical, and the gaps a
268
+ real product hits first (a red Delete, a separator before it, disabled rows)
269
+ were only reachable via `::ng-deep`.
270
+ - **New `menu` theme options** (`'menu'` joins core's `ComponentName` union):
271
+ `minWidth`, panel `color`/`border`/`borderRadius`/`shadow` (each falling
272
+ back to the `dropdown` options when unset, so menus follow generic popovers
273
+ until a theme deliberately splits them), `paddingVertical`/
274
+ `paddingHorizontal` (panel inset — `xs` inset plus item `borderRadius`
275
+ yields the "hover pill" look; `none` yields full-bleed rows), and
276
+ `dividerBorder`/`dividerSpacing` for separators.
277
+ - **New `menuItem` theme options + variants** (`'menuItem'` joins
278
+ `ComponentName`): `height`, `paddingHorizontal`, `gap`, `borderRadius`,
279
+ `typeface`, `textColor`, `hoverColor`, `activeSymbol` (undefined removes the
280
+ trailing check), and `transitionSpeed`. Theme `variants` on `menuItem` carry
281
+ tones — the base theme ships a `warn` tone for destructive actions.
282
+ - **`MenuItem` grows `variant`, `disabled`, and `{ divider: true }`.**
283
+ `variant: 'warn'` routes through the theme's `menuItem` variants; `disabled`
284
+ items render in the disabled color, carry `aria-disabled`, and are skipped
285
+ by keyboard navigation; dividers render as `role="separator"` rules styled
286
+ by the `menu` options. `isDivider` and the `UniMenuOptions`/
287
+ `UniMenuItemOptions` interfaces are exported from uni-angular.
288
+ - **`uni-dropdown` panel chrome is now input-overridable**
289
+ (`border`/`borderRadius`/`shadow`/`color`), falling back to the `dropdown`
290
+ theme options — this is the mechanism `uni-menu` uses; other consumers are
291
+ unchanged.
292
+ - **Default-rendering change:** menus now have `minWidth: 184` from the base
293
+ theme (previously they sized to the widest item). All other defaults
294
+ reproduce the previous look, including the 0.35s hover transition.
295
+ - **The Carbon experiment themes gain menu styling** (sharp full-bleed 40px
296
+ rows, IBM Plex, `$layer-hover`, red danger option, ~110ms motion) with a new
297
+ Carbon Menu story demonstrating that the same component renders both
298
+ aesthetics untouched.
299
+
300
+ ## 7.2.0
301
+
302
+ ### Minor Changes
303
+
304
+ - [`e72ec94`](https://github.com/uni-design-system/uni/commit/e72ec9410bb12d677c9c9102ccaf3e5bf4e8790e) Thanks [@gaenglish](https://github.com/gaenglish)! - Add `funnel` and `building` icons
305
+
306
+ Two gaps found while migrating a real app off inline SVG — both cases where the
307
+ existing set forced the app to keep hand-drawn artwork.
308
+ - **`funnel`** (`filter_alt`) — `filter` is `filter_list`, the stacked-lines
309
+ metaphor. Apps that name the feature itself a funnel ("Funnel Analytics") draw
310
+ the shape, and substituting stacked lines loses the reference.
311
+ - **`building`** (`apartment`) — pairs with `home` for residential-vs-commercial
312
+ distinctions, which had no built-in counterpart.
313
+
314
+ Both are Material Symbols Outlined 300 on the shared `0 -960 960 960` grid, so
315
+ the set is now 61 icons. Note `building` carries more internal detail than most
316
+ of the set; it reads well from ~14px but goes muddy below ~12px, so prefer a
317
+ larger size or `home`'s simpler silhouette in very small badges.
318
+
319
+ ## 7.1.0
320
+
321
+ ### Minor Changes
322
+
323
+ - [`c2421c0`](https://github.com/uni-design-system/uni/commit/c2421c04d01f36d74b489613d22997553f256d4f) Thanks [@gaenglish](https://github.com/gaenglish)! - Expand `BaseIcons` to 59 icons and add `uni-icon` sizing
324
+
325
+ The 34-icon set shipped in 7.0.0 covered the library's own components but not a
326
+ real application — an app migrating off inline `<svg>` (or off `uni-symbol`, in
327
+ Angular) ran out of names almost immediately. Everything here is additive; all
328
+ 34 existing names are unchanged.
329
+ - **25 new icons**, same Material Symbols Outlined 300 source and the same
330
+ `0 -960 960 960` grid as the rest of the set: navigation (`link`, `expand`,
331
+ `gridView`, `listView`), actions (`moreHorizontal`, `copy`, `send`,
332
+ `refresh`, `dragHandle`, `qrCode`), feedback (`star`, `verified`) and
333
+ user/system (`group`, `shieldPerson`, `clock`, `mail`, `chat`, `image`,
334
+ `document`, `payment`, `bank`, `trendingUp`, `extension`, `webhook`,
335
+ `logout`).
336
+ - `moreHorizontal` ships alongside `more` because that one is `more_vert` and
337
+ row-aligned menus need the horizontal kebab.
338
+ - `star` is separate from `favorite`, which is the heart.
339
+ - **`uni-icon` gains an optional `size` input** (`CssLength` — bare numbers are
340
+ px, strings pass through, so `20` and `'1.25rem'` both work). Left unset,
341
+ behaviour is unchanged: the icon fills its container, which is what lets a
342
+ themed control size its own glyph through padding. Set, it applies
343
+ width/height as inline styles, so an explicit size wins over the
344
+ fill-the-container rule regardless of style injection order. This removes the
345
+ per-call-site `width`/`height` CSS rule that replacing an inline `<svg>` with
346
+ `<uni-icon>` would otherwise need.
347
+ - The MCP `create-icon-tokens` tool now lists the built-in icon names above the
348
+ token map it returns. Apps routinely hand-draw their own `close`, `check` or
349
+ `plus`; encoding those into theme tokens works but adds redundant artwork on a
350
+ foreign grid when the theme already ships the glyph. The tool cannot recognise
351
+ a shape, so it hands the caller the list to check against before adding
352
+ anything.
353
+
354
+ ## 7.0.0
355
+
356
+ ### Major Changes
357
+
358
+ - [`f7727aa`](https://github.com/uni-design-system/uni/commit/f7727aa8895bd67f285ba4830858f994490bb7f7) Thanks [@gaenglish](https://github.com/gaenglish)! - Replace `BaseIcons` with a normalized 34-icon Material Symbols set
359
+
360
+ The built-in icon set was 11 glyphs drawn from four different grids
361
+ (`0 0 24 24`, `0 0 50 50`, `0 0 64 64`, `0 -960 960 960`), so `checkCircle` and
362
+ `xCircle` rendered as solid filled discs next to a hairline `alertCircle` and an
363
+ oversized `close` — three optical weights in one set.
364
+ - `BaseIcons` is now Material Symbols Outlined at weight 300, every glyph on a
365
+ single `0 -960 960 960` grid, grouped by intent: navigation (`menu`,
366
+ `chevronUp/Down/Left/Right`, `arrowLeft/Right`, `home`, `externalLink`),
367
+ actions (`search`, `close`, `plus`, `minus`, `more`, `delete`, `edit`,
368
+ `download`, `upload`, `share`, `filter`), feedback (`check`, `checkCircle`,
369
+ `xCircle`, `alertCircle`, `info`, `warning`, `lock`) and user/system
370
+ (`profile`, `settings`, `notification`, `favorite`, `help`, `calendar`).
371
+ - All 11 previously shipped names are preserved, so existing `<uni-icon>` call
372
+ sites and theme overrides keep working. Their **appearance changes**: the
373
+ filled `checkCircle`/`xCircle` are now outlined, and the set reads lighter
374
+ overall. `spinner` is carried over verbatim, keyframes intact.
375
+ - New `IconName` and `BaseIconName` types. `IconName` is
376
+ `BaseIconName | (string & {})`, so built-ins autocomplete and typos surface
377
+ while themes can still register arbitrary names via `createTheme({ icons })`.
378
+ `uni-icon`'s `name` input is typed with it instead of `keyof Icons`.
379
+ - **Breaking**: removes the unused `IconToken`, `IconStyle`, `ToggleableIcon`
380
+ and `IconConfig` exports. Nothing in the library referenced them and
381
+ `IconToken`'s 60-name union contradicted the actual `BaseIcons` keys. Use
382
+ `IconName` instead.
383
+ - New exported `svgToIconUri(svg, options?)` — the supported way to bring your
384
+ own icons (a brand set, a designer handoff) into a theme. It normalizes the
385
+ source (drops the xml declaration, comments and fixed `width`/`height` so the
386
+ mask scales), percent-encodes it, and **rejects artwork that cannot survive
387
+ masking**: no `viewBox`, an embedded raster `<image>`, an external reference,
388
+ a gradient/pattern fill, or more than one paint color. That last one is the
389
+ trap worth naming — masks use the alpha channel, so a multi-color logo
390
+ flattens to a silhouette in `currentColor`; pass `{ allowMultiColor: true }`
391
+ when that is intended. `createTheme({ icons })` merges the result over
392
+ `BaseIcons` per name, so a built-in name reskins and a new name is added.
393
+ - New MCP tool `create-icon-tokens`, the agent-facing front end to
394
+ `svgToIconUri`: hand it a project's inline `<svg>` (or a brand set) and it
395
+ returns a paste-ready `icons` map for `uni-theme.ts`. It encodes via the same
396
+ helper, so tokens are byte-identical to the built-in set, and adds the
397
+ set-level checks a per-icon helper cannot make — which names override
398
+ built-ins, and whether the artwork mixes viewBox grids. Bad artwork is
399
+ reported per icon rather than failing the batch, and re-running is safe
400
+ (already-encoded URIs pass through). The server instructions now point agents
401
+ at it whenever they encounter inline SVG.
402
+ - `BaseIcons` is generated by `scripts/generate-icons.mjs` (`pnpm icons:generate`),
403
+ which fetches from the canonical Material Symbols source and validates every
404
+ glyph before writing — a malformed path, an off-grid viewBox or geometry
405
+ outside the grid aborts the run. `pnpm icons:check` verifies the committed
406
+ file without writing. Add or change icons in that script's manifest and
407
+ regenerate; the file is not hand-edited. `icon.records.spec.ts` asserts the
408
+ same invariants against the committed output.
409
+
410
+ ## 6.1.0
411
+
412
+ ### Minor Changes
413
+
414
+ - [`28323ec`](https://github.com/uni-design-system/uni/commit/28323eca4ff239953de6dee9c34d34704627171e) Thanks [@gaenglish](https://github.com/gaenglish)! - TextStyle now accepts CSS length strings, not just px numbers
415
+ - `fontSize`, `lineHeight`, `letterSpacing`, `textIndent`, and `wordSpacing` on
416
+ `TextStyle` are widened from `number` to the new exported `CssLength`
417
+ (`number | string`). Bare numbers still mean px, matching Emotion's own
418
+ convention; strings pass through verbatim, so any unit the CSS engine accepts
419
+ works — `'1.2rem'`, `'2em'`, `'clamp(1rem, 2vw, 1.5rem)'`, unitless
420
+ line-height multipliers like `'1.5'`, etc.
421
+ - `toTypeface` no longer blindly appends `px`: numbers are converted, strings
422
+ are passed through untouched. Existing numeric themes are unaffected.
423
+
424
+ ## 6.0.1
425
+
426
+ ## 6.0.0
427
+
428
+ ### Minor Changes
429
+
430
+ - [`5a6fc60`](https://github.com/uni-design-system/uni/commit/5a6fc601952e0cbda80810a1c9062588c675d89f) Thanks [@gaenglish](https://github.com/gaenglish)! - Search input stripped back and made generic; debounce input dressed in the shared chrome
431
+ - **`uni-debounce-input`** now wears the themed input chrome via `uni-input-box`
432
+ (color, border, typeface, focus ring) and gains `label` (accessible name),
433
+ `placeholder`, `disabled`, `pre-input`/`post-input` attribute slots for adornments,
434
+ ARIA passthroughs (`role`, `ariaExpanded`, `ariaControls`, `ariaActivedescendant`)
435
+ for composite widgets, and `clear()`/`focus()` methods. Debounce behavior unchanged.
436
+ - **`uni-search-input` redesigned**: the opinionated solid-primary pill bar with the
437
+ embedded `title-large` label is gone. It's now a standard themed field — decorative
438
+ leading magnifier, clear button while a query exists (refocusing on clear), Enter
439
+ emits `search`, Escape closes/clears.
440
+ - **Type-ahead added**: pass `suggestions` (refresh from `change`) and the field
441
+ becomes an ARIA combobox — keyboard-navigable listbox (ArrowUp/Down, Enter selects,
442
+ emitting `suggestionSelected` + `search`), `aria-activedescendant` wiring, focus-out
443
+ closing. New `searchInput` theme options: `searchSymbol`, `clearSymbol`, suggestion
444
+ list `listColor`/`listShadow`/`listBorderRadius`, `maxSuggestions`.
445
+ - Visual breaking change for SearchInput consumers (deliberate strip-back); code API
446
+ is compatible (`label`/`width`/`change`/`search` retained; `label` is now the
447
+ accessible name + placeholder fallback rather than displayed text).
448
+
449
+ - [`8953d59`](https://github.com/uni-design-system/uni/commit/8953d59aa5f5eed57801534c7cbf5ff05453c316) Thanks [@gaenglish](https://github.com/gaenglish)! - Checkbox, radio, and toggle conform to theme tokens
450
+ - The last hardcoded control colors (`#FFF`, `#ccc`, `#d0d0d0`, `#e0e0e0`,
451
+ `rgba(0,0,0,0.2)`) are gone. Chrome now resolves from new option tokens —
452
+ checkbox `boxColor: 'surface'`; radio `ringColor: 'outline'` / `fillColor:
453
+ 'surface'` (radio gains a theme entry for the first time); toggle `trackColor:
454
+ 'surface-variant'` / `knobColor: 'surface'` — so all three finally render
455
+ correctly on dark and brand themes.
456
+ - The checkbox's check/dash strokes wear the variant's paired on-color
457
+ (`on-primary`, `on-warn`, …) instead of assuming white; disabled states use the
458
+ `disabled`/`on-disabled` tokens; the toggle knob's shadow is the theme's
459
+ brand-tinted `raised` stack, and its hover uses the button convention's
460
+ brightness filter instead of a fixed grey.
461
+
462
+ - [`7a6da4f`](https://github.com/uni-design-system/uni/commit/7a6da4f60979795f10e85493d1d13543f6a5a0e1) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-stat` KPI tile
463
+ - Muted label + large headline value (numbers auto-compact via
464
+ `Intl.NumberFormat`: `48234` → "48.2K") set in a new `stat` type-scale role
465
+ (32px semibold, proportional figures).
466
+ - Optional signed `delta` whose ink is decided by direction × `upIsGood` — churn
467
+ going down reads green, tickets going up reads red — with the arrow glyph
468
+ accompanied by screen-reader "up"/"down" text so state never rides color alone;
469
+ `caption` names the comparison period.
470
+ - Optional decorative 12-point sparkline (`trend` input): stroke in the outline hue,
471
+ endpoint dotted in the accent, `aria-hidden`.
472
+ - Fully token-driven via `stat.options`: card-recipe frame (`color`/`border`/
473
+ `borderRadius`), `labelTypeface`/`valueTypeface`, `positiveColor`/`negativeColor`
474
+ (the semantic inks, AA-guaranteed on surface), `trendColor`/`trendAccent`, spacing.
475
+
476
+ - [`fea0b2e`](https://github.com/uni-design-system/uni/commit/fea0b2e78da327f3acea144958af2d0dbbefb699) Thanks [@gaenglish](https://github.com/gaenglish)! - Fix invisible icons; icons become first-class theme primitives
477
+ - **Bug**: `uni-icon` resolves icons from `theme.icons`, but every theme shipped
478
+ `icons: {}` — the icon record was never wired in, so all icons (dialog close, button
479
+ spinner, search/clear affordances) rendered nothing.
480
+ - **Fix + pattern**: the default icon set now lives in core (`BaseIcons`, in
481
+ `concepts/iconography`) and `createTheme` merges a theme's `icons` over it — themes
482
+ can override or add icons under any name (inline SVG data URIs, masked with
483
+ `currentColor` so they recolor with the theme). The angular `icons` record re-exports
484
+ `BaseIcons` (deprecated).
485
+ - `uni-icon` also sets the standard `mask-image` (was webkit-only, so icons were
486
+ invisible in Firefox regardless) and renders nothing for unknown names instead of a
487
+ broken `url("undefined")`.
488
+ - The emitted `uni-theme.ts` gains an editable `icons` section, and the MCP's
489
+ `generate-uni-theme` guidance instructs agents: never inline SVG in components —
490
+ define an icon once in the theme's `icons` map and render it via `<uni-icon>`.
491
+
492
+ ## 5.2.0
493
+
494
+ ### Minor Changes
495
+
496
+ - [`1f5ee1d`](https://github.com/uni-design-system/uni/commit/1f5ee1dce290aceceec628677fdc9e36b5c5031f) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-avatar` and `uni-avatar-group` components
497
+ - Graceful fallback chain: image → initials derived from `name` (first + last) →
498
+ themed symbol. Accessible by default: `role="img"` + `aria-label` from the name,
499
+ `aria-hidden` when purely decorative.
500
+ - Fully token-driven: variants color from the role's `*-container` tokens, sizes from
501
+ the theme's `avatar` size records, corner radius from `options.borderRadius`
502
+ (`max` = circles; a `sharp` theme gets square avatars), initials typeface from
503
+ `options.typeface`, fallback symbol from `options.fallbackSymbol`.
504
+ - `uni-avatar-group` stacks avatars with a token-driven overlap (spacing token) and
505
+ separator ring (`ringColor`/`ringWidth` options); `max` collapses the overflow into
506
+ a themed "+N" chip that is itself a `uni-avatar` (verbatim `text` input).
507
+
508
+ - [`dca84b1`](https://github.com/uni-design-system/uni/commit/dca84b1f5a49d56608bac098c26b0e90992ed49d) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-breadcrumb` component
509
+ - WAI-ARIA breadcrumb: labelled `nav` landmark wrapping an ordered list, the last item
510
+ marked `aria-current="page"`, separators decorative (`aria-hidden`).
511
+ - Data-driven `items` (root first). Items with `href` render as real anchors; without
512
+ one they render as link-styled buttons emitting `itemClicked` for SPA routing.
513
+ - Token-driven via `breadcrumb.options`: `typeface`, link `color`, `currentColor`,
514
+ `separatorSymbol` (material symbol), and `gap` spacing.
515
+
516
+ - [`7844dde`](https://github.com/uni-design-system/uni/commit/7844ddec88f40027dafcaed5192f88d3c0cec54f) Thanks [@gaenglish](https://github.com/gaenglish)! - Button corner rounding and typography conform to component-options tokens
517
+ - `button` and `iconButton` themes gain `options: { borderRadius: 'max' }`; the hardcoded
518
+ per-size pixel radii (11/13/18/24) and the icon button's inline `borderRadius: 999` are
519
+ removed. Components resolve the token through `theme.radius()`, so the theme's radii
520
+ scale — shape languages (`sharp` → square, `playful` → pill) and custom radii
521
+ primitives — now restyles buttons like every other tokened component.
522
+ - Back-compat: the options radius is applied before theme `sizes`/`fixed` styles, so
523
+ hand-authored themes that still set a size-level `borderRadius` keep winning; icon
524
+ buttons fall back to the legacy circle when a theme predates iconButton options.
525
+ - `button` themes also gain `options: { typeface: 'button' }`: the hardcoded
526
+ `fontFamily: 'Euphemia'` (a font no theme loads) is removed from `fixed` and the sizes;
527
+ labels now render the type scale's `button` role (Red Hat Display, medium, capitalize),
528
+ with per-size `fontSize` still applied by `sizes`. Point the token at any typography
529
+ role — including custom ones — to restyle every button label.
530
+ - New `UniButtonOptions` interface (`borderRadius`, `typeface`) exported from the button
531
+ model.
532
+
533
+ - [`7844dde`](https://github.com/uni-design-system/uni/commit/7844ddec88f40027dafcaed5192f88d3c0cec54f) Thanks [@gaenglish](https://github.com/gaenglish)! - Card frame conforms to token primitives
534
+ - The card theme's hardcoded frame (`borderStyle`/`borderWidth`/`borderRadius: '8px'` in
535
+ `fixed`, `borderColor` per variant) is replaced by tokens: the card resolves its border
536
+ from the **border primitive named by its variant** (`borders.primary` …
537
+ `borders.success`), its corner radius from the radii scale (`options.borderRadius:
538
+ 'xs'` — same 8px by default), and an optional `options.elevation` shadow token.
539
+ - `UniCardOptions` gains `border` (pin every card to one primitive — including a custom
540
+ one — instead of variant-following) and documents the existing `borderRadius`/
541
+ `elevation`; redefining a border primitive in a theme now restyles cards and every
542
+ other component sharing that token.
543
+ - Token styles apply under the merged theme style, so hand-authored themes that still
544
+ set card `fixed`/`variants` frame styles keep winning. No visual change for default
545
+ themes — this is pure tokenization.
546
+
547
+ - [`17cf662`](https://github.com/uni-design-system/uni/commit/17cf6621dd78e4af0c8af3575f6a5d103d9fca53) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-drawer` and `uni-app-bar` — the dashboard shell
548
+ - **`uni-drawer`** has two modes sharing one content slot: `side` renders an in-flow
549
+ `<aside>` that pushes content (width-animated open/close, divider border primitive at
550
+ its edge, `aria-hidden` while closed); `over` renders a native `<dialog>` in the top
551
+ layer — focus trap, Escape, and scrim backdrop come from the platform, sliding in
552
+ from `position` (`start`/`end`). `open` is a two-way `model()`; Escape and backdrop
553
+ clicks keep it in sync. Tokens: `drawer.options` (`color`, `width`, `divider`,
554
+ `elevation`, `padding`, `backdrop`).
555
+ - **`uni-app-bar`**: leading/trailing content projection slots around a `title` (or
556
+ custom center content), trailing pushed to the far edge; optional `sticky`. Tokens:
557
+ `appBar.options` (`color`, `height`, `divider`, `typeface`, `padding`, `gap`,
558
+ optional `elevation`).
559
+ - The Drawer "DashboardShell" story documents the composition recipe: app bar with a
560
+ menu toggle + side drawer + content.
561
+
562
+ - [`7844dde`](https://github.com/uni-design-system/uni/commit/7844ddec88f40027dafcaed5192f88d3c0cec54f) Thanks [@gaenglish](https://github.com/gaenglish)! - Progress gauge derives from the palette instead of hardcoded pastels
563
+
564
+ The gauge's track colors were fixed pastel hexes (`#b3d4ea`, `#b3e7c2`, …) that ignored
565
+ the active theme entirely — secondary and success even shared the same green. Tracks now
566
+ use the role's `*-container` token (the palette's soft tint of that role) and arcs the
567
+ role base, so gauges follow any brand palette, in both light and dark modes.
568
+
569
+ - [`4ccf18b`](https://github.com/uni-design-system/uni/commit/4ccf18ba86dc81c344597c7bc3e46f5d92f3c232) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-skeleton` loading placeholder
570
+ - Three shapes: `text` (multi-line, ending on a short line like real copy), `rect`,
571
+ and `circle`; explicit `width`/`height` accept CSS strings or px numbers.
572
+ - Painted with surface tokens (`skeleton.options`: `color`, `highlightColor`,
573
+ `borderRadius`, `gap`) so placeholders sit naturally on light and dark themes; the
574
+ shimmer sweep (`animation`/`duration` options) only runs when the user allows motion
575
+ and degrades to static blocks under `prefers-reduced-motion`.
576
+ - `aria-hidden` — placeholders are invisible to assistive tech by design.
577
+
578
+ - [`5d9062a`](https://github.com/uni-design-system/uni/commit/5d9062ac36f782f2ebbc7012367121b0b86baf9c) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-slider` component — clears the declared-but-unbuilt `ComponentName` entry
579
+ - Native `<input type="range">` under the hood: platform keyboard interaction
580
+ (arrows, Home/End, Page Up/Down) and ARIA slider semantics for free; Signal Forms
581
+ contract (`FormValueControl<number>`) matching Input/Textarea.
582
+ - `min`/`max`/`step` inputs (fractional steps supported), required accessible `label`.
583
+ - Token-driven via `slider.options`: fill/thumb `color`, `trackColor`, `borderRadius`,
584
+ and geometry (`trackHeight`, `thumbSize`). The fill level rides a CSS custom
585
+ property, so dragging never regenerates Emotion styles; Firefox uses the native
586
+ `::-moz-range-progress`, WebKit a gradient stop.
587
+
588
+ - [`15a8f73`](https://github.com/uni-design-system/uni/commit/15a8f731f70bcd84211b7ff16b9272a2c6f2adc8) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-tabs` / `uni-tab` components
589
+ - Full WAI-ARIA tabs pattern: `tablist`/`tab`/`tabpanel` roles with wired
590
+ `aria-selected`/`aria-controls`/`aria-labelledby`, roving tabindex, automatic
591
+ activation on ArrowLeft/ArrowRight (wrapping, disabled tabs skipped), Home/End,
592
+ and motion-safe transitions.
593
+ - `selectedIndex` is a two-way `model()`; selection snaps to the nearest enabled tab.
594
+ Panel content is captured per-tab and only the selected panel is instantiated.
595
+ - Every visual knob is a theme option token (`tabs.options`): `typeface`, `textColor`,
596
+ `activeTextColor`, `indicatorColor`, `indicatorThickness` (thickness token),
597
+ `divider` (border primitive), `gap`/`padding` (spacing tokens), `borderRadius`, and
598
+ optional `activeColor` — the defaults render underline tabs; `borderRadius: 'max'` +
599
+ `activeColor` turns them into segmented pills with no component changes.
600
+
601
+ - [`b04b97b`](https://github.com/uni-design-system/uni/commit/b04b97bd109e36cbb8fcda31c73680d85509ab0a) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-textarea` component
602
+ - Multi-line text field with the same Signal Forms contract as `uni-input`
603
+ (`FormValueControl<string>`: `value` model, `disabled`/`invalid`/`dirty`/`required`,
604
+ touched-aware error styling, `aria-*` wiring).
605
+ - Reuses `uni-input-box` for its chrome, so it inherits the input's themed color,
606
+ border, typeface, focus outline, and disabled treatment automatically; the box's
607
+ field selectors now cover `textarea` (with auto height and vertical padding), and
608
+ the box accepts a `height` override.
609
+ - Theme options follow the token pattern: `textarea: { options: { rows: 3, resize:
610
+ 'vertical' } }` in the derived component themes; the `rows` input overrides per
611
+ instance.
612
+
613
+ - [`8f55b4c`](https://github.com/uni-design-system/uni/commit/8f55b4ca34bfa7006c5d55c895b323931a3d6a1b) Thanks [@gaenglish](https://github.com/gaenglish)! - Theme Builder becomes the full playground (PRD §5.2)
614
+ - **Light & dark side by side**: both palettes generate on every input change,
615
+ independent of the storybook-wide mode toggle, each rendered in its own panel.
616
+ - **Contrast report panel**: live pass/fail matrix over all checked pairs (summary +
617
+ failing rows always visible, full detail on demand). Failures only occur with hard
618
+ brand pins; the panel says so and points at soft targets.
619
+ - **Shape language input** (`sharp`/`modern`/`playful`): applies `ShapeRadii` live via
620
+ the new `radii` pass-through on `BrandPaletteConfig`/`createThemeFromPalette`.
621
+ - **Export paths**: copy the static `uni-theme.ts` (via `emitThemeFile`), copy a
622
+ pre-encoded `ng add` command, or copy W3C DTCG JSON for both modes via the new
623
+ `emitDtcgTokens()` interop emitter in uni-core (Style Dictionary compatible).
624
+
625
+ ## 5.1.0
626
+
627
+ ### Minor Changes
628
+
629
+ - [`c1dc853`](https://github.com/uni-design-system/uni/commit/c1dc853f762a27c466b033709ec84876b708995c) Thanks [@gaenglish](https://github.com/gaenglish)! - Brand-tinted, theme-scoped elevation shadows (PRD §3.5.C)
630
+ - New `generateShadows(colors, mode)`: light themes replace the dead-neutral
631
+ `rgba(0,0,0,…)` stacks with a shadow ink pulled toward the brand hue; dark themes go
632
+ near-zero (`raised: none`) with only a faint veil on floating overlays — elevation
633
+ reads from the surface lightness steps instead. The `warn` glow is tinted with the
634
+ theme's own error color in both modes.
635
+ - `generateThemes` now returns `lightShadows`/`darkShadows`; `generateUniThemes`,
636
+ `createThemeFromPalette` (Theme Builder), the emitted `uni-theme.ts`, and the shipped
637
+ stock Light/Dark themes all carry them.
638
+
639
+ ### Patch Changes
640
+
641
+ - [`f2951db`](https://github.com/uni-design-system/uni/commit/f2951db7f6267f29b56d127151ff843d445c40a4) Thanks [@gaenglish](https://github.com/gaenglish)! - Fix `ng add` against real published packages (found by fresh `ng new` e2e):
642
+ - The schematic bundle is CJS, but ng-packagr stamps `"type": "module"` into the
643
+ published package.json, so Node loaded it as ESM and the CLI reported "no ng add
644
+ actions". A nested `schematics/package.json` (`"type": "commonjs"`) scopes the
645
+ bundle back to CJS.
646
+ - The emitted `uni-theme.ts` used dot access on `Colors` (which has an index
647
+ signature), failing under `ng new`'s strict `noPropertyAccessFromIndexSignature`.
648
+ The emitter (and the MCP tool's guidance) now uses bracket access throughout.
649
+ - `uni-angular`'s peer range on `uni-core` is now `workspace:^` (publishes as `^5.x`)
650
+ instead of `workspace:*` (published as an exact pin). Alongside changesets'
651
+ `onlyUpdatePeerDependentsWhenOutOfRange`, this stops minor releases from being
652
+ inflated to majors by the peer-dependents rule — the cause of the 4.0.0 and 5.0.0
653
+ version jumps.
654
+
655
+ ## 5.0.0
656
+
657
+ ### Minor Changes
658
+
659
+ - [`f7f0bdd`](https://github.com/uni-design-system/uni/commit/f7f0bdddfac855955e022a852c2bfdccf8013a7b) Thanks [@gaenglish](https://github.com/gaenglish)! - OKLCH theme generation engine (`concepts/generation`)
660
+ - New `generateThemes(input)` / `generateUniThemes(input)`: brand seed(s) in, complete
661
+ WCAG-AA light+dark `Colors` pair out, with a machine-readable `ContrastReport`
662
+ (110 checked pairs per theme pair). Pure and deterministic.
663
+ - All palette math moved from HSL to perceptual OKLCH: uniform lightness slots across
664
+ hues, per-category chroma model (`CategoryChroma`), dark-mode accent chroma decoupling
665
+ (C ≤ 0.16), and a contrast guard-rail that adjusts lightness only — never hue.
666
+ - Brand colors ride as soft `targets` (kept verbatim when already AA, lightness-adjusted
667
+ when not) alongside the existing hard `brand` pins; accepted by `generatePalette`,
668
+ `createThemeFromPalette`, and Angular's `BrandPaletteConfig`.
669
+ - Semantic inks re-tuned per role: error 27°/C 0.20, warn 55°/C 0.18 (rotated off amber —
670
+ dark yellow reads brown), success 152°/C 0.16.
671
+ - `createTheme` accepts optional `radii`/`shadows` overrides; `ShapeRadii` presets
672
+ (`sharp` / `modern` / `playful`) emitted via the `shape` generation input.
673
+ - Theme Builder ships nine curated AA-clean presets built on soft targets.
674
+ - Deprecated (thin wrappers/tables retained): `uniColor`, `randomRangeValue`,
675
+ `CategorySaturation`, `CategoryLightness`. `schemeHues` moved to `color.utils`.
676
+ - `createTheme` deep-merges optional `borders` and `components` overrides over its
677
+ derived defaults — themes can define custom named primitives and rewire per-component
678
+ options without restating untouched sections.
679
+ - New `emitThemeFile()` renders a static `uni-theme.ts` — literal colors, visible border
680
+ primitives, sparse component overrides — the editable source of truth; the engine
681
+ never ships to the browser.
682
+ - New `ng add @uni-design-system/uni-angular` schematic: installs the peer set, writes
683
+ the generated theme file, registers `UNI_THEMES` in `app.config.ts`, adds typeface
684
+ links, scaffolds a themed smoke test, and prints the contrast summary.
685
+ - New MCP tool `generate-uni-theme`: returns the static theme file content, provider
686
+ snippet, and contrast report, with agent guidance to edit the file for restyling.
687
+ - `uni-core` gains its first Vitest suite, including a 1,080-seed contrast property
688
+ corpus with zero tolerance in both modes; the schematic gets its own spec suite.
689
+
690
+ ## 4.0.0
691
+
692
+ ### Minor Changes
693
+
694
+ - [`ef9b3b5`](https://github.com/uni-design-system/uni/commit/ef9b3b5d2c7bc68dc2a114b04b3960a759d631b9) Thanks [@gaenglish](https://github.com/gaenglish)! - Porting Components
695
+
696
+ ## 3.0.2
697
+
698
+ ## 3.0.1
699
+
700
+ ## 3.0.0
701
+
702
+ ## 2.0.4
703
+
704
+ ## 2.0.3
705
+
706
+ ## 2.0.2
707
+
708
+ ## 2.0.1
709
+
710
+ ### Patch Changes
711
+
712
+ - [`e2cad74`](https://github.com/uni-design-system/uni/commit/e2cad74631b3a9d2caf4816bcadedf19db99fec4) Thanks [@gaenglish](https://github.com/gaenglish)! - Fix: switch to bundled dual-package distribution via Vite to resolve strict ESM relative path failures
713
+
714
+ - [`be0fb26`](https://github.com/uni-design-system/uni/commit/be0fb2626c248fed37c2609d50eb1bfae40269e4) Thanks [@gaenglish](https://github.com/gaenglish)! - Setting Fixed Versioning for all packages.
715
+
716
+ ## 1.1.0
717
+
718
+ ### Minor Changes
719
+
720
+ - [`4a049de`](https://github.com/uni-design-system/uni/commit/4a049def689d56d6b6cc1d2da73c9facd93ed515) Thanks [@gaenglish](https://github.com/gaenglish)! - Adding cjs support
721
+
722
+ ## 1.0.0
723
+
724
+ ### Major Changes
725
+
726
+ - [`7d18157`](https://github.com/uni-design-system/uni/commit/7d18157cb131098688b70286513643423c37c756) Thanks [@gaenglish](https://github.com/gaenglish)! - init release