@uni-design-system/uni-core 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,195 @@
1
1
  # @uni-design-system/uni-core
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` is a three-row panel, and is no longer its own scroll container.
95
+
96
+ **The `<dialog>` used to be the scroller.** `over` mode set `overflowY: 'auto'`
97
+ on the panel and put the theme's padding there too, which made a pinned header
98
+ or footer impossible: padding on a scrolling box scrolls away with its content,
99
+ and any row you pinned against it could not sit flush to the panel edge. It also
100
+ set only the one axis — and a single explicit overflow axis computes the other
101
+ to `auto`, which is exactly how a container becomes an accidental scroller.
102
+ `side` had it right already, setting both.
103
+
104
+ The panel is now a flex column of three rows — an optional
105
+ `[uni-drawer-header]`, the projected body, an optional `[uni-drawer-buttons]`
106
+ (alias `[drawer-buttons]`) — and **only the body scrolls**. The panel itself is
107
+ `overflow: clip` on both axes, explicitly, never the shorthand. The body carries
108
+ `overscroll-behavior: contain`, so scrolling to its end does not start scrolling
109
+ the page behind it, and `position: relative`, so a stray absolutely positioned
110
+ descendant is contained rather than re-homed into an ancestor.
111
+
112
+ Those two must travel together: a positioned body _without_ a clipped shell is
113
+ worse than the status quo, because it pulls phantom overflow into the scroller
114
+ instead of out of the panel.
115
+
116
+ **`drawer.behavior.padding` is now the body's padding, not the panel's.** A
117
+ drawer with no header or footer looks the same as before. One that gains either
118
+ gets rows flush to the panel edge, which is the point.
119
+
120
+ Two new theme entries, `drawerHeader` and `drawerButtons`, mirror the dialog
121
+ pair knob for knob but default to a panel's posture rather than a dialog's: the
122
+ header's title is left-aligned rather than centered, and the footer trails its
123
+ actions rather than centering them.
124
+
125
+ Note one deliberate divergence from `[dialog-buttons]`: the drawer footer's
126
+ **confirm button does not close the drawer**. A panel's save is usually async
127
+ and can fail, so closing is left to the consumer via `(confirmed)`.
128
+
129
+ - [`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.
130
+
131
+ **`size` did nothing.** `uni-toggle` inherits a bindable `size` input from
132
+ `BaseComponent`, but its geometry came from a single `toggle.behavior.size`
133
+ number, so `<uni-toggle size="sm">` compiled, read as deliberate, and rendered
134
+ identically to every other switch. The theme now carries a `sizes` block and the
135
+ input selects from it.
136
+
137
+ **Geometry is stated per size, not derived from ratios.** Each size token is a
138
+ `width` / `height` / `padding` triple — `padding` being the knob's inset — and
139
+ the rest falls out: knob is `height - 2 * padding`, travel is `width - height`,
140
+ radius is `height / 2`. Real switch designs do not hold a constant proportion
141
+ across sizes (a consumer's 32x18 and 28x16 pair differs in both track and knob
142
+ ratio), so a single ratio token could match one size or the other but never
143
+ both.
144
+
145
+ `lg` is `BaseComponent`'s default and reproduces the previous geometry exactly —
146
+ 40x20, knob 16 — so **no existing toggle moves**. A theme still setting the
147
+ legacy `toggle.behavior.size` number keeps the old derived-ratio behaviour; that
148
+ option is now deprecated in favour of the `sizes` block.
149
+
150
+ **Fixed: the knob would have overhung the track at any other proportion.** The
151
+ checked transform was hardcoded to translate by one track height, which is only
152
+ correct while the track is `2x` wide and the knob `0.8x`. It is now derived, so
153
+ the knob lands the same distance from each edge at every size.
154
+
155
+ **New `checkedColor`, as an input and a theme option.** The on-state used to be
156
+ the instance's `variant`, which meant an app wanting one switch colour repeated
157
+ an attribute on every call site. `toggle.behavior.checkedColor` sets it once;
158
+ the input overrides per instance; `variant` remains the fallback when neither is
159
+ set. The focus ring follows the resolved colour rather than staying on `variant`.
160
+ An input as well as an option is necessary because `variant` defaults to
161
+ `'primary'` and so cannot be distinguished from unset — without one, a themed
162
+ `checkedColor` would have silently made `variant` inert with no way back.
163
+
164
+ **Toggle transitions are a motion token.** It hardcoded `0.3s ease` while
165
+ `uni-radio` already read `theme.motion(options.motion ?? 'control')`, so a theme
166
+ setting a motion scale moved the radio and not the switch.
167
+
168
+ ### Patch Changes
169
+
170
+ - [`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`
171
+ follows the theme's field chrome.
172
+
173
+ **Clicking `+` or `−` focused nothing.** Taking pointer capture means calling
174
+ `preventDefault()` on `pointerdown`, which also suppresses the browser's default
175
+ focus handling — and the buttons carry `tabindex="-1"`, so focus landed on
176
+ `<body>`. The arrow keys then did nothing, exactly when a user reaching for `+`
177
+ is most likely to try them. `createPressRepeat` gained a `focus` callback,
178
+ invoked on press and handed the pressed button; `uni-number-input` and
179
+ `uni-quantity-stepper` point it at their text field, the way a native spinner
180
+ does. Where there is no field — a read-only quantity stepper, whose buttons are
181
+ themselves the tab stops — focus goes to the button instead. **This affected
182
+ `uni-number-input` as well**, not just the stepper.
183
+
184
+ **`uni-quantity-stepper` ignored a theme's field styling.** It carried its own
185
+ `color` / `border` / `borderRadius` tokens, so a theme that restyles `input` —
186
+ Wellsourced fills its fields `#F3F2EF` — left the stepper stark white beside
187
+ them. Those three now default to the shared `input` chrome and are unset in the
188
+ base theme, so the stepper tracks whatever a theme does to its fields; they
189
+ remain available as per-component overrides for parting them deliberately. With
190
+ the focus indicator and the dividers already sourced this way, the container is
191
+ now consistently the field chrome unless a theme says otherwise.
192
+
3
193
  ## 10.0.0
4
194
 
5
195
  ### Major Changes
@@ -1057,6 +1057,30 @@ var buildBorders = (c) => ({
1057
1057
  * surface and draws the edge — so every role stays consistent and a theme can
1058
1058
  * still override any single cell.
1059
1059
  */
1060
+ /**
1061
+ * Accent roles for the selection controls (checkbox, radio, toggle).
1062
+ *
1063
+ * A variant names the intent; this says which colour token draws it, and the
1064
+ * component decides where it lands — the box fill, the ring, the dot, the
1065
+ * track, the focus ring. Keeping it as a role rather than a `variants`
1066
+ * StyleExpression is what stops interior class names like `.checkbox-check`
1067
+ * becoming public theme contract.
1068
+ *
1069
+ * The same seven names `button` and `iconButton` theme, so the library is
1070
+ * consistent about which intents exist by default.
1071
+ */
1072
+ var SELECTION_ACCENTS = {
1073
+ primary: { accent: "primary" },
1074
+ secondary: { accent: "secondary" },
1075
+ tertiary: { accent: "tertiary" },
1076
+ warn: { accent: "warn" },
1077
+ success: { accent: "success" },
1078
+ disabled: { accent: "disabled" },
1079
+ ghost: {
1080
+ accent: "ghost",
1081
+ onAccent: "on-primary"
1082
+ }
1083
+ };
1060
1084
  var tagVariant = (c, role) => ({ [role]: {
1061
1085
  backgroundColor: c[`${role}-container`],
1062
1086
  color: c[`on-${role}-container`],
@@ -1099,7 +1123,30 @@ var buildComponents = (c) => ({
1099
1123
  divider: "light",
1100
1124
  elevation: "menu",
1101
1125
  padding: "md",
1102
- backdrop: { background: "rgba(0, 0, 0, 0.4)" }
1126
+ backdrop: { background: "rgba(0, 0, 0, 0.4)" },
1127
+ scrim: true,
1128
+ background: "solid"
1129
+ } },
1130
+ drawerHeader: { options: {
1131
+ color: void 0,
1132
+ height: 56,
1133
+ padding: "md",
1134
+ textRole: "title-medium",
1135
+ textAlign: "left",
1136
+ divider: "light",
1137
+ closeButtonIcon: "close",
1138
+ closeButtonSize: "md"
1139
+ } },
1140
+ drawerButtons: { options: {
1141
+ gap: "sm",
1142
+ padding: "md",
1143
+ justifyContent: "flex-end",
1144
+ confirmButtonVariant: "primary",
1145
+ cancelButtonVariant: "quaternary",
1146
+ buttonSize: "md",
1147
+ divider: "light",
1148
+ stretch: false,
1149
+ reverseOrder: false
1103
1150
  } },
1104
1151
  avatar: {
1105
1152
  options: {
@@ -1170,18 +1217,24 @@ var buildComponents = (c) => ({
1170
1217
  activeColor: "primary-container",
1171
1218
  maxSuggestions: 8
1172
1219
  } },
1173
- checkbox: { options: {
1174
- size: 20,
1175
- boxColor: "surface",
1176
- borderRadius: 2,
1177
- focusRingGap: 2
1178
- } },
1179
- radio: { options: {
1180
- size: 20,
1181
- ringColor: "outline",
1182
- fillColor: "surface",
1183
- motion: "control"
1184
- } },
1220
+ checkbox: {
1221
+ options: {
1222
+ size: 20,
1223
+ boxColor: "surface",
1224
+ borderRadius: 2,
1225
+ focusRingGap: 2
1226
+ },
1227
+ variantOptions: SELECTION_ACCENTS
1228
+ },
1229
+ radio: {
1230
+ options: {
1231
+ size: 20,
1232
+ ringColor: "outline",
1233
+ fillColor: "surface",
1234
+ motion: "control"
1235
+ },
1236
+ variantOptions: SELECTION_ACCENTS
1237
+ },
1185
1238
  dialog: { options: {
1186
1239
  borderRadius: "lg",
1187
1240
  color: "primary-surface",
@@ -1243,13 +1296,16 @@ var buildComponents = (c) => ({
1243
1296
  activeSymbol: "check",
1244
1297
  motion: "control"
1245
1298
  },
1246
- variants: { warn: {
1247
- color: c.warn,
1248
- [HOVER_OR_KEYBOARD_FOCUS]: {
1249
- backgroundColor: c["warn-container"],
1250
- color: c["on-warn-container"]
1299
+ variants: {
1300
+ primary: {},
1301
+ warn: {
1302
+ color: c.warn,
1303
+ [HOVER_OR_KEYBOARD_FOCUS]: {
1304
+ backgroundColor: c["warn-container"],
1305
+ color: c["on-warn-container"]
1306
+ }
1251
1307
  }
1252
- } }
1308
+ }
1253
1309
  },
1254
1310
  popover: { options: {
1255
1311
  color: "primary-surface",
@@ -1320,9 +1376,6 @@ var buildComponents = (c) => ({
1320
1376
  } },
1321
1377
  quantityStepper: {
1322
1378
  options: {
1323
- color: "primary-surface",
1324
- border: "light",
1325
- borderRadius: "xs",
1326
1379
  incrementIcon: "plus",
1327
1380
  decrementIcon: "minus",
1328
1381
  deleteIcon: "delete",
@@ -1795,11 +1848,31 @@ var buildComponents = (c) => ({
1795
1848
  grade: 0,
1796
1849
  opticalSize: 24
1797
1850
  } },
1798
- toggle: { options: {
1799
- size: 20,
1800
- trackColor: "surface-variant",
1801
- knobColor: "surface"
1802
- } },
1851
+ toggle: {
1852
+ options: {
1853
+ trackColor: "surface-variant",
1854
+ knobColor: "surface",
1855
+ motion: "control"
1856
+ },
1857
+ variantOptions: SELECTION_ACCENTS,
1858
+ sizes: {
1859
+ sm: {
1860
+ width: 28,
1861
+ height: 16,
1862
+ padding: 3
1863
+ },
1864
+ md: {
1865
+ width: 32,
1866
+ height: 18,
1867
+ padding: 3
1868
+ },
1869
+ lg: {
1870
+ width: 40,
1871
+ height: 20,
1872
+ padding: 2
1873
+ }
1874
+ }
1875
+ },
1803
1876
  tooltip: { options: {
1804
1877
  border: void 0,
1805
1878
  borderRadius: "xs",
@@ -2604,6 +2677,17 @@ var checkComponents = (value, issues) => {
2604
2677
  }
2605
2678
  for (const [key, style] of Object.entries(styles)) if (style !== void 0) checkStyleExpression(style, `components.${name}.${section}.${key}`, issues);
2606
2679
  }
2680
+ const variantOptions = entry["variantOptions"];
2681
+ if (variantOptions !== void 0) {
2682
+ if (!isRecord(variantOptions)) issues.push({
2683
+ path: `components.${name}.variantOptions`,
2684
+ message: "must be an object"
2685
+ });
2686
+ else for (const [key, roles] of Object.entries(variantOptions)) if (roles !== void 0 && !isRecord(roles)) issues.push({
2687
+ path: `components.${name}.variantOptions.${key}`,
2688
+ message: "must be an object"
2689
+ });
2690
+ }
2607
2691
  if (entry["options"] !== void 0 && !isRecord(entry["options"])) issues.push({
2608
2692
  path: `components.${name}.options`,
2609
2693
  message: "must be an object"