srcdev-nuxt-components 9.6.10 → 9.6.12

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.
Files changed (125) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +4 -2
  2. package/.claude/skills/component-compliance-checklist.md +14 -3
  3. package/.claude/skills/component-inline-action-button.md +5 -4
  4. package/.claude/skills/component-naming.md +2 -1
  5. package/.claude/skills/components/{alert-mask-core.md → alert-mask.md} +38 -23
  6. package/.claude/skills/components/alert-masked-content.md +3 -3
  7. package/.claude/skills/components/container-glow.md +1 -1
  8. package/.claude/skills/components/display-theme-switch.md +8 -8
  9. package/.claude/skills/components/form-field.md +15 -0
  10. package/.claude/skills/components/form-fieldset.md +18 -2
  11. package/.claude/skills/components/input-button.md +26 -7
  12. package/.claude/skills/components/input-checkbox-radio.md +2 -0
  13. package/.claude/skills/components/input-description.md +5 -0
  14. package/.claude/skills/components/input-error.md +9 -2
  15. package/.claude/skills/components/input-label.md +8 -2
  16. package/.claude/skills/components/input-number.md +2 -0
  17. package/.claude/skills/components/input-otp.md +2 -0
  18. package/.claude/skills/components/input-range.md +2 -0
  19. package/.claude/skills/components/input-select.md +2 -0
  20. package/.claude/skills/components/input-text.md +2 -0
  21. package/.claude/skills/components/input-textarea.md +2 -0
  22. package/.claude/skills/components/marquee-scroller.md +51 -14
  23. package/.claude/skills/components/pending-effect.md +5 -4
  24. package/.claude/skills/components/{triple-toggle-switch.md → segmented-control.md} +34 -19
  25. package/.claude/skills/components/services-card.md +1 -1
  26. package/.claude/skills/components/tabbed-content.md +1 -1
  27. package/.claude/skills/components/toggle-switch.md +2 -0
  28. package/.claude/skills/index.md +5 -5
  29. package/.claude/skills/storybook-add-story.md +3 -1
  30. package/.claude/skills/theming-component-token-pattern.md +24 -0
  31. package/.claude/skills/theming-form-geometry-tokens.md +1 -1
  32. package/.claude/skills/theming-partial-override.md +1 -1
  33. package/.vscode/srcdev-component-alert-mask.code-snippets +41 -0
  34. package/.vscode/srcdev-component-form-field.code-snippets +7 -6
  35. package/.vscode/srcdev-component-form-fieldset.code-snippets +12 -12
  36. package/.vscode/srcdev-component-marquee-scroller.code-snippets +31 -28
  37. package/.vscode/{srcdev-component-triple-toggle-switch.code-snippets → srcdev-component-segmented-control.code-snippets} +6 -7
  38. package/app/assets/styles/setup/03.theming/_theme-slots.css +1 -1
  39. package/app/assets/styles/setup/04.elements/forms/03.generic-input-geometry.css +4 -0
  40. package/app/components/01.atoms/alert-mask/AlertMask.vue +194 -0
  41. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +57 -42
  42. package/app/components/01.atoms/alert-mask/stories/AlertMask.stories.ts +198 -0
  43. package/app/components/01.atoms/alert-mask/tests/AlertMask.spec.ts +233 -0
  44. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +77 -54
  45. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +103 -91
  46. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +132 -3
  47. package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +190 -102
  48. package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +17 -13
  49. package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +2 -2
  50. package/app/components/02.molecules/alert-masked-content/CONSUMER-STYLING.md +4 -4
  51. package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +60 -0
  52. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +12 -12
  53. package/app/components/02.molecules/cookie-consent-banner/CONSUMER-STYLING.md +3 -3
  54. package/app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue +1 -1
  55. package/app/components/02.molecules/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +1 -1
  56. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +9 -9
  57. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +16 -16
  58. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +53 -1
  59. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +8 -8
  60. package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +4 -4
  61. package/app/components/02.molecules/input-copy/InputCopy.vue +2 -2
  62. package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +2 -2
  63. package/app/components/03.organisms/google-reviews/tests/GoogleReviews.spec.ts +4 -4
  64. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +2 -2
  65. package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +2 -2
  66. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +6 -6
  67. package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +8 -0
  68. package/app/components/05.forms/form-errors/InputError.vue +43 -37
  69. package/app/components/05.forms/form-errors/stories/InputError.stories.ts +79 -1
  70. package/app/components/05.forms/form-errors/tests/InputError.spec.ts +29 -1
  71. package/app/components/05.forms/form-field/CONSUMER-STYLING.md +7 -0
  72. package/app/components/05.forms/form-field/stories/FormField.stories.ts +116 -2
  73. package/app/components/05.forms/form-fieldset/CONSUMER-STYLING.md +11 -1
  74. package/app/components/05.forms/form-fieldset/FormFieldset.vue +4 -2
  75. package/app/components/05.forms/form-fieldset/stories/FormFieldset.stories.ts +68 -4
  76. package/app/components/05.forms/form-fieldset/tests/FormFieldset.spec.ts +10 -0
  77. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +80 -47
  78. package/app/components/05.forms/input-button/InputButton.vue +52 -100
  79. package/app/components/05.forms/input-button/playwright/input-button.playwright.ts +1 -1
  80. package/app/components/05.forms/input-button/stories/InputButton.stories.ts +145 -8
  81. package/app/components/05.forms/input-button/tests/InputButton.spec.ts +76 -46
  82. package/app/components/05.forms/input-button/tests/__snapshots__/InputButton.spec.ts.snap +17 -17
  83. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +9 -1
  84. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +8 -0
  85. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +2 -2
  86. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +14 -2
  87. package/app/components/05.forms/input-description/InputDescription.vue +2 -1
  88. package/app/components/05.forms/input-description/stories/InputDescription.stories.ts +70 -4
  89. package/app/components/05.forms/input-description/tests/InputDescription.spec.ts +2 -1
  90. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +8 -0
  91. package/app/components/05.forms/input-label/InputLabel.vue +1 -0
  92. package/app/components/05.forms/input-label/stories/InputLabel.stories.ts +68 -2
  93. package/app/components/05.forms/input-label/tests/InputLabel.spec.ts +13 -3
  94. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +7 -1
  95. package/app/components/05.forms/input-number/InputNumber.vue +4 -5
  96. package/app/components/05.forms/input-otp/CONSUMER-STYLING.md +7 -1
  97. package/app/components/05.forms/input-otp/InputOtp.vue +2 -2
  98. package/app/components/05.forms/input-range/InputRange.vue +1 -1
  99. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +7 -1
  100. package/app/components/05.forms/input-select/InputSelect.vue +14 -5
  101. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +7 -1
  102. package/app/components/05.forms/input-text/InputText.vue +4 -4
  103. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +7 -1
  104. package/app/components/05.forms/input-textarea/InputTextarea.vue +2 -3
  105. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +4 -4
  106. package/app/components/05.forms/pending-effect/CONSUMER-STYLING.md +7 -3
  107. package/app/components/05.forms/pending-effect/PendingEffect.vue +3 -3
  108. package/app/components/05.forms/pending-effect/tests/PendingEffect.spec.ts +1 -1
  109. package/app/components/05.forms/segmented-control/CONSUMER-STYLING.md +125 -0
  110. package/app/components/05.forms/segmented-control/SegmentedControl.vue +231 -0
  111. package/app/components/05.forms/segmented-control/stories/SegmentedControl.stories.ts +215 -0
  112. package/app/components/05.forms/segmented-control/tests/SegmentedControl.spec.ts +202 -0
  113. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +7 -0
  114. package/app/components/05.forms/toggle-switch/ToggleSwitch.vue +2 -0
  115. package/app/types/components/index.ts +1 -1
  116. package/package.json +1 -1
  117. package/.vscode/srcdev-component-alert-mask-core.code-snippets +0 -31
  118. package/app/components/01.atoms/alert-mask/AlertMaskCore.vue +0 -166
  119. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +0 -105
  120. package/app/components/01.atoms/alert-mask/tests/AlertMaskCore.spec.ts +0 -342
  121. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +0 -99
  122. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitch.vue +0 -251
  123. package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitch.stories.ts +0 -104
  124. package/app/components/05.forms/triple-toggle-switch/tests/TripleToggleSwitch.spec.ts +0 -138
  125. /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). Test
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`.
@@ -203,6 +204,11 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
203
204
  `aria-hidden` (`ToggleSwitch`, 2026-10-16).
204
205
  - Live-updating or auto-advancing content (carousels, marquees, auto-dismissing toasts) has a
205
206
  way to pause it, per WCAG 2.2.2 — a visible control, or pause-on-hover/focus at minimum.
207
+ - Pause-on-focus listens for `focusin`/`focusout` on the root (with a `relatedTarget`
208
+ containment check), not `focus`/`blur`, which don't bubble: focusable content inside an
209
+ `aria-hidden` moving track otherwise gets focus while still hidden and moving. A looping
210
+ duplicate copy gets `inert`, and a focus-caused pause must not undo a pause the user chose
211
+ (`MarqueeScroller`, 2026-10-18).
206
212
  - `prefers-reduced-motion` is respected for any animation that isn't purely decorative.
207
213
  - A mode that hides a native control's own indicator (a range thumb, a checkbox box, a caret)
208
214
  draws its own visible state for the current value, and its interactive area isn't shrunk to a
@@ -290,14 +296,19 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
290
296
  2026-10-16).
291
297
  - A state rule (`[data-checked]`) written at the root level loses to deeper nested base rules
292
298
  for the same element. Nest it in the element's own block as `.root[data-x] & { … }`
293
- (`ToggleSwitch`, 2026-10-16; `css-nesting-conventions.md`).
299
+ (`ToggleSwitch`, 2026-10-16; `css-nesting-conventions.md`). Same for hover and focus: keep
300
+ both at the same nesting depth with focus after hover, or a deeper hover rule recolours the
301
+ focus ring while both apply (`InputSelect`, 2026-10-17).
294
302
  - Step buttons and any other script that changes a number round to the step's precision
295
303
  (`toFixed`), clamp to `min`/`max`, and compare limits with `<=`/`>=`, never `===` (0.1 steps
296
304
  drift to 0.30000000000000004, and an overshoot past an `===` check never disables the button).
297
305
  Reference: `stepBy()` in `InputNumberField`.
298
306
  - Layout measured in script (connector lines, indicators) re-measures when any measured item
299
307
  resizes or changes class, not only when the container resizes (`measured-layout-observers.md`,
300
- pitfall #28).
308
+ pitfall #28). Prefer a CSS-only position when one exists (rule above, `SegmentedControl`).
309
+ - Geometry drawn in script (SVG paths, canvas) is physical. Position content around it with
310
+ physical properties (`margin-left`, not `margin-inline-start`), or right-to-left content
311
+ overlaps a one-sided bar (`AlertMask`, 2026-10-17).
301
312
  - Action buttons are `InputButton` (`variant="inline"` when the wrapper styles them), not a raw
302
313
  `<button>` (`component-inline-button.md`, `component-inline-action-button.md`). The ledger
303
314
  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
 
@@ -98,12 +98,13 @@ external consumer, free to rename. Run `/check-component-usage` for the current
98
98
  | ~~ToggleSwitchWithLabel~~ | ✅ `ToggleSwitchField` (2026-10-16) | yes |
99
99
  | ~~ToggleSwitchWithLabelInline~~ | ✅ `layout="inline"` on `ToggleSwitchField` (2026-10-16) | - |
100
100
  | ~~TripleToggleSwitchCore~~ | ✅ `TripleToggleSwitch` (2026-09-27) | - |
101
+ | ~~TripleToggleSwitch~~ | ✅ `SegmentedControl` (2026-10-17): handles any option count | - |
101
102
  | ~~InputButtonCore~~ | ✅ `InputButton` (2026-09-28) | yes |
102
103
  | ~~InputCheckboxRadioCore~~ | ✅ `InputCheckboxRadio` (2026-09-27) | - |
103
104
  | InputCheckboxRadioButton | ✅ kept: button-style option, not a Field (2026-09-27) | - |
104
105
  | ~~InputCheckboxRadioWithLabel~~ | ✅ `InputCheckboxRadioField` (2026-09-27) | - |
105
106
  | ~~ActionMenuItemCore~~ | ✅ `ActionMenuItem` (2026-09-27) | - |
106
- | AlertMaskCore | `AlertMask` | - |
107
+ | ~~AlertMaskCore~~ | ✅ `AlertMask` (2026-10-17) | - |
107
108
  | CardCore | **decide**: `Card` is single-word and collision-prone; prefer a prefixed name (`Display*`, `Content*`) | yes |
108
109
  | AccordianCore | **decide**: also fixes the "Accordian" misspelling; bare `Accordion` is single-word and collision-prone, and it is built on `<details>`/`<summary>` | yes |
109
110
  | ~~TabsCore~~ | ✅ `TabbedContent` (2026-10-07) | - |
@@ -1,14 +1,18 @@
1
1
  ---
2
- name: AlertMaskCore
3
- description: AlertMaskCore SVG border/background mask sized to slotted content; config-prop-driven geometry/colour, no CSS token API
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
- # AlertMaskCore
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
- `AlertMaskCore` draws a border + background around its default slot content using an SVG `<mask>`,
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-core.d.ts`)
27
+ ### `AlertMaskConfig` (`app/types/components/alert-mask.d.ts`)
24
28
 
