stimeo-ui 0.2.0 → 0.3.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 (227) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/cable/index.d.ts +16 -16
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.d.ts +3 -2
  5. package/dist/controllers/accordion_controller.js +10 -0
  6. package/dist/controllers/accordion_controller.js.map +1 -1
  7. package/dist/controllers/alert_dialog_controller.d.ts +64 -0
  8. package/dist/controllers/alert_dialog_controller.js +318 -0
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -0
  10. package/dist/controllers/breadcrumb_controller.d.ts +97 -10
  11. package/dist/controllers/breadcrumb_controller.js +225 -13
  12. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  13. package/dist/controllers/calendar_controller.d.ts +11 -1
  14. package/dist/controllers/calendar_controller.js +89 -22
  15. package/dist/controllers/calendar_controller.js.map +1 -1
  16. package/dist/controllers/carousel_controller.d.ts +115 -0
  17. package/dist/controllers/carousel_controller.js +313 -0
  18. package/dist/controllers/carousel_controller.js.map +1 -0
  19. package/dist/controllers/clipboard_controller.d.ts +72 -0
  20. package/dist/controllers/clipboard_controller.js +144 -0
  21. package/dist/controllers/clipboard_controller.js.map +1 -0
  22. package/dist/controllers/collapsible_controller.d.ts +70 -0
  23. package/dist/controllers/collapsible_controller.js +327 -0
  24. package/dist/controllers/collapsible_controller.js.map +1 -0
  25. package/dist/controllers/color_picker_controller.d.ts +87 -0
  26. package/dist/controllers/color_picker_controller.js +252 -0
  27. package/dist/controllers/color_picker_controller.js.map +1 -0
  28. package/dist/controllers/combobox_controller.d.ts +14 -1
  29. package/dist/controllers/combobox_controller.js +162 -23
  30. package/dist/controllers/combobox_controller.js.map +1 -1
  31. package/dist/controllers/command_palette_controller.d.ts +2 -0
  32. package/dist/controllers/command_palette_controller.js +194 -17
  33. package/dist/controllers/command_palette_controller.js.map +1 -1
  34. package/dist/controllers/confirm_controller.js.map +1 -1
  35. package/dist/controllers/context_menu_controller.d.ts +7 -6
  36. package/dist/controllers/context_menu_controller.js +32 -10
  37. package/dist/controllers/context_menu_controller.js.map +1 -1
  38. package/dist/controllers/count_up_controller.js +8 -1
  39. package/dist/controllers/count_up_controller.js.map +1 -1
  40. package/dist/controllers/currency_input_controller.d.ts +72 -0
  41. package/dist/controllers/currency_input_controller.js +147 -0
  42. package/dist/controllers/currency_input_controller.js.map +1 -0
  43. package/dist/controllers/data_grid_controller.d.ts +89 -0
  44. package/dist/controllers/data_grid_controller.js +246 -0
  45. package/dist/controllers/data_grid_controller.js.map +1 -0
  46. package/dist/controllers/date_range_picker_controller.d.ts +88 -0
  47. package/dist/controllers/date_range_picker_controller.js +441 -0
  48. package/dist/controllers/date_range_picker_controller.js.map +1 -0
  49. package/dist/controllers/dialog_controller.js.map +1 -1
  50. package/dist/controllers/dismissible_controller.d.ts +57 -0
  51. package/dist/controllers/dismissible_controller.js +117 -0
  52. package/dist/controllers/dismissible_controller.js.map +1 -0
  53. package/dist/controllers/drawer_controller.d.ts +96 -0
  54. package/dist/controllers/drawer_controller.js +630 -0
  55. package/dist/controllers/drawer_controller.js.map +1 -0
  56. package/dist/controllers/dropdown_controller.d.ts +1 -2
  57. package/dist/controllers/dropdown_controller.js.map +1 -1
  58. package/dist/controllers/editable_controller.d.ts +73 -0
  59. package/dist/controllers/editable_controller.js +169 -0
  60. package/dist/controllers/editable_controller.js.map +1 -0
  61. package/dist/controllers/file_dropzone_controller.d.ts +92 -0
  62. package/dist/controllers/file_dropzone_controller.js +165 -0
  63. package/dist/controllers/file_dropzone_controller.js.map +1 -0
  64. package/dist/controllers/filter_controller.d.ts +60 -0
  65. package/dist/controllers/filter_controller.js +86 -0
  66. package/dist/controllers/filter_controller.js.map +1 -0
  67. package/dist/controllers/flash_controller.js +36 -5
  68. package/dist/controllers/flash_controller.js.map +1 -1
  69. package/dist/controllers/focus_controller.d.ts +1 -1
  70. package/dist/controllers/focus_controller.js.map +1 -1
  71. package/dist/controllers/form_field_controller.js.map +1 -1
  72. package/dist/controllers/form_validation_controller.js +1 -1
  73. package/dist/controllers/form_validation_controller.js.map +1 -1
  74. package/dist/controllers/highlight_controller.js +6 -4
  75. package/dist/controllers/highlight_controller.js.map +1 -1
  76. package/dist/controllers/hover_card_controller.d.ts +1 -1
  77. package/dist/controllers/hover_card_controller.js.map +1 -1
  78. package/dist/controllers/intersection_controller.js +67 -19
  79. package/dist/controllers/intersection_controller.js.map +1 -1
  80. package/dist/controllers/lazy_frame_controller.js +54 -11
  81. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  82. package/dist/controllers/listbox_controller.d.ts +9 -2
  83. package/dist/controllers/listbox_controller.js +257 -53
  84. package/dist/controllers/listbox_controller.js.map +1 -1
  85. package/dist/controllers/local_time_controller.js +2 -2
  86. package/dist/controllers/local_time_controller.js.map +1 -1
  87. package/dist/controllers/masonry_controller.d.ts +54 -0
  88. package/dist/controllers/masonry_controller.js +142 -0
  89. package/dist/controllers/masonry_controller.js.map +1 -0
  90. package/dist/controllers/menu_controller.d.ts +29 -11
  91. package/dist/controllers/menu_controller.js +104 -17
  92. package/dist/controllers/menu_controller.js.map +1 -1
  93. package/dist/controllers/menubar_controller.d.ts +142 -0
  94. package/dist/controllers/menubar_controller.js +785 -0
  95. package/dist/controllers/menubar_controller.js.map +1 -0
  96. package/dist/controllers/meter_controller.js.map +1 -1
  97. package/dist/controllers/multi_select_controller.d.ts +141 -0
  98. package/dist/controllers/multi_select_controller.js +755 -0
  99. package/dist/controllers/multi_select_controller.js.map +1 -0
  100. package/dist/controllers/navigation_menu_controller.d.ts +146 -0
  101. package/dist/controllers/navigation_menu_controller.js +511 -0
  102. package/dist/controllers/navigation_menu_controller.js.map +1 -0
  103. package/dist/controllers/nested_form_controller.d.ts +4 -5
  104. package/dist/controllers/nested_form_controller.js.map +1 -1
  105. package/dist/controllers/number_input_controller.d.ts +1 -1
  106. package/dist/controllers/number_input_controller.js +7 -0
  107. package/dist/controllers/number_input_controller.js.map +1 -1
  108. package/dist/controllers/otp_controller.js +18 -1
  109. package/dist/controllers/otp_controller.js.map +1 -1
  110. package/dist/controllers/overflow_indicator_controller.d.ts +15 -6
  111. package/dist/controllers/overflow_indicator_controller.js +246 -27
  112. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  113. package/dist/controllers/overflow_menu_controller.d.ts +78 -19
  114. package/dist/controllers/overflow_menu_controller.js +381 -57
  115. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  116. package/dist/controllers/pagination_controller.d.ts +36 -5
  117. package/dist/controllers/pagination_controller.js +163 -32
  118. package/dist/controllers/pagination_controller.js.map +1 -1
  119. package/dist/controllers/password_reveal_controller.d.ts +49 -0
  120. package/dist/controllers/password_reveal_controller.js +117 -0
  121. package/dist/controllers/password_reveal_controller.js.map +1 -0
  122. package/dist/controllers/persist_controller.js +6 -6
  123. package/dist/controllers/persist_controller.js.map +1 -1
  124. package/dist/controllers/pointer_drag_controller.d.ts +2 -3
  125. package/dist/controllers/pointer_drag_controller.js +9 -1
  126. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  127. package/dist/controllers/popover_controller.d.ts +1 -2
  128. package/dist/controllers/popover_controller.js +2 -2
  129. package/dist/controllers/popover_controller.js.map +1 -1
  130. package/dist/controllers/portal_controller.d.ts +4 -4
  131. package/dist/controllers/portal_controller.js.map +1 -1
  132. package/dist/controllers/progress_controller.js.map +1 -1
  133. package/dist/controllers/radio_group_controller.js +22 -3
  134. package/dist/controllers/radio_group_controller.js.map +1 -1
  135. package/dist/controllers/range_slider_controller.d.ts +97 -0
  136. package/dist/controllers/range_slider_controller.js +192 -0
  137. package/dist/controllers/range_slider_controller.js.map +1 -0
  138. package/dist/controllers/rating_controller.js +16 -2
  139. package/dist/controllers/rating_controller.js.map +1 -1
  140. package/dist/controllers/read_more_controller.d.ts +52 -0
  141. package/dist/controllers/read_more_controller.js +238 -0
  142. package/dist/controllers/read_more_controller.js.map +1 -0
  143. package/dist/controllers/reading_progress_controller.js.map +1 -1
  144. package/dist/controllers/resizable_controller.d.ts +3 -1
  145. package/dist/controllers/resizable_controller.js +65 -1
  146. package/dist/controllers/resizable_controller.js.map +1 -1
  147. package/dist/controllers/roving_controller.js +17 -2
  148. package/dist/controllers/roving_controller.js.map +1 -1
  149. package/dist/controllers/scroll_area_controller.js +101 -14
  150. package/dist/controllers/scroll_area_controller.js.map +1 -1
  151. package/dist/controllers/scroll_restore_controller.d.ts +54 -0
  152. package/dist/controllers/scroll_restore_controller.js +93 -0
  153. package/dist/controllers/scroll_restore_controller.js.map +1 -0
  154. package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
  155. package/dist/controllers/scroll_visibility_controller.js +40 -6
  156. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  157. package/dist/controllers/scrollspy_controller.d.ts +64 -15
  158. package/dist/controllers/scrollspy_controller.js +369 -74
  159. package/dist/controllers/scrollspy_controller.js.map +1 -1
  160. package/dist/controllers/separator_controller.d.ts +69 -0
  161. package/dist/controllers/separator_controller.js +96 -0
  162. package/dist/controllers/separator_controller.js.map +1 -0
  163. package/dist/controllers/sidebar_controller.d.ts +107 -0
  164. package/dist/controllers/sidebar_controller.js +761 -0
  165. package/dist/controllers/sidebar_controller.js.map +1 -0
  166. package/dist/controllers/skeleton_controller.js +1 -1
  167. package/dist/controllers/skeleton_controller.js.map +1 -1
  168. package/dist/controllers/slider_controller.d.ts +11 -1
  169. package/dist/controllers/slider_controller.js +32 -6
  170. package/dist/controllers/slider_controller.js.map +1 -1
  171. package/dist/controllers/sortable_controller.d.ts +3 -1
  172. package/dist/controllers/sortable_controller.js +34 -3
  173. package/dist/controllers/sortable_controller.js.map +1 -1
  174. package/dist/controllers/spinner_controller.js +1 -1
  175. package/dist/controllers/spinner_controller.js.map +1 -1
  176. package/dist/controllers/stepper_controller.d.ts +4 -0
  177. package/dist/controllers/stepper_controller.js +28 -12
  178. package/dist/controllers/stepper_controller.js.map +1 -1
  179. package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
  180. package/dist/controllers/stick_to_bottom_controller.js +9 -4
  181. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  182. package/dist/controllers/sticky_observer_controller.d.ts +17 -4
  183. package/dist/controllers/sticky_observer_controller.js +109 -20
  184. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  185. package/dist/controllers/submit_once_controller.d.ts +2 -2
  186. package/dist/controllers/submit_once_controller.js.map +1 -1
  187. package/dist/controllers/switch_controller.js +1 -0
  188. package/dist/controllers/switch_controller.js.map +1 -1
  189. package/dist/controllers/tabs_controller.d.ts +13 -4
  190. package/dist/controllers/tabs_controller.js +26 -3
  191. package/dist/controllers/tabs_controller.js.map +1 -1
  192. package/dist/controllers/tags_input_controller.d.ts +91 -0
  193. package/dist/controllers/tags_input_controller.js +295 -0
  194. package/dist/controllers/tags_input_controller.js.map +1 -0
  195. package/dist/controllers/theme_controller.d.ts +1 -1
  196. package/dist/controllers/theme_controller.js +42 -13
  197. package/dist/controllers/theme_controller.js.map +1 -1
  198. package/dist/controllers/time_picker_controller.d.ts +67 -0
  199. package/dist/controllers/time_picker_controller.js +231 -0
  200. package/dist/controllers/time_picker_controller.js.map +1 -0
  201. package/dist/controllers/toast_controller.d.ts +5 -14
  202. package/dist/controllers/toast_controller.js +40 -14
  203. package/dist/controllers/toast_controller.js.map +1 -1
  204. package/dist/controllers/toggle_group_controller.js +23 -2
  205. package/dist/controllers/toggle_group_controller.js.map +1 -1
  206. package/dist/controllers/toolbar_controller.d.ts +53 -13
  207. package/dist/controllers/toolbar_controller.js +230 -31
  208. package/dist/controllers/toolbar_controller.js.map +1 -1
  209. package/dist/controllers/tooltip_controller.d.ts +1 -1
  210. package/dist/controllers/tooltip_controller.js.map +1 -1
  211. package/dist/controllers/transition_controller.d.ts +14 -8
  212. package/dist/controllers/transition_controller.js +153 -38
  213. package/dist/controllers/transition_controller.js.map +1 -1
  214. package/dist/controllers/tree_view_controller.d.ts +135 -0
  215. package/dist/controllers/tree_view_controller.js +691 -0
  216. package/dist/controllers/tree_view_controller.js.map +1 -0
  217. package/dist/index.d.ts +27 -1818
  218. package/dist/index.js +4256 -915
  219. package/dist/index.js.map +1 -1
  220. package/dist/inspector/cli.d.ts +375 -39
  221. package/dist/inspector/cli.js +228 -14
  222. package/dist/inspector/cli.js.map +1 -1
  223. package/dist/inspector/cli_bin.js +419 -48
  224. package/dist/inspector/cli_bin.js.map +1 -1
  225. package/dist/inspector/examples.json +26 -26
  226. package/dist/inspector/manifest.json +1054 -127
  227. package/package.json +5 -6
