stimeo-ui 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -0
  3. data/README.md +120 -0
  4. data/dist/cable/index.js +123 -29
  5. data/dist/controllers/accordion_controller.js +98 -9
  6. data/dist/controllers/announcer_controller.js +96 -62
  7. data/dist/controllers/auto_submit_controller.js +83 -7
  8. data/dist/controllers/avatar_controller.js +1 -1
  9. data/dist/controllers/breadcrumb_controller.js +38 -11
  10. data/dist/controllers/bulk_select_controller.js +7 -6
  11. data/dist/controllers/calendar_controller.js +340 -123
  12. data/dist/controllers/carousel_controller.js +263 -38
  13. data/dist/controllers/character_counter_controller.js +40 -2
  14. data/dist/controllers/checkbox_controller.js +81 -12
  15. data/dist/controllers/clipboard_controller.js +63 -5
  16. data/dist/controllers/collapsible_controller.js +99 -14
  17. data/dist/controllers/color_picker_controller.js +80 -34
  18. data/dist/controllers/combobox_controller.js +106 -16
  19. data/dist/controllers/command_palette_controller.js +35 -3
  20. data/dist/controllers/conditional_fields_controller.js +85 -17
  21. data/dist/controllers/confirm_controller.js +3 -0
  22. data/dist/controllers/context_menu_controller.js +32 -12
  23. data/dist/controllers/countdown_controller.js +129 -26
  24. data/dist/controllers/currency_input_controller.js +221 -67
  25. data/dist/controllers/data_grid_controller.js +195 -29
  26. data/dist/controllers/date_range_picker_controller.js +151 -30
  27. data/dist/controllers/dialog_controller.js +35 -8
  28. data/dist/controllers/direct_upload_controller.js +22 -4
  29. data/dist/controllers/dirty_form_controller.js +46 -13
  30. data/dist/controllers/dismissible_controller.js +1 -0
  31. data/dist/controllers/drawer_controller.js +54 -19
  32. data/dist/controllers/dropdown_controller.js +36 -9
  33. data/dist/controllers/editable_controller.js +34 -0
  34. data/dist/controllers/file_dropzone_controller.js +144 -51
  35. data/dist/controllers/filter_controller.js +20 -6
  36. data/dist/controllers/flash_controller.js +432 -71
  37. data/dist/controllers/focus_controller.js +1 -0
  38. data/dist/controllers/form_field_controller.js +7 -5
  39. data/dist/controllers/form_validation_controller.js +19 -13
  40. data/dist/controllers/frame_loading_controller.js +45 -8
  41. data/dist/controllers/highlight_controller.js +82 -25
  42. data/dist/controllers/hover_card_controller.js +40 -14
  43. data/dist/controllers/idle_controller.js +90 -5
  44. data/dist/controllers/input_mask_controller.js +65 -9
  45. data/dist/controllers/intersection_controller.js +3 -0
  46. data/dist/controllers/lazy_frame_controller.js +11 -2
  47. data/dist/controllers/listbox_controller.js +203 -45
  48. data/dist/controllers/local_time_controller.js +10 -5
  49. data/dist/controllers/masonry_controller.js +31 -15
  50. data/dist/controllers/menu_controller.js +45 -16
  51. data/dist/controllers/menubar_controller.js +58 -24
  52. data/dist/controllers/meter_controller.js +9 -5
  53. data/dist/controllers/multi_select_controller.js +278 -104
  54. data/dist/controllers/navigation_menu_controller.js +48 -15
  55. data/dist/controllers/nested_form_controller.js +37 -8
  56. data/dist/controllers/network_status_controller.js +9 -1
  57. data/dist/controllers/number_input_controller.js +124 -21
  58. data/dist/controllers/optimistic_controller.js +42 -5
  59. data/dist/controllers/otp_controller.js +198 -55
  60. data/dist/controllers/overflow_indicator_controller.js +115 -21
  61. data/dist/controllers/overflow_menu_controller.js +141 -44
  62. data/dist/controllers/pagination_controller.js +74 -28
  63. data/dist/controllers/password_reveal_controller.js +59 -2
  64. data/dist/controllers/persist_controller.js +30 -8
  65. data/dist/controllers/pointer_drag_controller.js +131 -52
  66. data/dist/controllers/popover_controller.js +45 -11
  67. data/dist/controllers/portal_controller.js +6 -2
  68. data/dist/controllers/preview_guard_controller.js +16 -1
  69. data/dist/controllers/progress_controller.js +8 -4
  70. data/dist/controllers/radio_group_controller.js +42 -17
  71. data/dist/controllers/range_slider_controller.js +88 -42
  72. data/dist/controllers/rating_controller.js +39 -15
  73. data/dist/controllers/read_more_controller.js +100 -7
  74. data/dist/controllers/reading_progress_controller.js +65 -19
  75. data/dist/controllers/relative_time_controller.js +10 -5
  76. data/dist/controllers/resizable_controller.js +82 -22
  77. data/dist/controllers/scroll_area_controller.js +75 -27
  78. data/dist/controllers/scroll_restore_controller.js +37 -16
  79. data/dist/controllers/scroll_visibility_controller.js +49 -30
  80. data/dist/controllers/scrollspy_controller.js +71 -26
  81. data/dist/controllers/separator_controller.js +66 -37
  82. data/dist/controllers/sidebar_controller.js +77 -18
  83. data/dist/controllers/skeleton_controller.js +6 -1
  84. data/dist/controllers/slider_controller.js +82 -47
  85. data/dist/controllers/smart_sticky_header_controller.js +60 -26
  86. data/dist/controllers/sortable_controller.js +17 -2
  87. data/dist/controllers/spinner_controller.js +10 -2
  88. data/dist/controllers/step_indicator_controller.js +18 -17
  89. data/dist/controllers/stepper_controller.js +101 -19
  90. data/dist/controllers/stick_to_bottom_controller.js +104 -8
  91. data/dist/controllers/submit_once_controller.js +45 -9
  92. data/dist/controllers/switch_controller.js +101 -10
  93. data/dist/controllers/tabs_controller.js +21 -2
  94. data/dist/controllers/tags_input_controller.js +209 -59
  95. data/dist/controllers/textarea_autosize_controller.js +29 -3
  96. data/dist/controllers/theme_controller.js +64 -14
  97. data/dist/controllers/time_picker_controller.js +23 -8
  98. data/dist/controllers/toast_controller.js +451 -105
  99. data/dist/controllers/toggle_group_controller.js +159 -23
  100. data/dist/controllers/toolbar_controller.js +32 -0
  101. data/dist/controllers/tooltip_controller.js +39 -13
  102. data/dist/controllers/transition_controller.js +4 -0
  103. data/dist/controllers/tree_view_controller.js +169 -16
  104. data/dist/index.js +5002 -1911
  105. data/dist/positioning/index.js +2 -0
  106. data/lib/stimeo/ui/version.rb +1 -1
  107. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8220bc5fa861ec2aa75c7abeb2fcb6758a4ba6b56606b77d5bc6d948c9550a09
