@uni-design-system/uni-angular 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,1015 @@
1
+ # @uni-design-system/uni-angular
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)! - `uni-combobox` — the form-bound, closed-set, single-select autocomplete. `FormValueControl<T | null>` over the cdk's object `Options<T>`, so it drops into Signal Forms via `[field]`; minimum usage is `<uni-combobox label="State" [options]="states" [(value)]="state" />`. Typing filters, it never selects: the draft commits on an active option, a unique exact label match, or (Enter only) a filter narrowed to one enabled option; anything else reverts and emits `(rejected)`. The value contract is identical to `uni-select` (`T | null`, `compareWith`), so swapping one for the other as a list grows is a template-only change. Filtering is local by default; `[filterLocally]="false"` renders `options` verbatim and the app narrows them from the debounced `(query)` output — the async/server-side story. Glyphs are theme icon primitives (`toggleIcon`/`clearIcon`/`selectedIcon`) rendered by `uni-icon`.
8
+
9
+ Supporting cdk changes, both non-breaking:
10
+ - `Option<T>` gains optional `description` (secondary line, read as part of the option's name) and `disabled` (visible and announced, not committable).
11
+ - `ListboxNavigation` accepts a `disabled?: (index) => boolean` config hook — arrows skip disabled options, `Home`/`End` land on the nearest enabled one, an all-disabled list never activates. Existing consumers pass nothing and behave identically.
12
+
13
+ - [`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.
14
+
15
+ 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`.
16
+
17
+ - [`6f87212`](https://github.com/uni-design-system/uni/commit/6f87212da2ed421a8a4f2e57047ebcba485c0fd3) Thanks [@gaenglish](https://github.com/gaenglish)! - Popover v2, `uni-callout`, and `uni-tour` — the coach-mark family from the popover prototype, plus shared overlay primitives in the cdk.
18
+
19
+ **`uni-popover` v2 (upgrade in place).** Everything v1 shipped keeps its exact behavior (`placement`, `autoClose`, the three methods — `togglePopover`'s event is now optional — and both slots), and the chrome now resolves from a real `popover` theme entry whose defaults match the old hardcoded look. New: a two-way `open` model; `mode="tooltip"` (hover/focus timers, `role="tooltip"` + `aria-describedby`, WCAG 1.4.13 hoverable/dismissable, dev warning on focusable content — `uni-tooltip` remains for the wrap-content case); a detached `anchor` input (element or id, resolved at open) so the panel can hug a field while the trigger keeps the disclosure ARIA — and with no trigger content, no element claims `aria-expanded` it doesn't deserve; structured anatomy (`header` + `closable` ✕, `[popover-header]`/`[popover-footer]` slots that collapse when empty); `arrow`, `maxWidth` (theme default `38ch` — the one visible change for long unwrapped content; set the theme's `maxWidth: 'none'` to opt out), `openDelay`/`closeDelay`, and `opened`/`closed` outputs. Focus follows the APG disclosure pattern: stays on the trigger unless the panel marks `[autofocus]`, returns on close.
20
+
21
+ **`uni-callout` (new).** An anchored coach mark that dims the page and cuts a spotlight hole around its target. Every scrim piece is CSS-anchor-positioned, so the hole tracks scroll/resize/layout with zero listeners, and the spotlit element stays genuinely clickable through it (`targetInteractive=false` covers it instead). Non-modal `role="dialog"` with the "duet" focus loop — Tab cycles the panel's controls plus the interactive target — and focus restore that respects a user who moved into the target. `backdrop: 'spotlight' | 'dim' | 'none'`, `dismissible`, `dismissOnBackdrop` (otherwise backdrop clicks pulse the panel), and a `dismissed` output with `reason` (`close-button`/`escape`/`backdrop`/`programmatic`) plus `key` — the storage-free "don't show again" hooks. Retargets in place when `target` changes while open, which is what the tour rides.
22
+
23
+ **`uni-tour` (new).** A thin sequencer over one callout: `steps` with per-step placement/backdrop/interactivity, a two-way deep-linkable `active` model, Back/Next/Done with dots-or-fraction progress, and `advanceOn` interaction gates — clicks auto-advance, anything else unlocks Next and announces it through one polite live region. Missing-target steps are skipped in the direction of travel with a dev warning. Escape/✕ (named by `skipLabel`) skip and report the step.
24
+
25
+ **cdk.** New `overlay` module (`TRANSFORM_ORIGINS`, `setAnchorName`/`clearAnchorName`, `resolveElement` — `''` is unset, `isToggleOpen`, `discreteOverlayTransition`, `restoreOverlayFocus`, `focusableElements`) extracted from the dropdown's plumbing (dropdown's public API unchanged); `spotlightStyles(anchor, { pad, ringWidth, scrimColor })` beside `anchorStyles`, returning the window/strips/cover pieces as pure `calc(anchor(...))` style objects.
26
+
27
+ **Arrow fix.** `anchorArrowStyles` now clips the rotated-square arrow to its outer half with a per-placement `clip-path` cut exactly on the panel-edge diagonal: the two border strokes previously ran the full square and visibly cut into the panel surface; they now terminate precisely at the panel edge, and the panel's own border line — which sits just outside that edge — falls inside the kept half, so the arrow background covers it and the base reads as an opening in the border. Tooltip, popover, and callout inherit the fix with no call-site changes.
28
+
29
+ ## 8.2.0
30
+
31
+ ### Minor Changes
32
+
33
+ - [`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.
34
+
35
+ - [`96113ee`](https://github.com/uni-design-system/uni/commit/96113eecc54f6d9a7dcf4d97264a4ee4f4367410) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-select`: `compareWith` input for object values.
36
+
37
+ The select matched `value` against option values with `===`, so an object value that was structurally but not referentially equal — a saved record matched against options from a fresh fetch — never matched, and the native select silently rendered the first option (or the placeholder) instead of the preselection. The new `compareWith` input, called as `compareWith(optionValue, value)` and defaulting to reference equality, lets object-valued selects pass a key comparison like `(a, b) => a?.id === b?.id`. Primitive values were and remain unaffected.
38
+
39
+ ### Patch Changes
40
+
41
+ - [`57a8c4c`](https://github.com/uni-design-system/uni/commit/57a8c4c73c852a6b14c2e2916cad9bd0a1566787) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-notification-badge`: a theme that omits the `offset` option no longer breaks badge positioning — the position values serialized as the invalid length `'undefinedpx'` and the badge lost its corner placement. Missing `offset` now falls back to `0`.
42
+
43
+ - [`92e5d5e`](https://github.com/uni-design-system/uni/commit/92e5d5e88787bca796325313e2c07b4f7351afcb) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-calendar` / `uni-date-input`: a bound `''` now counts as "no value".
44
+
45
+ An empty string — the only typeable empty for a string-typed model, and the natural "no value yet" in consumer code — slipped past the month path's nullish (`??`) guards into the grid math: `viewMonth` became `''`, the month heading threw `RangeError: Invalid time value` from `Intl.format` on every change-detection pass, and the grid rendered zero weeks. This hit on first render (the popup content projects eagerly), not just on open. The guards are now falsy, matching how `displayText`, `splitDateTime`, and the rest of the datetime path already treat `''`, so a calendar or date-input bound to `''` renders the current month exactly like `undefined`. `uni-time-input` and `uni-date-time-input` were already safe. Consumers no longer need to normalize `''` to `undefined` before binding — and note the `value` models were always typed `UniDate | undefined`, so no `$any()` cast is needed for a `string | undefined` draft signal.
46
+
47
+ - [`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`.
48
+
49
+ - [`96113ee`](https://github.com/uni-design-system/uni/commit/96113eecc54f6d9a7dcf4d97264a4ee4f4367410) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-menu`, `uni-multi-select`, `uni-data-table`: stop tracking loop collections by identity (NG0956).
50
+
51
+ `uni-menu` items, `uni-multi-select` options, and `uni-data-table` records tracked by object identity, so a consumer rebuilding the array each change-detection pass — the natural way to write `[menuItems]="[...]"` or re-fetch table rows — recreated every DOM node and tripped NG0956. They now track by `$index` (none of these collections carries a stable key: menu items may be templates or dividers, option values may be objects, records are arbitrary), and data-table columns track by their unique `columnDef`. Consumers no longer need to memoize a stable array; DOM nodes — including a focused menu item — survive a rebuild.
52
+
53
+ ## 8.1.0
54
+
55
+ ### Minor Changes
56
+
57
+ - [`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.
58
+
59
+ 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>`.
60
+ - **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.
61
+ - **`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.
62
+ - **`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.
63
+ - **`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`.
64
+ - **`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.
65
+ - **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.
66
+ - **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.
67
+ - **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.
68
+
69
+ - [`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.
70
+
71
+ **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.
72
+
73
+ **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.
74
+
75
+ **Also in this release:**
76
+ - 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.
77
+ - The radio's dot grow/retract animation is a token — `radio.options.transitionSpeed` (seconds, default 0.3 preserving the current feel; 0 switches instantly).
78
+ - 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.
79
+
80
+ 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.
81
+
82
+ 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.
83
+
84
+ ## 8.0.0
85
+
86
+ ### Major Changes
87
+
88
+ - [`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
89
+
90
+ v1 was a single hardcoded look with the palette welded into its template, an
91
+ unconditional remove button, and no theme entry — a filter chip, a status pill
92
+ and a recipient token all rendered identically. v2 makes the chip a real themed
93
+ component and the building block `uni-tag-input` composes.
94
+
95
+ **Breaking (uni-angular)**
96
+ - **`(close)` is now `(removed)`.** The old name shadowed the native `close`
97
+ event and needed an eslint suppression to compile.
98
+ - **The remove button is opt-in** — set `[removable]="true"`. Previously every
99
+ tag, including a plain category label, shipped a "Remove …" button into the
100
+ accessibility tree.
101
+ - Codemod: rename `(close)` → `(removed)` and add `[removable]="true"` wherever
102
+ a `(close)` handler exists.
103
+
104
+ **Fixed**
105
+ - **A falsy value can now be removed.** `handleClose` guarded with `if (v)`, so
106
+ a tag keyed `''` or `0` silently could not be dismissed. Every defined value
107
+ emits, and a tag with no value emits `undefined`.
108
+ - The remove control now sizes from the chip instead of the icon-button's own
109
+ `sm` size — it was 22px inside a 24px chip, and taller than an `sm` chip
110
+ entirely.
111
+
112
+ **New**
113
+ - **Two orthogonal style axes**: `variant` picks the colour role, `tone` picks
114
+ the archetype (`soft` / `solid` / `outline`). Both resolve from the new `tag`
115
+ theme entry, with tones as nested `&.tone-*` selectors inside each variant so
116
+ a theme author restyles both axes in one place.
117
+ - **`'tag'` joins core's `ComponentName`**, with a `TagTone` type and a full
118
+ theme entry (`options` for radius/typeface/gap/symbols, `variants` per colour
119
+ role, `sizes` carrying geometry only).
120
+ - **`interactive`** turns the chip body into a `<button>` with `selected` mapped
121
+ to `aria-pressed`, keeping the remove control a sibling — nesting them would
122
+ be invalid HTML and would strand the inner control for keyboard users.
123
+ - **Lead slot**: `avatarSrc`, `avatarName` (initials fallback), `iconName`,
124
+ `symbolName` and `dot` convenience inputs, plus a `[tag-lead]` slot for
125
+ anything richer. Lead elements size from the chip height, and all are
126
+ `aria-hidden` so the chip's text stays its accessible name.
127
+ - **Glyphs are theme icon primitives, not Material ligatures.** The remove and
128
+ selected affordances resolve through the new `removeIcon` / `selectedIcon`
129
+ theme options (defaulting to the built-in `close` and `check`), so they mask
130
+ `currentColor`, recolour with the chip's tone, and can be swapped per theme.
131
+ They also contribute no text to the DOM, which a ligature does — one less way
132
+ for an accessible name to be polluted. `symbolName` remains as the escape
133
+ hatch for glyphs the theme's icon set doesn't carry.
134
+ - **`invalid`** sets `aria-invalid` and a dashed underline, so the state does
135
+ not rely on colour alone (WCAG 1.4.1); `disabled`, `maxWidth` truncation with
136
+ a `title`, and `removeLabel` for overriding the remove button's name.
137
+ - The component ships with 21 specs, having previously had none.
138
+
139
+ - [`6a4c7da`](https://github.com/uni-design-system/uni/commit/6a4c7da4c7c3f5758d8c66d1be714319c983e39e) Thanks [@gaenglish](https://github.com/gaenglish)! - `ThemeService`: validated writes, runtime theme registration, live options
140
+
141
+ The theme registry was bootstrap-frozen and nothing validated theme writes —
142
+ and a brand theme applied via `applyPalette` never appeared in
143
+ `uni-theme-switch`, leaving the select on a bogus value with no way back.
144
+
145
+ **Breaking**
146
+ - `ThemeService.theme` is now a **readonly signal**. Direct `theme.set(...)`
147
+ calls (already discouraged in the docs) must move to `setTheme(input)`,
148
+ which validates and returns `ThemeParseResult` — acceptance or the complete
149
+ list of rejection reasons — leaving the active theme untouched on rejection.
150
+ - `selectTheme(name)` now returns `boolean` and, for an unknown name, **does
151
+ nothing** — previously it silently kept the old theme while still recording
152
+ the bad key in `selectedThemeKey` and localStorage.
153
+ - `themeOptions` is now a `computed` (was a once-written writable signal).
154
+
155
+ **New**
156
+ - **`registerTheme(input, { select? })`** validates and adds a theme to the
157
+ live registry — it appears in `themeOptions`/`uni-theme-switch` immediately.
158
+ **`unregisterTheme(id)`** removes it, falling back to the first remaining
159
+ theme when it was active. Themes injected via `UNI_THEMES` are validated at
160
+ startup; malformed entries are excluded with a console warning listing every
161
+ reason.
162
+ - **`applyPalette` registers the generated theme under `CustomTheme`**, so
163
+ "Your Brand" is an ordinary, selectable option: it shows in the switcher,
164
+ survives switching away and back, and `clearCustomPalette` unregisters it.
165
+ - **`uni-select` fix:** a selection pointing at an option added in the same
166
+ change-detection pass now applies (per-option `[selected]` binding; the
167
+ select-level `[value]` write landed before new options existed and was
168
+ ignored by the browser) — any consumer with a growing options list was
169
+ affected.
170
+
171
+ ### Minor Changes
172
+
173
+ - [`970c36d`](https://github.com/uni-design-system/uni/commit/970c36d3d2f57e3cc2ad18fc1b5498eb382c45d4) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-multi-select-dropdown`: a real accessible name, keyboard navigation, debounced filtering — and its first specs
174
+
175
+ This was the weakest component in the library on exactly the axes the roadmap's
176
+ combobox item names, and it had **no spec file at all**, which is why the
177
+ missing accessible name survived since it was written.
178
+ - **New `label` input, and the trigger finally names the field.** Its
179
+ accessible name now reads "Fruits, 2 selected, Apple, Cherry". Previously it
180
+ announced only the current selection — a screen reader user heard
181
+ "Option 1, Option 3" with no way to tell which field it belonged to. The
182
+ selection count comes along, so "how many did I pick" is not left to counting
183
+ commas.
184
+ - **Arrow keys, Home and End walk the options** from anywhere in the panel,
185
+ including the filter box. Reaching the last of thirty options previously
186
+ meant thirty `Tab` presses. Wrapping and the index arithmetic come from the
187
+ CDK's shared `ListboxNavigation`, so the keys behave exactly as they do in
188
+ `uni-search-input` and `uni-tag-input`.
189
+ - **The filter is debounced** (new `debounceTime`, default 200ms) instead of
190
+ re-filtering on every keystroke — the open item in TODO.md.
191
+ - **An empty filter result says so** through `role="status"`, rather than
192
+ leaving the panel blank.
193
+ - **The options are grouped** as a `role="group"` labelled from `label`.
194
+ - **20 specs**, covering the accessible name, selection and toggling, disabled
195
+ behaviour, filtering and debounce, keyboard navigation, and the form-control
196
+ contract.
197
+
198
+ **A deliberate non-change:** the options stay real checkboxes rather than
199
+ becoming a multi-selectable `listbox`. APG notes that multi-select listboxes are
200
+ handled inconsistently across screen readers and suggests a checkbox group
201
+ instead, and real checkboxes keep each option's state announced natively — so
202
+ converting would have traded a well-supported pattern for a fashionable one,
203
+ and duplicated the checkbox's animated visual into this component where it
204
+ would drift.
205
+
206
+ - [`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
207
+
208
+ The type-to-add chip field — recipients, filters, labels. It is the **open-set**
209
+ control: anything typed can become a token, whether or not it matches a
210
+ suggestion. Closed-set picking stays with `uni-select` /
211
+ `uni-multi-select-dropdown`, which is the deliberate split — the two look alike
212
+ but differ on the one thing that decides the value type, so a single component
213
+ would force one of them to carry an API it can never use.
214
+ - **`FormValueControl<UniTagItem[]>`.** `{ value: 'a@b.com' }` is a complete
215
+ item; `label`, `avatarSrc`, `invalid` and `disabled` are optional enrichment.
216
+ - **Invalid entries stay in the value, flagged**, rather than being dropped — a
217
+ field that silently swallows a typo'd address is worse than one that shows it
218
+ in red, and Enter on the chip lifts it back into the input to fix. Duplicates
219
+ and over-`max` entries are refused through `(rejected)` with a reason.
220
+ - **Full keyboard contract**: separators commit, Tab commits without trapping,
221
+ Backspace on an empty field _focuses_ the last chip rather than deleting it
222
+ blind, and on a chip Backspace and Delete both remove but move focus in
223
+ opposite directions — what makes pruning a list feel right.
224
+ - **The whole field is one tab stop.** Chips carry `tabindex="-1"`, so Tab never
225
+ walks through eight recipients to reach the next control.
226
+ - **Announces through a `role="status"` live region** on every add, remove and
227
+ rejection; the removal route is described once per field rather than by every
228
+ chip.
229
+ - **`preset="email"`** wires an address validator, a paste parser that unwraps
230
+ `Name <a@b.com>` and keeps an unterminated tail in the field, and Space as a
231
+ separator.
232
+ - **`'tagInput'` joins core's `ComponentName`** with a theme entry for the chip
233
+ field's own tokens. Field chrome is not duplicated — it comes from `input` via
234
+ `uni-input-box`, so a tag input restyles with every other field.
235
+
236
+ **Also in this release**
237
+ - **`ListboxNavigation` in the CDK** (`createListboxNavigation`) owns the
238
+ combobox bookkeeping every type-ahead control needs: open state, active
239
+ option, wrap-around arithmetic, Home/End, and `aria-activedescendant` wiring
240
+ that can never point at an option a narrowing filter has removed.
241
+ `uni-search-input` now uses it — behaviour unchanged, and it gains Home/End
242
+ for free — and the multi-select combobox upgrade will too. Three hand-rolled
243
+ copies of one keyboard contract is how they drift.
244
+ - **`uni-tag` gains `controlTabIndex`**, so a composite that owns its own roving
245
+ focus can take its chips out of the tab order, and **`selected` is now
246
+ optional**: an interactive chip carries `aria-pressed` only when it is
247
+ genuinely a toggle. Announcing "not pressed" on a recipient chip is worse than
248
+ announcing nothing.
249
+
250
+ - [`5414517`](https://github.com/uni-design-system/uni/commit/5414517d52d17943a4730752ab5d90c304c37062) Thanks [@gaenglish](https://github.com/gaenglish)! - Themes registered from JSON get the built-in icons back
251
+
252
+ `registerTheme` and `setTheme` now hydrate a theme's icons on the way in: the
253
+ built-in set is merged _under_ whatever icons the payload carries, so the
254
+ theme's own icons still win.
255
+
256
+ This is what makes a theme fetched as JSON usable. Transports elide `BaseIcons`
257
+ — roughly 71% of a serialized theme, and bytes the consumer already ships — so
258
+ without hydration a fetched theme would validate cleanly and then render no
259
+ icons at all. The rule is the same one `createTheme` already applies at
260
+ construction (`{...BaseIcons, ...icons}`), so a theme behaves identically
261
+ whether it was built in-process or arrived over the wire.
262
+
263
+ Themes provided through `UNI_THEMES` are unaffected: they already carry the full
264
+ set, so hydration is a no-op.
265
+
266
+ ### Patch Changes
267
+
268
+ - [`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
269
+
270
+ Opening a `uni-menu` by click left the first item highlighted before the
271
+ pointer had touched it, reading as a preselected default. Two correct
272
+ behaviours combined badly: `onOpened()` implements ARIA roving focus by calling
273
+ `.focus()` on an item every open, and the item styles deliberately painted
274
+ `:focus` the same as `:hover`. Nothing was wrong with the focus itself — only
275
+ with painting it after a pointer open.
276
+ - **The highlight now keys on `:focus-visible`.** Programmatic focus following a
277
+ click doesn't match it, while keyboard-driven focus does — so mouse users get
278
+ no phantom highlight, and keyboard users keep the focus cursor. Roving-focus
279
+ bookkeeping is untouched: a mouse open still moves focus to the first item, so
280
+ screen readers announce it exactly as before.
281
+ - **New `HOVER_OR_KEYBOARD_FOCUS` constant exported from uni-core**, holding
282
+ that selector. Emotion merges styles by _exact selector text_, so a component's
283
+ base rule and any theme variant restyling it have to agree character for
284
+ character — a variant keyed `'&:hover, &:focus'` would both fail to override
285
+ and reintroduce the phantom highlight. Naming the selector once removes the
286
+ trap; the base theme's `menuItem.warn` tone and the Carbon/Wellsourced
287
+ showcase themes now use it.
288
+
289
+ Themes with their own `menuItem` variants should key the highlight with
290
+ `HOVER_OR_KEYBOARD_FOCUS`. Note that `:focus-visible` is a browser heuristic:
291
+ after a user has been navigating by keyboard, a subsequent click may still show
292
+ the highlight, which is the intended "this person is using the keyboard"
293
+ behaviour rather than a regression.
294
+
295
+ - Updated dependencies [[`3279186`](https://github.com/uni-design-system/uni/commit/3279186e2cee806056cfb446699ab108d30fb608), [`de83fe4`](https://github.com/uni-design-system/uni/commit/de83fe497a2f48569a7c49eea2e7ca640d931254), [`760b761`](https://github.com/uni-design-system/uni/commit/760b761ad77533e7e01f7a56731f74e470bbd948), [`6a4c7da`](https://github.com/uni-design-system/uni/commit/6a4c7da4c7c3f5758d8c66d1be714319c983e39e), [`5414517`](https://github.com/uni-design-system/uni/commit/5414517d52d17943a4730752ab5d90c304c37062)]:
296
+ - @uni-design-system/uni-core@8.0.0
297
+
298
+ ## 7.3.0
299
+
300
+ ### Minor Changes
301
+
302
+ - [`c828982`](https://github.com/uni-design-system/uni/commit/c828982442e5bdc1d6884551160bbceac8ecf8f7) Thanks [@gaenglish](https://github.com/gaenglish)! - `expand` gains a themable, size-aware speed
303
+
304
+ The reveal/collapse duration was hardcoded at 350ms, so a consumer wanting a
305
+ snappier disclosure — or wanting adjacent styling to fade on the same clock —
306
+ had nothing to reference. A fixed duration also reads differently at different
307
+ sizes: sluggish on a two-line region, rushed on a full-page one.
308
+ - **New `transitionSpeed` option in the `expand` theme options** (seconds,
309
+ default `0.35`, matching alert/card `transitionSpeed`) sets the base
310
+ duration. `'expand'` joins core's `ComponentName` union, so custom and
311
+ derived themes can type an `expand` entry.
312
+ - **Duration scales with content height.** The actual duration is
313
+ `transitionSpeed × √(height ÷ 240px)`, clamped to ~0.15–0.6s at the default
314
+ speed (the envelope scales proportionally with a themed speed), so perceived
315
+ speed stays steady across region sizes. The curve and its constants
316
+ (`expandDuration`, `EXPAND_DEFAULT_SPEED`, `EXPAND_MIN_DURATION`,
317
+ `EXPAND_MAX_DURATION`, `EXPAND_REFERENCE_HEIGHT`) are exported from
318
+ uni-core.
319
+ - **New per-instance `transitionSpeed` input** sets an exact duration for one
320
+ region, bypassing the scaling: `<uni-expand [transitionSpeed]="0.15">`.
321
+ - **The resolved duration is exposed as the public `duration` signal** on
322
+ `uni-expand`, so adjacent styling can move on the reveal's clock with a
323
+ plain binding — `[style.transition-duration]="expand.duration() + 's'"` —
324
+ keeping all timing in the theme/signal pipeline, with no custom CSS.
325
+ - **`expand-toggle` gains a matching `transitionSpeed` input** and otherwise
326
+ follows the theme token instead of its own hardcoded 350ms. Expand Area
327
+ binds the region's resolved `duration` to its toggle, keeping chevron and
328
+ reveal on one clock even when size-scaled or overridden.
329
+ - **Speed is theme-reactive**: swapping themes at runtime retimes regions and
330
+ chevrons live.
331
+ - **Enter/leave easing is now `ease-in-out`** (was `ease-in`), matching the
332
+ chevron rotation so trigger and region decelerate together.
333
+ - **`UniExpandOptions` is exported** from uni-angular for
334
+ `getComponentOptions<UniExpandOptions>('expand')` consumers.
335
+
336
+ - [`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
337
+
338
+ The menu was the only composite component with no theme surface of its own —
339
+ panel chrome came from the shared `dropdown` entry and every item-level knob
340
+ (height 38, `primary-container` hover, `check` active symbol, `label` type
341
+ role) was hardcoded. Every Uni menu therefore looked identical, and the gaps a
342
+ real product hits first (a red Delete, a separator before it, disabled rows)
343
+ were only reachable via `::ng-deep`.
344
+ - **New `menu` theme options** (`'menu'` joins core's `ComponentName` union):
345
+ `minWidth`, panel `color`/`border`/`borderRadius`/`shadow` (each falling
346
+ back to the `dropdown` options when unset, so menus follow generic popovers
347
+ until a theme deliberately splits them), `paddingVertical`/
348
+ `paddingHorizontal` (panel inset — `xs` inset plus item `borderRadius`
349
+ yields the "hover pill" look; `none` yields full-bleed rows), and
350
+ `dividerBorder`/`dividerSpacing` for separators.
351
+ - **New `menuItem` theme options + variants** (`'menuItem'` joins
352
+ `ComponentName`): `height`, `paddingHorizontal`, `gap`, `borderRadius`,
353
+ `typeface`, `textColor`, `hoverColor`, `activeSymbol` (undefined removes the
354
+ trailing check), and `transitionSpeed`. Theme `variants` on `menuItem` carry
355
+ tones — the base theme ships a `warn` tone for destructive actions.
356
+ - **`MenuItem` grows `variant`, `disabled`, and `{ divider: true }`.**
357
+ `variant: 'warn'` routes through the theme's `menuItem` variants; `disabled`
358
+ items render in the disabled color, carry `aria-disabled`, and are skipped
359
+ by keyboard navigation; dividers render as `role="separator"` rules styled
360
+ by the `menu` options. `isDivider` and the `UniMenuOptions`/
361
+ `UniMenuItemOptions` interfaces are exported from uni-angular.
362
+ - **`uni-dropdown` panel chrome is now input-overridable**
363
+ (`border`/`borderRadius`/`shadow`/`color`), falling back to the `dropdown`
364
+ theme options — this is the mechanism `uni-menu` uses; other consumers are
365
+ unchanged.
366
+ - **Default-rendering change:** menus now have `minWidth: 184` from the base
367
+ theme (previously they sized to the widest item). All other defaults
368
+ reproduce the previous look, including the 0.35s hover transition.
369
+ - **The Carbon experiment themes gain menu styling** (sharp full-bleed 40px
370
+ rows, IBM Plex, `$layer-hover`, red danger option, ~110ms motion) with a new
371
+ Carbon Menu story demonstrating that the same component renders both
372
+ aesthetics untouched.
373
+
374
+ ### Patch Changes
375
+
376
+ - [`ba4e38d`](https://github.com/uni-design-system/uni/commit/ba4e38d88ad65bbba8badf2b42115acfd4d433e2) Thanks [@gaenglish](https://github.com/gaenglish)! - Tooltip no longer blinks after activating a wrapped control
377
+
378
+ Clicking a button inside `uni-tooltip` used to fall through to the bubble's
379
+ tap-to-toggle handler and then re-show on the still-hovering pointer — a
380
+ state-flipping label ("Expand" → "Collapse") visibly blinked off and back on
381
+ after the click.
382
+ - **Activating an interactive element inside the host now hides the bubble
383
+ and suppresses it** until the pointer leaves and returns (or focus moves
384
+ away). Tap-to-toggle is unchanged for non-interactive hosts (inline text).
385
+ - **`expand-toggle` drops its icon-only tooltip.** The rotating chevron plus
386
+ `aria-expanded` and the button's accessible name already say everything the
387
+ bubble restated; use the `label` input when a disclosure needs a visible
388
+ name.
389
+
390
+ ## 7.2.0
391
+
392
+ ### Minor Changes
393
+
394
+ - [`8d77ed7`](https://github.com/uni-design-system/uni/commit/8d77ed75af1edf414baa7b9ea52a6982470b0c7c) Thanks [@gaenglish](https://github.com/gaenglish)! - Centre `icon-button`'s glyph, fix `expand-toggle`'s rotation, make `expand`
395
+ block-level and motion-safe, and give `expand-toggle` a label
396
+
397
+ Gaps found while building collapsible sections in a consuming app, where each
398
+ one had to be worked around locally.
399
+ - **`icon-button` centres its glyph.** Sizing `iconName` from the size token
400
+ (shipped in the previous release) made the glyph smaller than the button —
401
+ an `sm` button is a 22px box around an 18px icon — but the button was
402
+ `display: block`, so the glyph sat in the top-left corner with all the slack
403
+ on its right and bottom. It is now a centring flex box; flex is still
404
+ block-level, so the button's own layout is unchanged, and the
405
+ absolutely-positioned accessible-name span stays out of the flex flow.
406
+ **Visual change** for every `icon-button`: glyphs shift to the middle of the
407
+ box. `symbolName` ligatures centre too, which also means the known
408
+ oversized-ligature case (a 24px glyph in a 22px `sm` box) now clips evenly on
409
+ all sides instead of only bottom-right. Covered by a test.
410
+ - **`expand-toggle` rotates the glyph instead of its host.** This is a fix to
411
+ existing behaviour, visible in 7.1.0 and earlier: the 180° turn was applied to
412
+ the component host, which is both the tooltip's positioning box (`uni-tooltip`
413
+ sets `anchor-name` on its own element, nested inside the host) and taller than
414
+ the glyph, since an inline-level box reserves baseline descender space. So the
415
+ bubble bobbed along an arc as the chevron turned, and the chevron itself
416
+ drifted off-centre rather than spinning in place. The transform now lands on
417
+ `uni-icon` — a centred square sized to the glyph, and the only box here that
418
+ rotates symmetrically. The host keeps its `toggled` attribute, so any consumer
419
+ styling keyed on it still works.
420
+ - **`uni-expand` is now `display: block`.** As a custom element it defaulted to
421
+ `display: inline`, so its animated grid laid out as a block-in-inline box and
422
+ the revealed content's spacing came out subtly wrong. Every consumer was
423
+ writing `uni-expand { display: block }` by hand. **Visual change** for anyone
424
+ who was relying on the inline default or already shipping that override — the
425
+ override is now redundant and can be deleted.
426
+ - **The reveal respects `prefers-reduced-motion`** (WCAG 2.3.3). The
427
+ expand/collapse keyframes ran unconditionally; they're now wrapped in
428
+ `motionSafe`, as is `expand-toggle`'s chevron rotation. Under reduced motion
429
+ the region appears and disappears instantly. `overflow: hidden` moved inside
430
+ the guard deliberately: it exists to clip the box mid-animation, and leaving
431
+ it applied at rest would crop decorations that legitimately paint outside the
432
+ region (focus rings, offset outlines). Angular removes a leaving node on the
433
+ next frame when it detects no animation, so nothing hangs.
434
+ - **`expand-toggle` takes `label` and `sublabel`.** It was chevron-only, so any
435
+ disclosure that names its section — most of them — had to hand-roll the whole
436
+ trigger row and its styles, which is how consumers end up with a private
437
+ copy of this component. With `label` set the toggle renders a full-width row
438
+ (chevron, label, muted qualifier) as a single button whose accessible name is
439
+ the label, instead of an icon button sitting next to unrelated text. Only the
440
+ chevron rotates, so the label stays upright. Omit `label` and the icon-only
441
+ shape is unchanged, tooltip and all — `uni-expand-area` is unaffected.
442
+
443
+ - [`c532c95`](https://github.com/uni-design-system/uni/commit/c532c95d4a7adbf7c4015b8b556e3994c70948ed) Thanks [@gaenglish](https://github.com/gaenglish)! - Size `icon-button`'s `iconName` glyph from its size token
444
+
445
+ `icon-button` rendered a `symbolName` ligature at a font size, but an `iconName`
446
+ mask with no size at all — and the base `iconButton` size tokens carry no
447
+ padding, so the icon filled the entire button box edge to edge. That made
448
+ `symbolName` → `iconName` a visual regression rather than a like-for-like swap,
449
+ which matters now that `uni-icon` is the preferred path (a mask paints on the
450
+ first frame, where a ligature waits on the variable font).
451
+ - `iconName` is now sized from the size token's `fontSize` — the same value that
452
+ scales the rest of the control — so the glyph sits inside the button and grows
453
+ with `size`. Concretely, `size="sm"` renders an 18px glyph in a 22px button
454
+ instead of a 22px one.
455
+ - **Visual change** for existing `iconName` call sites: glyphs get slightly
456
+ smaller and gain breathing room. Themes that size icon-buttons with padding
457
+ (e.g. the Carbon example) set a matching `fontSize`, so they land on the same
458
+ glyph size either way and are unaffected.
459
+ - Known gap, now covered by a test that documents rather than blesses it:
460
+ `uni-symbol` takes its size from `opticalSize` (default 24) and ignores the
461
+ button's size token, so a `sm` button renders a 24px ligature in a 22px box.
462
+ Masked icons do not have that problem — one more reason to prefer `iconName`.
463
+
464
+ ### Patch Changes
465
+
466
+ - [`c532c95`](https://github.com/uni-design-system/uni/commit/c532c95d4a7adbf7c4015b8b556e3994c70948ed) Thanks [@gaenglish](https://github.com/gaenglish)! - Fix `uni-icon` ignoring `size="24"` written as a static attribute
467
+
468
+ `size` appended `px` only to numbers, but a static template attribute arrives as
469
+ a string — so `<uni-icon name="mail" size="24" />` emitted the invalid
470
+ `width: 24`. Browsers drop an invalid declaration, which silently fell back to
471
+ the stylesheet's `width: 100%; height: 100%`.
472
+
473
+ That failed in two different ways depending on the container, neither of them
474
+ obvious: inside a fixed box the icon quietly filled it (a 16px glyph rendering
475
+ at 30px), and inside a content-sized flex row `height: 100%` collapsed to zero,
476
+ so the icon vanished while still occupying full width and pushing its label
477
+ across the row.
478
+
479
+ A bare numeric string is now treated as px, matching the number form. Genuine
480
+ CSS lengths (`'1.25rem'`, `'50%'`, `calc(…)`) still pass through untouched.
481
+ `[size]="24"` was unaffected throughout, since property binding passes a real
482
+ number.
483
+
484
+ ## 7.1.0
485
+
486
+ ### Minor Changes
487
+
488
+ - [`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
489
+
490
+ The 34-icon set shipped in 7.0.0 covered the library's own components but not a
491
+ real application — an app migrating off inline `<svg>` (or off `uni-symbol`, in
492
+ Angular) ran out of names almost immediately. Everything here is additive; all
493
+ 34 existing names are unchanged.
494
+ - **25 new icons**, same Material Symbols Outlined 300 source and the same
495
+ `0 -960 960 960` grid as the rest of the set: navigation (`link`, `expand`,
496
+ `gridView`, `listView`), actions (`moreHorizontal`, `copy`, `send`,
497
+ `refresh`, `dragHandle`, `qrCode`), feedback (`star`, `verified`) and
498
+ user/system (`group`, `shieldPerson`, `clock`, `mail`, `chat`, `image`,
499
+ `document`, `payment`, `bank`, `trendingUp`, `extension`, `webhook`,
500
+ `logout`).
501
+ - `moreHorizontal` ships alongside `more` because that one is `more_vert` and
502
+ row-aligned menus need the horizontal kebab.
503
+ - `star` is separate from `favorite`, which is the heart.
504
+ - **`uni-icon` gains an optional `size` input** (`CssLength` — bare numbers are
505
+ px, strings pass through, so `20` and `'1.25rem'` both work). Left unset,
506
+ behaviour is unchanged: the icon fills its container, which is what lets a
507
+ themed control size its own glyph through padding. Set, it applies
508
+ width/height as inline styles, so an explicit size wins over the
509
+ fill-the-container rule regardless of style injection order. This removes the
510
+ per-call-site `width`/`height` CSS rule that replacing an inline `<svg>` with
511
+ `<uni-icon>` would otherwise need.
512
+ - The MCP `create-icon-tokens` tool now lists the built-in icon names above the
513
+ token map it returns. Apps routinely hand-draw their own `close`, `check` or
514
+ `plus`; encoding those into theme tokens works but adds redundant artwork on a
515
+ foreign grid when the theme already ships the glyph. The tool cannot recognise
516
+ a shape, so it hands the caller the list to check against before adding
517
+ anything.
518
+
519
+ ## 7.0.0
520
+
521
+ ### Minor Changes
522
+
523
+ - [`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
524
+
525
+ The built-in icon set was 11 glyphs drawn from four different grids
526
+ (`0 0 24 24`, `0 0 50 50`, `0 0 64 64`, `0 -960 960 960`), so `checkCircle` and
527
+ `xCircle` rendered as solid filled discs next to a hairline `alertCircle` and an
528
+ oversized `close` — three optical weights in one set.
529
+ - `BaseIcons` is now Material Symbols Outlined at weight 300, every glyph on a
530
+ single `0 -960 960 960` grid, grouped by intent: navigation (`menu`,
531
+ `chevronUp/Down/Left/Right`, `arrowLeft/Right`, `home`, `externalLink`),
532
+ actions (`search`, `close`, `plus`, `minus`, `more`, `delete`, `edit`,
533
+ `download`, `upload`, `share`, `filter`), feedback (`check`, `checkCircle`,
534
+ `xCircle`, `alertCircle`, `info`, `warning`, `lock`) and user/system
535
+ (`profile`, `settings`, `notification`, `favorite`, `help`, `calendar`).
536
+ - All 11 previously shipped names are preserved, so existing `<uni-icon>` call
537
+ sites and theme overrides keep working. Their **appearance changes**: the
538
+ filled `checkCircle`/`xCircle` are now outlined, and the set reads lighter
539
+ overall. `spinner` is carried over verbatim, keyframes intact.
540
+ - New `IconName` and `BaseIconName` types. `IconName` is
541
+ `BaseIconName | (string & {})`, so built-ins autocomplete and typos surface
542
+ while themes can still register arbitrary names via `createTheme({ icons })`.
543
+ `uni-icon`'s `name` input is typed with it instead of `keyof Icons`.
544
+ - **Breaking**: removes the unused `IconToken`, `IconStyle`, `ToggleableIcon`
545
+ and `IconConfig` exports. Nothing in the library referenced them and
546
+ `IconToken`'s 60-name union contradicted the actual `BaseIcons` keys. Use
547
+ `IconName` instead.
548
+ - New exported `svgToIconUri(svg, options?)` — the supported way to bring your
549
+ own icons (a brand set, a designer handoff) into a theme. It normalizes the
550
+ source (drops the xml declaration, comments and fixed `width`/`height` so the
551
+ mask scales), percent-encodes it, and **rejects artwork that cannot survive
552
+ masking**: no `viewBox`, an embedded raster `<image>`, an external reference,
553
+ a gradient/pattern fill, or more than one paint color. That last one is the
554
+ trap worth naming — masks use the alpha channel, so a multi-color logo
555
+ flattens to a silhouette in `currentColor`; pass `{ allowMultiColor: true }`
556
+ when that is intended. `createTheme({ icons })` merges the result over
557
+ `BaseIcons` per name, so a built-in name reskins and a new name is added.
558
+ - New MCP tool `create-icon-tokens`, the agent-facing front end to
559
+ `svgToIconUri`: hand it a project's inline `<svg>` (or a brand set) and it
560
+ returns a paste-ready `icons` map for `uni-theme.ts`. It encodes via the same
561
+ helper, so tokens are byte-identical to the built-in set, and adds the
562
+ set-level checks a per-icon helper cannot make — which names override
563
+ built-ins, and whether the artwork mixes viewBox grids. Bad artwork is
564
+ reported per icon rather than failing the batch, and re-running is safe
565
+ (already-encoded URIs pass through). The server instructions now point agents
566
+ at it whenever they encounter inline SVG.
567
+ - `BaseIcons` is generated by `scripts/generate-icons.mjs` (`pnpm icons:generate`),
568
+ which fetches from the canonical Material Symbols source and validates every
569
+ glyph before writing — a malformed path, an off-grid viewBox or geometry
570
+ outside the grid aborts the run. `pnpm icons:check` verifies the committed
571
+ file without writing. Add or change icons in that script's manifest and
572
+ regenerate; the file is not hand-edited. `icon.records.spec.ts` asserts the
573
+ same invariants against the committed output.
574
+
575
+ ### Patch Changes
576
+
577
+ - Updated dependencies [[`f7727aa`](https://github.com/uni-design-system/uni/commit/f7727aa8895bd67f285ba4830858f994490bb7f7)]:
578
+ - @uni-design-system/uni-core@7.0.0
579
+
580
+ ## 6.1.0
581
+
582
+ ## 6.0.1
583
+
584
+ ### Patch Changes
585
+
586
+ - [`4bfb057`](https://github.com/uni-design-system/uni/commit/4bfb057c55651c0375e7f35dd6f804e8d5c69e88) Thanks [@gaenglish](https://github.com/gaenglish)! - Canonical MDX docs structure across the entire library
587
+ - Component docs pages now follow one flow (spec in AGENTS.md): imports → Overview →
588
+ Usage (the compact playground: story + source + knobs) → named variation examples →
589
+ **Theme options** → Accessibility → Do/Don't. Property/API/Methods/Events tables are
590
+ retired — the playground's controls are the API reference.
591
+ - New `ThemeOptions` docs block renders a component's per-theme option tokens live from
592
+ the active Storybook theme (with color swatches), distinguishing per-theme options
593
+ from per-instance inputs; data-table's raw `ThemeDataBlock` dump is replaced.
594
+ - All ~55 component pages conform: the 7 form-control pages (200–390 lines of legacy
595
+ API tables) rewritten; empty Overviews authored (card, tag, icon-button, scroll-area,
596
+ expand-area, data-table); accessibility bullets added across navigation, feedback,
597
+ and form components.
598
+ - The MCP index now carries when-to-use guidance for 55 of 70 components (was 41) and
599
+ accessibility guidance for 36 (was 21) — the remainder are subcomponents documented
600
+ on their parent pages and internal directives.
601
+
602
+ ## 6.0.0
603
+
604
+ ### Major Changes
605
+
606
+ - [`a02afee`](https://github.com/uni-design-system/uni/commit/a02afee3c1c4721ff626445684f41de432443731) Thanks [@gaenglish](https://github.com/gaenglish)! - Layout components are attribute-only, on any element
607
+
608
+ **Breaking:** the shorthand element selectors `Box`, `Stack`, `Row`, `Grid`, `Wrap`,
609
+ and `GridArea` are removed — a concept inherited from another library that conflicted
610
+ with semantic HTML. Migrate to the attribute form: `<Box padding="md">` →
611
+ `<div box-layout padding="md">` (GridArea → `grid-area-layout`).
612
+
613
+ In exchange, the attribute selectors now apply to **any element**, not just `div` —
614
+ layout and semantics compose: `<main box-layout [grow]="1" padding="md">`,
615
+ `<nav stack-layout gap="sm">`, `<section stack-layout>`.
616
+
617
+ Also documented, unchanged in behavior: the sizing convention (number = px via
618
+ binding, `[height]="420"`; plain attribute = CSS length, `height="420px"`) and
619
+ Stack/Row's `fit-content` min-size defaults (set `[minHeight]="0"` / `[minWidth]="0"`
620
+ for scroll containment). All internal usages, stories, and docs are migrated —
621
+ including the Divider story's `<Center>`, which had silently never rendered (Center
622
+ never had an element selector).
623
+
624
+ - [`f1796fb`](https://github.com/uni-design-system/uni/commit/f1796fbb42015dafb3a122f6effe69bc2b07d525) Thanks [@gaenglish](https://github.com/gaenglish)! - Selector unification: one form per component
625
+
626
+ **Breaking:** every PascalCase alias selector is removed (`Card`, `Menu`, `Symbol`,
627
+ `Icon`, `Dialog`, `Button`, `Tabs`, `Snackbar`, `SelectInput`, `Confirmation`, … — a
628
+ concept inherited from another library). Each component now has exactly one canonical
629
+ form, chosen by what the component is:
630
+ - **Widgets and content-renderers** keep their `uni-*` element: `<uni-card>`,
631
+ `<uni-menu>`, `<uni-symbol>`, `<uni-icon>`, `<uni-tabs>`, `<uni-select>`,
632
+ `<uni-confirmation-dialog>`, …
633
+ - **Decorators of native elements** are attribute-only, and their `div`-locks are
634
+ lifted: `[uni-badge]`, `[uni-dialog-header]` (e.g. on `<header>`),
635
+ `[uni-dialog-buttons]`/`[dialog-buttons]` (e.g. on `<footer>`),
636
+ `[uni-scroll-area]`/`[scroll-area]`, `[uni-menu-item]`/`[menu-item]` (e.g. on
637
+ `<li>`).
638
+ - **Host-locked selectors stay host-locked** where the native element carries the
639
+ behavior: `dialog[uni-dialog]`, `button[uni-text-button]`/`button[text-button]`,
640
+ `button[uni-icon-button]`/`button[icon-button]`.
641
+
642
+ Migration is mechanical: `<Card>` → `<uni-card>`, `<Button …>` →
643
+ `<button text-button …>`, `<Dialog …>` → `<dialog uni-dialog …>`, `<Badge …>` →
644
+ `<div uni-badge …>`, `<ScrollArea …>` → `<div scroll-area …>`. All internal usages,
645
+ stories, and docs are migrated; `llms.txt` and the MCP index reflect the canonical
646
+ forms.
647
+
648
+ - [`a62e9b1`](https://github.com/uni-design-system/uni/commit/a62e9b116f0b57cf2a1e647155bc2fbc02b02b87) Thanks [@gaenglish](https://github.com/gaenglish)! - Text is attribute-only, with a value shorthand and element-aware defaults
649
+
650
+ **Breaking:** the `uni-text` and `Text` element selectors are removed. Text is now the
651
+ attribute `[uni-text]` on any element, keeping your HTML semantics:
652
+ - **Value shorthand** — the attribute value is the typeface:
653
+ `<h1 uni-text="display-small">`, `<span uni-text="caption">`, dynamic via
654
+ `[uni-text]="role()"`. The explicit `typeface` input still works (the attribute
655
+ value wins when both are set).
656
+ - **Element-aware defaults** — with no value, the typeface is inferred from the host:
657
+ `h1`→headline-large, `h2`→headline-medium, `h3`→headline-small, `h4`→title-large,
658
+ `p`→body-1-long, `small`/`figcaption`→caption, `blockquote`→quote, `label`→label,
659
+ else title-small. Plain semantic markup is correctly set with zero configuration.
660
+
661
+ Migrate `<uni-text typeface="body-1-long">…</uni-text>` →
662
+ `<p uni-text>…</p>` (or `<span uni-text="body-1-long">` where no semantic element
663
+ fits). All internal usages, stories, and docs are migrated.
664
+
665
+ ### Minor Changes
666
+
667
+ - [`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
668
+ - **`uni-debounce-input`** now wears the themed input chrome via `uni-input-box`
669
+ (color, border, typeface, focus ring) and gains `label` (accessible name),
670
+ `placeholder`, `disabled`, `pre-input`/`post-input` attribute slots for adornments,
671
+ ARIA passthroughs (`role`, `ariaExpanded`, `ariaControls`, `ariaActivedescendant`)
672
+ for composite widgets, and `clear()`/`focus()` methods. Debounce behavior unchanged.
673
+ - **`uni-search-input` redesigned**: the opinionated solid-primary pill bar with the
674
+ embedded `title-large` label is gone. It's now a standard themed field — decorative
675
+ leading magnifier, clear button while a query exists (refocusing on clear), Enter
676
+ emits `search`, Escape closes/clears.
677
+ - **Type-ahead added**: pass `suggestions` (refresh from `change`) and the field
678
+ becomes an ARIA combobox — keyboard-navigable listbox (ArrowUp/Down, Enter selects,
679
+ emitting `suggestionSelected` + `search`), `aria-activedescendant` wiring, focus-out
680
+ closing. New `searchInput` theme options: `searchSymbol`, `clearSymbol`, suggestion
681
+ list `listColor`/`listShadow`/`listBorderRadius`, `maxSuggestions`.
682
+ - Visual breaking change for SearchInput consumers (deliberate strip-back); code API
683
+ is compatible (`label`/`width`/`change`/`search` retained; `label` is now the
684
+ accessible name + placeholder fallback rather than displayed text).
685
+
686
+ - [`8953d59`](https://github.com/uni-design-system/uni/commit/8953d59aa5f5eed57801534c7cbf5ff05453c316) Thanks [@gaenglish](https://github.com/gaenglish)! - Checkbox, radio, and toggle conform to theme tokens
687
+ - The last hardcoded control colors (`#FFF`, `#ccc`, `#d0d0d0`, `#e0e0e0`,
688
+ `rgba(0,0,0,0.2)`) are gone. Chrome now resolves from new option tokens —
689
+ checkbox `boxColor: 'surface'`; radio `ringColor: 'outline'` / `fillColor:
690
+ 'surface'` (radio gains a theme entry for the first time); toggle `trackColor:
691
+ 'surface-variant'` / `knobColor: 'surface'` — so all three finally render
692
+ correctly on dark and brand themes.
693
+ - The checkbox's check/dash strokes wear the variant's paired on-color
694
+ (`on-primary`, `on-warn`, …) instead of assuming white; disabled states use the
695
+ `disabled`/`on-disabled` tokens; the toggle knob's shadow is the theme's
696
+ brand-tinted `raised` stack, and its hover uses the button convention's
697
+ brightness filter instead of a fixed grey.
698
+
699
+ - [`7a6da4f`](https://github.com/uni-design-system/uni/commit/7a6da4f60979795f10e85493d1d13543f6a5a0e1) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-stat` KPI tile
700
+ - Muted label + large headline value (numbers auto-compact via
701
+ `Intl.NumberFormat`: `48234` → "48.2K") set in a new `stat` type-scale role
702
+ (32px semibold, proportional figures).
703
+ - Optional signed `delta` whose ink is decided by direction × `upIsGood` — churn
704
+ going down reads green, tickets going up reads red — with the arrow glyph
705
+ accompanied by screen-reader "up"/"down" text so state never rides color alone;
706
+ `caption` names the comparison period.
707
+ - Optional decorative 12-point sparkline (`trend` input): stroke in the outline hue,
708
+ endpoint dotted in the accent, `aria-hidden`.
709
+ - Fully token-driven via `stat.options`: card-recipe frame (`color`/`border`/
710
+ `borderRadius`), `labelTypeface`/`valueTypeface`, `positiveColor`/`negativeColor`
711
+ (the semantic inks, AA-guaranteed on surface), `trendColor`/`trendAccent`, spacing.
712
+
713
+ - [`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
714
+ - **Bug**: `uni-icon` resolves icons from `theme.icons`, but every theme shipped
715
+ `icons: {}` — the icon record was never wired in, so all icons (dialog close, button
716
+ spinner, search/clear affordances) rendered nothing.
717
+ - **Fix + pattern**: the default icon set now lives in core (`BaseIcons`, in
718
+ `concepts/iconography`) and `createTheme` merges a theme's `icons` over it — themes
719
+ can override or add icons under any name (inline SVG data URIs, masked with
720
+ `currentColor` so they recolor with the theme). The angular `icons` record re-exports
721
+ `BaseIcons` (deprecated).
722
+ - `uni-icon` also sets the standard `mask-image` (was webkit-only, so icons were
723
+ invisible in Firefox regardless) and renders nothing for unknown names instead of a
724
+ broken `url("undefined")`.
725
+ - The emitted `uni-theme.ts` gains an editable `icons` section, and the MCP's
726
+ `generate-uni-theme` guidance instructs agents: never inline SVG in components —
727
+ define an icon once in the theme's `icons` map and render it via `<uni-icon>`.
728
+
729
+ ### Patch Changes
730
+
731
+ - Updated dependencies [[`5a6fc60`](https://github.com/uni-design-system/uni/commit/5a6fc601952e0cbda80810a1c9062588c675d89f), [`8953d59`](https://github.com/uni-design-system/uni/commit/8953d59aa5f5eed57801534c7cbf5ff05453c316), [`7a6da4f`](https://github.com/uni-design-system/uni/commit/7a6da4f60979795f10e85493d1d13543f6a5a0e1), [`fea0b2e`](https://github.com/uni-design-system/uni/commit/fea0b2e78da327f3acea144958af2d0dbbefb699)]:
732
+ - @uni-design-system/uni-core@6.0.0
733
+
734
+ ## 5.2.0
735
+
736
+ ### Minor Changes
737
+
738
+ - [`1f5ee1d`](https://github.com/uni-design-system/uni/commit/1f5ee1dce290aceceec628677fdc9e36b5c5031f) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-avatar` and `uni-avatar-group` components
739
+ - Graceful fallback chain: image → initials derived from `name` (first + last) →
740
+ themed symbol. Accessible by default: `role="img"` + `aria-label` from the name,
741
+ `aria-hidden` when purely decorative.
742
+ - Fully token-driven: variants color from the role's `*-container` tokens, sizes from
743
+ the theme's `avatar` size records, corner radius from `options.borderRadius`
744
+ (`max` = circles; a `sharp` theme gets square avatars), initials typeface from
745
+ `options.typeface`, fallback symbol from `options.fallbackSymbol`.
746
+ - `uni-avatar-group` stacks avatars with a token-driven overlap (spacing token) and
747
+ separator ring (`ringColor`/`ringWidth` options); `max` collapses the overflow into
748
+ a themed "+N" chip that is itself a `uni-avatar` (verbatim `text` input).
749
+
750
+ - [`dca84b1`](https://github.com/uni-design-system/uni/commit/dca84b1f5a49d56608bac098c26b0e90992ed49d) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-breadcrumb` component
751
+ - WAI-ARIA breadcrumb: labelled `nav` landmark wrapping an ordered list, the last item
752
+ marked `aria-current="page"`, separators decorative (`aria-hidden`).
753
+ - Data-driven `items` (root first). Items with `href` render as real anchors; without
754
+ one they render as link-styled buttons emitting `itemClicked` for SPA routing.
755
+ - Token-driven via `breadcrumb.options`: `typeface`, link `color`, `currentColor`,
756
+ `separatorSymbol` (material symbol), and `gap` spacing.
757
+
758
+ - [`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
759
+ - `button` and `iconButton` themes gain `options: { borderRadius: 'max' }`; the hardcoded
760
+ per-size pixel radii (11/13/18/24) and the icon button's inline `borderRadius: 999` are
761
+ removed. Components resolve the token through `theme.radius()`, so the theme's radii
762
+ scale — shape languages (`sharp` → square, `playful` → pill) and custom radii
763
+ primitives — now restyles buttons like every other tokened component.
764
+ - Back-compat: the options radius is applied before theme `sizes`/`fixed` styles, so
765
+ hand-authored themes that still set a size-level `borderRadius` keep winning; icon
766
+ buttons fall back to the legacy circle when a theme predates iconButton options.
767
+ - `button` themes also gain `options: { typeface: 'button' }`: the hardcoded
768
+ `fontFamily: 'Euphemia'` (a font no theme loads) is removed from `fixed` and the sizes;
769
+ labels now render the type scale's `button` role (Red Hat Display, medium, capitalize),
770
+ with per-size `fontSize` still applied by `sizes`. Point the token at any typography
771
+ role — including custom ones — to restyle every button label.
772
+ - New `UniButtonOptions` interface (`borderRadius`, `typeface`) exported from the button
773
+ model.
774
+
775
+ - [`7844dde`](https://github.com/uni-design-system/uni/commit/7844ddec88f40027dafcaed5192f88d3c0cec54f) Thanks [@gaenglish](https://github.com/gaenglish)! - Card frame conforms to token primitives
776
+ - The card theme's hardcoded frame (`borderStyle`/`borderWidth`/`borderRadius: '8px'` in
777
+ `fixed`, `borderColor` per variant) is replaced by tokens: the card resolves its border
778
+ from the **border primitive named by its variant** (`borders.primary` …
779
+ `borders.success`), its corner radius from the radii scale (`options.borderRadius:
780
+ 'xs'` — same 8px by default), and an optional `options.elevation` shadow token.
781
+ - `UniCardOptions` gains `border` (pin every card to one primitive — including a custom
782
+ one — instead of variant-following) and documents the existing `borderRadius`/
783
+ `elevation`; redefining a border primitive in a theme now restyles cards and every
784
+ other component sharing that token.
785
+ - Token styles apply under the merged theme style, so hand-authored themes that still
786
+ set card `fixed`/`variants` frame styles keep winning. No visual change for default
787
+ themes — this is pure tokenization.
788
+
789
+ - [`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
790
+ - **`uni-drawer`** has two modes sharing one content slot: `side` renders an in-flow
791
+ `<aside>` that pushes content (width-animated open/close, divider border primitive at
792
+ its edge, `aria-hidden` while closed); `over` renders a native `<dialog>` in the top
793
+ layer — focus trap, Escape, and scrim backdrop come from the platform, sliding in
794
+ from `position` (`start`/`end`). `open` is a two-way `model()`; Escape and backdrop
795
+ clicks keep it in sync. Tokens: `drawer.options` (`color`, `width`, `divider`,
796
+ `elevation`, `padding`, `backdrop`).
797
+ - **`uni-app-bar`**: leading/trailing content projection slots around a `title` (or
798
+ custom center content), trailing pushed to the far edge; optional `sticky`. Tokens:
799
+ `appBar.options` (`color`, `height`, `divider`, `typeface`, `padding`, `gap`,
800
+ optional `elevation`).
801
+ - The Drawer "DashboardShell" story documents the composition recipe: app bar with a
802
+ menu toggle + side drawer + content.
803
+
804
+ - [`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
805
+
806
+ The gauge's track colors were fixed pastel hexes (`#b3d4ea`, `#b3e7c2`, …) that ignored
807
+ the active theme entirely — secondary and success even shared the same green. Tracks now
808
+ use the role's `*-container` token (the palette's soft tint of that role) and arcs the
809
+ role base, so gauges follow any brand palette, in both light and dark modes.
810
+
811
+ - [`4ccf18b`](https://github.com/uni-design-system/uni/commit/4ccf18ba86dc81c344597c7bc3e46f5d92f3c232) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-skeleton` loading placeholder
812
+ - Three shapes: `text` (multi-line, ending on a short line like real copy), `rect`,
813
+ and `circle`; explicit `width`/`height` accept CSS strings or px numbers.
814
+ - Painted with surface tokens (`skeleton.options`: `color`, `highlightColor`,
815
+ `borderRadius`, `gap`) so placeholders sit naturally on light and dark themes; the
816
+ shimmer sweep (`animation`/`duration` options) only runs when the user allows motion
817
+ and degrades to static blocks under `prefers-reduced-motion`.
818
+ - `aria-hidden` — placeholders are invisible to assistive tech by design.
819
+
820
+ - [`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
821
+ - Native `<input type="range">` under the hood: platform keyboard interaction
822
+ (arrows, Home/End, Page Up/Down) and ARIA slider semantics for free; Signal Forms
823
+ contract (`FormValueControl<number>`) matching Input/Textarea.
824
+ - `min`/`max`/`step` inputs (fractional steps supported), required accessible `label`.
825
+ - Token-driven via `slider.options`: fill/thumb `color`, `trackColor`, `borderRadius`,
826
+ and geometry (`trackHeight`, `thumbSize`). The fill level rides a CSS custom
827
+ property, so dragging never regenerates Emotion styles; Firefox uses the native
828
+ `::-moz-range-progress`, WebKit a gradient stop.
829
+
830
+ - [`15a8f73`](https://github.com/uni-design-system/uni/commit/15a8f731f70bcd84211b7ff16b9272a2c6f2adc8) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-tabs` / `uni-tab` components
831
+ - Full WAI-ARIA tabs pattern: `tablist`/`tab`/`tabpanel` roles with wired
832
+ `aria-selected`/`aria-controls`/`aria-labelledby`, roving tabindex, automatic
833
+ activation on ArrowLeft/ArrowRight (wrapping, disabled tabs skipped), Home/End,
834
+ and motion-safe transitions.
835
+ - `selectedIndex` is a two-way `model()`; selection snaps to the nearest enabled tab.
836
+ Panel content is captured per-tab and only the selected panel is instantiated.
837
+ - Every visual knob is a theme option token (`tabs.options`): `typeface`, `textColor`,
838
+ `activeTextColor`, `indicatorColor`, `indicatorThickness` (thickness token),
839
+ `divider` (border primitive), `gap`/`padding` (spacing tokens), `borderRadius`, and
840
+ optional `activeColor` — the defaults render underline tabs; `borderRadius: 'max'` +
841
+ `activeColor` turns them into segmented pills with no component changes.
842
+
843
+ - [`b04b97b`](https://github.com/uni-design-system/uni/commit/b04b97bd109e36cbb8fcda31c73680d85509ab0a) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-textarea` component
844
+ - Multi-line text field with the same Signal Forms contract as `uni-input`
845
+ (`FormValueControl<string>`: `value` model, `disabled`/`invalid`/`dirty`/`required`,
846
+ touched-aware error styling, `aria-*` wiring).
847
+ - Reuses `uni-input-box` for its chrome, so it inherits the input's themed color,
848
+ border, typeface, focus outline, and disabled treatment automatically; the box's
849
+ field selectors now cover `textarea` (with auto height and vertical padding), and
850
+ the box accepts a `height` override.
851
+ - Theme options follow the token pattern: `textarea: { options: { rows: 3, resize:
852
+ 'vertical' } }` in the derived component themes; the `rows` input overrides per
853
+ instance.
854
+
855
+ - [`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)
856
+ - **Light & dark side by side**: both palettes generate on every input change,
857
+ independent of the storybook-wide mode toggle, each rendered in its own panel.
858
+ - **Contrast report panel**: live pass/fail matrix over all checked pairs (summary +
859
+ failing rows always visible, full detail on demand). Failures only occur with hard
860
+ brand pins; the panel says so and points at soft targets.
861
+ - **Shape language input** (`sharp`/`modern`/`playful`): applies `ShapeRadii` live via
862
+ the new `radii` pass-through on `BrandPaletteConfig`/`createThemeFromPalette`.
863
+ - **Export paths**: copy the static `uni-theme.ts` (via `emitThemeFile`), copy a
864
+ pre-encoded `ng add` command, or copy W3C DTCG JSON for both modes via the new
865
+ `emitDtcgTokens()` interop emitter in uni-core (Style Dictionary compatible).
866
+
867
+ ## 5.1.0
868
+
869
+ ### Patch Changes
870
+
871
+ - [`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):
872
+ - The schematic bundle is CJS, but ng-packagr stamps `"type": "module"` into the
873
+ published package.json, so Node loaded it as ESM and the CLI reported "no ng add
874
+ actions". A nested `schematics/package.json` (`"type": "commonjs"`) scopes the
875
+ bundle back to CJS.
876
+ - The emitted `uni-theme.ts` used dot access on `Colors` (which has an index
877
+ signature), failing under `ng new`'s strict `noPropertyAccessFromIndexSignature`.
878
+ The emitter (and the MCP tool's guidance) now uses bracket access throughout.
879
+ - `uni-angular`'s peer range on `uni-core` is now `workspace:^` (publishes as `^5.x`)
880
+ instead of `workspace:*` (published as an exact pin). Alongside changesets'
881
+ `onlyUpdatePeerDependentsWhenOutOfRange`, this stops minor releases from being
882
+ inflated to majors by the peer-dependents rule — the cause of the 4.0.0 and 5.0.0
883
+ version jumps.
884
+
885
+ ## 5.0.0
886
+
887
+ ### Minor Changes
888
+
889
+ - [`f7f0bdd`](https://github.com/uni-design-system/uni/commit/f7f0bdddfac855955e022a852c2bfdccf8013a7b) Thanks [@gaenglish](https://github.com/gaenglish)! - OKLCH theme generation engine (`concepts/generation`)
890
+ - New `generateThemes(input)` / `generateUniThemes(input)`: brand seed(s) in, complete
891
+ WCAG-AA light+dark `Colors` pair out, with a machine-readable `ContrastReport`
892
+ (110 checked pairs per theme pair). Pure and deterministic.
893
+ - All palette math moved from HSL to perceptual OKLCH: uniform lightness slots across
894
+ hues, per-category chroma model (`CategoryChroma`), dark-mode accent chroma decoupling
895
+ (C ≤ 0.16), and a contrast guard-rail that adjusts lightness only — never hue.
896
+ - Brand colors ride as soft `targets` (kept verbatim when already AA, lightness-adjusted
897
+ when not) alongside the existing hard `brand` pins; accepted by `generatePalette`,
898
+ `createThemeFromPalette`, and Angular's `BrandPaletteConfig`.
899
+ - Semantic inks re-tuned per role: error 27°/C 0.20, warn 55°/C 0.18 (rotated off amber —
900
+ dark yellow reads brown), success 152°/C 0.16.
901
+ - `createTheme` accepts optional `radii`/`shadows` overrides; `ShapeRadii` presets
902
+ (`sharp` / `modern` / `playful`) emitted via the `shape` generation input.
903
+ - Theme Builder ships nine curated AA-clean presets built on soft targets.
904
+ - Deprecated (thin wrappers/tables retained): `uniColor`, `randomRangeValue`,
905
+ `CategorySaturation`, `CategoryLightness`. `schemeHues` moved to `color.utils`.
906
+ - `createTheme` deep-merges optional `borders` and `components` overrides over its
907
+ derived defaults — themes can define custom named primitives and rewire per-component
908
+ options without restating untouched sections.
909
+ - New `emitThemeFile()` renders a static `uni-theme.ts` — literal colors, visible border
910
+ primitives, sparse component overrides — the editable source of truth; the engine
911
+ never ships to the browser.
912
+ - New `ng add @uni-design-system/uni-angular` schematic: installs the peer set, writes
913
+ the generated theme file, registers `UNI_THEMES` in `app.config.ts`, adds typeface
914
+ links, scaffolds a themed smoke test, and prints the contrast summary.
915
+ - New MCP tool `generate-uni-theme`: returns the static theme file content, provider
916
+ snippet, and contrast report, with agent guidance to edit the file for restyling.
917
+ - `uni-core` gains its first Vitest suite, including a 1,080-seed contrast property
918
+ corpus with zero tolerance in both modes; the schematic gets its own spec suite.
919
+
920
+ ### Patch Changes
921
+
922
+ - Updated dependencies [[`f7f0bdd`](https://github.com/uni-design-system/uni/commit/f7f0bdddfac855955e022a852c2bfdccf8013a7b)]:
923
+ - @uni-design-system/uni-core@5.0.0
924
+
925
+ ## 4.0.0
926
+
927
+ ### Minor Changes
928
+
929
+ - [`ef9b3b5`](https://github.com/uni-design-system/uni/commit/ef9b3b5d2c7bc68dc2a114b04b3960a759d631b9) Thanks [@gaenglish](https://github.com/gaenglish)! - Porting Components
930
+
931
+ ### Patch Changes
932
+
933
+ - Updated dependencies [[`ef9b3b5`](https://github.com/uni-design-system/uni/commit/ef9b3b5d2c7bc68dc2a114b04b3960a759d631b9)]:
934
+ - @uni-design-system/uni-core@4.0.0
935
+
936
+ ## 3.0.2
937
+
938
+ ### Patch Changes
939
+
940
+ - [`dcaf166`](https://github.com/uni-design-system/uni/commit/dcaf1668fb94716b192c65137a14f3ea561a0142) Thanks [@gaenglish](https://github.com/gaenglish)! - Extending the Symbol component to support theme options.
941
+
942
+ - Updated dependencies []:
943
+ - @uni-design-system/uni-core@3.0.2
944
+
945
+ ## 3.0.1
946
+
947
+ ### Patch Changes
948
+
949
+ - [`fabbb73`](https://github.com/uni-design-system/uni/commit/fabbb73e7b2124e14b3cc263b2b8ca43e0cf80bd) Thanks [@gaenglish](https://github.com/gaenglish)! - Exporting new components and formatting.
950
+
951
+ - Updated dependencies []:
952
+ - @uni-design-system/uni-core@3.0.1
953
+
954
+ ## 3.0.0
955
+
956
+ ### Minor Changes
957
+
958
+ - [`54a945c`](https://github.com/uni-design-system/uni/commit/54a945c51635898acb0c8ec0dd93ee234c580228) Thanks [@gaenglish](https://github.com/gaenglish)! - Exporting Components, Themes, and CDK
959
+
960
+ ### Patch Changes
961
+
962
+ - Updated dependencies []:
963
+ - @uni-design-system/uni-core@3.0.0
964
+
965
+ ## 2.0.4
966
+
967
+ ### Patch Changes
968
+
969
+ - [`ad5dd15`](https://github.com/uni-design-system/uni/commit/ad5dd151d245765b9d24a6ad1370a3e9d0d6b63e) Thanks [@gaenglish](https://github.com/gaenglish)! - Exporting multiple Angular Components
970
+
971
+ - Updated dependencies []:
972
+ - @uni-design-system/uni-core@2.0.4
973
+
974
+ ## 2.0.3
975
+
976
+ ### Patch Changes
977
+
978
+ - [`eb80cff`](https://github.com/uni-design-system/uni/commit/eb80cffaaba0009c1c30875f93ea7e75dcf92302) Thanks [@gaenglish](https://github.com/gaenglish)! - fix: setting dist directory in publishConfig
979
+
980
+ - Updated dependencies []:
981
+ - @uni-design-system/uni-core@2.0.3
982
+
983
+ ## 2.0.2
984
+
985
+ ### Patch Changes
986
+
987
+ - Updated dependencies []:
988
+ - @uni-design-system/uni-core@2.0.2
989
+
990
+ ## 2.0.1
991
+
992
+ ### Patch Changes
993
+
994
+ - [`be0fb26`](https://github.com/uni-design-system/uni/commit/be0fb2626c248fed37c2609d50eb1bfae40269e4) Thanks [@gaenglish](https://github.com/gaenglish)! - Setting Fixed Versioning for all packages.
995
+
996
+ - Updated dependencies [[`e2cad74`](https://github.com/uni-design-system/uni/commit/e2cad74631b3a9d2caf4816bcadedf19db99fec4), [`be0fb26`](https://github.com/uni-design-system/uni/commit/be0fb2626c248fed37c2609d50eb1bfae40269e4)]:
997
+ - @uni-design-system/uni-core@2.0.1
998
+
999
+ ## 2.0.0
1000
+
1001
+ ### Patch Changes
1002
+
1003
+ - Updated dependencies [[`4a049de`](https://github.com/uni-design-system/uni/commit/4a049def689d56d6b6cc1d2da73c9facd93ed515)]:
1004
+ - @uni-design-system/uni-core@1.1.0
1005
+
1006
+ ## 1.0.0
1007
+
1008
+ ### Major Changes
1009
+
1010
+ - [`7d18157`](https://github.com/uni-design-system/uni/commit/7d18157cb131098688b70286513643423c37c756) Thanks [@gaenglish](https://github.com/gaenglish)! - init release
1011
+
1012
+ ### Patch Changes
1013
+
1014
+ - Updated dependencies [[`7d18157`](https://github.com/uni-design-system/uni/commit/7d18157cb131098688b70286513643423c37c756)]:
1015
+ - @uni-design-system/uni-core@1.0.0