@@ -2,10 +2,9 @@
2
2
  * Shared types for the Stimeo Inspector CLI (`stimeo check`).
3
3
  *
4
4
  * The Inspector statically checks HTML/ERB against a *manifest* describing the
5
- * official `stimeo--` controllers. The same engine powers both the project's own
6
- * contract checks (e.g. `stimeo check app/views`) and the
7
- * user-facing product feature; it is therefore intentionally input-path
8
- * agnostic.
5
+ * official `stimeo--` controllers. The same engine powers both the library's
6
+ * own contract checks (e.g. `stimeo check app/views`) and the user-facing CLI;
7
+ * it is therefore intentionally input-path agnostic.
9
8
  */
10
9
  /**
11
10
  * Per-controller manifest entry.
@@ -36,6 +35,11 @@ interface ControllerManifest {
36
35
  readonly events: readonly string[];
37
36
  /** Targets that must be present at least once inside the controller scope. */
38
37
  readonly requiredTargets: readonly string[];
38
+ /**
39
+ * Target requirements that only apply once an optional target appears. See
40
+ * {@link ConditionalTargetRule}.
41
+ */
42
+ readonly conditionalTargets: readonly ConditionalTargetRule[];
39
43
  /**
40
44
  * Accessibility requirements the *consumer's markup* must satisfy (stage 3):
41
45
  * ARIA attributes the controller does **not** set at runtime and therefore
@@ -45,8 +49,8 @@ interface ControllerManifest {
45
49
  */