4
- data.tar.gz: 95b3643b1a9bbdfdf7d556b5952c1431e6adc1ae557e5d0cf5f40b102276593a
3
+ metadata.gz: 725d3da54257b2c556c30de96e34c325ed04ccfd06a9c065484aab388f5f969c
4
+ data.tar.gz: ea9c6e5c0c2fd7dea325e6769af6b51f51e74a179575cbb05695cf965a141357
5
5
  SHA512:
6
- metadata.gz: 837c894e5a64b823b2afc662dc4cb6e74daeedb04bee3d348bbd56127ee544cb31247f0534ffca2c60a1f676a2421ec2e3dbe0bef6ecbaeed85eac17f41ac1e1
7
- data.tar.gz: 22e7cfc49254e48c27915cb9b83f632ea1348db396046a5a41be5513c8a8e4f01f00feee422c582c2423569d1cdf73228201ffc848554bb54cd1d927e18d9550
6
+ metadata.gz: b080427c85f5accf04f627e757046fac13eda2a48b0ea1e27c548fbd035cb8bc90ddfcbfdb79f3bd63c7899e16649d873efcbdc6a964c5a4e2682271ccf48749
7
+ data.tar.gz: b0d2bcd93e2beb311c4fe4e9a09f843b89cdc87e43ad88f8fd5a13e5146e939b8b9abce3adda89922c486337a141d854cfe74778a04b6249780554761cb3d4d5
data/CHANGELOG.md CHANGED
@@ -7,6 +7,216 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
  While the version is `0.x`, the public API (the `stimeo--*` data attributes) may
