@uni-design-system/uni-angular 10.0.0 → 10.2.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 CHANGED
@@ -1,5 +1,346 @@
1
1
  # @uni-design-system/uni-angular
2
2
 
3
+ ## 10.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`1cd0140`](https://github.com/uni-design-system/uni/commit/1cd0140d2e6a4c753f48fb46418ba08769979263) Thanks [@gaenglish](https://github.com/gaenglish)! - Checkbox, radio and toggle take their accent from the theme, not from the
8
+ variant's name.
9
+
10
+ **Twelve sites across the three controls resolved a variant name as a colour
11
+ token.** That held together only because every name in the closed union happened
12
+ to also be a colour. Under an open registry the coincidence ends by design:
13
+ `<uni-checkbox variant="destructive">` would look up `colors['destructive']`,
14
+ miss, and **silently render primary** — a wrong-coloured control with no error,
15
+ no warning, and nothing to grep for.
16
+
17
+ Checkbox was worse than it looked. Alongside five `getThemeColor` calls it had
18
+ two more through a second resolver that built `on-${variant}`, so an
19
+ unregistered intent also missed its paired content colour and fell back to
20
+ `on-primary` — the tick would have stayed light on a dark fill even after the
21
+ box was fixed.
22
+
23
+ The theme now says which colour draws each intent, through a new
24
+ `variantOptions` map on `ComponentTheme`:
25
+
26
+ ```ts
27
+ checkbox: {
28
+ variantOptions: {
29
+ primary: { accent: 'primary' },
30
+ warn: { accent: 'warn' },
31
+ },
32
+ }
33
+ ```
34
+
35
+ `variantOptions` is per-variant data a component **reads**, as against `variants`,
36
+ which is CSS that gets **applied**. The distinction earns its place here: a
37
+ checkbox's accent lands on the box outline, the checked and indeterminate fills,
38
+ the tick and the focus ring at once, and expressing that as CSS would have meant
39
+ the theme naming `.checkbox-check` and `.radio-inner` — promoting private DOM to
40
+ public theme contract.
41
+
42
+ All three controls gain a `checkedColor` input as the per-instance override,
43
+ matching the one `uni-toggle` already had; its resolution order is now input →
44
+ the variant's themed accent → theme option. The base theme defines the same
45
+ seven intents `button` and `iconButton` do, so the library is consistent about
46
+ which exist by default, and `getThemeColor` — triplicated byte-for-byte across
47
+ the three components, with a silent fallback to primary — is gone.
48
+
49
+ Rendering is unchanged for anything that does not set `variant`: the default
50
+ still resolves to the primary accent and its paired on-colour.
51
+
52
+ - [`1cd0140`](https://github.com/uni-design-system/uni/commit/1cd0140d2e6a4c753f48fb46418ba08769979263) Thanks [@gaenglish](https://github.com/gaenglish)! - `Variant` is an open registry: a design system can define its own intents.
53
+
54
+ A variant names _what an action means_, and it is the theme's job to describe
55
+ how that intent is drawn. So the set of names was never Uni's to fix — an app
56
+ whose actions are `destructive`, `subtle` and `info` had to translate them onto
57
+ twelve names chosen elsewhere. `Variant` is now `keyof UniVariantRegistry`,
58
+ extended by declaration merging:
59
+
60
+ ```ts
61
+ declare module '@uni-design-system/uni-core' {
62
+ interface UniVariantRegistry {
63
+ destructive: true;
64
+ }
65
+ }
66
+ ```
67
+
68
+ `variant="destructive"` then compiles wherever a variant is accepted, and
69
+ `variant="destructve"` still does not — which the library's other open-token
70
+ idiom, `Named | (string & {})`, cannot give you, and which would also have
71
+ collapsed the theme's `variants` map keys to `string`.
72
+
73
+ Only the type was ever closed: theme validation checks the _shape_ of a
74
+ `variants` block and never its key names, so a custom variant already reached
75
+ `componentStyle` untouched at runtime.
76
+
77
+ **The registry extends; it cannot replace.** Declaration merging has no way to
78
+ remove a member, so Uni's twelve names stay legal in a consuming app; enforcing
79
+ a house set is a lint concern rather than a type. Two names are reserved and
80
+ documented as always present: `primary`, which every component inherits as its
81
+ default, and `disabled`, which the disabled state resolves to.
82
+
83
+ **An unthemed variant now says so.** With a closed union this was nearly
84
+ impossible; with an open set it is the ordinary state of a work in progress —
85
+ a variant registered and used before its theme block exists. The theme service
86
+ warns once per component and variant in dev, naming what the theme does define,
87
+ mirroring what it already did for an unknown spacing token. Components that
88
+ theme no variants at all stay silent, since a missing key there is not a gap.
89
+
90
+ ## 10.1.0
91
+
92
+ ### Minor Changes
93
+
94
+ - [`f28c951`](https://github.com/uni-design-system/uni/commit/f28c95117f2844b58a265b59c2ea8b9715156670) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-drawer` gains the API an editor panel needs, and a close it can refuse.
95
+
96
+ **Closing can now be vetoed.** Escape, the backdrop, the header's close button
97
+ and the footer's cancel all funnel through one decision that emits
98
+ `closeRequest` with a `reason` — `'escape' | 'backdrop' | 'close-button'`. Set
99
+ `disableAutoClose` and the drawer stops acting on its own: it asks, and waits
100
+ for you to set `open`.
101
+
102
+ That split exists because the confirmation it has to accommodate is
103
+ _asynchronous_. A synchronous veto — a preventable event — cannot express
104
+ "ask the user, then decide", so every consumer would prevent unconditionally
105
+ and close manually anyway. Leave `disableAutoClose` off and behaviour is
106
+ unchanged, so adding a listener alone breaks nothing.
107
+
108
+ ```html
109
+ <uni-drawer [(open)]="open" [disableAutoClose]="form.dirty()" (closeRequest)="confirmDiscard()" />
110
+ ```
111
+
112
+ New inputs: `width` (per-instance override of the theme's width — a nav drawer
113
+ is 280 and an editor panel 480, and both live in one app), `headline` and
114
+ `defaultCloseButton` for the header row, and `initialFocus`, a selector resolved
115
+ inside the panel when it opens.
116
+
117
+ **`ariaLabel` no longer defaults to `'Navigation'`.** A drawer with a header is
118
+ labelled by that header via `aria-labelledby`; without one, `ariaLabel` is used;
119
+ with neither, the drawer is unnamed. The old default meant every drawer that
120
+ wasn't a nav drawer announced itself as one, and a wrong accessible name is
121
+ worse than a missing one — the missing one is at least caught by an audit. If
122
+ you relied on it, set `ariaLabel="Navigation"` explicitly.
123
+
124
+ **`scrim` turns the dimming off** without changing the modality. As a `scrim`
125
+ input or a `drawer.behavior.scrim` theme option, false leaves `::backdrop`
126
+ transparent so the page behind stays legible — an editor panel beside a board
127
+ the user is still reading. Focus is still trapped and the page behind is still
128
+ inert: it is a visibility choice, not a modality one. `background` joins it,
129
+ selecting `solid`, `glass` or `gradient` as a token choice rather than per-app
130
+ CSS.
131
+
132
+ **Fixed: a closed overlay drawer rendered in normal flow behind the page.** The
133
+ panel's `display: flex` outranks the UA stylesheet's
134
+ `dialog:not([open]) { display: none }`, so the drawer was visible on first
135
+ paint and reappeared behind the content after every close. An explicit
136
+ `&:not([open])` rule restores it. The closing animation is unaffected — `open`
137
+ is only removed once it ends.
138
+
139
+ - [`f28c951`](https://github.com/uni-design-system/uni/commit/f28c95117f2844b58a265b59c2ea8b9715156670) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-drawer` is a three-row panel, and is no longer its own scroll container.
140
+
141
+ **The `<dialog>` used to be the scroller.** `over` mode set `overflowY: 'auto'`
142
+ on the panel and put the theme's padding there too, which made a pinned header
143
+ or footer impossible: padding on a scrolling box scrolls away with its content,
144
+ and any row you pinned against it could not sit flush to the panel edge. It also
145
+ set only the one axis — and a single explicit overflow axis computes the other
146
+ to `auto`, which is exactly how a container becomes an accidental scroller.
147
+ `side` had it right already, setting both.
148
+
149
+ The panel is now a flex column of three rows — an optional
150
+ `[uni-drawer-header]`, the projected body, an optional `[uni-drawer-buttons]`
151
+ (alias `[drawer-buttons]`) — and **only the body scrolls**. The panel itself is
152
+ `overflow: clip` on both axes, explicitly, never the shorthand. The body carries
153
+ `overscroll-behavior: contain`, so scrolling to its end does not start scrolling
154
+ the page behind it, and `position: relative`, so a stray absolutely positioned
155
+ descendant is contained rather than re-homed into an ancestor.
156
+
157
+ Those two must travel together: a positioned body _without_ a clipped shell is
158
+ worse than the status quo, because it pulls phantom overflow into the scroller
159
+ instead of out of the panel.
160
+
161
+ **`drawer.behavior.padding` is now the body's padding, not the panel's.** A
162
+ drawer with no header or footer looks the same as before. One that gains either
163
+ gets rows flush to the panel edge, which is the point.
164
+
165
+ Two new theme entries, `drawerHeader` and `drawerButtons`, mirror the dialog
166
+ pair knob for knob but default to a panel's posture rather than a dialog's: the
167
+ header's title is left-aligned rather than centered, and the footer trails its
168
+ actions rather than centering them.
169
+
170
+ Note one deliberate divergence from `[dialog-buttons]`: the drawer footer's
171
+ **confirm button does not close the drawer**. A panel's save is usually async
172
+ and can fail, so closing is left to the consumer via `(confirmed)`.
173
+
174
+ - [`67e17ea`](https://github.com/uni-design-system/uni/commit/67e17ea927e3ca24b7abecdcccf134d53ac3656b) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-toggle` honours `size`, and the checked colour has a theme home.
175
+
176
+ **`size` did nothing.** `uni-toggle` inherits a bindable `size` input from
177
+ `BaseComponent`, but its geometry came from a single `toggle.behavior.size`
178
+ number, so `<uni-toggle size="sm">` compiled, read as deliberate, and rendered
179
+ identically to every other switch. The theme now carries a `sizes` block and the
180
+ input selects from it.
181
+
182
+ **Geometry is stated per size, not derived from ratios.** Each size token is a
183
+ `width` / `height` / `padding` triple — `padding` being the knob's inset — and
184
+ the rest falls out: knob is `height - 2 * padding`, travel is `width - height`,
185
+ radius is `height / 2`. Real switch designs do not hold a constant proportion
186
+ across sizes (a consumer's 32x18 and 28x16 pair differs in both track and knob
187
+ ratio), so a single ratio token could match one size or the other but never
188
+ both.
189
+
190
+ `lg` is `BaseComponent`'s default and reproduces the previous geometry exactly —
191
+ 40x20, knob 16 — so **no existing toggle moves**. A theme still setting the
192
+ legacy `toggle.behavior.size` number keeps the old derived-ratio behaviour; that
193
+ option is now deprecated in favour of the `sizes` block.
194
+
195
+ **Fixed: the knob would have overhung the track at any other proportion.** The
196
+ checked transform was hardcoded to translate by one track height, which is only
197
+ correct while the track is `2x` wide and the knob `0.8x`. It is now derived, so
198
+ the knob lands the same distance from each edge at every size.
199
+
200
+ **New `checkedColor`, as an input and a theme option.** The on-state used to be
201
+ the instance's `variant`, which meant an app wanting one switch colour repeated
202
+ an attribute on every call site. `toggle.behavior.checkedColor` sets it once;
203
+ the input overrides per instance; `variant` remains the fallback when neither is
204
+ set. The focus ring follows the resolved colour rather than staying on `variant`.
205
+ An input as well as an option is necessary because `variant` defaults to
206
+ `'primary'` and so cannot be distinguished from unset — without one, a themed
207
+ `checkedColor` would have silently made `variant` inert with no way back.
208
+
209
+ **Toggle transitions are a motion token.** It hardcoded `0.3s ease` while
210
+ `uni-radio` already read `theme.motion(options.motion ?? 'control')`, so a theme
211
+ setting a motion scale moved the radio and not the switch.
212
+
213
+ ### Patch Changes
214
+
215
+ - [`f28c951`](https://github.com/uni-design-system/uni/commit/f28c95117f2844b58a265b59c2ea8b9715156670) Thanks [@gaenglish](https://github.com/gaenglish)! - Document the drawer as an editor panel, and assert the layout that makes it one.
216
+
217
+ Both existing Drawer stories were navigation shells, so the shape that actually
218
+ stresses the component — a pinned header, a long scrolling form, a pinned save
219
+ bar — had no worked example. `EditorPanel` is that example, with a
220
+ `uni-number-input` and a `uni-quantity-stepper` near the bottom of the scroll,
221
+ where the sr-only overflow bug used to surface.
222
+
223
+ It carries a play function that asserts the scroll geometry: the panel's
224
+ `scrollHeight` equals its `clientHeight`, setting `scrollTop` on it does
225
+ nothing, the footer has no scrollable content of its own, the body scrolls to
226
+ its last element and stops, and no descendant has escaped its scroll container
227
+ to land on the panel. Those assertions live in the story rather than the unit
228
+ spec on purpose — jsdom has no layout engine, so every one of them would pass
229
+ vacuously there.
230
+
231
+ The MDX gains the three-row layout, the close-request contract, an input table,
232
+ and theme-option blocks for the two new component entries.
233
+
234
+ - [`8f85b48`](https://github.com/uni-design-system/uni/commit/8f85b48b06f67e7f44ce29149dd32aae856e2f19) Thanks [@gaenglish](https://github.com/gaenglish)! - New **Experiments → Forms layout pressure** docs page: every form control in an
235
+ `auto 1fr` grid, with a bar showing what it left for its sibling.
236
+
237
+ It exists to make one class of bug visible, because nothing else can see it. A
238
+ `1fr` track is `minmax(auto, 1fr)`, and that `auto` floor is the control's own
239
+ min-content size — so a control reporting a larger intrinsic width than it needs
240
+ quietly steals track width from whatever sits beside it, while still looking
241
+ correct in isolation. That shipped once: `uni-quantity-stepper`'s value cell is a
242
+ native `<input>` defaulting to `size="20"`, so it measured ~230px instead of
243
+ ~92px and collapsed a consumer's grid column.
244
+
245
+ Neither the specs nor `build-storybook` catch it — the specs assert computed
246
+ styles and ARIA, and jsdom does not do layout — so it took a consumer report.
247
+ The page is deliberately width-constrained, since on a wide canvas nothing
248
+ competes for the track and the defect cannot appear. The measurements are live
249
+ and update as the viewport changes.
250
+
251
+ - [`67e17ea`](https://github.com/uni-design-system/uni/commit/67e17ea927e3ca24b7abecdcccf134d53ac3656b) Thanks [@gaenglish](https://github.com/gaenglish)! - Signal Forms docs said `[field]`; the directive is `[formField]`.
252
+
253
+ `[field]` was the selector in the Angular 21.0 Signal Forms preview and was
254
+ renamed before release. In 21.2 the directive is `FormField`, selector
255
+ `[formField]`, with its required input aliased to `formField` — `[field]` does
256
+ not exist at all. Every form control in this library was documented with the
257
+ name that had been removed, across seven component doc comments and five MDX
258
+ files, and all of it flowed into the MCP index and the generated API reference.
259
+ A consuming app found this, not us.
260
+
261
+ Nothing about the components changed: they already satisfy `FormValueControl` /
262
+ `FormCheckboxControl` and always bound correctly.
263
+
264
+ **The reason it rotted is that nothing compiled a binding.** No spec or story in
265
+ the library imported `@angular/forms/signals` — the toggle's "Form Signals" story
266
+ hand-bound `[checked]` and `[touched]` as plain props, which demonstrates
267
+ nothing about Signal Forms and would keep passing through any rename. There is
268
+ now a spec that binds a real `form()` to `uni-toggle` through `[formField]` and
269
+ asserts the round trip in both directions, plus `touched` and `required`
270
+ propagation, so the next rename fails CI instead of the docs. The story binds a
271
+ real form too.
272
+
273
+ - [`f28c951`](https://github.com/uni-design-system/uni/commit/f28c95117f2844b58a265b59c2ea8b9715156670) Thanks [@gaenglish](https://github.com/gaenglish)! - Visually hidden text no longer inflates a consuming app's scroll containers.
274
+
275
+ **Eighteen controls quietly added scrollable distance to whatever box happened
276
+ to be above them.** `visuallyHidden` was `position: absolute`, and the controls
277
+ that emit it — `uni-number-input`, `uni-quantity-stepper`, the toggle's hidden
278
+ `<input>`, and fifteen others — are `position: static`. An absolutely positioned
279
+ box resolves its containing block to the nearest _positioned_ ancestor, so each
280
+ 1x1 span skipped every `overflow: auto` between it and that ancestor and landed
281
+ in the distant ancestor's scrollable overflow. A consumer reported a fixed side
282
+ panel measuring `scrollHeight: 1891` against `clientHeight: 793` — the whole
283
+ 1098px difference came from seven invisible spans that had escaped the panel's
284
+ body scroller.
285
+
286
+ The helper is now `position: fixed`, whose containing block is the viewport, so
287
+ it joins no ancestor's scrollable overflow at all. The element stays 1x1 and
288
+ clipped to nothing, so screen reader behaviour is unchanged. Inside a
289
+ `transform`ed ancestor a fixed box re-anchors to that ancestor, which is
290
+ harmless here: where the box lands never mattered, only what it overflowed.
291
+
292
+ This class of bug is invisible in isolation — it needs a consumer to nest the
293
+ control inside a scrolling shell before it appears — so it is now covered by a
294
+ test that renders the emitting controls and asserts what actually reaches the
295
+ DOM, not just the recipe.
296
+
297
+ - [`7545ff3`](https://github.com/uni-design-system/uni/commit/7545ff3e6dd85d29157963488f75f9e3681947c7) Thanks [@gaenglish](https://github.com/gaenglish)! - Stepper buttons now leave focus in their field, and `uni-quantity-stepper`
298
+ follows the theme's field chrome.
299
+
300
+ **Clicking `+` or `−` focused nothing.** Taking pointer capture means calling
301
+ `preventDefault()` on `pointerdown`, which also suppresses the browser's default
302
+ focus handling — and the buttons carry `tabindex="-1"`, so focus landed on
303
+ `<body>`. The arrow keys then did nothing, exactly when a user reaching for `+`
304
+ is most likely to try them. `createPressRepeat` gained a `focus` callback,
305
+ invoked on press and handed the pressed button; `uni-number-input` and
306
+ `uni-quantity-stepper` point it at their text field, the way a native spinner
307
+ does. Where there is no field — a read-only quantity stepper, whose buttons are
308
+ themselves the tab stops — focus goes to the button instead. **This affected
309
+ `uni-number-input` as well**, not just the stepper.
310
+
311
+ **`uni-quantity-stepper` ignored a theme's field styling.** It carried its own
312
+ `color` / `border` / `borderRadius` tokens, so a theme that restyles `input` —
313
+ Wellsourced fills its fields `#F3F2EF` — left the stepper stark white beside
314
+ them. Those three now default to the shared `input` chrome and are unset in the
315
+ base theme, so the stepper tracks whatever a theme does to its fields; they
316
+ remain available as per-component overrides for parting them deliberately. With
317
+ the focus indicator and the dividers already sourced this way, the container is
318
+ now consistently the field chrome unless a theme says otherwise.
319
+
320
+ - [`8f85b48`](https://github.com/uni-design-system/uni/commit/8f85b48b06f67e7f44ce29149dd32aae856e2f19) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-quantity-stepper` no longer claims ~230px of width it does not need.
321
+
322
+ The value cell is a native `<input>`, which defaults to `size="20"`. Its
323
+ `flex: 1 1 auto` meant `flex-basis` resolved to that intrinsic ~20-character
324
+ width rather than to `valueWidth`, so the control measured ~230px instead of the
325
+ ~92px its buttons and a 3ch value actually need. Worse, that inflated width is
326
+ the control's `auto` size, and a `1fr` grid track is `minmax(auto, 1fr)` — so a
327
+ stepper stole track width from whatever sat beside it in a grid. Reported by
328
+ Wellsourced, who worked around it with `uni-quantity-stepper input { width: 0 }`.
329
+
330
+ The input is now sized from its content (`size` bound to the rendered value's
331
+ length), so the intrinsic width tells the truth. `valueWidth` stays the floor via
332
+ `min-width`, which is what keeps stepping 9 → 10 from reflowing the row, and the
333
+ cell still grows past it with the digits — measured at 92px for one to three
334
+ digits, 116px at `12,000`, 140px at `1,234,567`.
335
+
336
+ This is deliberately not the `width`-instead-of-`min-width` fix that was also
337
+ suggested: a fixed cell would have pinned the value at `valueWidth` and clipped
338
+ longer numbers, losing the growth the option documents.
339
+
340
+ **For consumers carrying the workaround:** it is safe to leave in place — the
341
+ control measures the same 92px either way — but remove it to get the growth
342
+ back, since `width: 0` forces `flex-basis: 0` and pins the cell at the floor.
343
+
3
344
  ## 10.0.0
4
345
 
5
346
  ### Major Changes
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@uni-design-system/uni-angular"><img src="https://img.shields.io/npm/v/%40uni-design-system%2Funi-angular" alt="npm version"></a>
13
- <a href="https://github.com/uni-design-system/uni/actions/workflows/ci.yml"><img src="https://github.com/uni-design-system/uni/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
+ <a href="https://github.com/uni-design-system/uni/actions/workflows/release.yml"><img src="https://github.com/uni-design-system/uni/actions/workflows/release.yml/badge.svg" alt="CI"></a>
14
14
  </p>
15
15
 
16
16
  <p align="center">