46
50
  readonly a11y: readonly A11yRequirement[];
47
51
  /**
48
- * Keyboard prerequisites (stage 3, schema v4): targets whose documented
49
- * contract makes the *author* responsible for focusability — the controller
52
+ * Keyboard prerequisites (stage 3): targets whose documented contract makes
53
+ * the *author* responsible for focusability — the controller
50
54
  * moves focus (or relies on the Tab order) but never writes `tabindex` for
51
55
  * them. Each listed target must be natively focusable or carry `tabindex`.
52
56
  * Targets whose controller initializes a roving tabindex on connect (tabs,
@@ -54,8 +58,8 @@ interface ControllerManifest {
54
58
  */
55
59
  readonly keyboard: readonly KeyboardRequirement[];
56
60
  /**
57
- * Author-futile attributes (stage 3, schema v4): ARIA the controller
58
- * recomputes wholesale at runtime, where an authored value can neither
61
+ * Author-futile attributes (stage 3): ARIA the controller recomputes
62
+ * wholesale at runtime, where an authored value can neither
59
63
  * survive nor serve as pre-connect initial state (e.g. a combobox input's
60
64
  * `aria-activedescendant`). Authoring one is reported as a *warning*.
61
65
  * Attributes whose authored value is a legitimate initial (`aria-expanded`,
@@ -63,13 +67,146 @@ interface ControllerManifest {
63
67
  */