8
8
  change between releases.
9
9
 
10
+ ## [0.16.0] - 2026-09-26
11
+
12
+ Minor release with no new components. Widgets now report their own state
13
+ changes — `open` / `close`, and `reconcile` when the page rather than the user
14
+ moves committed state — mirror their value into hidden form fields that fire a
15
+ native `change`, and no longer write normalized values back to their Values.
16
+ Several event, detail, action, and Value names were unified, so read Changed and
17
+ Removed before upgrading. The Inspector manifest moves to schema v14, and
18
+ **`stimeo check` can report errors on views that passed under 0.15.0** (row
19
+ templates, action params, event names, submit-once's `idle` / `busy` pairs), so
20
+ run it before deploying.
21
+
22
+ ### Added
23
+
24
+ - accordion, collapsible, context-menu, dialog, drawer, dropdown, hover-card,
25
+ menu, menubar, navigation-menu, popover, read-more, sidebar, and tooltip:
26
+ `open` / `close` events whose `detail.reason` says why the state moved, typed
27
+ by the new `StateReason` type export. accordion also carries
28
+ `{ index, trigger, panel }`, menubar `{ index, menu }`, navigation-menu
29
+ `{ index, panel }`, and sidebar `{ mode }`.
30
+ - tabs: a `change` event with `{ index, total, previous }`.
31
+ - A `reconcile` event, once per batch and never on connect, when the page — code
32
+ writing a Value or an attribute, or a morph — rather than the user moves
33
+ committed state: calendar `{ date }`, currency-input `{ value, formatted }`,
34
+ data-grid `{ rows }`, date-range-picker `{ start, end }`, listbox
35
+ `{ value, option }`, pagination `{ page, total, previous }`, range-slider
36
+ `{ start, end }`, resizable `{ value, fraction }`, separator and slider
37
+ `{ value }`, stepper `{ index, previous, total, step }`, switch `{ checked }`,
38
+ theme `{ mode, resolved }`, toggle-group `{ values }`, and tree-view
39
+ `{ item }`. sidebar reports `reconcile` `{ mode, open }` when crossing its
40
+ breakpoint switches the mode.
41
+ - Hidden form fields that fire a native `change` on a user commit and stay
42
+ silent on connect, morphs, and Value writes: calendar `field` / `monthField`,
43
+ range-slider `startField` / `endField`, and a `field` on slider, switch
44
+ (`"true"` / `"false"`), and tree-view (the selected item's `data-value`).
45
+ toggle-group and data-grid mirror into a `fields` container with `name`
46
+ (`values[]` / `rows[]`) and `form` Values, data-grid from the selected rows'
47
+ `data-value`. tags-input gains the `form` Value multi-select already has.
48
+ - Label pairs the controller swaps with the state: `expandedLabel` /
49
+ `collapsedLabel` on accordion, collapsible, and read-more, and `onLabel` /
50
+ `offLabel` on carousel's play toggle and password-reveal. Regions it shows
51
+ only in their state: `empty` on combobox and multi-select, `hasNew` on
52
+ stick-to-bottom, and `prompt` / `idle` on idle, which also sets `data-prompt`
53
+ while the warning stands.
54
+ - listbox: a `placeholder` Value the trigger shows while nothing is selected.
55
+ - clipboard: `copy` carries `message`, the `copiedLabel` or `errorLabel` for the
56
+ outcome, so `stimeo--clipboard:copy->stimeo--toast#show` needs no script.
57
+ - stepper: `change` also carries `total`, so it wires straight into
58
+ step-indicator's `setIndex`.
59
+ - Inspector: `unknown-action-event`, `invalid-template-root`, and action-param
60
+ checks (`missing-action-param`, `invalid-action-param`, and the warning
61
+ `confusable-action-param`) for overflow-indicator's `direction`, stepper's
62
+ `index`, toast's `type`, and announcer's `assertive`.
63
+
64
+ ### Changed
65
+
66
+ - **Breaking** switch: the event is `change` (was `changed`).
67
+ - **Breaking** step-indicator: the `current` Value is `index`, `setCurrent` is
68
+ `setIndex` and reads `detail.index`, and `change` carries
69
+ `{ index, previous, total }`.
70
+ - **Breaking** toast: `body` → `message` (param, detail, and
71
+ `data-toast-slot`).
72
+ - **Breaking** toast, tags-input, multi-select, and file-dropzone: the row
73
+ `<template>` must hold exactly one element, and that element carries the
74
+ `item` / `tag` target itself. A wrapped row or a second element adds nothing;
75
+ tags-input, multi-select, and file-dropzone say so on the console once per
76
+ connection, and `stimeo check` reports `invalid-template-root`.
77
+ - **Breaking** detail keys: filter `visible` → `visibleCount`, overflow-menu
78
+ `{ visible, hidden }` → `{ overflowCount, total }`, portal `mount`
79
+ `target` → `destination`.
80
+ - **Breaking** slider, range-slider, separator, resizable, rating, color-picker,
81
+ stepper, pagination, bulk-select, carousel (`autoplay`), and calendar (`month`)
82
+ no longer write a normalized value back to their Values: the attribute keeps
83
+ what the page wrote, only a user move writes it, and a declared value takes
84
+ effect again once the bounds allow it. Read the published state
85
+ from ARIA, the hidden field, or the events. theme still writes a stored choice
86
+ back to `mode`.
87
+ - **Breaking** a page-driven move reports `reconcile`, not `change`:
88
+ currency-input on a `locale`, `currency`, or `precision` change, a swapped-in
89
+ display, or a reconnect that re-rounds; otp on a native form reset or a
90
+ `pattern` change that drops entered characters, with no `complete` either.
91
+ rating's `reconcile` reports every move of the shown rating the page causes, an
92
+ in-range `value` write included, and nothing for a clamp that leaves it where
93
+ it was.
94
+ - **Breaking** color-picker, date-range-picker, multi-select, rating, and
95
+ tags-input fire a native `change` on a user commit — from the hidden `field`,
96
+ or from the `fields` container on multi-select and tags-input — so a form with
97
+ `stimeo--auto-submit` or a `change` listener now reacts to it.
98
+ - **Breaking** number-input: a step from the buttons, the arrow and Page keys,
99
+ Home and End, or press-and-hold fires the native `input` and `change` a typed
100
+ value does, ahead of `stimeo--number-input:change`, so a form with
101
+ `stimeo--auto-submit` or a `change` listener now reacts to each step.
102
+ - **Breaking** calendar: without a `month`, the grid opens on the selected day's
103
+ month instead of today's and leaves `month` empty (read the shown month from
104
+ `monthField`). A `selected` day outside `min` / `max` is withheld — no
105
+ `aria-selected`, an empty `field` — and published again once the bounds allow
106
+ it; the `selected` Value is never rewritten. Focus inside the grid follows a
107
+ `month` change that moves the painted month to the new month's tab stop.
108
+ - **Breaking** date-range-picker: a confirmed range whose ends `min`, `max`, or
109
+ `disabledDates` exclude narrows to the nearest selectable days, or clears when
110
+ none remain, and reports `reconcile`; `connect()` narrows a range read from its
111
+ fields the same way, silently.
112
+ - **Breaking** flash and toast never evict a hovered or focused notification for
113
+ `max`: the stack stays over the cap until that hold ends, then the oldest
114
+ unheld ones go. toast evicts with reason `limit` (was `timeout`), and a `max`
115
+ of `0` or less, or not a finite number, means no limit (toast used to remove
116
+ every toast).
117
+ - **Breaking** carousel: the `autoplay` Value is no longer switched off at the
118
+ end of a non-looping set or under reduced motion. The declared rotation
119
+ resumes once a slide lies ahead again; reduced motion holds it until the user
120
+ presses play or the page changes `autoplay`.
121
+ - **Breaking** resizable and filter dispatch `change` only when the outcome
122
+ moved: resizable's position, filter's `{ active, visibleCount, total }`.
123
+ - **Breaking** slider, range-slider, and separator decide a user move's
124
+ `change` against the last published value; toggle-group, calendar, and
125
+ date-range-picker skip a `change` or `select` that a listener superseded.
126
+ - Components follow more runtime changes: calendar repaints on `min`, `max`, and
127
+ `weekStart`, and a consumer's `aria-disabled` moves with its date to the cell
128
+ that shows it; countdown follows `completeLabel`; clipboard rewrites a shown
129
+ result on `copiedLabel` / `errorLabel`; flash applies a changed `max` at once;
130
+ theme applies a `mode` declaration, reported as `reconcile`, while a stored
131
+ choice still wins; overflow-menu relabels its More trigger on `moreLabel`;
132
+ stepper re-derives every step when steps are added or removed.
133
+ - countdown: the completion text it wrote, marked
134
+ `data-stimeo--countdown-owns-status`, is taken back when the timer leaves
135
+ `complete`, and a `deadline` moved forward returns it to `paused`.
136
+ overflow-menu marks the More label it wrote the same way
137
+ (`data-stimeo--overflow-menu-owns-label`) and leaves alone a trigger the
138
+ consumer put elements or a name into.
139
+ - listbox: with nothing selected the trigger shows `placeholder`, else the text
140
+ it held before the first selection, instead of keeping the last selected
141
+ option's text.
142
+ - number-input: a text-type spinbutton reads full-width digits and signs
143
+ (`34`, `-5`) as numbers.
144
+ - color-picker: a repaint that does not move the color no longer overwrites text
145
+ being typed into the hex input.
146
+ - carousel: an in-page move keeps a running rotation without a new `play`, and a
147
+ real detach while the element stays in the document emits `pause`.
148
+ - Target attributes are read as a token list: calendar, date-range-picker,
149
+ flash, nested-form, and toast find a target that also carries other names, and
150
+ `stimeo check` resolves them the same way.
151
+ - currency-input, input-mask, number-input, and otp hold a page write that
152
+ arrives during IME composition until the composition ends.
153
+ - Inspector: submit-once's `idle` / `busy` pair is checked per submit control,
154
+ so a button holding one half is reported (`missing-conditional-target`) even
155
+ when another button holds both.
156
+
157
+ ### Removed
158
+
159
+ - **Breaking** overflow-indicator: the `update` action. The controller listens
160
+ to its viewport's `scroll` itself, and `stimeo check` reports a leftover
161
+ `scroll->stimeo--overflow-indicator#update` as `unknown-action-method`.
162
+ - **Breaking** calendar: the public `render()` method; the grid repaints itself
163
+ when `min`, `max`, or `weekStart` change.
164
+
165
+ ### Fixed
166
+
167
+ - A user-clicked form reset re-derives checkbox, conditional-fields, and otp
168
+ state.
169
+ - pointer-drag: a `setPointerCapture()` that throws no longer locks the handle
170
+ out of both pointer and keyboard dragging. slider, range-slider, and
171
+ color-picker: another element taking the pointer capture mid-drag no longer
172
+ ends the drag.
173
+ - otp: an edit that reaches a field right after an IME confirmation without a
174
+ keystroke — dictation, autofill, a drop — is taken instead of dropped.
175
+ - otp: the authored `aria-invalid`, `aria-errormessage`, and `aria-describedby`
176
+ come back even when a page cached while an error showed is restored under
177
+ another connection; while the error shows, each field carries a
178
+ `data-otp-*-lease` marker holding them.
179
+ - tags-input, multi-select, and file-dropzone: a row whose root is itself the
180
+ `remove` button or the `label` can be removed, focused, and relabelled.
181
+ - stepper: `goto` stays put on an `index` param that is empty or that Stimulus
182
+ reads as a boolean, `null`, or an array (`"true"`, `"null"`, `"[1]"`), instead
183
+ of jumping to a step; `stimeo check` already reports these params.
184
+ - The type declarations link `open`, `close`, `confirm`, and `scrollTo` to the
185
+ component's own method instead of the DOM global of the same name.
186
+
187
+ ## [0.15.0] - 2026-09-20
188
+
189
+ Minor release with no new components. Shared internals were consolidated across
190
+ five refactors; toast and the locale-aware components change behaviour, so read
191
+ Changed before upgrading. The Inspector manifest stays on schema v12.
192
+
193
+ ### Added
194
+
195
+ - calendar, date-range-picker: a `locale` Value.
196
+
197
+ ### Changed
198
+
199
+ - toast: a toast whose dismiss deadline has already lapsed is no longer removed
200
+ the moment a pointer or focus enters it. It stays, marked `data-paused="true"`,
201
+ and leaves 1ms after the last hold is released — so focus never drops to
202
+ `<body>` from under the close button it was on.
203
+ - calendar, date-range-picker: the month label resolves its locale as the
204
+ `locale` Value, then the nearest `lang` (the element itself included), then the
205
+ runtime default — instead of `<html lang>` with an `"en"` fallback.
206
+ - currency-input: `locale` no longer defaults to `"en-US"` and resolves the same
207
+ way, so a page under `<html lang="de">` reads and writes `1.234,56`. The hidden
208
+ value stays in its ASCII normal form, and a declaration `Intl` rejects still
209
+ falls back to `"en-US"`.
210
+ - auto-submit: the `stimeo--announcer:announce` it bridges carries
211
+ `assertive: false` and a trimmed `message`; a message of only whitespace is not
212
+ announced.
213
+
214
+ ### Fixed
215
+
216
+ - announcer: region timers are released on `disconnect()` and before a Turbo
217
+ snapshot, so an announcement made after reconnecting can no longer cancel the
218
+ auto-clear of a different live region.
219
+
10
220
  ## [0.14.0] - 2026-09-19
11
221
 
12
222
  Minor release with no new components. input-mask changes what a broken `tokens`
@@ -1068,6 +1278,8 @@ Initial public alpha: 101 behavior-only, accessible Stimulus controllers driven
1068
1278
  by `data-*` attributes, shipping no CSS. Published to npm (with provenance) and
1069
1279
  RubyGems.
1070
1280
 
1281
+ [0.16.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.16.0
1282
+ [0.15.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.15.0
1071
1283
  [0.14.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.14.0
1072
1284
  [0.13.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.13.0
1073
1285
  [0.12.0]: https://github.com/taiyaky/stimeo-ui/releases/tag/v0.12.0
data/README.md CHANGED
@@ -114,6 +114,126 @@ The `eslint-plugin-jsx-a11y` equivalents are
114
114
  `no-noninteractive-tabindex`. These components' real accessibility is exercised
115
115
  with axe-core and real screen readers in this project's own test suite.
116
116
 
117
+ ## Composing components
118
+
119
+ Parts dispatch `stimeo--<identifier>:<event>` with a `detail`, and Stimulus can bind
120
+ one part's event straight to another part's action. Wiring two parts is a
121
+ `data-action`, not a `<script>`.
122
+
123
+ ```html
124
+ <!-- Copy a link and say so, with nothing in between. -->
125
+ <div data-controller="stimeo--toast"
126
+ data-action="stimeo--clipboard:copy->stimeo--toast#show">
127
+ <div data-controller="stimeo--clipboard"
128
+ data-stimeo--clipboard-text-value="https://example.com/share"
129
+ data-stimeo--clipboard-copied-label-value="Copied"
130
+ data-stimeo--clipboard-error-label-value="Copy failed">
131
+ <button type="button" data-stimeo--clipboard-target="button"
132
+ data-action="click->stimeo--clipboard#copy">Copy</button>
133
+ </div>
134
+ <ol data-stimeo--toast-target="list"></ol>
135
+ <template data-stimeo--toast-target="template">
136
+ <li data-stimeo--toast-target="item"><span data-toast-slot="message"></span></li>
137
+ </template>
138
+ </div>
139
+ ```
140
+
141
+ ```html
142
+ <!-- A wizard moves a read-only progress indicator. -->
143
+ <div data-controller="stimeo--stepper">
144
+ <ol data-controller="stimeo--step-indicator"
145
+ data-action="stimeo--stepper:change@window->stimeo--step-indicator#setIndex">
146
+ <li data-stimeo--step-indicator-target="step">Cart</li>
147
+ <li data-stimeo--step-indicator-target="step">Shipping</li>
148
+ </ol>
149
+ <!-- the stepper's own step targets and buttons -->
150
+ </div>
151
+ ```
152
+
153
+ ```html
154
+ <!-- A value a widget stepped submits the form, through the events a browser
155
+ would have fired for its own control. -->
156
+ <form data-controller="stimeo--auto-submit"
157
+ data-action="change->stimeo--auto-submit#submit">
158
+ <div data-controller="stimeo--number-input">
159
+ <input type="number" name="quantity" value="1" aria-label="Quantity"
160
+ data-stimeo--number-input-target="input"
161
+ data-action="change->stimeo--number-input#onInput
162
+ keydown->stimeo--number-input#onKeydown" />
163
+ <button type="button" aria-label="Increase" tabindex="-1"
164
+ data-stimeo--number-input-target="increment"
165
+ data-action="click->stimeo--number-input#increment">+</button>
166
+ </div>
167
+ </form>
168
+ ```
169
+
170
+ Two things to know:
171
+
172
+ - **Events bubble from the part that dispatched them.** The receiver has to be an
173
+ ancestor. Anywhere else — a sibling, or a receiver nested *inside* the part that
174
+ dispatches — needs `@window`, because bubbling only ever goes up:
175
+ `stimeo--clipboard:copy@window->stimeo--toast#show`.
176
+ - **Do not say the same thing twice.** A toast's message slot is a `role="status"`
177
+ live region. Wiring the same result to the shared announcer as well
178
+ (`announce-copied-text`, …) reads it out twice. Pick one.
179
+
180
+ `stimeo check` verifies both halves of a wire: the controller and action on the
181
+ receiving side, and the event name on the emitting side.
182
+
183
+ ## Styling notes
184
+
185
+ ### `hidden` under a utility CSS layer
186
+
187
+ Controllers show and hide their own declared regions with the `hidden` attribute —
188
+ a listbox option filtered out, an "empty" row, the half of a label that does not
189
+ belong to the current state. `hidden` only carries `display: none` from the user-agent
190
+ stylesheet, so any rule that sets `display` wins over it.
191
+
192
+ Utility-first frameworks make that collision likely. Tailwind v4 declares
193
+ `@layer theme, base, components, utilities`, and DaisyUI puts component classes such
194
+ as `.menu li`, `.tabs`, and `.steps` in the **last** layer. A hidden element inside one
195
+ of them stays visible, and a `display: none` you add in `@layer components` still loses.
196
+
197
+ Put the override **outside every layer** — unlayered rules beat layered ones, so no
198
+ `!important` is needed:
199
+
200
+ ```css
201
+ /* not inside @layer */
202
+ .menu li[hidden],
203
+ .menu [role="option"][hidden] {
204
+ display: none;
205
+ }
206
+ ```
207
+
208
+ Most visible with `combobox`, `listbox`, `multi-select`, `empty-state`, `filter`, and
209
+ `command-palette`, whose options and empty rows are the ones being hidden.
210
+
211
+ ### State-dependent text belongs in markup
212
+
213
+ It is tempting to put the words that change with a state into CSS:
214
+
215
+ ```css
216
+ /* don't */
217
+ .play::after { content: "Auto-advance: off"; }
218
+ .play[aria-pressed="true"]::after { content: "Auto-advance: on"; }
219
+ ```
220
+
221
+ Text in `content` never reaches a translation catalogue, and the usual "find untranslated
222
+ strings" scan reads HTML, so it does not turn up there either. `content: attr(aria-valuetext)`
223
+ has the same problem from the other side: the attribute is composed at runtime in one
224
+ language. Keep the words in markup and let the controller show the half that applies:
225
+
226
+ ```html
227
+ <button data-stimeo--read-more-target="trigger" aria-expanded="false">
228
+ <span data-stimeo--read-more-target="collapsedLabel">Read more</span>
229
+ <span data-stimeo--read-more-target="expandedLabel" hidden>Show less</span>
230
+ </button>
231
+ ```
232
+
233
+ Controllers that read out a composed value take the wording as an attribute instead —
234
+ `color-picker` accepts `data-value-text`, and the announcing controllers accept
235
+ `announce-*-text`.
236
+
117
237
  ## Inspector CLI & MCP server
118
238
 
119
239
  Stimeo UI bundles a zero-dependency static checker for its own markup contract