srcdev-nuxt-components 9.6.7 → 9.6.8
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/.claude/rules/consumer/ids-from-useid.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +6 -0
- package/.claude/skills/component-naming.md +2 -2
- package/.claude/skills/components/input-checkbox-radio.md +19 -3
- package/.claude/skills/components/input-checkbox.md +14 -3
- package/.claude/skills/components/input-description.md +1 -1
- package/.claude/skills/components/input-error.md +1 -1
- package/.claude/skills/components/input-label.md +2 -2
- package/.claude/skills/components/input-number.md +44 -13
- package/.claude/skills/components/input-radio.md +15 -1
- package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
- package/.claude/skills/components/input-textarea-core.md +4 -4
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/testing-add-unit-test.md +17 -0
- package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
- package/.vscode/srcdev-component-input-range.code-snippets +38 -18
- package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
- package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
- package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
- package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
- package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
- package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
- package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
- package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +42 -2
- package/app/components/05.forms/input-number/InputNumber.vue +37 -25
- package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
- package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
- package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
- package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
- package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
- package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
- package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
- package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
- package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
- package/app/components/05.forms/input-range/InputRange.vue +247 -0
- package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
- package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
- package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
- package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
- package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +5 -5
- package/package.json +1 -1
- package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
- package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/**/*.vue"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Element ids come from `useId()`, never from data
|
|
7
|
+
|
|
8
|
+
Build `id`/`for` pairs from `useId()` (plus a loop index for repeated items), never from props or
|
|
9
|
+
data values like `` `${name}-${item.value}` ``.
|
|
10
|
+
|
|
11
|
+
Why: two instances sharing a `name` and values produce duplicate ids, and a value with spaces makes
|
|
12
|
+
an invalid one, so labels toggle the wrong control (`MultipleCheckboxes`, 2026-10-16). Test
|
|
13
|
+
uniqueness with both instances inside one host component: separate `mountSuspended` calls each
|
|
14
|
+
restart the `useId()` counter. Detail: skill `testing-add-unit-test.md` (srcdev-nuxt-components).
|
|
@@ -262,6 +262,12 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
262
262
|
the edits in private memory. Note that an inline-size container can't size itself from its
|
|
263
263
|
content: say so in `CONSUMER-STYLING.md` (`ProfileSection`).
|
|
264
264
|
- Grid tracks that hold text are `minmax(0, …)` so an unbroken string can't widen the grid.
|
|
265
|
+
- An `auto-fit` column minimum taken from a token or a measured width is wrapped in
|
|
266
|
+
`min(100%, …)`, or one long item makes the column wider than its container.
|
|
267
|
+
- A component whose root is a `<fieldset>` sets `min-inline-size: 0` on it: a native fieldset
|
|
268
|
+
defaults to its min-content width and overflows a narrow column (`MultipleCheckboxes`,
|
|
269
|
+
2026-10-16).
|
|
270
|
+
- Element ids come from `useId()`, never from data values (rule `consumer/ids-from-useid.md`).
|
|
265
271
|
- Layout measured in script (connector lines, indicators) re-measures when any measured item
|
|
266
272
|
resizes or changes class, not only when the container resizes (`measured-layout-observers.md`,
|
|
267
273
|
pitfall #28).
|
|
@@ -83,8 +83,8 @@ external consumer, free to rename. Run `/check-component-usage` for the current
|
|
|
83
83
|
| InputTextWithLabel | `InputTextField` | yes |
|
|
84
84
|
| InputPasswordWithLabel | `InputPasswordField` | yes |
|
|
85
85
|
| InputTextAsNumberWithLabel | `InputTextAsNumberField` | - |
|
|
86
|
-
| InputRangeCore | `InputRange` | - |
|
|
87
|
-
| InputRangeDefault | `InputRangeField` | - |
|
|
86
|
+
| ~~InputRangeCore~~ | ✅ `InputRange` (2026-10-16) | - |
|
|
87
|
+
| ~~InputRangeDefault~~ | ✅ `InputRangeField` (2026-10-16) | - |
|
|
88
88
|
| InputTextareaCore | `InputTextarea` | - |
|
|
89
89
|
| InputTextareaWithLabel | `InputTextareaField` | yes |
|
|
90
90
|
| InputSelectCore | `InputSelect` | yes |
|
|
@@ -28,7 +28,6 @@ Use these three directly only for a custom group or layout:
|
|
|
28
28
|
|
|
29
29
|
<InputCheckboxRadioButton
|
|
30
30
|
v-for="option in options"
|
|
31
|
-
:id="`service-${option.value}`"
|
|
32
31
|
:key="option.value"
|
|
33
32
|
v-model="state.services"
|
|
34
33
|
type="checkbox"
|
|
@@ -85,9 +84,9 @@ Same props as the control minus `isButton` (always on), plus:
|
|
|
85
84
|
|
|
86
85
|
| Prop | Type | Default | Notes |
|
|
87
86
|
|---|---|---|---|
|
|
88
|
-
| `id` | `string` |
|
|
87
|
+
| `id` | `string` | generated | Used for both the input and `label[for]`. Optional since 2026-10-16: omitted, it comes from `useId()`. Never build it from data values (rule `ids-from-useid.md`). |
|
|
89
88
|
| `label` | `string` | `""` | Replaced by the `labelContent` slot when given. |
|
|
90
|
-
| `:options-layout` | `OptionsLayout` | `"equal-widths"` | `data-options-layout
|
|
89
|
+
| `:options-layout` | `OptionsLayout` | `"equal-widths"` | `data-options-layout`, a styling hook only (until 2026-10-16 `inline` made the label `nowrap`). |
|
|
91
90
|
| `direction` | `"row" \| "row-reverse"` | `"row"` | `data-direction`; `row-reverse` puts the control on the right. Reacts to changes. |
|
|
92
91
|
| `:is-pill` | `boolean` | `false` | `data-pill`, fully rounded. |
|
|
93
92
|
| `:item-icon` | `string` | `"material-symbols:add-2"` | Decorator icon name. The `itemIcon` slot replaces it entirely. |
|
|
@@ -124,6 +123,15 @@ Full detail in `app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md
|
|
|
124
123
|
|
|
125
124
|
---
|
|
126
125
|
|
|
126
|
+
## Long and hostile content
|
|
127
|
+
|
|
128
|
+
Both labels wrap long unbroken words and URLs (`overflow-wrap: anywhere`, `min-inline-size: 0` on
|
|
129
|
+
label and root), and the control and decorator icon keep their size (`flex-shrink: 0`). No
|
|
130
|
+
line-clamp tokens: an option label names the choice and must stay readable. The Field and Button
|
|
131
|
+
`StressTest` stories cover it; the bare control renders no text, so it has none.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
127
135
|
## History
|
|
128
136
|
|
|
129
137
|
**Migrated 2026-09-27** (3/5 → full compliance):
|
|
@@ -147,3 +155,11 @@ Full detail in `app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md
|
|
|
147
155
|
- Button radius, gap, paddings and the Field label colour became tokens; label colour default moved
|
|
148
156
|
from `--colour-text-default` to `--theme-text`. Removed an undeclared `--_box-shadow` and an
|
|
149
157
|
invalid unitless `min-height`.
|
|
158
|
+
|
|
159
|
+
**Stress pass 2026-10-16**:
|
|
160
|
+
|
|
161
|
+
- Removed the inline-layout `nowrap` on the button label (private `--_white-space`), which made an
|
|
162
|
+
unbroken label overflow the row; labels now wrap and the control/icon no longer shrink.
|
|
163
|
+
- `InputCheckboxRadioButton`'s `id` became optional with a `useId()` fallback.
|
|
164
|
+
- Specs now unmount every wrapper in `afterEach`; stories moved to `StoryObj` with Docs
|
|
165
|
+
descriptions, parent-story links, `CanvasSwitcher` (Field, Button) and `StressTest` stories.
|
|
@@ -24,7 +24,7 @@ instead.
|
|
|
24
24
|
|
|
25
25
|
| Prop (template form) | Type | Default | Notes |
|
|
26
26
|
|---|---|---|---|
|
|
27
|
-
| `name` | `string` | (required) | Shared `name` on every checkbox; also used
|
|
27
|
+
| `name` | `string` | (required) | Shared `name` on every checkbox; also used for the error/description ids. Button-presentation checkbox ids come from `useId()` plus the option index (until 2026-10-16 they were `${name}-${item.value}`, which duplicated across two groups sharing a name and broke on values with spaces). |
|
|
28
28
|
| `legend` | `string` | (required) | Fieldset `<legend>` text. |
|
|
29
29
|
| `:error-message` | `string \| object` | (required) | Shown by `InputError` when `fieldHasError` is true. Pass `""` when there's no error. An array renders as a list. |
|
|
30
30
|
| `:field-has-error` | `boolean` | `false` | Shows the error, sets `aria-invalid` on the fieldset and points each checkbox's `aria-describedby` at the error. |
|
|
@@ -36,7 +36,7 @@ instead.
|
|
|
36
36
|
| `:display-as-disc` | `boolean` | `false` | Round checkbox symbol. |
|
|
37
37
|
| `:theme` | `FormUiTheme` | `"default"` | Forwarded to each control. |
|
|
38
38
|
| `:input-variant` | `InputUiVariant` | `"normal"` | Forwarded to each control and the error message. |
|
|
39
|
-
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset
|
|
39
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset>`; updates when the prop changes. |
|
|
40
40
|
| `data-testid` | `string` | `"multiple-checkboxes"` | On the root `<fieldset>`. |
|
|
41
41
|
| `label` | `string` | `""` | **Ignored.** Kept only so existing call sites that pass it don't break; it has never rendered. Use `legend` or the description slots. |
|
|
42
42
|
|
|
@@ -84,7 +84,7 @@ instead.
|
|
|
84
84
|
| `:options-layout` | `OptionsLayout` | `"equal-widths"` | Rendered as `data-options-layout` on the items container. |
|
|
85
85
|
| `:theme` | `FormUiTheme` | `"default"` | |
|
|
86
86
|
| `:input-variant` | `InputUiVariant` | `"normal"` | |
|
|
87
|
-
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset
|
|
87
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset>`; updates when the prop changes. |
|
|
88
88
|
| `data-testid` | `string` | `"single-checkbox"` | On the root `<fieldset>`. |
|
|
89
89
|
|
|
90
90
|
### v-model
|
|
@@ -122,6 +122,17 @@ controls' own colours are the `--input-checkbox-*` tokens in `input-checkbox-rad
|
|
|
122
122
|
|
|
123
123
|
---
|
|
124
124
|
|
|
125
|
+
## Long and hostile content
|
|
126
|
+
|
|
127
|
+
Both root fieldsets set `min-inline-size: 0` (a native `<fieldset>` otherwise grows to its
|
|
128
|
+
min-content width and overflows a narrow column) and `overflow-wrap: anywhere`, which inherits
|
|
129
|
+
into the legend, description, labels and error, so long unbroken words and URLs wrap. Equal-width
|
|
130
|
+
columns are capped at `100%` of the container. Button-mode labels wrap too (`InputCheckboxRadioButton` dropped its inline `nowrap`
|
|
131
|
+
2026-10-16). No line-clamp tokens: the legend, option labels and error name the choice or the
|
|
132
|
+
problem and must stay readable. The `StressTest` story in each story file covers this.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
125
136
|
## Migration notes (2026-09-25)
|
|
126
137
|
|
|
127
138
|
Brought to full compliance. Behaviour changes a consumer could notice:
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`InputDescription` renders a form field's help text. Every `*Field` wrapper in `05.forms`
|
|
6
6
|
(`InputTextWithLabel`, `InputNumberField`, `InputSelectWithLabel`, `InputTextareaWithLabel`,
|
|
7
|
-
`
|
|
7
|
+
`InputRangeField`, `InputTextAsNumberWithLabel`, `SingleCheckbox`, `MultipleCheckboxes`,
|
|
8
8
|
`MultipleRadiobuttons`) already renders one and forwards its own `descriptionText` /
|
|
9
9
|
`descriptionHtml` slots into it, so consumers normally just pass those slots to the wrapper:
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`InputError` renders the red error message strip under a form control. Every `*Field` wrapper in
|
|
6
6
|
`05.forms` (`InputTextWithLabel`, `InputNumberField`, `InputSelectWithLabel`, `InputTextareaWithLabel`,
|
|
7
|
-
`
|
|
7
|
+
`InputRangeField`, `ToggleSwitchWithLabel`, `SingleCheckbox`, `MultipleCheckboxes`,
|
|
8
8
|
`MultipleRadiobuttons`, `InputTextAsNumberWithLabel`) already renders one, so consumers rarely use
|
|
9
9
|
it directly. Use it yourself only when building a custom field wrapper.
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`InputLabel` renders a form field's `<label>`. Every labelled wrapper in `05.forms`
|
|
6
6
|
(`InputTextWithLabel`, `InputTextAsNumberWithLabel`, `InputNumberField`, `InputSelectWithLabel`,
|
|
7
|
-
`InputTextareaWithLabel`, `
|
|
7
|
+
`InputTextareaWithLabel`, `InputRangeField`, `ToggleSwitchWithLabel`,
|
|
8
8
|
`ToggleSwitchWithLabelInline`) already renders one from its `label` prop, so consumers normally
|
|
9
9
|
never use it directly.
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ export default defineAppConfig({
|
|
|
62
62
|
- `"required"` mode: a marker on fields with `required`; `"optional"` mode: a marker on fields
|
|
63
63
|
without it; `"none"`: nothing (default, so existing sites are unchanged).
|
|
64
64
|
- `InputTextWithLabel`, `InputTextAsNumberWithLabel`, `InputNumberField`, `InputSelectWithLabel`,
|
|
65
|
-
`InputTextareaWithLabel` and `
|
|
65
|
+
`InputTextareaWithLabel` and `InputRangeField` forward `:required`.
|
|
66
66
|
`ToggleSwitchWithLabel`/`ToggleSwitchWithLabelInline` pass `indicator="none"`: a switch always
|
|
67
67
|
has a value, so neither marker means anything there.
|
|
68
68
|
- The wrappers don't expose the indicator props per field; use `app.config`.
|
|
@@ -29,17 +29,17 @@ Most consumers should reach for **InputNumberField** rather than `InputNumber` d
|
|
|
29
29
|
| `:placeholder` | `string` | `""` | Native `placeholder`. |
|
|
30
30
|
| `:required` | `boolean` | `false` | Native `required`. |
|
|
31
31
|
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
32
|
-
| `:weight` | `FormWeight` | `"normal"` | Adds an `input-number--{weight}` class to the input, for prop-shape consistency with sibling `05.forms` components. **No CSS in the library currently styles this class — it's a no-op today**, same convention as `
|
|
33
|
-
| `:input-variant` | `InputUiVariant` | `"normal"` |
|
|
32
|
+
| `:weight` | `FormWeight` | `"normal"` | Adds an `input-number--{weight}` class to the input, for prop-shape consistency with sibling `05.forms` components. **No CSS in the library currently styles this class — it's a no-op today**, same convention as `InputRange`'s `weight` prop. |
|
|
33
|
+
| `:input-variant` | `InputUiVariant` | `"normal"` | Rendered as `data-input-variant` on the wrapper (a bare class until 2026-10-16) — `"normal"` (bordered box) and `"underlined"` (bottom-border only) have CSS; `"outlined"` doesn't (see CONSUMER-STYLING.md). Same prop/behaviour as `InputTextCore`. |
|
|
34
34
|
| `:field-has-error` | `boolean` | `false` | Drives `data-invalid` on the wrapper. |
|
|
35
|
-
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the input element. |
|
|
35
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the input element. Reacts to changes. |
|
|
36
36
|
| `:aria-describedby` | `string` | `""` | Forwarded to the native input. |
|
|
37
37
|
|
|
38
38
|
### v-model
|
|
39
39
|
|
|
40
40
|
`v-model` — `number | readonly number[]`, **required** (`defineModel({ required: true })`). Bound
|
|
41
41
|
directly to the native number input. Required rather than defaulted deliberately — same reasoning
|
|
42
|
-
as `
|
|
42
|
+
as `InputRange`'s `v-model` (see that skill doc) — this also satisfies `vue/require-default-prop`.
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -47,15 +47,15 @@ as `InputRangeCore`'s `v-model` (see that skill doc) — this also satisfies `vu
|
|
|
47
47
|
|
|
48
48
|
| Slot | Notes |
|
|
49
49
|
|------|-------|
|
|
50
|
-
| `left` | Rendered before the input (e.g. a step-down button)
|
|
51
|
-
| `right` | Rendered after the input (e.g. a step-up button)
|
|
50
|
+
| `left` | Rendered before the input (e.g. a step-down button) in `.input-number-slot-left`. Sets `data-has-left-slot` on the wrapper. |
|
|
51
|
+
| `right` | Rendered after the input (e.g. a step-up button) in `.input-number-slot-right`. Sets `data-has-right-slot` on the wrapper. |
|
|
52
52
|
|
|
53
|
-
Any `.input-button` inside `.slot` gets its own border/outline stripped and is sized to
|
|
53
|
+
Any `.input-button` inside `.input-number-slot` gets its own border/outline stripped and is sized to
|
|
54
54
|
match the input (`aspect-ratio: 1`, `width: var(--input-min-height)`) regardless of which slot
|
|
55
55
|
it's in. The divider border is then added directly to that button element specifically when its
|
|
56
|
-
slot is present
|
|
57
|
-
right-hand mirror
|
|
58
|
-
containing `.slot` div. This makes step buttons read as embedded segments of the same control
|
|
56
|
+
slot is present (`.input-number-slot-left .input-button { border-right: ... }` and the
|
|
57
|
+
right-hand mirror), so it sits flush against the button's own edge rather than around the
|
|
58
|
+
containing `.input-number-slot` div. This makes step buttons read as embedded segments of the same control
|
|
59
59
|
rather than separate floating buttons. The divider reuses the wrapper's own border colour token
|
|
60
60
|
(no separate divider token), same as `InputTextCore`/`InputTextAsNumberWithLabel`.
|
|
61
61
|
|
|
@@ -83,7 +83,7 @@ score-1/5 state. Several latent bugs were fixed alongside the standard checklist
|
|
|
83
83
|
- The native `<input>` had no static `input-number` class — only the dynamic
|
|
84
84
|
`elementClasses` passthrough — so the entire `.input-number { ... }` style block (font,
|
|
85
85
|
padding, colour, focus box-shadow) never matched anything. Fixed by adding the class directly.
|
|
86
|
-
- `placeholder` was declared as a prop but never bound on the `<input>` — unlike `
|
|
86
|
+
- `placeholder` was declared as a prop but never bound on the `<input>` — unlike `InputRange`
|
|
87
87
|
(where a range input genuinely has no placeholder concept and the no-op is intentional and
|
|
88
88
|
documented), a number input *does* support `placeholder` meaningfully, so this was a real gap,
|
|
89
89
|
not a deliberate no-op. Fixed by binding `:placeholder`.
|
|
@@ -107,9 +107,9 @@ score-1/5 state. Several latent bugs were fixed alongside the standard checklist
|
|
|
107
107
|
aligned with the rest of `05.forms`.
|
|
108
108
|
- `weight` was passed from `InputNumberField` to `InputNumber` but `InputNumber` had no
|
|
109
109
|
matching prop, so it fell through as a raw, meaningless `weight="normal"` HTML attribute on the
|
|
110
|
-
wrapper `<div>`. Fixed by declaring the prop on `InputNumber` too, matching `
|
|
110
|
+
wrapper `<div>`. Fixed by declaring the prop on `InputNumber` too, matching `InputRange`.
|
|
111
111
|
- `defineModel()` had no default, tripping `vue/require-default-prop` — fixed the same way as
|
|
112
|
-
`
|
|
112
|
+
`InputRange`: `defineModel({ required: true })`, not a rule disable or an artificial default.
|
|
113
113
|
- A single-use private token, `--_border`, was inlined to the public
|
|
114
114
|
`var(--input-number-border, var(--theme-border))` directly at its point of use — it had no
|
|
115
115
|
composition or state-swap behind it, just a bare pass-through (see pitfall #20). `--_surface`
|
|
@@ -221,3 +221,34 @@ button icon content itself is consumer-supplied).
|
|
|
221
221
|
|
|
222
222
|
Step-down is disabled (`readonly` on its `InputButton`) once the value reaches `min`, and
|
|
223
223
|
step-up once it reaches `max`; clicking either button while at that boundary is a no-op.
|
|
224
|
+
Each press adds or subtracts `step` (its absolute value, or 1 when it's 0), rounds away
|
|
225
|
+
floating-point drift (0.2 + 0.1 gives 0.3) and clamps to `min`/`max`, so a step that would overshoot
|
|
226
|
+
lands on the limit and a typed out-of-range value is pulled back in. From an empty field the first
|
|
227
|
+
press sets 0, clamped into range. `:style-class-passthrough` lands on the root `.input-number-field`
|
|
228
|
+
only (it also reached the inner input until 2026-10-16) and reacts to changes. The root carries
|
|
229
|
+
`data-input-variant`, `data-theme` and `data-invalid`; its old `error`, `theme-*` and variant
|
|
230
|
+
classes are gone.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Stress pass 2026-10-16
|
|
235
|
+
|
|
236
|
+
- **Stepping** (`InputNumberField`): 0.1 steps drifted (0.30000000000000004); a step that didn't
|
|
237
|
+
divide the range overshot `max`/`min` (8 + 5 with max 10 gave 13); an empty field turned into
|
|
238
|
+
`NaN`. Now rounded to the step's precision, clamped to `min`/`max`, and an empty field starts at
|
|
239
|
+
0, clamped. A zero or negative `step` steps by its size, or 1.
|
|
240
|
+
- **Mobile keyboard**: `inputmode="numeric"` and `pattern="[0-9]+"` were hardcoded, so a negative
|
|
241
|
+
or decimal value couldn't be typed on phones (and number inputs ignore `pattern`). `inputmode` is
|
|
242
|
+
now `numeric` for whole steps, `decimal` for fractional ones, and omitted when `min` is negative;
|
|
243
|
+
`pattern` is gone.
|
|
244
|
+
- **Width**: the input's minimum width was `max`'s digit count in `em`, uncapped and blind to a
|
|
245
|
+
longer negative `min` or the step's decimals. It now counts the longest of `min`/`max` plus the
|
|
246
|
+
step's decimals, capped at `12em`; the wrapper has `max-inline-size: 100%` and the step buttons
|
|
247
|
+
don't shrink. The Field wraps long label, description and error copy.
|
|
248
|
+
- **State hooks**: bare state classes became data attributes and the slot holders got prefixed
|
|
249
|
+
class names (CONSUMER-STYLING.md has the mapping). Removed the non-existent `body-normal-bold`
|
|
250
|
+
utility class from the label and a dead `.label-description` rule. `InputError` now gets
|
|
251
|
+
`input-variant` like the label does.
|
|
252
|
+
- Specs unmount in `afterEach`; stories moved to `StoryObj` with Docs descriptions, a
|
|
253
|
+
`DecimalSteps` story, `CanvasSwitcher` and a `StressTest` story on the Field. The bare
|
|
254
|
+
control's step-button demo uses `InputButton`, not raw buttons.
|
|
@@ -41,7 +41,7 @@ option in `fieldData.data`, and an `InputError`. It's the single-choice sibling
|
|
|
41
41
|
| `:options-layout` | `OptionsLayout` | `"equal-widths"` | `"equal-widths"` (auto-fit grid sized to the longest label), `"inline"` (wrapping row) or `"block"` (column). Rendered as `data-options-layout` on the items container. |
|
|
42
42
|
| `:theme` | `FormUiTheme` | `"default"` | Passed to each radio. |
|
|
43
43
|
| `:input-variant` | `InputUiVariant` | `"normal"` | Passed to each radio, the description and the error. |
|
|
44
|
-
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset
|
|
44
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root `<fieldset>`; updates when the prop changes. |
|
|
45
45
|
| `data-testid` | `string` | `"multiple-radio-buttons"` | On the root `<fieldset>`. |
|
|
46
46
|
|
|
47
47
|
### v-model
|
|
@@ -93,3 +93,17 @@ tokens from `input-checkbox-radio`.
|
|
|
93
93
|
to `data-options-layout`; `MultipleCheckboxes` and `SingleCheckbox` got the same change.
|
|
94
94
|
- Gap and top margin are public tokens. The label-width measurement no longer includes a
|
|
95
95
|
selector for a class that doesn't exist. `fieldData` is a typed required model.
|
|
96
|
+
|
|
97
|
+
**Stress pass 2026-10-16**:
|
|
98
|
+
|
|
99
|
+
- Button-mode radio ids were `${name}-${item.value}`, so two groups sharing a `name` got duplicate
|
|
100
|
+
ids and a value with spaces made an invalid one (CLAUDE.md pitfall 30). `MultipleRadiobuttons`
|
|
101
|
+
no longer passes an `id`; `InputCheckboxRadioButton` generates one with `useId()`.
|
|
102
|
+
- The root fieldset sets `min-inline-size: 0` and `overflow-wrap: anywhere`, and equal-width
|
|
103
|
+
columns are capped at `min(100%, ...)`, so long legends, labels and errors wrap inside a narrow
|
|
104
|
+
column (same fix as `MultipleCheckboxes`). No line-clamp tokens: the legend, option labels and
|
|
105
|
+
error must stay readable.
|
|
106
|
+
- `styleClassPassthrough` is watched instead of read once at mount.
|
|
107
|
+
- The spec unmounts every wrapper in `afterEach`; the story moved to `StoryObj` with Docs
|
|
108
|
+
descriptions, `CanvasSwitcher` and a `StressTest` story, and its gap control is labelled as a
|
|
109
|
+
story-only CSS token.
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# InputRange Component
|
|
2
|
+
|
|
3
|
+
> **Renamed 2026-10-16**: `InputRangeCore` → `InputRange`, `InputRangeDefault` → `InputRangeField`
|
|
4
|
+
> (DOM classes `.input-range-core` → `.input-range`, `.input-range-with-label` →
|
|
5
|
+
> `.input-range-field`; full class list in CONSUMER-STYLING.md). Skill doc renamed from
|
|
6
|
+
> `input-range-core.md`. No consumer app used either old name.
|
|
2
7
|
|
|
3
8
|
## Overview
|
|
4
9
|
|
|
5
|
-
`
|
|
10
|
+
`InputRange` is the low-level native `<input type="range">` primitive for the `05.forms`
|
|
6
11
|
range-slider family. It renders a single native range input with left/right button slots (for
|
|
7
12
|
step-down/step-up controls), an optional `markers` slot for custom tick marks overlaid on the
|
|
8
13
|
track, and an optional `datalist` slot for native tick-mark snapping. It has no label,
|
|
9
|
-
description, or error-message rendering of its own — that's composed by the `
|
|
14
|
+
description, or error-message rendering of its own — that's composed by the `InputRangeField`
|
|
10
15
|
variant below.
|
|
11
16
|
|
|
12
|
-
Most consumers should reach for **
|
|
17
|
+
Most consumers should reach for **InputRangeField** rather than `InputRange` directly.
|
|
13
18
|
|
|
14
19
|
---
|
|
15
20
|
|
|
@@ -20,7 +25,7 @@ Most consumers should reach for **InputRangeDefault** rather than `InputRangeCor
|
|
|
20
25
|
| Prop (template form) | Type | Default | Notes |
|
|
21
26
|
|------|------|---------|-------|
|
|
22
27
|
| `:id` | `string` | (required) | Applied to the native input. |
|
|
23
|
-
| `:name` | `string` | (required) | Applied to the native input
|
|
28
|
+
| `:name` | `string` | (required) | Applied to the native input. (Until 2026-10-16 it also built the datalist id, `${name}-datalist`; that id now comes from `useId()`.) |
|
|
24
29
|
| `:min` | `number` | (required) | Native `min`. |
|
|
25
30
|
| `:max` | `number` | (required) | Native `max`. |
|
|
26
31
|
| `:step` | `number` | `1` | Native `step`. |
|
|
@@ -28,8 +33,8 @@ Most consumers should reach for **InputRangeDefault** rather than `InputRangeCor
|
|
|
28
33
|
| `:placeholder` | `string` | `""` | Declared but not rendered — range inputs have no placeholder concept. Kept for prop-shape consistency with sibling `05.forms` components. |
|
|
29
34
|
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
30
35
|
| `:weight` | `FormWeight` | `"normal"` | Adds an `input-range--{weight}` class to the input. **No CSS in the library currently styles this class — it's a no-op today**, kept for prop-shape consistency with sibling components. |
|
|
31
|
-
| `:field-has-error` | `boolean` | `false` |
|
|
32
|
-
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the input element. |
|
|
36
|
+
| `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the input (it did nothing here until 2026-10-16). |
|
|
37
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the input element. Reacts to changes. |
|
|
33
38
|
| `:aria-describedby` | `string` | `""` | Forwarded to the native input. |
|
|
34
39
|
|
|
35
40
|
### v-model
|
|
@@ -50,8 +55,8 @@ disable, not a fake default).
|
|
|
50
55
|
|------|-------|
|
|
51
56
|
| `left` | Rendered before the track (e.g. a step-down button). |
|
|
52
57
|
| `right` | Rendered after the track (e.g. a step-up button). |
|
|
53
|
-
| `markers` | Rendered as an overlay on top of the track (`grid-area: element-stack`, same cell as the input).
|
|
54
|
-
| `datalist` | Rendered after the input
|
|
58
|
+
| `markers` | Rendered as an overlay on top of the track (`grid-area: element-stack`, same cell as the input). Sets `data-has-markers` on the wrapper: a line joining the markers is drawn behind them, the input becomes a transparent layer over the whole marker row (so the row is draggable and clickable), and the thumb is restyled as a ring the size of one marker that circles the selected one. That lines up because markers are spaced `space-between` and given an explicit size (icon size + 2 × padding), the same as the thumb. Use one marker per step (`(max - min) / step + 1`). Expected shape: a `.input-range-markers` wrapper containing `.input-range-marker` elements (each optionally containing an `.input-range-marker-icon`); those classes are pre-styled (they were `.marker`/`.marker-icon` until 2026-10-16). |
|
|
59
|
+
| `datalist` | Rendered after the input, with a `datalistId` slot prop: put it on your `<datalist>` (`<template #datalist="{ datalistId }"><datalist :id="datalistId">`) and the input's `list` points at it. The id comes from `useId()`, so two ranges sharing a `name` don't collide. `list` is omitted when the slot is empty. |
|
|
55
60
|
|
|
56
61
|
---
|
|
57
62
|
|
|
@@ -93,13 +98,13 @@ was itself commented out.
|
|
|
93
98
|
|
|
94
99
|
## Variants
|
|
95
100
|
|
|
96
|
-
###
|
|
101
|
+
### InputRangeField
|
|
97
102
|
|
|
98
|
-
`
|
|
103
|
+
`InputRangeField` (`InputRangeField.vue`) composes `InputRange` with `InputLabel`,
|
|
99
104
|
`InputDescription`, and `InputError`, plus optional step-down/step-up buttons (`InputButton`)
|
|
100
105
|
wired into the `left`/`right` slots.
|
|
101
106
|
|
|
102
|
-
**Additional props over
|
|
107
|
+
**Additional props over InputRange:**
|
|
103
108
|
|
|
104
109
|
| Prop (template form) | Type | Default | Notes |
|
|
105
110
|
|------|------|---------|-------|
|
|
@@ -110,11 +115,11 @@ wired into the `left`/`right` slots.
|
|
|
110
115
|
|
|
111
116
|
**Slots**: `descriptionHtml`, `descriptionText` (both forwarded to `InputDescription`), plus
|
|
112
117
|
`left`/`right`/`markers`/`datalist` (forwarded straight through to the underlying
|
|
113
|
-
`
|
|
118
|
+
`InputRange`; `markers` wasn't forwarded until 2026-10-16, and `datalist` passes `datalistId` on — the step-down/step-up buttons only render when you use the `left`/`right`
|
|
114
119
|
slots yourself, since the button icon content itself is consumer-supplied via `#iconOnly`).
|
|
115
120
|
|
|
116
121
|
```vue
|
|
117
|
-
<
|
|
122
|
+
<InputRangeField
|
|
118
123
|
name="priceRange"
|
|
119
124
|
label="Price range"
|
|
120
125
|
v-model="price"
|
|
@@ -126,17 +131,47 @@ slots yourself, since the button icon content itself is consumer-supplied via `#
|
|
|
126
131
|
>
|
|
127
132
|
<template #left><Icon name="mdi:minus" /></template>
|
|
128
133
|
<template #right><Icon name="mdi:plus" /></template>
|
|
129
|
-
</
|
|
134
|
+
</InputRangeField>
|
|
130
135
|
```
|
|
131
136
|
|
|
132
|
-
`v-model` is `number`, **required** — same as `
|
|
137
|
+
`v-model` is `number`, **required** — same as `InputRange` above.
|
|
138
|
+
|
|
139
|
+
Each step-button press adds or subtracts `step` (its absolute value, or 1 when it's 0), rounds away
|
|
140
|
+
floating-point drift (0.2 + 0.1 gives 0.3) and clamps to `min`/`max`; step-down goes read-only at or
|
|
141
|
+
below `min`, step-up at or above `max`. `:style-class-passthrough` lands on the root
|
|
142
|
+
`.input-range-field` only (it also reached the inner input until 2026-10-16) and reacts to changes.
|
|
143
|
+
The root carries `data-theme` and `data-invalid` (its old `error` class is gone), and the label
|
|
144
|
+
gets `input-range-label` (it was the number field's `input-number-label`, with a non-existent
|
|
145
|
+
`body-normal-bold` utility).
|
|
133
146
|
|
|
134
147
|
**Fixed 2026-09-21**: removed a dead `deepCssClassPassthrough` prop (declared, never consumed
|
|
135
148
|
anywhere in the component or referenced elsewhere in the codebase) and a dead `<style>` block
|
|
136
149
|
targeting `.input-range-label`/`.label-description` — classes that nothing in this component (or
|
|
137
150
|
`InputLabel`/`InputDescription`) actually renders, so the block had no effect. Also removed two
|
|
138
151
|
dead commented-out lines (`// const id = useId();`, a duplicate no-op `FormUiTheme` computed), and
|
|
139
|
-
(same day, follow-up pass) a `const props = withDefaults(...)` in both `
|
|
140
|
-
`
|
|
152
|
+
(same day, follow-up pass) a `const props = withDefaults(...)` in both `InputRange` and
|
|
153
|
+
`InputRangeField` that had become entirely unused once the dead `FormUiTheme` computed was
|
|
141
154
|
removed — the template's `:id`/`:name`/etc. shorthand bindings resolve straight from `defineProps`
|
|
142
155
|
automatically in `<script setup>`, no destructuring or `props.` reference needed.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Stress pass 2026-10-16
|
|
160
|
+
|
|
161
|
+
- **Stepping** (`InputRangeField`): 0.1 steps drifted (0.30000000000000004) and a step that didn't
|
|
162
|
+
divide the range overshot `min`/`max`; the read-only checks used `===`, so an overshot value
|
|
163
|
+
never disabled the button. Now rounded, clamped, and `<=`/`>=`. A zero or negative `step`
|
|
164
|
+
steps by its size, or 1.
|
|
165
|
+
- **Datalist id**: was `${name}-datalist`, so two ranges sharing a `name` linked to the same list
|
|
166
|
+
(rule `ids-from-useid.md`). Now `useId()`, handed to the slot as `datalistId`.
|
|
167
|
+
- **Error state**: `fieldHasError` did nothing on `InputRange`; it now sets `data-invalid` and
|
|
168
|
+
`aria-invalid`.
|
|
169
|
+
- **Slots**: the Field didn't forward `markers`.
|
|
170
|
+
- **Classes**: bare/generic classes became prefixed classes or data attributes, and the marker
|
|
171
|
+
defaults moved from dark-page colours to `--theme-*` tokens (CONSUMER-STYLING.md has the
|
|
172
|
+
mapping). The Field's passthrough no longer reaches the inner input as well as its root.
|
|
173
|
+
- **Overflow**: the slider container gets `min-inline-size: 0` and the step buttons don't
|
|
174
|
+
shrink; the Field wraps long label, description and error copy.
|
|
175
|
+
- Specs unmount in `afterEach`; stories moved to `StoryObj` with Docs descriptions, a
|
|
176
|
+
`DecimalSteps` story, `CanvasSwitcher` and a `StressTest` story on the Field. The bare
|
|
177
|
+
control's step-button demo uses `InputButton`, not raw buttons.
|
|
@@ -33,7 +33,7 @@ directly.
|
|
|
33
33
|
|
|
34
34
|
- `v-model` — `string | number | readonly string[] | null | undefined`, **required**
|
|
35
35
|
(`defineModel({ required: true })`, satisfying `vue/require-default-prop` the same way as
|
|
36
|
-
`
|
|
36
|
+
`InputRange`/`InputNumber` — see `project_ledger_eslint_issues_column` in memory).
|
|
37
37
|
"Required" here means a consumer must bind `v-model`, not that the value can't be an empty
|
|
38
38
|
string — `""` is a perfectly valid textarea value.
|
|
39
39
|
- `v-model:is-dirty` — `boolean`. Set once by `InputTextareaWithLabel` the first time the value
|
|
@@ -50,7 +50,7 @@ directly.
|
|
|
50
50
|
| `left` | Rendered before the textarea (decorative — e.g. an icon). Adds `has-left-slot` to the wrapper. |
|
|
51
51
|
| `right` | Rendered after the textarea (decorative). Adds `has-right-slot` to the wrapper. |
|
|
52
52
|
|
|
53
|
-
Unlike `InputNumber`/`
|
|
53
|
+
Unlike `InputNumber`/`InputRange`, these slots have no built-in `InputButton`
|
|
54
54
|
divider/embedded-button styling — they're plain decorative content (see the Storybook stories,
|
|
55
55
|
which use emoji), not a stepper control.
|
|
56
56
|
|
|
@@ -75,7 +75,7 @@ the full table with defaults.
|
|
|
75
75
|
score-1/5 state (no public tokens, no CONSUMER-STYLING.md, no tests, no skill doc, no snippet).
|
|
76
76
|
|
|
77
77
|
- **Silently broken styling from undefined CSS custom properties** (same bug class as pitfall #21
|
|
78
|
-
and `
|
|
78
|
+
and `InputRange`'s dead `--theme-form-range-accent-color`): the wrapper's `padding-inline`
|
|
79
79
|
read `--element-decorator-padding-inline`, and the placeholder's `font-size`/`font-style`/
|
|
80
80
|
`line-height`/`font-weight` read `--input-placeholder-font-size`/`-font-style`/`-line-height`/
|
|
81
81
|
`-font-weight` — **none of these five custom properties were declared anywhere in the
|
|
@@ -105,7 +105,7 @@ score-1/5 state (no public tokens, no CONSUMER-STYLING.md, no tests, no skill do
|
|
|
105
105
|
`InputLabel` (which does have a real `label` concept) already renders it correctly. Covered by
|
|
106
106
|
a regression test.
|
|
107
107
|
- **`defineModel()` had no default**, tripping `vue/require-default-prop` in both files — fixed
|
|
108
|
-
with `{ required: true }`, same pattern as `
|
|
108
|
+
with `{ required: true }`, same pattern as `InputRange`/`InputNumber` (see the v-model
|
|
109
109
|
note above for why this is still correct even though an empty string is a valid value here).
|
|
110
110
|
- **`InputTextareaWithLabel.stories.ts` used `reactive()` in two render functions
|
|
111
111
|
(`AllVariants`/`FormExamples`) without importing it** — only `computed` was imported from
|
package/.claude/skills/index.md
CHANGED
|
@@ -147,8 +147,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
147
147
|
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
148
148
|
├── input-text-core.md — InputTextCore: native text/date/number input primitive, min/max pass-through (date-picker range), CSS token API; Variants section covers InputTextWithLabel/InputPasswordWithLabel/InputTextAsNumberWithLabel
|
|
149
149
|
├── input-button.md — InputButton (renamed from InputButtonCore 2026-09-28): button/NuxtLink/a, variants, icon slots, readonly (aria-disabled, activation blocked), tokens
|
|
150
|
-
├── input-range
|
|
151
|
-
├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField
|
|
150
|
+
├── input-range.md — InputRange: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeField; renamed from InputRangeCore/InputRangeDefault 2026-10-16, clamped stepping, useId datalist id, data-* hooks
|
|
151
|
+
├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField; clamped, rounded stepping, data-* state hooks, min/step-aware inputmode
|
|
152
152
|
├── input-otp.md — InputOtp: one-time-code digit boxes (paste/autofill spreading, Backspace/arrow/Home/End nav, Enter submits, leading zeros kept, complete event, hidden input); Variants section covers InputOtpField
|
|
153
153
|
├── form-field.md — FormField: layout wrapper for one form control (width cap, gutter, field spacing), data-width/data-has-gutter/data-invalid hooks, dead .underline rule removed, CSS token API
|
|
154
154
|
├── form-fieldset.md — FormFieldset: shared fieldset+legend for checkbox/radio groups, groupRole prop (hardcoded radiogroup bug fixed), aria-required only on radiogroup, dead description slot removed, legend token API
|
|
@@ -159,9 +159,9 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
159
159
|
├── pending-effect.md — PendingEffect: animated dashed border InputButton draws while is-pending (has-pending-effect), tokens renamed to --pending-effect-* and read at point of use, animation moved off the host button, aria-hidden
|
|
160
160
|
├── input-textarea-core.md — InputTextareaCore: native textarea primitive, left/right decorative slots, undefined-token/dead-code/label-leak bugs fixed, CSS token API; Variants section covers InputTextareaWithLabel
|
|
161
161
|
├── input-select-core.md — InputSelectCore: native select primitive, data-driven options with icon decorator, dead required/styleClassPassthrough/isDirty-isActive functionality fixed, appearance:base-select browser-support note, CSS token API; Variants section covers InputSelectWithLabel
|
|
162
|
-
├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens
|
|
163
|
-
├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API
|
|
164
|
-
├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API
|
|
162
|
+
├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens, long labels wrap (inline nowrap removed), optional useId button id
|
|
163
|
+
├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API, wraps hostile content (fieldset min-inline-size, useId option ids)
|
|
164
|
+
├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API, useId button ids, wraps hostile content
|
|
165
165
|
├── toggle-switch-core.md — ToggleSwitchCore: pill/square checkbox-backed toggle primitive, dead round-prop/data-theme-error functionality and story theme-options bugs fixed, CSS token API; Variants section covers ToggleSwitchWithLabel (descriptionText/descriptionHtml via InputDescription, `description` slot removed 2026-09-29)/ToggleSwitchWithLabelInline
|
|
166
166
|
├── triple-toggle-switch.md — TripleToggleSwitch (renamed from TripleToggleSwitchCore): three-option icon radio pill with sliding marker, radiogroup + ariaLabel, system/light/dark marker-gradient tokens, reduced-motion, CSS token API
|
|
167
167
|
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
@@ -321,6 +321,23 @@ it("passes myProp to ChildComponent", async () => {
|
|
|
321
321
|
|
|
322
322
|
Import the child component directly in the test file — it is not auto-imported there.
|
|
323
323
|
|
|
324
|
+
## Testing `useId()` uniqueness across instances
|
|
325
|
+
|
|
326
|
+
Each `mountSuspended` call is its own Nuxt app, so `useId()` restarts its counter and two separately
|
|
327
|
+
mounted instances get the **same** ids. That's a test artefact, not a bug. To prove two instances
|
|
328
|
+
don't collide, render both inside one host component (`MultipleCheckboxes.spec.ts`):
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
const Host = defineComponent({
|
|
332
|
+
components: { ComponentUnderTest },
|
|
333
|
+
setup: () => ({ props: initialPropsData }),
|
|
334
|
+
template: `<div><ComponentUnderTest v-bind="props" /><ComponentUnderTest v-bind="props" /></div>`,
|
|
335
|
+
});
|
|
336
|
+
const host = await mountSuspended(Host);
|
|
337
|
+
const ids = host.findAll("input").map((input) => input.attributes("id"));
|
|
338
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
339
|
+
```
|
|
340
|
+
|
|
324
341
|
## Mocking browser APIs
|
|
325
342
|
|
|
326
343
|
### Global constructors (ResizeObserver, IntersectionObserver, etc.)
|