stimeo-ui 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/controllers/alert_dialog_controller.d.ts +2 -0
  3. package/dist/controllers/alert_dialog_controller.js +75 -4
  4. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  5. package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
  6. package/dist/controllers/aspect_ratio_controller.js +19 -11
  7. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  8. package/dist/controllers/auto_submit_controller.d.ts +2 -0
  9. package/dist/controllers/auto_submit_controller.js.map +1 -1
  10. package/dist/controllers/avatar_controller.d.ts +36 -15
  11. package/dist/controllers/avatar_controller.js +237 -40
  12. package/dist/controllers/avatar_controller.js.map +1 -1
  13. package/dist/controllers/breadcrumb_controller.d.ts +2 -0
  14. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  15. package/dist/controllers/bulk_select_controller.d.ts +2 -0
  16. package/dist/controllers/bulk_select_controller.js.map +1 -1
  17. package/dist/controllers/calendar_controller.d.ts +2 -0
  18. package/dist/controllers/calendar_controller.js.map +1 -1
  19. package/dist/controllers/carousel_controller.d.ts +12 -3
  20. package/dist/controllers/carousel_controller.js +85 -9
  21. package/dist/controllers/carousel_controller.js.map +1 -1
  22. package/dist/controllers/character_counter_controller.d.ts +52 -20
  23. package/dist/controllers/character_counter_controller.js +338 -63
  24. package/dist/controllers/character_counter_controller.js.map +1 -1
  25. package/dist/controllers/checkbox_controller.d.ts +34 -5
  26. package/dist/controllers/checkbox_controller.js +136 -25
  27. package/dist/controllers/checkbox_controller.js.map +1 -1
  28. package/dist/controllers/color_picker_controller.d.ts +10 -1
  29. package/dist/controllers/color_picker_controller.js +35 -9
  30. package/dist/controllers/color_picker_controller.js.map +1 -1
  31. package/dist/controllers/combobox_controller.d.ts +2 -0
  32. package/dist/controllers/combobox_controller.js.map +1 -1
  33. package/dist/controllers/command_palette_controller.d.ts +2 -0
  34. package/dist/controllers/command_palette_controller.js +75 -4
  35. package/dist/controllers/command_palette_controller.js.map +1 -1
  36. package/dist/controllers/conditional_fields_controller.d.ts +42 -14
  37. package/dist/controllers/conditional_fields_controller.js +345 -51
  38. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  39. package/dist/controllers/confirm_controller.d.ts +2 -0
  40. package/dist/controllers/confirm_controller.js +75 -4
  41. package/dist/controllers/confirm_controller.js.map +1 -1
  42. package/dist/controllers/count_up_controller.d.ts +2 -0
  43. package/dist/controllers/count_up_controller.js.map +1 -1
  44. package/dist/controllers/countdown_controller.d.ts +4 -0
  45. package/dist/controllers/countdown_controller.js.map +1 -1
  46. package/dist/controllers/currency_input_controller.d.ts +2 -0
  47. package/dist/controllers/currency_input_controller.js.map +1 -1
  48. package/dist/controllers/data_grid_controller.d.ts +3 -0
  49. package/dist/controllers/data_grid_controller.js.map +1 -1
  50. package/dist/controllers/date_range_picker_controller.d.ts +23 -4
  51. package/dist/controllers/date_range_picker_controller.js +157 -30
  52. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  53. package/dist/controllers/dialog_controller.js +75 -4
  54. package/dist/controllers/dialog_controller.js.map +1 -1
  55. package/dist/controllers/direct_upload_controller.d.ts +56 -24
  56. package/dist/controllers/direct_upload_controller.js +201 -45
  57. package/dist/controllers/direct_upload_controller.js.map +1 -1
  58. package/dist/controllers/dirty_form_controller.d.ts +14 -6
  59. package/dist/controllers/dirty_form_controller.js +192 -29
  60. package/dist/controllers/dirty_form_controller.js.map +1 -1
  61. package/dist/controllers/dismissible_controller.d.ts +2 -0
  62. package/dist/controllers/dismissible_controller.js +83 -18
  63. package/dist/controllers/dismissible_controller.js.map +1 -1
  64. package/dist/controllers/drawer_controller.js +75 -4
  65. package/dist/controllers/drawer_controller.js.map +1 -1
  66. package/dist/controllers/empty_state_controller.d.ts +2 -0
  67. package/dist/controllers/empty_state_controller.js.map +1 -1
  68. package/dist/controllers/file_dropzone_controller.d.ts +9 -1
  69. package/dist/controllers/file_dropzone_controller.js +26 -3
  70. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  71. package/dist/controllers/filter_controller.d.ts +2 -0
  72. package/dist/controllers/filter_controller.js.map +1 -1
  73. package/dist/controllers/flash_controller.d.ts +4 -0
  74. package/dist/controllers/flash_controller.js.map +1 -1
  75. package/dist/controllers/focus_controller.d.ts +4 -3
  76. package/dist/controllers/focus_controller.js +75 -4
  77. package/dist/controllers/focus_controller.js.map +1 -1
  78. package/dist/controllers/form_field_controller.d.ts +50 -10
  79. package/dist/controllers/form_field_controller.js +280 -62
  80. package/dist/controllers/form_field_controller.js.map +1 -1
  81. package/dist/controllers/form_validation_controller.d.ts +10 -8
  82. package/dist/controllers/form_validation_controller.js +208 -83
  83. package/dist/controllers/form_validation_controller.js.map +1 -1
  84. package/dist/controllers/frame_loading_controller.d.ts +2 -0
  85. package/dist/controllers/frame_loading_controller.js.map +1 -1
  86. package/dist/controllers/highlight_controller.d.ts +2 -0
  87. package/dist/controllers/highlight_controller.js.map +1 -1
  88. package/dist/controllers/hover_card_controller.d.ts +2 -2
  89. package/dist/controllers/hover_card_controller.js.map +1 -1
  90. package/dist/controllers/idle_controller.d.ts +5 -3
  91. package/dist/controllers/idle_controller.js +27 -5
  92. package/dist/controllers/idle_controller.js.map +1 -1
  93. package/dist/controllers/input_mask_controller.d.ts +2 -0
  94. package/dist/controllers/input_mask_controller.js.map +1 -1
  95. package/dist/controllers/lazy_frame_controller.d.ts +2 -0
  96. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  97. package/dist/controllers/listbox_controller.d.ts +2 -0
  98. package/dist/controllers/listbox_controller.js.map +1 -1
  99. package/dist/controllers/local_time_controller.d.ts +2 -0
  100. package/dist/controllers/local_time_controller.js.map +1 -1
  101. package/dist/controllers/masonry_controller.d.ts +2 -0
  102. package/dist/controllers/masonry_controller.js.map +1 -1
  103. package/dist/controllers/menubar_controller.js +5 -3
  104. package/dist/controllers/menubar_controller.js.map +1 -1
  105. package/dist/controllers/meter_controller.d.ts +2 -0
  106. package/dist/controllers/meter_controller.js.map +1 -1
  107. package/dist/controllers/multi_select_controller.d.ts +48 -12
  108. package/dist/controllers/multi_select_controller.js +460 -151
  109. package/dist/controllers/multi_select_controller.js.map +1 -1
  110. package/dist/controllers/nested_form_controller.d.ts +2 -0
  111. package/dist/controllers/nested_form_controller.js.map +1 -1
  112. package/dist/controllers/network_status_controller.d.ts +2 -0
  113. package/dist/controllers/network_status_controller.js.map +1 -1
  114. package/dist/controllers/number_input_controller.d.ts +26 -6
  115. package/dist/controllers/number_input_controller.js +317 -51
  116. package/dist/controllers/number_input_controller.js.map +1 -1
  117. package/dist/controllers/otp_controller.d.ts +3 -0
  118. package/dist/controllers/otp_controller.js.map +1 -1
  119. package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
  120. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  121. package/dist/controllers/overflow_menu_controller.d.ts +2 -0
  122. package/dist/controllers/overflow_menu_controller.js +6 -1
  123. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  124. package/dist/controllers/pagination_controller.d.ts +2 -0
  125. package/dist/controllers/pagination_controller.js +35 -1
  126. package/dist/controllers/pagination_controller.js.map +1 -1
  127. package/dist/controllers/password_reveal_controller.d.ts +2 -0
  128. package/dist/controllers/password_reveal_controller.js.map +1 -1
  129. package/dist/controllers/password_strength_controller.d.ts +5 -3
  130. package/dist/controllers/password_strength_controller.js +20 -2
  131. package/dist/controllers/password_strength_controller.js.map +1 -1
  132. package/dist/controllers/persist_controller.d.ts +33 -19
  133. package/dist/controllers/persist_controller.js +449 -120
  134. package/dist/controllers/persist_controller.js.map +1 -1
  135. package/dist/controllers/pointer_drag_controller.d.ts +3 -0
  136. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  137. package/dist/controllers/popover_controller.d.ts +2 -2
  138. package/dist/controllers/popover_controller.js +77 -4
  139. package/dist/controllers/popover_controller.js.map +1 -1
  140. package/dist/controllers/portal_controller.d.ts +3 -2
  141. package/dist/controllers/portal_controller.js.map +1 -1
  142. package/dist/controllers/preview_guard_controller.d.ts +2 -0
  143. package/dist/controllers/preview_guard_controller.js.map +1 -1
  144. package/dist/controllers/progress_controller.d.ts +3 -0
  145. package/dist/controllers/progress_controller.js.map +1 -1
  146. package/dist/controllers/radio_group_controller.d.ts +44 -15
  147. package/dist/controllers/radio_group_controller.js +540 -56
  148. package/dist/controllers/radio_group_controller.js.map +1 -1
  149. package/dist/controllers/rating_controller.d.ts +38 -31
  150. package/dist/controllers/rating_controller.js +276 -89
  151. package/dist/controllers/rating_controller.js.map +1 -1
  152. package/dist/controllers/reading_progress_controller.d.ts +2 -0
  153. package/dist/controllers/reading_progress_controller.js.map +1 -1
  154. package/dist/controllers/resizable_controller.d.ts +2 -0
  155. package/dist/controllers/resizable_controller.js +33 -0
  156. package/dist/controllers/resizable_controller.js.map +1 -1
  157. package/dist/controllers/roving_controller.d.ts +6 -0
  158. package/dist/controllers/roving_controller.js +60 -5
  159. package/dist/controllers/roving_controller.js.map +1 -1
  160. package/dist/controllers/scroll_area_controller.d.ts +25 -11
  161. package/dist/controllers/scroll_area_controller.js +557 -125
  162. package/dist/controllers/scroll_area_controller.js.map +1 -1
  163. package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
  164. package/dist/controllers/scroll_visibility_controller.js +33 -0
  165. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  166. package/dist/controllers/scrollspy_controller.d.ts +2 -0
  167. package/dist/controllers/scrollspy_controller.js.map +1 -1
  168. package/dist/controllers/separator_controller.d.ts +54 -11
  169. package/dist/controllers/separator_controller.js +354 -38
  170. package/dist/controllers/separator_controller.js.map +1 -1
  171. package/dist/controllers/sidebar_controller.js +83 -10
  172. package/dist/controllers/sidebar_controller.js.map +1 -1
  173. package/dist/controllers/skeleton_controller.d.ts +2 -0
  174. package/dist/controllers/skeleton_controller.js.map +1 -1
  175. package/dist/controllers/slider_controller.d.ts +2 -0
  176. package/dist/controllers/slider_controller.js.map +1 -1
  177. package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
  178. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  179. package/dist/controllers/spinner_controller.d.ts +2 -0
  180. package/dist/controllers/spinner_controller.js.map +1 -1
  181. package/dist/controllers/step_indicator_controller.d.ts +2 -0
  182. package/dist/controllers/step_indicator_controller.js.map +1 -1
  183. package/dist/controllers/stepper_controller.d.ts +2 -0
  184. package/dist/controllers/stepper_controller.js.map +1 -1
  185. package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
  186. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  187. package/dist/controllers/sticky_observer_controller.d.ts +2 -0
  188. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  189. package/dist/controllers/submit_once_controller.d.ts +94 -38
  190. package/dist/controllers/submit_once_controller.js +399 -121
  191. package/dist/controllers/submit_once_controller.js.map +1 -1
  192. package/dist/controllers/switch_controller.d.ts +2 -0
  193. package/dist/controllers/switch_controller.js.map +1 -1
  194. package/dist/controllers/tags_input_controller.d.ts +43 -11
  195. package/dist/controllers/tags_input_controller.js +356 -120
  196. package/dist/controllers/tags_input_controller.js.map +1 -1
  197. package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
  198. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  199. package/dist/controllers/theme_controller.d.ts +2 -0
  200. package/dist/controllers/theme_controller.js +8 -6
  201. package/dist/controllers/theme_controller.js.map +1 -1
  202. package/dist/controllers/time_picker_controller.d.ts +44 -13
  203. package/dist/controllers/time_picker_controller.js +296 -107
  204. package/dist/controllers/time_picker_controller.js.map +1 -1
  205. package/dist/controllers/toast_controller.d.ts +4 -0
  206. package/dist/controllers/toast_controller.js.map +1 -1
  207. package/dist/controllers/toggle_group_controller.d.ts +41 -13
  208. package/dist/controllers/toggle_group_controller.js +378 -55
  209. package/dist/controllers/toggle_group_controller.js.map +1 -1
  210. package/dist/controllers/toolbar_controller.js +5 -3
  211. package/dist/controllers/toolbar_controller.js.map +1 -1
  212. package/dist/controllers/tooltip_controller.d.ts +2 -2
  213. package/dist/controllers/tooltip_controller.js.map +1 -1
  214. package/dist/controllers/transition_controller.d.ts +4 -3
  215. package/dist/controllers/transition_controller.js.map +1 -1
  216. package/dist/controllers/tree_view_controller.d.ts +2 -0
  217. package/dist/controllers/tree_view_controller.js +7 -4
  218. package/dist/controllers/tree_view_controller.js.map +1 -1
  219. package/dist/index.js +4963 -1581
  220. package/dist/index.js.map +1 -1
  221. package/dist/inspector/cli.d.ts +123 -6
  222. package/dist/inspector/cli.js +212 -18
  223. package/dist/inspector/cli.js.map +1 -1
  224. package/dist/inspector/cli_bin.js +274 -50
  225. package/dist/inspector/cli_bin.js.map +1 -1
  226. package/dist/inspector/examples.json +15 -15
  227. package/dist/inspector/manifest.json +602 -80
  228. package/dist/positioning/index.d.ts +4 -2
  229. package/dist/positioning/index.js.map +1 -1
  230. package/package.json +1 -1
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "stimeo--aspect-ratio": {
21
21
  "file": "examples/aspect_ratio/_demo.html.erb",
22
- "source": "<%# Markup for the aspect-ratio demo.\n The library only supplies the ratio Value as the --stimeo--aspect-ratio custom\n property; demo.css does the actual ratio rendering\n (aspect-ratio: var(--stimeo--aspect-ratio)). %>\n<div class=\"aspect-ratio-demo\">\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"16/9\">\n <div class=\"ratio-box__content\" data-stimeo--aspect-ratio-target=\"content\">16 / 9</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.wide\") %></figcaption>\n </figure>\n\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"1/1\">\n <div class=\"ratio-box__content\" data-stimeo--aspect-ratio-target=\"content\">1 / 1</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.square\") %></figcaption>\n </figure>\n\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"3/4\">\n <div class=\"ratio-box__content\" data-stimeo--aspect-ratio-target=\"content\">3 / 4</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.portrait\") %></figcaption>\n </figure>\n</div>\n"
22
+ "source": "<%# Markup for the aspect-ratio demo.\n The library only supplies the ratio Value as the --stimeo--aspect-ratio custom\n property; demo.css does the actual ratio rendering\n (aspect-ratio: var(--stimeo--aspect-ratio)). %>\n<div class=\"aspect-ratio-demo\">\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"16/9\">\n <div class=\"ratio-box__content\">16 / 9</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.wide\") %></figcaption>\n </figure>\n\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"1/1\">\n <div class=\"ratio-box__content\">1 / 1</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.square\") %></figcaption>\n </figure>\n\n <figure class=\"aspect-ratio-demo__item\">\n <div class=\"ratio-box\" data-controller=\"stimeo--aspect-ratio\"\n data-stimeo--aspect-ratio-ratio-value=\"3/4\">\n <div class=\"ratio-box__content\">3 / 4</div>\n </div>\n <figcaption><%= t(\"components.aspect_ratio.demo.portrait\") %></figcaption>\n </figure>\n</div>\n"
23
23
  },