64
68
  readonly managedAria: readonly ManagedAriaRule[];
65
69
  /**
66
- * Conditional cross-controller composition rules (stage 3, schema v5):
67
- * value-alignment contracts between this controller and a co-located
68
- * companion (e.g. sortable's sort axis vs roving's `orientation`). Prose-only
69
- * before v5, where a dropped or misaligned value silently broke keyboard
70
- * interaction while `stimeo check` passed.
70
+ * Conditional cross-controller composition rules (stage 3): value-alignment
71
+ * contracts between this controller and a co-located companion (e.g.
72
+ * sortable's sort axis vs roving's `orientation`). A dropped or misaligned
73
+ * value silently breaks keyboard interaction, and no single-controller rule
74
+ * can see it.
71
75
  */
72
76
  readonly compositions: readonly CompositionRule[];
77
+ /**
78
+ * Required companion controllers (stage 3): elements the contract says must
79
+ * *also* declare another controller (e.g. an overflow menu's More
80
+ * wrapper, whose menu behavior is delegated wholesale). Distinct from
81
+ * {@link compositions}, which fire only once the companion is already there:
82
+ * these fire on its **absence**, the case a rule keyed on presence can never
83
+ * see.
84
+ */
85
+ readonly companions: readonly CompanionRequirement[];
86
+ /**
87
+ * Reverse-direction target rules (stage 3): markup that carries a pattern's
88
+ * ARIA but was never declared as a target. Every other rule reads
89
+ * target → required attribute; these read attribute → required target, the
90
+ * direction in which an undeclared element is invisible to the controller
91
+ * (and therefore to every forward rule).
92
+ */
93
+ readonly targetDeclarations: readonly TargetDeclarationRule[];
94
+ /**
95
+ * Set-level count constraints (stage 3): how many elements may or must exist
96
+ * inside a scope or a container target. Every other family judges one element
97
+ * at a time, which cannot express contracts of the form "exactly one" / "at
98
+ * most one" however load-bearing they are.
99
+ */
100
+ readonly cardinality: readonly CardinalityRule[];
101
+ /**
102
+ * Attributes that must **not** be present in a given configuration
103
+ * (stage 3). The inverse of {@link a11y}, and distinct from
104
+ * {@link managedAria}: those attributes are futile because the *controller*
105
+ * owns them, whereas these stay the author's to write — they are simply
106
+ * contradicted by the markup around them (a menu declaring itself busy while
107
+ * its items are right there).
108
+ */
109
+ readonly forbiddenAria: readonly ForbiddenAriaRule[];
110
+ }
111
+ /**
112
+ * A condition on the controller's **own** Stimulus value, read from the scope
113
+ * element. Shared by every rule family that needs one.
114
+ *
115
+ * The value is compared by its **effective** reading — the authored attribute,
116
+ * or {@link default} when the attribute is absent — so a rule can be scoped to
117
+ * a non-default configuration without flagging the default one. That is the
118
+ * whole point: a horizontal toolbar must *not* carry
119
+ * `aria-orientation="vertical"`, so the requirement only exists in the vertical
120
+ * configuration and an unconditional rule would reject correct markup.
121
+ *
122
+ * {@link default} duplicates the controller's `static values` default; a
123
+ * manifest test guards it against drift.
124
+ */
125
+ interface ValueCondition {
126
+ /** Value name (camelCase, as declared in `static values`). */
127
+ readonly value: string;
128
+ /** Effective values that arm the rule. */
129
+ readonly equals: readonly string[];
130
+ /** The value's declared default, used when the attribute is absent. */
131
+ readonly default: string;
132
+ }
133
+ /**
134
+ * A condition on how many targets an element **holds**, evaluated against the
135
+ * very element the rule applies to — not the controller scope, the way
136
+ * {@link ValueCondition} is. Both may appear on one rule; both must hold.
137
+ *
138
+ * It exists for contracts whose ARIA depends on the element's own contents
139
+ * rather than on any configuration value. A `role="menu"` requires owned
140
+ * `menuitem`s, so a menu the consumer fills asynchronously has to declare the
141
+ * temporary absence with `aria-busy` — a requirement that must arm on
142
+ * emptiness alone, since the controller cannot tell "still loading" from
143
+ * "nothing to show" and therefore never infers it.
144
+ *
145
+ * Bounds are inclusive and independently optional; a condition with neither is
146
+ * meaningless and rejected by the manifest tests.
147
+ */
148
+ interface ContentCondition {
149
+ /** Target name counted **inside** the element the rule applies to. */
150
+ readonly target: string;
151
+ /** Arms the rule when the count is at least this. */
152
+ readonly min?: number;
153
+ /** Arms the rule when the count is at most this. */
154
+ readonly max?: number;
155
+ }
156
+ /**
157
+ * A condition on the element's **own tag**, evaluated per element the way
158
+ * {@link ContentCondition} is.
159
+ *
160
+ * It exists for roles whose accessible name ARIA marks *required* but which a
161
+ * **native** element can name with no author ARIA at all: a
162
+ * `<table role="grid">` names from its `<caption>`, a
163
+ * `<fieldset role="radiogroup">` from its `<legend>`, an
164
+ * `<input role="combobox">` from a `<label for>`. Requiring `aria-label` on
165
+ * those would reject correct markup, while dropping the rule outright would
166
+ * leave the `div`-based spelling — the one with no native naming path at all —
167
+ * unchecked. This disarms exactly the spellings that have one.
168
+ *
169
+ * The exemption is keyed on the **tag**, not on finding the name: a
170
+ * `<label for>` legitimately lives in another partial, so looking for it would
171
+ * report a missing name that is present one file over. Erring toward
172
+ * under-detection is the same call the cardinality rules make for
173
+ * ERB-generated values.
174
+ */
175
+ interface ElementCondition {
176
+ /** Lowercase tag names whose native naming path disarms the requirement. */
177
+ readonly exceptTags: readonly string[];
178
+ }
179
+ /**
180
+ * A condition on the **whole source file**: how many elements in it carry a
181
+ * given role. Every other condition reads one element or one scope; this one
182
+ * reads the file.
183
+ *
184
+ * It exists for ARIA's *conditionally* levelled names, which come in both
185
+ * directions: a `toolbar`'s name is "Recommended" and becomes "Required if
186
+ * multiple toolbars on a page" ({@link A11yRequirement.escalateWhen}), while a
187
+ * focusable `separator`'s name is discretionary and only becomes "Recommended
188
+ * if more than one focusable separator" ({@link A11yRequirement.whenDocument}).
189
+ * One raises the level of a standing requirement; the other brings a
190
+ * requirement into existence.
191
+ *
192
+ * A file is not a page, so this deliberately **under**-approximates: elements
193
+ * split across partials are counted apart and stay at the lower level. That is
194
+ * the safe direction — it never invents a requirement the author does not have.
195
+ */
196
+ interface DocumentCondition {
197
+ /** Role counted across the file. */
198
+ readonly role: string;
199
+ /** Holds when at least this many elements in the file carry {@link role}. */
200
+ readonly atLeast: number;
201
+ /**
202
+ * Counts only Tab-reachable elements; omit to count every carrier of the
203
+ * role. ARIA qualifies some of these conditions by focusability and some not:
204
+ * a `separator`'s name matters once there is more than one **focusable**
205
+ * separator, because that is when a user can land on both and needs to tell
206
+ * them apart — a decorative `hr` carrying the role does not create that
207
+ * problem. A `toolbar`'s condition has no such qualifier, so it counts all.
208
+ */
209
+ readonly focusable?: boolean;
73
210
  }
