stimeo-ui 0.17.0 → 0.18.1

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 (299) hide show
  1. package/CHANGELOG.md +136 -2
  2. package/README.md +19 -10
  3. package/dist/cable/index.d.ts +11 -9
  4. package/dist/cable/index.js +22 -14
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.js +14 -6
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.js +145 -118
  10. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  11. package/dist/controllers/announcer_controller.d.ts +5 -3
  12. package/dist/controllers/announcer_controller.js +113 -17
  13. package/dist/controllers/announcer_controller.js.map +1 -1
  14. package/dist/controllers/auto_submit_controller.js +185 -60
  15. package/dist/controllers/auto_submit_controller.js.map +1 -1
  16. package/dist/controllers/avatar_controller.js +55 -43
  17. package/dist/controllers/avatar_controller.js.map +1 -1
  18. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  19. package/dist/controllers/bulk_select_controller.d.ts +3 -3
  20. package/dist/controllers/bulk_select_controller.js +64 -50
  21. package/dist/controllers/bulk_select_controller.js.map +1 -1
  22. package/dist/controllers/calendar_controller.d.ts +15 -12
  23. package/dist/controllers/calendar_controller.js +55 -53
  24. package/dist/controllers/calendar_controller.js.map +1 -1
  25. package/dist/controllers/carousel_controller.d.ts +9 -5
  26. package/dist/controllers/carousel_controller.js +124 -86
  27. package/dist/controllers/carousel_controller.js.map +1 -1
  28. package/dist/controllers/character_counter_controller.d.ts +27 -6
  29. package/dist/controllers/character_counter_controller.js +291 -95
  30. package/dist/controllers/character_counter_controller.js.map +1 -1
  31. package/dist/controllers/checkbox_controller.js +6 -0
  32. package/dist/controllers/checkbox_controller.js.map +1 -1
  33. package/dist/controllers/clipboard_controller.js +118 -27
  34. package/dist/controllers/clipboard_controller.js.map +1 -1
  35. package/dist/controllers/collapsible_controller.d.ts +1 -1
  36. package/dist/controllers/collapsible_controller.js +52 -49
  37. package/dist/controllers/collapsible_controller.js.map +1 -1
  38. package/dist/controllers/color_picker_controller.js +29 -7
  39. package/dist/controllers/color_picker_controller.js.map +1 -1
  40. package/dist/controllers/combobox_controller.d.ts +1 -1
  41. package/dist/controllers/combobox_controller.js +210 -71
  42. package/dist/controllers/combobox_controller.js.map +1 -1
  43. package/dist/controllers/command_palette_controller.d.ts +9 -9
  44. package/dist/controllers/command_palette_controller.js +290 -140
  45. package/dist/controllers/command_palette_controller.js.map +1 -1
  46. package/dist/controllers/conditional_fields_controller.js +145 -58
  47. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  48. package/dist/controllers/confirm_controller.js +145 -118
  49. package/dist/controllers/confirm_controller.js.map +1 -1
  50. package/dist/controllers/context_menu_controller.d.ts +49 -14
  51. package/dist/controllers/context_menu_controller.js +138 -73
  52. package/dist/controllers/context_menu_controller.js.map +1 -1
  53. package/dist/controllers/count_up_controller.js.map +1 -1
  54. package/dist/controllers/countdown_controller.d.ts +2 -2
  55. package/dist/controllers/countdown_controller.js +109 -21
  56. package/dist/controllers/countdown_controller.js.map +1 -1
  57. package/dist/controllers/currency_input_controller.d.ts +75 -11
  58. package/dist/controllers/currency_input_controller.js +446 -72
  59. package/dist/controllers/currency_input_controller.js.map +1 -1
  60. package/dist/controllers/data_grid_controller.d.ts +10 -8
  61. package/dist/controllers/data_grid_controller.js +134 -28
  62. package/dist/controllers/data_grid_controller.js.map +1 -1
  63. package/dist/controllers/date_range_picker_controller.d.ts +12 -9
  64. package/dist/controllers/date_range_picker_controller.js +93 -72
  65. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  66. package/dist/controllers/dialog_controller.d.ts +4 -5
  67. package/dist/controllers/dialog_controller.js +145 -118
  68. package/dist/controllers/dialog_controller.js.map +1 -1
  69. package/dist/controllers/direct_upload_controller.d.ts +1 -1
  70. package/dist/controllers/direct_upload_controller.js +24 -14
  71. package/dist/controllers/direct_upload_controller.js.map +1 -1
  72. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  73. package/dist/controllers/dirty_form_controller.js +68 -31
  74. package/dist/controllers/dirty_form_controller.js.map +1 -1
  75. package/dist/controllers/dismissible_controller.js +66 -46
  76. package/dist/controllers/dismissible_controller.js.map +1 -1
  77. package/dist/controllers/drawer_controller.js +145 -118
  78. package/dist/controllers/drawer_controller.js.map +1 -1
  79. package/dist/controllers/dropdown_controller.js +76 -63
  80. package/dist/controllers/dropdown_controller.js.map +1 -1
  81. package/dist/controllers/editable_controller.js +200 -62
  82. package/dist/controllers/editable_controller.js.map +1 -1
  83. package/dist/controllers/empty_state_controller.js +13 -6
  84. package/dist/controllers/empty_state_controller.js.map +1 -1
  85. package/dist/controllers/file_dropzone_controller.d.ts +3 -0
  86. package/dist/controllers/file_dropzone_controller.js +55 -48
  87. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  88. package/dist/controllers/filter_controller.js +9 -2
  89. package/dist/controllers/filter_controller.js.map +1 -1
  90. package/dist/controllers/flash_controller.d.ts +4 -4
  91. package/dist/controllers/flash_controller.js +66 -33
  92. package/dist/controllers/flash_controller.js.map +1 -1
  93. package/dist/controllers/focus_controller.d.ts +3 -3
  94. package/dist/controllers/focus_controller.js +39 -14
  95. package/dist/controllers/focus_controller.js.map +1 -1
  96. package/dist/controllers/form_field_controller.d.ts +2 -1
  97. package/dist/controllers/form_field_controller.js +52 -43
  98. package/dist/controllers/form_field_controller.js.map +1 -1
  99. package/dist/controllers/form_validation_controller.d.ts +3 -3
  100. package/dist/controllers/form_validation_controller.js +18 -11
  101. package/dist/controllers/form_validation_controller.js.map +1 -1
  102. package/dist/controllers/frame_loading_controller.d.ts +13 -12
  103. package/dist/controllers/frame_loading_controller.js +55 -48
  104. package/dist/controllers/frame_loading_controller.js.map +1 -1
  105. package/dist/controllers/highlight_controller.d.ts +5 -4
  106. package/dist/controllers/highlight_controller.js.map +1 -1
  107. package/dist/controllers/hover_card_controller.d.ts +4 -1
  108. package/dist/controllers/hover_card_controller.js +67 -61
  109. package/dist/controllers/hover_card_controller.js.map +1 -1
  110. package/dist/controllers/idle_controller.d.ts +6 -5
  111. package/dist/controllers/idle_controller.js +10 -6
  112. package/dist/controllers/idle_controller.js.map +1 -1
  113. package/dist/controllers/input_mask_controller.d.ts +31 -13
  114. package/dist/controllers/input_mask_controller.js +434 -24
  115. package/dist/controllers/input_mask_controller.js.map +1 -1
  116. package/dist/controllers/intersection_controller.js +3 -0
  117. package/dist/controllers/intersection_controller.js.map +1 -1
  118. package/dist/controllers/lazy_frame_controller.d.ts +3 -2
  119. package/dist/controllers/lazy_frame_controller.js +15 -3
  120. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  121. package/dist/controllers/listbox_controller.d.ts +10 -7
  122. package/dist/controllers/listbox_controller.js +260 -79
  123. package/dist/controllers/listbox_controller.js.map +1 -1
  124. package/dist/controllers/local_time_controller.d.ts +8 -6
  125. package/dist/controllers/local_time_controller.js +48 -13
  126. package/dist/controllers/local_time_controller.js.map +1 -1
  127. package/dist/controllers/masonry_controller.js +11 -4
  128. package/dist/controllers/masonry_controller.js.map +1 -1
  129. package/dist/controllers/menu_controller.d.ts +24 -5
  130. package/dist/controllers/menu_controller.js +138 -82
  131. package/dist/controllers/menu_controller.js.map +1 -1
  132. package/dist/controllers/menubar_controller.d.ts +49 -7
  133. package/dist/controllers/menubar_controller.js +65 -19
  134. package/dist/controllers/menubar_controller.js.map +1 -1
  135. package/dist/controllers/meter_controller.d.ts +13 -20
  136. package/dist/controllers/meter_controller.js +270 -24
  137. package/dist/controllers/meter_controller.js.map +1 -1
  138. package/dist/controllers/multi_select_controller.d.ts +19 -16
  139. package/dist/controllers/multi_select_controller.js +272 -95
  140. package/dist/controllers/multi_select_controller.js.map +1 -1
  141. package/dist/controllers/navigation_menu_controller.d.ts +19 -5
  142. package/dist/controllers/navigation_menu_controller.js +67 -19
  143. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  144. package/dist/controllers/nested_form_controller.js +98 -64
  145. package/dist/controllers/nested_form_controller.js.map +1 -1
  146. package/dist/controllers/network_status_controller.js +70 -52
  147. package/dist/controllers/network_status_controller.js.map +1 -1
  148. package/dist/controllers/number_input_controller.d.ts +24 -6
  149. package/dist/controllers/number_input_controller.js +344 -94
  150. package/dist/controllers/number_input_controller.js.map +1 -1
  151. package/dist/controllers/optimistic_controller.d.ts +8 -5
  152. package/dist/controllers/optimistic_controller.js +29 -11
  153. package/dist/controllers/optimistic_controller.js.map +1 -1
  154. package/dist/controllers/otp_controller.d.ts +35 -4
  155. package/dist/controllers/otp_controller.js +502 -104
  156. package/dist/controllers/otp_controller.js.map +1 -1
  157. package/dist/controllers/overflow_indicator_controller.js +6 -0
  158. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  159. package/dist/controllers/overflow_menu_controller.d.ts +10 -9
  160. package/dist/controllers/overflow_menu_controller.js +136 -21
  161. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  162. package/dist/controllers/pagination_controller.d.ts +3 -2
  163. package/dist/controllers/pagination_controller.js +47 -14
  164. package/dist/controllers/pagination_controller.js.map +1 -1
  165. package/dist/controllers/password_reveal_controller.d.ts +5 -4
  166. package/dist/controllers/password_reveal_controller.js +52 -49
  167. package/dist/controllers/password_reveal_controller.js.map +1 -1
  168. package/dist/controllers/password_strength_controller.d.ts +1 -1
  169. package/dist/controllers/password_strength_controller.js +13 -6
  170. package/dist/controllers/password_strength_controller.js.map +1 -1
  171. package/dist/controllers/persist_controller.js +48 -128
  172. package/dist/controllers/persist_controller.js.map +1 -1
  173. package/dist/controllers/pointer_drag_controller.d.ts +20 -8
  174. package/dist/controllers/pointer_drag_controller.js +255 -24
  175. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  176. package/dist/controllers/popover_controller.d.ts +9 -5
  177. package/dist/controllers/popover_controller.js +91 -65
  178. package/dist/controllers/popover_controller.js.map +1 -1
  179. package/dist/controllers/portal_controller.js +6 -0
  180. package/dist/controllers/portal_controller.js.map +1 -1
  181. package/dist/controllers/preview_guard_controller.js +14 -3
  182. package/dist/controllers/preview_guard_controller.js.map +1 -1
  183. package/dist/controllers/progress_controller.d.ts +9 -19
  184. package/dist/controllers/progress_controller.js +271 -27
  185. package/dist/controllers/progress_controller.js.map +1 -1
  186. package/dist/controllers/radio_group_controller.d.ts +11 -8
  187. package/dist/controllers/radio_group_controller.js +167 -80
  188. package/dist/controllers/radio_group_controller.js.map +1 -1
  189. package/dist/controllers/range_slider_controller.d.ts +4 -3
  190. package/dist/controllers/range_slider_controller.js +27 -7
  191. package/dist/controllers/range_slider_controller.js.map +1 -1
  192. package/dist/controllers/rating_controller.js +93 -57
  193. package/dist/controllers/rating_controller.js.map +1 -1
  194. package/dist/controllers/read_more_controller.js +10 -6
  195. package/dist/controllers/read_more_controller.js.map +1 -1
  196. package/dist/controllers/reading_progress_controller.js +22 -5
  197. package/dist/controllers/reading_progress_controller.js.map +1 -1
  198. package/dist/controllers/relative_time_controller.d.ts +4 -4
  199. package/dist/controllers/relative_time_controller.js +75 -11
  200. package/dist/controllers/relative_time_controller.js.map +1 -1
  201. package/dist/controllers/reset_on_restore_controller.d.ts +3 -3
  202. package/dist/controllers/reset_on_restore_controller.js +6 -0
  203. package/dist/controllers/reset_on_restore_controller.js.map +1 -1
  204. package/dist/controllers/resizable_controller.d.ts +3 -0
  205. package/dist/controllers/resizable_controller.js +55 -22
  206. package/dist/controllers/resizable_controller.js.map +1 -1
  207. package/dist/controllers/roving_controller.d.ts +3 -3
  208. package/dist/controllers/roving_controller.js +10 -4
  209. package/dist/controllers/roving_controller.js.map +1 -1
  210. package/dist/controllers/scroll_area_controller.js +129 -88
  211. package/dist/controllers/scroll_area_controller.js.map +1 -1
  212. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  213. package/dist/controllers/scroll_restore_controller.js +14 -2
  214. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  215. package/dist/controllers/scroll_visibility_controller.js +93 -60
  216. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  217. package/dist/controllers/scrollspy_controller.js +11 -5
  218. package/dist/controllers/scrollspy_controller.js.map +1 -1
  219. package/dist/controllers/separator_controller.d.ts +1 -1
  220. package/dist/controllers/separator_controller.js +59 -43
  221. package/dist/controllers/separator_controller.js.map +1 -1
  222. package/dist/controllers/sidebar_controller.d.ts +1 -1
  223. package/dist/controllers/sidebar_controller.js +150 -122
  224. package/dist/controllers/sidebar_controller.js.map +1 -1
  225. package/dist/controllers/skeleton_controller.js +55 -48
  226. package/dist/controllers/skeleton_controller.js.map +1 -1
  227. package/dist/controllers/slider_controller.d.ts +2 -1
  228. package/dist/controllers/slider_controller.js +27 -6
  229. package/dist/controllers/slider_controller.js.map +1 -1
  230. package/dist/controllers/smart_sticky_header_controller.d.ts +1 -1
  231. package/dist/controllers/smart_sticky_header_controller.js +13 -5
  232. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  233. package/dist/controllers/sortable_controller.d.ts +4 -4
  234. package/dist/controllers/sortable_controller.js +27 -14
  235. package/dist/controllers/sortable_controller.js.map +1 -1
  236. package/dist/controllers/spinner_controller.js +55 -48
  237. package/dist/controllers/spinner_controller.js.map +1 -1
  238. package/dist/controllers/step_indicator_controller.d.ts +4 -4
  239. package/dist/controllers/step_indicator_controller.js +6 -0
  240. package/dist/controllers/step_indicator_controller.js.map +1 -1
  241. package/dist/controllers/stepper_controller.d.ts +2 -3
  242. package/dist/controllers/stepper_controller.js +6 -0
  243. package/dist/controllers/stepper_controller.js.map +1 -1
  244. package/dist/controllers/stick_to_bottom_controller.d.ts +3 -1
  245. package/dist/controllers/stick_to_bottom_controller.js +19 -8
  246. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  247. package/dist/controllers/sticky_observer_controller.js +49 -43
  248. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  249. package/dist/controllers/submit_once_controller.d.ts +2 -1
  250. package/dist/controllers/submit_once_controller.js +145 -85
  251. package/dist/controllers/submit_once_controller.js.map +1 -1
  252. package/dist/controllers/switch_controller.d.ts +7 -4
  253. package/dist/controllers/switch_controller.js +172 -28
  254. package/dist/controllers/switch_controller.js.map +1 -1
  255. package/dist/controllers/tabs_controller.d.ts +7 -4
  256. package/dist/controllers/tabs_controller.js +6 -1
  257. package/dist/controllers/tabs_controller.js.map +1 -1
  258. package/dist/controllers/tags_input_controller.d.ts +4 -3
  259. package/dist/controllers/tags_input_controller.js +157 -28
  260. package/dist/controllers/tags_input_controller.js.map +1 -1
  261. package/dist/controllers/textarea_autosize_controller.d.ts +1 -1
  262. package/dist/controllers/textarea_autosize_controller.js +40 -19
  263. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  264. package/dist/controllers/theme_controller.d.ts +3 -2
  265. package/dist/controllers/theme_controller.js +10 -4
  266. package/dist/controllers/theme_controller.js.map +1 -1
  267. package/dist/controllers/time_picker_controller.d.ts +38 -5
  268. package/dist/controllers/time_picker_controller.js +192 -54
  269. package/dist/controllers/time_picker_controller.js.map +1 -1
  270. package/dist/controllers/toast_controller.d.ts +4 -5
  271. package/dist/controllers/toast_controller.js +58 -27
  272. package/dist/controllers/toast_controller.js.map +1 -1
  273. package/dist/controllers/toggle_group_controller.d.ts +2 -3
  274. package/dist/controllers/toggle_group_controller.js +158 -56
  275. package/dist/controllers/toggle_group_controller.js.map +1 -1
  276. package/dist/controllers/toolbar_controller.d.ts +8 -6
  277. package/dist/controllers/toolbar_controller.js +150 -21
  278. package/dist/controllers/toolbar_controller.js.map +1 -1
  279. package/dist/controllers/tooltip_controller.d.ts +4 -1
  280. package/dist/controllers/tooltip_controller.js.map +1 -1
  281. package/dist/controllers/transition_controller.js.map +1 -1
  282. package/dist/controllers/tree_view_controller.d.ts +1 -1
  283. package/dist/controllers/tree_view_controller.js +134 -25
  284. package/dist/controllers/tree_view_controller.js.map +1 -1
  285. package/dist/index.d.ts +4 -1
  286. package/dist/index.js +2515 -1315
  287. package/dist/index.js.map +1 -1
  288. package/dist/inspector/cli.d.ts +25 -12
  289. package/dist/inspector/cli.js +21 -3
  290. package/dist/inspector/cli.js.map +1 -1
  291. package/dist/inspector/cli_bin.d.ts +1 -1
  292. package/dist/inspector/cli_bin.js +62 -8
  293. package/dist/inspector/cli_bin.js.map +1 -1
  294. package/dist/inspector/examples.json +13 -13
  295. package/dist/inspector/manifest.json +19 -5
  296. package/dist/positioning/index.d.ts +9 -8
  297. package/dist/positioning/index.js +6 -0
  298. package/dist/positioning/index.js.map +1 -1
  299. package/package.json +11 -10
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "stimeo--carousel": {
45
45
  "file": "examples/carousel/_demo.html.erb",
46
- "source": "<%# Markup for the carousel demo.\n The library handles slide advance, autoplay, syncing the current slide's\n data-state / hidden / inert, the pickers' (tabs) aria-selected and roving, the\n aria-disabled of controls that cannot be reached, and the viewport's aria-live.\n There is no data-action anywhere: the controller delegates clicks, picker keys,\n hover, and focus from its own element, so the markup only declares targets.\n Autoplay deliberately demonstrates WCAG 2.2.2: the play toggle is what stops it for\n good, while hovering, focusing, or backgrounding the tab only suspends it until that\n condition ends. This is spelled out in the on-screen note below the carousel.\n Transitions and layout are the consumer's CSS. %>\n<section\n class=\"carousel\"\n data-controller=\"stimeo--carousel\"\n aria-roledescription=\"carousel\"\n aria-label=\"<%= t(\"components.carousel.demo.label\") %>\"\n data-stimeo--carousel-autoplay-value=\"false\"\n data-stimeo--carousel-interval-value=\"2500\"\n data-stimeo--carousel-loop-value=\"true\">\n <div class=\"carousel__bar\">\n <button\n type=\"button\"\n class=\"carousel__play\"\n aria-label=\"<%= t(\"components.carousel.demo.autoplay\") %>\"\n data-stimeo--carousel-target=\"playToggle\">\n <%# The library owns aria-pressed (derived from the autoplay Value, whether a slide\n is left to rotate to, and the reduced-motion preference) and the hidden of the\n two glyph halves: ❚❚ shows while autoplay runs, ▶ while it is stopped. The\n icons are decorative; the accessible name comes from the aria-label above and\n stays the same in both states. %>\n <span aria-hidden=\"true\" data-stimeo--carousel-target=\"offLabel\">▶</span>\n <span aria-hidden=\"true\" hidden data-stimeo--carousel-target=\"onLabel\">❚❚</span>\n </button>\n <%# A visual read-out only. The library makes the viewport the live region, so a\n second one here would make a screen reader announce the change twice. %>\n <p\n class=\"carousel__status\"\n data-carousel-status\n data-template=\"<%= t(\"components.carousel.demo.status_template\") %>\"\n data-playing-suffix=\"<%= t(\"components.carousel.demo.status_playing\") %>\"></p>\n </div>\n\n <div class=\"carousel__viewport\" data-stimeo--carousel-target=\"viewport\">\n <% t(\"components.carousel.demo.slides\").each_with_index do |slide, i| %>\n <div\n id=\"carousel-slide-<%= i + 1 %>\"\n class=\"carousel__slide\"\n role=\"tabpanel\"\n aria-roledescription=\"slide\"\n aria-label=\"<%= \"#{i + 1} of #{t('components.carousel.demo.slides').size}\" %>\"\n aria-labelledby=\"carousel-dot-<%= i + 1 %>\"\n data-stimeo--carousel-target=\"slide\"\n <%= \"hidden inert\" if i.positive? %>>\n <h3 class=\"carousel__title\"><%= slide[:title] %></h3>\n <p><%= slide[:body] %></p>\n </div>\n <% end %>\n </div>\n\n <div class=\"carousel__nav\">\n <button type=\"button\" class=\"carousel__arrow\"\n aria-label=\"<%= t(\"components.carousel.demo.prev\") %>\"\n data-stimeo--carousel-target=\"prev\">‹</button>\n <button type=\"button\" class=\"carousel__arrow\"\n aria-label=\"<%= t(\"components.carousel.demo.next\") %>\"\n data-stimeo--carousel-target=\"next\">›</button>\n </div>\n\n <div class=\"carousel__dots\" role=\"tablist\"\n aria-label=\"<%= t(\"components.carousel.demo.tablist\") %>\">\n <% t(\"components.carousel.demo.slides\").each_with_index do |slide, i| %>\n <button\n id=\"carousel-dot-<%= i + 1 %>\"\n class=\"carousel__dot\"\n role=\"tab\"\n aria-selected=\"<%= i.zero? %>\"\n aria-controls=\"carousel-slide-<%= i + 1 %>\"\n aria-label=\"<%= slide[:title] %>\"\n tabindex=\"<%= i.zero? ? 0 : -1 %>\"\n data-stimeo--carousel-target=\"picker\"></button>\n <% end %>\n </div>\n</section>\n\n<%# On-screen explanation of the deliberate autoplay accessibility behavior, so the\n pause-on-hover / hard-stop-on-focus is understood as intentional (WCAG 2.2.2), not a bug.\n Kept outside the <section> so reading it doesn't itself pause the carousel. %>\n<p class=\"carousel__hint\"><%= t(\"components.carousel.demo.hint\") %></p>\n"
46
+ "source": "<%# Markup for the carousel demo.\n The library handles slide advance, autoplay, syncing the current slide's\n data-state / hidden / inert, the pickers' (tabs) aria-selected and roving, the\n aria-disabled of controls that cannot be reached, and the viewport's aria-live.\n There is no data-action anywhere: the controller delegates clicks, picker keys,\n hover, and focus from its own element, so the markup only declares targets.\n Autoplay deliberately demonstrates WCAG 2.2.2: the play toggle is what stops it for\n good, while hovering, focusing, or backgrounding the tab only suspends it until that\n condition ends. This is spelled out in the on-screen note below the carousel.\n Transitions and layout are the consumer's CSS. %>\n<section\n class=\"carousel\"\n data-controller=\"stimeo--carousel\"\n aria-roledescription=\"carousel\"\n aria-label=\"<%= t(\"components.carousel.demo.label\") %>\"\n data-stimeo--carousel-autoplay-value=\"false\"\n data-stimeo--carousel-interval-value=\"2500\"\n data-stimeo--carousel-loop-value=\"true\">\n <div class=\"carousel__bar\">\n <button\n type=\"button\"\n class=\"carousel__play\"\n aria-label=\"<%= t(\"components.carousel.demo.autoplay\") %>\"\n data-stimeo--carousel-target=\"playToggle\">\n <%# The library owns aria-pressed (derived from the autoplay Value, whether a slide\n is left to rotate to, and the reduced-motion preference) and the hidden of the\n two glyph halves: ❚❚ shows while autoplay runs, ▶ while it is stopped. The\n icons are decorative; the accessible name comes from the aria-label above and\n stays the same in both states. %>\n <span aria-hidden=\"true\" data-stimeo--carousel-target=\"offLabel\">▶</span>\n <span aria-hidden=\"true\" hidden data-stimeo--carousel-target=\"onLabel\">❚❚</span>\n </button>\n <%# A visual read-out only. The library makes the viewport the live region, so a\n second one here would make a screen reader announce the change twice. %>\n <p\n class=\"carousel__status\"\n data-carousel-status\n data-template=\"<%= t(\"components.carousel.demo.status_template\") %>\"\n data-playing-suffix=\"<%= t(\"components.carousel.demo.status_playing\") %>\"></p>\n </div>\n\n <div class=\"carousel__viewport\" data-stimeo--carousel-target=\"viewport\">\n <% t(\"components.carousel.demo.slides\").each_with_index do |slide, i| %>\n <div\n id=\"carousel-slide-<%= i + 1 %>\"\n class=\"carousel__slide\"\n role=\"tabpanel\"\n aria-roledescription=\"slide\"\n aria-label=\"<%= \"#{i + 1} of #{t('components.carousel.demo.slides').size}\" %>\"\n aria-labelledby=\"carousel-dot-<%= i + 1 %>\"\n data-stimeo--carousel-target=\"slide\"\n <%= \"hidden inert\" if i.positive? %>>\n <h3 class=\"carousel__title\"><%= slide[:title] %></h3>\n <p><%= slide[:body] %></p>\n </div>\n <% end %>\n </div>\n\n <div class=\"carousel__nav\">\n <button type=\"button\" class=\"carousel__arrow\"\n aria-label=\"<%= t(\"components.carousel.demo.prev\") %>\"\n data-stimeo--carousel-target=\"prev\">‹</button>\n <button type=\"button\" class=\"carousel__arrow\"\n aria-label=\"<%= t(\"components.carousel.demo.next\") %>\"\n data-stimeo--carousel-target=\"next\">›</button>\n </div>\n\n <div class=\"carousel__dots\" role=\"tablist\" data-stimeo--carousel-target=\"pickerList\"\n aria-label=\"<%= t(\"components.carousel.demo.tablist\") %>\">\n <% t(\"components.carousel.demo.slides\").each_with_index do |slide, i| %>\n <button\n id=\"carousel-dot-<%= i + 1 %>\"\n class=\"carousel__dot\"\n role=\"tab\"\n aria-selected=\"<%= i.zero? %>\"\n aria-controls=\"carousel-slide-<%= i + 1 %>\"\n aria-label=\"<%= slide[:title] %>\"\n tabindex=\"<%= i.zero? ? 0 : -1 %>\"\n data-stimeo--carousel-target=\"picker\"></button>\n <% end %>\n </div>\n</section>\n\n<%# On-screen explanation of the deliberate autoplay accessibility behavior, so the\n hover and focus suspensions are understood as intentional (WCAG 2.2.2).\n Kept outside the <section> so reading it doesn't itself pause the carousel. %>\n<p class=\"carousel__hint\"><%= t(\"components.carousel.demo.hint\") %></p>\n"
47
47
  },