25
29
  | Field | Type | Default | Description |
26
30
  |---|---|---|---|
27
- | `backgroundColour` | `string` | `"rgba(0,0,0,0.25)"` | Inner fill colour. |
28
- | `borderColour` | `string` | `"var(--orange-08)"` | Border fill colour. |
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
- <AlertMaskCore>
47
+ <AlertMask>
40
48
  <p>Alert content</p>
41
- </AlertMaskCore>
49
+ </AlertMask>
42
50
 
43
- <AlertMaskCore
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
- </AlertMaskCore>
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
- - `borderColour`/`backgroundColour` are applied as `fill` **styles**, not attributes, so a `var()`
69
- value (e.g. `var(--theme-accent)`, or a consumer token) resolves against the element's ancestors.
70
- A `var(--theme-*)` colour needs a `data-theme` ancestor to resolve.
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 internal `--_inset-*` custom properties, so it visually sits inside the drawn
74
- border without overlapping it.
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
- ## No CSS token API
86
+ ## Styling
77
87
 
78
- Unlike most components in this library, `AlertMaskCore` has no public `--alert-mask-core-*` CSS
79
- custom properties — the SVG path geometry can't be driven by `var()`, so `config` is the entire
80
- override surface (colour included). Colour strings may be `var()` references, so a wrapper
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 AlertMaskCore's SVG mask (accent border cut-out + translucent fill); the masked variant DisplayToast/DisplayPrompt swap in
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 `AlertMaskCore`, which draws
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`. `AlertMaskCore` now gives each instance a unique mask id,
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
- `AlertMaskCore` → `AlertMaskedContent`), and this component has no such
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 TripleToggleSwitch
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
- `TripleToggleSwitch` (`app/components/05.forms/triple-toggle-switch/`), wired to
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 `TripleToggleSwitch`'s public `--triple-toggle-switch-*` tokens. See
47
- CONSUMER-STYLING.md here and `TripleToggleSwitch`'s own CONSUMER-STYLING.md.
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 `TripleToggleSwitch`'s own default styling (same selectors, same values) or
56
- actively bypassed its public `--triple-toggle-switch-*` tokens with hardcoded literals (a
57
- consumer setting e.g. `--triple-toggle-switch-surface` globally would have been silently
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
- `TripleToggleSwitch`'s own public sizing tokens (`--triple-toggle-switch-gap`/`-padding`/
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.
@@ -57,10 +57,25 @@ Full token table in `app/components/05.forms/form-field/CONSUMER-STYLING.md`. Pu
57
57
  Target state via `.form-field[data-width="wide"]`, `.form-field[data-has-gutter]`,
58
58
  `.form-field[data-invalid]`.
59
59
 
60
+ FormField doesn't change `overflow-wrap` or `min-inline-size` on its slot content: wrapping long
61
+ copy is the slotted control's job (every `*Field` component handles its own). Plain text slotted
62
+ directly needs its own wrapping rule.
63
+
64
+ ---
65
+
66
+ ## Storybook
67
+
68
+ `Components/Forms/Form Field/FormField`: `Default`, `AllWidths`, `NoGutter`, and `StressTest`
69
+ (each width, with and without gutter and error state, around an `InputTextField` with hostile
70
+ copy). Stories use the `CanvasSwitcher` decorator.
71
+
60
72
  ---
61
73
 
62
74
  ## History
63
75
 
76
+ **2026-10-17**: added the `StressTest` story, `CanvasSwitcher` decorator and Docs-page
77
+ descriptions; nothing in the component broke under it.
78
+
64
79
  **Migrated 2026-09-26** (0/5 → full compliance):
65
80
 
66
81
  - Width and gutter modifiers moved from bare classes (`.narrow`, `.medium`, `.wide`, `.has-gutter`)
@@ -21,8 +21,8 @@ ARIA semantics.
21
21
  | `:legend` | `string` | `""` | Legend text. The legend renders when this or the `legend` slot is given. |
22
22
  | `group-role` | `"group" \| "radiogroup"` | `"group"` | `"group"` renders no `role` attribute (a fieldset's native role is already `group`). `"radiogroup"` sets `role="radiogroup"`: use it for radio buttons only. |
23
23
  | `:required` | `boolean` | `false` | Sets `aria-required="true"` **only** when `group-role="radiogroup"`. `aria-required` isn't allowed on `role="group"`, so checkbox groups get nothing; put "(required)" in the legend text instead. |
24
- | `:field-has-error` | `boolean` | `false` | Sets `aria-invalid` on the fieldset. |
25
- | `:data-testid` | `string` | `""` | Passed to the fieldset. |
24
+ | `:field-has-error` | `boolean` | `false` | Sets `aria-invalid="true"` on the fieldset; the attribute is absent when `false`. |
25
+ | `:data-testid` | `string` | `""` | Passed to the fieldset; omitted when empty. |
26
26
  | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the fieldset, via `useStyleClassPassthrough` (reacts to prop changes). |
27
27
 
28
28
  ## Slots
@@ -54,10 +54,26 @@ content `--form-fieldset-content-margin-block|margin-inline`, legend
54
54
  `--form-fieldset-legend-color|font-size|font-weight|line-height|margin-block|margin-inline`. Error state hook:
55
55
  `.form-fieldset[aria-invalid="true"]`.
56
56
 
57
+ The fieldset sets `min-inline-size: 0` and the legend `overflow-wrap: anywhere`, so neither
58
+ overflows a narrow column. Slotted controls handle their own wrapping.
59
+
60
+ ---
61
+
62
+ ## Storybook
63
+
64
+ `Components/Forms/Form Fieldset/FormFieldset`: `Default`, `CheckboxGroup`, `WithError`, and
65
+ `StressTest` (hostile legend copy with the error state on). Stories use the `CanvasSwitcher`
66
+ decorator and link to the three components that render FormFieldset.
67
+
57
68
  ---
58
69
 
59
70
  ## History
60
71
 
72
+ **2026-10-17**: StressTest story, CanvasSwitcher and Docs-page descriptions added. Fixed: a
73
+ valid fieldset rendered `aria-invalid="false"`; an empty `dataTestid` rendered `data-testid=""`;
74
+ the fieldset had no `min-inline-size: 0` (each wrapping component set it on its own class), and
75
+ the legend didn't wrap unbroken copy.
76
+
61
77
  **Migrated 2026-09-26** (0/5 → full compliance):
62
78
 
63
79
  - **Role bug fixed:** `role="radiogroup"` used to be hardcoded, so screen readers announced
@@ -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` | Adds `.is-pending` |
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
- Labels stay on one line by default. Set `--input-button-text-white-space: normal` to let a long
70
- label wrap inside a narrow container (added 2026-10-06; `ServicesCardGrid` does this for its CTAs).
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>` list. |
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. Don't hardcode a required
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` pass `indicator="none"`: a switch always
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.