74
211
  /**
75
212
  * A single accessibility requirement on a controller's markup (Inspector
@@ -77,7 +214,7 @@ interface ControllerManifest {
77
214
  * {@link target} element; when {@link values} is given, the present
78
215
  * attribute's value must be one of them. {@link or} widens the requirement
79
216
  * with alternative attribute/value groups — the requirement is satisfied when
80
- * *any* group is (schema v4; e.g. `role="status"` **or** `aria-live`).
217
+ * *any* group is (e.g. `role="status"` **or** `aria-live`).
81
218
  */
82
219
  interface A11yRequirement {
83
220
  /**
@@ -100,6 +237,55 @@ interface A11yRequirement {
100
237
  * its own right (e.g. `aria-live="off"` silencing a satisfied `role="status"`).
101
238
  */
102
239
  readonly or?: readonly A11yAlternative[];
240
+ /**
241
+ * Restricts the requirement to one configuration of the controller's own
242
+ * value; omit for unconditional requirements. It exists for ARIA that is
243
+ * mandatory in one configuration and *wrong* in another.
244
+ */
245
+ readonly when?: ValueCondition;
246
+ /**
247
+ * Restricts the requirement to elements holding a given number of a target;
248
+ * omit for requirements that do not depend on the contents. Evaluated per
249
+ * element, so sibling targets are judged independently.
250
+ */
251
+ readonly whenContains?: ContentCondition;
252
+ /**
253
+ * Disarms the requirement on elements whose tag already carries a native
254
+ * naming path; omit for requirements that hold for every spelling of the
255
+ * role.
256
+ */
257
+ readonly whenElement?: ElementCondition;
258
+ /**
259
+ * Arms the requirement only in files satisfying a file-level condition; omit
260
+ * for requirements that apply to every file.
261
+ *
262
+ * Some ARIA names are optional alone and only start to matter in company: a
263
+ * lone focusable `separator` needs no name, while a second one on the same
264
+ * page leaves the two indistinguishable without one. Arming on the count
265
+ * keeps the single-splitter page — by far the common one — silent, instead of
266
+ * demanding a name that would only ever be read out as noise.
267
+ */
268
+ readonly whenDocument?: DocumentCondition;
269
+ /**
270
+ * Severity of an unmet requirement; omit for `"error"`.
271
+ *
272
+ * ARIA separates names it *requires* from names it merely *recommends*
273
+ * (`toolbar`, `menubar`, `tablist`, `menu`). A missing recommended name is
274
+ * real contract guidance but not a definite defect — the pattern still works
275
+ * without it — so it reports as a warning, the bar `managed-aria` and
276
+ * `forbidden-aria` already sit at. Reporting both levels as errors would make
277
+ * the check say "broken" where ARIA says "could be clearer".
278
+ */
279
+ readonly severity?: DiagnosticSeverity;
280
+ /**
281
+ * Raises {@link severity} to `"error"` while the file-level condition holds;
282
+ * omit for requirements whose level never moves. ARIA's conditional
283
+ * requirements have exactly this shape — a toolbar's name is recommended on
284
+ * its own and required once the file holds a second one, because that is the
285
+ * point where the name stops being decoration and becomes the only way a user
286
+ * can tell the two apart.
287
+ */
288
+ readonly escalateWhen?: DocumentCondition;
103
289
  /** Human-readable fix suggestion shown by the CLI (stage 4). */
104
290
  readonly suggestion: string;
105
291
  }
