stimeo-ui 0.5.0 → 0.7.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 (230) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/controllers/alert_dialog_controller.d.ts +2 -0
  3. package/dist/controllers/alert_dialog_controller.js +75 -4
  4. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  5. package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
  6. package/dist/controllers/aspect_ratio_controller.js +19 -11
  7. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  8. package/dist/controllers/auto_submit_controller.d.ts +2 -0
  9. package/dist/controllers/auto_submit_controller.js.map +1 -1
  10. package/dist/controllers/avatar_controller.d.ts +36 -15
  11. package/dist/controllers/avatar_controller.js +237 -40
  12. package/dist/controllers/avatar_controller.js.map +1 -1
  13. package/dist/controllers/breadcrumb_controller.d.ts +2 -0
  14. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  15. package/dist/controllers/bulk_select_controller.d.ts +2 -0
  16. package/dist/controllers/bulk_select_controller.js.map +1 -1
  17. package/dist/controllers/calendar_controller.d.ts +2 -0
  18. package/dist/controllers/calendar_controller.js.map +1 -1
  19. package/dist/controllers/carousel_controller.d.ts +12 -3
  20. package/dist/controllers/carousel_controller.js +85 -9
  21. package/dist/controllers/carousel_controller.js.map +1 -1
  22. package/dist/controllers/character_counter_controller.d.ts +52 -20
  23. package/dist/controllers/character_counter_controller.js +338 -63
  24. package/dist/controllers/character_counter_controller.js.map +1 -1
  25. package/dist/controllers/checkbox_controller.d.ts +34 -5
  26. package/dist/controllers/checkbox_controller.js +136 -25
  27. package/dist/controllers/checkbox_controller.js.map +1 -1
  28. package/dist/controllers/color_picker_controller.d.ts +10 -1
  29. package/dist/controllers/color_picker_controller.js +35 -9
  30. package/dist/controllers/color_picker_controller.js.map +1 -1
  31. package/dist/controllers/combobox_controller.d.ts +2 -0
  32. package/dist/controllers/combobox_controller.js.map +1 -1
  33. package/dist/controllers/command_palette_controller.d.ts +2 -0
  34. package/dist/controllers/command_palette_controller.js +75 -4
  35. package/dist/controllers/command_palette_controller.js.map +1 -1
  36. package/dist/controllers/conditional_fields_controller.d.ts +42 -14
  37. package/dist/controllers/conditional_fields_controller.js +345 -51
  38. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  39. package/dist/controllers/confirm_controller.d.ts +2 -0
  40. package/dist/controllers/confirm_controller.js +75 -4
  41. package/dist/controllers/confirm_controller.js.map +1 -1
  42. package/dist/controllers/count_up_controller.d.ts +2 -0
  43. package/dist/controllers/count_up_controller.js.map +1 -1
  44. package/dist/controllers/countdown_controller.d.ts +4 -0
  45. package/dist/controllers/countdown_controller.js.map +1 -1
  46. package/dist/controllers/currency_input_controller.d.ts +2 -0
  47. package/dist/controllers/currency_input_controller.js.map +1 -1
  48. package/dist/controllers/data_grid_controller.d.ts +3 -0
  49. package/dist/controllers/data_grid_controller.js.map +1 -1
  50. package/dist/controllers/date_range_picker_controller.d.ts +23 -4
  51. package/dist/controllers/date_range_picker_controller.js +157 -30
  52. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  53. package/dist/controllers/dialog_controller.js +75 -4
  54. package/dist/controllers/dialog_controller.js.map +1 -1
  55. package/dist/controllers/direct_upload_controller.d.ts +56 -24
  56. package/dist/controllers/direct_upload_controller.js +201 -45
  57. package/dist/controllers/direct_upload_controller.js.map +1 -1
  58. package/dist/controllers/dirty_form_controller.d.ts +14 -6
  59. package/dist/controllers/dirty_form_controller.js +192 -29
  60. package/dist/controllers/dirty_form_controller.js.map +1 -1
  61. package/dist/controllers/dismissible_controller.d.ts +2 -0
  62. package/dist/controllers/dismissible_controller.js +83 -18
  63. package/dist/controllers/dismissible_controller.js.map +1 -1
  64. package/dist/controllers/drawer_controller.js +75 -4
  65. package/dist/controllers/drawer_controller.js.map +1 -1
  66. package/dist/controllers/empty_state_controller.d.ts +2 -0
  67. package/dist/controllers/empty_state_controller.js.map +1 -1
  68. package/dist/controllers/file_dropzone_controller.d.ts +9 -1
  69. package/dist/controllers/file_dropzone_controller.js +26 -3
  70. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  71. package/dist/controllers/filter_controller.d.ts +2 -0
  72. package/dist/controllers/filter_controller.js.map +1 -1
  73. package/dist/controllers/flash_controller.d.ts +4 -0
  74. package/dist/controllers/flash_controller.js.map +1 -1
  75. package/dist/controllers/focus_controller.d.ts +4 -3
  76. package/dist/controllers/focus_controller.js +75 -4
  77. package/dist/controllers/focus_controller.js.map +1 -1
  78. package/dist/controllers/form_field_controller.d.ts +50 -10
  79. package/dist/controllers/form_field_controller.js +280 -62
  80. package/dist/controllers/form_field_controller.js.map +1 -1
  81. package/dist/controllers/form_validation_controller.d.ts +10 -8
  82. package/dist/controllers/form_validation_controller.js +208 -83
  83. package/dist/controllers/form_validation_controller.js.map +1 -1
  84. package/dist/controllers/frame_loading_controller.d.ts +2 -0
  85. package/dist/controllers/frame_loading_controller.js.map +1 -1
  86. package/dist/controllers/highlight_controller.d.ts +2 -0
  87. package/dist/controllers/highlight_controller.js.map +1 -1
  88. package/dist/controllers/hover_card_controller.d.ts +2 -2
  89. package/dist/controllers/hover_card_controller.js.map +1 -1
  90. package/dist/controllers/idle_controller.d.ts +5 -3
  91. package/dist/controllers/idle_controller.js +27 -5
  92. package/dist/controllers/idle_controller.js.map +1 -1
  93. package/dist/controllers/input_mask_controller.d.ts +2 -0
  94. package/dist/controllers/input_mask_controller.js.map +1 -1
  95. package/dist/controllers/lazy_frame_controller.d.ts +2 -0
  96. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  97. package/dist/controllers/listbox_controller.d.ts +2 -0
  98. package/dist/controllers/listbox_controller.js.map +1 -1
  99. package/dist/controllers/local_time_controller.d.ts +2 -0
  100. package/dist/controllers/local_time_controller.js.map +1 -1
  101. package/dist/controllers/masonry_controller.d.ts +2 -0
  102. package/dist/controllers/masonry_controller.js.map +1 -1
  103. package/dist/controllers/menubar_controller.js +5 -3
  104. package/dist/controllers/menubar_controller.js.map +1 -1
  105. package/dist/controllers/meter_controller.d.ts +2 -0
  106. package/dist/controllers/meter_controller.js.map +1 -1
  107. package/dist/controllers/multi_select_controller.d.ts +48 -12
  108. package/dist/controllers/multi_select_controller.js +460 -151
  109. package/dist/controllers/multi_select_controller.js.map +1 -1
  110. package/dist/controllers/nested_form_controller.d.ts +2 -0
  111. package/dist/controllers/nested_form_controller.js.map +1 -1
  112. package/dist/controllers/network_status_controller.d.ts +2 -0
  113. package/dist/controllers/network_status_controller.js.map +1 -1
  114. package/dist/controllers/number_input_controller.d.ts +26 -6
  115. package/dist/controllers/number_input_controller.js +317 -51
  116. package/dist/controllers/number_input_controller.js.map +1 -1
  117. package/dist/controllers/otp_controller.d.ts +3 -0
  118. package/dist/controllers/otp_controller.js.map +1 -1
  119. package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
  120. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  121. package/dist/controllers/overflow_menu_controller.d.ts +2 -0
  122. package/dist/controllers/overflow_menu_controller.js +6 -1
  123. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  124. package/dist/controllers/pagination_controller.d.ts +2 -0
  125. package/dist/controllers/pagination_controller.js +35 -1
  126. package/dist/controllers/pagination_controller.js.map +1 -1
  127. package/dist/controllers/password_reveal_controller.d.ts +2 -0
  128. package/dist/controllers/password_reveal_controller.js.map +1 -1
  129. package/dist/controllers/password_strength_controller.d.ts +5 -3
  130. package/dist/controllers/password_strength_controller.js +20 -2
  131. package/dist/controllers/password_strength_controller.js.map +1 -1
  132. package/dist/controllers/persist_controller.d.ts +33 -19
  133. package/dist/controllers/persist_controller.js +449 -120
  134. package/dist/controllers/persist_controller.js.map +1 -1
  135. package/dist/controllers/pointer_drag_controller.d.ts +3 -0
  136. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  137. package/dist/controllers/popover_controller.d.ts +2 -2
  138. package/dist/controllers/popover_controller.js +77 -4
  139. package/dist/controllers/popover_controller.js.map +1 -1
  140. package/dist/controllers/portal_controller.d.ts +3 -2
  141. package/dist/controllers/portal_controller.js.map +1 -1
  142. package/dist/controllers/preview_guard_controller.d.ts +2 -0
  143. package/dist/controllers/preview_guard_controller.js.map +1 -1
  144. package/dist/controllers/progress_controller.d.ts +3 -0
  145. package/dist/controllers/progress_controller.js.map +1 -1
  146. package/dist/controllers/radio_group_controller.d.ts +44 -15
  147. package/dist/controllers/radio_group_controller.js +540 -56
  148. package/dist/controllers/radio_group_controller.js.map +1 -1
  149. package/dist/controllers/rating_controller.d.ts +38 -31
  150. package/dist/controllers/rating_controller.js +276 -89
  151. package/dist/controllers/rating_controller.js.map +1 -1
  152. package/dist/controllers/reading_progress_controller.d.ts +2 -0
  153. package/dist/controllers/reading_progress_controller.js.map +1 -1
  154. package/dist/controllers/resizable_controller.d.ts +2 -0
  155. package/dist/controllers/resizable_controller.js +33 -0
  156. package/dist/controllers/resizable_controller.js.map +1 -1
  157. package/dist/controllers/roving_controller.d.ts +6 -0
  158. package/dist/controllers/roving_controller.js +60 -5
  159. package/dist/controllers/roving_controller.js.map +1 -1
  160. package/dist/controllers/scroll_area_controller.d.ts +25 -11
  161. package/dist/controllers/scroll_area_controller.js +557 -125
  162. package/dist/controllers/scroll_area_controller.js.map +1 -1
  163. package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
  164. package/dist/controllers/scroll_visibility_controller.js +33 -0
  165. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  166. package/dist/controllers/scrollspy_controller.d.ts +2 -0
  167. package/dist/controllers/scrollspy_controller.js.map +1 -1
  168. package/dist/controllers/separator_controller.d.ts +54 -11
  169. package/dist/controllers/separator_controller.js +354 -38
  170. package/dist/controllers/separator_controller.js.map +1 -1
  171. package/dist/controllers/sidebar_controller.js +83 -10
  172. package/dist/controllers/sidebar_controller.js.map +1 -1
  173. package/dist/controllers/skeleton_controller.d.ts +2 -0
  174. package/dist/controllers/skeleton_controller.js.map +1 -1
  175. package/dist/controllers/slider_controller.d.ts +2 -0
  176. package/dist/controllers/slider_controller.js.map +1 -1
  177. package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
  178. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  179. package/dist/controllers/spinner_controller.d.ts +2 -0
  180. package/dist/controllers/spinner_controller.js.map +1 -1
  181. package/dist/controllers/step_indicator_controller.d.ts +2 -0
  182. package/dist/controllers/step_indicator_controller.js.map +1 -1
  183. package/dist/controllers/stepper_controller.d.ts +2 -0
  184. package/dist/controllers/stepper_controller.js.map +1 -1
  185. package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
  186. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  187. package/dist/controllers/sticky_observer_controller.d.ts +2 -0
  188. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  189. package/dist/controllers/submit_once_controller.d.ts +94 -38
  190. package/dist/controllers/submit_once_controller.js +399 -121
  191. package/dist/controllers/submit_once_controller.js.map +1 -1
  192. package/dist/controllers/switch_controller.d.ts +2 -0
  193. package/dist/controllers/switch_controller.js.map +1 -1
  194. package/dist/controllers/tags_input_controller.d.ts +43 -11
  195. package/dist/controllers/tags_input_controller.js +356 -120
  196. package/dist/controllers/tags_input_controller.js.map +1 -1
  197. package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
  198. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  199. package/dist/controllers/theme_controller.d.ts +2 -0
  200. package/dist/controllers/theme_controller.js +8 -6
  201. package/dist/controllers/theme_controller.js.map +1 -1
  202. package/dist/controllers/time_picker_controller.d.ts +44 -13
  203. package/dist/controllers/time_picker_controller.js +296 -107
  204. package/dist/controllers/time_picker_controller.js.map +1 -1
  205. package/dist/controllers/toast_controller.d.ts +4 -0
  206. package/dist/controllers/toast_controller.js.map +1 -1
  207. package/dist/controllers/toggle_group_controller.d.ts +41 -13
  208. package/dist/controllers/toggle_group_controller.js +378 -55
  209. package/dist/controllers/toggle_group_controller.js.map +1 -1
  210. package/dist/controllers/toolbar_controller.js +5 -3
  211. package/dist/controllers/toolbar_controller.js.map +1 -1
  212. package/dist/controllers/tooltip_controller.d.ts +2 -2
  213. package/dist/controllers/tooltip_controller.js.map +1 -1
  214. package/dist/controllers/transition_controller.d.ts +4 -3
  215. package/dist/controllers/transition_controller.js.map +1 -1
  216. package/dist/controllers/tree_view_controller.d.ts +2 -0
  217. package/dist/controllers/tree_view_controller.js +7 -4
  218. package/dist/controllers/tree_view_controller.js.map +1 -1
  219. package/dist/index.js +4963 -1581
  220. package/dist/index.js.map +1 -1
  221. package/dist/inspector/cli.d.ts +123 -6
  222. package/dist/inspector/cli.js +212 -18
  223. package/dist/inspector/cli.js.map +1 -1
  224. package/dist/inspector/cli_bin.js +274 -50
  225. package/dist/inspector/cli_bin.js.map +1 -1
  226. package/dist/inspector/examples.json +15 -15
  227. package/dist/inspector/manifest.json +602 -80
  228. package/dist/positioning/index.d.ts +4 -2
  229. package/dist/positioning/index.js.map +1 -1
  230. package/package.json +1 -1
