srcdev-nuxt-components 9.6.7 → 9.6.9
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 +18 -1
- package/.claude/skills/component-inline-action-button.md +1 -1
- package/.claude/skills/component-local-style-override.md +1 -1
- package/.claude/skills/component-naming.md +13 -13
- package/.claude/skills/components/container-glow.md +2 -2
- package/.claude/skills/components/form-field.md +2 -2
- package/.claude/skills/components/form-wrapper.md +1 -1
- package/.claude/skills/components/glowing-border.md +1 -1
- 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 +7 -7
- package/.claude/skills/components/input-error.md +3 -3
- package/.claude/skills/components/input-label.md +7 -8
- package/.claude/skills/components/input-number.md +60 -29
- 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-select.md +153 -0
- package/.claude/skills/components/input-text.md +220 -0
- package/.claude/skills/components/input-textarea.md +147 -0
- package/.claude/skills/components/select-menu.md +4 -4
- package/.claude/skills/components/toggle-switch.md +143 -0
- package/.claude/skills/composable-zod-validation.md +1 -1
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/icon-sizing.md +1 -1
- package/.claude/skills/index.md +10 -10
- package/.claude/skills/storybook-add-story.md +1 -1
- package/.claude/skills/testing-add-unit-test.md +17 -0
- package/.claude/skills/theming-component-token-pattern.md +8 -8
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
- package/.vscode/srcdev-component-input-range.code-snippets +38 -18
- package/.vscode/srcdev-component-input-select.code-snippets +20 -14
- package/.vscode/srcdev-component-input-text.code-snippets +46 -40
- package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
- package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
- package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
- package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
- package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
- 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 +7 -7
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +6 -6
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +50 -6
- package/app/components/05.forms/input-number/InputNumber.vue +39 -27
- 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/input-select/CONSUMER-STYLING.md +80 -10
- package/app/components/05.forms/input-select/InputSelect.vue +198 -0
- package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
- package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
- package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
- package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
- package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
- package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
- package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
- package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
- package/app/components/05.forms/input-text/InputTextField.vue +168 -0
- package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
- package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
- package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
- package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
- package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
- package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
- package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
- package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
- package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
- package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
- package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
- package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +34 -36
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
- package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
- package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
- package/package.json +1 -1
- package/.claude/skills/components/input-select-core.md +0 -151
- package/.claude/skills/components/input-text-core.md +0 -204
- package/.claude/skills/components/input-textarea-core.md +0 -154
- package/.claude/skills/components/toggle-switch-core.md +0 -169
- 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
- package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
- package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
- package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
- package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
- package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
- package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
|
@@ -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.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# InputSelect Component
|
|
2
|
+
|
|
3
|
+
> **Renamed 2026-10-16**: `InputSelectCore` → `InputSelect`, `InputSelectWithLabel` →
|
|
4
|
+
> `InputSelectField` (DOM classes `.input-select-core` → `.input-select`,
|
|
5
|
+
> `.input-select-with-label` → `.input-select-field`; full class list in CONSUMER-STYLING.md).
|
|
6
|
+
> Skill doc renamed from `input-select-core.md`. A consumer app uses both old names; list its edits
|
|
7
|
+
> privately and apply them when it upgrades.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`InputSelect` is the low-level native `<select>` primitive for the `05.forms` select family.
|
|
12
|
+
It renders a native select with a data-driven list of `<option>`s (each optionally decorated with
|
|
13
|
+
an icon), an optional placeholder option, and adopts the modern CSS Customizable Select API
|
|
14
|
+
(`appearance: base-select`) for a fully styleable open-picker state where supported, with a
|
|
15
|
+
graceful CSS fallback (see "Browser support" below). It has no label, description, or
|
|
16
|
+
error-message rendering of its own — that's composed by the `InputSelectField` variant below.
|
|
17
|
+
|
|
18
|
+
Most consumers should reach for **InputSelectField** rather than `InputSelect` directly.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Props reference
|
|
23
|
+
|
|
24
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:field-has-error`, `:style-class-passthrough`, `:aria-describedby`.
|
|
25
|
+
|
|
26
|
+
| Prop (template form) | Type | Default | Notes |
|
|
27
|
+
|------|------|---------|-------|
|
|
28
|
+
| `:id` | `string` | (required) | Applied to the native select. |
|
|
29
|
+
| `:name` | `string` | (required) | Applied to the native select. |
|
|
30
|
+
| `:placeholder` | `string` | `""` | Renders a disabled placeholder `<option>`, selected while `modelValue` is `""`, `null` or `undefined` (not `0`). No option rendered at all when unset. |
|
|
31
|
+
| `:title` | `string` | `"Please select an option"` | Native `title` attribute (tooltip). Visitor-facing copy: pass a translated string. |
|
|
32
|
+
| `:required` | `boolean` | `false` | Native `required`. |
|
|
33
|
+
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
34
|
+
| `:input-variant` | `InputUiVariant` | `"normal"` | Sets `data-input-variant` on the wrapper. `"normal"` (bordered box) and `"underlined"` (bottom border only) have CSS; `"outlined"` doesn't. |
|
|
35
|
+
| `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the select. |
|
|
36
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the select element. Reacts to changes. |
|
|
37
|
+
| `:aria-describedby` | `string` | `""` | Forwarded to the native select; omitted when empty. |
|
|
38
|
+
|
|
39
|
+
### v-models
|
|
40
|
+
|
|
41
|
+
- `v-model` — `string | number | readonly number[]`, **required** (`defineModel({ required: true })`, satisfying `vue/require-default-prop` — see `project_ledger_eslint_issues_column` in memory).
|
|
42
|
+
- `v-model:field-data` — `IFormMultipleOptions` (`{ data: IOptionsConfig[]; total; skip; limit }`), **required**. The `data` array drives the rendered `<option>`s; only `data` is read. The template dereferences `fieldData.data` unconditionally, so a consumer that doesn't bind it crashes on render. A one-way `:field-data` works too: the component never writes it.
|
|
43
|
+
- `v-model:is-dirty` — `boolean`. Set to `true` on the native `change` event (never reset to `false` by this component). Reflected as `data-dirty` on the wrapper.
|
|
44
|
+
- `v-model:is-active` — `boolean`. Tracks native focus/blur (`focusin`/`focusout`). Reflected as `data-active` on the wrapper.
|
|
45
|
+
|
|
46
|
+
### Option icons
|
|
47
|
+
|
|
48
|
+
An `IOptionsConfig` item with `icon` renders an `<Icon class="input-select-option-icon">`
|
|
49
|
+
(`aria-hidden`) before its label. It only shows where `appearance: base-select` is supported; a
|
|
50
|
+
native dropdown renders option text only. Labels render as text, never as markup.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## CSS token API
|
|
55
|
+
|
|
56
|
+
See [CONSUMER-STYLING.md](../../app/components/05.forms/input-select/CONSUMER-STYLING.md) for the
|
|
57
|
+
full table with defaults and the state hooks.
|
|
58
|
+
|
|
59
|
+
**Common tokens:**
|
|
60
|
+
|
|
61
|
+
- `--input-select-surface` / `-surface-hover` — wrapper/select background and option-row hover background
|
|
62
|
+
- `--input-select-text-color` — select text colour
|
|
63
|
+
- `--input-select-border` / `-border-hover` / `-border-focus` — border colour resting, mouse-hover outline, and `:focus-visible` outline (also the open picker's border/outline)
|
|
64
|
+
- `--input-select-option-gap` / `-option-icon-size` — option icon spacing and size
|
|
65
|
+
|
|
66
|
+
**State hooks** (on `.input-select-wrapper`): `data-input-variant`, `data-theme`, `data-invalid`,
|
|
67
|
+
`data-dirty`, `data-active`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Browser support: `appearance: base-select`
|
|
72
|
+
|
|
73
|
+
The open-picker styling (`&::picker(select)`, `:open`/`:not(:open)` states, the transition/opacity
|
|
74
|
+
`@starting-style` reveal) relies on the CSS Customizable Select API
|
|
75
|
+
(`appearance: base-select`/`::picker(select)`), a very new feature not yet supported everywhere.
|
|
76
|
+
`appearance: none` is set first, then overridden by `appearance: base-select` only where the
|
|
77
|
+
browser recognises that value, so elsewhere (including Safari 16) the select falls back to the
|
|
78
|
+
browser's native dropdown with full functionality. This is decorative degradation, not a
|
|
79
|
+
functional break like `ExpandingPanel`'s `::details-content` case (pitfall #19), but the
|
|
80
|
+
open-picker styling should still be spot-checked on a real Safari session before treating it as
|
|
81
|
+
visually final.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Changed 2026-10-16: rename, state hooks, stress fixes
|
|
86
|
+
|
|
87
|
+
- Renamed to the Control/Field convention (see the note at the top).
|
|
88
|
+
- State classes (`.normal`/`.underlined`/`.outlined`, `.dirty`, `.active`, `.error`) became data
|
|
89
|
+
attributes, so a consumer's unlayered `.error` rule can't restyle the field.
|
|
90
|
+
- The placeholder option used `:selected="!modelValue"`, so a model value of `0` also marked the
|
|
91
|
+
placeholder selected. It now checks for an empty value only.
|
|
92
|
+
- `aria-describedby=""` was rendered when no description id was passed; it's now omitted.
|
|
93
|
+
- `styleClassPassthrough` now reacts to prop changes (it was read once at setup).
|
|
94
|
+
- `InputSelectField` applied its passthrough to the inner select; it now applies it to its own
|
|
95
|
+
root only. It also gained a `title` prop (forwarded), and its root is `.input-select-field`
|
|
96
|
+
itself rather than an unclassed outer `<div>` around it.
|
|
97
|
+
- New public tokens: `--input-select-text-color`, `--input-select-option-gap`,
|
|
98
|
+
`--input-select-option-icon-size`.
|
|
99
|
+
- Long option labels wrap in the open picker, and the Field wraps long label, description and
|
|
100
|
+
error copy. `StressTest` stories on both components.
|
|
101
|
+
|
|
102
|
+
## Fixed 2026-09-22: dead functionality and prop-shape bugs
|
|
103
|
+
|
|
104
|
+
Migrated from a `variants:true`, `eslint_issues:true`, score-2/5 state.
|
|
105
|
+
|
|
106
|
+
- `required` and `styleClassPassthrough` were declared but never bound.
|
|
107
|
+
- `isDirty`/`isActive` were read but nothing set them; `@focusin`/`@focusout`/`@change` added.
|
|
108
|
+
- The placeholder option used `readonly` (not valid on `<option>`); switched to `disabled`.
|
|
109
|
+
- `theme`/`inputVariant` moved to the shared `FormUiTheme`/`InputUiVariant` types; `fieldData`
|
|
110
|
+
moved from an `as Ref<T>` cast to `defineModel<T>("fieldData", { required: true })`.
|
|
111
|
+
- Dead commented-out CSS and single-use private tokens removed (pitfall #20).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Variants
|
|
116
|
+
|
|
117
|
+
### InputSelectField
|
|
118
|
+
|
|
119
|
+
`InputSelectField` (`InputSelectField.vue`) composes `InputSelect` with `InputLabel`,
|
|
120
|
+
`InputDescription` (before the control, or after the control and error when
|
|
121
|
+
`inputVariant === "outlined"`, matching `InputTextField`'s placement rule), and `InputError`.
|
|
122
|
+
Ids for the label, description and error come from `useAriaDescribedById`.
|
|
123
|
+
|
|
124
|
+
**Additional props over InputSelect** (it takes no `id`; the id is generated):
|
|
125
|
+
|
|
126
|
+
| Prop (template form) | Type | Default | Notes |
|
|
127
|
+
|------|------|---------|-------|
|
|
128
|
+
| `:data-testid` | `string` | `"input-select-field"` | Sets `data-testid` on the root. (Was `"input-select-with-label"` until 2026-10-16.) |
|
|
129
|
+
| `:label` | `string` | (required) | Rendered via `InputLabel`. |
|
|
130
|
+
| `:error-message` | `object \| string` | (required) | Rendered via `InputError` when `field-has-error` is true. |
|
|
131
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Applied to the `.input-select-field` root only, not the inner select. |
|
|
132
|
+
|
|
133
|
+
`name`, `placeholder`, `title`, `required`, `field-has-error`, `theme` and `input-variant` are
|
|
134
|
+
forwarded. While `field-has-error` is set, the inner `InputSelect` gets `theme="error"` whatever
|
|
135
|
+
the `theme` prop says; the Field root's own `data-theme` keeps the prop value.
|
|
136
|
+
|
|
137
|
+
**Slots**: `descriptionHtml`, `descriptionText` (both forwarded to `InputDescription`).
|
|
138
|
+
|
|
139
|
+
```vue
|
|
140
|
+
<InputSelectField
|
|
141
|
+
v-model="colour"
|
|
142
|
+
v-model:field-data="colourOptions"
|
|
143
|
+
name="colour"
|
|
144
|
+
label="Favourite colour"
|
|
145
|
+
placeholder="Choose a colour"
|
|
146
|
+
:error-message="errors.colour"
|
|
147
|
+
:field-has-error="!!errors.colour"
|
|
148
|
+
>
|
|
149
|
+
<template #descriptionText>Pick the colour you like best</template>
|
|
150
|
+
</InputSelectField>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`v-model` and `v-model:field-data` are both **required**, same as `InputSelect` above.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# InputText Component
|
|
2
|
+
|
|
3
|
+
> **Renamed 2026-10-16**: `InputTextCore` → `InputText`, `InputTextWithLabel` → `InputTextField`,
|
|
4
|
+
> `InputPasswordWithLabel` → `InputPasswordField`, `InputTextAsNumberWithLabel` →
|
|
5
|
+
> `InputTextAsNumberField` (DOM classes `.input-text-core` → `.input-text`,
|
|
6
|
+
> `.input-text-with-label` → `.input-text-field`; full class list in CONSUMER-STYLING.md). Skill
|
|
7
|
+
> doc renamed from `input-text-core.md`. Consumer apps use the first three old names; list their
|
|
8
|
+
> edits privately and apply them when each upgrades.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
`InputText` is the low-level native `<input>` primitive for the `05.forms` text-input family. It
|
|
13
|
+
renders one styled text-like input with left/right slots, focus/dirty state models, and digit-only
|
|
14
|
+
typing for `inputmode="numeric"`. It has no label, description or error rendering of its own:
|
|
15
|
+
those come from the Field wrappers under **Variants**, or pair it with your own `<label for>`.
|
|
16
|
+
|
|
17
|
+
Most consumers should reach for one of the Variants rather than `InputText` directly.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Props reference
|
|
22
|
+
|
|
23
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:input-variant`, `:aria-describedby`.
|
|
24
|
+
|
|
25
|
+
| Prop (template form) | Type | Default | Notes |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `:type` | `InputTypesText` (`"text" \| "email" \| "password" \| "number" \| "tel" \| "url" \| "date"`) | `"text"` | Native input type. |
|
|
28
|
+
| `:inputmode` | `InputMode` | `"text"` | Virtual keyboard hint. `"numeric"` also adds `pattern="[0-9]+"` and blocks typing anything but digits (a `beforeinput` handler; deletions and paste without data pass). Sets `data-inputmode` on the wrapper. |
|
|
29
|
+
| `:maxlength` | `number` | `255` | Native `maxlength`. |
|
|
30
|
+
| `:min` / `:max` | `string \| number` | (optional) | Passed straight through, e.g. an ISO date for `type="date"`. Inert on text-like types. |
|
|
31
|
+
| `:id` | `string` | (required) | Applied to the native input. |
|
|
32
|
+
| `:name` | `string` | (required) | Applied to the native input. |
|
|
33
|
+
| `:required` | `boolean` | `false` | Native `required`. |
|
|
34
|
+
| `:readonly` | `boolean` | `false` | Native `readonly`; sets `data-readonly` on the wrapper. Added 2026-10-16: a `readonly` attribute used to fall through onto the wrapper div, not the input. |
|
|
35
|
+
| `:autocomplete` | `string` | (unset) | Native `autocomplete` token (`email`, `name`, `new-password`...). Added 2026-10-16. |
|
|
36
|
+
| `:placeholder` | `string` | `""` | Native placeholder. |
|
|
37
|
+
| `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the input. It does **not** change `theme`. |
|
|
38
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the native input. Reacts to changes. |
|
|
39
|
+
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
40
|
+
| `:aria-describedby` | `string` | `""` | Forwarded to the native input; omitted when empty. |
|
|
41
|
+
| `:input-variant` | `InputUiVariant` | `"normal"` | Sets `data-input-variant`. `normal` (bordered box) and `underlined` have CSS; `outlined` doesn't. |
|
|
42
|
+
|
|
43
|
+
### v-models
|
|
44
|
+
|
|
45
|
+
| Model | Type | Notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `v-model` | `string` | **Required** (`defineModel({ required: true })`). |
|
|
48
|
+
| `v-model:is-dirty` | `boolean` | Read only: `InputText` never sets it. The Fields set it once the value is non-empty. Reflected as `data-dirty`. |
|
|
49
|
+
| `v-model:is-active` | `boolean` | Tracks focus (`focusin`/`focusout`). Reflected as `data-active`. |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Slots
|
|
54
|
+
|
|
55
|
+
| Slot | Notes |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `left` | Rendered before the input in `.input-text-slot-left`, inside the bordered wrapper (an icon, or `InputButton variant="inline"`, which the slot styles). Sets `data-has-left-slot`. |
|
|
58
|
+
| `right` | Same, after the input, in `.input-text-slot-right`. Sets `data-has-right-slot`; a right-slot `InputButton` removes the wrapper's end padding so it sits flush. |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## CSS token API
|
|
63
|
+
|
|
64
|
+
See [CONSUMER-STYLING.md](../../app/components/05.forms/input-text/CONSUMER-STYLING.md) for the
|
|
65
|
+
full table with defaults and the state hooks.
|
|
66
|
+
|
|
67
|
+
**Common tokens:**
|
|
68
|
+
|
|
69
|
+
- `--input-text-surface` / `-surface-hover` — wrapper background, slot button hover
|
|
70
|
+
- `--input-text-border` / `-border-hover` / `-border-focus` — border and outline colours
|
|
71
|
+
- `--input-text-color` — input text colour (also the browser-autofill text colour, global rule)
|
|
72
|
+
- `--input-text-placeholder-color` — placeholder colour
|
|
73
|
+
|
|
74
|
+
**State hooks** (on `.input-text-wrapper`): `data-input-variant`, `data-theme`, `data-invalid`,
|
|
75
|
+
`data-dirty`, `data-active`, `data-readonly`, `data-inputmode`, `data-has-left-slot`,
|
|
76
|
+
`data-has-right-slot`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Using `min`/`max` for a native date picker
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<InputText
|
|
84
|
+
id="appointment-date"
|
|
85
|
+
v-model="appointmentDate"
|
|
86
|
+
name="appointmentDate"
|
|
87
|
+
type="date"
|
|
88
|
+
:min="new Date().toISOString().split('T')[0]"
|
|
89
|
+
max="2027-12-31"
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The browser enforces the range in its picker; validate the submitted value server-side as well.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Changed 2026-10-16: rename, state hooks, wrapper fixes
|
|
98
|
+
|
|
99
|
+
- Renamed to the Control/Field convention (note at the top).
|
|
100
|
+
- State classes became data attributes (`.normal`/`.underlined` → `[data-input-variant]`,
|
|
101
|
+
`.dirty`/`.active`/`.error` → `[data-dirty]`/`[data-active]`/`[data-invalid]`,
|
|
102
|
+
`.has-left-slot`/`.has-right-slot` → `[data-has-*-slot]`); `.slot.left-slot`/`.right-slot` →
|
|
103
|
+
`.input-text-slot-left`/`-right`. The input no longer carries `dirty`/`active` classes.
|
|
104
|
+
- Numeric filtering used a `beforeinput` listener added in `onMounted` that was never removed and
|
|
105
|
+
added a new `input` listener per keystroke (it reverted pattern mismatches after the fact). It's
|
|
106
|
+
now a template `@beforeinput` handler that cancels non-digit input up front, and it follows
|
|
107
|
+
`inputmode` changes after mount.
|
|
108
|
+
- `aria-describedby=""` is no longer rendered when empty; an unused `FormUiTheme` computed is gone.
|
|
109
|
+
- `InputTextField` passed `label` and `error-message` to `InputText`, which has no such props, so
|
|
110
|
+
they landed on `.input-text-wrapper` as HTML attributes. It also applied its passthrough to both
|
|
111
|
+
its root and the input; now its root only.
|
|
112
|
+
- `InputPasswordField` forwarded both description slots unconditionally, so every password field
|
|
113
|
+
rendered an empty `InputDescription` and pointed `aria-describedby` at it. Slots now forward
|
|
114
|
+
only when filled. Its toggle icon class is `.input-password-field-icon` (was `.icon`).
|
|
115
|
+
- `InputTextAsNumberField`: step buttons overshot `min`/`max` and drifted on fractional steps
|
|
116
|
+
(now round, clamp and start from 0 when empty, like `InputNumberField`); its own CSS read
|
|
117
|
+
`--theme-*` directly and now uses the `--input-text-*` tokens; the step buttons ignored `theme`;
|
|
118
|
+
`inputmode` follows `min`/`step` (a negative `min` or fractional `step` couldn't be typed); the
|
|
119
|
+
input's width is capped at `12em`; root class `.input-text-as-number-field` (it reused
|
|
120
|
+
`.input-text-with-label`), and `InputError` gets `input-variant`.
|
|
121
|
+
- The global autofill rule in `04.elements/forms/00.element-defaults.css` read an undefined
|
|
122
|
+
`--_input-text-core-color`, so autofilled text lost its colour; it now uses `--input-text-color`.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Variants
|
|
127
|
+
|
|
128
|
+
Three wrappers compose `InputText` with `InputLabel`, `InputDescription` and `InputError`. Ids
|
|
129
|
+
come from `useAriaDescribedById`, so they take `name`, not `id`. Each applies
|
|
130
|
+
`style-class-passthrough` to its own root only.
|
|
131
|
+
|
|
132
|
+
### InputTextField
|
|
133
|
+
|
|
134
|
+
The general-purpose labelled text field. Root `.input-text-field`, with `data-theme`,
|
|
135
|
+
`data-input-variant`, `data-invalid`, `data-dirty` (set once the value is non-empty) and
|
|
136
|
+
`data-active`.
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<InputTextField
|
|
140
|
+
v-model="email"
|
|
141
|
+
name="email"
|
|
142
|
+
type="email"
|
|
143
|
+
label="Email address"
|
|
144
|
+
autocomplete="email"
|
|
145
|
+
:error-message="errors.email"
|
|
146
|
+
:field-has-error="!!errors.email"
|
|
147
|
+
>
|
|
148
|
+
<template #descriptionText>We only use it to reply to you.</template>
|
|
149
|
+
</InputTextField>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Props: `type` (required), `inputmode`, `maxlength`, `min`, `max`, `name`, `placeholder`,
|
|
153
|
+
`required`, `readonly`, `autocomplete`, `fieldHasError`, `theme`, `inputVariant`, plus `label`
|
|
154
|
+
(required), `errorMessage: object | string` (required, pass `""` if unused) and
|
|
155
|
+
`styleClassPassthrough`. Slots: `left`, `right`, `descriptionText`, `descriptionHtml`. The
|
|
156
|
+
description renders before the input, or after the input and error for `outlined`.
|
|
157
|
+
|
|
158
|
+
### InputPasswordField
|
|
159
|
+
|
|
160
|
+
`InputTextField` (root `.input-text-field.input-password-field`) with a show/hide toggle
|
|
161
|
+
(`InputButton variant="inline"` in the right slot) that swaps `type` between `password` and
|
|
162
|
+
`text`.
|
|
163
|
+
|
|
164
|
+
```vue
|
|
165
|
+
<InputPasswordField
|
|
166
|
+
v-model="password"
|
|
167
|
+
name="password"
|
|
168
|
+
label="Password"
|
|
169
|
+
autocomplete="new-password"
|
|
170
|
+
:error-message="errors.password"
|
|
171
|
+
:field-has-error="!!errors.password"
|
|
172
|
+
/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Extra props: `showPasswordText`/`hidePasswordText` (defaults `"Show password"`/`"Hide password"`,
|
|
176
|
+
the toggle's accessible name; pass translated strings), `autocomplete` (default
|
|
177
|
+
`"current-password"`; use `"new-password"` on sign-up). A passed `type` is accepted and ignored.
|
|
178
|
+
Slots: `descriptionText`, `descriptionHtml`.
|
|
179
|
+
|
|
180
|
+
### InputTextAsNumberField
|
|
181
|
+
|
|
182
|
+
A compact numeric stepper on a text input (no native spinner). Root
|
|
183
|
+
`.input-text-as-number-field`.
|
|
184
|
+
|
|
185
|
+
```vue
|
|
186
|
+
<InputTextAsNumberField
|
|
187
|
+
v-model="quantity"
|
|
188
|
+
name="quantity"
|
|
189
|
+
label="Quantity"
|
|
190
|
+
:min="1"
|
|
191
|
+
:max="99"
|
|
192
|
+
step-down-text="Decrease quantity"
|
|
193
|
+
step-up-text="Increase quantity"
|
|
194
|
+
:error-message="errors.quantity"
|
|
195
|
+
>
|
|
196
|
+
<template #left><Icon name="mdi:minus" /></template>
|
|
197
|
+
<template #right><Icon name="mdi:plus" /></template>
|
|
198
|
+
</InputTextAsNumberField>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- `v-model` is `number | undefined`: clearing the field sets `undefined`; text that isn't a
|
|
202
|
+
finite number is ignored.
|
|
203
|
+
- Step buttons appear when the `left`/`right` slots are filled (the slot content is the icon).
|
|
204
|
+
They step by `step`, round to its precision, clamp to `min`/`max` (pulling a typed
|
|
205
|
+
out-of-range value back in), start from 0 (clamped) when empty, and go read-only at the limits.
|
|
206
|
+
- `inputmode` is `numeric` (digits only) for a whole-number, non-negative range, `decimal` for a
|
|
207
|
+
fractional `step`, `text` for a negative `min`.
|
|
208
|
+
- Extra props: `min`, `max` (required), `step` (default `1`), `stepDownText`/`stepUpText`
|
|
209
|
+
(defaults `"Step down"`/`"Step up"`).
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Accessibility
|
|
214
|
+
|
|
215
|
+
- `field-has-error` sets `aria-invalid="true"`; the Fields link the error and description via
|
|
216
|
+
`aria-describedby`.
|
|
217
|
+
- Focus is shown on the wrapper with `:has(input:focus-visible, button:focus-visible)`.
|
|
218
|
+
- The password and stepper buttons get accessible names through `InputButton`'s `buttonText`
|
|
219
|
+
(visually hidden in icon-only mode).
|
|
220
|
+
- Use `autocomplete` on personal-data fields (WCAG 1.3.5).
|