@uni-design-system/uni-core 9.0.1 → 10.0.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,203 @@
1
1
  # @uni-design-system/uni-core
2
2
 
3
+ ## 10.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [`21b655d`](https://github.com/uni-design-system/uni/commit/21b655df0f93e2e2de6a22ccf38050b474d4e5ab) Thanks [@gaenglish](https://github.com/gaenglish)! - `uni-slider` is rebuilt on custom thumbs instead of `<input type="range">`,
8
+ gaining range mode, marks, a value readout and an exact decimal step model. This
9
+ is a breaking change to both the component's value shape and its theme options.
10
+
11
+ **Why it could not stay native.** One `<input type="range">` cannot carry two
12
+ thumbs, so range mode, thumb crossing and per-thumb ARIA bounds were all
13
+ unreachable; marks and a value tooltip were unstyleable through it. The
14
+ alternative — a second component for the range case — would have meant two
15
+ keyboard implementations to keep in step, which is the drift the shared step
16
+ model exists to prevent. What the platform was giving us (the slider ARIA
17
+ pattern and the keyboard map) is reimplemented explicitly and covered by 29
18
+ specs.
19
+
20
+ **Breaking: value shape.**
21
+
22
+ ```ts
23
+ // before
24
+ value = model<number>(0);
25
+ // after — the shape follows `mode`, and `null` is empty
26
+ value = model<number | UniNumberRange | null>(null);
27
+ ```
28
+
29
+ A `single` slider still reads and writes a plain number, so
30
+ `[(value)]="volume"` is unchanged. Code that relied on `value()` being
31
+ non-nullable, or that read it without narrowing, now needs to handle `null` and
32
+ the `{ start, end }` range.
33
+
34
+ **Breaking: theme options.** `slider.options.color` is **removed**. Fill and
35
+ thumb colour now come from the `variant` role pair, the rule every other
36
+ component follows, so `variant="warn"` recolours a slider with no theme edit. A
37
+ theme that set `color` should delete it and pass `variant` at the call site.
38
+ `trackColor` now defaults to `primary-container` rather than `surface-variant`.
39
+
40
+ New options: `thumbBorderRadius`, `minTouchTarget`, `markSize`, `markColor`,
41
+ `labelTypeface`, `labelColor`, `tooltipColor`, `tooltipTextColor`,
42
+ `tooltipShadow`, `tooltipBorderRadius`, `transitionMs`. `trackHeight`,
43
+ `thumbSize` and `borderRadius` are unchanged.
44
+
45
+ **New capability.**
46
+ - `mode="range"` — two thumbs, a `{ start, end }` value, `minGap` to fence the
47
+ ends apart. Thumbs may cross and swap, and the dragged one keeps focus.
48
+ - `marks` and `snapToMarks` — labelled stops, spoken in place of the number.
49
+ - `valueDisplay` — `none`, `inline`, `tooltip`, or `input`, which seats a
50
+ compact `uni-number-input` at the trailing edge, two-way bound to the same
51
+ value: drag for the ballpark, type for the exact figure. Single mode only.
52
+ - `origin` — anchor the fill somewhere other than `min`, for sliders spanning ±.
53
+ - `sliding` and `changed` outputs, replacing an implicit per-frame model write.
54
+ **Bind `changed` in forms**; `sliding` fires every frame of a drag.
55
+ - `largeStep`, defaulting to a tenth of the range, for `PageUp`/`PageDown` and
56
+ `Shift+Arrow`.
57
+ - Right-to-left support: the horizontal arrows and the track mirror; the value
58
+ does not.
59
+
60
+ **Also new: the `cdk/number` primitives** this is built on, shared with the
61
+ numeric input family still to come — exact scaled-`BigInt` decimal arithmetic
62
+ (`stepDecimal`, `roundDecimal`, `clampDecimal`), locale-aware
63
+ `parseNumber`/`formatNumber` over `Intl`, and `createPressRepeat` for
64
+ hold-to-repeat stepper buttons. Stepping `0.1` twenty times from `0` now lands
65
+ on exactly `2`, and `1.15` rounds to `1.2` where `(1.15).toFixed(1)` gives
66
+ `'1.1'`.
67
+
68
+ ### Minor Changes
69
+
70
+ - [`2a79bb8`](https://github.com/uni-design-system/uni/commit/2a79bb8ebd8fd0b5cd792697972b6f48b444c80f) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-number-input`: the field for a quantity, a price, a percentage or a
71
+ measurement, with locale-aware parsing, `Intl` formatting on commit,
72
+ prefix/suffix adornments, min/max/step fences and steppers that hold to repeat.
73
+
74
+ ```html
75
+ <uni-number-input label="Quantity" [(value)]="qty" [min]="1" />
76
+ <uni-number-input label="Unit price" currency="USD" [(value)]="price" />
77
+ ```
78
+
79
+ **Why not `<input type="number">`.** Per the HTML value sanitization algorithm,
80
+ a number input whose text is not a valid floating-point number reports
81
+ `value === ''`. Type `12,50` as most of Europe does, or paste `1,234.56` from a
82
+ spreadsheet, and the app reads an empty field with no way to tell that from a
83
+ blank one — a data-loss bug, and the reason this is `type="text"` with
84
+ `role="spinbutton"`. The platform control also cannot group thousands, cannot
85
+ place an affix outside the editable text, has ~10px unstyleable spinners below
86
+ the WCAG 2.2 target minimum, changes value on the scroll wheel while focused,
87
+ and steps in floats.
88
+
89
+ **What it does instead.**
90
+ - **Presets** — `decimal`, `integer`, `currency`, `percent` — supply decimals,
91
+ grouping, affix and `inputmode` together, so a money field is `label`,
92
+ `currency="USD"`, `[(value)]`. `numberFormat` is the escape hatch, merged over
93
+ the preset.
94
+ - **Parses what people actually type**: canonical ASCII always, locale grouping
95
+ (`1.234,56` in German), pasted affixes and currency symbols, accounting
96
+ negatives `(1,234.56)`, localized digit systems, compact `1.5k`, and — behind
97
+ `allowExpressions` — spreadsheet arithmetic like `12*3`, via a shunting-yard
98
+ parser that never calls `eval`.
99
+ - **Unreadable text stays in the field**, flagged with a dashed underline and a
100
+ `rejected` event, rather than being silently swallowed.
101
+ - **Exact arithmetic.** Stepping `0.1` twenty times from `0` lands on exactly
102
+ `2`; `1.15` rounds to `1.2` where `(1.15).toFixed(1)` gives `'1.1'`. A second
103
+ `valueAsString` model carries digits a `number` cannot, and a dev-mode warning
104
+ fires when a bound `value` cannot round-trip.
105
+ - **Percent never divides behind your back**: `preset="percent"` shows `15%` for
106
+ `15`. Models that really are fractions set `valueIsFraction`.
107
+ - Clamping on commit rather than per keystroke, visible fences that disable the
108
+ matching stepper and announce, `wrap` for cyclic fields, `emptyStepValue`,
109
+ four stepper layouts, and hold-to-repeat that announces once on release.
110
+
111
+ `uni-slider` gains `valueDisplay="input"`, which seats one of these as its
112
+ readout — drag for the ballpark, type for the exact value.
113
+
114
+ Adds `numberInput` to `ComponentName` with a theme entry. Field chrome is not
115
+ duplicated there: colour, border, radius and focus come from the shared `input`
116
+ options via `uni-input-box`, so a number field restyles with every other field.
117
+
118
+ `uni-input-box` gains a `managedInset` input. The themed leading inset normally
119
+ rides the inner `<input>`, which is right while the text is the field's leading
120
+ edge and wrong the moment an adornment sits in front — a currency prefix would
121
+ hug the border while the number it belongs to sat indented past it. A field with
122
+ adornments sets `managedInset` and places the inset on whichever element is
123
+ actually first. Existing fields are unaffected: the default is `false`.
124
+
125
+ - [`e7875ee`](https://github.com/uni-design-system/uni/commit/e7875ee81030a703f4ca1904bb94cb8ddc7f57b9) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-number-range-input`: two linked numeric fields in one chrome with a
126
+ single `{ start, end }` value — price filters, thresholds, tolerances. This
127
+ completes the numeric family alongside `uni-number-input`,
128
+ `uni-quantity-stepper` and the rebuilt `uni-slider`.
129
+
130
+ ```html
131
+ <uni-number-range-input label="Price range" currency="USD" [(value)]="price" [minGap]="50" />
132
+ ```
133
+
134
+ `start`/`end` deliberately match `UniDateRange`, so the library has one range
135
+ vocabulary, and they never collide with the `min`/`max` **inputs**, which mean
136
+ the fence rather than the value.
137
+
138
+ **The rules that make it one field rather than two glued together:**
139
+ - **Either end alone is a valid value.** `{ start: 50 }` means "50 and up",
140
+ which is a real filter. This is where it diverges from `uni-date-time-input`,
141
+ whose two parts are two halves of one answer.
142
+ - **Stepping is fenced; typing swaps.** A stepper can never walk one end through
143
+ the other — its wall is the other end, held off by `minGap`, and each end's
144
+ `aria-valuemin`/`aria-valuemax` report that wall rather than the outer bounds.
145
+ A _typed_ backwards commit is swapped and announced instead, the rule
146
+ `uni-calendar` applies to a backwards date range: clamping against the other
147
+ end would destroy the number just entered.
148
+ - **`minGap` pushes the end you edited**, not the other one, which is what makes
149
+ stepping behave as a fence rather than dragging the range along.
150
+ - A refused draft flags only the end it was typed into; the other stays valid.
151
+ - `preset`, `currency`, `prefix`, `suffix`, `decimals`, `grouping`, `locale` and
152
+ `roundingMode` are forwarded to both ends so the halves always read alike.
153
+
154
+ It owns its commit path rather than nesting two `uni-number-input`s, because the
155
+ two behaviours above need _different_ bounds — a stepper must be fenced at the
156
+ other end while a typed commit must arrive un-clamped — and a child field
157
+ applies one bound pair to both. The arithmetic, parsing and formatting are still
158
+ the shared `cdk/number` primitives.
159
+
160
+ Adds `numberRangeInput` to `ComponentName` with a theme entry (`partGap`,
161
+ `dividerText`, `dividerColor`). Field chrome is not duplicated there: colour,
162
+ border, radius and focus come from the shared `input` options via
163
+ `uni-input-box`. `dividerText` is literal punctuation rather than an icon token —
164
+ an en dash between two numbers is not a glyph a theme swaps artwork for.
165
+
166
+ - [`589cecb`](https://github.com/uni-design-system/uni/commit/589cecb178d9119eea3fbc3f3cd9149eefdaa036) Thanks [@gaenglish](https://github.com/gaenglish)! - New `uni-quantity-stepper`: `− 3 +` for cart lines, table cells and seat counts
167
+ — the numeric core with no field chrome, no label and no room for either.
168
+
169
+ ```html
170
+ <uni-quantity-stepper label="Quantity, Blue T-shirt (M)" [(value)]="qty" [min]="1" />
171
+ ```
172
+
173
+ A separate component rather than a `chrome="bare"` flag on `uni-number-input`,
174
+ because this control is defined by what it does _not_ have — presets, affixes,
175
+ expressions, four stepper layouts — and eight inputs are easier to write
176
+ correctly than forty plus a list of which ones to leave alone. The arithmetic,
177
+ parsing and hold-to-repeat come from the same `cdk/number` primitives, so
178
+ `1,200` and the keyboard map behave identically in both.
179
+ - **`deleteAtMin`** is the cart pattern in one attribute: at the floor the
180
+ decrement becomes a remove affordance, renamed `Remove {label}`, and emits
181
+ `removed` rather than stepping to zero. Without it every shop reimplements the
182
+ same `value === 1 ? remove() : step(-1)` branch outside the component. (The
183
+ spec called this output `emptied`; that is a native `HTMLMediaElement` event
184
+ name, which `@angular-eslint/no-output-native` rightly rejects, and `removed`
185
+ is already what `uni-tag` calls the same request.)
186
+ - The middle is a real input by default — typing `12` beats tapping `+` eleven
187
+ times, and it takes the same grouped and locale-aware entry the field does.
188
+ `editable=false` renders the number as text for read-mostly tables, and the
189
+ buttons become the tab stops since there is nothing else to focus.
190
+ - `size` is `sm` / `md` / `lg` at 24 / 32 / 40px _outer_ height, so an `md`
191
+ stepper lines up with a 32px field beside it, with the buttons square at that
192
+ height. `md` and `lg` clear the 24×24 pointer target of WCAG 2.2 SC 2.5.8;
193
+ `sm` leaves 22px inside its border and is the dense desktop option.
194
+
195
+ Adds `quantityStepper` to `ComponentName` with a theme entry. Unlike the other
196
+ numeric controls it does **not** inherit the shared `input` chrome — it is not a
197
+ field — so it carries its own container tokens, defaulted to the same values
198
+ `input` uses so a cart stepper and a form field look related out of the box.
199
+ Height comes from the entry's `sizes` block rather than an option.
200
+
3
201
  ## 9.0.1
4
202
 
5
203
  ### Patch Changes
@@ -1294,6 +1294,47 @@ var buildComponents = (c) => ({
1294
1294
  rows: 3,
1295
1295
  resize: "vertical"
1296
1296
  } },
1297
+ numberInput: { options: {
1298
+ stepperLayout: "stacked",
1299
+ incrementIcon: "plus",
1300
+ decrementIcon: "minus",
1301
+ stepUpIcon: "chevronUp",
1302
+ stepDownIcon: "chevronDown",
1303
+ stepperWidth: 32,
1304
+ minTouchTarget: 24,
1305
+ affixColor: "on-primary-surface-variant",
1306
+ affixGap: "xs",
1307
+ align: "start",
1308
+ tabularNumerals: true,
1309
+ repeatDelayMs: 500,
1310
+ repeatIntervalMs: 100,
1311
+ repeatFastIntervalMs: 25,
1312
+ repeatRampMs: 2e3
1313
+ } },
1314
+ numberRangeInput: { options: {
1315
+ partGap: "sm",
1316
+ dividerText: "–",
1317
+ dividerColor: "outline",
1318
+ affixColor: "on-primary-surface-variant",
1319
+ affixGap: "xs"
1320
+ } },
1321
+ quantityStepper: {
1322
+ options: {
1323
+ color: "primary-surface",
1324
+ border: "light",
1325
+ borderRadius: "xs",
1326
+ incrementIcon: "plus",
1327
+ decrementIcon: "minus",
1328
+ deleteIcon: "delete",
1329
+ tabularNumerals: true,
1330
+ valueWidth: "3ch"
1331
+ },
1332
+ sizes: {
1333
+ sm: { height: 24 },
1334
+ md: { height: 32 },
1335
+ lg: { height: 40 }
1336
+ }
1337
+ },
1297
1338
  tabs: { options: {
1298
1339
  typeface: "title-small",
1299
1340
  textColor: "on-surface-variant",
@@ -1717,11 +1758,21 @@ var buildComponents = (c) => ({
1717
1758
  gap: "xxs"
1718
1759
  } },
1719
1760
  slider: { options: {
1720
- color: "primary",
1721
- trackColor: "surface-variant",
1722
- borderRadius: "max",
1723
1761
  trackHeight: 4,
1724
- thumbSize: 16
1762
+ trackColor: "primary-container",
1763
+ borderRadius: "max",
1764
+ thumbSize: 16,
1765
+ thumbBorderRadius: "max",
1766
+ minTouchTarget: 24,
1767
+ markSize: 3,
1768
+ markColor: "on-primary-container",
1769
+ labelTypeface: "label",
1770
+ labelColor: "on-surface-variant",
1771
+ tooltipColor: "inverse-surface",
1772
+ tooltipTextColor: "on-inverse-surface",
1773
+ tooltipShadow: "menu",
1774
+ tooltipBorderRadius: "xs",
1775
+ transitionMs: 120
1725
1776
  } },
1726
1777
  skeleton: { options: {
1727
1778
  color: "surface-variant",