@@ -111,9 +297,9 @@ interface A11yAlternative {
111
297
  readonly values?: readonly string[];
112
298
  }
113
299
  /**
114
- * A keyboard prerequisite (stage 3, schema v4): every present element of
115
- * {@link target} must be reachable by keyboard. What "reachable" means depends
116
- * on how the controller drives focus, expressed by {@link reach}:
300
+ * A keyboard prerequisite (stage 3): every present element of {@link target}
301
+ * must be reachable by keyboard. What "reachable" means depends on how the
302
+ * controller drives focus, expressed by {@link reach}:
117
303
  *
118
304
  * - `"tab"` (Tab stop): the element is a steady tab stop the user reaches with
119
305
  * Tab, so it must be natively tab-focusable (`button`, `input` except
@@ -143,10 +329,10 @@ interface KeyboardRequirement {
143
329
  readonly suggestion: string;
144
330
  }
145
331
  /**
146
- * An author-futile attribute rule (stage 3, schema v4): authoring any of
147
- * {@link attrs} on the {@link target} element draws a `managed-aria`
148
- * **warning** — the controller recomputes the attribute wholesale, so the
149
- * authored value is dead weight that misleads readers of the markup.
332
+ * An author-futile attribute rule (stage 3): authoring any of {@link attrs} on
333
+ * the {@link target} element draws a `managed-aria` **warning** — the
334
+ * controller recomputes the attribute wholesale, so the authored value is dead
335
+ * weight that misleads readers of the markup.
150
336
  */