48
48
  "stimeo--character-counter": {
49
49
  "file": "examples/character_counter/_demo.html.erb",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "stimeo--combobox": {
69
69
  "file": "examples/combobox/_demo.html.erb",
70
- "source": "<%# Markup for the combobox (APG Combobox / list autocomplete) demo.\n stimeo--combobox provides list filtering on input, Arrow/Enter/Esc, and\n aria-activedescendant. Focus stays on the input and the active option is shown via\n aria-selected. On selection, data-value (or the text, if absent) is placed in the input. %>\n<div class=\"combobox\" data-controller=\"stimeo--combobox\">\n <label id=\"fruit-label\" class=\"combobox__label\" for=\"fruit-input\"><%= t(\n \"components.combobox.demo.label\"\n ) %></label>\n <input\n id=\"fruit-input\"\n type=\"text\"\n class=\"combobox__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"fruit-list\"\n autocomplete=\"off\"\n data-stimeo--combobox-target=\"input\"\n data-action=\"input->stimeo--combobox#filter\n keydown->stimeo--combobox#onKeydown\n focus->stimeo--combobox#open\n click->stimeo--combobox#open\">\n\n <ul\n id=\"fruit-list\"\n class=\"combobox__list\"\n role=\"listbox\"\n aria-labelledby=\"fruit-label\"\n data-stimeo--combobox-target=\"list\"\n hidden>\n <li role=\"option\" id=\"fruit-apple\" class=\"combobox__option\" data-value=\"Apple\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#select\">Apple</li>\n <li role=\"option\" id=\"fruit-apricot\" class=\"combobox__option\" data-value=\"Apricot\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#select\">Apricot</li>\n <li role=\"option\" id=\"fruit-banana\" class=\"combobox__option\" data-value=\"Banana\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#select\">Banana</li>\n <li role=\"option\" id=\"fruit-cherry\" class=\"combobox__option\" data-value=\"Cherry\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#select\">Cherry</li>\n </ul>\n\n <%# The no-results message is a region of the empty state: the library shows it while the\n open listbox has no match and hides it otherwise, so it is authored hidden. Copy via\n i18n. %>\n <div\n class=\"combobox__empty\"\n data-stimeo--combobox-target=\"empty\"\n hidden><%= t(\"components.combobox.demo.empty\") %></div>\n</div>\n"
70
+ "source": "<%# Markup for the combobox (APG Combobox / list autocomplete) demo.\n stimeo--combobox provides list filtering on input, Arrow/Enter/Esc, and\n aria-activedescendant. Focus stays on the input and the active option is shown via\n aria-selected. On selection, data-value (or the text, if absent) is placed in the input. %>\n<div class=\"combobox\" data-controller=\"stimeo--combobox\">\n <label id=\"fruit-label\" class=\"combobox__label\" for=\"fruit-input\"><%= t(\n \"components.combobox.demo.label\"\n ) %></label>\n <input\n id=\"fruit-input\"\n type=\"text\"\n class=\"combobox__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"fruit-list\"\n autocomplete=\"off\"\n data-stimeo--combobox-target=\"input\"\n data-action=\"input->stimeo--combobox#filter\n keydown->stimeo--combobox#onKeydown\n focus->stimeo--combobox#open\n click->stimeo--combobox#open\">\n\n <ul\n id=\"fruit-list\"\n class=\"combobox__list\"\n role=\"listbox\"\n aria-labelledby=\"fruit-label\"\n data-stimeo--combobox-target=\"list\"\n hidden>\n <% %w[apple apricot banana cherry].each do |fruit| %>\n <% label = t(\"components.combobox.demo.fruits.#{fruit}\") %>\n <li role=\"option\" id=\"fruit-<%= fruit %>\" class=\"combobox__option\" data-value=\"<%= label %>\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#select\"><%= label %></li>\n <% end %>\n </ul>\n\n <%# The no-results message is a region of the empty state: the library shows it while the\n open listbox has no match and hides it otherwise, so it is authored hidden. Copy via\n i18n. %>\n <div\n class=\"combobox__empty\"\n data-stimeo--combobox-target=\"empty\"\n hidden><%= t(\"components.combobox.demo.empty\") %></div>\n</div>\n"
71
71
  },
72
72
  "stimeo--command-palette": {
73
73
  "file": "examples/command_palette/_demo.html.erb",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "stimeo--context-menu": {
85
85
  "file": "examples/context_menu/_demo.html.erb",
86
- "source": "<%# Markup for the context_menu demo.\n A right-click on the region (or the ContextMenu key / Shift+F10) opens a\n role=\"menu\" at the pointer position. The interaction model is the same as a Menu\n Button. The click coordinate is reflected on the menu as\n --stimeo--context-menu-x / -y, and the Playground CSS uses it for top/left\n placement (works even without the positioning module). %>\n<div class=\"context-menu\" data-controller=\"stimeo--context-menu\">\n <div\n class=\"context-menu__region\"\n data-stimeo--context-menu-target=\"region\"\n tabindex=\"0\"\n aria-haspopup=\"menu\"\n aria-controls=\"context-menu-list\"\n data-action=\"\n contextmenu->stimeo--context-menu#open\n keydown->stimeo--context-menu#onRegionKeydown\">\n <%= t(\"components.context_menu.demo.region\") %>\n </div>\n\n <ul\n class=\"context-menu__list\"\n id=\"context-menu-list\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.context_menu.demo.label\") %>\"\n data-stimeo--context-menu-target=\"menu\"\n hidden>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.copy\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.paste\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.delete\") %>\n </button>\n </li>\n </ul>\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
86
+ "source": "<%# Markup for the context_menu demo.\n A right-click on the region (or the ContextMenu key / Shift+F10) opens a\n role=\"menu\" at the pointer position. The menu itself follows the APG Menu\n pattern. The region's role and name are yours: here a named group, which\n advertises its opening key with aria-keyshortcuts. The click coordinate is\n reflected on the menu as --stimeo--context-menu-x / -y, and the Playground\n CSS uses it for top/left placement (works even without the positioning\n module). %>\n<div class=\"context-menu\" data-controller=\"stimeo--context-menu\">\n <div\n class=\"context-menu__region\"\n data-stimeo--context-menu-target=\"region\"\n tabindex=\"0\"\n role=\"group\"\n aria-label=\"<%= t(\"components.context_menu.demo.region_label\") %>\"\n aria-keyshortcuts=\"Shift+F10\"\n aria-controls=\"context-menu-list\"\n data-action=\"\n contextmenu->stimeo--context-menu#open\n keydown->stimeo--context-menu#onRegionKeydown\">\n <%= t(\"components.context_menu.demo.region\") %>\n </div>\n\n <ul\n class=\"context-menu__list\"\n id=\"context-menu-list\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.context_menu.demo.label\") %>\"\n data-stimeo--context-menu-target=\"menu\"\n hidden>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.copy\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.paste\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.delete\") %>\n </button>\n </li>\n </ul>\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
87
87
  },
88
88
  "stimeo--count-up": {
89
89
  "file": "examples/count_up/_demo.html.erb",
@@ -103,7 +103,7 @@
103
103
  },
104
104
  "stimeo--date-range-picker": {
105
105
  "file": "examples/date_range_picker/_demo.html.erb",
106
- "source": "<%# Markup for the date-range-picker (range-selection calendar) demo.\n stimeo--date-range-picker derives from calendar and handles two-point start/end\n selection, a provisional range preview mid-selection, marking in-range cells,\n auto-swapping when start > end, applying presets, and grid keyboard movement\n (roving). Range ends are shown with aria-selected and the inside with data-in-range;\n placement and styling live in demo.css.\n\n Two unavailable days are declared as data rather than written onto cells. The\n 42 cells are reused for a different date every month, so availability has to\n be keyed by the date; the controller derives each cell's aria-disabled from\n this array on every paint. A range may still span such a day — it just cannot\n end on one. %>\n<% blackout = [3.days.from_now, 4.days.from_now].map { |day| day.strftime(\"%Y-%m-%d\") } %>\n<div class=\"drp demo-card\" data-controller=\"stimeo--date-range-picker\"\n data-stimeo--date-range-picker-disabled-dates-value=\"<%= blackout.to_json %>\">\n <div class=\"drp__header\">\n <button class=\"drp__nav\" type=\"button\"\n data-action=\"click->stimeo--date-range-picker#prev\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.prev_month\") %>\">‹</button>\n <span class=\"drp__label\" id=\"drp-month\" aria-live=\"polite\"\n data-stimeo--date-range-picker-target=\"monthLabel\"></span>\n <button class=\"drp__nav\" type=\"button\"\n data-action=\"click->stimeo--date-range-picker#next\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.next_month\") %>\">›</button>\n </div>\n\n <div class=\"drp__weekdays\" aria-hidden=\"true\">\n <% t(\"components.date_range_picker.demo.weekdays\").each do |day| %>\n <span class=\"drp__weekday\"><%= day %></span>\n <% end %>\n </div>\n\n <div class=\"drp__grid\" role=\"grid\" aria-labelledby=\"drp-month\"\n data-stimeo--date-range-picker-target=\"grid\">\n <% 6.times do %>\n <div class=\"drp__row\" role=\"row\">\n <% 7.times do %>\n <button class=\"drp__cell\" type=\"button\" role=\"gridcell\" tabindex=\"-1\"\n data-stimeo--date-range-picker-target=\"cell\"\n data-action=\"click->stimeo--date-range-picker#selectDate\n mouseenter->stimeo--date-range-picker#previewTo\n focus->stimeo--date-range-picker#previewTo\n keydown->stimeo--date-range-picker#onKeydown\"></button>\n <% end %>\n </div>\n <% end %>\n </div>\n\n <div class=\"drp__presets\" role=\"group\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.presets_label\") %>\">\n <button class=\"drp__preset\" type=\"button\" data-range=\"today\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_today\") %>\n </button>\n <button class=\"drp__preset\" type=\"button\" data-range=\"last7\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_last7\") %>\n </button>\n <button class=\"drp__preset\" type=\"button\" data-range=\"thisMonth\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_this_month\") %>\n </button>\n </div>\n\n <span class=\"drp__status\" role=\"status\" aria-live=\"polite\"\n data-stimeo--date-range-picker-target=\"status\"></span>\n\n <%# Says on screen why two days are struck out. The dates move with today, so\n without this the exclusion reads as a glitch rather than a demonstration. %>\n <p class=\"drp__note\"><%= t(\"components.date_range_picker.demo.blackout_note\") %></p>\n\n <input type=\"hidden\" name=\"start_date\" data-stimeo--date-range-picker-target=\"startField\" />\n <input type=\"hidden\" name=\"end_date\" data-stimeo--date-range-picker-target=\"endField\" />\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.field\") %></span>\n <span class=\"demo-readout__value\" data-readout\n data-empty-text=\"<%= t(\"catalog.readout.empty\") %>\">—</span>\n</p>\n"
106
+ "source": "<%# Markup for the date-range-picker (range-selection calendar) demo.\n stimeo--date-range-picker reuses stimeo--calendar's month-grid navigation model and\n handles two-point start/end selection, a provisional range preview mid-selection,\n marking in-range cells, auto-swapping when start > end, applying presets, and grid\n keyboard movement (roving). Range ends are shown with aria-selected and the inside\n with data-in-range; placement and styling live in demo.css.\n\n Two unavailable days are declared as data rather than written onto cells. The\n 42 cells are reused for a different date every month, so availability has to\n be keyed by the date; the controller derives each cell's aria-disabled from\n this array on every paint. A range may still span such a day — it just cannot\n end on one. %>\n<% blackout = [3.days.from_now, 4.days.from_now].map { |day| day.strftime(\"%Y-%m-%d\") } %>\n<div class=\"drp demo-card\" data-controller=\"stimeo--date-range-picker\"\n data-stimeo--date-range-picker-disabled-dates-value=\"<%= blackout.to_json %>\">\n <div class=\"drp__header\">\n <button class=\"drp__nav\" type=\"button\"\n data-action=\"click->stimeo--date-range-picker#prev\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.prev_month\") %>\">‹</button>\n <span class=\"drp__label\" id=\"drp-month\" aria-live=\"polite\"\n data-stimeo--date-range-picker-target=\"monthLabel\"></span>\n <button class=\"drp__nav\" type=\"button\"\n data-action=\"click->stimeo--date-range-picker#next\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.next_month\") %>\">›</button>\n </div>\n\n <div class=\"drp__weekdays\" aria-hidden=\"true\">\n <% t(\"components.date_range_picker.demo.weekdays\").each do |day| %>\n <span class=\"drp__weekday\"><%= day %></span>\n <% end %>\n </div>\n\n <div class=\"drp__grid\" role=\"grid\" aria-labelledby=\"drp-month\"\n data-stimeo--date-range-picker-target=\"grid\">\n <% 6.times do %>\n <div class=\"drp__row\" role=\"row\">\n <% 7.times do %>\n <button class=\"drp__cell\" type=\"button\" role=\"gridcell\" tabindex=\"-1\"\n data-stimeo--date-range-picker-target=\"cell\"\n data-action=\"click->stimeo--date-range-picker#selectDate\n mouseenter->stimeo--date-range-picker#previewTo\n focus->stimeo--date-range-picker#previewTo\n keydown->stimeo--date-range-picker#onKeydown\"></button>\n <% end %>\n </div>\n <% end %>\n </div>\n\n <div class=\"drp__presets\" role=\"group\"\n aria-label=\"<%= t(\"components.date_range_picker.demo.presets_label\") %>\">\n <button class=\"drp__preset\" type=\"button\" data-range=\"today\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_today\") %>\n </button>\n <button class=\"drp__preset\" type=\"button\" data-range=\"last7\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_last7\") %>\n </button>\n <button class=\"drp__preset\" type=\"button\" data-range=\"thisMonth\"\n data-action=\"click->stimeo--date-range-picker#applyPreset\">\n <%= t(\"components.date_range_picker.demo.preset_this_month\") %>\n </button>\n </div>\n\n <span class=\"drp__status\" role=\"status\" aria-live=\"polite\"\n data-stimeo--date-range-picker-target=\"status\"></span>\n\n <%# Says on screen why two days are struck out. The dates move with today, so\n without this the exclusion reads as a glitch rather than a demonstration. %>\n <p class=\"drp__note\"><%= t(\"components.date_range_picker.demo.blackout_note\") %></p>\n\n <input type=\"hidden\" name=\"start_date\" data-stimeo--date-range-picker-target=\"startField\" />\n <input type=\"hidden\" name=\"end_date\" data-stimeo--date-range-picker-target=\"endField\" />\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.field\") %></span>\n <span class=\"demo-readout__value\" data-readout\n data-empty-text=\"<%= t(\"catalog.readout.empty\") %>\">—</span>\n</p>\n"
107
107
  },
108
108
  "stimeo--dialog": {
109
109
  "file": "examples/dialog/_demo.html.erb",
@@ -127,7 +127,7 @@
127
127
  },
128
128
  "stimeo--dropdown": {
129
129
  "file": "examples/dropdown/_demo.html.erb",
130
- "source": "<%# Markup for the dropdown demo.\n The single source used for both the live render and the copy-paste code.\n Connect to stimeo--dropdown via data-controller / data-*-target / data-action.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dropdown\" data-controller=\"stimeo--dropdown\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n aria-expanded=\"false\"\n data-stimeo--dropdown-target=\"trigger\"\n data-action=\"click->stimeo--dropdown#toggle\">\n <%= t(\"components.dropdown.demo.trigger\") %>\n </button>\n\n <%# The toggled menu. Starts hidden (closed).\n Each link uses click->stimeo--dropdown#close to close the menu on selection.\n (The links are inside the controller element, so an outside click won't close\n it — wire it explicitly.) %>\n <ul class=\"dropdown__menu\" data-stimeo--dropdown-target=\"menu\" hidden>\n <li><a class=\"demo-menuitem\" href=\"#profile\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.profile\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#settings\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.settings\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#sign-out\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.sign_out\"\n ) %></a></li>\n </ul>\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
130
+ "source": "<%# Markup for the dropdown demo.\n The single source used for both the live render and the copy-paste code.\n Connect to stimeo--dropdown via data-controller / data-*-target / data-action.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dropdown\" data-controller=\"stimeo--dropdown\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n aria-expanded=\"false\"\n data-stimeo--dropdown-target=\"trigger\"\n data-action=\"click->stimeo--dropdown#toggle\">\n <%= t(\"components.dropdown.demo.trigger\") %>\n </button>\n\n <%# The toggled menu. Starts hidden (closed).\n Each placeholder link closes the menu and prevents navigation so the event\n readout stays visible. Real destination links can omit :prevent.\n (The links are inside the controller element, so an outside click won't close\n it — wire it explicitly.) %>\n <ul class=\"dropdown__menu\" data-stimeo--dropdown-target=\"menu\" hidden>\n <li><a class=\"demo-menuitem\" href=\"#profile\"\n data-action=\"click->stimeo--dropdown#close:prevent\"><%= t(\n \"components.dropdown.demo.profile\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#settings\"\n data-action=\"click->stimeo--dropdown#close:prevent\"><%= t(\n \"components.dropdown.demo.settings\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#sign-out\"\n data-action=\"click->stimeo--dropdown#close:prevent\"><%= t(\n \"components.dropdown.demo.sign_out\"\n ) %></a></li>\n </ul>\n</div>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
131
131
  },
132
132
  "stimeo--editable": {
133
133
  "file": "examples/editable/_demo.html.erb",
@@ -151,7 +151,7 @@
151
151
  },
152
152
  "stimeo--focus": {
153
153
  "file": "examples/focus/_demo.html.erb",
154
- "source": "<%# Focus-scope demo: the Activate button sits outside the scope, so it invokes the\n controller's activate action through the playground's exposed Stimulus app\n (window.Stimulus) in demo.js. Once active, Tab cycles within the box, focus lands on\n the initial target, and Escape or the Release button (a descendant, wired with a plain\n data-action) ends it and restores focus. inert is left off so the soft boundary does\n not isolate the rest of the catalog page. The library only manages focus and reflects\n data-focus-trapped; demo.css owns the look. %>\n<div class=\"focus-demo\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-focus-demo-activate\n aria-pressed=\"false\"\n data-activate-label=\"<%= t('components.focus.demo.activate') %>\"\n data-deactivate-label=\"<%= t('components.focus.demo.deactivate') %>\">\n <%= t(\"components.focus.demo.activate\") %>\n </button>\n\n <div class=\"focus-demo__scope\" data-controller=\"stimeo--focus\">\n <p class=\"focus-demo__hint\"><%= t(\"components.focus.demo.hint\") %></p>\n <label class=\"focus-demo__field\">\n <span><%= t(\"components.focus.demo.field\") %></span>\n <input type=\"text\" class=\"demo-input\" data-stimeo--focus-target=\"initial\" />\n </label>\n <div class=\"focus-demo__bar\">\n <button type=\"button\" class=\"demo-trigger\"><%= t(\"components.focus.demo.inner\") %></button>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"click->stimeo--focus#deactivate\">\n <%= t(\"components.focus.demo.release\") %>\n </button>\n </div>\n </div>\n</div>\n"
154
+ "source": "<%# stimeo--focus demo: the Activate button sits outside the scope, so it invokes the\n controller's activate action through the playground's exposed Stimulus app\n (window.Stimulus) in demo.js. Once active, Tab cycles within the box, focus lands on\n the initial target, and Escape or the Release button (a descendant, wired with a plain\n data-action) ends it and restores focus. inert is left off so the soft boundary does\n not isolate the rest of the catalog page. The library only manages focus and reflects\n data-focus-trapped; demo.css owns the look. %>\n<div class=\"focus-demo\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-focus-demo-activate\n aria-pressed=\"false\"\n data-activate-label=\"<%= t('components.focus.demo.activate') %>\"\n data-deactivate-label=\"<%= t('components.focus.demo.deactivate') %>\">\n <%= t(\"components.focus.demo.activate\") %>\n </button>\n\n <div class=\"focus-demo__scope\" data-controller=\"stimeo--focus\">\n <p class=\"focus-demo__hint\"><%= t(\"components.focus.demo.hint\") %></p>\n <label class=\"focus-demo__field\">\n <span><%= t(\"components.focus.demo.field\") %></span>\n <input type=\"text\" class=\"demo-input\" data-stimeo--focus-target=\"initial\" />\n </label>\n <div class=\"focus-demo__bar\">\n <button type=\"button\" class=\"demo-trigger\"><%= t(\"components.focus.demo.inner\") %></button>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"click->stimeo--focus#deactivate\">\n <%= t(\"components.focus.demo.release\") %>\n </button>\n </div>\n </div>\n</div>\n"
155
155
  },
156
156
  "stimeo--form-field": {
157
157
  "file": "examples/form_field/_demo.html.erb",
@@ -223,7 +223,7 @@
223
223
  },
224
224
  "stimeo--navigation-menu": {
225
225
  "file": "examples/navigation_menu/_demo.html.erb",
226
- "source": "<%# Markup for the navigation-menu demo.\n Two arrangements are shown.\n\n 1. Click-only (the default): a global nav where each top item opens/closes a\n sub-panel (a set of links). The contents are links and don't use role=\"menu\"\n (APG Disclosure navigation). The library handles syncing aria-expanded,\n single-open behavior, closing on Escape / outside click / focus leaving, and\n moving between top items with the left/right keys.\n 2. Hover (opt-in): openOnHover plus a hoverArea wrapper — the APG \"Disclosure\n Navigation with Top-Level Links\" arrangement, where each <li> holds a real\n link, a small disclosure button, and the panel. Hovering anywhere over the\n <li> opens that item's panel; every keyboard path stays identical.\n\n Panel placement and styling are the consumer's CSS (demo.css). %>\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.click_caption\") %></p>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.label\") %>\">\n <ul class=\"nav-menu__list\">\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-products\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.products.label\") %>\n </button>\n <div id=\"nav-menu-products\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#analytics\"><%= t(\n \"components.navigation_menu.demo.products.links.analytics\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#automation\"><%= t(\n \"components.navigation_menu.demo.products.links.automation\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#reports\"><%= t(\n \"components.navigation_menu.demo.products.links.reports\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-company\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.company.label\") %>\n </button>\n <div id=\"nav-menu-company\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#about\"><%= t(\n \"components.navigation_menu.demo.company.links.about\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#careers\"><%= t(\n \"components.navigation_menu.demo.company.links.careers\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.hover_caption\") %></p>\n<%# openOnHover is opt-in; hoverArea widens the hover region to the whole <li>, so\n hovering the top-level link (not just the disclosure button) opens the panel.\n The button keeps its own accessible name so keyboard and SR users can tell the\n link and its disclosure apart. %>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n data-stimeo--navigation-menu-open-on-hover-value=\"true\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.hover.label\") %>\">\n <ul class=\"nav-menu__list\">\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#solutions\">\n <%= t(\"components.navigation_menu.demo.hover.solutions.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-solutions\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-solutions\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#retail\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.retail\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#finance\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.finance\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#resources\">\n <%= t(\"components.navigation_menu.demo.hover.resources.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-resources\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.resources.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-resources\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#guides\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.guides\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#blog\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.blog\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
226
+ "source": "<%# Markup for the navigation-menu demo.\n Two arrangements are shown.\n\n 1. Click-only (the default): a global nav where each top item opens/closes a\n sub-panel (a set of links). The contents are links and don't use role=\"menu\"\n (APG Disclosure navigation). The library handles syncing aria-expanded,\n single-open behavior, closing on Escape / outside click / focus leaving, and\n moving between top items with the left/right keys.\n 2. Hover (opt-in): openOnHover plus a hoverArea wrapper — the APG \"Disclosure\n Navigation with Top-Level Links\" arrangement, where each <li> holds a real\n link, a small disclosure button, and the panel. Hovering anywhere over the\n <li> opens that item's panel; every keyboard path stays identical.\n\n Panel placement and styling are the consumer's CSS (demo.css). %>\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.click_caption\") %></p>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.label\") %>\">\n <ul class=\"nav-menu__list\" data-stimeo--navigation-menu-target=\"list\">\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-products\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.products.label\") %>\n </button>\n <div id=\"nav-menu-products\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#analytics\"><%= t(\n \"components.navigation_menu.demo.products.links.analytics\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#automation\"><%= t(\n \"components.navigation_menu.demo.products.links.automation\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#reports\"><%= t(\n \"components.navigation_menu.demo.products.links.reports\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-company\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.company.label\") %>\n </button>\n <div id=\"nav-menu-company\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#about\"><%= t(\n \"components.navigation_menu.demo.company.links.about\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#careers\"><%= t(\n \"components.navigation_menu.demo.company.links.careers\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.hover_caption\") %></p>\n<%# openOnHover is opt-in; hoverArea widens the hover region to the whole <li>, so\n hovering the top-level link (not just the disclosure button) opens the panel.\n The button keeps its own accessible name so keyboard and SR users can tell the\n link and its disclosure apart. %>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n data-stimeo--navigation-menu-open-on-hover-value=\"true\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.hover.label\") %>\">\n <ul class=\"nav-menu__list\" data-stimeo--navigation-menu-target=\"list\">\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#solutions\">\n <%= t(\"components.navigation_menu.demo.hover.solutions.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-solutions\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-solutions\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#retail\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.retail\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#finance\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.finance\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#resources\">\n <%= t(\"components.navigation_menu.demo.hover.resources.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-resources\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.resources.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-resources\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#guides\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.guides\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#blog\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.blog\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n\n<%# Readout: what the component just reported. Purely visual — the component\n already announces what a screen reader needs, so this line is aria-hidden to\n keep it from being read twice. The value is a code fragment (API identifiers\n and values), so only the label is translated. %>\n<p class=\"demo-readout\" aria-hidden=\"true\">\n <span><%= t(\"catalog.readout.events\") %></span>\n <span class=\"demo-readout__value\" data-readout>—</span>\n</p>\n"
227
227
  },
228
228
  "stimeo--nested-form": {
229
229
  "file": "examples/nested_form/_demo.html.erb",
@@ -251,11 +251,11 @@
251
251
  },
252
252
  "stimeo--overflow-menu": {
253
253
  "file": "examples/overflow_menu/_demo.html.erb",
254
- "source": "<%# Overflow-menu demo: drag the width slider to shrink the toolbar — the controller\n measures the items and banks the lowest-priority ones into the More menu (delegated\n to stimeo--menu), moving them back as space returns. The items need no data-action of\n their own: Menu handles clicks and keys from its own element, so an item becomes\n operable the moment it lands in the menu. Only the More trigger is bound. The library\n moves items and sets data-overflowing / data-overflow-count; demo.css owns the look. %>\n<div class=\"overflow-demo\">\n <label class=\"demo-width-control\">\n <span><%= t(\"components.overflow_menu.demo.width\") %></span>\n <input type=\"range\" min=\"220\" max=\"640\" value=\"640\" data-overflow-demo-width>\n </label>\n\n <div\n class=\"overflow-demo__bar\"\n data-controller=\"stimeo--overflow-menu\"\n <%# A group, not a toolbar: these buttons stay in the normal Tab order. The\n single-tab-stop roving an APG Toolbar implies belongs to stimeo--toolbar,\n which this controller does not provide. %>\n role=\"group\"\n aria-label=\"<%= t(\"components.overflow_menu.name\") %>\">\n <div class=\"overflow-demo__items\" data-stimeo--overflow-menu-target=\"items\">\n <% %w[save edit share archive delete].each_with_index do |id, i| %>\n <button\n type=\"button\"\n class=\"demo-menuitem overflow-demo__item\"\n <%= \"data-priority=\\\"#{i + 1}\\\"\".html_safe if i < 3 %>>\n <%= t(\"components.overflow_menu.demo.#{id}\") %>\n </button>\n <% end %>\n </div>\n\n <div\n class=\"overflow-demo__more\"\n data-controller=\"stimeo--menu\"\n data-stimeo--overflow-menu-target=\"more\"\n hidden>\n <button\n id=\"overflow-menu-trigger\"\n type=\"button\"\n class=\"demo-trigger overflow-demo__trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.overflow_menu.demo.more\") %>\n </button>\n <%# A div, not a ul: the controller appends the items themselves, and a ul may only\n hold li children. One container shape keeps the markup contract unambiguous. %>\n <div class=\"overflow-demo__menu\" role=\"menu\" aria-labelledby=\"overflow-menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden></div>\n </div>\n </div>\n</div>\n"
254
+ "source": "<%# Overflow-menu demo: drag the width slider to shrink the bar — the controller\n measures the items and banks the lowest-priority ones into the More menu (delegated\n to stimeo--menu), moving them back as space returns. The items need no data-action of\n their own: stimeo--menu handles clicks and keys from its own element, so an item becomes\n operable the moment it lands in the menu. Only the More trigger is bound. The library\n moves items and sets data-overflowing / data-overflow-count; demo.css owns the look. %>\n<div class=\"overflow-demo\">\n <label class=\"demo-width-control\">\n <span><%= t(\"components.overflow_menu.demo.width\") %></span>\n <input type=\"range\" min=\"220\" max=\"640\" value=\"640\" data-overflow-demo-width>\n </label>\n\n <div\n class=\"overflow-demo__bar\"\n data-controller=\"stimeo--overflow-menu\"\n <%# A group, not a toolbar: these buttons stay in the normal Tab order. The\n single-tab-stop roving an APG Toolbar implies belongs to stimeo--toolbar,\n which this controller does not provide. %>\n role=\"group\"\n aria-label=\"<%= t(\"components.overflow_menu.name\") %>\">\n <div class=\"overflow-demo__items\" data-stimeo--overflow-menu-target=\"items\">\n <% %w[save edit share archive delete].each_with_index do |id, i| %>\n <button\n type=\"button\"\n class=\"demo-menuitem overflow-demo__item\"\n <%= \"data-priority=\\\"#{i + 1}\\\"\".html_safe if i < 3 %>>\n <%= t(\"components.overflow_menu.demo.#{id}\") %>\n </button>\n <% end %>\n </div>\n\n <div\n class=\"overflow-demo__more\"\n data-controller=\"stimeo--menu\"\n data-stimeo--overflow-menu-target=\"more\"\n hidden>\n <button\n id=\"overflow-menu-trigger\"\n type=\"button\"\n class=\"demo-trigger overflow-demo__trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.overflow_menu.demo.more\") %>\n </button>\n <%# A div, not a ul: the controller appends the items themselves, and a ul may only\n hold li children. One container shape keeps the markup contract unambiguous. %>\n <div class=\"overflow-demo__menu\" role=\"menu\" aria-labelledby=\"overflow-menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden></div>\n </div>\n </div>\n</div>\n"
255
255
  },
256
256
  "stimeo--pagination": {
257
257
  "file": "examples/pagination/_demo.html.erb",
258
- "source": "<%# Markup for the pagination demo.\n Built on nav + aria-label and aria-current=\"page\", the library handles current-page\n management, boundary control of the prev/next buttons, and firing the change event.\n Data fetching and URL rewriting are the consumer's (here demo.js just updates the\n results area). %>\n<nav\n class=\"pagination\"\n data-controller=\"stimeo--pagination\"\n aria-label=\"<%= t(\"components.pagination.demo.label\") %>\"\n data-stimeo--pagination-page-value=\"1\"\n data-stimeo--pagination-total-value=\"5\">\n <button\n type=\"button\"\n class=\"pagination__button\"\n data-stimeo--pagination-target=\"prev\"\n data-action=\"click->stimeo--pagination#prev\">\n <%= t(\"components.pagination.demo.prev\") %>\n </button>\n <% (1..5).each do |page| %>\n <button\n type=\"button\"\n class=\"pagination__button pagination__page\"\n data-page=\"<%= page %>\"\n <%= \"aria-current=\\\"page\\\"\".html_safe if page == 1 %>\n data-stimeo--pagination-target=\"page\"\n data-action=\"click->stimeo--pagination#select\"><%= page %></button>\n <% end %>\n <button\n type=\"button\"\n class=\"pagination__button\"\n data-stimeo--pagination-target=\"next\"\n data-action=\"click->stimeo--pagination#next\">\n <%= t(\"components.pagination.demo.next\") %>\n </button>\n</nav>\n\n<p\n class=\"pagination__status\"\n data-pagination-status\n data-template=\"<%= t(\"components.pagination.demo.status_template\") %>\"\n aria-live=\"polite\">\n <%= t(\"components.pagination.demo.status_template\", page: 1, total: 5) %>\n</p>\n"
258
+ "source": "<%# Markup for the pagination demo.\n Built on nav + aria-label and aria-current=\"page\", the library handles current-page\n management, boundary control of the prev/next buttons, and firing the change event.\n Data fetching and URL rewriting are the consumer's (here demo.js just updates the\n results area). %>\n<nav\n class=\"pagination\"\n data-controller=\"stimeo--pagination\"\n aria-label=\"<%= t(\"components.pagination.demo.label\") %>\"\n data-stimeo--pagination-page-value=\"1\"\n data-stimeo--pagination-total-value=\"5\">\n <button\n type=\"button\"\n class=\"pagination__button\"\n data-stimeo--pagination-target=\"prev\"\n data-action=\"click->stimeo--pagination#prev\">\n <%= t(\"components.pagination.demo.prev\") %>\n </button>\n <% (1..5).each do |page| %>\n <button\n type=\"button\"\n class=\"pagination__button pagination__page\"\n data-page=\"<%= page %>\"\n <%= \"aria-current=\\\"page\\\"\".html_safe if page == 1 %>\n data-stimeo--pagination-target=\"page\"\n data-action=\"click->stimeo--pagination#select\"><%= page %></button>\n <% end %>\n <button\n type=\"button\"\n class=\"pagination__button\"\n data-stimeo--pagination-target=\"next\"\n data-action=\"click->stimeo--pagination#next\">\n <%= t(\"components.pagination.demo.next\") %>\n </button>\n</nav>\n\n<p\n class=\"pagination__status\"\n data-pagination-status\n data-template=\"<%= t(\"components.pagination.demo.status_template\") %>\"\n aria-live=\"polite\">\n <%= t(\"components.pagination.demo.initial_status\", page: 1, total: 5) %>\n</p>\n"
259
259
  },
260
260
  "stimeo--password-reveal": {
261
261
  "file": "examples/password_reveal/_demo.html.erb",
@@ -271,7 +271,7 @@
271
271
  },
272
272
  "stimeo--pointer-drag": {
273
273
  "file": "examples/pointer_drag/_demo.html.erb",
274
- "source": "<%# pointer-drag: the primitive normalizes the drag lifecycle (threshold, axis\n lock, pointer capture, touch-action, cancel) and adds a keyboard alternative.\n With the opt-in follow value it also moves the card itself — translate on\n move, committed on drop, restored on cancel — so the consumer JS below only\n mirrors the signal into the status line. The card can travel out of the\n dashed area, which the area clips; the reset action brings it back, since\n follow's committed offset is internal state a consumer cannot clear. The\n button sits outside the controller element, so its click reaches the action\n as a custom event the element's own data-action maps. %>\n<div class=\"pointer-drag-demo\">\n <p class=\"pointer-drag-demo__hint\"><%= t(\"components.pointer_drag.demo.hint\") %></p>\n <div class=\"pointer-drag-demo__area\">\n <button type=\"button\" class=\"pointer-drag-demo__card\" id=\"pointer-drag-card\"\n data-controller=\"stimeo--pointer-drag\"\n data-stimeo--pointer-drag-follow-value=\"true\"\n data-action=\"demo:reset->stimeo--pointer-drag#reset\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\"\n aria-hidden=\"true\">\n <circle cx=\"9\" cy=\"6\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"6\" r=\"1.5\"></circle>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"12\" r=\"1.5\"></circle>\n <circle cx=\"9\" cy=\"18\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"18\" r=\"1.5\"></circle>\n </svg>\n <%= t(\"components.pointer_drag.demo.card_label\") %>\n </button>\n </div>\n <p class=\"pointer-drag-demo__actions\">\n <button type=\"button\" class=\"demo-trigger\" data-pointer-drag-reset>\n <%= t(\"components.pointer_drag.demo.reset_label\") %>\n </button>\n </p>\n <p class=\"pointer-drag-demo__status\" data-pointer-drag-status aria-live=\"polite\"\n data-moved-template=\"<%= t(\"components.pointer_drag.demo.moved_template\") %>\"\n data-idle-text=\"<%= t(\"components.pointer_drag.demo.idle_text\") %>\"></p>\n</div>\n"
274
+ "source": "<%# pointer-drag: the primitive normalizes the drag lifecycle (threshold, axis\n lock, pointer capture, touch-action, cancel) and adds a keyboard alternative.\n With the opt-in follow value it also moves the card itself — translate on\n move, committed on drop, restored on cancel — so the consumer JS below only\n mirrors the signal into the status line. The card can travel out of the\n dashed area, which the area clips; the reset action brings it back, ending\n any drag in flight and clearing the inline translate follow moved it with.\n With follow on, a translate the page writes itself in the <x>px <y>px form\n works too, since the next drag reads its start back from the inline\n translate. The button sits outside the controller element, so its click\n reaches the action as a custom event the element's own data-action maps. %>\n<div class=\"pointer-drag-demo\">\n <p class=\"pointer-drag-demo__hint\"><%= t(\"components.pointer_drag.demo.hint\") %></p>\n <div class=\"pointer-drag-demo__area\">\n <button type=\"button\" class=\"pointer-drag-demo__card\" id=\"pointer-drag-card\"\n data-controller=\"stimeo--pointer-drag\"\n data-stimeo--pointer-drag-follow-value=\"true\"\n data-action=\"demo:reset->stimeo--pointer-drag#reset\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\" stroke=\"none\"\n aria-hidden=\"true\">\n <circle cx=\"9\" cy=\"6\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"6\" r=\"1.5\"></circle>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"12\" r=\"1.5\"></circle>\n <circle cx=\"9\" cy=\"18\" r=\"1.5\"></circle><circle cx=\"15\" cy=\"18\" r=\"1.5\"></circle>\n </svg>\n <%= t(\"components.pointer_drag.demo.card_label\") %>\n </button>\n </div>\n <p class=\"pointer-drag-demo__actions\">\n <button type=\"button\" class=\"demo-trigger\" data-pointer-drag-reset>\n <%= t(\"components.pointer_drag.demo.reset_label\") %>\n </button>\n </p>\n <p class=\"pointer-drag-demo__status\" data-pointer-drag-status aria-live=\"polite\"\n data-moved-template=\"<%= t(\"components.pointer_drag.demo.moved_template\") %>\"\n data-idle-text=\"<%= t(\"components.pointer_drag.demo.idle_text\") %>\"></p>\n</div>\n"
275
275
  },
276
276
  "stimeo--popover": {
277
277
  "file": "examples/popover/_demo.html.erb",
@@ -283,7 +283,7 @@
283
283
  },
284
284
  "stimeo--presence": {
285
285
  "file": "examples/presence/_demo.html.erb",
286
- "source": "<%# presence: a who's-here roster with zero server state. Each client beacons\n { id, name } through PresenceChannel on a heartbeat; every client expires\n silent peers locally and re-announces itself to newcomers, so the roster\n converges in one round trip. Your own beacons are ignored (echo\n suppression), so open this page in a second tab to see a peer appear — then\n close it or navigate it away for the leave notice (sent best-effort on\n disconnect and pagehide); a lost notice is caught by the timeout expiry. The id/name\n pair is randomized per render, which is fine for a demo; a real app renders\n current_user.id / current_user.name. The count templates (data-zero/-one/\n -other) keep the text localizable; %{count} is substituted by the\n controller. Heartbeat/timeout are shortened so leaving is quick to observe. %>\n<% token = SecureRandom.hex(4) %>\n<% guest = t(\"components.presence.demo.guest_name\", token: token.first(4)) %>\n<div class=\"presence-demo\"\n data-controller=\"stimeo--presence\"\n data-stimeo--presence-channel-value=\"PresenceChannel\"\n data-stimeo--presence-params-value='{\"room\":\"<%= PresenceChannel::ROOM %>\"}'\n data-stimeo--presence-id-value=\"<%= token %>\"\n data-stimeo--presence-name-value=\"<%= guest %>\"\n data-stimeo--presence-heartbeat-value=\"5000\"\n data-stimeo--presence-timeout-value=\"12000\">\n <p class=\"presence-demo__hint\"><%= t(\"components.presence.demo.hint\") %></p>\n <p class=\"presence-demo__you\"><%= t(\"components.presence.demo.you_are\", name: guest) %></p>\n <p class=\"presence-demo__count\"\n data-stimeo--presence-target=\"count\"\n data-zero=\"<%= t(\"components.presence.demo.count_zero\") %>\"\n data-one=\"<%= t(\"components.presence.demo.count_one\") %>\"\n data-other=\"<%= t(\"components.presence.demo.count_other\") %>\"></p>\n <ul class=\"presence-demo__list\"\n aria-label=\"<%= t(\"components.presence.demo.list_label\") %>\"\n data-stimeo--presence-target=\"list\"></ul>\n <template data-stimeo--presence-target=\"template\">\n <li class=\"presence-demo__peer\">\n <span class=\"presence-demo__dot\" aria-hidden=\"true\"></span>\n <span data-presence-name></span>\n </li>\n </template>\n</div>\n"
286
+ "source": "<%# presence: a who's-here roster with zero server state. Each client beacons\n { id, name } through PresenceChannel on a heartbeat; every client expires\n silent peers locally and re-announces itself to newcomers, so the roster\n converges in one round trip. Your own beacons are ignored (echo\n suppression), so open this page in a second tab to see a peer appear — then\n close it or navigate it away for the leave notice (sent best-effort on\n disconnect and pagehide); a lost notice is caught by the timeout expiry. The id/name\n pair is randomized per render, which is fine for a demo; a real app renders\n current_user.id / current_user.name. The count templates (data-zero/-one/\n -other) keep the text localizable; {count} is substituted by the\n controller. Heartbeat/timeout are shortened so leaving is quick to observe. %>\n<% token = SecureRandom.hex(4) %>\n<% guest = t(\"components.presence.demo.guest_name\", token: token.first(4)) %>\n<div class=\"presence-demo\"\n data-controller=\"stimeo--presence\"\n data-stimeo--presence-channel-value=\"PresenceChannel\"\n data-stimeo--presence-params-value='{\"room\":\"<%= PresenceChannel::ROOM %>\"}'\n data-stimeo--presence-id-value=\"<%= token %>\"\n data-stimeo--presence-name-value=\"<%= guest %>\"\n data-stimeo--presence-heartbeat-value=\"5000\"\n data-stimeo--presence-timeout-value=\"12000\">\n <p class=\"presence-demo__hint\"><%= t(\"components.presence.demo.hint\") %></p>\n <p class=\"presence-demo__you\"><%= t(\"components.presence.demo.you_are\", name: guest) %></p>\n <p class=\"presence-demo__count\"\n data-stimeo--presence-target=\"count\"\n data-zero=\"<%= t(\"components.presence.demo.count_zero\") %>\"\n data-one=\"<%= t(\"components.presence.demo.count_one\") %>\"\n data-other=\"<%= t(\"components.presence.demo.count_other\") %>\"></p>\n <ul class=\"presence-demo__list\"\n aria-label=\"<%= t(\"components.presence.demo.list_label\") %>\"\n data-stimeo--presence-target=\"list\"></ul>\n <template data-stimeo--presence-target=\"template\">\n <li class=\"presence-demo__peer\">\n <span class=\"presence-demo__dot\" aria-hidden=\"true\"></span>\n <span data-presence-name></span>\n </li>\n </template>\n</div>\n"
287
287
  },
288
288
  "stimeo--preview-guard": {
289
289
  "file": "examples/preview_guard/_demo.html.erb",
@@ -415,7 +415,7 @@
415
415
  },
416
416
  "stimeo--time-picker": {
417
417
  "file": "examples/time_picker/_demo.html.erb",
418
- "source": "<%# Markup for the time-picker (time selection) demo.\n stimeo--time-picker treats each segment (hour / minute / AM·PM) as an APG Spinbutton,\n handling arrow-key increment/decrement, edge wrap and carry (minute→hour), left/right\n segment movement, and direct numeric entry, and reflects the composed 24-hour value\n (HH:MM) into a hidden input. Each segment is an independent tab stop. %>\n<div\n class=\"time-picker\"\n data-controller=\"stimeo--time-picker\"\n data-stimeo--time-picker-hour-cycle-value=\"12\"\n data-stimeo--time-picker-step-value=\"5\"\n role=\"group\"\n aria-label=\"<%= t(\"components.time_picker.demo.group_label\") %>\">\n <span class=\"time-picker__segment\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.hours\") %>\"\n tabindex=\"0\" aria-valuenow=\"9\" aria-valuemin=\"1\" aria-valuemax=\"12\" aria-valuetext=\"09\"\n data-segment=\"hour\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\">09</span>\n <span class=\"time-picker__colon\" aria-hidden=\"true\">:</span>\n <span class=\"time-picker__segment\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.minutes\") %>\"\n tabindex=\"0\" aria-valuenow=\"30\" aria-valuemin=\"0\" aria-valuemax=\"59\" aria-valuetext=\"30\"\n data-segment=\"minute\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\">30</span>\n <span class=\"time-picker__segment time-picker__segment--meridiem\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.meridiem\") %>\"\n tabindex=\"0\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"1\" aria-valuetext=\"AM\"\n data-segment=\"meridiem\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\">AM</span>\n\n <input type=\"hidden\" name=\"time\" data-stimeo--time-picker-target=\"field\" />\n</div>\n"
418
+ "source": "<%# Markup for the time-picker (time selection) demo.\n stimeo--time-picker treats each segment (hour / minute / AM·PM) as an APG Spinbutton,\n handling arrow-key increment/decrement, edge wrap and carry (minute→hour), left/right\n segment movement, and direct numeric entry, and reflects the composed 24-hour value\n (HH:MM) into a hidden input. Each segment is an independent tab stop. %>\n<% zero_based_hour = I18n.locale == :ja %>\n<div\n class=\"time-picker\"\n data-controller=\"stimeo--time-picker\"\n data-stimeo--time-picker-hour-cycle-value=\"12\"\n data-stimeo--time-picker-hour12-zero-based-value=\"<%= zero_based_hour %>\"\n data-stimeo--time-picker-step-value=\"5\"\n data-stimeo--time-picker-am-label-value=\"<%= t(\"components.time_picker.demo.am\") %>\"\n data-stimeo--time-picker-pm-label-value=\"<%= t(\"components.time_picker.demo.pm\") %>\"\n role=\"group\"\n aria-label=\"<%= t(\"components.time_picker.demo.group_label\") %>\">\n <span class=\"time-picker__segment\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.hours\") %>\"\n tabindex=\"0\" aria-valuenow=\"9\" aria-valuetext=\"09\"\n aria-valuemin=\"<%= zero_based_hour ? 0 : 1 %>\"\n aria-valuemax=\"<%= zero_based_hour ? 11 : 12 %>\"\n data-segment=\"hour\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\">09</span>\n <span class=\"time-picker__colon\" aria-hidden=\"true\">:</span>\n <span class=\"time-picker__segment\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.minutes\") %>\"\n tabindex=\"0\" aria-valuenow=\"30\" aria-valuemin=\"0\" aria-valuemax=\"59\" aria-valuetext=\"30\"\n data-segment=\"minute\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\">30</span>\n <span class=\"time-picker__segment time-picker__segment--meridiem\" role=\"spinbutton\"\n aria-label=\"<%= t(\"components.time_picker.demo.meridiem\") %>\"\n tabindex=\"0\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"1\"\n aria-valuetext=\"<%= t(\"components.time_picker.demo.am\") %>\"\n data-segment=\"meridiem\" data-stimeo--time-picker-target=\"segment\"\n data-action=\"keydown->stimeo--time-picker#onKeydown\"><%= t(\n \"components.time_picker.demo.am\"\n ) %></span>\n\n <input type=\"hidden\" name=\"time\" data-stimeo--time-picker-target=\"field\" />\n</div>\n"
419
419
  },
420
420
  "stimeo--toast": {
421
421
  "file": "examples/toast/_demo.html.erb",
@@ -443,7 +443,7 @@
443
443
  },
444
444
  "stimeo--typing-indicator": {
445
445
  "file": "examples/typing_indicator/_demo.html.erb",
446
- "source": "<%# typing-indicator: the state lives on the server stream, not in client memory.\n Typing in the composer broadcasts a throttled signal through TypingChannel;\n every OTHER client shows \"X is typing…\" in the visible status slot and clears\n it automatically after silence. Own echoes are suppressed by design, so open\n this page in a second tab to see the indicator. The guest name is randomized\n per render so each tab is a distinct \"user\" (a real app passes\n current_user.name). The data-one/data-many templates keep the announcement\n localizable; %{name}/%{names} are substituted by the controller. The status slot is\n plain visible copy — assistive tech is reached through the page's shared announcer\n instead, so the announce-one/announce-many values carry the spoken wording ({name}\n syntax, not %{}). Leaving them off shows the indicator without announcing it. %>\n<% guest = t(\"components.typing_indicator.demo.guest_name\", token: SecureRandom.hex(2)) %>\n<% announce_one = t(\"components.typing_indicator.demo.announce_one\") %>\n<% announce_many = t(\"components.typing_indicator.demo.announce_many\") %>\n<div class=\"typing-demo\"\n data-controller=\"stimeo--typing-indicator\"\n data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n data-stimeo--typing-indicator-params-value='{\"room\":\"<%= TypingChannel::ROOM %>\"}'\n data-stimeo--typing-indicator-name-value=\"<%= guest %>\"\n data-stimeo--typing-indicator-timeout-value=\"3000\"\n data-stimeo--typing-indicator-throttle-value=\"1000\"\n data-stimeo--typing-indicator-announce-one-text-value=\"<%= announce_one %>\"\n data-stimeo--typing-indicator-announce-many-text-value=\"<%= announce_many %>\">\n <p class=\"typing-demo__hint\"><%= t(\"components.typing_indicator.demo.hint\") %></p>\n <p class=\"typing-demo__you\"><%= t(\"components.typing_indicator.demo.you_are\", name: guest) %></p>\n <label class=\"typing-demo__label\" for=\"typing-demo-input\">\n <%= t(\"components.typing_indicator.demo.input_label\") %>\n </label>\n <textarea id=\"typing-demo-input\" class=\"demo-input typing-demo__input\" rows=\"3\"\n data-stimeo--typing-indicator-target=\"input\"></textarea>\n <p class=\"typing-demo__status\"\n data-stimeo--typing-indicator-target=\"status\"\n data-one=\"<%= t(\"components.typing_indicator.demo.one\") %>\"\n data-many=\"<%= t(\"components.typing_indicator.demo.many\") %>\"></p>\n</div>\n"
446
+ "source": "<%# typing-indicator: the state lives on the server stream, not in client memory.\n Typing in the composer broadcasts a throttled signal through TypingChannel;\n every OTHER client shows \"X is typing…\" in the visible status slot and clears\n it automatically after silence. Own echoes are suppressed by design, so open\n this page in a second tab to see the indicator. The guest name is randomized\n per render so each tab is a distinct \"user\" (a real app passes\n current_user.name). The data-one/data-many templates keep the visible copy\n localizable; {name}/{names} are substituted by the controller. The status slot is\n plain visible copy — assistive tech is reached through the page's shared announcer\n instead, so the announce-one/announce-many values carry the spoken wording with\n the same {name}/{names} syntax. Leaving them off shows the indicator without announcing it. %>\n<% guest = t(\"components.typing_indicator.demo.guest_name\", token: SecureRandom.hex(2)) %>\n<% announce_one = t(\"components.typing_indicator.demo.announce_one\") %>\n<% announce_many = t(\"components.typing_indicator.demo.announce_many\") %>\n<div class=\"typing-demo\"\n data-controller=\"stimeo--typing-indicator\"\n data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n data-stimeo--typing-indicator-params-value='{\"room\":\"<%= TypingChannel::ROOM %>\"}'\n data-stimeo--typing-indicator-name-value=\"<%= guest %>\"\n data-stimeo--typing-indicator-timeout-value=\"3000\"\n data-stimeo--typing-indicator-throttle-value=\"1000\"\n data-stimeo--typing-indicator-announce-one-text-value=\"<%= announce_one %>\"\n data-stimeo--typing-indicator-announce-many-text-value=\"<%= announce_many %>\">\n <p class=\"typing-demo__hint\"><%= t(\"components.typing_indicator.demo.hint\") %></p>\n <p class=\"typing-demo__you\"><%= t(\"components.typing_indicator.demo.you_are\", name: guest) %></p>\n <label class=\"typing-demo__label\" for=\"typing-demo-input\">\n <%= t(\"components.typing_indicator.demo.input_label\") %>\n </label>\n <textarea id=\"typing-demo-input\" class=\"demo-input typing-demo__input\" rows=\"3\"\n data-stimeo--typing-indicator-target=\"input\"></textarea>\n <p class=\"typing-demo__status\"\n data-stimeo--typing-indicator-target=\"status\"\n data-one=\"<%= t(\"components.typing_indicator.demo.one\") %>\"\n data-many=\"<%= t(\"components.typing_indicator.demo.many\") %>\"></p>\n</div>\n"
447
447
  }
448
448
  }
449
449
  }