@@ -48,6 +48,16 @@ interface ControllerManifest {
48
48
  * {@link ConditionalTargetRule}.
49
49
  */
50
50
  readonly conditionalTargets: readonly ConditionalTargetRule[];
51
+ /**
52
+ * Actions required on a particular element in one Value configuration. See
53
+ * {@link RequiredActionRule}.
54
+ */
55
+ readonly requiredActions: readonly RequiredActionRule[];
56
+ /**
57
+ * States an action opens that the consumer's wiring has to close. See
58
+ * {@link ActionCompletionRule}.
59
+ */
60
+ readonly actionCompletion: readonly ActionCompletionRule[];
51
61
  /**
52
62
  * Accessibility requirements the *consumer's markup* must satisfy (stage 3):
53
63
  * ARIA attributes the controller does **not** set at runtime and therefore
@@ -140,6 +150,8 @@ interface ControllerManifest {
140
150
  interface ValueCondition {
141
151
  /** Value name (camelCase, as declared in `static values`). */
142
152
  readonly value: string;
153
+ /** Stimulus decoder used before comparing the effective value. */
154
+ readonly type: "boolean" | "string";
143
155
  /** Effective values that arm the rule. */
144
156
  readonly equals: readonly string[];
145
157
  /** The value's declared default, used when the attribute is absent. */
@@ -227,9 +239,11 @@ interface DocumentCondition {
227
239
  * A single accessibility requirement on a controller's markup (Inspector
228
240
  * stage 3). At least one of {@link attrs} must be present on the
229
241
  * {@link target} element; when {@link values} is given, the present
230
- * attribute's value must be one of them. {@link or} widens the requirement
231
- * with alternative attribute/value groups — the requirement is satisfied when
232
- * *any* group is (e.g. `role="status"` **or** `aria-live`).
242
+ * attribute's value must be one of them, and when it is not, the value must be
243
+ * non-empty — ARIA derives no name from `aria-label=""`, so an empty value
244
+ * leaves the requirement unmet. {@link or} widens the requirement with
245
+ * alternative attribute/value groups — the requirement is satisfied when *any*
246
+ * group is (e.g. `role="status"` **or** `aria-live`).
233
247
  */
234
248
  interface A11yRequirement {
235
249
  /**
@@ -597,6 +611,94 @@ interface ConditionalTargetRule {
597
611
  readonly whenPresent: string;
598
612
  /** Targets that become required once {@link whenPresent} appears. */
599
613
  readonly require: readonly string[];
614
+ /**
615
+ * Require each {@link require} entry to sit **inside** the `whenPresent`
616
+ * element rather than anywhere in the scope.
617
+ *
618
+ * A `<template>` is cloned at runtime, so a part declared outside it never
619
+ * reaches the clone. Without this the presence check passes on markup the
620
+ * controller then refuses to build from.
621
+ */
622
+ readonly requireInside?: boolean;
623
+ /**
624
+ * Require each {@link require} entry to share its nearest enclosing host —
625
+ * the closest ancestor matching one of these selectors — with the
626
+ * `whenPresent` element.
627
+ *
628
+ * Neither presence nor {@link requireInside} can express a pair that must sit
629
+ * in the *same* container without being nested in one another. A controller
630
+ * that swaps one host's two halves reads them per host, so halves split
631
+ * across two hosts leave each host with an incomplete pair, and the swap
632
+ * silently does nothing on markup where both names are present.
633
+ *
634
+ * Only a pair whose halves both resolve to a host is judged. A template
635
+ * helper that emits the host leaves nothing in the parsed markup, so an
636
+ * element with no host above it is indistinguishable from one whose host is
637
+ * generated — reporting either would be a guess, and the check stays quiet.
638
+ */
639
+ readonly requireSameHost?: readonly HostSelector[];
640
+ /**
641
+ * What to call the shared host in the diagnostic (e.g. `submit control`).
642
+ * The selectors' tag names read as a disjunction of element names, which
643
+ * names the mechanism rather than the thing the author is looking at.
644
+ */
645
+ readonly hostLabel?: string;
646
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
647
+ readonly suggestion: string;
648
+ }
649
+ /**
650
+ * One element shape that can act as a host for {@link
651
+ * ConditionalTargetRule.requireSameHost}.
652
+ *
653
+ * A tag alone is too coarse where the element's `type` decides its role, so an
654
+ * optional attribute filter narrows it. Omitting {@link attr} accepts the tag
655
+ * however it is spelled.
656
+ */
657
+ interface HostSelector {
658
+ /** Lowercased tag name. */
659
+ readonly tag: string;
660
+ /** Attribute that must carry one of {@link values} for the tag to qualify. */
661
+ readonly attr?: string;
662
+ /** Accepted lowercased values of {@link attr}. */
663
+ readonly values?: readonly string[];
664
+ }
665
+ /**
666
+ * An action that puts a controller into a state it cannot leave on its own.
667
+ *
668
+ * A controller whose busy state is opened declaratively has no way to know the
669
+ * consumer never wired the matching exit: nothing throws, the markup is
670
+ * well-formed, and the only symptom is a control that stays disabled for the
671
+ * rest of the page's life. The check reads the wiring the author did declare
672
+ * and says so before it ships.
673
+ */
674
+ interface ActionCompletionRule {
675
+ /** Action that opens the state. */
676
+ readonly opens: string;
677
+ /** Any one of these actions closes it again. */
678
+ readonly closedBy: readonly string[];
679
+ /** Event types for {@link opens} that the controller cannot complete itself. */
680
+ readonly whenTriggeredBy: readonly string[];
681
+ /** Value whose non-zero number supplies an automatic exit instead. */
682
+ readonly escapeValue?: string;
683
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
684
+ readonly suggestion: string;
685
+ }
686
+ /**
687
+ * An action binding that must exist for one configured behavior to be usable.
688
+ *
689
+ * Declaring an action method on the controller only proves the method exists;
690
+ * Stimulus still needs the consumer to wire it. Optional interactive variants
691
+ * can otherwise enter the Tab order while every key silently does nothing.
692
+ */
693
+ interface RequiredActionRule {
694
+ /** Target whose element must carry the action; `""` means the scope element. */
695
+ readonly target: string;
696
+ /** Public controller action method that must be wired. */
697
+ readonly action: string;
698
+ /** Event types that make the binding usable for this contract. */
699
+ readonly eventTypes: readonly string[];
700
+ /** Configuration in which the binding becomes mandatory. */
701
+ readonly when?: ValueCondition;
600
702
  /** Human-readable fix suggestion shown by the CLI (stage 4). */
601
703
  readonly suggestion: string;
602
704
  }
@@ -607,18 +709,33 @@ interface ConditionalTargetRule {
607
709
  * ""))`) before applying these bounds. ERB-generated values are undecidable and
608
710
  * skipped rather than guessed.
609
711
  */
610
- interface ValueConstraint {
712
+ interface NumericValueConstraint {
611
713
  /** Value name (camelCase, as declared in `static values`). */
612
714
  readonly value: string;
613
- /** Decoder family. Numeric bounds are the first supported semantic family. */
715
+ /** Decoder family. */
614
716
  readonly type: "number";
615
717
  /** Reject `NaN` and infinities when true. */
616
718
  readonly finite?: boolean;
617
719
  /** Require a decoded number strictly greater than this bound. */
618
720
  readonly greaterThan?: number;
721
+ /** Require a decoded number with no fractional part. */
722
+ readonly integer?: boolean;
723
+ /** Human-readable fix suggestion shown by the CLI (stage 4). */
724
+ readonly suggestion: string;
725
+ }
726
+ /** A statically checkable finite set of accepted String Value literals. */
727
+ interface StringValueConstraint {
728
+ /** Value name (camelCase, as declared in `static values`). */
729
+ readonly value: string;
730
+ /** Decoder family. */
731
+ readonly type: "string";
732
+ /** Exact authored literals accepted by the public contract. */
733
+ readonly allowedValues: readonly string[];
619
734
  /** Human-readable fix suggestion shown by the CLI (stage 4). */
620
735
  readonly suggestion: string;
621
736
  }
737
+ /** A statically checkable semantic contract on one declared Stimulus Value. */
738
+ type ValueConstraint = NumericValueConstraint | StringValueConstraint;
622
739
  /**
623
740
  * A statically checkable relationship between two numeric Stimulus Values.
624
741
  *
@@ -646,7 +763,7 @@ type DiagnosticSeverity = "error" | "warning";
646
763
  * for testing, machine-readable output, and `data-stimeo-ignore` suppression
647
764
  * lists (which is why the set also exists as a runtime array).
648
765
  */
649
- declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "invalid-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "unknown-ignore-code"];
766
+ declare const DIAGNOSTIC_CODES: readonly ["unknown-controller", "unknown-target", "unknown-value", "invalid-value", "unknown-action-controller", "unknown-action-method", "orphan-target", "missing-required-target", "missing-conditional-target", "missing-required-action", "missing-action-completion", "missing-aria", "invalid-aria-value", "keyboard-inaccessible", "invalid-host", "unresolved-idref", "managed-aria", "composition-mismatch", "missing-companion", "undeclared-target", "cardinality-violation", "forbidden-aria", "missing-announcer", "unknown-ignore-code"];
650
767
  /** See {@link DIAGNOSTIC_CODES}. */
651
768
  type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[number];
652
769
  /**
@@ -5,6 +5,65 @@ import { fileURLToPath } from 'url';
5
5
 
6
6
  // src/inspector/cli.ts
7
7
 
8
+ // src/inspector/announcer_rules.ts
9
+ var ANNOUNCER_IDENTIFIER = "stimeo--announcer";
10
+ var ANNOUNCING_CONTROLLERS = [
11
+ "stimeo--auto-submit",
12
+ "stimeo--character-counter",
13
+ "stimeo--countdown",
14
+ "stimeo--direct-upload",
15
+ "stimeo--empty-state",
16
+ "stimeo--flash",
17
+ "stimeo--form-field",
18
+ "stimeo--frame-loading",
19
+ "stimeo--meter",
20
+ "stimeo--multi-select",
21
+ "stimeo--nested-form",
22
+ "stimeo--network-status",
23
+ "stimeo--progress",
24
+ "stimeo--skeleton",
25
+ "stimeo--spinner",
26
+ "stimeo--submit-once",
27
+ "stimeo--tags-input"
28
+ ];
29
+ function positionAt(source, index) {
30
+ const before = source.slice(0, index);
31
+ const line = before.split("\n").length;
32
+ const lastBreak = before.lastIndexOf("\n");
33
+ return { line, column: index - lastBreak };
34
+ }
35
+ function firstAnnouncingHit(scanned) {
36
+ let best = null;
37
+ let bestIndex = Number.POSITIVE_INFINITY;
38
+ for (const identifier of ANNOUNCING_CONTROLLERS) {
39
+ const index = scanned.source.indexOf(identifier);
40
+ if (index === -1 || index >= bestIndex) continue;
41
+ bestIndex = index;
42
+ best = { file: scanned.file, identifier, ...positionAt(scanned.source, index) };
43
+ }
44
+ return best;
45
+ }
46
+ function announcerSeatingDiagnostic(sources) {
47
+ let speaker = null;
48
+ for (const scanned of sources) {
49
+ if (scanned.source.includes(ANNOUNCER_IDENTIFIER)) return null;
50
+ if (!speaker) speaker = firstAnnouncingHit(scanned);
51
+ }
52
+ if (!speaker) return null;
53
+ return {
54
+ file: speaker.file,
55
+ diagnostic: {
56
+ code: "missing-announcer",
57
+ severity: "warning",
58
+ message: `"${speaker.identifier}" speaks through the shared announcer, and none of the scanned files seats one.`,
59
+ line: speaker.line,
60
+ column: speaker.column,
61
+ length: speaker.identifier.length,
62
+ suggestion: `Add an element with data-controller="${ANNOUNCER_IDENTIFIER}" (and an assertive/polite target) to the layout, or include the layout in the checked paths.`
63
+ }
64
+ };
65
+ }
66
+
8
67
  // src/inspector/html_parser.ts
9
68
  var ERB_ELEMENT_TAG = "#erb";
10
69
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
@@ -930,10 +989,16 @@ function actionDescriptors(dataActionValue) {
930
989
  if (hash === -1) continue;
931
990
  let lhs = descriptor.slice(0, hash);
932
991
  const arrow = lhs.indexOf("->");
933
- if (arrow !== -1) lhs = lhs.slice(arrow + 2);
992
+ let eventType = "";
993
+ if (arrow !== -1) {
994
+ eventType = lhs.slice(0, arrow);
995
+ lhs = lhs.slice(arrow + 2);
996
+ const at = eventType.indexOf("@");
997
+ if (at !== -1) eventType = eventType.slice(0, at);
998
+ }
934
999
  if (!lhs.startsWith(NAMESPACE_PREFIX)) continue;
935
1000
  const method = /^[a-zA-Z_$][\w$]*/.exec(descriptor.slice(hash + 1))?.[0] ?? "";
936
- descriptors.push({ identifier: lhs, method });
1001
+ descriptors.push({ identifier: lhs, method, eventType });
937
1002
  }
938
1003
  return descriptors;
939
1004
  }
@@ -952,6 +1017,8 @@ var DIAGNOSTIC_CODES = [
952
1017
  "orphan-target",
953
1018
  "missing-required-target",
954
1019
  "missing-conditional-target",
1020
+ "missing-required-action",
1021
+ "missing-action-completion",
955
1022
  "missing-aria",
956
1023
  "invalid-aria-value",
957
1024
  "keyboard-inaccessible",
@@ -963,10 +1030,30 @@ var DIAGNOSTIC_CODES = [
963
1030
  "undeclared-target",
964
1031
  "cardinality-violation",
965
1032
  "forbidden-aria",
1033
+ "missing-announcer",
966
1034
  "unknown-ignore-code"
967
1035
  ];
968
1036
 
969
1037
  // src/inspector/check.ts
1038
+ function isInside(node, ancestor) {
1039
+ for (let current = node.parent; current; current = current.parent) {
1040
+ if (current === ancestor) return true;
1041
+ }
1042
+ return false;
1043
+ }
1044
+ function matchesHost(node, selector) {
1045
+ if (node.tag !== selector.tag) return false;
1046
+ if (!selector.attr) return true;
1047
+ const attr = node.attrs.find((candidate) => candidate.name === selector.attr);
1048
+ return attr !== void 0 && (selector.values ?? []).includes(attr.value.trim().toLowerCase());
1049
+ }
1050
+ function nearestHost(node, selectors) {
1051
+ for (let current = node.parent; current; current = current.parent) {
1052
+ if (current.origin !== "markup" || current.opaque) return void 0;
1053
+ if (selectors.some((selector) => matchesHost(current, selector))) return current;
1054
+ }
1055
+ return null;
1056
+ }
970
1057
  function checkSource(source, manifest) {
971
1058
  const diagnostics = [];
972
1059
  const tree = parseHtml(neutralizeErb(source), erbElements(source));
@@ -985,7 +1072,9 @@ function checkSource(source, manifest) {
985
1072
  (a) => a.name === `data-${identifier}-${dasherize(when.value)}-value`
986
1073
  );
987
1074
  if (hostAttr && isDynamicValue(hostAttr)) return null;
988
- return when.equals.includes(hostAttr ? hostAttr.value.trim() : when.default);
1075
+ const raw = hostAttr ? hostAttr.value : when.default;
1076
+ const effective = when.type === "boolean" ? String(!(raw === "0" || raw.toLowerCase() === "false")) : raw;
1077
+ return when.equals.includes(effective);
989
1078
  };
990
1079
  const isContentEditable2 = (element) => {
991
1080
  let current = element;
@@ -1058,6 +1147,17 @@ function checkSource(source, manifest) {
1058
1147
  const scopes = [];
1059
1148
  const presence = /* @__PURE__ */ new Map();
1060
1149
  const targetNodes = /* @__PURE__ */ new Map();
1150
+ const wiredActions = /* @__PURE__ */ new Map();
1151
+ const recordAction = (owner, identifier, element, method, eventType) => {
1152
+ let byId = wiredActions.get(owner);
1153
+ if (!byId) {
1154
+ byId = /* @__PURE__ */ new Map();
1155
+ wiredActions.set(owner, byId);
1156
+ }
1157
+ const found = byId.get(identifier);
1158
+ if (found) found.push({ element, method, eventType });
1159
+ else byId.set(identifier, [{ element, method, eventType }]);
1160
+ };
1061
1161
  const recordPresence = (owner, identifier, target) => {
1062
1162
  let byId = presence.get(owner);
1063
1163
  if (!byId) {
@@ -1266,11 +1366,12 @@ function checkSource(source, manifest) {
1266
1366
  (candidate) => dasherize(candidate.value) === parsed.valueToken
1267
1367
  );
1268
1368
  if (constraint && !valueSatisfiesConstraint(attr.value, constraint)) {
1369
+ const literal = constraint.type === "string" ? attr.value : attr.value.trim();
1269
1370
  report(
1270
1371
  node,
1271
1372
  "invalid-value",
1272
1373
  "error",
1273
- `Invalid value "${attr.value.trim()}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1374
+ `Invalid value "${literal}" for "${parsed.identifier}.${parsed.valueToken}". Expected ${describeValueConstraint(constraint)}.`,
1274
1375
  attr,
