stimeo-ui 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/cable/index.d.ts +16 -16
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.js +10 -0
  5. package/dist/controllers/accordion_controller.js.map +1 -1
  6. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  7. package/dist/controllers/breadcrumb_controller.d.ts +97 -10
  8. package/dist/controllers/breadcrumb_controller.js +225 -13
  9. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  10. package/dist/controllers/calendar_controller.d.ts +11 -1
  11. package/dist/controllers/calendar_controller.js +89 -22
  12. package/dist/controllers/calendar_controller.js.map +1 -1
  13. package/dist/controllers/carousel_controller.d.ts +16 -1
  14. package/dist/controllers/carousel_controller.js +47 -6
  15. package/dist/controllers/carousel_controller.js.map +1 -1
  16. package/dist/controllers/collapsible_controller.js +2 -2
  17. package/dist/controllers/collapsible_controller.js.map +1 -1
  18. package/dist/controllers/color_picker_controller.d.ts +12 -1
  19. package/dist/controllers/color_picker_controller.js +46 -7
  20. package/dist/controllers/color_picker_controller.js.map +1 -1
  21. package/dist/controllers/combobox_controller.d.ts +14 -1
  22. package/dist/controllers/combobox_controller.js +162 -23
  23. package/dist/controllers/combobox_controller.js.map +1 -1
  24. package/dist/controllers/command_palette_controller.d.ts +2 -0
  25. package/dist/controllers/command_palette_controller.js +194 -17
  26. package/dist/controllers/command_palette_controller.js.map +1 -1
  27. package/dist/controllers/confirm_controller.js.map +1 -1
  28. package/dist/controllers/context_menu_controller.d.ts +7 -6
  29. package/dist/controllers/context_menu_controller.js +32 -10
  30. package/dist/controllers/context_menu_controller.js.map +1 -1
  31. package/dist/controllers/count_up_controller.js.map +1 -1
  32. package/dist/controllers/data_grid_controller.d.ts +19 -1
  33. package/dist/controllers/data_grid_controller.js +82 -4
  34. package/dist/controllers/data_grid_controller.js.map +1 -1
  35. package/dist/controllers/date_range_picker_controller.d.ts +1 -0
  36. package/dist/controllers/date_range_picker_controller.js +27 -3
  37. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  38. package/dist/controllers/dialog_controller.js.map +1 -1
  39. package/dist/controllers/dismissible_controller.js.map +1 -1
  40. package/dist/controllers/drawer_controller.js.map +1 -1
  41. package/dist/controllers/dropdown_controller.d.ts +1 -2
  42. package/dist/controllers/dropdown_controller.js.map +1 -1
  43. package/dist/controllers/editable_controller.js +1 -0
  44. package/dist/controllers/editable_controller.js.map +1 -1
  45. package/dist/controllers/focus_controller.d.ts +1 -1
  46. package/dist/controllers/focus_controller.js.map +1 -1
  47. package/dist/controllers/form_field_controller.js.map +1 -1
  48. package/dist/controllers/form_validation_controller.js +1 -1
  49. package/dist/controllers/form_validation_controller.js.map +1 -1
  50. package/dist/controllers/highlight_controller.js.map +1 -1
  51. package/dist/controllers/hover_card_controller.d.ts +1 -1
  52. package/dist/controllers/hover_card_controller.js.map +1 -1
  53. package/dist/controllers/intersection_controller.js +36 -11
  54. package/dist/controllers/intersection_controller.js.map +1 -1
  55. package/dist/controllers/lazy_frame_controller.js +31 -10
  56. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  57. package/dist/controllers/listbox_controller.d.ts +9 -2
  58. package/dist/controllers/listbox_controller.js +257 -53
  59. package/dist/controllers/listbox_controller.js.map +1 -1
  60. package/dist/controllers/local_time_controller.js +2 -2
  61. package/dist/controllers/local_time_controller.js.map +1 -1
  62. package/dist/controllers/menu_controller.d.ts +29 -11
  63. package/dist/controllers/menu_controller.js +104 -17
  64. package/dist/controllers/menu_controller.js.map +1 -1
  65. package/dist/controllers/menubar_controller.d.ts +91 -11
  66. package/dist/controllers/menubar_controller.js +415 -63
  67. package/dist/controllers/menubar_controller.js.map +1 -1
  68. package/dist/controllers/meter_controller.js.map +1 -1
  69. package/dist/controllers/multi_select_controller.d.ts +31 -5
  70. package/dist/controllers/multi_select_controller.js +312 -29
  71. package/dist/controllers/multi_select_controller.js.map +1 -1
  72. package/dist/controllers/navigation_menu_controller.d.ts +65 -18
  73. package/dist/controllers/navigation_menu_controller.js +154 -27
  74. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  75. package/dist/controllers/nested_form_controller.d.ts +4 -5
  76. package/dist/controllers/nested_form_controller.js.map +1 -1
  77. package/dist/controllers/number_input_controller.d.ts +1 -1
  78. package/dist/controllers/number_input_controller.js +7 -0
  79. package/dist/controllers/number_input_controller.js.map +1 -1
  80. package/dist/controllers/otp_controller.js +18 -1
  81. package/dist/controllers/otp_controller.js.map +1 -1
  82. package/dist/controllers/overflow_indicator_controller.d.ts +3 -2
  83. package/dist/controllers/overflow_indicator_controller.js +81 -13
  84. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  85. package/dist/controllers/overflow_menu_controller.d.ts +78 -19
  86. package/dist/controllers/overflow_menu_controller.js +381 -57
  87. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  88. package/dist/controllers/pagination_controller.d.ts +36 -5
  89. package/dist/controllers/pagination_controller.js +163 -32
  90. package/dist/controllers/pagination_controller.js.map +1 -1
  91. package/dist/controllers/persist_controller.js +6 -6
  92. package/dist/controllers/persist_controller.js.map +1 -1
  93. package/dist/controllers/pointer_drag_controller.d.ts +2 -3
  94. package/dist/controllers/pointer_drag_controller.js +9 -1
  95. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  96. package/dist/controllers/popover_controller.d.ts +1 -2
  97. package/dist/controllers/popover_controller.js +2 -2
  98. package/dist/controllers/popover_controller.js.map +1 -1
  99. package/dist/controllers/portal_controller.d.ts +4 -4
  100. package/dist/controllers/portal_controller.js.map +1 -1
  101. package/dist/controllers/progress_controller.js.map +1 -1
  102. package/dist/controllers/radio_group_controller.js +22 -3
  103. package/dist/controllers/radio_group_controller.js.map +1 -1
  104. package/dist/controllers/range_slider_controller.d.ts +14 -4
  105. package/dist/controllers/range_slider_controller.js +32 -6
  106. package/dist/controllers/range_slider_controller.js.map +1 -1
  107. package/dist/controllers/rating_controller.js +16 -2
  108. package/dist/controllers/rating_controller.js.map +1 -1
  109. package/dist/controllers/read_more_controller.js +63 -19
  110. package/dist/controllers/read_more_controller.js.map +1 -1
  111. package/dist/controllers/reading_progress_controller.js.map +1 -1
  112. package/dist/controllers/resizable_controller.d.ts +3 -1
  113. package/dist/controllers/resizable_controller.js +65 -1
  114. package/dist/controllers/resizable_controller.js.map +1 -1
  115. package/dist/controllers/roving_controller.js +17 -2
  116. package/dist/controllers/roving_controller.js.map +1 -1
  117. package/dist/controllers/scroll_area_controller.js +86 -12
  118. package/dist/controllers/scroll_area_controller.js.map +1 -1
  119. package/dist/controllers/scroll_restore_controller.js +1 -1
  120. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  121. package/dist/controllers/scroll_visibility_controller.d.ts +6 -3
  122. package/dist/controllers/scroll_visibility_controller.js +33 -3
  123. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  124. package/dist/controllers/scrollspy_controller.d.ts +64 -15
  125. package/dist/controllers/scrollspy_controller.js +346 -73
  126. package/dist/controllers/scrollspy_controller.js.map +1 -1
  127. package/dist/controllers/separator_controller.d.ts +20 -5
  128. package/dist/controllers/separator_controller.js +9 -0
  129. package/dist/controllers/separator_controller.js.map +1 -1
  130. package/dist/controllers/sidebar_controller.d.ts +1 -1
  131. package/dist/controllers/sidebar_controller.js.map +1 -1
  132. package/dist/controllers/skeleton_controller.js +1 -1
  133. package/dist/controllers/skeleton_controller.js.map +1 -1
  134. package/dist/controllers/slider_controller.d.ts +11 -1
  135. package/dist/controllers/slider_controller.js +32 -6
  136. package/dist/controllers/slider_controller.js.map +1 -1
  137. package/dist/controllers/sortable_controller.d.ts +3 -1
  138. package/dist/controllers/sortable_controller.js +34 -3
  139. package/dist/controllers/sortable_controller.js.map +1 -1
  140. package/dist/controllers/spinner_controller.js +1 -1
  141. package/dist/controllers/spinner_controller.js.map +1 -1
  142. package/dist/controllers/stick_to_bottom_controller.d.ts +5 -6
  143. package/dist/controllers/stick_to_bottom_controller.js +2 -1
  144. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  145. package/dist/controllers/sticky_observer_controller.js +32 -11
  146. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  147. package/dist/controllers/submit_once_controller.d.ts +2 -2
  148. package/dist/controllers/submit_once_controller.js.map +1 -1
  149. package/dist/controllers/switch_controller.js +1 -0
  150. package/dist/controllers/switch_controller.js.map +1 -1
  151. package/dist/controllers/tabs_controller.d.ts +11 -3
  152. package/dist/controllers/tabs_controller.js +26 -3
  153. package/dist/controllers/tabs_controller.js.map +1 -1
  154. package/dist/controllers/tags_input_controller.js +22 -2
  155. package/dist/controllers/tags_input_controller.js.map +1 -1
  156. package/dist/controllers/theme_controller.d.ts +1 -1
  157. package/dist/controllers/theme_controller.js +22 -3
  158. package/dist/controllers/theme_controller.js.map +1 -1
  159. package/dist/controllers/time_picker_controller.js +20 -1
  160. package/dist/controllers/time_picker_controller.js.map +1 -1
  161. package/dist/controllers/toast_controller.d.ts +4 -5
  162. package/dist/controllers/toast_controller.js +4 -5
  163. package/dist/controllers/toast_controller.js.map +1 -1
  164. package/dist/controllers/toggle_group_controller.js +23 -2
  165. package/dist/controllers/toggle_group_controller.js.map +1 -1
  166. package/dist/controllers/toolbar_controller.d.ts +53 -13
  167. package/dist/controllers/toolbar_controller.js +230 -31
  168. package/dist/controllers/toolbar_controller.js.map +1 -1
  169. package/dist/controllers/tooltip_controller.d.ts +1 -1
  170. package/dist/controllers/tooltip_controller.js.map +1 -1
  171. package/dist/controllers/transition_controller.js.map +1 -1
  172. package/dist/controllers/tree_view_controller.d.ts +87 -6
  173. package/dist/controllers/tree_view_controller.js +467 -51
  174. package/dist/controllers/tree_view_controller.js.map +1 -1
  175. package/dist/index.js +3514 -689
  176. package/dist/index.js.map +1 -1
  177. package/dist/inspector/cli.d.ts +375 -39
  178. package/dist/inspector/cli.js +228 -14
  179. package/dist/inspector/cli.js.map +1 -1
  180. package/dist/inspector/cli_bin.js +419 -48
  181. package/dist/inspector/cli_bin.js.map +1 -1
  182. package/dist/inspector/examples.json +24 -24
  183. package/dist/inspector/manifest.json +1040 -125
  184. package/package.json +5 -6
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "stimeo--alert-dialog": {
9
9
  "file": "examples/alert_dialog/_demo.html.erb",
10
- "source": "<%# Markup for the alert-dialog demo.\n role=\"alertdialog\" conveys urgency and interrupts until an explicit confirm/cancel\n response is given. Unlike a plain dialog it does not close on a backdrop click.\n The library handles focus move, focus trap, Esc (cancel), background scroll lock,\n background inert, returning focus to the trigger, and firing confirm / cancel\n events. Styling lives in demo.css. %>\n<div class=\"alert-dialog\" data-controller=\"stimeo--alert-dialog\">\n <button\n type=\"button\"\n class=\"alert-dialog__trigger\"\n data-stimeo--alert-dialog-target=\"trigger\"\n data-action=\"click->stimeo--alert-dialog#open\">\n <%= t(\"components.alert_dialog.demo.open_button\") %>\n </button>\n\n <%# A live region that announces the latest response; demo.js reflects confirm / cancel.\n For bilingual support the copy is resolved with I18n server-side and passed via\n data-* attributes, which demo.js only reads (never hardcoded in JS). %>\n <p\n class=\"alert-dialog__status\"\n role=\"status\"\n data-alert-dialog-status\n data-confirm-message=\"<%= t(\"components.alert_dialog.demo.confirmed_message\") %>\"\n data-cancel-message-user=\"<%= t(\n \"components.alert_dialog.demo.cancelled_message\",\n reason: t(\"components.alert_dialog.demo.cancel_reason_user\")\n ) %>\"\n data-cancel-message-escape=\"<%= t(\n \"components.alert_dialog.demo.cancelled_message\",\n reason: t(\"components.alert_dialog.demo.cancel_reason_escape\")\n ) %>\"\n hidden></p>\n\n <%# The role=\"alertdialog\" + aria-modal region. Starts hidden (closed).\n aria-labelledby points to the title, aria-describedby to the body message. %>\n <div\n class=\"alert-dialog__backdrop\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"alert-dialog-title\"\n aria-describedby=\"alert-dialog-desc\"\n data-stimeo--alert-dialog-target=\"dialog\"\n hidden>\n <div class=\"alert-dialog__panel\">\n <h3 id=\"alert-dialog-title\" class=\"alert-dialog__title\">\n <%= t(\"components.alert_dialog.demo.confirm_title\") %>\n </h3>\n <p id=\"alert-dialog-desc\"><%= t(\"components.alert_dialog.demo.confirm_desc\") %></p>\n <div class=\"alert-dialog__actions\">\n <%# Default focus goes to the least-destructive action: Cancel. %>\n <button\n type=\"button\"\n data-stimeo--alert-dialog-target=\"initialFocus\"\n data-action=\"click->stimeo--alert-dialog#cancel\">\n <%= t(\"components.alert_dialog.demo.cancel\") %>\n </button>\n <button\n type=\"button\"\n class=\"alert-dialog__danger\"\n data-action=\"click->stimeo--alert-dialog#confirm\">\n <%= t(\"components.alert_dialog.demo.confirm\") %>\n </button>\n </div>\n </div>\n </div>\n</div>\n"
10
+ "source": "<%# Markup for the alert-dialog demo.\n role=\"alertdialog\" conveys urgency and interrupts until an explicit confirm/cancel\n response is given. Unlike a plain dialog it does not close on a backdrop click.\n The library handles focus move, focus trap, Esc (cancel), background scroll lock,\n background inert, returning focus to the trigger, and firing confirm / cancel\n events. Styling lives in demo.css. %>\n<div class=\"alert-dialog\" data-controller=\"stimeo--alert-dialog\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stimeo--alert-dialog-target=\"trigger\"\n data-action=\"click->stimeo--alert-dialog#open\">\n <%= t(\"components.alert_dialog.demo.open_button\") %>\n </button>\n\n <%# A live region that announces the latest response; demo.js reflects confirm / cancel.\n For bilingual support the copy is resolved with I18n server-side and passed via\n data-* attributes, which demo.js only reads (never hardcoded in JS). %>\n <p\n class=\"alert-dialog__status\"\n role=\"status\"\n data-alert-dialog-status\n data-confirm-message=\"<%= t(\"components.alert_dialog.demo.confirmed_message\") %>\"\n data-cancel-message-user=\"<%= t(\n \"components.alert_dialog.demo.cancelled_message\",\n reason: t(\"components.alert_dialog.demo.cancel_reason_user\")\n ) %>\"\n data-cancel-message-escape=\"<%= t(\n \"components.alert_dialog.demo.cancelled_message\",\n reason: t(\"components.alert_dialog.demo.cancel_reason_escape\")\n ) %>\"\n hidden></p>\n\n <%# The role=\"alertdialog\" + aria-modal region. Starts hidden (closed).\n aria-labelledby points to the title, aria-describedby to the body message. %>\n <div\n class=\"alert-dialog__backdrop\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"alert-dialog-title\"\n aria-describedby=\"alert-dialog-desc\"\n data-stimeo--alert-dialog-target=\"dialog\"\n hidden>\n <div class=\"alert-dialog__panel\">\n <h3 id=\"alert-dialog-title\" class=\"alert-dialog__title\">\n <%= t(\"components.alert_dialog.demo.confirm_title\") %>\n </h3>\n <p id=\"alert-dialog-desc\"><%= t(\"components.alert_dialog.demo.confirm_desc\") %></p>\n <div class=\"alert-dialog__actions\">\n <%# Default focus goes to the least-destructive action: Cancel. %>\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stimeo--alert-dialog-target=\"initialFocus\"\n data-action=\"click->stimeo--alert-dialog#cancel\">\n <%= t(\"components.alert_dialog.demo.cancel\") %>\n </button>\n <button\n type=\"button\"\n class=\"demo-trigger alert-dialog__danger\"\n data-action=\"click->stimeo--alert-dialog#confirm\">\n <%= t(\"components.alert_dialog.demo.confirm\") %>\n </button>\n </div>\n </div>\n </div>\n</div>\n"
11
11
  },