@@ -1,6 +1,6 @@
1
1
  {
2
- "schemaVersion": 15,
3
- "packageVersion": "0.17.0",
2
+ "schemaVersion": 17,
3
+ "packageVersion": "0.18.1",
4
4
  "controllers": {
5
5
  "stimeo--accordion": {
6
6
  "targets": [
@@ -443,6 +443,7 @@
443
443
  "prev",
444
444
  "next",
445
445
  "picker",
446
+ "pickerList",
446
447
  "playToggle",
447
448
  "onLabel",
448
449
  "offLabel"
@@ -1551,7 +1552,16 @@
1551
1552
  "compositions": [],
1552
1553
  "companions": [],
1553
1554
  "targetDeclarations": [],
1554
- "cardinality": [],
1555
+ "cardinality": [
1556
+ {
1557
+ "within": "",
1558
+ "target": "display",
1559
+ "max": 1,
1560
+ "severity": "warning",
1561
+ "excludeTemplateContents": true,
1562
+ "suggestion": "Keep one \"display\" input per \"stimeo--currency-input\" controller; only the first display is read, so input in the others never reaches the submitted value."
1563
+ }
1564
+ ],
1555
1565
  "forbiddenAria": []
1556
1566
  },
1557
1567
  "stimeo--data-grid": {
@@ -3391,7 +3401,8 @@
3391
3401
  "targets": [
3392
3402
  "trigger",
3393
3403
  "panel",
3394
- "hoverArea"
3404
+ "hoverArea",
3405
+ "list"
3395
3406
  ],
3396
3407
  "values": [
3397
3408
  "openOnHover",
@@ -6199,9 +6210,12 @@
6199
6210
  ],
6200
6211
  "values": [
6201
6212
  "hourCycle",
6213
+ "hour12ZeroBased",
6202
6214
  "step",
6203
6215
  "seconds",
6204
- "wrap"
6216
+ "wrap",
6217
+ "amLabel",
6218
+ "pmLabel"
6205
6219
  ],
6206
6220
  "valueConstraints": [
6207
6221
  {
@@ -7,8 +7,8 @@ import { Controller, Application } from '@hotwired/stimulus';
7
7
  * `anchor`, flipping/shifting it away from viewport edges as the page scrolls or
8
8
  * resizes. It is the declarative surface of the opt-in {@link attachPositioning}
9
9
  * engine (`@floating-ui/dom`-based), exposing its `autoUpdate` behavior as a
10
- * controller. No dedicated APG pattern; it is the
11
- * placement primitive the popup patterns (Tooltip / Menu / Popover …) build on.
10
+ * controller. No dedicated APG pattern; it is a placement primitive a consumer pairs
11
+ * with a popup such as `stimeo--tooltip`, `stimeo--menu` or `stimeo--popover`.
12
12
  *
13
13
  * Markup contract (identifier: `stimeo--anchored`):
14
14
  * <div data-controller="stimeo--anchored"
@@ -18,9 +18,9 @@ import { Controller, Application } from '@hotwired/stimulus';
18
18
  * <div data-stimeo--anchored-target="floating" role="…">…</div>
19
19
  * </div>
20
20
  *
21
- * `active` drives tracking (start/stop) and fires on connect, mirroring Focus
22
- * Scope's `trapValueChanged`; set it `false` while the floating element is hidden
23
- * so no measurement runs. The other Values map to {@link PositioningOptions} and
21
+ * `active` drives tracking (start/stop) and fires on connect, mirroring
22
+ * `stimeo--focus`'s `trapValueChanged`; set it `false` while the floating element is
23
+ * hidden so no measurement runs. The other Values map to {@link PositioningOptions} and
24
24
  * re-apply live while tracking. A declaration the engine cannot use — a
25
25
  * `placement` outside its set, a non-finite `offset` or `padding` — falls back to
26
26
  * that Value's default, so a typo never reaches the coordinates or the hook. Only
@@ -36,8 +36,8 @@ import { Controller, Application } from '@hotwired/stimulus';
36
36
  *
37
37
  * @remarks
38
38
  * Behavior only. It does **not** open/close, manage focus, or render an overlay
39
- * (pair with Dialog / Popover and {@link "../controllers/focus_controller"}), and
40
- * it does **not** move DOM (pair with Portal). It lives in the opt-in
39
+ * (pair with `stimeo--dialog` / `stimeo--popover` and `stimeo--focus`), and it does
40
+ * **not** move DOM (pair with `stimeo--portal`). It lives in the opt-in
41
41
  * `stimeo-ui/positioning` subpath so the core `import "stimeo-ui"` stays
42
42
  * zero-dependency; only consumers who register it pull in `@floating-ui/dom`. The
43
43
  * `autoUpdate` cleanup is released on `disconnect()` (Turbo navigation included)
@@ -247,4 +247,5 @@ declare const positioningControllers: {
247
247
  */
248
248
  declare function registerPositioning(application: Application): void;
249
249
 
250
- export { AnchoredController, type PositionResult, type PositioningOptions, attachPositioning, position, positioningControllers, registerPositioning };
250
+ export { AnchoredController, attachPositioning, position, positioningControllers, registerPositioning };
251
+ export type { PositionResult, PositioningOptions };
@@ -28,6 +28,9 @@ var MicrotaskCoalescer = class {
28
28
  this.#queued = false;
29
29
  this.#generation += 1;
30
30
  }
31
+ get pending() {
32
+ return this.#queued;
33
+ }
31
34
  schedule() {
32
35
  if (!this.#active || this.#queued) return;
33
36
  this.#queued = true;
@@ -58,6 +61,9 @@ var MorphRenderWatcher = class {
58
61
  schedule() {
59
62
  this.#pass.schedule();
60
63
  }
64
+ get pending() {
65
+ return this.#pass.pending;
66
+ }
61
67
  disconnect() {
62
68
  this.#listeners.dispose();
63
69
  this.#pass.cancel();