srcdev-nuxt-components 9.6.9 → 9.6.11
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 +4 -2
- package/.claude/skills/component-compliance-checklist.md +24 -2
- package/.claude/skills/component-inline-action-button.md +5 -4
- package/.claude/skills/component-naming.md +8 -2
- package/.claude/skills/components/{alert-mask-core.md → alert-mask.md} +38 -23
- package/.claude/skills/components/alert-masked-content.md +3 -3
- package/.claude/skills/components/container-glow.md +1 -1
- package/.claude/skills/components/display-theme-switch.md +8 -8
- package/.claude/skills/components/input-button.md +26 -7
- package/.claude/skills/components/input-checkbox-radio.md +2 -0
- package/.claude/skills/components/input-description.md +5 -0
- package/.claude/skills/components/input-error.md +9 -2
- package/.claude/skills/components/input-label.md +8 -2
- package/.claude/skills/components/input-number.md +2 -0
- package/.claude/skills/components/input-otp.md +2 -0
- package/.claude/skills/components/input-range.md +2 -0
- package/.claude/skills/components/input-select.md +2 -0
- package/.claude/skills/components/input-text.md +2 -0
- package/.claude/skills/components/input-textarea.md +2 -0
- package/.claude/skills/components/pending-effect.md +5 -4
- package/.claude/skills/components/{triple-toggle-switch.md → segmented-control.md} +34 -19
- package/.claude/skills/components/services-card.md +1 -1
- package/.claude/skills/components/tabbed-content.md +1 -1
- package/.claude/skills/components/toggle-switch.md +2 -0
- package/.claude/skills/css-nesting-conventions.md +22 -1
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +3 -1
- package/.claude/skills/theming-component-token-pattern.md +24 -0
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.claude/skills/theming-partial-override.md +1 -1
- package/.vscode/srcdev-component-alert-mask.code-snippets +41 -0
- package/.vscode/{srcdev-component-triple-toggle-switch.code-snippets → srcdev-component-segmented-control.code-snippets} +6 -7
- package/app/assets/styles/setup/03.theming/_theme-slots.css +1 -1
- package/app/assets/styles/setup/04.elements/forms/03.generic-input-geometry.css +4 -0
- package/app/components/01.atoms/alert-mask/AlertMask.vue +194 -0
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +57 -42
- package/app/components/01.atoms/alert-mask/stories/AlertMask.stories.ts +198 -0
- package/app/components/01.atoms/alert-mask/tests/AlertMask.spec.ts +233 -0
- package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +2 -2
- package/app/components/02.molecules/alert-masked-content/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +60 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +12 -12
- package/app/components/02.molecules/cookie-consent-banner/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue +1 -1
- package/app/components/02.molecules/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +1 -1
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +9 -9
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +16 -16
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +53 -1
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +8 -8
- package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/input-copy/InputCopy.vue +2 -2
- package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +2 -2
- package/app/components/03.organisms/google-reviews/tests/GoogleReviews.spec.ts +4 -4
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +2 -2
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +2 -2
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +6 -6
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/form-errors/InputError.vue +43 -37
- package/app/components/05.forms/form-errors/stories/InputError.stories.ts +79 -1
- package/app/components/05.forms/form-errors/tests/InputError.spec.ts +29 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +80 -47
- package/app/components/05.forms/input-button/InputButton.vue +52 -100
- package/app/components/05.forms/input-button/playwright/input-button.playwright.ts +1 -1
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +145 -8
- package/app/components/05.forms/input-button/tests/InputButton.spec.ts +76 -46
- package/app/components/05.forms/input-button/tests/__snapshots__/InputButton.spec.ts.snap +17 -17
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +9 -1
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +8 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +2 -2
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +14 -2
- package/app/components/05.forms/input-description/InputDescription.vue +2 -1
- package/app/components/05.forms/input-description/stories/InputDescription.stories.ts +70 -4
- package/app/components/05.forms/input-description/tests/InputDescription.spec.ts +2 -1
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +10 -2
- package/app/components/05.forms/input-label/InputLabel.vue +1 -0
- package/app/components/05.forms/input-label/stories/InputLabel.stories.ts +68 -2
- package/app/components/05.forms/input-label/tests/InputLabel.spec.ts +13 -3
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-number/InputNumber.vue +4 -5
- package/app/components/05.forms/input-otp/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-otp/InputOtp.vue +2 -2
- package/app/components/05.forms/input-range/InputRange.vue +1 -1
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-select/InputSelect.vue +14 -5
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-text/InputText.vue +4 -4
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-textarea/InputTextarea.vue +2 -3
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +4 -4
- package/app/components/05.forms/pending-effect/CONSUMER-STYLING.md +7 -3
- package/app/components/05.forms/pending-effect/PendingEffect.vue +3 -3
- package/app/components/05.forms/pending-effect/tests/PendingEffect.spec.ts +1 -1
- package/app/components/05.forms/segmented-control/CONSUMER-STYLING.md +125 -0
- package/app/components/05.forms/segmented-control/SegmentedControl.vue +231 -0
- package/app/components/05.forms/segmented-control/stories/SegmentedControl.stories.ts +215 -0
- package/app/components/05.forms/segmented-control/tests/SegmentedControl.spec.ts +202 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +7 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitch.vue +2 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +1 -1
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +0 -31
- package/app/components/01.atoms/alert-mask/AlertMaskCore.vue +0 -166
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +0 -105
- package/app/components/01.atoms/alert-mask/tests/AlertMaskCore.spec.ts +0 -342
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +0 -99
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitch.vue +0 -251
- package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitch.stories.ts +0 -104
- package/app/components/05.forms/triple-toggle-switch/tests/TripleToggleSwitch.spec.ts +0 -138
- /package/app/types/components/{alert-mask-core.d.ts → alert-mask.d.ts} +0 -0
|
@@ -6,9 +6,11 @@ paths:
|
|
|
6
6
|
# Element ids come from `useId()`, never from data
|
|
7
7
|
|
|
8
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}` ``.
|
|
9
|
+
data values like `` `${name}-${item.value}` ``. A radio group's default `name` comes from
|
|
10
|
+
`useId()` too, never a fixed string.
|
|
10
11
|
|
|
11
12
|
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).
|
|
13
|
+
an invalid one, so labels toggle the wrong control (`MultipleCheckboxes`, 2026-10-16). A fixed default radio `name` makes every instance on a page
|
|
14
|
+
one group, so picking in one clears the others (`SegmentedControl`, 2026-10-17). Test
|
|
13
15
|
uniqueness with both instances inside one host component: separate `mountSuspended` calls each
|
|
14
16
|
restart the `useId()` counter. Detail: skill `testing-add-unit-test.md` (srcdev-nuxt-components).
|
|
@@ -183,7 +183,8 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
183
183
|
8. **VS Code snippet** — create or update `.vscode/srcdev-component-{family}.code-snippets`,
|
|
184
184
|
covering the control and its Field wrapper in one file. For a teleported component, a CSS
|
|
185
185
|
override scaffold must not nest its selector under a page class: it never matches (the
|
|
186
|
-
`DisplayToast` snippet did, fixed 2026-10-16).
|
|
186
|
+
`DisplayToast` snippet did, fixed 2026-10-16). A tab stop with a default value is `${3:8}`:
|
|
187
|
+
`$38` is read as tab stop 38 and the default never appears (`AlertMask`, fixed 2026-10-17).
|
|
187
188
|
9. **Accessibility** — check for gaps beyond what already-passing tests would catch:
|
|
188
189
|
- Interactive controls (buttons, toggles, custom form-like widgets) have an accessible name —
|
|
189
190
|
visible text, `aria-label`, or `aria-labelledby`.
|
|
@@ -196,6 +197,11 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
196
197
|
- A toast, banner or status message doesn't `.focus()` itself when it appears (rule
|
|
197
198
|
`consumer/a11y-no-focus-steal.md`).
|
|
198
199
|
- Focus is visible (`:focus-visible`, not a suppressed outline) on anything focusable.
|
|
200
|
+
- `aria-invalid` is bound as `fieldHasError ? 'true' : undefined`, never the boolean itself:
|
|
201
|
+
`:aria-invalid="fieldHasError"` renders `aria-invalid="false"` on every valid field
|
|
202
|
+
(`InputTextarea`, `ToggleSwitch`, fixed 2026-10-16). Same for an empty `aria-describedby`.
|
|
203
|
+
- A checkbox drawn as an on/off switch has `role="switch"`, and its decorative track is
|
|
204
|
+
`aria-hidden` (`ToggleSwitch`, 2026-10-16).
|
|
199
205
|
- Live-updating or auto-advancing content (carousels, marquees, auto-dismissing toasts) has a
|
|
200
206
|
way to pause it, per WCAG 2.2.2 — a visible control, or pause-on-hover/focus at minimum.
|
|
201
207
|
- `prefers-reduced-motion` is respected for any animation that isn't purely decorative.
|
|
@@ -275,13 +281,29 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
275
281
|
defaults to its min-content width and overflows a narrow column (`MultipleCheckboxes`,
|
|
276
282
|
2026-10-16).
|
|
277
283
|
- Element ids come from `useId()`, never from data values (rule `consumer/ids-from-useid.md`).
|
|
284
|
+
- A wrapper passes its child only props the child declares. Anything else falls through as an
|
|
285
|
+
HTML attribute on the child's root: `InputTextField` put `label="…"` and
|
|
286
|
+
`error-message="…"` on `.input-text-wrapper` (fixed 2026-10-16). Check the child root's
|
|
287
|
+
attributes in a test.
|
|
288
|
+
- A control styled with `all: unset` restores the UA defaults it depends on. On a `<textarea>`
|
|
289
|
+
that reset `white-space: pre-wrap` (inherited, so it took the page's `normal`); set
|
|
290
|
+
`white-space: pre-wrap` and `overflow-wrap: anywhere` explicitly (`InputTextarea`,
|
|
291
|
+
2026-10-16).
|
|
292
|
+
- A state rule (`[data-checked]`) written at the root level loses to deeper nested base rules
|
|
293
|
+
for the same element. Nest it in the element's own block as `.root[data-x] & { … }`
|
|
294
|
+
(`ToggleSwitch`, 2026-10-16; `css-nesting-conventions.md`). Same for hover and focus: keep
|
|
295
|
+
both at the same nesting depth with focus after hover, or a deeper hover rule recolours the
|
|
296
|
+
focus ring while both apply (`InputSelect`, 2026-10-17).
|
|
278
297
|
- Step buttons and any other script that changes a number round to the step's precision
|
|
279
298
|
(`toFixed`), clamp to `min`/`max`, and compare limits with `<=`/`>=`, never `===` (0.1 steps
|
|
280
299
|
drift to 0.30000000000000004, and an overshoot past an `===` check never disables the button).
|
|
281
300
|
Reference: `stepBy()` in `InputNumberField`.
|
|
282
301
|
- Layout measured in script (connector lines, indicators) re-measures when any measured item
|
|
283
302
|
resizes or changes class, not only when the container resizes (`measured-layout-observers.md`,
|
|
284
|
-
pitfall #28).
|
|
303
|
+
pitfall #28). Prefer a CSS-only position when one exists (rule above, `SegmentedControl`).
|
|
304
|
+
- Geometry drawn in script (SVG paths, canvas) is physical. Position content around it with
|
|
305
|
+
physical properties (`margin-left`, not `margin-inline-start`), or right-to-left content
|
|
306
|
+
overlaps a one-sided bar (`AlertMask`, 2026-10-17).
|
|
285
307
|
- Action buttons are `InputButton` (`variant="inline"` when the wrapper styles them), not a raw
|
|
286
308
|
`<button>` (`component-inline-button.md`, `component-inline-action-button.md`). The ledger
|
|
287
309
|
doesn't check this, which is how `InputCopy` scored 5/5 with one (fixed 2026-10-15).
|
|
@@ -76,7 +76,7 @@ Target `.my-action-button.input-button` inside the wrapper to override InputButt
|
|
|
76
76
|
|
|
77
77
|
## What `variant="inline"` still brings
|
|
78
78
|
|
|
79
|
-
`inline` adds no colours, but the base `.input-button` rule and its inner `.button-text` still
|
|
79
|
+
`inline` adds no colours, but the base `.input-button` rule and its inner `.input-button-text` still
|
|
80
80
|
apply. Override what the wrapper doesn't want, on the `.my-action-button.input-button` selector:
|
|
81
81
|
|
|
82
82
|
- `box-sizing: content-box`: set `border-box` if the button has a `max-inline-size: 100%` or sits
|
|
@@ -84,10 +84,11 @@ apply. Override what the wrapper doesn't want, on the `.my-action-button.input-b
|
|
|
84
84
|
- `min-height: var(--button-min-height)`: set `auto` to size from your own padding.
|
|
85
85
|
- `grid-auto-flow: column` layout, `overflow: hidden` and a `transition: all` (replace it with
|
|
86
86
|
your own `transition` list).
|
|
87
|
-
- `.button-text` takes `font-size: var(--button-font-size)` and
|
|
87
|
+
- `.input-button-text` takes `font-size: var(--button-font-size)` and
|
|
88
88
|
`text-transform: var(--button-text-transform)`: set `font-size: inherit; text-transform: none`
|
|
89
|
-
on `& .button-text` to keep the wrapper's type. It already wraps unbroken text
|
|
90
|
-
(`overflow-wrap: anywhere`, with `white-space` from `--input-button-text-white-space
|
|
89
|
+
on `& .input-button-text` to keep the wrapper's type. It already wraps unbroken text
|
|
90
|
+
(`overflow-wrap: anywhere`, with `white-space` from `--input-button-text-white-space`, default
|
|
91
|
+
`normal`).
|
|
91
92
|
- The label is the `buttonText` string prop; there's no label slot. A wrapper that had label
|
|
92
93
|
slots on a raw `<button>` turns them into string props when it switches (a breaking change).
|
|
93
94
|
|
|
@@ -60,7 +60,12 @@ stripping `Core`.
|
|
|
60
60
|
control name).
|
|
61
61
|
2. `sed` the old names across `app/`, `.claude/`, `.vscode/`, `MIGRATION.md` and `CLAUDE.md`:
|
|
62
62
|
component names, `import` paths, story titles/`*StoryArgs` interfaces, `describe()` names, and the
|
|
63
|
-
root/base DOM class (update `CONSUMER-STYLING.md` and test selectors that use it).
|
|
63
|
+
root/base DOM class (update `CONSUMER-STYLING.md` and test selectors that use it). Match on word
|
|
64
|
+
boundaries (`\bToggleSwitchCore\b`): a bare `ToggleSwitchCore` also matched inside
|
|
65
|
+
`TripleToggleSwitchCore` and rewrote its history notes (2026-10-16). Leave out the gitignored
|
|
66
|
+
`.claude/settings.json` (its old allowlist entries match too) and any dated "Renamed"/"Changed"
|
|
67
|
+
notes, and rename a removed wrapper by hand before the bulk pass, or `XWithLabel` → `XField`
|
|
68
|
+
turns `XWithLabelInline` into a name that never existed.
|
|
64
69
|
3. Add a "Renamed YYYY-MM-DD" note at the top of the skill doc listing old → new names and classes.
|
|
65
70
|
4. Grep consumer repos (`/check-component-usage`) and **list** the files/lines that use the old
|
|
66
71
|
names (in the private consumer notes, never in this repo). Don't edit the consumers yet: they pin
|
|
@@ -93,12 +98,13 @@ external consumer, free to rename. Run `/check-component-usage` for the current
|
|
|
93
98
|
| ~~ToggleSwitchWithLabel~~ | ✅ `ToggleSwitchField` (2026-10-16) | yes |
|
|
94
99
|
| ~~ToggleSwitchWithLabelInline~~ | ✅ `layout="inline"` on `ToggleSwitchField` (2026-10-16) | - |
|
|
95
100
|
| ~~TripleToggleSwitchCore~~ | ✅ `TripleToggleSwitch` (2026-09-27) | - |
|
|
101
|
+
| ~~TripleToggleSwitch~~ | ✅ `SegmentedControl` (2026-10-17): handles any option count | - |
|
|
96
102
|
| ~~InputButtonCore~~ | ✅ `InputButton` (2026-09-28) | yes |
|
|
97
103
|
| ~~InputCheckboxRadioCore~~ | ✅ `InputCheckboxRadio` (2026-09-27) | - |
|
|
98
104
|
| InputCheckboxRadioButton | ✅ kept: button-style option, not a Field (2026-09-27) | - |
|
|
99
105
|
| ~~InputCheckboxRadioWithLabel~~ | ✅ `InputCheckboxRadioField` (2026-09-27) | - |
|
|
100
106
|
| ~~ActionMenuItemCore~~ | ✅ `ActionMenuItem` (2026-09-27) | - |
|
|
101
|
-
| AlertMaskCore | `AlertMask` | - |
|
|
107
|
+
| ~~AlertMaskCore~~ | ✅ `AlertMask` (2026-10-17) | - |
|
|
102
108
|
| CardCore | **decide**: `Card` is single-word and collision-prone; prefer a prefixed name (`Display*`, `Content*`) | yes |
|
|
103
109
|
| AccordianCore | **decide**: also fixes the "Accordian" misspelling; bare `Accordion` is single-word and collision-prone, and it is built on `<details>`/`<summary>` | yes |
|
|
104
110
|
| ~~TabsCore~~ | ✅ `TabbedContent` (2026-10-07) | - |
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: AlertMask
|
|
3
|
+
description: AlertMask SVG border and fill sized to slotted content; config-prop geometry, --alert-mask-* colour tokens
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# AlertMask
|
|
8
|
+
|
|
9
|
+
> **Renamed 2026-10-17**: `AlertMaskCore` → `AlertMask` (Control/Field naming), root class
|
|
10
|
+
> `.alert-mask-core` → `.alert-mask`, types file `alert-mask-core.d.ts` → `alert-mask.d.ts`
|
|
11
|
+
> (`AlertMaskConfig` unchanged). Props and slot unchanged.
|
|
8
12
|
|
|
9
13
|
## Overview
|
|
10
14
|
|
|
11
|
-
`
|
|
15
|
+
`AlertMask` draws a border + background around its default slot content using an SVG `<mask>`,
|
|
12
16
|
sized to the content's measured dimensions via `ResizeObserver`. It's the low-level primitive
|
|
13
17
|
`AlertMaskedContent` (`app/components/02.molecules/alert-masked-content/`) builds on for its
|
|
14
18
|
accent-bordered alert shape.
|
|
@@ -20,12 +24,12 @@ accent-bordered alert shape.
|
|
|
20
24
|
| `config` | `AlertMaskConfig` | `{}` | Border/background colour and geometry (see below). All fields optional. |
|
|
21
25
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
|
|
22
26
|
|
|
23
|
-
### `AlertMaskConfig` (`app/types/components/alert-mask
|
|
27
|
+
### `AlertMaskConfig` (`app/types/components/alert-mask.d.ts`)
|
|
24
28
|
|
|
25
29
|
| Field | Type | Default | Description |
|
|
26
30
|
|---|---|---|---|
|
|
27
|
-
| `backgroundColour` | `string` | `
|
|
28
|
-
| `borderColour` | `string` |
|
|
31
|
+
| `backgroundColour` | `string` | token `--alert-mask-background-colour` | Inner fill colour; overrides the token. |
|
|
32
|
+
| `borderColour` | `string` | token `--alert-mask-border-colour` | Border fill colour; overrides the token. |
|
|
29
33
|
| `radiusLeft` | `number` | `12` | Left-side corner radius (px). |
|
|
30
34
|
| `radiusRight` | `number` | `12` | Right-side corner radius (px). |
|
|
31
35
|
| `borderLeft` | `number` | `8` | Left border thickness (px). |
|
|
@@ -33,14 +37,18 @@ accent-bordered alert shape.
|
|
|
33
37
|
| `borderRight` | `number` | `8` | Right border thickness (px). |
|
|
34
38
|
| `borderBottom` | `number` | `8` | Bottom border thickness (px). |
|
|
35
39
|
|
|
40
|
+
Sides are physical, matching the drawn path. Negative or non-finite sizes fall back to `0` and the
|
|
41
|
+
default respectively; radii are clamped to half the box's smaller side (inner radii to the inner
|
|
42
|
+
box), and borders that leave no inner box drop the fill.
|
|
43
|
+
|
|
36
44
|
## Basic usage
|
|
37
45
|
|
|
38
46
|
```vue
|
|
39
|
-
<
|
|
47
|
+
<AlertMask>
|
|
40
48
|
<p>Alert content</p>
|
|
41
|
-
</
|
|
49
|
+
</AlertMask>
|
|
42
50
|
|
|
43
|
-
<
|
|
51
|
+
<AlertMask
|
|
44
52
|
:config="{
|
|
45
53
|
borderColour: 'var(--theme-accent)',
|
|
46
54
|
backgroundColour: 'rgba(0, 0, 0, 0.3)',
|
|
@@ -53,33 +61,33 @@ accent-bordered alert shape.
|
|
|
53
61
|
}"
|
|
54
62
|
>
|
|
55
63
|
<p>Accent-bordered alert content</p>
|
|
56
|
-
</
|
|
64
|
+
</AlertMask>
|
|
57
65
|
```
|
|
58
66
|
|
|
59
67
|
## How it works
|
|
60
68
|
|
|
61
69
|
- The default-slot content is measured via `ResizeObserver` (initial measurement runs after
|
|
62
|
-
`nextTick` on mount).
|
|
70
|
+
`nextTick` on mount). Only the content size is stored; the SVG size is computed from it plus the
|
|
71
|
+
current borders, so a `config` change redraws without waiting for a resize. Nothing draws until
|
|
72
|
+
the first measurement.
|
|
63
73
|
- An SVG outer path (rounded rect using `radiusLeft`/`radiusRight`) and inner path (the same rect
|
|
64
74
|
inset by the border thicknesses) are computed from that measurement, and the region between them
|
|
65
75
|
is masked to `borderColour`; the inner region is filled with `backgroundColour`.
|
|
66
76
|
- Each instance gets its own mask id (`alert-mask-<useId>`), so several masks on one page don't
|
|
67
77
|
share a shape. The SVG is `aria-hidden`.
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
- Colours come from `--alert-mask-border-colour` / `--alert-mask-background-colour` (theme
|
|
79
|
+
defaults). A `config` colour is applied as an inline `fill` **style**, so it wins and a `var()`
|
|
80
|
+
value resolves against the element's ancestors.
|
|
71
81
|
- The `ResizeObserver` is disconnected on unmount.
|
|
72
82
|
- The slot content itself sits in a separate `.alert-mask-content` div, inset by the same border
|
|
73
|
-
thicknesses via
|
|
74
|
-
|
|
83
|
+
thicknesses via private `--_inset-left|right|top|bottom` (physical `margin`, so right-to-left
|
|
84
|
+
layouts keep the content clear of a left accent bar).
|
|
75
85
|
|
|
76
|
-
##
|
|
86
|
+
## Styling
|
|
77
87
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
component can expose its own tokens through them (as `AlertMaskedContent` does with
|
|
82
|
-
`--alert-masked-content-border-colour`/`-background`). See CONSUMER-STYLING.md.
|
|
88
|
+
Colour tokens `--alert-mask-border-colour` and `--alert-mask-background-colour`; geometry through
|
|
89
|
+
`config`. `AlertMaskedContent` passes both colours through `config` from its own
|
|
90
|
+
`--alert-masked-content-*` tokens. See CONSUMER-STYLING.md.
|
|
83
91
|
|
|
84
92
|
## Notes
|
|
85
93
|
|
|
@@ -96,3 +104,10 @@ component can expose its own tokens through them (as `AlertMaskedContent` does w
|
|
|
96
104
|
resolves reliably, and the `ResizeObserver` is disconnected on unmount (it previously leaked).
|
|
97
105
|
Stories now render over a photo/gradient backdrop with a `data-theme` wrapper; on the plain canvas
|
|
98
106
|
they looked flat grey.
|
|
107
|
+
- 2026-10-17 migration: renamed from `AlertMaskCore`. Fixed: content margins used `0` when
|
|
108
|
+
`config` omitted a border while the path used the default `8` (content overlapped the border);
|
|
109
|
+
the SVG size only updated on a content resize, so a `config` border change left it stale; no
|
|
110
|
+
clamping, so an oversized radius or negative border drew a broken path; content margins were
|
|
111
|
+
logical while the path is physical (right-to-left content overlapped the left accent bar);
|
|
112
|
+
passthrough didn't react. Colours became `--alert-mask-*` tokens with `--theme-*` defaults.
|
|
113
|
+
The snippet's tab stops (`$38` read as stop 38) were fixed. `StressTest` story added.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: AlertMaskedContent
|
|
3
|
-
description: AlertMaskedContent — AlertContentInner inside
|
|
3
|
+
description: AlertMaskedContent — AlertContentInner inside AlertMask's SVG mask (accent border cut-out + translucent fill); the masked variant DisplayToast/DisplayPrompt swap in
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ type: reference
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
11
|
The "glass" sibling of `AlertContent`. Same icon/title/body/dismiss layout (the shared
|
|
12
|
-
`AlertContentInner`), but instead of an opaque surface it sits inside `
|
|
12
|
+
`AlertContentInner`), but instead of an opaque surface it sits inside `AlertMask`, which draws
|
|
13
13
|
an accent-coloured border as an SVG cut-out and a translucent fill, so the page behind shows
|
|
14
14
|
through.
|
|
15
15
|
|
|
@@ -91,5 +91,5 @@ See `CONSUMER-STYLING.md` next to the component. Public tokens:
|
|
|
91
91
|
- Changed 2026-09-27: colours are now overridable via the two public tokens (previously fixed
|
|
92
92
|
literals, reachable only through `maskConfig`), and the transparent inner background uses
|
|
93
93
|
`AlertContentInner`'s public `--alert-content-inner-background` instead of reaching into its
|
|
94
|
-
private `--_alert-content-inner-bg`. `
|
|
94
|
+
private `--_alert-content-inner-bg`. `AlertMask` now gives each instance a unique mask id,
|
|
95
95
|
so several masked alerts on one page (e.g. stacked toasts) no longer all use the first one's shape.
|
|
@@ -97,7 +97,7 @@ apply instantly instead of fading).
|
|
|
97
97
|
|
|
98
98
|
- 2026-09-20 migration: renamed from `ContainerGlowCore` to `ContainerGlow` — the `Core` suffix in
|
|
99
99
|
this library denotes a primitive with a non-Core wrapper built on top of it (`InputTextCore` → `InputTextWithLabel`, since renamed;
|
|
100
|
-
`
|
|
100
|
+
`AlertMask` → `AlertMaskedContent`), and this component has no such
|
|
101
101
|
sibling. Checked all consumer repos first; found one real usage in a defunct
|
|
102
102
|
demo app, left as a follow-up for that repo rather than edited here.
|
|
103
103
|
- Moved from `app/components/container-glow/` (unplaced) into
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: DisplayThemeSwitch
|
|
3
|
-
description: DisplayThemeSwitch system/light/dark theme picker wired to useSettingsStore, wraps
|
|
3
|
+
description: DisplayThemeSwitch system/light/dark theme picker wired to useSettingsStore, wraps SegmentedControl
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ type: reference
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
11
|
`DisplayThemeSwitch` is a system/light/dark colour-scheme picker. It's a thin wrapper around
|
|
12
|
-
`
|
|
12
|
+
`SegmentedControl` (`app/components/05.forms/segmented-control/`), wired to
|
|
13
13
|
`useSettingsStore` (`app/stores/useSettingsStore.ts`) so selecting an option updates the app's
|
|
14
14
|
`colourScheme` state and applies the matching class (`system`/`light`/`dark`) to
|
|
15
15
|
`<html>`. Renders inside `<ClientOnly>` since the store's persisted value isn't known until
|
|
@@ -43,8 +43,8 @@ hydration.
|
|
|
43
43
|
## Styling
|
|
44
44
|
|
|
45
45
|
No `--display-theme-switch-*` tokens of its own — every visual aspect (colour, marker gradient,
|
|
46
|
-
sizing) is delegated to `
|
|
47
|
-
CONSUMER-STYLING.md here and `
|
|
46
|
+
sizing) is delegated to `SegmentedControl`'s public `--segmented-control-*` tokens. See
|
|
47
|
+
CONSUMER-STYLING.md here and `SegmentedControl`'s own CONSUMER-STYLING.md.
|
|
48
48
|
|
|
49
49
|
## Notes
|
|
50
50
|
|
|
@@ -52,10 +52,10 @@ CONSUMER-STYLING.md here and `TripleToggleSwitch`'s own CONSUMER-STYLING.md.
|
|
|
52
52
|
`02.molecules/`. Added `systemLabel`/`lightLabel`/`darkLabel`/`systemIcon`/`lightIcon`/`darkIcon`
|
|
53
53
|
props — the three option labels/icons were previously hardcoded English strings with no override
|
|
54
54
|
hook. Removed a large block of dead/duplicate CSS in this component's own `<style>` that either
|
|
55
|
-
no-op-duplicated `
|
|
56
|
-
actively bypassed its public `--
|
|
57
|
-
consumer setting e.g. `--
|
|
55
|
+
no-op-duplicated `SegmentedControl`'s own default styling (same selectors, same values) or
|
|
56
|
+
actively bypassed its public `--segmented-control-*` tokens with hardcoded literals (a
|
|
57
|
+
consumer setting e.g. `--segmented-control-surface` globally would have been silently
|
|
58
58
|
overridden back). Only the genuine `small`-variant sizing override remains, now driving
|
|
59
|
-
`
|
|
59
|
+
`SegmentedControl`'s own public sizing tokens (`--segmented-control-gap`/`-padding`/
|
|
60
60
|
`-option-padding`/`-icon-size`, added in the same migration) instead of reaching into its
|
|
61
61
|
private `--_form-*`/`--_scheme-icon-font-size` locals directly.
|
|
@@ -18,14 +18,14 @@ so its tokens restyle those too.
|
|
|
18
18
|
| Prop | Type | Default | Notes |
|
|
19
19
|
|---|---|---|---|
|
|
20
20
|
| `buttonText` | `string` | `""` | Visible label; becomes `sr-only` with the `#iconOnly` slot, so always set it for icon-only buttons |
|
|
21
|
-
| `variant` | `"primary" \| "secondary" \| "tertiary" \| "inline"` | `"primary"` | `inline` is for action buttons inside custom wrappers (see `component-inline-action-button.md`) |
|
|
21
|
+
| `variant` | `"primary" \| "secondary" \| "tertiary" \| "inline"` | `"primary"` | Rendered as `data-variant`. Primary is filled, secondary outlined, tertiary text-only (tint on hover). `inline` has no colours of its own and is for action buttons inside custom wrappers (see `component-inline-action-button.md`) |
|
|
22
22
|
| `type` | `"button" \| "reset" \| "submit"` | `"button"` | Ignored when rendered as a link |
|
|
23
23
|
| `href` | `string` | — | Renders a link instead of a button |
|
|
24
24
|
| `external` | `boolean` | `false` | Force a real navigation for a same-origin `href` that isn't a Vue Router page (e.g. a Nitro server route) |
|
|
25
25
|
| `theme` | `"default" \| "success" \| "error" \| "warning"` | `"default"` | Sets `data-theme`, re-declaring the `--theme-*` palette on the button |
|
|
26
|
-
| `isPill` | `boolean` | `false` | Fully rounded |
|
|
27
|
-
| `isPending` | `boolean` | `false` |
|
|
28
|
-
| `hasPendingEffect` | `boolean` | `false` | Renders the `PendingEffect` overlay |
|
|
26
|
+
| `isPill` | `boolean` | `false` | Fully rounded (`data-pill`) |
|
|
27
|
+
| `isPending` | `boolean` | `false` | Sets `data-pending` (dimmed primary surface; with `hasPendingEffect`, animates the border and blocks clicks) |
|
|
28
|
+
| `hasPendingEffect` | `boolean` | `false` | Renders the `PendingEffect` overlay, sets `data-pending-effect` |
|
|
29
29
|
| `readonly` | `boolean` | `false` | Dimmed, `aria-disabled="true"`, and not operable: clicks (mouse or keyboard) are swallowed before any consumer `@click` runs |
|
|
30
30
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Classes on the root. Reactive after mount |
|
|
31
31
|
|
|
@@ -56,7 +56,7 @@ Give slotted `<Icon>`s `class="icon"` to pick up `--input-icon-size` (applied as
|
|
|
56
56
|
## Accessibility
|
|
57
57
|
|
|
58
58
|
- Icon-only buttons keep `buttonText` as `sr-only` text: never leave it empty.
|
|
59
|
-
- `readonly` is exposed as `aria-disabled="true"` and blocks activation; the button stays focusable
|
|
59
|
+
- `readonly` is exposed as `aria-disabled="true"` (omitted otherwise) and blocks activation; the button stays focusable
|
|
60
60
|
so its label is still discoverable (unlike native `disabled`).
|
|
61
61
|
- Focus ring: `:focus-visible` with `--button-focus-ring-width` / `-offset`.
|
|
62
62
|
|
|
@@ -66,10 +66,29 @@ Colour tokens are `--input-button-{variant}-{property}` with `--theme-*` fallbac
|
|
|
66
66
|
typography are the global `--button-*` tokens. Full reference:
|
|
67
67
|
`app/components/05.forms/input-button/CONSUMER-STYLING.md`.
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
Long labels wrap inside the button (`--input-button-text-white-space` defaults to `normal`, and
|
|
70
|
+
unbroken strings break anywhere). Set it to `nowrap` to keep a label on one line; it then widens
|
|
71
|
+
the button instead. Button text is never line-clamped.
|
|
72
|
+
|
|
73
|
+
State hooks on the root: `data-variant`, `data-theme`, `data-pill`, `data-pending`,
|
|
74
|
+
`data-pending-effect`, `data-link`, `data-icon-only`. Inner elements: `.input-button-text` and
|
|
75
|
+
`.input-button-icon[data-slot="left" | "right" | "icon-only"]`.
|
|
71
76
|
|
|
72
77
|
## Notes
|
|
73
78
|
|
|
74
79
|
- Before 2026-09-28, `readonly` only set `pointer-events: none`, so keyboard users could still
|
|
75
80
|
activate it; the icon size (`width`/`height` on `.icon`) was silently overridden by `@nuxt/icon`.
|
|
81
|
+
- **2026-10-17**: secondary changed from a filled `--theme-surface-inverted` button to outlined
|
|
82
|
+
(transparent, `--theme-surface` border and text, `--theme-surface-subtle` on hover), and tertiary
|
|
83
|
+
from a grey `--slate-01` fill with an underline to text-only. Both left the `contrast-color()`
|
|
84
|
+
trial, which now covers primary only. Primary and secondary gained a 2px `box-shadow` ring
|
|
85
|
+
(`--input-button-{variant}-shadow`, default `--colour-theme-2`; width `--input-button-shadow-width`),
|
|
86
|
+
and their hover outline now takes the hover border colour (1px, no offset). Tertiary has neither.
|
|
87
|
+
- **2026-10-17**: state moved from bare classes to `data-*` attributes (`.primary` →
|
|
88
|
+
`[data-variant="primary"]`, `.pill` → `[data-pill]`, `.is-pending` → `[data-pending]`,
|
|
89
|
+
`.pending-effect` → `[data-pending-effect]`, `.is-link` → `[data-link]`, `.icon-only` →
|
|
90
|
+
`[data-icon-only]`), and the inner classes were prefixed (`.button-text` →
|
|
91
|
+
`.input-button-text`, `.btn-icon.left` → `.input-button-icon[data-slot="left"]`). The bare names
|
|
92
|
+
collided with consumer CSS (pitfall #16). `--input-button-text-white-space` now defaults to
|
|
93
|
+
`normal` (was `nowrap`, which overflowed narrow columns). `aria-disabled` is omitted instead of
|
|
94
|
+
`"false"` when not readonly. Added the `StressTest` story.
|
|
@@ -163,3 +163,5 @@ line-clamp tokens: an option label names the choice and must stay readable. The
|
|
|
163
163
|
- `InputCheckboxRadioButton`'s `id` became optional with a `useId()` fallback.
|
|
164
164
|
- Specs now unmount every wrapper in `afterEach`; stories moved to `StoryObj` with Docs
|
|
165
165
|
descriptions, parent-story links, `CanvasSwitcher` (Field, Button) and `StressTest` stories.
|
|
166
|
+
|
|
167
|
+
**2026-10-17**: resting shadow ring, the box (`--input-checkbox-shadow`, not inside the button style) and the button style (`--input-checkbox-button-shadow`). Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -100,3 +100,8 @@ look, zero the slot margins and space it with the root margins (example in CONSU
|
|
|
100
100
|
bottom margin collapsed through and won anyway).
|
|
101
101
|
- An empty `descriptionId` no longer renders `id=""`. `styleClassPassthrough` now reacts to prop
|
|
102
102
|
changes.
|
|
103
|
+
|
|
104
|
+
**2026-10-17** (stress-test pass): `--input-description-line-height` defaults to `1.5` (was
|
|
105
|
+
`var(--step-4)`, equal to the font size, so wrapped lines touched); the root wraps unbroken copy
|
|
106
|
+
(`overflow-wrap: anywhere`). Added the `StressTest` story, a CanvasSwitcher decorator, Docs page
|
|
107
|
+
descriptions and a link to the InputTextField story; the spec restores mocks after each test.
|
|
@@ -24,7 +24,7 @@ the surrounding theme.
|
|
|
24
24
|
| Prop (template form) | Type | Default | Notes |
|
|
25
25
|
|------|------|---------|-------|
|
|
26
26
|
| `:id` | `string` | (required) | Must match an id in the control's `aria-describedby` while in error. Wrappers get it from `useAriaDescribedById(...).errorId`, which only adds it to `aria-describedby` when `fieldHasError` is true. |
|
|
27
|
-
| `:error-message` | `string \| string[] \| object` | (required) | A string renders as one message; an array renders as a `<ul
|
|
27
|
+
| `:error-message` | `string \| string[] \| object` | (required) | A string renders as one message; an array renders as a `<ul role="list">`, with blank entries dropped. Long and unbroken text wraps inside the strip (`overflow-wrap: anywhere`); it is never line-clamped. |
|
|
28
28
|
| `:show-error` | `boolean` | (required) | Opens the strip. Rendered as `data-visible`; when `false` the root gets `aria-hidden="true"`. |
|
|
29
29
|
| `:is-detached` | `boolean` | (required) | `true`: sits below the control with a `2rem` gap, fully rounded. `false`: tucked under the control's bottom edge. Rendered as `data-detached`. |
|
|
30
30
|
| `:input-variant` | `"normal" \| "outlined" \| "underlined"` | `"normal"` | Match the control's variant. Rendered as `data-input-variant`. `underlined` drops the rounded corners and (when attached) the outline. |
|
|
@@ -41,7 +41,7 @@ No slots.
|
|
|
41
41
|
```vue
|
|
42
42
|
<template>
|
|
43
43
|
<div class="my-field">
|
|
44
|
-
<input :id :aria-describedby="ariaDescribedby()" :aria-invalid="fieldHasError" />
|
|
44
|
+
<input :id :aria-describedby="ariaDescribedby()" :aria-invalid="fieldHasError ? 'true' : undefined" />
|
|
45
45
|
<InputError :id="errorId" :error-message :show-error="fieldHasError" :is-detached="false" :input-variant />
|
|
46
46
|
</div>
|
|
47
47
|
</template>
|
|
@@ -66,6 +66,8 @@ const { id, errorId, ariaDescribedby } = useAriaDescribedById(props.name, toRef(
|
|
|
66
66
|
read when the control is focused. Moving focus to the first invalid field on submit is the
|
|
67
67
|
form's job (see `FormWrapper` / pattern forms).
|
|
68
68
|
- Hidden errors are `aria-hidden="true"`; the icon is always `aria-hidden`.
|
|
69
|
+
- The message list keeps `role="list"`, since `list-style: none` makes Safari/VoiceOver drop list
|
|
70
|
+
semantics.
|
|
69
71
|
- The open/close transition is disabled under `prefers-reduced-motion: reduce`.
|
|
70
72
|
|
|
71
73
|
---
|
|
@@ -103,3 +105,8 @@ re-declares those on the element itself, so ancestor overrides of them never lan
|
|
|
103
105
|
Hardcoded font size/weight, padding, list gap and detached gap promoted to public tokens.
|
|
104
106
|
- `aria-hidden` now omitted when visible (was `"false"`); icon marked `aria-hidden`;
|
|
105
107
|
`prefers-reduced-motion` respected; `styleClassPassthrough` now reacts to prop changes.
|
|
108
|
+
|
|
109
|
+
**2026-10-17** (stress-test pass): added the `StressTest` story and a CanvasSwitcher decorator;
|
|
110
|
+
message text wraps long unbroken strings (`min-inline-size: 0`, `overflow-wrap: anywhere`) and the
|
|
111
|
+
icon no longer shrinks; blank array entries are dropped; the list gets `role="list"`; child rules
|
|
112
|
+
nested inside the root block. No line-clamp token on purpose: a truncated error can't be acted on.
|
|
@@ -41,7 +41,9 @@ Use `InputLabel` directly only when building a custom field wrapper:
|
|
|
41
41
|
| `htmlLabel` | Rich markup (e.g. a required marker). Rendered first. Phrasing content only: it's inside a `<label>`, so no interactive elements. |
|
|
42
42
|
| `textLabel` | Plain label text. |
|
|
43
43
|
|
|
44
|
-
Both render directly inside the `<label>`, no wrapper elements.
|
|
44
|
+
Both render directly inside the `<label>`, no wrapper elements. Long or unbroken copy wraps
|
|
45
|
+
(`overflow-wrap: anywhere` on the label); there is no line-clamp token, since slot copy is the
|
|
46
|
+
consumer's own markup to clamp. Don't hardcode a required
|
|
45
47
|
asterisk in a slot: use the indicator below.
|
|
46
48
|
|
|
47
49
|
---
|
|
@@ -62,7 +64,7 @@ export default defineAppConfig({
|
|
|
62
64
|
without it; `"none"`: nothing (default, so existing sites are unchanged).
|
|
63
65
|
- `InputTextField`, `InputTextAsNumberField`, `InputNumberField`, `InputSelectField`,
|
|
64
66
|
`InputTextareaField` and `InputRangeField` forward `:required`.
|
|
65
|
-
`ToggleSwitchField`
|
|
67
|
+
`ToggleSwitchField` passes `indicator="none"`: a switch always
|
|
66
68
|
has a value, so neither marker means anything there.
|
|
67
69
|
- The wrappers don't expose the indicator props per field; use `app.config`.
|
|
68
70
|
- Text props are the i18n hook: pass translated `optionalText`/`requiredText` (or set them in
|
|
@@ -116,3 +118,7 @@ Hooks: `data-input-variant`, `data-invalid`, `.input-label-indicator[data-indica
|
|
|
116
118
|
**2026-09-27**: added the required/optional indicator (`required`, `indicator`, `requiredText`,
|
|
117
119
|
`optionalText`, `requiredIcon`, `optionalIcon`, all app.config-backed under `srcdev.inputLabel`),
|
|
118
120
|
with wrappers forwarding `:required`.
|
|
121
|
+
|
|
122
|
+
**2026-10-17** (stress-test pass): `overflow-wrap: anywhere` on the label so unbroken copy and
|
|
123
|
+
markers wrap in a narrow column; added the `StressTest` story, a CanvasSwitcher decorator, Docs
|
|
124
|
+
page descriptions and a link to the InputTextField story; spec now unmounts every wrapper.
|
|
@@ -252,3 +252,5 @@ classes are gone.
|
|
|
252
252
|
- Specs unmount in `afterEach`; stories moved to `StoryObj` with Docs descriptions, a
|
|
253
253
|
`DecimalSteps` story, `CanvasSwitcher` and a `StressTest` story on the Field. The bare
|
|
254
254
|
control's step-button demo uses `InputButton`, not raw buttons.
|
|
255
|
+
|
|
256
|
+
**2026-10-17**: resting shadow ring (`--input-number-shadow`), `normal` variant; underlined unchanged. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -118,3 +118,5 @@ focused the first box on mount; pass `autofocus` to keep that. Its boxes were na
|
|
|
118
118
|
read the code from `v-model` or the hidden input (`name`) instead. Rename the app's file away (or
|
|
119
119
|
delete it) once on the layer release that ships this, or the app's own `InputOtp` shadows the
|
|
120
120
|
layer's.
|
|
121
|
+
|
|
122
|
+
**2026-10-17**: resting shadow ring (`--input-otp-shadow`), each box, `normal` variant. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -175,3 +175,5 @@ automatically in `<script setup>`, no destructuring or `props.` reference needed
|
|
|
175
175
|
- Specs unmount in `afterEach`; stories moved to `StoryObj` with Docs descriptions, a
|
|
176
176
|
`DecimalSteps` story, `CanvasSwitcher` and a `StressTest` story on the Field. The bare
|
|
177
177
|
control's step-button demo uses `InputButton`, not raw buttons.
|
|
178
|
+
|
|
179
|
+
**2026-10-17**: deliberately left out of the form-control shadow ring and border-colour hover. The default mode is the native slider (no border to ring), and markers mode draws each marker's edge as an outline. Decided to keep it as it is.
|
|
@@ -151,3 +151,5 @@ the `theme` prop says; the Field root's own `data-theme` keeps the prop value.
|
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
`v-model` and `v-model:field-data` are both **required**, same as `InputSelect` above.
|
|
154
|
+
|
|
155
|
+
**2026-10-17**: resting shadow ring (`--input-select-shadow`), `normal` variant; underlined keeps its old 2px hover outline. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -218,3 +218,5 @@ A compact numeric stepper on a text input (no native spinner). Root
|
|
|
218
218
|
- The password and stepper buttons get accessible names through `InputButton`'s `buttonText`
|
|
219
219
|
(visually hidden in icon-only mode).
|
|
220
220
|
- Use `autocomplete` on personal-data fields (WCAG 1.3.5).
|
|
221
|
+
|
|
222
|
+
**2026-10-17**: resting shadow ring (`--input-text-shadow`), `normal` variant; underlined unchanged. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -145,3 +145,5 @@ with `data-theme`, `data-input-variant`, `data-invalid`, `data-dirty` and `data-
|
|
|
145
145
|
<template #descriptionText>Please describe your enquiry in detail</template>
|
|
146
146
|
</InputTextareaField>
|
|
147
147
|
```
|
|
148
|
+
|
|
149
|
+
**2026-10-17**: resting shadow ring (`--input-textarea-shadow`), `normal` variant; underlined unchanged. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
`PendingEffect` is a decorative SVG that draws an animated dashed line around a busy button. It
|
|
6
6
|
is an internal part of `InputButton`: set `has-pending-effect` on the button and toggle
|
|
7
|
-
`is-pending`. Don't render it on its own. Its CSS depends on the host
|
|
8
|
-
(and `position: relative`, which that rule provides), plus
|
|
7
|
+
`is-pending`. Don't render it on its own. Its CSS depends on the host being `.input-button[data-pending-effect]`
|
|
8
|
+
(and `position: relative`, which that rule provides), plus `[data-pending]` to show and animate it.
|
|
9
|
+
Those were the classes `.pending-effect` and `.is-pending` until 2026-10-17.
|
|
9
10
|
|
|
10
11
|
```vue
|
|
11
12
|
<InputButton button-text="Submit" type="submit" has-pending-effect :is-pending="submitting" />
|
|
@@ -26,10 +27,10 @@ No slots, no events.
|
|
|
26
27
|
|
|
27
28
|
## Behaviour
|
|
28
29
|
|
|
29
|
-
- Hidden (`opacity: 0`) until the host has
|
|
30
|
+
- Hidden (`opacity: 0`) until the host has `[data-pending]`.
|
|
30
31
|
- The host must not clip overflow: the line is drawn outside the host's edge. `InputButton`
|
|
31
32
|
sets `overflow: hidden` on buttons generally, and switches it to `visible` when
|
|
32
|
-
|
|
33
|
+
`[data-pending-effect]` is present. Before 2026-09-26 it didn't, so the effect never rendered at all.
|
|
33
34
|
- Under `prefers-reduced-motion: reduce` it never shows, since the animation is the whole effect.
|
|
34
35
|
- `aria-hidden="true"` and `focusable="false"`: it is decorative. Busy state for assistive tech
|
|
35
36
|
is the host button's responsibility.
|