12
12
  "stimeo--anchored": {
13
13
  "file": "examples/anchored/_demo.html.erb",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "stimeo--breadcrumb": {
33
33
  "file": "examples/breadcrumb/_demo.html.erb",
34
- "source": "<%# Markup for the breadcrumb demo.\n The APG structure (nav + ol + aria-current=\"page\") lives in the markup; the\n library only handles the responsive behavior of collapsing middle items into a\n \"…\" disclosure button when width runs short. Separators are drawn by demo.css's\n ::after. %>\n<nav\n class=\"breadcrumb\"\n data-controller=\"stimeo--breadcrumb\"\n aria-label=\"<%= t(\"components.breadcrumb.demo.label\") %>\">\n <ol class=\"breadcrumb__list\" data-stimeo--breadcrumb-target=\"list\">\n <li class=\"breadcrumb__item\">\n <a href=\"#\"><%= t(\"components.breadcrumb.demo.home\") %></a>\n </li>\n <li class=\"breadcrumb__item\" data-stimeo--breadcrumb-target=\"ellipsis\" hidden>\n <button\n type=\"button\"\n class=\"breadcrumb__ellipsis\"\n aria-expanded=\"false\"\n aria-controls=\"breadcrumb-collapsed\"\n aria-label=\"<%= t(\"components.breadcrumb.demo.expand\") %>\"\n data-stimeo--breadcrumb-target=\"trigger\"\n data-action=\"click->stimeo--breadcrumb#toggle\">…</button>\n </li>\n <li\n class=\"breadcrumb__item\"\n id=\"breadcrumb-collapsed\"\n data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#\"><%= t(\"components.breadcrumb.demo.section\") %></a>\n </li>\n <li class=\"breadcrumb__item\" data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#\"><%= t(\"components.breadcrumb.demo.subsection\") %></a>\n </li>\n <li class=\"breadcrumb__item\" data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#\"><%= t(\"components.breadcrumb.demo.category\") %></a>\n </li>\n <li class=\"breadcrumb__item\">\n <a href=\"#\" aria-current=\"page\"><%= t(\"components.breadcrumb.demo.current\") %></a>\n </li>\n </ol>\n</nav>\n"
34
+ "source": "<%# Markup for the breadcrumb demo.\n The APG structure (nav + ol + aria-current=\"page\") lives in the markup; the\n library only handles the responsive behavior of collapsing middle items into a\n \"…\" disclosure button when width runs short. Separators are drawn by demo.css's\n ::after. aria-controls lists the id of *every* item the button collapses, so the\n control relationship is complete rather than pointing at the first one only.\n\n About the href values — this part is demo scaffolding, not a pattern to copy. A real\n breadcrumb links to real paths and wants Turbo to handle them; replace the hrefs and\n drop the data-turbo attribute when you copy this.\n\n A catalog demo has nowhere real to go, and the usual placeholder `href=\"#\"` is worse\n than useless here: Turbo reads it as a visit to the current page, re-renders the body\n from the server's HTML, and the trail you just expanded snaps shut (then flashes open\n from the cached snapshot on the way back). Pointing at a same-page fragment only\n helps on the first press — once the URL carries that hash, the next press is a\n same-URL visit again and resets just the same, which is measurable in the browser.\n So the demo opts out of Turbo for this subtree (data-turbo is inherited, so one\n attribute covers every link): the browser then treats them as what they are, a scroll\n to an element in this document, and the controller's state is never torn down. %>\n<div id=\"breadcrumb-demo\" class=\"breadcrumb-demo\" data-turbo=\"false\">\n <%# The trail collapses only when it does not fit, so the demo needs a width it can\n lose. Widen it past the full trail and nothing collapses and nothing scrolls;\n narrow it and the middle items bank behind the \"…\". %>\n <label class=\"demo-width-control\">\n <span><%= t(\"components.breadcrumb.demo.width\") %></span>\n <input type=\"range\" min=\"220\" max=\"640\" value=\"352\" data-breadcrumb-demo-width>\n </label>\n\n <nav\n class=\"breadcrumb\"\n data-controller=\"stimeo--breadcrumb\"\n aria-label=\"<%= t(\"components.breadcrumb.demo.label\") %>\">\n <ol class=\"breadcrumb__list\" data-stimeo--breadcrumb-target=\"list\">\n <li class=\"breadcrumb__item\">\n <a href=\"#breadcrumb-demo\"><%= t(\"components.breadcrumb.demo.home\") %></a>\n </li>\n <li class=\"breadcrumb__item\" data-stimeo--breadcrumb-target=\"ellipsis\" hidden>\n <button\n type=\"button\"\n class=\"breadcrumb__ellipsis\"\n aria-expanded=\"false\"\n aria-controls=\"breadcrumb-collapsed-1 breadcrumb-collapsed-2 breadcrumb-collapsed-3\"\n aria-label=\"<%= t(\"components.breadcrumb.demo.expand\") %>\"\n data-stimeo--breadcrumb-target=\"trigger\"\n data-action=\"click->stimeo--breadcrumb#toggle\">…</button>\n </li>\n <li\n class=\"breadcrumb__item\"\n id=\"breadcrumb-collapsed-1\"\n data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#breadcrumb-demo\"><%= t(\"components.breadcrumb.demo.section\") %></a>\n </li>\n <li\n class=\"breadcrumb__item\"\n id=\"breadcrumb-collapsed-2\"\n data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#breadcrumb-demo\"><%= t(\"components.breadcrumb.demo.subsection\") %></a>\n </li>\n <li\n class=\"breadcrumb__item\"\n id=\"breadcrumb-collapsed-3\"\n data-stimeo--breadcrumb-target=\"collapsible\">\n <a href=\"#breadcrumb-demo\"><%= t(\"components.breadcrumb.demo.category\") %></a>\n </li>\n <li class=\"breadcrumb__item\">\n <a href=\"#breadcrumb-demo\" aria-current=\"page\">\n <%= t(\"components.breadcrumb.demo.current\") %>\n </a>\n </li>\n </ol>\n </nav>\n</div>\n"
35
35
  },
36
36
  "stimeo--bulk-select": {
37
37
  "file": "examples/bulk_select/_demo.html.erb",
@@ -67,11 +67,11 @@
67
67
  },
68
68
  "stimeo--combobox": {
69
69
  "file": "examples/combobox/_demo.html.erb",
70
- "source": "<%# Markup for the combobox (APG Combobox / list autocomplete) demo.\n stimeo--combobox provides list filtering on input, Arrow/Enter/Esc, and\n aria-activedescendant. Focus stays on the input and the active option is shown via\n aria-selected. On selection, data-value (or the text, if absent) is placed in the input. %>\n<div class=\"combobox\" data-controller=\"stimeo--combobox\">\n <label class=\"combobox__label\" for=\"fruit-input\"><%= t(\n \"components.combobox.demo.label\"\n ) %></label>\n <input\n id=\"fruit-input\"\n type=\"text\"\n class=\"combobox__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"fruit-list\"\n autocomplete=\"off\"\n data-stimeo--combobox-target=\"input\"\n data-action=\"input->stimeo--combobox#filter\n keydown->stimeo--combobox#onKeydown\n focus->stimeo--combobox#open\n click->stimeo--combobox#open\">\n\n <ul\n id=\"fruit-list\"\n class=\"combobox__list\"\n role=\"listbox\"\n data-stimeo--combobox-target=\"list\"\n hidden>\n <li role=\"option\" id=\"fruit-apple\" class=\"combobox__option\" data-value=\"Apple\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Apple</li>\n <li role=\"option\" id=\"fruit-apricot\" class=\"combobox__option\" data-value=\"Apricot\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Apricot</li>\n <li role=\"option\" id=\"fruit-banana\" class=\"combobox__option\" data-value=\"Banana\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Banana</li>\n <li role=\"option\" id=\"fruit-cherry\" class=\"combobox__option\" data-value=\"Cherry\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Cherry</li>\n </ul>\n\n <%# Empty state shown when there are no matches. CSS reacts to\n data-stimeo--combobox-empty (set on the root by the library) to reveal it. Copy via i18n. %>\n <div class=\"combobox__empty\"><%= t(\"components.combobox.empty\") %></div>\n</div>\n"
70
+ "source": "<%# Markup for the combobox (APG Combobox / list autocomplete) demo.\n stimeo--combobox provides list filtering on input, Arrow/Enter/Esc, and\n aria-activedescendant. Focus stays on the input and the active option is shown via\n aria-selected. On selection, data-value (or the text, if absent) is placed in the input. %>\n<div class=\"combobox\" data-controller=\"stimeo--combobox\">\n <label id=\"fruit-label\" class=\"combobox__label\" for=\"fruit-input\"><%= t(\n \"components.combobox.demo.label\"\n ) %></label>\n <input\n id=\"fruit-input\"\n type=\"text\"\n class=\"combobox__input\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-autocomplete=\"list\"\n aria-controls=\"fruit-list\"\n autocomplete=\"off\"\n data-stimeo--combobox-target=\"input\"\n data-action=\"input->stimeo--combobox#filter\n keydown->stimeo--combobox#onKeydown\n focus->stimeo--combobox#open\n click->stimeo--combobox#open\">\n\n <ul\n id=\"fruit-list\"\n class=\"combobox__list\"\n role=\"listbox\"\n aria-labelledby=\"fruit-label\"\n data-stimeo--combobox-target=\"list\"\n hidden>\n <li role=\"option\" id=\"fruit-apple\" class=\"combobox__option\" data-value=\"Apple\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Apple</li>\n <li role=\"option\" id=\"fruit-apricot\" class=\"combobox__option\" data-value=\"Apricot\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Apricot</li>\n <li role=\"option\" id=\"fruit-banana\" class=\"combobox__option\" data-value=\"Banana\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Banana</li>\n <li role=\"option\" id=\"fruit-cherry\" class=\"combobox__option\" data-value=\"Cherry\"\n data-stimeo--combobox-target=\"option\"\n data-action=\"click->stimeo--combobox#selectByClick\">Cherry</li>\n </ul>\n\n <%# Empty state shown when there are no matches. CSS reacts to\n data-stimeo--combobox-empty (set on the root by the library) to reveal it. Copy via i18n. %>\n <div class=\"combobox__empty\"><%= t(\"components.combobox.empty\") %></div>\n</div>\n"
71
71
  },
72
72
  "stimeo--command-palette": {
73
73
  "file": "examples/command_palette/_demo.html.erb",
74
- "source": "<%# Markup for the command_palette demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--command-palette provides the global shortcut (Cmd+K) / arrow navigation /\n focus trap. The controller registers the global keydown itself in connect(), so no\n keydown wiring is needed here. %>\n<div\n class=\"command-palette-demo\"\n data-controller=\"stimeo--command-palette\">\n \n <button\n type=\"button\"\n class=\"command-palette-demo__trigger\"\n data-action=\"click->stimeo--command-palette#open\">\n <%= t(\"components.command_palette.demo.open_button\") %>\n </button>\n <p class=\"command-palette-demo__help\">\n <%= t(\"components.command_palette.demo.help_text_html\") %>\n </p>\n\n <%# Overlay backdrop. Closes only on a click on the backdrop itself\n (closeOnBackdrop ignores inner clicks). %>\n <div\n class=\"command-palette\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"<%= t(\"components.command_palette.demo.popular_title\") %>\"\n data-stimeo--command-palette-target=\"dialog\"\n data-action=\"click->stimeo--command-palette#closeOnBackdrop\"\n hidden>\n \n <%# The palette body container. %>\n <div class=\"command-palette__panel\">\n <%# Control area (Combobox structure). role/aria-* go on the real input\n (the controller syncs them). %>\n <div class=\"command-palette__search-wrapper\">\n \n <svg class=\"command-palette__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n\n <input\n type=\"text\"\n placeholder=\"<%= t(\"components.command_palette.demo.placeholder\") %>\"\n class=\"command-palette__input\"\n data-stimeo--command-palette-target=\"input\"\n data-action=\"input->stimeo--command-palette#filter\n keydown->stimeo--command-palette#onKeydown\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n aria-controls=\"playground-command-list\"\n aria-autocomplete=\"list\" />\n\n <button\n type=\"button\"\n class=\"command-palette__close-btn\"\n data-action=\"click->stimeo--command-palette#close\"\n aria-label=\"<%= t(\"components.command_palette.demo.close_label\") %>\">\n <kbd class=\"command-palette-demo__kbd font-normal text-xs\">ESC</kbd>\n </button>\n </div>\n\n <%# Listbox display area. %>\n <ul\n id=\"playground-command-list\"\n role=\"listbox\"\n aria-label=\"<%= t(\"components.command_palette.demo.popular_title\") %>\"\n class=\"command-palette__listbox\"\n data-stimeo--command-palette-target=\"list\">\n \n <%# Section heading (data-disabled=\"true\" makes it non-selectable). %>\n <li id=\"cmd-popular-heading\" role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-disabled=\"true\" class=\"command-palette__group-title\">\n <%= t(\"components.command_palette.demo.popular_title\") %>\n </li>\n\n <li\n id=\"cmd-profile\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.profile_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/>\n <circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.profile_label\"\n ) %></span>\n <span class=\"command-palette__option-shortcut\">Go P</span>\n </li>\n\n <li\n id=\"cmd-settings\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.settings_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65\n 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0\n 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65\n 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0\n 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0\n 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1\n 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0\n 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0\n 0-1.51 1z\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.settings_label\"\n ) %></span>\n <span class=\"command-palette__option-shortcut\">Go S</span>\n </li>\n\n <li id=\"cmd-admin-heading\" role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-disabled=\"true\" class=\"command-palette__group-title\">\n <%= t(\"components.command_palette.demo.admin_title\") %>\n </li>\n\n <li\n id=\"cmd-theme-dark\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.dark_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.dark_label\"\n ) %></span>\n </li>\n\n <li\n id=\"cmd-theme-light\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.light_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.light_label\"\n ) %></span>\n </li>\n </ul>\n </div>\n </div>\n</div>\n"
74
+ "source": "<%# Markup for the command_palette demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--command-palette provides the global shortcut (Cmd+K) / arrow navigation /\n focus trap. The controller registers the global keydown itself in connect(), so no\n keydown wiring is needed here. %>\n<div\n class=\"command-palette-demo\"\n data-controller=\"stimeo--command-palette\">\n \n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-action=\"click->stimeo--command-palette#open\">\n <%= t(\"components.command_palette.demo.open_button\") %>\n </button>\n <p class=\"command-palette-demo__help\">\n <%= t(\"components.command_palette.demo.help_text_html\") %>\n </p>\n\n <%# Overlay backdrop. Closes only on a click on the backdrop itself\n (closeOnBackdrop ignores inner clicks). %>\n <div\n class=\"command-palette\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"<%= t(\"components.command_palette.demo.popular_title\") %>\"\n data-stimeo--command-palette-target=\"dialog\"\n data-action=\"click->stimeo--command-palette#closeOnBackdrop\"\n hidden>\n \n <%# The palette body container. %>\n <div class=\"command-palette__panel\">\n <%# Control area (Combobox structure). role/aria-* go on the real input\n (the controller syncs them). %>\n <div class=\"command-palette__search-wrapper\">\n \n <svg class=\"command-palette__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/>\n </svg>\n\n <input\n type=\"text\"\n placeholder=\"<%= t(\"components.command_palette.demo.placeholder\") %>\"\n class=\"command-palette__input\"\n data-stimeo--command-palette-target=\"input\"\n data-action=\"input->stimeo--command-palette#filter\n keydown->stimeo--command-palette#onKeydown\"\n role=\"combobox\"\n aria-expanded=\"false\"\n aria-haspopup=\"listbox\"\n aria-controls=\"playground-command-list\"\n aria-autocomplete=\"list\" />\n\n <button\n type=\"button\"\n class=\"command-palette__close-btn\"\n data-action=\"click->stimeo--command-palette#close\"\n aria-label=\"<%= t(\"components.command_palette.demo.close_label\") %>\">\n <kbd class=\"command-palette-demo__kbd font-normal text-xs\">ESC</kbd>\n </button>\n </div>\n\n <%# Listbox display area. %>\n <ul\n id=\"playground-command-list\"\n role=\"listbox\"\n aria-label=\"<%= t(\"components.command_palette.demo.popular_title\") %>\"\n class=\"command-palette__listbox\"\n data-stimeo--command-palette-target=\"list\">\n \n <%# Section heading (data-disabled=\"true\" makes it non-selectable). %>\n <li id=\"cmd-popular-heading\" role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-disabled=\"true\" class=\"command-palette__group-title\">\n <%= t(\"components.command_palette.demo.popular_title\") %>\n </li>\n\n <li\n id=\"cmd-profile\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.profile_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/>\n <circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.profile_label\"\n ) %></span>\n <span class=\"command-palette__option-shortcut\">Go P</span>\n </li>\n\n <li\n id=\"cmd-settings\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.settings_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65\n 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0\n 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65\n 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0\n 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0\n 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1\n 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0\n 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0\n 0-1.51 1z\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.settings_label\"\n ) %></span>\n <span class=\"command-palette__option-shortcut\">Go S</span>\n </li>\n\n <li id=\"cmd-admin-heading\" role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-disabled=\"true\" class=\"command-palette__group-title\">\n <%= t(\"components.command_palette.demo.admin_title\") %>\n </li>\n\n <li\n id=\"cmd-theme-dark\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.dark_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.dark_label\"\n ) %></span>\n </li>\n\n <li\n id=\"cmd-theme-light\"\n role=\"option\"\n data-stimeo--command-palette-target=\"option\"\n data-action=\"click->stimeo--command-palette#selectByClick\"\n data-alert=\"<%= t(\"components.command_palette.demo.light_alert\") %>\"\n class=\"command-palette__option\">\n <svg class=\"command-palette__option-icon\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"/>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/>\n </svg>\n <span class=\"command-palette__option-text\"><%= t(\n \"components.command_palette.demo.light_label\"\n ) %></span>\n </li>\n </ul>\n </div>\n </div>\n</div>\n"
75
75
  },
76
76
  "stimeo--conditional-fields": {
77
77
  "file": "examples/conditional_fields/_demo.html.erb",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "stimeo--context-menu": {
85
85
  "file": "examples/context_menu/_demo.html.erb",
86
- "source": "<%# Markup for the context_menu demo.\n A right-click on the region (or the ContextMenu key / Shift+F10) opens a\n role=\"menu\" at the pointer position. The interaction model is the same as a Menu\n Button. The click coordinate is reflected on the menu as\n --stimeo-context-menu-x / -y, and the Playground CSS uses it for top/left\n placement (works even without the positioning module). %>\n<div class=\"context-menu\" data-controller=\"stimeo--context-menu\">\n <div\n class=\"context-menu__region\"\n data-stimeo--context-menu-target=\"region\"\n tabindex=\"0\"\n aria-haspopup=\"menu\"\n aria-controls=\"context-menu-list\"\n data-action=\"\n contextmenu->stimeo--context-menu#open\n keydown->stimeo--context-menu#onRegionKeydown\">\n <%= t(\"components.context_menu.demo.region\") %>\n </div>\n\n <ul\n class=\"context-menu__list\"\n id=\"context-menu-list\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.context_menu.demo.label\") %>\"\n data-stimeo--context-menu-target=\"menu\"\n hidden>\n <li role=\"none\">\n <button type=\"button\" class=\"context-menu__item\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.copy\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"context-menu__item\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.paste\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"context-menu__item\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.delete\") %>\n </button>\n </li>\n </ul>\n</div>\n"
86
+ "source": "<%# Markup for the context_menu demo.\n A right-click on the region (or the ContextMenu key / Shift+F10) opens a\n role=\"menu\" at the pointer position. The interaction model is the same as a Menu\n Button. The click coordinate is reflected on the menu as\n --stimeo-context-menu-x / -y, and the Playground CSS uses it for top/left\n placement (works even without the positioning module). %>\n<div class=\"context-menu\" data-controller=\"stimeo--context-menu\">\n <div\n class=\"context-menu__region\"\n data-stimeo--context-menu-target=\"region\"\n tabindex=\"0\"\n aria-haspopup=\"menu\"\n aria-controls=\"context-menu-list\"\n data-action=\"\n contextmenu->stimeo--context-menu#open\n keydown->stimeo--context-menu#onRegionKeydown\">\n <%= t(\"components.context_menu.demo.region\") %>\n </div>\n\n <ul\n class=\"context-menu__list\"\n id=\"context-menu-list\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.context_menu.demo.label\") %>\"\n data-stimeo--context-menu-target=\"menu\"\n hidden>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.copy\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.paste\") %>\n </button>\n </li>\n <li role=\"none\">\n <button type=\"button\" class=\"demo-menuitem\" role=\"menuitem\" tabindex=\"-1\"\n data-stimeo--context-menu-target=\"item\"\n data-action=\"\n click->stimeo--context-menu#activate\n keydown->stimeo--context-menu#onItemKeydown\">\n <%= t(\"components.context_menu.demo.delete\") %>\n </button>\n </li>\n </ul>\n</div>\n"
87
87
  },
88
88
  "stimeo--count-up": {
89
89
  "file": "examples/count_up/_demo.html.erb",
@@ -107,7 +107,7 @@
107
107
  },
108
108
  "stimeo--dialog": {
109
109
  "file": "examples/dialog/_demo.html.erb",
110
- "source": "<%# Markup for the dialog demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--dialog provides the focus trap, Esc, and background scroll lock.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dialog\" data-controller=\"stimeo--dialog\">\n <button\n type=\"button\"\n class=\"dialog__trigger\"\n data-stimeo--dialog-target=\"trigger\"\n data-action=\"click->stimeo--dialog#open\">\n <%= t(\"components.dialog.demo.open_button\") %>\n </button>\n\n <%# The role=\"dialog\" + aria-modal region. Starts hidden (closed).\n Clicking this element itself (the backdrop) also closes it. %>\n <div\n class=\"dialog__backdrop\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"dialog-title\"\n data-stimeo--dialog-target=\"dialog\"\n data-action=\"click->stimeo--dialog#closeOnBackdrop\"\n hidden>\n <div class=\"dialog__panel\">\n <h3 id=\"dialog-title\" class=\"dialog__title\"><%= t(\n \"components.dialog.demo.confirm_title\"\n ) %></h3>\n <p><%= t(\"components.dialog.demo.confirm_desc\") %></p>\n <div class=\"dialog__actions\">\n <button type=\"button\" data-action=\"click->stimeo--dialog#close\"><%= t(\n \"components.dialog.demo.cancel\"\n ) %></button>\n <button type=\"button\" data-action=\"click->stimeo--dialog#close\"><%= t(\n \"components.dialog.demo.ok\"\n ) %></button>\n </div>\n </div>\n </div>\n</div>\n"
110
+ "source": "<%# Markup for the dialog demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--dialog provides the focus trap, Esc, and background scroll lock.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dialog\" data-controller=\"stimeo--dialog\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stimeo--dialog-target=\"trigger\"\n data-action=\"click->stimeo--dialog#open\">\n <%= t(\"components.dialog.demo.open_button\") %>\n </button>\n\n <%# The role=\"dialog\" + aria-modal region. Starts hidden (closed).\n Clicking this element itself (the backdrop) also closes it. %>\n <div\n class=\"dialog__backdrop\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"dialog-title\"\n data-stimeo--dialog-target=\"dialog\"\n data-action=\"click->stimeo--dialog#closeOnBackdrop\"\n hidden>\n <div class=\"dialog__panel\">\n <h3 id=\"dialog-title\" class=\"dialog__title\"><%= t(\n \"components.dialog.demo.confirm_title\"\n ) %></h3>\n <p><%= t(\"components.dialog.demo.confirm_desc\") %></p>\n <div class=\"dialog__actions\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-action=\"click->stimeo--dialog#close\"><%= t(\n \"components.dialog.demo.cancel\"\n ) %></button>\n <button\n type=\"button\"\n class=\"demo-trigger demo-trigger--primary\"\n data-action=\"click->stimeo--dialog#close\"><%= t(\n \"components.dialog.demo.ok\"\n ) %></button>\n </div>\n </div>\n </div>\n</div>\n"
111
111
  },
112
112
  "stimeo--direct-upload": {
113
113
  "file": "examples/direct_upload/_demo.html.erb",
@@ -123,11 +123,11 @@
123
123
  },
124
124
  "stimeo--drawer": {
125
125
  "file": "examples/drawer/_demo.html.erb",
126
- "source": "<%# Markup for the drawer (side panel) demo.\n A modal panel that slides in from the right edge. In addition to focus move/trap,\n Esc, background scroll lock, background inert, and returning focus to the trigger,\n the library syncs data-state (open/closed) and applies hidden once the transition\n finishes. Which edge it moves from and how (the slide) and the interpretation of\n placement are expressed by the consumer's CSS (demo.css) reading data-placement /\n data-state. %>\n<div\n class=\"drawer\"\n data-controller=\"stimeo--drawer\"\n data-stimeo--drawer-placement-value=\"right\">\n <button\n type=\"button\"\n class=\"drawer__trigger\"\n data-stimeo--drawer-target=\"trigger\"\n data-action=\"click->stimeo--drawer#open\">\n <%= t(\"components.drawer.demo.open_button\") %>\n </button>\n\n <%# Overlay (backdrop). Click to close. data-state stays in sync with the panel. %>\n <div\n class=\"drawer__overlay\"\n data-stimeo--drawer-target=\"overlay\"\n data-action=\"click->stimeo--drawer#closeOnBackdrop\"\n hidden>\n <%# The role=\"dialog\" + aria-modal panel. Starts closed / hidden. %>\n <div\n class=\"drawer__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"drawer-title\"\n data-stimeo--drawer-target=\"panel\"\n data-state=\"closed\"\n hidden>\n <h3 id=\"drawer-title\" class=\"drawer__title\">\n <%= t(\"components.drawer.demo.title\") %>\n </h3>\n <p><%= t(\"components.drawer.demo.body\") %></p>\n <button type=\"button\" class=\"drawer__close\" data-action=\"click->stimeo--drawer#close\">\n <%= t(\"components.drawer.demo.close\") %>\n </button>\n </div>\n </div>\n</div>\n"
126
+ "source": "<%# Markup for the drawer (side panel) demo.\n A modal panel that slides in from the right edge. In addition to focus move/trap,\n Esc, background scroll lock, background inert, and returning focus to the trigger,\n the library syncs data-state (open/closed) and applies hidden once the transition\n finishes. Which edge it moves from and how (the slide) and the interpretation of\n placement are expressed by the consumer's CSS (demo.css) reading data-placement /\n data-state. %>\n<div\n class=\"drawer\"\n data-controller=\"stimeo--drawer\"\n data-stimeo--drawer-placement-value=\"right\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stimeo--drawer-target=\"trigger\"\n data-action=\"click->stimeo--drawer#open\">\n <%= t(\"components.drawer.demo.open_button\") %>\n </button>\n\n <%# Overlay (backdrop). Click to close. data-state stays in sync with the panel. %>\n <div\n class=\"drawer__overlay\"\n data-stimeo--drawer-target=\"overlay\"\n data-action=\"click->stimeo--drawer#closeOnBackdrop\"\n hidden>\n <%# The role=\"dialog\" + aria-modal panel. Starts closed / hidden. %>\n <div\n class=\"drawer__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"drawer-title\"\n data-stimeo--drawer-target=\"panel\"\n data-state=\"closed\"\n hidden>\n <h3 id=\"drawer-title\" class=\"drawer__title\">\n <%= t(\"components.drawer.demo.title\") %>\n </h3>\n <p><%= t(\"components.drawer.demo.body\") %></p>\n <button\n type=\"button\"\n class=\"demo-trigger drawer__close\"\n data-action=\"click->stimeo--drawer#close\">\n <%= t(\"components.drawer.demo.close\") %>\n </button>\n </div>\n </div>\n</div>\n"
127
127
  },
128
128
  "stimeo--dropdown": {
129
129
  "file": "examples/dropdown/_demo.html.erb",
130
- "source": "<%# Markup for the dropdown demo.\n The single source used for both the live render and the copy-paste code.\n Connect to stimeo--dropdown via data-controller / data-*-target / data-action.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dropdown\" data-controller=\"stimeo--dropdown\">\n <button\n type=\"button\"\n class=\"dropdown__trigger\"\n aria-expanded=\"false\"\n data-stimeo--dropdown-target=\"trigger\"\n data-action=\"click->stimeo--dropdown#toggle\">\n <%= t(\"components.dropdown.demo.trigger\") %>\n </button>\n\n <%# The toggled menu. Starts hidden (closed).\n Each link uses click->stimeo--dropdown#close to close the menu on selection.\n (The links are inside the controller element, so an outside click won't close\n it — wire it explicitly.) %>\n <ul class=\"dropdown__menu\" data-stimeo--dropdown-target=\"menu\" hidden>\n <li><a href=\"#profile\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.profile\"\n ) %></a></li>\n <li><a href=\"#settings\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.settings\"\n ) %></a></li>\n <li><a href=\"#sign-out\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.sign_out\"\n ) %></a></li>\n </ul>\n</div>\n"
130
+ "source": "<%# Markup for the dropdown demo.\n The single source used for both the live render and the copy-paste code.\n Connect to stimeo--dropdown via data-controller / data-*-target / data-action.\n The behavior is the library's; the look (classes) is the Playground's CSS. %>\n<div class=\"dropdown\" data-controller=\"stimeo--dropdown\">\n <button\n type=\"button\"\n class=\"demo-trigger\"\n aria-expanded=\"false\"\n data-stimeo--dropdown-target=\"trigger\"\n data-action=\"click->stimeo--dropdown#toggle\">\n <%= t(\"components.dropdown.demo.trigger\") %>\n </button>\n\n <%# The toggled menu. Starts hidden (closed).\n Each link uses click->stimeo--dropdown#close to close the menu on selection.\n (The links are inside the controller element, so an outside click won't close\n it — wire it explicitly.) %>\n <ul class=\"dropdown__menu\" data-stimeo--dropdown-target=\"menu\" hidden>\n <li><a class=\"demo-menuitem\" href=\"#profile\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.profile\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#settings\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.settings\"\n ) %></a></li>\n <li><a class=\"demo-menuitem\" href=\"#sign-out\" data-action=\"click->stimeo--dropdown#close\"><%= t(\n \"components.dropdown.demo.sign_out\"\n ) %></a></li>\n </ul>\n</div>\n"
131
131
  },
132
132
  "stimeo--editable": {
133
133
  "file": "examples/editable/_demo.html.erb",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "stimeo--file-dropzone": {
141
141
  "file": "examples/file_dropzone/_demo.html.erb",
142
- "source": "<%# Markup for the file-dropzone (file drag & drop / image preview) demo.\n Click or keyboard launches the native file input, and drag & drop also adds files.\n It validates accept / maxSize / count and shows image thumbnails via objectURL.\n The library handles the drop bridge, validation, preview create/revoke, focus\n handoff on removal, and live announcements. Drag & drop is only an aid — selection\n is always possible via click / keyboard. %>\n<div class=\"file-dropzone\" data-controller=\"stimeo--file-dropzone\"\n data-stimeo--file-dropzone-max-size-value=\"5242880\"\n data-stimeo--file-dropzone-max-files-value=\"4\"\n data-stimeo--file-dropzone-drag-label-value=\"<%= t(\n 'components.file_dropzone.demo.drag_label'\n ) %>\">\n <div\n class=\"file-dropzone__zone\"\n data-stimeo--file-dropzone-target=\"zone\"\n data-action=\"dragover->stimeo--file-dropzone#onDragOver\n dragleave->stimeo--file-dropzone#onDragLeave\n drop->stimeo--file-dropzone#onDrop\">\n <button\n type=\"button\"\n class=\"file-dropzone__trigger\"\n data-stimeo--file-dropzone-target=\"trigger\"\n data-action=\"click->stimeo--file-dropzone#openDialog\">\n <%= t(\"components.file_dropzone.demo.trigger\") %>\n </button>\n <input\n type=\"file\"\n accept=\"image/*\"\n multiple\n aria-label=\"<%= t('components.file_dropzone.demo.input_label') %>\"\n class=\"file-dropzone__input visually-hidden\"\n data-stimeo--file-dropzone-target=\"input\"\n data-action=\"change->stimeo--file-dropzone#onChange\" />\n </div>\n\n <ul\n class=\"file-dropzone__list\"\n aria-label=\"<%= t('components.file_dropzone.demo.list_label') %>\"\n data-stimeo--file-dropzone-target=\"list\"></ul>\n\n <%# Demo-only: the library accepts only images within the limits (5 MB / 4 files) and\n fires stimeo--file-dropzone:reject for the rest — accepted images get a thumbnail\n preview. Surface the reason visibly so a rejected file (e.g. an over-size photo) is\n not mistaken for a broken preview. demo.js fills this from the reject event; the\n reason copy is localized here. %>\n <p class=\"file-dropzone__error\"\n data-file-dropzone-error\n hidden\n data-reason-type=\"<%= t('components.file_dropzone.demo.reject_type') %>\"\n data-reason-size=\"<%= t('components.file_dropzone.demo.reject_size') %>\"\n data-reason-count=\"<%= t('components.file_dropzone.demo.reject_count') %>\"></p>\n\n <span\n role=\"status\"\n aria-live=\"polite\"\n class=\"file-dropzone__status visually-hidden\"\n data-stimeo--file-dropzone-target=\"status\"></span>\n\n <template data-stimeo--file-dropzone-target=\"itemTemplate\">\n <li class=\"file-dropzone__item\" data-stimeo--file-dropzone-target=\"item\">\n <img class=\"file-dropzone__thumb\" data-file-dropzone-slot=\"thumb\" alt=\"\" hidden />\n <span class=\"file-dropzone__name\" data-file-dropzone-slot=\"name\"></span>\n <%# Removal is handled by a delegated listener on the list 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=\"file-dropzone__remove\">\n <%= t(\"components.file_dropzone.demo.remove\") %>\n </button>\n </li>\n </template>\n</div>\n"
142
+ "source": "<%# Markup for the file-dropzone (file drag & drop / image preview) demo.\n Click or keyboard launches the native file input, and drag & drop also adds files.\n It validates accept / maxSize / count and shows image thumbnails via objectURL.\n The library handles the drop bridge, validation, preview create/revoke, focus\n handoff on removal, and live announcements. Drag & drop is only an aid — selection\n is always possible via click / keyboard. %>\n<div class=\"file-dropzone\" data-controller=\"stimeo--file-dropzone\"\n data-stimeo--file-dropzone-max-size-value=\"5242880\"\n data-stimeo--file-dropzone-max-files-value=\"4\"\n data-stimeo--file-dropzone-drag-label-value=\"<%= t(\n 'components.file_dropzone.demo.drag_label'\n ) %>\">\n <div\n class=\"file-dropzone__zone\"\n data-stimeo--file-dropzone-target=\"zone\"\n data-action=\"dragover->stimeo--file-dropzone#onDragOver\n dragleave->stimeo--file-dropzone#onDragLeave\n drop->stimeo--file-dropzone#onDrop\">\n <button\n type=\"button\"\n class=\"demo-trigger file-dropzone__trigger\"\n data-stimeo--file-dropzone-target=\"trigger\"\n data-action=\"click->stimeo--file-dropzone#openDialog\">\n <%= t(\"components.file_dropzone.demo.trigger\") %>\n </button>\n <input\n type=\"file\"\n accept=\"image/*\"\n multiple\n aria-label=\"<%= t('components.file_dropzone.demo.input_label') %>\"\n class=\"file-dropzone__input visually-hidden\"\n data-stimeo--file-dropzone-target=\"input\"\n data-action=\"change->stimeo--file-dropzone#onChange\" />\n </div>\n\n <ul\n class=\"file-dropzone__list\"\n aria-label=\"<%= t('components.file_dropzone.demo.list_label') %>\"\n data-stimeo--file-dropzone-target=\"list\"></ul>\n\n <%# Demo-only: the library accepts only images within the limits (5 MB / 4 files) and\n fires stimeo--file-dropzone:reject for the rest — accepted images get a thumbnail\n preview. Surface the reason visibly so a rejected file (e.g. an over-size photo) is\n not mistaken for a broken preview. demo.js fills this from the reject event; the\n reason copy is localized here. %>\n <p class=\"file-dropzone__error\"\n data-file-dropzone-error\n hidden\n data-reason-type=\"<%= t('components.file_dropzone.demo.reject_type') %>\"\n data-reason-size=\"<%= t('components.file_dropzone.demo.reject_size') %>\"\n data-reason-count=\"<%= t('components.file_dropzone.demo.reject_count') %>\"></p>\n\n <span\n role=\"status\"\n aria-live=\"polite\"\n class=\"file-dropzone__status visually-hidden\"\n data-stimeo--file-dropzone-target=\"status\"></span>\n\n <template data-stimeo--file-dropzone-target=\"itemTemplate\">\n <li class=\"file-dropzone__item\" data-stimeo--file-dropzone-target=\"item\">\n <img class=\"file-dropzone__thumb\" data-file-dropzone-slot=\"thumb\" alt=\"\" hidden />\n <span class=\"file-dropzone__name\" data-file-dropzone-slot=\"name\"></span>\n <%# Removal is handled by a delegated listener on the list 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=\"file-dropzone__remove\">\n <%= t(\"components.file_dropzone.demo.remove\") %>\n </button>\n </li>\n </template>\n</div>\n"
143
143
  },
144
144
  "stimeo--filter": {
145
145
  "file": "examples/filter/_demo.html.erb",
@@ -159,7 +159,7 @@
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=\"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 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"
163
163
  },
164
164
  "stimeo--frame-loading": {
165
165
  "file": "examples/frame_loading/_demo.html.erb",
@@ -191,7 +191,7 @@
191
191
  },
192
192
  "stimeo--listbox": {
193
193
  "file": "examples/listbox/_demo.html.erb",
194
- "source": "<%# Markup for the listbox demo.\n Pressing the role=\"combobox\" trigger opens a role=\"listbox\"; arrows / typeahead\n navigate the options to pick one. Focus stays on the trigger and the active option\n is shown via aria-activedescendant. The library handles open/close, option\n movement, single selection, reflecting into the trigger label and hidden input, and\n closing on Escape / outside click. Static placement is in demo.css. %>\n<div class=\"listbox\" data-controller=\"stimeo--listbox\">\n <span id=\"listbox-label\" class=\"listbox__label\"><%= t(\"components.listbox.demo.label\") %></span>\n <button\n type=\"button\"\n class=\"listbox__trigger\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded=\"false\"\n aria-controls=\"listbox-options\"\n aria-labelledby=\"listbox-label listbox-value\"\n data-stimeo--listbox-target=\"trigger\"\n data-action=\"click->stimeo--listbox#toggle keydown->stimeo--listbox#onTriggerKeydown\">\n <span id=\"listbox-value\" data-stimeo--listbox-target=\"value\">\n <%= t(\"components.listbox.demo.placeholder\") %>\n </span>\n <span class=\"listbox__chevron\" aria-hidden=\"true\">▾</span>\n </button>\n\n <ul\n id=\"listbox-options\"\n class=\"listbox__list\"\n role=\"listbox\"\n aria-label=\"<%= t(\"components.listbox.demo.label\") %>\"\n hidden\n data-stimeo--listbox-target=\"list\">\n <% %w[apple banana cherry grape orange].each_with_index do |fruit, index| %>\n <li\n id=\"listbox-opt-<%= index %>\"\n class=\"listbox__option\"\n role=\"option\"\n aria-selected=\"false\"\n data-value=\"<%= fruit %>\"\n data-stimeo--listbox-target=\"option\"\n data-action=\"click->stimeo--listbox#select\">\n <%= t(\"components.listbox.demo.options.#{fruit}\") %>\n </li>\n <% end %>\n </ul>\n\n <input type=\"hidden\" name=\"fruit\" data-stimeo--listbox-target=\"field\" />\n</div>\n"
194
+ "source": "<%# Markup for the listbox demo.\n Pressing the role=\"combobox\" trigger opens a role=\"listbox\"; arrows / typeahead\n navigate the options to pick one. Focus stays on the trigger and the active option\n is shown via aria-activedescendant. The library handles open/close, option\n movement, single selection, reflecting into the trigger label and hidden input, and\n closing on Escape / outside click. Static placement is in demo.css. %>\n<div class=\"listbox\" data-controller=\"stimeo--listbox\">\n <span id=\"listbox-label\" class=\"listbox__label\"><%= t(\"components.listbox.demo.label\") %></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=\"listbox-options\"\n aria-labelledby=\"listbox-label listbox-value\"\n data-stimeo--listbox-target=\"trigger\"\n data-action=\"click->stimeo--listbox#toggle keydown->stimeo--listbox#onTriggerKeydown\">\n <span id=\"listbox-value\" data-stimeo--listbox-target=\"value\">\n <%= t(\"components.listbox.demo.placeholder\") %>\n </span>\n <span class=\"listbox__chevron\" aria-hidden=\"true\">▾</span>\n </button>\n\n <ul\n id=\"listbox-options\"\n class=\"listbox__list\"\n role=\"listbox\"\n aria-label=\"<%= t(\"components.listbox.demo.label\") %>\"\n hidden\n data-stimeo--listbox-target=\"list\">\n <%# Two pairs share a first letter (apple/apricot, banana/blueberry) so pressing\n that letter repeatedly cycles between them — the APG type-ahead behavior. %>\n <% %w[apple apricot banana blueberry cherry grape orange].each_with_index do |fruit, index| %>\n <li\n id=\"listbox-opt-<%= index %>\"\n class=\"listbox__option\"\n role=\"option\"\n aria-selected=\"false\"\n data-value=\"<%= fruit %>\"\n data-stimeo--listbox-target=\"option\"\n data-action=\"click->stimeo--listbox#select\">\n <%= t(\"components.listbox.demo.options.#{fruit}\") %>\n </li>\n <% end %>\n </ul>\n\n <input type=\"hidden\" name=\"fruit\" data-stimeo--listbox-target=\"field\" />\n</div>\n"
195
195
  },
196
196
  "stimeo--live-counter": {
197
197
  "file": "examples/live_counter/_demo.html.erb",
@@ -207,11 +207,11 @@
207
207
  },
208
208
  "stimeo--menu": {
209
209
  "file": "examples/menu/_demo.html.erb",
210
- "source": "<%# Markup for the menu (APG Menu Button) demo.\n stimeo--menu is a full role=\"menu\"/\"menuitem\" menu providing arrow-key roving\n focus, Esc, and outside click (distinct from the disclosure dropdown).\n Positioning (static placement) is the Playground's CSS. %>\n<div class=\"menu\" data-controller=\"stimeo--menu\">\n <button\n id=\"menu-trigger\"\n type=\"button\"\n class=\"menu__trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menu-list\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.menu.demo.trigger\") %>\n </button>\n\n <%# The trigger labels the menu; each item is a role=\"menuitem\" button (tabindex=-1). %>\n <ul id=\"menu-list\" class=\"menu__list\" role=\"menu\" aria-labelledby=\"menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"menu__item\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"\n data-action=\"click->stimeo--menu#activate\n keydown->stimeo--menu#onItemKeydown\"><%= t(\n \"components.menu.demo.edit\"\n ) %></button>\n </li>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"menu__item\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"\n data-action=\"click->stimeo--menu#activate\n keydown->stimeo--menu#onItemKeydown\"><%= t(\n \"components.menu.demo.duplicate\"\n ) %></button>\n </li>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"menu__item\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"\n data-action=\"click->stimeo--menu#activate\n keydown->stimeo--menu#onItemKeydown\"><%= t(\n \"components.menu.demo.delete\"\n ) %></button>\n </li>\n </ul>\n</div>\n"
210
+ "source": "<%# Markup for the menu (APG Menu Button) demo.\n stimeo--menu is a full role=\"menu\"/\"menuitem\" menu providing arrow-key roving\n focus, Esc, and outside click (distinct from the disclosure dropdown).\n Positioning (static placement) is the Playground's CSS. %>\n<div class=\"menu\" data-controller=\"stimeo--menu\">\n <button\n id=\"menu-trigger\"\n type=\"button\"\n class=\"demo-trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menu-list\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.menu.demo.trigger\") %>\n </button>\n\n <%# The trigger labels the menu; each item is a role=\"menuitem\" button (tabindex=-1).\n Items need no data-action: the controller handles their clicks and keys from its own\n element, so items added or moved in later work without being wired up. %>\n <ul id=\"menu-list\" class=\"menu__list\" role=\"menu\" aria-labelledby=\"menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"demo-menuitem\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"><%= t(\"components.menu.demo.edit\") %></button>\n </li>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"demo-menuitem\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"><%= t(\"components.menu.demo.duplicate\") %></button>\n </li>\n <li role=\"none\">\n <button type=\"button\" role=\"menuitem\" class=\"demo-menuitem\" tabindex=\"-1\"\n data-stimeo--menu-target=\"item\"><%= t(\"components.menu.demo.delete\") %></button>\n </li>\n </ul>\n</div>\n"
211
211
  },
212
212
  "stimeo--menubar": {
213
213
  "file": "examples/menubar/_demo.html.erb",
214
- "source": "<%# Markup for the menubar demo.\n Top-level items directly under role=\"menubar\" each open a role=\"menu\". The library\n handles roving between top items (left/right keys), opening a menu with\n ArrowDown/Enter, up/down movement and typeahead within a menu, moving sideways to\n an adjacent menu, returning on Escape, and closing on outside click / Tab. Top\n items ↔ menus are linked via aria-controls / id. The look is in demo.css. %>\n<div\n class=\"menubar\"\n data-controller=\"stimeo--menubar\"\n role=\"menubar\"\n aria-label=\"<%= t(\"components.menubar.demo.label\") %>\"\n>\n <button\n type=\"button\"\n class=\"menubar__top\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menubar-file\"\n data-stimeo--menubar-target=\"top\"\n data-action=\"click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown\">\n <%= t(\"components.menubar.demo.menus.file.label\") %>\n </button>\n <ul\n id=\"menubar-file\"\n class=\"menubar__menu\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.menubar.demo.menus.file.label\") %>\"\n hidden\n data-stimeo--menubar-target=\"menu\"\n >\n <% %w[new open save].each do |action| %>\n <li role=\"none\">\n <button\n type=\"button\"\n class=\"menubar__item\"\n role=\"menuitem\"\n tabindex=\"-1\"\n data-stimeo--menubar-target=\"item\"\n data-action=\"click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown\">\n <%= t(\"components.menubar.demo.menus.file.items.#{action}\") %>\n </button>\n </li>\n <% end %>\n </ul>\n\n <button\n type=\"button\"\n class=\"menubar__top\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menubar-edit\"\n data-stimeo--menubar-target=\"top\"\n data-action=\"click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown\">\n <%= t(\"components.menubar.demo.menus.edit.label\") %>\n </button>\n <ul\n id=\"menubar-edit\"\n class=\"menubar__menu\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.menubar.demo.menus.edit.label\") %>\"\n hidden\n data-stimeo--menubar-target=\"menu\"\n >\n <% %w[cut copy paste].each do |action| %>\n <li role=\"none\">\n <button\n type=\"button\"\n class=\"menubar__item\"\n role=\"menuitem\"\n tabindex=\"-1\"\n data-stimeo--menubar-target=\"item\"\n data-action=\"click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown\">\n <%= t(\"components.menubar.demo.menus.edit.items.#{action}\") %>\n </button>\n </li>\n <% end %>\n </ul>\n</div>\n"
214
+ "source": "<%# Markup for the menubar demo.\n Top-level items directly under role=\"menubar\" each open a role=\"menu\". The library\n handles roving between top items (left/right keys), opening a menu with\n ArrowDown/Enter, up/down movement and typeahead within a menu, moving sideways to\n an adjacent menu, returning on Escape, and closing on outside click / Tab. Top\n items ↔ menus are linked via aria-controls / id. The look is in demo.css. %>\n<div\n class=\"menubar\"\n data-controller=\"stimeo--menubar\"\n role=\"menubar\"\n aria-label=\"<%= t(\"components.menubar.demo.label\") %>\"\n>\n <button\n type=\"button\"\n class=\"demo-baritem\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menubar-file\"\n data-stimeo--menubar-target=\"top\"\n data-action=\"click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown\">\n <%= t(\"components.menubar.demo.menus.file.label\") %>\n </button>\n <ul\n id=\"menubar-file\"\n class=\"menubar__menu\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.menubar.demo.menus.file.label\") %>\"\n hidden\n data-stimeo--menubar-target=\"menu\"\n >\n <% %w[new open save].each do |action| %>\n <li role=\"none\">\n <button\n type=\"button\"\n class=\"demo-menuitem\"\n role=\"menuitem\"\n tabindex=\"-1\"\n data-stimeo--menubar-target=\"item\"\n data-action=\"click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown\">\n <%= t(\"components.menubar.demo.menus.file.items.#{action}\") %>\n </button>\n </li>\n <% end %>\n </ul>\n\n <button\n type=\"button\"\n class=\"demo-baritem\"\n role=\"menuitem\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n aria-controls=\"menubar-edit\"\n data-stimeo--menubar-target=\"top\"\n data-action=\"click->stimeo--menubar#toggle keydown->stimeo--menubar#onTopKeydown\">\n <%= t(\"components.menubar.demo.menus.edit.label\") %>\n </button>\n <ul\n id=\"menubar-edit\"\n class=\"menubar__menu\"\n role=\"menu\"\n aria-label=\"<%= t(\"components.menubar.demo.menus.edit.label\") %>\"\n hidden\n data-stimeo--menubar-target=\"menu\"\n >\n <% %w[cut copy paste].each do |action| %>\n <li role=\"none\">\n <button\n type=\"button\"\n class=\"demo-menuitem\"\n role=\"menuitem\"\n tabindex=\"-1\"\n data-stimeo--menubar-target=\"item\"\n data-action=\"click->stimeo--menubar#activate keydown->stimeo--menubar#onItemKeydown\">\n <%= t(\"components.menubar.demo.menus.edit.items.#{action}\") %>\n </button>\n </li>\n <% end %>\n </ul>\n</div>\n"
215
215
  },
216
216
  "stimeo--meter": {
217
217
  "file": "examples/meter/_demo.html.erb",
@@ -223,7 +223,7 @@
223
223
  },
224
224
  "stimeo--navigation-menu": {
225
225
  "file": "examples/navigation_menu/_demo.html.erb",
226
- "source": "<%# Markup for the navigation-menu demo.\n A global nav where each top item opens/closes a sub-panel (a set of links). The\n contents are links and don't use role=\"menu\" (APG Disclosure navigation). The\n library handles syncing aria-expanded, single-open behavior, closing on Escape /\n outside click / focus leaving, and moving between top items with the left/right\n keys. Panel placement and styling are the consumer's CSS (demo.css). %>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.label\") %>\">\n <ul class=\"nav-menu__list\">\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"nav-menu__trigger\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-products\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.products.label\") %>\n </button>\n <div id=\"nav-menu-products\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a href=\"#analytics\"><%= t(\n \"components.navigation_menu.demo.products.links.analytics\"\n ) %></a>\n <a href=\"#automation\"><%= t(\n \"components.navigation_menu.demo.products.links.automation\"\n ) %></a>\n <a href=\"#reports\"><%= t(\"components.navigation_menu.demo.products.links.reports\") %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"nav-menu__trigger\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-company\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.company.label\") %>\n </button>\n <div id=\"nav-menu-company\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a href=\"#about\"><%= t(\"components.navigation_menu.demo.company.links.about\") %></a>\n <a href=\"#careers\"><%= t(\"components.navigation_menu.demo.company.links.careers\") %></a>\n </div>\n </li>\n </ul>\n</nav>\n"
226
+ "source": "<%# Markup for the navigation-menu demo.\n Two arrangements are shown.\n\n 1. Click-only (the default): a global nav where each top item opens/closes a\n sub-panel (a set of links). The contents are links and don't use role=\"menu\"\n (APG Disclosure navigation). The library handles syncing aria-expanded,\n single-open behavior, closing on Escape / outside click / focus leaving, and\n moving between top items with the left/right keys.\n 2. Hover (opt-in): openOnHover plus a hoverArea wrapper — the APG \"Disclosure\n Navigation with Top-Level Links\" arrangement, where each <li> holds a real\n link, a small disclosure button, and the panel. Hovering anywhere over the\n <li> opens that item's panel; every keyboard path stays identical.\n\n Panel placement and styling are the consumer's CSS (demo.css). %>\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.click_caption\") %></p>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.label\") %>\">\n <ul class=\"nav-menu__list\">\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-products\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.products.label\") %>\n </button>\n <div id=\"nav-menu-products\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#analytics\"><%= t(\n \"components.navigation_menu.demo.products.links.analytics\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#automation\"><%= t(\n \"components.navigation_menu.demo.products.links.automation\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#reports\"><%= t(\n \"components.navigation_menu.demo.products.links.reports\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\">\n <button\n type=\"button\"\n class=\"demo-baritem\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-company\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <%= t(\"components.navigation_menu.demo.company.label\") %>\n </button>\n <div id=\"nav-menu-company\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#about\"><%= t(\n \"components.navigation_menu.demo.company.links.about\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#careers\"><%= t(\n \"components.navigation_menu.demo.company.links.careers\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n\n<p class=\"nav-menu__caption\"><%= t(\"components.navigation_menu.demo.hover_caption\") %></p>\n<%# openOnHover is opt-in; hoverArea widens the hover region to the whole <li>, so\n hovering the top-level link (not just the disclosure button) opens the panel.\n The button keeps its own accessible name so keyboard and SR users can tell the\n link and its disclosure apart. %>\n<nav class=\"nav-menu\" data-controller=\"stimeo--navigation-menu\"\n data-stimeo--navigation-menu-open-on-hover-value=\"true\"\n aria-label=\"<%= t(\"components.navigation_menu.demo.hover.label\") %>\">\n <ul class=\"nav-menu__list\">\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#solutions\">\n <%= t(\"components.navigation_menu.demo.hover.solutions.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-solutions\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-solutions\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#retail\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.retail\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#finance\"><%= t(\n \"components.navigation_menu.demo.hover.solutions.links.finance\"\n ) %></a>\n </div>\n </li>\n <li class=\"nav-menu__item\" data-stimeo--navigation-menu-target=\"hoverArea\">\n <a class=\"demo-baritem nav-menu__link\" href=\"#resources\">\n <%= t(\"components.navigation_menu.demo.hover.resources.label\") %>\n </a>\n <button\n type=\"button\"\n class=\"demo-baritem nav-menu__disclosure\"\n aria-expanded=\"false\"\n aria-controls=\"nav-menu-resources\"\n data-stimeo--navigation-menu-target=\"trigger\"\n data-action=\"click->stimeo--navigation-menu#toggle\n keydown->stimeo--navigation-menu#onTriggerKeydown\">\n <span aria-hidden=\"true\">▾</span>\n <span class=\"visually-hidden\"><%= t(\n \"components.navigation_menu.demo.hover.resources.toggle\"\n ) %></span>\n </button>\n <div id=\"nav-menu-resources\" class=\"nav-menu__panel\"\n data-stimeo--navigation-menu-target=\"panel\" hidden>\n <a class=\"demo-menuitem\" href=\"#guides\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.guides\"\n ) %></a>\n <a class=\"demo-menuitem\" href=\"#blog\"><%= t(\n \"components.navigation_menu.demo.hover.resources.links.blog\"\n ) %></a>\n </div>\n </li>\n </ul>\n</nav>\n"
227
227
  },
228
228
  "stimeo--nested-form": {
229
229
  "file": "examples/nested_form/_demo.html.erb",
@@ -251,7 +251,7 @@
251
251
  },
252
252
  "stimeo--overflow-menu": {
253
253
  "file": "examples/overflow_menu/_demo.html.erb",
254
- "source": "<%# Overflow-menu demo: drag the width slider to shrink the toolbar — the controller\n measures the items and banks the lowest-priority ones into the More menu (delegated\n to stimeo--menu), moving them back as space returns. Items carry the Menu item\n data-action up front; it stays inert in the bar (no stimeo--menu ancestor there) and\n activates once an item is banked into the More menu. The library moves items and sets\n data-overflowing / data-overflow-count; demo.css owns the look. %>\n<div class=\"overflow-demo\">\n <label class=\"overflow-demo__width\">\n <span><%= t(\"components.overflow_menu.demo.width\") %></span>\n <input type=\"range\" min=\"220\" max=\"640\" value=\"640\" data-overflow-demo-width>\n </label>\n\n <div\n class=\"overflow-demo__bar\"\n data-controller=\"stimeo--overflow-menu\"\n role=\"toolbar\"\n aria-label=\"<%= t(\"components.overflow_menu.name\") %>\">\n <div class=\"overflow-demo__items\" data-stimeo--overflow-menu-target=\"items\">\n <% %w[save edit share archive delete].each_with_index do |id, i| %>\n <button\n type=\"button\"\n class=\"overflow-demo__item\"\n <%= \"data-priority=\\\"#{i + 1}\\\"\".html_safe if i < 3 %>\n data-action=\"click->stimeo--menu#activate keydown->stimeo--menu#onItemKeydown\">\n <%= t(\"components.overflow_menu.demo.#{id}\") %>\n </button>\n <% end %>\n </div>\n\n <div\n class=\"overflow-demo__more\"\n data-controller=\"stimeo--menu\"\n data-stimeo--overflow-menu-target=\"more\"\n hidden>\n <button\n id=\"overflow-menu-trigger\"\n type=\"button\"\n class=\"overflow-demo__trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.overflow_menu.demo.more\") %>\n </button>\n <ul class=\"overflow-demo__menu\" role=\"menu\" aria-labelledby=\"overflow-menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden></ul>\n </div>\n </div>\n</div>\n"
254
+ "source": "<%# Overflow-menu demo: drag the width slider to shrink the toolbar — the controller\n measures the items and banks the lowest-priority ones into the More menu (delegated\n to stimeo--menu), moving them back as space returns. The items need no data-action of\n their own: Menu handles clicks and keys from its own element, so an item becomes\n operable the moment it lands in the menu. Only the More trigger is bound. The library\n moves items and sets data-overflowing / data-overflow-count; demo.css owns the look. %>\n<div class=\"overflow-demo\">\n <label class=\"demo-width-control\">\n <span><%= t(\"components.overflow_menu.demo.width\") %></span>\n <input type=\"range\" min=\"220\" max=\"640\" value=\"640\" data-overflow-demo-width>\n </label>\n\n <div\n class=\"overflow-demo__bar\"\n data-controller=\"stimeo--overflow-menu\"\n <%# A group, not a toolbar: these buttons stay in the normal Tab order. The\n single-tab-stop roving an APG Toolbar implies belongs to stimeo--toolbar,\n which this controller does not provide. %>\n role=\"group\"\n aria-label=\"<%= t(\"components.overflow_menu.name\") %>\">\n <div class=\"overflow-demo__items\" data-stimeo--overflow-menu-target=\"items\">\n <% %w[save edit share archive delete].each_with_index do |id, i| %>\n <button\n type=\"button\"\n class=\"demo-menuitem overflow-demo__item\"\n <%= \"data-priority=\\\"#{i + 1}\\\"\".html_safe if i < 3 %>>\n <%= t(\"components.overflow_menu.demo.#{id}\") %>\n </button>\n <% end %>\n </div>\n\n <div\n class=\"overflow-demo__more\"\n data-controller=\"stimeo--menu\"\n data-stimeo--overflow-menu-target=\"more\"\n hidden>\n <button\n id=\"overflow-menu-trigger\"\n type=\"button\"\n class=\"demo-trigger overflow-demo__trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=\"false\"\n data-stimeo--menu-target=\"trigger\"\n data-action=\"click->stimeo--menu#toggle keydown->stimeo--menu#onTriggerKeydown\">\n <%= t(\"components.overflow_menu.demo.more\") %>\n </button>\n <%# A div, not a ul: the controller appends the items themselves, and a ul may only\n hold li children. One container shape keeps the markup contract unambiguous. %>\n <div class=\"overflow-demo__menu\" role=\"menu\" aria-labelledby=\"overflow-menu-trigger\"\n data-stimeo--menu-target=\"menu\" hidden></div>\n </div>\n </div>\n</div>\n"
255
255
  },
256
256
  "stimeo--pagination": {
257
257
  "file": "examples/pagination/_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 no longer previews, so Space (select the focused star) is visible.\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 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"
307
307
  },
308
308
  "stimeo--read-more": {
309
309
  "file": "examples/read_more/_demo.html.erb",
@@ -323,7 +323,7 @@
323
323
  },
324
324
  "stimeo--resizable": {
325
325
  "file": "examples/resizable/_demo.html.erb",
326
- "source": "<%# Markup for the resizable (splitter / pane-split) demo.\n stimeo--resizable provides PointerCapture dragging, a double-click collapse\n toggle, and keyboard adjustment, and exposes the ratio (0..1) on the root element\n as the CSS variable --stimeo--resizable-fraction. %>\n<div class=\"resizable-demo-container\">\n <div\n class=\"resizable-demo\"\n id=\"resizable-splitter\"\n data-controller=\"stimeo--resizable\"\n data-stimeo--resizable-min-value=\"20\"\n data-stimeo--resizable-max-value=\"80\"\n data-stimeo--resizable-value-value=\"50\"\n data-stimeo--resizable-step-value=\"5\"\n style=\"--stimeo--resizable-fraction: 0.5;\"\n >\n \n <div\n class=\"resizable-demo__pane resizable-demo__pane--left\"\n data-stimeo--resizable-target=\"primary\"\n >\n <div class=\"resizable-demo__pane-content\">\n <h3><%= t(\"components.resizable.demo.pane_left\") %></h3>\n <p><%= t(\"components.resizable.demo.pane_left_body\") %></p>\n </div>\n </div>\n \n <div\n class=\"resizable-demo__handle\"\n role=\"separator\"\n tabindex=\"0\"\n aria-label=\"<%= t(\"components.resizable.demo.splitter_label\") %>\"\n aria-orientation=\"vertical\"\n data-stimeo--resizable-target=\"separator\"\n data-action=\"\n pointerdown->stimeo--resizable#onPointerDown\n keydown->stimeo--resizable#onKeydown\n dblclick->stimeo--resizable#toggle\n \"\n >\n <div class=\"resizable-demo__handle-bar\" aria-hidden=\"true\"></div>\n </div>\n \n <div\n class=\"resizable-demo__pane resizable-demo__pane--right\"\n data-stimeo--resizable-target=\"secondary\"\n >\n <div class=\"resizable-demo__pane-content\">\n <h3><%= t(\"components.resizable.demo.pane_right\") %></h3>\n <p><%= t(\"components.resizable.demo.pane_right_body\") %></p>\n </div>\n </div>\n </div>\n\n <div class=\"resizable-demo__controls\">\n <%# demo.js (a module) wires this up with addEventListener. Inline on*\n attributes can't reference a module-scoped function, so they aren't used. %>\n <button class=\"resizable-demo__control-btn\" type=\"button\" data-resizable-toggle-direction>\n <%= t(\"components.resizable.demo.toggle_direction\") %>\n </button>\n </div>\n</div>\n"
326
+ "source": "<%# Markup for the resizable (splitter / pane-split) demo.\n stimeo--resizable provides PointerCapture dragging, a double-click collapse\n toggle, and keyboard adjustment, and exposes the ratio (0..1) on the root element\n as the CSS variable --stimeo--resizable-fraction. %>\n<div class=\"resizable-demo-container\">\n <div\n class=\"resizable-demo\"\n id=\"resizable-splitter\"\n data-controller=\"stimeo--resizable\"\n data-stimeo--resizable-min-value=\"20\"\n data-stimeo--resizable-max-value=\"80\"\n data-stimeo--resizable-value-value=\"50\"\n data-stimeo--resizable-step-value=\"5\"\n style=\"--stimeo--resizable-fraction: 0.5;\"\n >\n \n <div\n class=\"resizable-demo__pane resizable-demo__pane--left\"\n id=\"resizable-demo-primary\"\n data-stimeo--resizable-target=\"primary\"\n >\n <div class=\"resizable-demo__pane-content\">\n <h3 id=\"resizable-demo-primary-label\">\n <%= t(\"components.resizable.demo.pane_left\") %>\n </h3>\n <p><%= t(\"components.resizable.demo.pane_left_body\") %></p>\n </div>\n </div>\n \n <div\n class=\"resizable-demo__handle\"\n role=\"separator\"\n tabindex=\"0\"\n <%# Window Splitter names the splitter after the primary pane. That pane\n has a visible heading here, so the pattern asks for aria-labelledby\n rather than a hand-written aria-label. %>\n aria-labelledby=\"resizable-demo-primary-label\"\n aria-controls=\"resizable-demo-primary\"\n aria-orientation=\"vertical\"\n data-stimeo--resizable-target=\"separator\"\n data-action=\"\n pointerdown->stimeo--resizable#onPointerDown\n keydown->stimeo--resizable#onKeydown\n dblclick->stimeo--resizable#toggle\n \"\n >\n <div class=\"resizable-demo__handle-bar\" aria-hidden=\"true\"></div>\n </div>\n \n <div\n class=\"resizable-demo__pane resizable-demo__pane--right\"\n data-stimeo--resizable-target=\"secondary\"\n >\n <div class=\"resizable-demo__pane-content\">\n <h3><%= t(\"components.resizable.demo.pane_right\") %></h3>\n <p><%= t(\"components.resizable.demo.pane_right_body\") %></p>\n </div>\n </div>\n </div>\n\n <div class=\"resizable-demo__controls\">\n <%# demo.js (a module) wires this up with addEventListener. Inline on*\n attributes can't reference a module-scoped function, so they aren't used. %>\n <button\n class=\"demo-trigger resizable-demo__control-btn\"\n type=\"button\"\n data-resizable-toggle-direction>\n <%= t(\"components.resizable.demo.toggle_direction\") %>\n </button>\n </div>\n</div>\n"
327
327
  },
328
328
  "stimeo--roving": {
329
329
  "file": "examples/roving/_demo.html.erb",
@@ -335,7 +335,7 @@
335
335
  },
336
336
  "stimeo--scroll-restore": {
337
337
  "file": "examples/scroll_restore/_demo.html.erb",
338
- "source": "<%# Markup for the scroll-restore (scroll position persistence) demo.\n The behavior shines when the DOM is torn down and rebuilt — exactly what Turbo\n does on navigation. To make that observable without leaving the page, the\n \"rebuild\" button removes the two scroll regions and re-inserts them (demo.js).\n The left region uses stimeo--scroll-restore (its offset is saved to\n sessionStorage and restored on reconnect); the right one has no controller and\n snaps back to the top, so the difference is obvious. It is behavior only — the\n library sets no ARIA/CSS and never moves focus; the box look is demo.css's. %>\n<div class=\"scroll-restore-demo\" data-scroll-restore-demo>\n <p><%= t(\"components.scroll_restore.demo.hint\") %></p>\n\n <div class=\"scroll-restore-demo__toolbar\">\n <button type=\"button\" class=\"scroll-restore-demo__rebuild\" data-scroll-restore-remount>\n <%= t(\"components.scroll_restore.demo.rebuild\") %>\n </button>\n </div>\n\n <%# demo.js captures this slot's HTML and swaps it out/in to fire the controller's\n disconnect (save) → connect (restore) cycle, mirroring a Turbo navigation. %>\n <div class=\"scroll-restore-demo__cols\" data-scroll-restore-slot>\n <div class=\"scroll-restore-demo__col\">\n <p class=\"scroll-restore-demo__label\"><%= t(\n \"components.scroll_restore.demo.with_label\"\n ) %></p>\n <div\n class=\"scroll-restore-demo__box\"\n data-controller=\"stimeo--scroll-restore\"\n data-stimeo--scroll-restore-key-value=\"catalog-demo\">\n <ol class=\"scroll-restore-demo__list\">\n <% (1..20).each do |n| %>\n <li class=\"scroll-restore-demo__row\"><%= t(\n \"components.scroll_restore.demo.row\", number: n\n ) %></li>\n <% end %>\n </ol>\n </div>\n </div>\n\n <div class=\"scroll-restore-demo__col\">\n <p class=\"scroll-restore-demo__label\"><%= t(\n \"components.scroll_restore.demo.without_label\"\n ) %></p>\n <div class=\"scroll-restore-demo__box\">\n <ol class=\"scroll-restore-demo__list\">\n <% (1..20).each do |n| %>\n <li class=\"scroll-restore-demo__row\"><%= t(\n \"components.scroll_restore.demo.row\", number: n\n ) %></li>\n <% end %>\n </ol>\n </div>\n </div>\n </div>\n</div>\n"
338
+ "source": "<%# Markup for the scroll-restore (scroll position persistence) demo.\n The behavior shines when the DOM is torn down and rebuilt — exactly what Turbo\n does on navigation. To make that observable without leaving the page, the\n \"rebuild\" button removes the two scroll regions and re-inserts them (demo.js).\n The left region uses stimeo--scroll-restore (its offset is saved to\n sessionStorage and restored on reconnect); the right one has no controller and\n snaps back to the top, so the difference is obvious. It is behavior only — the\n library sets no ARIA/CSS and never moves focus; the box look is demo.css's. %>\n<div class=\"scroll-restore-demo\" data-scroll-restore-demo>\n <p><%= t(\"components.scroll_restore.demo.hint\") %></p>\n\n <div class=\"scroll-restore-demo__toolbar\">\n <button type=\"button\" class=\"scroll-restore-demo__rebuild\" data-scroll-restore-remount>\n <%= t(\"components.scroll_restore.demo.rebuild\") %>\n </button>\n </div>\n\n <%# demo.js captures this slot's HTML and swaps it out/in to fire the controller's\n disconnect (save) → connect (restore) cycle, mirroring a Turbo navigation. %>\n <div class=\"scroll-restore-demo__cols\" data-scroll-restore-slot>\n <div class=\"scroll-restore-demo__col\">\n <p class=\"scroll-restore-demo__label\" id=\"scroll-restore-with-label\"><%= t(\n \"components.scroll_restore.demo.with_label\"\n ) %></p>\n <%# Scrolls, but every child is plain <li> text: without a tab stop the list is\n unreachable by keyboard (WCAG 2.1.1). Named by the label above rather than a new\n string, so the two boxes stay distinguishable to a screen reader. %>\n <div\n class=\"scroll-restore-demo__box\"\n tabindex=\"0\"\n role=\"region\"\n aria-labelledby=\"scroll-restore-with-label\"\n data-controller=\"stimeo--scroll-restore\"\n data-stimeo--scroll-restore-key-value=\"catalog-demo\">\n <ol class=\"scroll-restore-demo__list\">\n <% (1..20).each do |n| %>\n <li class=\"scroll-restore-demo__row\"><%= t(\n \"components.scroll_restore.demo.row\", number: n\n ) %></li>\n <% end %>\n </ol>\n </div>\n </div>\n\n <div class=\"scroll-restore-demo__col\">\n <p class=\"scroll-restore-demo__label\" id=\"scroll-restore-without-label\"><%= t(\n \"components.scroll_restore.demo.without_label\"\n ) %></p>\n <div\n class=\"scroll-restore-demo__box\"\n tabindex=\"0\"\n role=\"region\"\n aria-labelledby=\"scroll-restore-without-label\">\n <ol class=\"scroll-restore-demo__list\">\n <% (1..20).each do |n| %>\n <li class=\"scroll-restore-demo__row\"><%= t(\n \"components.scroll_restore.demo.row\", number: n\n ) %></li>\n <% end %>\n </ol>\n </div>\n </div>\n </div>\n</div>\n"
339
339
  },
340
340
  "stimeo--scroll-visibility": {
341
341
  "file": "examples/scroll_visibility/_demo.html.erb",
@@ -343,11 +343,11 @@
343
343
  },
344
344
  "stimeo--scrollspy": {
345
345
  "file": "examples/scrollspy/_demo.html.erb",
346
- "source": "<%# Markup for the scrollspy (table-of-contents scroll tracking) demo.\n stimeo--scrollspy provides intersection monitoring via IntersectionObserver and\n nearest-to-top-trigger detection, and automatically sets and manages\n aria-current=\"location\" on the active link. %>\n<div class=\"scrollspy-demo\">\n <nav\n class=\"scrollspy-demo__nav\"\n data-controller=\"stimeo--scrollspy\"\n data-stimeo--scrollspy-offset-value=\"20\"\n data-stimeo--scrollspy-root-selector-value=\".scrollspy-demo__content\"\n aria-label=\"<%= t(\"components.scrollspy.demo.title\") %>\"\n >\n <div class=\"scrollspy-demo__nav-title\"><%= t(\"components.scrollspy.demo.title\") %></div>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-intro\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.intro_title\") %>\n </a>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-usage\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.usage_title\") %>\n </a>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-api\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.api_title\") %>\n </a>\n </nav>\n\n <div class=\"scrollspy-demo__content\">\n <section id=\"toc-intro\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.intro_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.intro_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n\n <section id=\"toc-usage\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.usage_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.usage_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n\n <section id=\"toc-api\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.api_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.api_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n </div>\n</div>\n"
346
+ "source": "<%# Markup for the scrollspy (table-of-contents scroll tracking) demo.\n stimeo--scrollspy watches the sections with IntersectionObserver and marks the one whose\n top edge sits closest to the trigger line: offset px (20 here) below the top of the\n scroll root, which is the .scrollspy-demo__content container rather than the viewport.\n Intersecting sections win; when none intersects, the closest tracked section keeps the\n highlight, so the table of contents never goes blank. The library sets\n aria-current=\"location\" on the matching link and takes only that value back from the\n others, so an aria-current you set yourself survives. %>\n<div class=\"scrollspy-demo\">\n <nav\n class=\"scrollspy-demo__nav\"\n data-controller=\"stimeo--scrollspy\"\n data-stimeo--scrollspy-offset-value=\"20\"\n data-stimeo--scrollspy-root-selector-value=\".scrollspy-demo__content\"\n aria-label=\"<%= t(\"components.scrollspy.demo.title\") %>\"\n >\n <div class=\"scrollspy-demo__nav-title\"><%= t(\"components.scrollspy.demo.title\") %></div>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-intro\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.intro_title\") %>\n </a>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-usage\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.usage_title\") %>\n </a>\n <a\n class=\"scrollspy-demo__link\"\n href=\"#toc-api\"\n data-stimeo--scrollspy-target=\"link\"\n data-action=\"click->stimeo--scrollspy#scrollTo\"\n >\n <%= t(\"components.scrollspy.demo.api_title\") %>\n </a>\n </nav>\n\n <%# The content area scrolls but holds no focusable element of its own, so it needs an\n explicit tab stop or keyboard-only users cannot scroll it (WCAG 2.1.1; Safari does not\n make overflow containers implicitly focusable). role=\"region\" is safe to add here\n because the label below gives it an accessible name. %>\n <div\n class=\"scrollspy-demo__content\"\n tabindex=\"0\"\n role=\"region\"\n aria-label=\"<%= t(\"components.scrollspy.demo.viewport_label\") %>\"\n >\n <section id=\"toc-intro\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.intro_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.intro_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n\n <section id=\"toc-usage\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.usage_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.usage_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n\n <section id=\"toc-api\" class=\"scrollspy-demo__section\">\n <h2><%= t(\"components.scrollspy.demo.api_title\") %></h2>\n <p><%= t(\"components.scrollspy.demo.api_body\") %></p>\n <div class=\"scrollspy-demo__spacer\"></div>\n </section>\n </div>\n</div>\n"
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 <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.left\") %></div>\n <div\n class=\"separator separator--vertical\"\n role=\"separator\"\n tabindex=\"0\"\n aria-label=\"<%= t('components.separator.demo.handle') %>\"\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-label=\"<%= t('components.separator.demo.handle_horizontal') %>\"\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 <div class=\"separator-demo__pane\"><%= t(\"components.separator.demo.bottom\") %></div>\n </div>\n</div>\n"
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"
351
351
  },
352
352
  "stimeo--sidebar": {
353
353
  "file": "examples/sidebar/_demo.html.erb",
@@ -383,7 +383,7 @@
383
383
  },
384
384
  "stimeo--stick-to-bottom": {
385
385
  "file": "examples/stick_to_bottom/_demo.html.erb",
386
- "source": "<%# Stick-to-bottom demo: the scrollable log follows new messages while you're at the\n bottom, but holds and shows the \"new messages\" jump button if you've scrolled up. This\n catalog has no Action Cable / Turbo Stream, so the Add button appends a <li> — exactly\n the mutation a Turbo Stream broadcast would make — and the controller reacts. The jump\n button lives inside the log (a descendant, wired with a plain data-action) and CSS\n reveals it only while data-has-new. demo.js starts the log scrolled to the bottom. The\n library only follows/flags and reflects data-pinned / data-has-new; demo.css owns the look. %>\n<div class=\"stb-demo\">\n <div\n class=\"stb-demo__log\"\n data-controller=\"stimeo--stick-to-bottom\"\n data-stimeo--stick-to-bottom-behavior-value=\"smooth\">\n <ul class=\"stb-demo__messages\" data-stimeo--stick-to-bottom-target=\"content\">\n <% (1..8).each do |i| %>\n <li><%= t(\"components.stick_to_bottom.demo.message\") %> <%= i %></li>\n <% end %>\n </ul>\n <button\n type=\"button\"\n class=\"stb-demo__jump\"\n data-action=\"click->stimeo--stick-to-bottom#scrollToBottom\">\n <%= t(\"components.stick_to_bottom.demo.jump\") %>\n </button>\n </div>\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stb-demo-add\n data-message-label=\"<%= t(\"components.stick_to_bottom.demo.message\") %>\">\n <%= t(\"components.stick_to_bottom.demo.add\") %>\n </button>\n</div>\n"
386
+ "source": "<%# Stick-to-bottom demo: the scrollable log follows new messages while you're at the\n bottom, but holds and shows the \"new messages\" jump button if you've scrolled up. This\n catalog has no Action Cable / Turbo Stream, so the Add button appends a <li> — exactly\n the mutation a Turbo Stream broadcast would make — and the controller reacts. The jump\n button lives inside the log (a descendant, wired with a plain data-action) and CSS\n reveals it only while data-has-new. demo.js starts the log scrolled to the bottom. The\n library only follows/flags and reflects data-pinned / data-has-new; demo.css owns the look.\n\n The log scrolls but its only control — the jump button — is hidden until there is\n something to jump to, so most of the time a keyboard user cannot reach it at all\n (WCAG 2.1.1). Rather than hand-write a tab stop, compose stimeo--scroll-area over it:\n that controller exists for exactly this and gives the viewport tabindex=\"0\" only while\n it holds no rendered focusable content, so the tab stop appears and disappears in step\n with the jump button instead of doubling up with it. It goes on a wrapper because\n stick-to-bottom must sit on the scrolling element itself, and it takes the log as its\n viewport target. role=\"region\" comes free with the label below. %>\n<div class=\"stb-demo\">\n <div class=\"stb-demo__log-wrap\" data-controller=\"stimeo--scroll-area\">\n <div\n class=\"stb-demo__log\"\n data-controller=\"stimeo--stick-to-bottom\"\n data-stimeo--stick-to-bottom-behavior-value=\"smooth\"\n data-stimeo--scroll-area-target=\"viewport\"\n aria-label=\"<%= t(\"components.stick_to_bottom.demo.viewport_label\") %>\">\n <ul class=\"stb-demo__messages\" data-stimeo--stick-to-bottom-target=\"content\">\n <% (1..8).each do |i| %>\n <li><%= t(\"components.stick_to_bottom.demo.message\") %> <%= i %></li>\n <% end %>\n </ul>\n <button\n type=\"button\"\n class=\"stb-demo__jump\"\n data-action=\"click->stimeo--stick-to-bottom#scrollToBottom\">\n <%= t(\"components.stick_to_bottom.demo.jump\") %>\n </button>\n </div>\n </div>\n <button\n type=\"button\"\n class=\"demo-trigger\"\n data-stb-demo-add\n data-message-label=\"<%= t(\"components.stick_to_bottom.demo.message\") %>\">\n <%= t(\"components.stick_to_bottom.demo.add\") %>\n </button>\n</div>\n"
387
387
  },
388
388
  "stimeo--sticky-observer": {
389
389
  "file": "examples/sticky_observer/_demo.html.erb",
@@ -419,7 +419,7 @@
419
419
  },
420
420
  "stimeo--toast": {
421
421
  "file": "examples/toast/_demo.html.erb",
422
- "source": "<%# Markup for the toast demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--toast provides max-count management, WAI-ARIA live region support, and\n pausing the auto-dismiss timer on hover or keyboard focus.\n\n Showing a toast is done with attributes alone (no hand-written JS):\n put data-action=\"click->stimeo--toast#show\" plus\n data-stimeo--toast-body-param / data-stimeo--toast-type-param on the trigger button.\n For advanced cases where you want to trigger it remotely from JS, the controller\n element keeps show->stimeo--toast#show, so CustomEvent('show', { detail }) works too. %>\n<div\n class=\"toast-demo\"\n data-controller=\"stimeo--toast\"\n data-stimeo--toast-duration-value=\"5000\"\n data-stimeo--toast-max-value=\"3\"\n data-action=\"show->stimeo--toast#show\">\n\n <div class=\"toast-demo__triggers\">\n <button\n type=\"button\"\n class=\"toast-demo__btn toast-demo__btn--status\"\n data-action=\"click->stimeo--toast#show\"\n data-stimeo--toast-body-param=\"<%= t(\"components.toast.demo.status_body\") %>\"\n data-stimeo--toast-type-param=\"status\">\n <%= t(\"components.toast.demo.show_status\") %>\n </button>\n\n <button\n type=\"button\"\n class=\"toast-demo__btn toast-demo__btn--alert\"\n data-action=\"click->stimeo--toast#show\"\n data-stimeo--toast-body-param=\"<%= t(\"components.toast.demo.alert_body\") %>\"\n data-stimeo--toast-type-param=\"alert\">\n <%= t(\"components.toast.demo.show_alert\") %>\n </button>\n </div>\n\n <%# The live region is a descendant of the controller. The controller element only\n needs to contain the trigger and the list/template; role=\"region\" goes on this\n inner container. %>\n <div\n id=\"playground-toast\"\n role=\"region\"\n aria-label=\"<%= t(\"components.toast.demo.region_label\") %>\"\n class=\"toast-container\">\n \n <%# The element where notifications are placed. %>\n <ol class=\"toast-list\" data-stimeo--toast-target=\"list\"></ol>\n\n <%# Template used to create new toasts. %>\n <template data-stimeo--toast-target=\"template\">\n <li\n class=\"toast-item\"\n data-stimeo--toast-target=\"item\"\n tabindex=\"0\">\n <div class=\"toast-item__content\">\n <span role=\"status\" data-toast-slot=\"body\" class=\"toast-item__body\"></span>\n <button\n type=\"button\"\n class=\"toast-item__dismiss\"\n data-toast-dismiss\n aria-label=\"<%= t(\"components.toast.demo.dismiss_label\") %>\">\n <svg\n class=\"toast-item__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n </li>\n </template>\n </div>\n</div>\n"
422
+ "source": "<%# Markup for the toast demo.\n The single source used for both the live render and the copy-paste code.\n stimeo--toast provides max-count management, WAI-ARIA live region support, and\n pausing the auto-dismiss timer on hover or keyboard focus.\n\n Showing a toast is done with attributes alone (no hand-written JS):\n put data-action=\"click->stimeo--toast#show\" plus\n data-stimeo--toast-body-param / data-stimeo--toast-type-param on the trigger button.\n For advanced cases where you want to trigger it remotely from JS, the controller\n element keeps show->stimeo--toast#show, so CustomEvent('show', { detail }) works too. %>\n<div\n class=\"toast-demo\"\n data-controller=\"stimeo--toast\"\n data-stimeo--toast-duration-value=\"5000\"\n data-stimeo--toast-max-value=\"3\"\n data-action=\"show->stimeo--toast#show\">\n\n <div class=\"toast-demo__triggers\">\n <button\n type=\"button\"\n class=\"demo-trigger toast-demo__btn--status\"\n data-action=\"click->stimeo--toast#show\"\n data-stimeo--toast-body-param=\"<%= t(\"components.toast.demo.status_body\") %>\"\n data-stimeo--toast-type-param=\"status\">\n <%= t(\"components.toast.demo.show_status\") %>\n </button>\n\n <button\n type=\"button\"\n class=\"demo-trigger toast-demo__btn--alert\"\n data-action=\"click->stimeo--toast#show\"\n data-stimeo--toast-body-param=\"<%= t(\"components.toast.demo.alert_body\") %>\"\n data-stimeo--toast-type-param=\"alert\">\n <%= t(\"components.toast.demo.show_alert\") %>\n </button>\n </div>\n\n <%# The live region is a descendant of the controller. The controller element only\n needs to contain the trigger and the list/template; role=\"region\" goes on this\n inner container. %>\n <div\n id=\"playground-toast\"\n role=\"region\"\n aria-label=\"<%= t(\"components.toast.demo.region_label\") %>\"\n class=\"toast-container\">\n \n <%# The element where notifications are placed. %>\n <ol class=\"toast-list\" data-stimeo--toast-target=\"list\"></ol>\n\n <%# Template used to create new toasts. %>\n <template data-stimeo--toast-target=\"template\">\n <li\n class=\"toast-item\"\n data-stimeo--toast-target=\"item\"\n tabindex=\"0\">\n <div class=\"toast-item__content\">\n <span role=\"status\" data-toast-slot=\"body\" class=\"toast-item__body\"></span>\n <button\n type=\"button\"\n class=\"toast-item__dismiss\"\n data-toast-dismiss\n aria-label=\"<%= t(\"components.toast.demo.dismiss_label\") %>\">\n <svg\n class=\"toast-item__icon\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\">\n <path d=\"M18 6L6 18M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n </li>\n </template>\n </div>\n</div>\n"
423
423
  },
424
424
  "stimeo--toggle-group": {
425
425
  "file": "examples/toggle_group/_demo.html.erb",
@@ -427,7 +427,7 @@
427
427
  },
428
428
  "stimeo--toolbar": {
429
429
  "file": "examples/toolbar/_demo.html.erb",
430
- "source": "<%# Markup for the toolbar demo.\n Two toolbars share the same roving-tabindex behavior; only orientation-value differs,\n so the arrow axis follows it: Left/Right for horizontal, Up/Down for vertical (Home/End\n jump to the ends in both). Each caption is the toolbar's accessible name via\n aria-labelledby. Pressing/toggling each button is each element's responsibility. %>\n<div class=\"toolbar-demo\">\n <%# Horizontal: arrows Left/Right. %>\n <div class=\"toolbar-demo__group\">\n <span class=\"toolbar-demo__caption\" id=\"toolbar-demo-horizontal\">\n <%= t(\"components.toolbar.demo.horizontal\") %>\n </span>\n <div\n class=\"toolbar\"\n data-controller=\"stimeo--toolbar\"\n role=\"toolbar\"\n aria-labelledby=\"toolbar-demo-horizontal\"\n data-stimeo--toolbar-orientation-value=\"horizontal\">\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"0\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.bold\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.italic\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.underline\") %>\n </button>\n <span class=\"toolbar__separator\" role=\"separator\" aria-orientation=\"vertical\"></span>\n <button type=\"button\" class=\"toolbar__button\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.link\") %>\n </button>\n </div>\n </div>\n\n <%# Vertical: same controls, arrows Up/Down. The separator is horizontal here. %>\n <div class=\"toolbar-demo__group\">\n <span class=\"toolbar-demo__caption\" id=\"toolbar-demo-vertical\">\n <%= t(\"components.toolbar.demo.vertical\") %>\n </span>\n <div\n class=\"toolbar toolbar--vertical\"\n data-controller=\"stimeo--toolbar\"\n role=\"toolbar\"\n aria-labelledby=\"toolbar-demo-vertical\"\n data-stimeo--toolbar-orientation-value=\"vertical\">\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"0\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.bold\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.italic\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.underline\") %>\n </button>\n <span class=\"toolbar__separator\" role=\"separator\" aria-orientation=\"horizontal\"></span>\n <button type=\"button\" class=\"toolbar__button\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\" data-action=\"keydown->stimeo--toolbar#onKeydown\">\n <%= t(\"components.toolbar.demo.link\") %>\n </button>\n </div>\n </div>\n</div>\n"
430
+ "source": "<%# Markup for the toolbar demo.\n Two toolbars share the same roving-tabindex behavior; only orientation-value differs,\n so the arrow axis follows it: Left/Right for horizontal, Up/Down for vertical (Home/End\n jump to the ends in both). A vertical toolbar also needs aria-orientation=\"vertical\" —\n like role and the accessible name, that ARIA is the author's, not the library's.\n Each caption is the toolbar's accessible name via aria-labelledby. Keydown is delegated\n on the toolbar container, so no control needs a data-action. Pressing/toggling each\n button is each element's own responsibility. %>\n<div class=\"toolbar-demo\">\n <div class=\"toolbar-demo__groups\">\n <%# Horizontal: arrows Left/Right. %>\n <div class=\"toolbar-demo__group\">\n <span class=\"toolbar-demo__caption\" id=\"toolbar-demo-horizontal\">\n <%= t(\"components.toolbar.demo.horizontal\") %>\n </span>\n <div\n class=\"toolbar\"\n data-controller=\"stimeo--toolbar\"\n role=\"toolbar\"\n aria-labelledby=\"toolbar-demo-horizontal\"\n data-stimeo--toolbar-orientation-value=\"horizontal\">\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"0\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.bold\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.italic\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.underline\") %>\n </button>\n <span class=\"toolbar__separator\" role=\"separator\" aria-orientation=\"vertical\"></span>\n <button type=\"button\" class=\"toolbar__button\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.link\") %>\n </button>\n </div>\n </div>\n\n <%# Vertical: same controls, arrows Up/Down. The separator is horizontal here. %>\n <div class=\"toolbar-demo__group\">\n <span class=\"toolbar-demo__caption\" id=\"toolbar-demo-vertical\">\n <%= t(\"components.toolbar.demo.vertical\") %>\n </span>\n <div\n class=\"toolbar toolbar--vertical\"\n data-controller=\"stimeo--toolbar\"\n role=\"toolbar\"\n aria-labelledby=\"toolbar-demo-vertical\"\n aria-orientation=\"vertical\"\n data-stimeo--toolbar-orientation-value=\"vertical\">\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"0\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.bold\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.italic\") %>\n </button>\n <button type=\"button\" class=\"toolbar__button\" aria-pressed=\"false\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.underline\") %>\n </button>\n <span class=\"toolbar__separator\" role=\"separator\" aria-orientation=\"horizontal\"></span>\n <button type=\"button\" class=\"toolbar__button\" tabindex=\"-1\"\n data-stimeo--toolbar-target=\"control\">\n <%= t(\"components.toolbar.demo.link\") %>\n </button>\n </div>\n </div>\n </div>\n\n <%# \"Link\" deliberately has no pressed state: it is an action, not a toggle. Saying so\n keeps its lack of visible feedback from reading as a broken control. %>\n <p class=\"toolbar-demo__hint\"><%= t(\"components.toolbar.demo.hint\") %></p>\n</div>\n"
431
431
  },
432
432
  "stimeo--tooltip": {
433
433
  "file": "examples/tooltip/_demo.html.erb",
@@ -439,7 +439,7 @@
439
439
  },
440
440
  "stimeo--tree-view": {
441
441
  "file": "examples/tree_view/_demo.html.erb",
442
- "source": "<%# Markup for the tree-view (tree view / single selection) demo.\n Nested role=\"tree\" / \"treeitem\" / \"group\" express the hierarchy. Arrows move between\n visible items; ArrowRight / ArrowLeft expand/collapse and move between parent/child;\n Home/End, typeahead, and Enter/Space select a single item. The library handles roving,\n aria-expanded / aria-selected sync, and toggling each group's hidden. Across the whole\n tree only one treeitem is tabindex=0. %>\n<ul class=\"tree-view\" data-controller=\"stimeo--tree-view\" role=\"tree\"\n aria-label=\"<%= t('components.tree_view.demo.label') %>\">\n <li\n class=\"tree-view__item\"\n role=\"treeitem\"\n aria-expanded=\"true\"\n aria-selected=\"false\"\n tabindex=\"0\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">app</span>\n <ul class=\"tree-view__group\" role=\"group\" data-stimeo--tree-view-target=\"group\">\n <li\n class=\"tree-view__item\"\n role=\"treeitem\"\n aria-expanded=\"false\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">controllers</span>\n <ul class=\"tree-view__group\" role=\"group\" data-stimeo--tree-view-target=\"group\" hidden>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">application_controller.rb</span>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">components_controller.rb</span>\n </li>\n </ul>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">application.rb</span>\n </li>\n </ul>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">README.md</span>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">Gemfile</span>\n </li>\n</ul>\n"
442
+ "source": "<%# Markup for the tree-view (tree view / single selection) demo.\n Nested role=\"tree\" / \"treeitem\" / \"group\" express the hierarchy. Arrows move between\n visible items; ArrowRight / ArrowLeft expand/collapse and move between parent/child;\n Home/End, typeahead, and Enter/Space select a single item. The library handles roving,\n aria-expanded / aria-selected sync, and toggling each group's hidden. Across the whole\n tree only one treeitem is tabindex=0.\n The chevron is an optional pointer affordance wired to the toggle action. It stays\n tabindex=\"-1\" (the tree remains a single Tab stop) and aria-hidden (the item itself\n already announces its expanded state); clicking it expands/collapses without selecting,\n because the tree never consumes events raised inside a nested control. The toggle action\n hands focus to the row afterwards, so focus never rests on this decorative button and\n the arrow keys keep working right after a click. %>\n<ul class=\"tree-view\" data-controller=\"stimeo--tree-view\" role=\"tree\"\n aria-label=\"<%= t('components.tree_view.demo.label') %>\">\n <li\n class=\"tree-view__item\"\n role=\"treeitem\"\n aria-expanded=\"true\"\n aria-selected=\"false\"\n tabindex=\"0\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <button type=\"button\" class=\"tree-view__chevron\" tabindex=\"-1\" aria-hidden=\"true\"\n data-action=\"click->stimeo--tree-view#toggle\"></button>\n <span class=\"tree-view__label\">app</span>\n <ul class=\"tree-view__group\" role=\"group\" data-stimeo--tree-view-target=\"group\">\n <li\n class=\"tree-view__item\"\n role=\"treeitem\"\n aria-expanded=\"false\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <button type=\"button\" class=\"tree-view__chevron\" tabindex=\"-1\" aria-hidden=\"true\"\n data-action=\"click->stimeo--tree-view#toggle\"></button>\n <span class=\"tree-view__label\">controllers</span>\n <ul class=\"tree-view__group\" role=\"group\" data-stimeo--tree-view-target=\"group\" hidden>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">application_controller.rb</span>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">components_controller.rb</span>\n </li>\n </ul>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">application.rb</span>\n </li>\n </ul>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">README.md</span>\n </li>\n <li class=\"tree-view__item\" role=\"treeitem\" aria-selected=\"false\" tabindex=\"-1\"\n data-stimeo--tree-view-target=\"item\"\n data-action=\"keydown->stimeo--tree-view#onKeydown click->stimeo--tree-view#onClick\">\n <span class=\"tree-view__label\">Gemfile</span>\n </li>\n</ul>\n"
443
443
  },
444
444
  "stimeo--typing-indicator": {
445
445
  "file": "examples/typing_indicator/_demo.html.erb",