1275
1376
  constraint.suggestion
1276
1377
  );
@@ -1281,8 +1382,12 @@ function checkSource(source, manifest) {
1281
1382
  }
1282
1383
  }
1283
1384
  if (attr.name === "data-action") {
1284
- for (const { identifier, method } of actionDescriptors(attr.value)) {
1385
+ for (const { identifier, method, eventType } of actionDescriptors(attr.value)) {
1285
1386
  const controller = known[identifier];
1387
+ if (controller && method.length > 0) {
1388
+ const owner = findOwner(node, identifier);
1389
+ if (owner) recordAction(owner, identifier, node, method, eventType);
1390
+ }
1286
1391
  if (!controller) {
1287
1392
  const best = nearestName(identifier, knownIdentifiers);
1288
1393
  report(
@@ -1364,18 +1469,89 @@ function checkSource(source, manifest) {
1364
1469
  const present = presence.get(node)?.get(identifier) ?? /* @__PURE__ */ new Set();
1365
1470
  for (const rule of controller.conditionalTargets) {
1366
1471
  if (!present.has(rule.whenPresent)) continue;
1367
- const missing = rule.require.filter((target) => !present.has(target));
1472
+ const nodesOf = (target) => targetNodes.get(node)?.get(identifier)?.get(target) ?? [];
1473
+ const owners = nodesOf(rule.whenPresent);
1474
+ const hosts = rule.requireSameHost;
1475
+ const sharesHost = (target, selectors) => {
1476
+ const ownerHosts = owners.map((owner) => nearestHost(owner, selectors));
1477
+ if (ownerHosts.some((host) => !host)) return true;
1478
+ const candidates = nodesOf(target);
1479
+ if (candidates.length === 0) return false;
1480
+ return candidates.some((el) => {
1481
+ const host = nearestHost(el, selectors);
1482
+ return host ? ownerHosts.includes(host) : true;
1483
+ });
1484
+ };
1485
+ const satisfied = (target) => {
1486
+ if (hosts) return sharesHost(target, hosts);
1487
+ if (rule.requireInside)
1488
+ return nodesOf(target).some((el) => owners.some((owner) => isInside(el, owner)));
1489
+ return present.has(target);
1490
+ };
1491
+ const missing = rule.require.filter((target) => !satisfied(target));
1368
1492
  if (missing.length === 0) continue;
1369
1493
  report(
1370
1494
  node,
1371
1495
  "missing-conditional-target",
1372
1496
  complete ? "error" : "warning",
1373
- `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
1497
+ hosts ? `"${identifier}" requires ${list(missing)} in the same ${rule.hostLabel ?? "element"} as its "${rule.whenPresent}".` : rule.requireInside ? `"${identifier}" requires ${list(missing)} inside its "${rule.whenPresent}".` : `"${identifier}" has a "${rule.whenPresent}" target, which also requires ${list(missing)}.`,
1374
1498
  node,
1375
1499
  complete ? rule.suggestion : UNRESOLVED_TARGET_HINT
1376
1500
  );
1377
1501
  }
1378
1502
  }
1503
+ for (const { node, identifier } of scopes) {
1504
+ const controller = known[identifier];
1505
+ if (!controller) continue;
1506
+ const wired = wiredActions.get(node)?.get(identifier) ?? [];
1507
+ for (const rule of controller.requiredActions) {
1508
+ if (conditionHolds(node, identifier, rule.when) !== true) continue;
1509
+ const elements = rule.target === "" ? [node] : targetNodes.get(node)?.get(identifier)?.get(rule.target) ?? [];
1510
+ for (const element of elements) {
1511
+ const actionAttr = element.attrs.find((attr) => attr.name === "data-action");
1512
+ if (element.opaque || actionAttr && isDynamicValue(actionAttr)) continue;
1513
+ const satisfied = wired.some(
1514
+ (action) => action.element === element && action.method === rule.action && rule.eventTypes.includes(action.eventType)
1515
+ );
1516
+ if (satisfied) continue;
1517
+ const where = rule.target === "" ? "scope element" : `"${rule.target}" target`;
1518
+ report(
1519
+ element,
1520
+ "missing-required-action",
1521
+ "error",
1522
+ `The ${where} of "${identifier}" requires action "${rule.action}" on ${list(rule.eventTypes)} in this configuration.`,
1523
+ element,
1524
+ rule.suggestion
1525
+ );
1526
+ }
1527
+ }
1528
+ }
1529
+ for (const { node, identifier } of scopes) {
1530
+ const controller = known[identifier];
1531
+ if (!controller) continue;
1532
+ const wired = wiredActions.get(node)?.get(identifier) ?? [];
1533
+ if (wired.length === 0) continue;
1534
+ for (const rule of controller.actionCompletion) {
1535
+ const opens = wired.some(
1536
+ (action) => action.method === rule.opens && rule.whenTriggeredBy.includes(action.eventType)
1537
+ );
1538
+ if (!opens) continue;
1539
+ if (wired.some((action) => rule.closedBy.includes(action.method))) continue;
1540
+ if (rule.escapeValue) {
1541
+ const attrName = `data-${identifier}-${dasherize(rule.escapeValue)}-value`;
1542
+ const authored = node.attrs.find((attr) => attr.name === attrName);
1543
+ if (authored && (isDynamicValue(authored) || Number(authored.value.trim()) !== 0)) continue;
1544
+ }
1545
+ report(
1546
+ node,
1547
+ "missing-action-completion",
1548
+ "warning",
1549
+ `"${identifier}" opens a state with "${rule.opens}" here, and nothing in this scope closes it.`,
1550
+ node,
1551
+ rule.suggestion
1552
+ );
1553
+ }
1554
+ }
1379
1555
  for (const { node, identifier } of scopes) {
1380
1556
  const controller = known[identifier];
1381
1557
  if (!controller) continue;
@@ -1388,7 +1564,7 @@ function checkSource(source, manifest) {
1388
1564
  if (!hasReadableMarkup(el)) continue;
1389
1565
  if (!contentHolds(node, identifier, el, req.whenContains)) continue;
1390
1566
  if (!elementHolds(el, req.whenElement)) continue;
1391
- checkA11y(report, el, identifier, req, severity);
1567
+ checkA11y(report, el, identifier, req, severity, isDynamicValue);
1392
1568
  }
1393
1569
  }
1394
1570
  }
@@ -1678,20 +1854,24 @@ function checkSource(source, manifest) {
1678
1854
  return diagnostics;
1679
1855
  }
1680
1856
  function valueSatisfiesConstraint(raw, constraint) {
1857
+ if (constraint.type === "string") return constraint.allowedValues.includes(raw);
1681
1858
  const value = decodeNumericValue(raw);
1682
1859
  if (Number.isNaN(value)) return false;
1683
1860
  if (constraint.finite && !Number.isFinite(value)) return false;
1684
1861
  if (constraint.greaterThan !== void 0 && !(value > constraint.greaterThan)) return false;
1862
+ if (constraint.integer && !Number.isInteger(value)) return false;
1685
1863
  return true;
1686
1864
  }
1687
1865
  function decodeNumericValue(raw) {
1688
1866
  return Number(raw.replace(/_/g, ""));
1689
1867
  }
1690
1868
  function describeValueConstraint(constraint) {
1869
+ if (constraint.type === "string") return `one of ${list(constraint.allowedValues)}`;
1691
1870
  const parts = [constraint.finite ? "a finite number" : "a number"];
1692
1871
  if (constraint.greaterThan !== void 0) {
1693
1872
  parts.push(`greater than ${constraint.greaterThan}`);
1694
1873
  }
1874
+ if (constraint.integer) parts.push("with no fractional part");
1695
1875
  return parts.join(" ");
1696
1876
  }
1697
1877
  var UNRESOLVED_TARGET_HINT = "Part of this scope is rendered by a helper or a non-literal data: hash, so the target may exist at runtime.";
@@ -1699,7 +1879,7 @@ var UNRESOLVED_HOST_HINT = "An enclosing helper's data: option is not a literal
1699
1879
  function hasReadableMarkup(el) {
1700
1880
  return el.origin === "markup";
1701
1881
  }
1702
- function checkA11y(report, el, identifier, req, severity) {
1882
+ function checkA11y(report, el, identifier, req, severity, isDynamicValue) {
1703
1883
  const groups = [
1704
1884
  { attrs: req.attrs, values: req.values },
1705
1885
  ...req.or ?? []
@@ -1719,9 +1899,14 @@ function checkA11y(report, el, identifier, req, severity) {
1719
1899
  }
1720
1900
  const invalid = [];
1721
1901
  let anyPresent = false;
1902
+ let blank;
1722
1903
  for (const [name, allowed] of allowedByAttr) {
1723
1904
  const attr = el.attrs.find((a) => a.name === name);
1724
1905
  if (!attr) continue;
1906
+ if (allowed.anyValue && attr.value.trim() === "" && !isDynamicValue(attr)) {
1907
+ blank ??= attr;
1908
+ continue;
1909
+ }
1725
1910
  anyPresent = true;
1726
1911
  if (!allowed.anyValue && !allowed.values.has(attr.value.trim())) {
1727
1912
  invalid.push({ attr, values: [...allowed.values] });
@@ -1733,8 +1918,8 @@ function checkA11y(report, el, identifier, req, severity) {
1733
1918
  el,
1734
1919
  "missing-aria",
1735
1920
  severity,
1736
- `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
1737
- el,
1921
+ blank ? `${blank.name} is empty on the ${where} of "${identifier}", which ${verb} a value there.` : `"${identifier}" ${verb} ${describeAttrs([...allowedByAttr.keys()])} on its ${where}.`,
1922
+ blank ?? el,
1738
1923
  req.suggestion
1739
1924
  );
1740
1925
  return;
@@ -1936,6 +2121,8 @@ var CONTROLLER_ARRAY_FIELDS = [
1936
2121
  "events",
1937
2122
  "requiredTargets",
1938
2123
  "conditionalTargets",
2124
+ "requiredActions",
2125
+ "actionCompletion",
1939
2126
  "a11y",
1940
2127
  "keyboard",
1941
2128
  "hosts",
@@ -1998,10 +2185,16 @@ function collectFiles(target, out) {
1998
2185
  }
1999
2186
  if (isCheckableFile(target)) out.push(target);
2000
2187
  }
2001
- function checkFile(file, manifest) {
2002
- const source = readFileSync(file, "utf8");
2188
+ function checkFile(file, source, manifest) {
2003
2189
  return { file, diagnostics: checkSource(source, manifest) };
2004
2190
  }
2191
+ function withAnnouncerSeating(reports, sources) {
2192
+ const seating = announcerSeatingDiagnostic(sources);
2193
+ if (!seating) return [...reports];
2194
+ return reports.map(
2195
+ (report) => report.file === seating.file ? { file: report.file, diagnostics: [...report.diagnostics, seating.diagnostic] } : report
2196
+ );
2197
+ }
2005
2198
  function formatDiagnostic(d) {
2006
2199
  const where = `${d.line}:${d.column}`.padEnd(7);
2007
2200
  const head = ` ${where} ${d.severity.padEnd(7)} ${d.message} [${d.code}]`;
@@ -2134,13 +2327,14 @@ ${USAGE}`);
2134
2327
  const manifest = load();
2135
2328
  const cwd = process.cwd();
2136
2329
  const reports = [];
2330
+ const sources = [];
2137
2331
  for (const file of files.sort()) {
2138
- reports.push({
2139
- file: relative(cwd, file) || file,
2140
- diagnostics: checkFile(file, manifest).diagnostics
2141
- });
2332
+ const path = relative(cwd, file) || file;
2333
+ const source = readFileSync(file, "utf8");
2334
+ sources.push({ file: path, source });
2335
+ reports.push(checkFile(path, source, manifest));
2142
2336
  }
2143
- const summary = buildCheckReport(reports, files.length);
2337
+ const summary = buildCheckReport(withAnnouncerSeating(reports, sources), files.length);
2144
2338
  if (json) {
2145
2339
  write(JSON.stringify(summary, null, 2));
2146
2340
  return summary.errorCount > 0 ? 1 : 0;