24
24
  "stimeo--auto-submit": {
25
25
  "file": "examples/auto_submit/_demo.html.erb",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "stimeo--avatar": {
29
29
  "file": "examples/avatar/_demo.html.erb",
30
- "source": "<%# Markup for the avatar demo.\n The accessible name is owned solely by the container (role=\"img\" + aria-label);\n the inner <img> and fallback use aria-hidden=\"true\" to avoid double announcement.\n The library only detects load/error and toggles display (data-state); shape,\n color, and size are drawn by demo.css. %>\n<div class=\"avatar-demo\">\n <%# 1. An avatar that loads successfully. The src is a self-contained data-URI SVG that\n draws a person silhouette (a picture, not letters) so the \"loaded image\" state reads\n clearly different from the initials fallback below. %>\n <figure class=\"avatar-demo__item\">\n <span\n class=\"avatar\"\n data-controller=\"stimeo--avatar\"\n role=\"img\"\n aria-label=\"<%= t(\"components.avatar.demo.name_ok\") %>\"\n data-stimeo--avatar-src-value=\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Cdefs%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%236366f1'/%3E%3Cstop%20offset='1'%20stop-color='%234338ca'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='96'%20height='96'%20fill='url(%23g)'/%3E%3Ccircle%20cx='48'%20cy='38'%20r='17'%20fill='%23eef2ff'/%3E%3Cpath%20d='M20%2084%20a28%2028%200%200%201%2056%200%20Z'%20fill='%23eef2ff'/%3E%3C/svg%3E\">\n <img\n class=\"avatar__image\"\n alt=\"\"\n aria-hidden=\"true\"\n data-stimeo--avatar-target=\"image\"\n data-action=\"load->stimeo--avatar#onLoad error->stimeo--avatar#onError\">\n <span\n class=\"avatar__fallback\"\n aria-hidden=\"true\"\n hidden\n data-stimeo--avatar-target=\"fallback\"\n >AU</span>\n </span>\n <figcaption><%= t(\"components.avatar.demo.caption_ok\") %></figcaption>\n </figure>\n\n <%# 2. An avatar that fails to load (nonexistent path → fallback initials). The browser\n logs an expected 404 for the missing image — that is the point: it triggers the error\n → fallback path the library handles. %>\n <figure class=\"avatar-demo__item\">\n <span\n class=\"avatar\"\n data-controller=\"stimeo--avatar\"\n role=\"img\"\n aria-label=\"<%= t(\"components.avatar.demo.name_fallback\") %>\"\n data-stimeo--avatar-src-value=\"/this-image-does-not-exist.jpg\">\n <img\n class=\"avatar__image\"\n alt=\"\"\n aria-hidden=\"true\"\n data-stimeo--avatar-target=\"image\"\n data-action=\"load->stimeo--avatar#onLoad error->stimeo--avatar#onError\">\n <span\n class=\"avatar__fallback\"\n aria-hidden=\"true\"\n hidden\n data-stimeo--avatar-target=\"fallback\"\n >JD</span>\n </span>\n <figcaption><%= t(\"components.avatar.demo.caption_fallback\") %></figcaption>\n </figure>\n</div>\n"
30
+ "source": "<%# Markup for the avatar demo.\n The accessible name is owned solely by the container (role=\"img\" + aria-label);\n the inner <img> and fallback use aria-hidden=\"true\" to avoid double announcement.\n The library only tracks the source and its load state, exposing empty / loading /\n loaded / error on data-state; shape, color, and size are drawn by demo.css. %>\n<div class=\"avatar-demo\">\n <%# 1. An avatar that loads successfully. The src is a self-contained data-URI SVG that\n draws a person silhouette (a picture, not letters) so the \"loaded image\" state reads\n clearly different from the initials fallback below. %>\n <figure class=\"avatar-demo__item\">\n <span\n class=\"avatar\"\n data-controller=\"stimeo--avatar\"\n role=\"img\"\n aria-label=\"<%= t(\"components.avatar.demo.name_ok\") %>\"\n data-stimeo--avatar-src-value=\"data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Cdefs%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%236366f1'/%3E%3Cstop%20offset='1'%20stop-color='%234338ca'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='96'%20height='96'%20fill='url(%23g)'/%3E%3Ccircle%20cx='48'%20cy='38'%20r='17'%20fill='%23eef2ff'/%3E%3Cpath%20d='M20%2084%20a28%2028%200%200%201%2056%200%20Z'%20fill='%23eef2ff'/%3E%3C/svg%3E\">\n <img\n class=\"avatar__image\"\n alt=\"\"\n aria-hidden=\"true\"\n data-stimeo--avatar-target=\"image\"\n data-action=\"load->stimeo--avatar#onLoad error->stimeo--avatar#onError\">\n <span\n class=\"avatar__fallback\"\n aria-hidden=\"true\"\n hidden\n data-stimeo--avatar-target=\"fallback\"\n >AU</span>\n </span>\n <figcaption><%= t(\"components.avatar.demo.caption_ok\") %></figcaption>\n </figure>\n\n <%# 2. An avatar that fails to load (nonexistent path → fallback initials). The browser\n logs an expected 404 for the missing image — that is the point: it triggers the error\n → fallback path the library handles. %>\n <figure class=\"avatar-demo__item\">\n <span\n class=\"avatar\"\n data-controller=\"stimeo--avatar\"\n role=\"img\"\n aria-label=\"<%= t(\"components.avatar.demo.name_fallback\") %>\"\n data-stimeo--avatar-src-value=\"/this-image-does-not-exist.jpg\">\n <img\n class=\"avatar__image\"\n alt=\"\"\n aria-hidden=\"true\"\n data-stimeo--avatar-target=\"image\"\n data-action=\"load->stimeo--avatar#onLoad error->stimeo--avatar#onError\">\n <span\n class=\"avatar__fallback\"\n aria-hidden=\"true\"\n hidden\n data-stimeo--avatar-target=\"fallback\"\n >JD</span>\n </span>\n <figcaption><%= t(\"components.avatar.demo.caption_fallback\") %></figcaption>\n </figure>\n\n <%# 3. An avatar with no image target at all. Both targets are optional, so a\n fallback-only avatar is valid and settles in the empty state. %>\n <figure class=\"avatar-demo__item\">\n <span\n class=\"avatar\"\n data-controller=\"stimeo--avatar\"\n role=\"img\"\n aria-label=\"<%= t(\"components.avatar.demo.name_empty\") %>\">\n <span\n class=\"avatar__fallback\"\n aria-hidden=\"true\"\n data-stimeo--avatar-target=\"fallback\"\n >GU</span>\n </span>\n <figcaption><%= t(\"components.avatar.demo.caption_empty\") %></figcaption>\n </figure>\n</div>\n"
31
31
  },
32
32
  "stimeo--breadcrumb": {
33
33
  "file": "examples/breadcrumb/_demo.html.erb",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "stimeo--character-counter": {
49
49
  "file": "examples/character_counter/_demo.html.erb",
50
- "source": "<%# Character counter demo: a textarea whose remaining count is shown beneath it.\n The controller writes the count into the output (an aria-live=\"polite\" region),\n toggles data-near-limit / data-over-limit on the root, and sets aria-invalid on the\n field past the max. This demo styles those state hooks; the library only updates the\n count text and the hooks — no consumer JS is needed (it reacts to input on its own). %>\n<div class=\"character-counter-demo\">\n <label class=\"character-counter-demo__label\" for=\"cc-message\">\n <%= t(\"components.character_counter.demo.label\") %>\n </label>\n <div\n class=\"character-counter\"\n data-controller=\"stimeo--character-counter\"\n data-stimeo--character-counter-max-value=\"140\"\n data-stimeo--character-counter-warn-at-value=\"20\">\n <textarea\n id=\"cc-message\"\n class=\"character-counter__input\"\n rows=\"3\"\n data-stimeo--character-counter-target=\"input\"\n aria-describedby=\"cc-message-count\"\n placeholder=\"<%= t('components.character_counter.demo.placeholder') %>\"></textarea>\n <span\n id=\"cc-message-count\"\n class=\"character-counter__output\"\n data-stimeo--character-counter-target=\"output\"\n aria-live=\"polite\"></span>\n </div>\n</div>\n"
50
+ "source": "<%# Markup for the character-counter demo. The visible output updates on every\n keystroke, while the settled remaining count is debounced into the shared\n stimeo--announcer seated below. This demo's CSS styles the root's near/over\n hooks and the over-limit aria-invalid; watching the field needs no consumer\n JavaScript. %>\n<% announcement = t(\"components.character_counter.demo.announcement\") %>\n<div class=\"character-counter-demo\">\n <label class=\"character-counter-demo__label\" for=\"cc-message\">\n <%= t(\"components.character_counter.demo.label\") %>\n </label>\n <div\n class=\"character-counter\"\n data-controller=\"stimeo--character-counter\"\n data-stimeo--character-counter-max-value=\"140\"\n data-stimeo--character-counter-warn-at-value=\"20\"\n data-stimeo--character-counter-announce-text-value=\"<%= announcement %>\">\n <textarea\n id=\"cc-message\"\n class=\"character-counter__input\"\n rows=\"3\"\n data-stimeo--character-counter-target=\"input\"\n aria-describedby=\"cc-message-count\"\n placeholder=\"<%= t('components.character_counter.demo.placeholder') %>\"></textarea>\n <span\n id=\"cc-message-count\"\n class=\"character-counter__output\"\n data-stimeo--character-counter-target=\"output\"></span>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\"\n data-stimeo--announcer-target=\"polite\"\n aria-live=\"polite\"\n aria-atomic=\"true\"></div>\n </div>\n </div>\n</div>\n"
51
51
  },
52
52
  "stimeo--checkbox": {
53
53
  "file": "examples/checkbox/_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<div class=\"drp demo-card\" data-controller=\"stimeo--date-range-picker\">\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 <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"
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"
107
107
  },
108
108
  "stimeo--dialog": {
109
109
  "file": "examples/dialog/_demo.html.erb",
@@ -111,11 +111,11 @@
111
111
  },
112
112
  "stimeo--direct-upload": {
113
113
  "file": "examples/direct_upload/_demo.html.erb",
114
- "source": "<%# Direct upload demo: there is no server, so demo.js fires the ActiveStorage\n direct-upload:* events to drive the rows. The library clones the row template per\n file, updates aria-valuenow / data-upload-state / the --stimeo--upload-progress\n var, and announces completion into the status live region. This demo styles the\n bars; the sr-only status carries the announcement. %>\n<div class=\"direct-upload-demo\">\n <button type=\"button\" class=\"demo-trigger\" data-direct-upload-start>\n <%= t(\"components.direct_upload.demo.start\") %>\n </button>\n <div\n class=\"direct-upload\"\n data-controller=\"stimeo--direct-upload\"\n data-stimeo--direct-upload-done-label-value=\"<%= t('components.direct_upload.demo.done') %>\"\n data-stimeo--direct-upload-error-label-value=\"<%= t('components.direct_upload.demo.error') %>\">\n <div class=\"direct-upload__list\" data-stimeo--direct-upload-target=\"list\"></div>\n <template data-stimeo--direct-upload-target=\"row\">\n <div class=\"direct-upload__row\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <span class=\"direct-upload__name\" data-field=\"name\"></span>\n <span class=\"direct-upload__track\"><span class=\"direct-upload__bar\"></span></span>\n <span class=\"direct-upload__percent\" data-field=\"percent\"></span>\n </div>\n </template>\n <span class=\"visually-hidden\" data-stimeo--direct-upload-target=\"status\"\n aria-live=\"polite\"></span>\n </div>\n</div>\n"
114
+ "source": "<%# Direct upload demo: there is no server, so demo.js fires the ActiveStorage\n direct-upload:* events to drive the rows — including one upload that fails\n (error followed by end, the order ActiveStorage really uses). The library\n clones the row template per file, updates aria-valuenow / aria-valuetext /\n data-upload-state / the --stimeo--upload-progress var, and hands completion\n and failure announcements to the shared announcer below. This demo styles\n the bars. %>\n<% done_text = t(\"components.direct_upload.demo.done\") %>\n<% error_text = t(\"components.direct_upload.demo.error\") %>\n<div class=\"direct-upload-demo\">\n <button type=\"button\" class=\"demo-trigger\" data-direct-upload-start>\n <%= t(\"components.direct_upload.demo.start\") %>\n </button>\n <div\n class=\"direct-upload\"\n data-controller=\"stimeo--direct-upload\"\n data-stimeo--direct-upload-announce-done-text-value=\"<%= done_text %>\"\n data-stimeo--direct-upload-announce-error-text-value=\"<%= error_text %>\">\n <div class=\"direct-upload__list\" data-stimeo--direct-upload-target=\"list\"></div>\n <template data-stimeo--direct-upload-target=\"row\">\n <div class=\"direct-upload__row\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\">\n <span class=\"direct-upload__name\" data-field=\"name\"></span>\n <span class=\"direct-upload__track\"><span class=\"direct-upload__bar\"></span></span>\n <span class=\"direct-upload__percent\" data-field=\"percent\"></span>\n </div>\n </template>\n </div>\n <%# Shared announcer: completion and failure are the transitions worth reading;\n per-tick progress stays on each row's aria-valuenow. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\"\n data-stimeo--announcer-target=\"polite\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n </div>\n</div>\n"
115
115
  },
116
116
  "stimeo--dirty-form": {
117
117
  "file": "examples/dirty_form/_demo.html.erb",
118
- "source": "<%# Dirty-form demo: edit the field and the form gets data-dirty (shown as a badge).\n While dirty, leaving is guarded (beforeunload + Turbo turbo:before-visit → a native\n confirm). \"Save\" calls the markClean action to clear the dirty state (this catalog\n has no server, so it stands in for a successful save). The library owns the\n data-dirty hook and the guards; this demo only styles the badge. %>\n<div class=\"dirty-form-demo\">\n <form class=\"dirty-form\" data-controller=\"stimeo--dirty-form\">\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.label\") %></span>\n <input type=\"text\" name=\"title\" value=\"<%= t('components.dirty_form.demo.value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n</div>\n"
118
+ "source": "<%# Dirty-form demo: edit either field and its form gets data-dirty (shown as a badge).\n When both forms are dirty, one Turbo visit dispatches both guard events and opens\n one native confirm using the first eligible form in live DOM order. \"Save\" calls\n markClean for that form; this catalog has no server, so it stands in for a successful\n save. The library owns data-dirty and navigation coordination; the demo only styles\n the visible state. %>\n<div class=\"dirty-form-demo\">\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.first_message') %>\"\n aria-labelledby=\"dirty-form-first-title\"\n >\n <h3 id=\"dirty-form-first-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.first_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.first_label\") %></span>\n <input type=\"text\" name=\"title\" value=\"<%= t('components.dirty_form.demo.first_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.second_message') %>\"\n aria-labelledby=\"dirty-form-second-title\"\n >\n <h3 id=\"dirty-form-second-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.second_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.second_label\") %></span>\n <input type=\"text\" name=\"summary\" value=\"<%= t('components.dirty_form.demo.second_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n</div>\n"
119
119
  },
120
120
  "stimeo--dismissible": {
121
121
  "file": "examples/dismissible/_demo.html.erb",
@@ -155,11 +155,11 @@
155
155
  },
156
156
  "stimeo--form-field": {
157
157
  "file": "examples/form_field/_demo.html.erb",
158
- "source": "<%# Markup for the form-field demo.\n Associates label / description / error with the control via aria-describedby /\n aria-invalid / aria-errormessage. Showing/clearing the error uses the action\n parameter (message) and #clearError. The look (e.g. the invalid border) switches\n on data-stimeo--form-field-invalid and aria-invalid. %>\n<div class=\"form-field-demo\" data-controller=\"stimeo--form-field\"\n data-stimeo--form-field-focus-on-error-value=\"true\">\n <label class=\"form-field-demo__label\" for=\"ff-email\">\n <%= t(\"components.form_field.demo.label\") %>\n </label>\n\n <input class=\"form-field-demo__control\" id=\"ff-email\" type=\"email\" aria-invalid=\"false\"\n placeholder=\"<%= t('components.form_field.demo.placeholder') %>\"\n data-stimeo--form-field-target=\"control\" />\n\n <%# Helper description. On connect it's assigned an id and linked into the\n control's aria-describedby. %>\n <p class=\"form-field-demo__description\" data-stimeo--form-field-target=\"description\">\n <%= t(\"components.form_field.demo.hint\") %>\n </p>\n\n <%# Note clarifying the demo's intent (this part only wires up the error state;\n it doesn't validate or clear the value). %>\n <p class=\"form-field-demo__note\"><%= t(\"components.form_field.demo.note\") %></p>\n\n <%# Error region. role=\"alert\" so it's announced without moving focus. Starts hidden. %>\n <p class=\"form-field-demo__error\" role=\"alert\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n\n <div class=\"form-field-demo__actions\">\n <%# setError reads the message from the action parameter (no consumer JS needed). %>\n <button type=\"button\" class=\"demo-trigger\"\n data-stimeo--form-field-message-param=\"<%= t('components.form_field.demo.error') %>\"\n data-action=\"click->stimeo--form-field#setError\">\n <%= t(\"components.form_field.demo.validate\") %>\n </button>\n <button type=\"button\" class=\"demo-trigger\"\n data-action=\"click->stimeo--form-field#clearError\">\n <%= t(\"components.form_field.demo.clear\") %>\n </button>\n </div>\n\n <%# Helper region that subscribes to the validate event to show status (updated by demo.js). %>\n <p class=\"form-field-demo__status\" id=\"ff-status\" aria-live=\"polite\"\n data-valid-text=\"<%= t('components.form_field.demo.valid') %>\"></p>\n</div>\n"
158
+ "source": "<%# Markup for the form-field demo.\n Associates label / description / error with the control via aria-describedby /\n aria-invalid / aria-errormessage. Showing/clearing the error uses the action\n parameter (message) and #clearError. The look switches on\n data-stimeo--form-field-invalid and aria-invalid. %>\n<div class=\"form-field-demo\" data-controller=\"stimeo--form-field\"\n data-stimeo--form-field-focus-on-error-value=\"true\">\n <label class=\"form-field-demo__label\" for=\"ff-email\">\n <%= t(\"components.form_field.demo.label\") %>\n </label>\n\n <input class=\"form-field-demo__control\" id=\"ff-email\" type=\"email\" aria-invalid=\"false\"\n placeholder=\"<%= t('components.form_field.demo.placeholder') %>\"\n data-stimeo--form-field-target=\"control\" />\n\n <%# Helper description. It is assigned an id and linked into the control's\n aria-describedby. %>\n <p class=\"form-field-demo__description\" data-stimeo--form-field-target=\"description\">\n <%= t(\"components.form_field.demo.hint\") %>\n </p>\n\n <%# Note clarifying the demo's intent (this part only wires up the error state;\n it doesn't validate or clear the value). %>\n <p class=\"form-field-demo__note\"><%= t(\"components.form_field.demo.note\") %></p>\n\n <%# Visual error region. Starts hidden; speech is delegated to the shared\n announcer seated below. %>\n <p class=\"form-field-demo__error\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n\n <div class=\"form-field-demo__actions\">\n <%# setError reads the message from the action parameter (no consumer JS needed). %>\n <button type=\"button\" class=\"demo-trigger\"\n data-stimeo--form-field-message-param=\"<%= t('components.form_field.demo.error') %>\"\n data-action=\"click->stimeo--form-field#setError\">\n <%= t(\"components.form_field.demo.validate\") %>\n </button>\n <button type=\"button\" class=\"demo-trigger\"\n data-action=\"click->stimeo--form-field#clearError\">\n <%= t(\"components.form_field.demo.clear\") %>\n </button>\n </div>\n\n <%# Helper region that subscribes to the validate event and shows the valid\n state only (updated by demo.js). %>\n <p class=\"form-field-demo__status\" id=\"ff-status\" aria-live=\"polite\"\n data-valid-text=\"<%= t('components.form_field.demo.valid') %>\"></p>\n\n <%# Assertive announcement target. It is seated in the accessibility tree ahead\n of the first error, which is what makes the message observable. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\" data-stimeo--announcer-target=\"assertive\"\n aria-live=\"assertive\" aria-atomic=\"true\"></div>\n </div>\n</div>\n"
159
159
  },
160
160
  "stimeo--form-validation": {
161
161
  "file": "examples/form_validation/_demo.html.erb",
162
- "source": "<%# Markup for the form-validation demo.\n stimeo--form-validation orchestrates WHEN native constraint validation runs\n (submit / blur / change / input) and routes each control's validationMessage\n into its stimeo--form-field error region — the ARIA wiring stays in form-field.\n Rules are plain markup attributes (required, type=\"email\"); message text comes\n from the browser. The plan listbox joins through a validatable mirror:\n <input type=\"text\" hidden required> — the hidden ATTRIBUTE, not type=\"hidden\",\n which is barred from constraint validation. The authored novalidate keeps the\n no-JS fallback graceful (the controller preserves it). Valid submissions are\n kept on the page by demo.js so the catalog can show a success status. %>\n<form class=\"form-validation-demo\" action=\"#\" method=\"get\" novalidate\n data-controller=\"stimeo--form-validation\"\n data-stimeo--form-validation-stimeo--form-field-outlet=\".form-validation-demo__field\">\n\n <div class=\"form-validation-demo__field\" data-controller=\"stimeo--form-field\">\n <label class=\"form-validation-demo__label\" for=\"fv-email\">\n <%= t(\"components.form_validation.demo.email_label\") %>\n </label>\n <input class=\"form-validation-demo__control\" id=\"fv-email\" name=\"email\" type=\"email\"\n required placeholder=\"<%= t('components.form_validation.demo.email_placeholder') %>\"\n data-stimeo--form-field-target=\"control\" />\n <p class=\"form-validation-demo__error\" role=\"alert\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n </div>\n\n <%# The listbox keeps its committed value in the mirror below. The form-field\n control is the visible trigger, so aria-invalid and invalid focus land there. %>\n <div class=\"form-validation-demo__field listbox\"\n data-controller=\"stimeo--form-field stimeo--listbox\">\n <span id=\"fv-plan-label\" class=\"form-validation-demo__label\">\n <%= t(\"components.form_validation.demo.plan_label\") %>\n </span>\n <button\n type=\"button\"\n class=\"demo-trigger listbox__trigger\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded=\"false\"\n aria-controls=\"fv-plan-options\"\n aria-labelledby=\"fv-plan-label fv-plan-value\"\n data-stimeo--listbox-target=\"trigger\"\n data-stimeo--form-field-target=\"control\"\n data-action=\"click->stimeo--listbox#toggle keydown->stimeo--listbox#onTriggerKeydown\">\n <span id=\"fv-plan-value\" data-stimeo--listbox-target=\"value\">\n <%= t(\"components.form_validation.demo.plan_placeholder\") %>\n </span>\n <span class=\"listbox__chevron\" aria-hidden=\"true\">▾</span>\n </button>\n <ul id=\"fv-plan-options\" class=\"listbox__list\" role=\"listbox\"\n aria-labelledby=\"fv-plan-label\" hidden data-stimeo--listbox-target=\"list\">\n <% %w[free pro team].each_with_index do |plan, index| %>\n <li id=\"fv-plan-opt-<%= index %>\" class=\"listbox__option\" role=\"option\"\n aria-selected=\"false\" data-value=\"<%= plan %>\"\n data-stimeo--listbox-target=\"option\"\n data-action=\"click->stimeo--listbox#select\">\n <%= t(\"components.form_validation.demo.plans.#{plan}\") %>\n </li>\n <% end %>\n </ul>\n <%# Validatable mirror: the hidden attribute participates in constraint\n validation and submits with the form — type=\"hidden\" would do neither. %>\n <input type=\"text\" hidden required name=\"plan\" data-stimeo--listbox-target=\"field\" />\n <p class=\"form-validation-demo__error\" role=\"alert\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n </div>\n\n <p class=\"form-validation-demo__note\"><%= t(\"components.form_validation.demo.note\") %></p>\n\n <button type=\"submit\" class=\"demo-trigger\">\n <%= t(\"components.form_validation.demo.submit\") %>\n </button>\n\n <%# Status line updated by demo.js; templates ride data-* so the copy stays bilingual. %>\n <p class=\"form-validation-demo__status\" id=\"fv-status\" aria-live=\"polite\"\n data-valid-text=\"<%= t('components.form_validation.demo.valid_status') %>\"\n data-invalid-template=\"<%= t('components.form_validation.demo.invalid_template') %>\"></p>\n</form>\n"
162
+ "source": "<%# Markup for the form-validation demo.\n stimeo--form-validation orchestrates WHEN native constraint validation runs\n (submit / blur / change / input) and routes each control's message into its\n stimeo--form-field visual error — the ARIA wiring stays in form-field, and\n the speech goes to the shared announcer seated at the end of this form.\n Rules are plain markup attributes (required, type=\"email\", plus the\n declarative disallow=\"whitespace\"); the wording comes from localized\n data-stimeo--form-validation-message-* overrides rather than the browser, so\n the catalog reads the same in both locales. The plan listbox joins through a\n validatable mirror: <input type=\"text\" hidden required> — the hidden\n ATTRIBUTE, not type=\"hidden\", which is barred from constraint validation.\n The authored novalidate keeps the no-JS fallback graceful (the controller\n preserves it). Valid submissions are kept on the page by demo.js so the\n catalog can show a success status. %>\n<form class=\"form-validation-demo\" action=\"#\" method=\"get\" novalidate\n data-controller=\"stimeo--form-validation\"\n data-stimeo--form-validation-stimeo--form-field-outlet=\".form-validation-demo__field\">\n\n <div class=\"form-validation-demo__field\" data-controller=\"stimeo--form-field\">\n <label class=\"form-validation-demo__label\" for=\"fv-email\">\n <%= t(\"components.form_validation.demo.email_label\") %>\n </label>\n <input class=\"form-validation-demo__control\" id=\"fv-email\" name=\"email\" type=\"email\"\n required placeholder=\"<%= t('components.form_validation.demo.email_placeholder') %>\"\n data-stimeo--form-validation-message-value-missing=\n \"<%= t('components.form_validation.demo.messages.email_required') %>\"\n data-stimeo--form-validation-message-type-mismatch=\n \"<%= t('components.form_validation.demo.messages.email_type') %>\"\n data-stimeo--form-field-target=\"control\" />\n <%# Visual error only. Speech goes through the shared announcer seated below. %>\n <p class=\"form-validation-demo__error\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n </div>\n\n <div class=\"form-validation-demo__field\" data-controller=\"stimeo--form-field\">\n <label class=\"form-validation-demo__label\" for=\"fv-name\">\n <%= t(\"components.form_validation.demo.name_label\") %>\n </label>\n <input class=\"form-validation-demo__control\" id=\"fv-name\" name=\"name\" type=\"text\"\n required placeholder=\"<%= t('components.form_validation.demo.name_placeholder') %>\"\n data-stimeo--form-validation-disallow=\"whitespace\"\n data-stimeo--form-validation-message-value-missing=\n \"<%= t('components.form_validation.demo.messages.name_required') %>\"\n data-stimeo--form-validation-message-whitespace=\n \"<%= t('components.form_validation.demo.messages.name_whitespace') %>\"\n data-stimeo--form-field-target=\"control\" />\n <p class=\"form-validation-demo__error\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n </div>\n\n <%# The listbox keeps its committed value in the mirror below. The form-field\n control is the visible trigger, so aria-invalid and invalid focus land there. %>\n <div class=\"form-validation-demo__field listbox\"\n data-controller=\"stimeo--form-field stimeo--listbox\">\n <span id=\"fv-plan-label\" class=\"form-validation-demo__label\">\n <%= t(\"components.form_validation.demo.plan_label\") %>\n </span>\n <button\n type=\"button\"\n class=\"demo-trigger listbox__trigger\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded=\"false\"\n aria-controls=\"fv-plan-options\"\n aria-labelledby=\"fv-plan-label fv-plan-value\"\n data-stimeo--listbox-target=\"trigger\"\n data-stimeo--form-field-target=\"control\"\n data-action=\"click->stimeo--listbox#toggle keydown->stimeo--listbox#onTriggerKeydown\">\n <span id=\"fv-plan-value\" data-stimeo--listbox-target=\"value\">\n <%= t(\"components.form_validation.demo.plan_placeholder\") %>\n </span>\n <span class=\"listbox__chevron\" aria-hidden=\"true\">▾</span>\n </button>\n <ul id=\"fv-plan-options\" class=\"listbox__list\" role=\"listbox\"\n aria-labelledby=\"fv-plan-label\" hidden data-stimeo--listbox-target=\"list\">\n <% %w[free pro team].each_with_index do |plan, index| %>\n <li id=\"fv-plan-opt-<%= index %>\" class=\"listbox__option\" role=\"option\"\n aria-selected=\"false\" data-value=\"<%= plan %>\"\n data-stimeo--listbox-target=\"option\"\n data-action=\"click->stimeo--listbox#select\">\n <%= t(\"components.form_validation.demo.plans.#{plan}\") %>\n </li>\n <% end %>\n </ul>\n <%# Validatable mirror: the hidden attribute participates in constraint\n validation and submits with the form — type=\"hidden\" would do neither. %>\n <input type=\"text\" hidden required name=\"plan\"\n data-stimeo--form-validation-message-value-missing=\n \"<%= t('components.form_validation.demo.messages.plan_required') %>\"\n data-stimeo--listbox-target=\"field\" />\n <p class=\"form-validation-demo__error\" hidden\n data-stimeo--form-field-target=\"error\"></p>\n </div>\n\n <p class=\"form-validation-demo__note\"><%= t(\"components.form_validation.demo.note\") %></p>\n\n <button type=\"submit\" class=\"demo-trigger\">\n <%= t(\"components.form_validation.demo.submit\") %>\n </button>\n\n <%# Status line updated by demo.js; templates ride data-* so the copy stays bilingual. %>\n <p class=\"form-validation-demo__status\" id=\"fv-status\" aria-live=\"polite\"\n data-valid-text=\"<%= t('components.form_validation.demo.valid_status') %>\"\n data-invalid-template=\"<%= t('components.form_validation.demo.invalid_template') %>\"></p>\n\n <%# Assertive announcement target for the field errors form-field sets. One\n receiver serves every field, and it is seated in the accessibility tree\n ahead of the first error, which is what makes the message observable. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\" data-stimeo--announcer-target=\"assertive\"\n aria-live=\"assertive\" aria-atomic=\"true\"></div>\n </div>\n</form>\n"
163
163
  },
164
164
  "stimeo--frame-loading": {
165
165
  "file": "examples/frame_loading/_demo.html.erb",
@@ -219,7 +219,7 @@
219
219
  },
220
220
  "stimeo--multi-select": {
221
221
  "file": "examples/multi_select/_demo.html.erb",
222
- "source": "<%# Markup for the multi-select (multiple-selection combobox) demo.\n Filter candidates as you type, select several, and show the selected ones as chips.\n Focus stays on the input; the active candidate is shown via aria-activedescendant\n and selection via aria-selected. The library handles filtering, multi-select\n toggling, chip create/remove, roving, live announcements, and dismiss. Placement\n lives in demo.css. %>\n<div class=\"multi-select\" data-controller=\"stimeo--multi-select\">\n <span id=\"multi-select-label\" class=\"multi-select__label\">\n <%= t(\"components.multi_select.demo.label\") %>\n </span>\n\n <ul\n class=\"multi-select__tags\"\n aria-label=\"<%= t(\"components.multi_select.demo.selected_label\") %>\"\n data-stimeo--multi-select-target=\"tags\"></ul>\n\n <input\n type=\"text\"\n class=\"multi-select__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"multi-select-list\"\n aria-labelledby=\"multi-select-label\"\n placeholder=\"<%= t(\"components.multi_select.demo.placeholder\") %>\"\n data-stimeo--multi-select-target=\"input\"\n data-action=\"input->stimeo--multi-select#filter\n keydown->stimeo--multi-select#onKeydown\n focus->stimeo--multi-select#open\" />\n\n <ul\n id=\"multi-select-list\"\n class=\"multi-select__list\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label=\"<%= t(\"components.multi_select.demo.options_label\") %>\"\n hidden\n data-stimeo--multi-select-target=\"list\">\n <% %w[apple banana cherry grape orange melon].each_with_index do |fruit, index| %>\n <li\n id=\"multi-select-opt-<%= index %>\"\n class=\"multi-select__option\"\n role=\"option\"\n aria-selected=\"false\"\n data-value=\"<%= fruit %>\"\n data-stimeo--multi-select-target=\"option\"\n data-action=\"click->stimeo--multi-select#toggleOption\">\n <%= t(\"components.multi_select.demo.options.#{fruit}\") %>\n </li>\n <% end %>\n </ul>\n\n <span\n role=\"status\"\n aria-live=\"polite\"\n class=\"multi-select__status visually-hidden\"\n data-stimeo--multi-select-target=\"status\"></span>\n\n <template data-stimeo--multi-select-target=\"tagTemplate\">\n <li class=\"multi-select__tag\" data-stimeo--multi-select-target=\"tag\">\n <span data-multi-select-slot=\"label\"></span>\n <%# Removal is handled by a delegated listener on the tags container, so it\n works instantly without waiting on Stimulus wiring a data-action onto the\n dynamically-added button. %>\n <button\n type=\"button\"\n class=\"multi-select__remove\"\n tabindex=\"-1\">×</button>\n </li>\n </template>\n</div>\n"
222
+ "source": "<%# Markup for the multi-select (multiple-selection combobox) demo.\n Filter candidates as you type, select several, and show the selected ones as chips.\n Focus stays on the input; the active candidate is shown via aria-activedescendant\n and selection via aria-selected. The library handles filtering, multi-select\n toggling, chip create/remove, roving, shared announcements, and dismiss. Placement\n lives in demo.css. %>\n<% announce_selected = t(\"components.multi_select.demo.announce_selected\") %>\n<% announce_removed = t(\"components.multi_select.demo.announce_removed\") %>\n<% remove_label = t(\"components.multi_select.demo.remove_label\") %>\n<div\n class=\"multi-select\"\n data-controller=\"stimeo--multi-select\"\n data-stimeo--multi-select-announce-text-value=\"<%= announce_selected %>\"\n data-stimeo--multi-select-announce-removed-text-value=\"<%= announce_removed %>\">\n <span id=\"multi-select-label\" class=\"multi-select__label\">\n <%= t(\"components.multi_select.demo.label\") %>\n </span>\n\n <ul\n class=\"multi-select__tags\"\n aria-label=\"<%= t(\"components.multi_select.demo.selected_label\") %>\"\n data-stimeo--multi-select-target=\"tags\"></ul>\n\n <input\n type=\"text\"\n class=\"multi-select__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"multi-select-list\"\n aria-labelledby=\"multi-select-label\"\n placeholder=\"<%= t(\"components.multi_select.demo.placeholder\") %>\"\n data-stimeo--multi-select-target=\"input\"\n data-action=\"input->stimeo--multi-select#filter\n keydown->stimeo--multi-select#onKeydown\n focus->stimeo--multi-select#open\" />\n\n <ul\n id=\"multi-select-list\"\n class=\"multi-select__list\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label=\"<%= t(\"components.multi_select.demo.options_label\") %>\"\n hidden\n data-stimeo--multi-select-target=\"list\">\n <% %w[apple banana cherry grape orange melon].each_with_index do |fruit, index| %>\n <li\n id=\"multi-select-opt-<%= index %>\"\n class=\"multi-select__option\"\n role=\"option\"\n aria-selected=\"false\"\n data-value=\"<%= fruit %>\"\n data-stimeo--multi-select-target=\"option\"\n data-action=\"click->stimeo--multi-select#toggleOption\">\n <%= t(\"components.multi_select.demo.options.#{fruit}\") %>\n </li>\n <% end %>\n </ul>\n\n <template data-stimeo--multi-select-target=\"tagTemplate\">\n <li class=\"multi-select__tag\" data-stimeo--multi-select-target=\"tag\">\n <span data-stimeo--multi-select-target=\"label\"></span>\n <%# Removal is handled by a delegated listener on the tags container, so it\n works instantly without waiting on Stimulus wiring a data-action onto the\n dynamically-added button. %>\n <button\n type=\"button\"\n class=\"multi-select__remove\"\n tabindex=\"-1\"\n aria-label=\"<%= remove_label %>\"\n data-stimeo--multi-select-target=\"remove\">×</button>\n </li>\n </template>\n\n <%# Selection announcements go to the shared announcer that lives in the page. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\" data-stimeo--announcer-target=\"polite\"\n aria-live=\"polite\" aria-atomic=\"true\"></div>\n </div>\n</div>\n"
223
223
  },
224
224
  "stimeo--navigation-menu": {
225
225
  "file": "examples/navigation_menu/_demo.html.erb",
@@ -295,7 +295,7 @@
295
295
  },
296
296
  "stimeo--radio-group": {
297
297
  "file": "examples/radio_group/_demo.html.erb",
298
- "source": "<%# Markup for the radio-group (APG Radio Group) demo.\n A custom card-style radio. Single selection is shown via aria-checked and the single\n tab stop via roving tabindex. Arrows move the selection (focus = selection). The\n selected value is reflected into a hidden field; the look is the consumer's CSS. %>\n<div class=\"radio-group-demo\" role=\"radiogroup\" aria-labelledby=\"rg-label\"\n data-controller=\"stimeo--radio-group\">\n <span class=\"radio-group-demo__legend\" id=\"rg-label\">\n <%= t(\"components.radio_group.demo.legend\") %>\n </span>\n <p class=\"radio-group-demo__hint\"><%= t(\"components.radio_group.demo.hint\") %></p>\n\n <div class=\"radio-group-demo__options\">\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"0\"\n data-value=\"basic\" data-stimeo--radio-group-target=\"radio\"\n data-action=\"click->stimeo--radio-group#select\n keydown->stimeo--radio-group#onKeydown\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.basic\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.basic_desc\") %></span>\n </div>\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"-1\"\n data-value=\"pro\" data-stimeo--radio-group-target=\"radio\"\n data-action=\"click->stimeo--radio-group#select\n keydown->stimeo--radio-group#onKeydown\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.pro\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.pro_desc\") %></span>\n </div>\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"-1\"\n data-value=\"max\" data-stimeo--radio-group-target=\"radio\"\n data-action=\"click->stimeo--radio-group#select\n keydown->stimeo--radio-group#onKeydown\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.max\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.max_desc\") %></span>\n </div>\n </div>\n\n <input type=\"hidden\" name=\"plan\" value=\"\" data-stimeo--radio-group-target=\"field\" />\n\n <p class=\"radio-group-demo__status\" aria-live=\"polite\">\n <%= t(\"components.radio_group.demo.selected\") %>\n <span data-radio-value><%= t(\"components.radio_group.demo.none\") %></span>\n </p>\n</div>\n"
298
+ "source": "<%# Markup for the radio-group (APG Radio Group) demo.\n A custom card-style radio. Single selection is shown via aria-checked and the single\n tab stop via roving tabindex. Arrows move the selection (focus = selection). The group\n delegates click and keydown, so the radios carry no data-action of their own. The\n selected value is reflected into a hidden field; the look is the consumer's CSS. %>\n<div class=\"radio-group-demo\" role=\"radiogroup\" aria-labelledby=\"rg-label\"\n data-controller=\"stimeo--radio-group\">\n <span class=\"radio-group-demo__legend\" id=\"rg-label\">\n <%= t(\"components.radio_group.demo.legend\") %>\n </span>\n <p class=\"radio-group-demo__hint\"><%= t(\"components.radio_group.demo.hint\") %></p>\n\n <div class=\"radio-group-demo__options\">\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"0\"\n data-value=\"basic\" data-stimeo--radio-group-target=\"radio\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.basic\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.basic_desc\") %></span>\n </div>\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"-1\"\n data-value=\"pro\" data-stimeo--radio-group-target=\"radio\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.pro\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.pro_desc\") %></span>\n </div>\n <div class=\"radio-group-demo__option\" role=\"radio\" aria-checked=\"false\" tabindex=\"-1\"\n data-value=\"max\" data-stimeo--radio-group-target=\"radio\">\n <span class=\"radio-group-demo__name\"><%= t(\"components.radio_group.demo.max\") %></span>\n <span class=\"radio-group-demo__desc\"><%= t(\"components.radio_group.demo.max_desc\") %></span>\n </div>\n </div>\n\n <input type=\"hidden\" name=\"plan\" value=\"\" data-stimeo--radio-group-target=\"field\" />\n\n <p class=\"radio-group-demo__status\" aria-live=\"polite\">\n <%= t(\"components.radio_group.demo.selected\") %>\n <span data-radio-value><%= t(\"components.radio_group.demo.none\") %></span>\n </p>\n</div>\n"
299
299
  },
300
300
  "stimeo--range-slider": {
301
301
  "file": "examples/range_slider/_demo.html.erb",
@@ -303,7 +303,7 @@
303
303
  },
304
304
  "stimeo--rating": {
305
305
  "file": "examples/rating/_demo.html.erb",
306
- "source": "<%# Markup for the rating (APG Radio Group, ordinal scale) demo.\n Each symbol is role=\"radio\" with an aria-label (\"3 stars\", etc.). Selection is\n aria-checked; the currently filled range is data-rating-hover (the selected value or\n a hover preview). Focus alone does not preview, so Space (select the focused star) shows.\n The star look is the consumer's CSS. %>\n<div class=\"rating-demo-wrap\">\n <p class=\"rating-demo__hint\"><%= t(\"components.rating.demo.hint\") %></p>\n <div class=\"rating-demo\" role=\"radiogroup\" aria-label=\"<%= t('components.rating.demo.label') %>\"\n data-controller=\"stimeo--rating\"\n data-stimeo--rating-value-value=\"0\" data-stimeo--rating-max-value=\"5\">\n <% (1..5).each do |n| %>\n <span class=\"rating-demo__star\" role=\"radio\" aria-checked=\"false\"\n aria-label=\"<%= t('components.rating.demo.star', count: n) %>\"\n tabindex=\"<%= n == 1 ? 0 : -1 %>\"\n data-rating-value=\"<%= n %>\" data-stimeo--rating-target=\"symbol\"\n data-action=\"click->stimeo--rating#select\n mouseenter->stimeo--rating#preview\n mouseleave->stimeo--rating#endPreview\n keydown->stimeo--rating#onKeydown\">\n <svg class=\"rating-demo__icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M12 2l2.9 6.26 6.86.6-5.2 4.52 1.56 6.72L12 17.1 5.88 20.6l1.56-6.72-5.2-4.52\n 6.86-.6z\"></path>\n </svg>\n </span>\n <% end %>\n\n <input type=\"hidden\" name=\"rating\" value=\"0\" data-stimeo--rating-target=\"field\" />\n\n <%# Subscribe to the change event to show the current value (updated by demo.js). %>\n <span class=\"rating-demo__status\" aria-live=\"polite\"\n data-empty-text=\"<%= t('components.rating.demo.empty') %>\"\n data-rated-template=\"<%= t('components.rating.demo.rated') %>\"><%= t(\n 'components.rating.demo.empty'\n ) %></span>\n </div>\n</div>\n"
306
+ "source": "<%# Markup for the rating (APG Radio Group, ordinal scale) demo.\n Each symbol is role=\"radio\" with an aria-label (\"3 stars\", etc.). Selection is\n aria-checked; the currently filled range is data-rating-hover (the selected value or\n a hover/focus preview). DOM order defines values 1..N. The star look is the consumer's CSS. %>\n<div class=\"rating-demo-wrap\">\n <p class=\"rating-demo__hint\"><%= t(\"components.rating.demo.hint\") %></p>\n <div class=\"rating-demo\" role=\"radiogroup\" aria-label=\"<%= t('components.rating.demo.label') %>\"\n data-controller=\"stimeo--rating\"\n data-stimeo--rating-value-value=\"0\">\n <% (1..5).each do |n| %>\n <span class=\"rating-demo__star\" role=\"radio\" aria-checked=\"false\"\n aria-label=\"<%= t('components.rating.demo.star', count: n) %>\"\n tabindex=\"<%= n == 1 ? 0 : -1 %>\"\n data-stimeo--rating-target=\"symbol\"\n data-action=\"click->stimeo--rating#select\n mouseenter->stimeo--rating#preview\n mouseleave->stimeo--rating#endPreview\n focus->stimeo--rating#preview\n blur->stimeo--rating#endPreview\n keydown->stimeo--rating#onKeydown\">\n <svg class=\"rating-demo__icon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path d=\"M12 2l2.9 6.26 6.86.6-5.2 4.52 1.56 6.72L12 17.1 5.88 20.6l1.56-6.72-5.2-4.52\n 6.86-.6z\"></path>\n </svg>\n </span>\n <% end %>\n\n <input type=\"hidden\" name=\"rating\" value=\"0\" data-stimeo--rating-target=\"field\" />\n\n <%# Subscribe to committed and reconciled values to update this instance's status. %>\n <span class=\"rating-demo__status\" aria-live=\"polite\"\n data-empty-text=\"<%= t('components.rating.demo.empty') %>\"\n data-rated-template=\"<%= t('components.rating.demo.rated') %>\"><%= t(\n 'components.rating.demo.empty'\n ) %></span>\n </div>\n</div>\n"
307
307
  },
308
308
  "stimeo--read-more": {
309
309
  "file": "examples/read_more/_demo.html.erb",
@@ -347,7 +347,7 @@
347
347
  },
348
348
  "stimeo--separator": {
349
349
  "file": "examples/separator/_demo.html.erb",
350
- "source": "<%# Markup for the separator demo.\n The top is a decorative separator (only role=\"separator\" and aria-orientation).\n The bottom is a valued, focusable separator that increments/decrements aria-valuenow\n with the arrow keys and fires stimeo--separator:change. The actual pane-width change\n (demo.js) and line rendering (demo.css) are the consumer's job. %>\n<div class=\"separator-demo\">\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.intro\") %></p>\n <div\n class=\"separator separator--horizontal\"\n role=\"separator\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-orientation-value=\"horizontal\"></div>\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.after\") %></p>\n\n <div class=\"separator-demo__split\">\n <%# The primary pane: demo.css sizes this one from --separator-split, so the\n value the splitter reports is this pane's size. A vertical splitter can\n only take the left pane as primary, because ArrowRight both moves the\n handle right and increases the value. %>\n <div class=\"separator-demo__pane\" id=\"separator-demo-primary\">\n <%= t(\"components.separator.demo.left\") %>\n </div>\n <div\n class=\"separator separator--vertical\"\n role=\"separator\"\n tabindex=\"0\"\n <%# The pattern names the splitter after the primary pane, and that pane\n carries its own visible text, so it is referenced instead of writing a\n separate label. %>\n aria-labelledby=\"separator-demo-primary\"\n aria-controls=\"separator-demo-primary\"\n aria-orientation=\"vertical\"\n aria-valuemin=\"20\"\n aria-valuemax=\"80\"\n aria-valuenow=\"50\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-focusable-value=\"true\"\n data-stimeo--separator-step-value=\"5\"\n data-action=\"keydown->stimeo--separator#onKeydown\"></div>\n <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.right\") %></div>\n </div>\n\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.after_horizontal\") %></p>\n\n <%# Horizontal, value-bearing separator (top/bottom split). aria-orientation=\"horizontal\"\n scopes the arrows to ArrowUp/ArrowDown. demo.js reuses the same --separator-split var. %>\n <div class=\"separator-demo__split separator-demo__split--stacked\">\n <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.top\") %></div>\n <div\n class=\"separator separator--horizontal-handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-labelledby=\"separator-demo-primary-stacked\"\n aria-controls=\"separator-demo-primary-stacked\"\n aria-orientation=\"horizontal\"\n aria-valuemin=\"20\"\n aria-valuemax=\"80\"\n aria-valuenow=\"50\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-focusable-value=\"true\"\n data-stimeo--separator-step-value=\"5\"\n data-action=\"keydown->stimeo--separator#onKeydown\"></div>\n <%# The primary pane here is the bottom one: demo.css sizes the last child\n from --separator-split, which is what lets ArrowUp both move the handle\n up and increase the value. %>\n <div class=\"separator-demo__pane\" id=\"separator-demo-primary-stacked\">\n <%= t(\"components.separator.demo.bottom\") %>\n </div>\n </div>\n</div>\n"
350
+ "source": "<%# Separator demo markup. The first separator is decorative; the other two are\n focusable and value-bearing, dispatching change as the arrow keys adjust them.\n The consumer-owned demo.js and demo.css turn that value into pane sizes and\n the divider's appearance. %>\n<div class=\"separator-demo\">\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.intro\") %></p>\n <div\n class=\"separator separator--horizontal\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-orientation-value=\"horizontal\"></div>\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.after\") %></p>\n\n <div class=\"separator-demo__split\">\n <%# The primary pane. Its width follows --separator-split, so the separator's\n Value reports this pane's size: ArrowRight raises the value while moving\n the vertical divider right. %>\n <div class=\"separator-demo__pane\" id=\"separator-demo-primary\">\n <%= t(\"components.separator.demo.left\") %>\n </div>\n <div\n class=\"separator separator--vertical\"\n <%# Reuse the primary pane's visible text as the separator's name. %>\n aria-labelledby=\"separator-demo-primary\"\n aria-controls=\"separator-demo-primary\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-orientation-value=\"vertical\"\n data-stimeo--separator-focusable-value=\"true\"\n data-stimeo--separator-min-value=\"20\"\n data-stimeo--separator-max-value=\"80\"\n data-stimeo--separator-step-value=\"5\"\n data-stimeo--separator-value-value=\"50\"\n data-action=\"keydown->stimeo--separator#onKeydown\"></div>\n <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.right\") %></div>\n </div>\n\n <p class=\"separator-demo__text\"><%= t(\"components.separator.demo.after_horizontal\") %></p>\n\n <%# A horizontal separator takes ArrowUp and ArrowDown. demo.js maps the same\n --separator-split custom property onto the top / bottom pane ratio. %>\n <div class=\"separator-demo__split separator-demo__split--stacked\">\n <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.top\") %></div>\n <div\n class=\"separator separator--horizontal-handle\"\n aria-labelledby=\"separator-demo-primary-stacked\"\n aria-controls=\"separator-demo-primary-stacked\"\n data-controller=\"stimeo--separator\"\n data-stimeo--separator-orientation-value=\"horizontal\"\n data-stimeo--separator-focusable-value=\"true\"\n data-stimeo--separator-min-value=\"20\"\n data-stimeo--separator-max-value=\"80\"\n data-stimeo--separator-step-value=\"5\"\n data-stimeo--separator-value-value=\"50\"\n data-action=\"keydown->stimeo--separator#onKeydown\"></div>\n <%# Here the lower pane is the primary one. Flexing the last child from\n --separator-split makes ArrowUp raise the primary pane's value while\n moving the divider up. %>\n <div class=\"separator-demo__pane\" id=\"separator-demo-primary-stacked\">\n <%= t(\"components.separator.demo.bottom\") %>\n </div>\n </div>\n</div>\n"
351
351
  },
352
352
  "stimeo--sidebar": {
353
353
  "file": "examples/sidebar/_demo.html.erb",
@@ -391,7 +391,7 @@
391
391
  },
392
392
  "stimeo--submit-once": {
393
393
  "file": "examples/submit_once/_demo.html.erb",
394
- "source": "<%# Submit-once demo: the submit button disables and swaps its label on submit, then\n restores. This catalog has no server, so demo.js intercepts the submit and simulates\n a round trip by firing turbo:submit-end after a short delay. The library owns the\n disable / aria-busy / data-submitting / label state; this demo only styles it. %>\n<div class=\"submit-once-demo\">\n <form\n data-controller=\"stimeo--submit-once\"\n data-stimeo--submit-once-busy-label-value=\"<%= t('components.submit_once.demo.busy') %>\"\n data-action=\"submit->stimeo--submit-once#start\">\n <label class=\"submit-once-demo__field\">\n <span><%= t(\"components.submit_once.demo.label\") %></span>\n <input type=\"text\" name=\"title\" class=\"submit-once-demo__input\">\n </label>\n <button type=\"submit\" class=\"demo-trigger\" data-stimeo--submit-once-target=\"submit\">\n <%= t(\"components.submit_once.demo.submit\") %>\n </button>\n </form>\n</div>\n"
394
+ "source": "<%# Submit-once demo. A structured idle / busy target pair keeps the DOM the\n consumer authored while the button is disabled. This catalog has no server,\n so demo.js fires Turbo's start / end lifecycle around a short fake round\n trip. %>\n<% announce_start = t(\"components.submit_once.demo.announce_start\") %>\n<% announce_ready = t(\"components.submit_once.demo.announce_ready\") %>\n<% submit_label = t(\"components.submit_once.demo.submit\") %>\n<% busy_label = t(\"components.submit_once.demo.busy\") %>\n<div class=\"submit-once-demo\">\n <form\n data-controller=\"stimeo--submit-once\"\n data-stimeo--submit-once-announce-text-value=\"<%= announce_start %>\"\n data-stimeo--submit-once-announce-ready-text-value=\"<%= announce_ready %>\">\n <label class=\"submit-once-demo__field\">\n <span><%= t(\"components.submit_once.demo.label\") %></span>\n <input type=\"text\" name=\"title\" class=\"submit-once-demo__input\">\n </label>\n <button type=\"submit\" class=\"demo-trigger\" data-stimeo--submit-once-target=\"submit\">\n <span data-stimeo--submit-once-target=\"idle\"><%= submit_label %></span>\n <span data-stimeo--submit-once-target=\"busy\" hidden><%= busy_label %></span>\n </button>\n </form>\n\n <%# aria-busy exposes state that can be queried; this always-present region is\n what announces the transition itself, without moving focus. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\" data-stimeo--announcer-target=\"polite\"\n aria-live=\"polite\" aria-atomic=\"true\"></div>\n </div>\n</div>\n"
395
395
  },
396
396
  "stimeo--switch": {
397
397
  "file": "examples/switch/_demo.html.erb",
@@ -403,7 +403,7 @@
403
403
  },
404
404
  "stimeo--tags-input": {
405
405
  "file": "examples/tags_input/_demo.html.erb",
406
- "source": "<%# Markup for the tags-input demo.\n Enter or a comma (delimiter) turns the input value into a tag, blocking duplicates /\n empties / over-limit. Tag remove buttons move via roving (left/right keys), and\n Backspace on an empty input deletes the previous tag. The library handles tag\n create/remove, hidden-field sync, focus handoff, and live announcements. %>\n<div class=\"tags-input\" data-controller=\"stimeo--tags-input\"\n data-stimeo--tags-input-delimiter-value=\",\"\n data-stimeo--tags-input-name-value=\"frameworks[]\">\n <ul\n class=\"tags-input__tags\"\n role=\"list\"\n aria-label=\"<%= t(\"components.tags_input.demo.tags_label\") %>\"\n data-stimeo--tags-input-target=\"tags\"></ul>\n\n <input\n type=\"text\"\n class=\"tags-input__input\"\n aria-label=\"<%= t(\"components.tags_input.demo.input_label\") %>\"\n aria-describedby=\"tags-input-help\"\n data-stimeo--tags-input-target=\"input\"\n data-action=\"keydown->stimeo--tags-input#onKeydown\" />\n\n <span id=\"tags-input-help\" class=\"tags-input__help\">\n <%= t(\"components.tags_input.demo.help\") %>\n </span>\n\n <span\n role=\"status\"\n aria-live=\"polite\"\n class=\"tags-input__status visually-hidden\"\n data-stimeo--tags-input-target=\"status\"></span>\n\n <%# The library creates and syncs the submit hidden inputs as name=\"frameworks[]\". %>\n <div hidden data-stimeo--tags-input-target=\"fields\"></div>\n\n <template data-stimeo--tags-input-target=\"tagTemplate\">\n <li class=\"tags-input__tag\" role=\"listitem\" data-stimeo--tags-input-target=\"tag\">\n <span data-tags-input-slot=\"label\"></span>\n <%# Removal is handled by a delegated listener on the tags container, so it\n works instantly without waiting on Stimulus wiring a data-action onto the\n dynamically-added button. %>\n <button\n type=\"button\"\n class=\"tags-input__remove\"\n tabindex=\"-1\">×</button>\n </li>\n </template>\n</div>\n"
406
+ "source": "<%# Markup for the tags-input demo.\n Enter or a comma (delimiter) turns the input value into a tag, blocking duplicates /\n empties / over-limit. Tag remove buttons move via roving (left/right keys), and\n Backspace on an empty input deletes the previous tag. The library handles tag\n create/remove, hidden-field sync, focus handoff, and shared announcements. %>\n<% announce_added = t(\"components.tags_input.demo.announce_added\") %>\n<% announce_removed = t(\"components.tags_input.demo.announce_removed\") %>\n<% remove_label = t(\"components.tags_input.demo.remove_label\") %>\n<div class=\"tags-input\" data-controller=\"stimeo--tags-input\"\n data-stimeo--tags-input-delimiter-value=\",\"\n data-stimeo--tags-input-name-value=\"frameworks[]\"\n data-stimeo--tags-input-announce-text-value=\"<%= announce_added %>\"\n data-stimeo--tags-input-announce-removed-text-value=\"<%= announce_removed %>\">\n <ul\n class=\"tags-input__tags\"\n role=\"list\"\n aria-label=\"<%= t(\"components.tags_input.demo.tags_label\") %>\"\n data-stimeo--tags-input-target=\"tags\"></ul>\n\n <input\n type=\"text\"\n class=\"tags-input__input\"\n aria-label=\"<%= t(\"components.tags_input.demo.input_label\") %>\"\n aria-describedby=\"tags-input-help\"\n data-stimeo--tags-input-target=\"input\"\n data-action=\"keydown->stimeo--tags-input#onKeydown\" />\n\n <span id=\"tags-input-help\" class=\"tags-input__help\">\n <%= t(\"components.tags_input.demo.help\") %>\n </span>\n\n <%# The library creates and syncs the submit hidden inputs as name=\"frameworks[]\". %>\n <div hidden data-stimeo--tags-input-target=\"fields\"></div>\n\n <template data-stimeo--tags-input-target=\"tagTemplate\">\n <li class=\"tags-input__tag\" role=\"listitem\" data-stimeo--tags-input-target=\"tag\">\n <span data-stimeo--tags-input-target=\"label\"></span>\n <%# Removal is handled by a delegated listener on the tags container, so it\n works instantly without waiting on Stimulus wiring a data-action onto the\n dynamically-added button. %>\n <button\n type=\"button\"\n class=\"tags-input__remove\"\n tabindex=\"-1\"\n aria-label=\"<%= remove_label %>\"\n data-stimeo--tags-input-target=\"remove\">×</button>\n </li>\n </template>\n\n <%# Tag announcements go to the shared announcer that lives in the page. %>\n <div data-controller=\"stimeo--announcer\">\n <div class=\"visually-hidden\" data-stimeo--announcer-target=\"polite\"\n aria-live=\"polite\" aria-atomic=\"true\"></div>\n </div>\n</div>\n"
407
407
  },
408
408
  "stimeo--textarea-autosize": {
409
409
  "file": "examples/textarea_autosize/_demo.html.erb",
@@ -423,7 +423,7 @@
423
423
  },
424
424
  "stimeo--toggle-group": {
425
425
  "file": "examples/toggle_group/_demo.html.erb",
426
- "source": "<%# Markup for the toggle-group (APG Button toggle + Toolbar roving) demo.\n Pressed state is aria-pressed; the single tab stop is roving tabindex. Arrows only\n move focus; toggling is Space/Enter and click. Multiple selection (multiple). The look\n switches on [aria-pressed=\"true\"]. %>\n<div class=\"toggle-group-demo\" role=\"group\"\n aria-label=\"<%= t('components.toggle_group.demo.label') %>\"\n data-controller=\"stimeo--toggle-group\">\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"0\"\n aria-label=\"<%= t('components.toggle_group.demo.bold') %>\" data-value=\"bold\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 4h8a4 4 0 0 1 0 8H6z\"></path>\n <path d=\"M6 12h9a4 4 0 0 1 0 8H6z\"></path>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.italic') %>\" data-value=\"italic\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"></line>\n <line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"></line>\n <line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"></line>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.underline') %>\" data-value=\"underline\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 3v7a6 6 0 0 0 12 0V3\"></path>\n <line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"></line>\n </svg>\n </button>\n</div>\n"
426
+ "source": "<%# Markup for the toggle-group demo: multiple selection, single selection, and\n unavailable items. The group delegates click and keydown, so the items carry\n no data-action of their own. %>\n<div class=\"toggle-group-demo-suite\">\n <section class=\"toggle-group-demo__example\">\n <h3 class=\"toggle-group-demo__heading\" id=\"toggle-group-multiple-heading\">\n <%= t(\"components.toggle_group.demo.multiple_heading\") %>\n </h3>\n <div class=\"toggle-group-demo\" role=\"group\"\n aria-labelledby=\"toggle-group-multiple-heading\"\n data-controller=\"stimeo--toggle-group\">\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"0\"\n aria-label=\"<%= t('components.toggle_group.demo.bold') %>\" data-value=\"bold\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 4h8a4 4 0 0 1 0 8H6z\"></path>\n <path d=\"M6 12h9a4 4 0 0 1 0 8H6z\"></path>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.italic') %>\" data-value=\"italic\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"></line>\n <line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"></line>\n <line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"></line>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.underline') %>\" data-value=\"underline\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 3v7a6 6 0 0 0 12 0V3\"></path>\n <line x1=\"4\" y1=\"21\" x2=\"20\" y2=\"21\"></line>\n </svg>\n </button>\n </div>\n </section>\n\n <section class=\"toggle-group-demo__example\">\n <h3 class=\"toggle-group-demo__heading\" id=\"toggle-group-single-heading\">\n <%= t(\"components.toggle_group.demo.single_heading\") %>\n </h3>\n <div class=\"toggle-group-demo\" role=\"group\"\n aria-labelledby=\"toggle-group-single-heading\"\n data-controller=\"stimeo--toggle-group\"\n data-stimeo--toggle-group-mode-value=\"single\">\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"true\" tabindex=\"0\"\n aria-label=\"<%= t('components.toggle_group.demo.align_left') %>\" data-value=\"left\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" aria-hidden=\"true\">\n <path d=\"M4 6h16M4 10h11M4 14h16M4 18h9\"></path>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.align_center') %>\" data-value=\"center\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" aria-hidden=\"true\">\n <path d=\"M4 6h16M6 10h12M4 14h16M7 18h10\"></path>\n </svg>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item\" aria-pressed=\"false\" tabindex=\"-1\"\n aria-label=\"<%= t('components.toggle_group.demo.align_right') %>\" data-value=\"right\"\n data-stimeo--toggle-group-target=\"item\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" aria-hidden=\"true\">\n <path d=\"M4 6h16M9 10h11M4 14h16M11 18h9\"></path>\n </svg>\n </button>\n </div>\n </section>\n\n <section class=\"toggle-group-demo__example\">\n <h3 class=\"toggle-group-demo__heading\" id=\"toggle-group-disabled-heading\">\n <%= t(\"components.toggle_group.demo.disabled_heading\") %>\n </h3>\n <div class=\"toggle-group-demo\" role=\"group\"\n aria-labelledby=\"toggle-group-disabled-heading\"\n data-controller=\"stimeo--toggle-group\">\n <button type=\"button\" class=\"toggle-group-demo__item toggle-group-demo__item--wide\"\n aria-pressed=\"false\" tabindex=\"0\" data-value=\"available\"\n data-stimeo--toggle-group-target=\"item\">\n <%= t(\"components.toggle_group.demo.available\") %>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item toggle-group-demo__item--wide\"\n aria-pressed=\"false\" tabindex=\"-1\" data-value=\"native-disabled\" disabled\n data-stimeo--toggle-group-target=\"item\">\n <%= t(\"components.toggle_group.demo.native_disabled\") %>\n </button>\n <button type=\"button\" class=\"toggle-group-demo__item toggle-group-demo__item--wide\"\n aria-pressed=\"false\" tabindex=\"-1\" data-value=\"aria-disabled\" aria-disabled=\"true\"\n data-stimeo--toggle-group-target=\"item\">\n <%= t(\"components.toggle_group.demo.aria_disabled\") %>\n </button>\n </div>\n </section>\n</div>\n"
427
427
  },
428
428
  "stimeo--toolbar": {
429
429
  "file": "examples/toolbar/_demo.html.erb",