151
337
  interface ManagedAriaRule {
152
338
  /**
@@ -160,8 +346,8 @@ interface ManagedAriaRule {
160
346
  readonly suggestion: string;
161
347
  }
162
348
  /**
163
- * A conditional cross-controller composition rule (stage 3, schema v5): when a
164
- * companion controller is co-located on one of the host's elements, one of the
349
+ * A conditional cross-controller composition rule (stage 3): when a companion
350
+ * controller is co-located on one of the host's elements, one of the
165
351
  * companion's values must align with a value of the host. The rule fires only
166
352
  * when {@link coController} is actually declared there — composition itself
167
353
  * stays optional (whether to compose is the author's call; the rule only
@@ -194,14 +380,7 @@ interface CompositionRule {
194
380
  * unconditional rules. The rule applies only when the host's effective
195
381
  * value is one of `equals`.
196
382
  */
197
- readonly when?: {
198
- /** Host value name (camelCase, as declared in `static values`). */
199
- readonly value: string;
200
- /** Effective host values that arm the rule. */
201
- readonly equals: readonly string[];
202
- /** The host value's declared default, used when the attribute is absent. */
203
- readonly default: string;
204
- };
383
+ readonly when?: ValueCondition;
205
384
  /** Requirement on the companion's value (read from the companion's element). */
206
385
  readonly require: {
207
386
  /** Companion value name (camelCase, as declared in `static values`). */
@@ -217,6 +396,138 @@ interface CompositionRule {
217
396
  /** Human-readable fix suggestion shown by the CLI (stage 4). */
218
397
  readonly suggestion: string;
219
398
  }
399
+ /**
400
+ * A required companion controller (stage 3): the {@link target} element must
401
+ * itself declare {@link controller} in its `data-controller`.
402
+ *
403
+ * This is the **absence** counterpart of {@link CompositionRule}. A composition
404
+ * rule guards *how* two controllers are wired once both are present, and stays
405
+ * silent when the companion was never added — correct for optional
406
+ * compositions, useless for the mandatory ones. Where the host delegates a
407
+ * whole interaction (a More wrapper whose menu semantics and keyboard handling
408
+ * belong entirely to the menu controller), dropping the companion leaves markup
409
+ * that renders, passes every forward rule, and simply never opens.
410
+ */
411
+ interface CompanionRequirement {
412
+ /**
413
+ * Target name whose element must declare the companion; the empty string
414
+ * `""` means the controller's own scope element.
415
+ */
416
+ readonly target: string;
417
+ /** Companion controller identifier required in the element's `data-controller`. */
418
+ readonly controller: string;
419
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
420
+ readonly suggestion: string;
421
+ }
422
+ /**
423
+ * A reverse-direction target rule (stage 3): inside the controller's scope,
424
+ * every element carrying {@link attr} (with one of {@link values}, when given)
425
+ * must be declared as the {@link target} target.
426
+ *
427
+ * Forward rules ask "does this target carry the ARIA it needs?" and therefore
428
+ * only ever see markup the controller already knows about. The failure this
429
+ * rule exists for is the opposite one: markup that *looks* like part of the
430
+ * pattern to a screen reader — it has the role — but was never wired as a
431
+ * target, so the controller's roving, visible-item search, typeahead, and
432
+ * selection sync all skip it. Nothing in the forward direction can detect that,
433
+ * because the element is absent from every target set the forward rules read.
434
+ *
435
+ * Ownership is resolved by the **nearest** enclosing controller of the same
436
+ * identifier, so nested instances judge only their own elements.
437
+ */
438
+ interface TargetDeclarationRule {
439
+ /** Attribute that marks an element as part of the pattern (e.g. `role`). */
440
+ readonly attr: string;
441
+ /** Attribute values that mark it; omit to match any non-empty value. */
442
+ readonly values?: readonly string[];
443
+ /** Target name the matched element must be declared as. */
444
+ readonly target: string;
445
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
446
+ readonly suggestion: string;
447
+ }
448
+ /**
449
+ * A set-level count constraint (stage 3): how many {@link target} elements may
450
+ * live inside {@link within}, optionally narrowed to those carrying
451
+ * {@link attr}.
452
+ *
453
+ * Every other rule family judges elements one at a time, which cannot express
454
+ * the two contracts that matter most in a set: "this wrapper resolves to
455
+ * exactly one control" and "no more than one element is selected". Both fail
456
+ * silently — a hover wrapper holding two triggers always opens the first, and a
457
+ * second authored selection is quietly normalized away at connect, discarding
458
+ * the author's intent with no diagnostic anywhere.
459
+ *
460
+ * Bounds are inclusive and independently optional; a rule with neither is
461
+ * meaningless and rejected by the manifest tests.
462
+ */
463
+ interface CardinalityRule {
464
+ /**
465
+ * Container target whose element bounds the count — each container element is
466
+ * counted separately. The empty string `""` counts across the whole
467
+ * controller scope.
468
+ */
469
+ readonly within: string;
470
+ /** Target name whose elements are counted. */
471
+ readonly target: string;
472
+ /** Count only elements carrying this attribute; omit to count them all. */
473
+ readonly attr?: string;
474
+ /** Restricts {@link attr} to these values; omit to accept any value. */
475
+ readonly values?: readonly string[];
476
+ /** Smallest permitted count (inclusive); omit for no floor. */
477
+ readonly min?: number;
478
+ /** Largest permitted count (inclusive); omit for no ceiling. */
479
+ readonly max?: number;
480
+ /**
481
+ * Restricts the constraint to one configuration of the controller's own
482
+ * value; omit for unconditional constraints. Multiplicity is frequently a
483
+ * *configured* property (a grid whose `selection` value decides whether two
484
+ * selected rows are a bug or the point), so a fixed bound would be wrong in
485
+ * one of the configurations.
486
+ */
487
+ readonly when?: ValueCondition;
488
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
489
+ readonly suggestion: string;
490
+ }
491
+ /**
492
+ * An attribute that must **not** be present in a given configuration
493
+ * (stage 3): authoring any of {@link attrs} on the {@link target} element —
494
+ * with one of {@link values}, when given — contradicts the markup around it.
495
+ *
496
+ * Three families speak about an attribute the author might write, and the
497
+ * difference is *why* it should go:
498
+ *
499
+ * - {@link A11yRequirement} — it is missing and the pattern needs it.
500
+ * - {@link ManagedAriaRule} — the **controller** recomputes it, so any authored
501
+ * value is dead weight in every state.
502
+ * - This rule — the author legitimately owns the attribute, and it is even
503
+ * required in the *other* configuration; here it simply states something the
504
+ * surrounding markup contradicts.
505
+ *
506
+ * Reported as a **warning**, not an error, and deliberately so: a static reader
507
+ * sees one file at one instant, and cannot separate a stale declaration from a
508
+ * genuine in-progress one (a menu whose items stream in a chunk at a time is
509
+ * correctly busy *with* items present). The markup may be lying; the page still
510
+ * works either way.
511
+ */
512
+ interface ForbiddenAriaRule {
513
+ /**
514
+ * Target name the rule applies to; the empty string `""` means the
515
+ * controller's own scope element.
516
+ */
517
+ readonly target: string;
518
+ /** Attribute names that must not be present. */
519
+ readonly attrs: readonly string[];
520
+ /** Restricts the rule to these values; omit to forbid the attribute outright. */
521
+ readonly values?: readonly string[];
522
+ /**
523
+ * Restricts the rule to elements holding a given number of a target. Without
524
+ * a condition the rule would forbid the attribute unconditionally, which is
525
+ * {@link ManagedAriaRule}'s job — so in practice every rule here carries one.
526
+ */
527
+ readonly whenContains?: ContentCondition;
528
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
529
+ readonly suggestion: string;
530
+ }
220
531
  /**
221
532
  * The bundled manifest. `schemaVersion` tracks the manifest *format*;
222
533
  * `packageVersion` tracks the `stimeo-ui` release it was generated from so a
@@ -228,6 +539,31 @@ interface Manifest {
228
539
  /** Keyed by controller identifier, e.g. `stimeo--menu`. */
229
540
  readonly controllers: Readonly<Record<string, ControllerManifest>>;
230
541
  }
542
+ /**
543
+ * A target requirement that only exists once another *optional* target is
544
+ * present.
545
+ *
546
+ * `requiredTargets` is unconditional, which cannot express the shape two
547
+ * controllers actually have: a feature that is entirely opt-in, but **incomplete
548
+ * without its whole set**. A breadcrumb without any `collapsible` is a valid plain
549
+ * trail; add one and the disclosure (`ellipsis` + `trigger`) becomes mandatory,
550
+ * because without it the collapsed items have no control that can reveal them. A
551
+ * file-dropzone without an `itemTemplate` never renders a list; add one without a
552
+ * `list` and the selected files render nowhere.
553
+ *
554
+ * Both fail the same way: the required targets are *present enough* to pass every
555
+ * other check, the page loads, `stimeo check` is green — and the feature silently
556
+ * does nothing. Making the trigger target unconditionally required is not an
557
+ * option: it would reject the plain spelling, which is the common one.
558
+ */
559
+ interface ConditionalTargetRule {
560
+ /** The optional target whose presence turns the feature on. */
561
+ readonly whenPresent: string;
562
+ /** Targets that become required once {@link whenPresent} appears. */
563
+ readonly require: readonly string[];
564
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
565
+ readonly suggestion: string;
566
+ }
231
567
  /** Severity of a diagnostic. Only `error` affects the process exit code. */
232
568
  type DiagnosticSeverity = "error" | "warning";
233
569
  /**
@@ -235,7 +571,7 @@ type DiagnosticSeverity = "error" | "warning";
235
571
  * for testing, machine-readable output, and `data-stimeo-ignore` suppression
236
572
  * lists (which is why the set also exists as a runtime array).
237
573
  */
238
- declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "unresolved-idref", "managed-aria", "composition-mismatch", "unknown-ignore-code"];
574
+ declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "unknown-ignore-code"];
239
575
  /** See {@link DIAGNOSTIC_CODES}. */
240
576
  type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
241
577
  /**
@@ -268,8 +604,8 @@ interface Diagnostic {
268
604
  * Length in source characters of the anchored token starting at `column` —
269
605
  * the attribute name for attribute-anchored diagnostics, or the opening
270
606
  * `<tag` for element-anchored ones. Lets editors underline the exact token
271
- * instead of guessing a word boundary. Optional so hand-built or historical
272
- * reports without it stay valid; consumers should fall back to `1`.
607
+ * instead of guessing a word boundary. Optional so hand-built reports without
608
+ * it stay valid; consumers should fall back to `1`.
273
609
  */
274
610
  readonly length?: number;
275
611
  /**
@@ -288,8 +624,8 @@ interface FileReport {
288
624
  /**
289
625
  * Machine-readable result of `stimeo check --json`: the structured counterpart
290
626
  * of the human report, for editor tooling and CI. `files` lists only sources
291
- * that produced diagnostics; `ok` is
292
- * true when no error-severity diagnostic was found (mirrors the exit code).
627
+ * that produced diagnostics; `ok` is true when no error-severity diagnostic was
628
+ * found (mirrors the exit code).
293
629
  */
294
630
  interface CheckReport {
295
631
  /** True when no error-severity diagnostics were found. */
@@ -304,11 +640,11 @@ interface CheckReport {
304
640
  readonly files: readonly FileReport[];
305
641
  }
306
642
 
307
- /** One bundled example: the demo markup shown verbatim in the catalog. */
643
+ /** One bundled example: the demo markup shipped for a single controller. */
308
644
  interface ExampleEntry {
309
645
  /**
310
- * Repo-relative provenance: `playground/.../demos/menu/_demo.html.erb` in
311
- * the dev monorepo, `examples/menu/_demo.html.erb` in the public mirror.
646
+ * Repo-relative path the example was read from, e.g.
647
+ * `examples/menu/_demo.html.erb`.
312
648
  */
313
649
  readonly file: string;
314
650
  /** The HTML/ERB source. */