stimeo-ui 0.13.0 → 0.14.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 (103) hide show
  1. package/CHANGELOG.md +39 -5
  2. package/dist/cable/index.d.ts +1 -1
  3. package/dist/cable/index.js.map +1 -1
  4. package/dist/controllers/accordion_controller.d.ts +1 -1
  5. package/dist/controllers/accordion_controller.js.map +1 -1
  6. package/dist/controllers/calendar_controller.d.ts +2 -2
  7. package/dist/controllers/calendar_controller.js +31 -7
  8. package/dist/controllers/calendar_controller.js.map +1 -1
  9. package/dist/controllers/carousel_controller.js +8 -6
  10. package/dist/controllers/carousel_controller.js.map +1 -1
  11. package/dist/controllers/clipboard_controller.d.ts +1 -1
  12. package/dist/controllers/clipboard_controller.js.map +1 -1
  13. package/dist/controllers/collapsible_controller.d.ts +1 -1
  14. package/dist/controllers/collapsible_controller.js.map +1 -1
  15. package/dist/controllers/conditional_fields_controller.js +7 -1
  16. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  17. package/dist/controllers/currency_input_controller.js +68 -23
  18. package/dist/controllers/currency_input_controller.js.map +1 -1
  19. package/dist/controllers/date_range_picker_controller.d.ts +3 -3
  20. package/dist/controllers/date_range_picker_controller.js +31 -7
  21. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  22. package/dist/controllers/dialog_controller.d.ts +2 -2
  23. package/dist/controllers/dialog_controller.js.map +1 -1
  24. package/dist/controllers/direct_upload_controller.js +21 -10
  25. package/dist/controllers/direct_upload_controller.js.map +1 -1
  26. package/dist/controllers/dismissible_controller.d.ts +1 -1
  27. package/dist/controllers/dismissible_controller.js.map +1 -1
  28. package/dist/controllers/dropdown_controller.d.ts +1 -1
  29. package/dist/controllers/dropdown_controller.js.map +1 -1
  30. package/dist/controllers/form_field_controller.js +7 -1
  31. package/dist/controllers/form_field_controller.js.map +1 -1
  32. package/dist/controllers/input_mask_controller.d.ts +3 -2
  33. package/dist/controllers/input_mask_controller.js +33 -12
  34. package/dist/controllers/input_mask_controller.js.map +1 -1
  35. package/dist/controllers/intersection_controller.js +10 -5
  36. package/dist/controllers/intersection_controller.js.map +1 -1
  37. package/dist/controllers/lazy_frame_controller.js +10 -5
  38. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  39. package/dist/controllers/local_time_controller.d.ts +3 -2
  40. package/dist/controllers/local_time_controller.js +34 -7
  41. package/dist/controllers/local_time_controller.js.map +1 -1
  42. package/dist/controllers/masonry_controller.d.ts +1 -1
  43. package/dist/controllers/masonry_controller.js +1 -1
  44. package/dist/controllers/masonry_controller.js.map +1 -1
  45. package/dist/controllers/menu_controller.js +10 -2
  46. package/dist/controllers/menu_controller.js.map +1 -1
  47. package/dist/controllers/menubar_controller.js +10 -2
  48. package/dist/controllers/menubar_controller.js.map +1 -1
  49. package/dist/controllers/navigation_menu_controller.js +8 -2
  50. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  51. package/dist/controllers/otp_controller.js +23 -10
  52. package/dist/controllers/otp_controller.js.map +1 -1
  53. package/dist/controllers/overflow_menu_controller.js +10 -1
  54. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  55. package/dist/controllers/password_reveal_controller.d.ts +1 -1
  56. package/dist/controllers/password_reveal_controller.js.map +1 -1
  57. package/dist/controllers/pointer_drag_controller.js +10 -3
  58. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  59. package/dist/controllers/portal_controller.js +21 -10
  60. package/dist/controllers/portal_controller.js.map +1 -1
  61. package/dist/controllers/radio_group_controller.js +11 -3
  62. package/dist/controllers/radio_group_controller.js.map +1 -1
  63. package/dist/controllers/read_more_controller.d.ts +1 -1
  64. package/dist/controllers/read_more_controller.js.map +1 -1
  65. package/dist/controllers/relative_time_controller.js +31 -5
  66. package/dist/controllers/relative_time_controller.js.map +1 -1
  67. package/dist/controllers/reset_before_cache_controller.js +23 -10
  68. package/dist/controllers/reset_before_cache_controller.js.map +1 -1
  69. package/dist/controllers/resizable_controller.js +7 -2
  70. package/dist/controllers/resizable_controller.js.map +1 -1
  71. package/dist/controllers/roving_controller.js +7 -3
  72. package/dist/controllers/roving_controller.js.map +1 -1
  73. package/dist/controllers/scroll_visibility_controller.d.ts +1 -1
  74. package/dist/controllers/scroll_visibility_controller.js +22 -13
  75. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  76. package/dist/controllers/scrollspy_controller.d.ts +1 -1
  77. package/dist/controllers/scrollspy_controller.js +11 -6
  78. package/dist/controllers/scrollspy_controller.js.map +1 -1
  79. package/dist/controllers/smart_sticky_header_controller.js +23 -12
  80. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  81. package/dist/controllers/sticky_observer_controller.js +10 -5
  82. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  83. package/dist/controllers/switch_controller.d.ts +1 -1
  84. package/dist/controllers/switch_controller.js.map +1 -1
  85. package/dist/controllers/tabs_controller.d.ts +1 -1
  86. package/dist/controllers/tabs_controller.js.map +1 -1
  87. package/dist/controllers/theme_controller.d.ts +2 -3
  88. package/dist/controllers/theme_controller.js +23 -13
  89. package/dist/controllers/theme_controller.js.map +1 -1
  90. package/dist/controllers/toggle_group_controller.js +11 -3
  91. package/dist/controllers/toggle_group_controller.js.map +1 -1
  92. package/dist/controllers/toolbar_controller.js +7 -3
  93. package/dist/controllers/toolbar_controller.js.map +1 -1
  94. package/dist/index.js +165 -171
  95. package/dist/index.js.map +1 -1
  96. package/dist/inspector/cli.d.ts +38 -1
  97. package/dist/inspector/cli.js +80 -7
  98. package/dist/inspector/cli.js.map +1 -1
  99. package/dist/inspector/cli_bin.js +103 -7
  100. package/dist/inspector/cli_bin.js.map +1 -1
  101. package/dist/inspector/examples.json +5 -5
  102. package/dist/inspector/manifest.json +127 -1
  103. package/package.json +9 -11
@@ -115,7 +115,7 @@
115
115
  },
116
116
  "stimeo--dirty-form": {
117
117
  "file": "examples/dirty_form/_demo.html.erb",
118
- "source": "<%# Dirty-form demo: edit either field and its form gets data-dirty (shown as a badge).\n When both forms are dirty, one Turbo visit dispatches both guard events and opens\n one native confirm using the first eligible form in live DOM order. \"Save\" calls\n markClean for that form; this catalog has no server, so it stands in for a successful\n save. The library owns data-dirty and navigation coordination; the demo only styles\n the visible state. %>\n<div class=\"dirty-form-demo\">\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.first_message') %>\"\n aria-labelledby=\"dirty-form-first-title\"\n >\n <h3 id=\"dirty-form-first-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.first_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.first_label\") %></span>\n <input type=\"text\" name=\"title\" value=\"<%= t('components.dirty_form.demo.first_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.second_message') %>\"\n aria-labelledby=\"dirty-form-second-title\"\n >\n <h3 id=\"dirty-form-second-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.second_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.second_label\") %></span>\n <input type=\"text\" name=\"summary\" value=\"<%= t('components.dirty_form.demo.second_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n</div>\n"
118
+ "source": "<%# Dirty-form demo: edit either field and its form gets data-dirty (shown as a badge).\n When both forms are dirty, one Turbo visit dispatches both guard events and opens\n one native confirm using the first eligible form in live DOM order. \"Save\" calls\n markClean for that form; this catalog has no server, so it stands in for a successful\n save. The library owns data-dirty and navigation coordination; the demo only styles\n the visible state. %>\n<div class=\"dirty-form-demo\">\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.first_message') %>\"\n aria-labelledby=\"dirty-form-first-title\"\n >\n <h3 id=\"dirty-form-first-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.first_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.first_label\") %></span>\n <input type=\"text\" name=\"title\" value=\"<%= t('components.dirty_form.demo.first_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"click->stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n\n <form\n class=\"dirty-form\"\n data-controller=\"stimeo--dirty-form\"\n data-stimeo--dirty-form-message-value=\"<%= t('components.dirty_form.demo.second_message') %>\"\n aria-labelledby=\"dirty-form-second-title\"\n >\n <h3 id=\"dirty-form-second-title\" class=\"dirty-form__title\">\n <%= t(\"components.dirty_form.demo.second_title\") %>\n </h3>\n <label class=\"dirty-form__field\">\n <span><%= t(\"components.dirty_form.demo.second_label\") %></span>\n <input type=\"text\" name=\"summary\" value=\"<%= t('components.dirty_form.demo.second_value') %>\">\n </label>\n <div class=\"dirty-form__bar\">\n <span class=\"dirty-form__badge\"><%= t(\"components.dirty_form.demo.unsaved\") %></span>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"click->stimeo--dirty-form#markClean\">\n <%= t(\"components.dirty_form.demo.save\") %>\n </button>\n </div>\n </form>\n</div>\n"
119
119
  },
120
120
  "stimeo--dismissible": {
121
121
  "file": "examples/dismissible/_demo.html.erb",
@@ -143,7 +143,7 @@
143
143
  },
144
144
  "stimeo--filter": {
145
145
  "file": "examples/filter/_demo.html.erb",
146
- "source": "<%# Markup for the filter (faceted list) demo. Behavior only — no consumer JS.\n The toggle-group owns the chips' pressed state + roving focus; stimeo--filter\n reads the pressed tokens (data-value) and shows/hides items by their\n data-stimeo--filter-tokens. match=\"all\" means an item must carry every active\n token. The empty element appears when nothing matches; Clear resets the chips. %>\n<div class=\"filter-demo\" data-controller=\"stimeo--filter\"\n data-stimeo--filter-match-value=\"all\"\n data-action=\"stimeo--toggle-group:change->stimeo--filter#apply\">\n <div class=\"filter-demo__bar\">\n <div class=\"filter-demo__chips\" role=\"group\"\n aria-label=\"<%= t('components.filter.demo.legend') %>\"\n data-controller=\"stimeo--toggle-group\">\n <button type=\"button\" class=\"filter-demo__chip\" aria-pressed=\"false\" tabindex=\"0\"\n data-value=\"veg\"\n data-stimeo--filter-target=\"control\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <%= t(\"components.filter.demo.vegetarian\") %>\n </button>\n <button type=\"button\" class=\"filter-demo__chip\" aria-pressed=\"false\" tabindex=\"-1\"\n data-value=\"spicy\"\n data-stimeo--filter-target=\"control\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <%= t(\"components.filter.demo.spicy\") %>\n </button>\n </div>\n <button type=\"button\" class=\"filter-demo__clear\" data-action=\"stimeo--filter#clear\">\n <%= t(\"components.filter.demo.clear\") %>\n </button>\n </div>\n\n <%# Each item shows its facet tokens as tags so it is clear why a chip filters it in/out. %>\n <%\n token_labels = {\n \"veg\" => t(\"components.filter.demo.vegetarian\"),\n \"spicy\" => t(\"components.filter.demo.spicy\"),\n }\n dishes = [\n { key: \"margherita\", tokens: \"veg\" },\n { key: \"pepperoni\", tokens: \"\" },\n { key: \"curry\", tokens: \"veg spicy\" },\n { key: \"wings\", tokens: \"spicy\" },\n ]\n %>\n <ul class=\"filter-demo__list\">\n <% dishes.each do |dish| %>\n <li class=\"filter-demo__item\" data-stimeo--filter-target=\"item\"\n data-stimeo--filter-tokens=\"<%= dish[:tokens] %>\">\n <span class=\"filter-demo__name\"><%= t(\"components.filter.demo.#{dish[:key]}\") %></span>\n <span class=\"filter-demo__tags\">\n <% dish[:tokens].split.each do |tok| %>\n <span class=\"filter-demo__tag\"><%= token_labels[tok] %></span>\n <% end %>\n </span>\n </li>\n <% end %>\n </ul>\n\n <p class=\"filter-demo__empty\" role=\"status\" data-stimeo--filter-target=\"empty\" hidden>\n <%= t(\"components.filter.demo.empty\") %>\n </p>\n</div>\n"
146
+ "source": "<%# Markup for the filter (faceted list) demo. Behavior only — no consumer JS.\n The toggle-group owns the chips' pressed state + roving focus; stimeo--filter\n reads the pressed tokens (data-value) and shows/hides items by their\n data-stimeo--filter-tokens. match=\"all\" means an item must carry every active\n token. The empty element appears when nothing matches; Clear resets the chips. %>\n<div class=\"filter-demo\" data-controller=\"stimeo--filter\"\n data-stimeo--filter-match-value=\"all\"\n data-action=\"stimeo--toggle-group:change->stimeo--filter#apply\">\n <div class=\"filter-demo__bar\">\n <div class=\"filter-demo__chips\" role=\"group\"\n aria-label=\"<%= t('components.filter.demo.legend') %>\"\n data-controller=\"stimeo--toggle-group\">\n <button type=\"button\" class=\"filter-demo__chip\" aria-pressed=\"false\" tabindex=\"0\"\n data-value=\"veg\"\n data-stimeo--filter-target=\"control\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <%= t(\"components.filter.demo.vegetarian\") %>\n </button>\n <button type=\"button\" class=\"filter-demo__chip\" aria-pressed=\"false\" tabindex=\"-1\"\n data-value=\"spicy\"\n data-stimeo--filter-target=\"control\"\n data-stimeo--toggle-group-target=\"item\"\n data-action=\"click->stimeo--toggle-group#toggle\n keydown->stimeo--toggle-group#onKeydown\">\n <%= t(\"components.filter.demo.spicy\") %>\n </button>\n </div>\n <button type=\"button\" class=\"filter-demo__clear\" data-action=\"click->stimeo--filter#clear\">\n <%= t(\"components.filter.demo.clear\") %>\n </button>\n </div>\n\n <%# Each item shows its facet tokens as tags so it is clear why a chip filters it in/out. %>\n <%\n token_labels = {\n \"veg\" => t(\"components.filter.demo.vegetarian\"),\n \"spicy\" => t(\"components.filter.demo.spicy\"),\n }\n dishes = [\n { key: \"margherita\", tokens: \"veg\" },\n { key: \"pepperoni\", tokens: \"\" },\n { key: \"curry\", tokens: \"veg spicy\" },\n { key: \"wings\", tokens: \"spicy\" },\n ]\n %>\n <ul class=\"filter-demo__list\">\n <% dishes.each do |dish| %>\n <li class=\"filter-demo__item\" data-stimeo--filter-target=\"item\"\n data-stimeo--filter-tokens=\"<%= dish[:tokens] %>\">\n <span class=\"filter-demo__name\"><%= t(\"components.filter.demo.#{dish[:key]}\") %></span>\n <span class=\"filter-demo__tags\">\n <% dish[:tokens].split.each do |tok| %>\n <span class=\"filter-demo__tag\"><%= token_labels[tok] %></span>\n <% end %>\n </span>\n </li>\n <% end %>\n </ul>\n\n <p class=\"filter-demo__empty\" role=\"status\" data-stimeo--filter-target=\"empty\" hidden>\n <%= t(\"components.filter.demo.empty\") %>\n </p>\n</div>\n"
147
147
  },
148
148
  "stimeo--flash": {
149
149
  "file": "examples/flash/_demo.html.erb",
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "stimeo--live-counter": {
197
197
  "file": "examples/live_counter/_demo.html.erb",
198
- "source": "<%# live-counter: the count's single source of truth is the DOM text\n (server-rendered below from the channel's demo counter) plus the\n LikesChannel stream. Clicking Like bumps the number optimistically and\n performs increment; the server adds it to its counter and broadcasts the\n absolute { count }, so every subscribed tab converges — open a second tab\n and click in either. The trigger target keeps the button disabled while an\n increment would be dropped (before the subscription confirms, during an\n outage, after a rejection). The client id is randomized per render, which\n identifies this tab rather than this user — a real app passes any per-client\n string (SecureRandom.uuid). Sharing one id across tabs still converges, since\n an echo only cancels one guess of its own size, but a per-tab id keeps every\n echo paired with the tab that guessed it. It is printed below so two tabs\n side by side can be told apart; an app has no reason to show it. The room comes from the\n channel's allowlisted constant so the subscription params and the\n server-rendered initial value below can never drift (a real app passes its\n record id, e.g. post.id). %>\n<% room = LikesChannel::ROOM %>\n<% tab = SecureRandom.hex(4) %>\n<div class=\"live-counter-demo\"\n data-controller=\"stimeo--live-counter\"\n data-stimeo--live-counter-channel-value=\"LikesChannel\"\n data-stimeo--live-counter-params-value='{\"room\":\"<%= room %>\"}'\n data-stimeo--live-counter-id-value=\"<%= tab %>\">\n <p class=\"live-counter-demo__hint\"><%= t(\"components.live_counter.demo.hint\") %></p>\n <p class=\"live-counter-demo__you\"><%=\n t(\"components.live_counter.demo.this_tab\", token: tab) %></p>\n <div class=\"live-counter-demo__row\">\n <button type=\"button\" class=\"demo-trigger live-counter-demo__button\"\n data-stimeo--live-counter-target=\"trigger\"\n data-action=\"stimeo--live-counter#increment\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M19 14c1.5-1.5 3-3.2 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.8 0-3 .5-4.5 2\n -1.5-1.5-2.7-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4 3 5.5l7 7Z\"/>\n </svg>\n <%= t(\"components.live_counter.demo.like\") %>\n </button>\n <p class=\"live-counter-demo__count\">\n <%# Server-rendered initial value: reading the channel's in-memory demo\n counter keeps the DOM truth aligned with the server (a real app\n renders the persisted count, e.g. post.likes_count). %>\n <span class=\"live-counter-demo__value\" data-stimeo--live-counter-target=\"value\"><%=\n LikesChannel.count(room) %></span>\n <span class=\"live-counter-demo__unit\"><%= t(\"components.live_counter.demo.unit\") %></span>\n </p>\n </div>\n</div>\n"
198
+ "source": "<%# live-counter: the count's single source of truth is the DOM text\n (server-rendered below from the channel's demo counter) plus the\n LikesChannel stream. Clicking Like bumps the number optimistically and\n performs increment; the server adds it to its counter and broadcasts the\n absolute { count }, so every subscribed tab converges — open a second tab\n and click in either. The trigger target keeps the button disabled while an\n increment would be dropped (before the subscription confirms, during an\n outage, after a rejection). The client id is randomized per render, which\n identifies this tab rather than this user — a real app passes any per-client\n string (SecureRandom.uuid). Sharing one id across tabs still converges, since\n an echo only cancels one guess of its own size, but a per-tab id keeps every\n echo paired with the tab that guessed it. It is printed below so two tabs\n side by side can be told apart; an app has no reason to show it. The room comes from the\n channel's allowlisted constant so the subscription params and the\n server-rendered initial value below can never drift (a real app passes its\n record id, e.g. post.id). %>\n<% room = LikesChannel::ROOM %>\n<% tab = SecureRandom.hex(4) %>\n<div class=\"live-counter-demo\"\n data-controller=\"stimeo--live-counter\"\n data-stimeo--live-counter-channel-value=\"LikesChannel\"\n data-stimeo--live-counter-params-value='{\"room\":\"<%= room %>\"}'\n data-stimeo--live-counter-id-value=\"<%= tab %>\">\n <p class=\"live-counter-demo__hint\"><%= t(\"components.live_counter.demo.hint\") %></p>\n <p class=\"live-counter-demo__you\"><%=\n t(\"components.live_counter.demo.this_tab\", token: tab) %></p>\n <div class=\"live-counter-demo__row\">\n <button type=\"button\" class=\"demo-trigger live-counter-demo__button\"\n data-stimeo--live-counter-target=\"trigger\"\n data-action=\"click->stimeo--live-counter#increment\">\n <svg class=\"demo-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M19 14c1.5-1.5 3-3.2 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.8 0-3 .5-4.5 2\n -1.5-1.5-2.7-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4 3 5.5l7 7Z\"/>\n </svg>\n <%= t(\"components.live_counter.demo.like\") %>\n </button>\n <p class=\"live-counter-demo__count\">\n <%# Server-rendered initial value: reading the channel's in-memory demo\n counter keeps the DOM truth aligned with the server (a real app\n renders the persisted count, e.g. post.likes_count). %>\n <span class=\"live-counter-demo__value\" data-stimeo--live-counter-target=\"value\"><%=\n LikesChannel.count(room) %></span>\n <span class=\"live-counter-demo__unit\"><%= t(\"components.live_counter.demo.unit\") %></span>\n </p>\n </div>\n</div>\n"
199
199
  },
200
200
  "stimeo--local-time": {
201
201
  "file": "examples/local_time/_demo.html.erb",
@@ -243,7 +243,7 @@
243
243
  },
244
244
  "stimeo--otp": {
245
245
  "file": "examples/otp/_demo.html.erb",
246
- "source": "<%# Markup for the otp (OTP PIN passcode) demo.\n Provides auto-advance on entry in each field, Backspace to clear, paste and\n autofill splitting, and value sync. The digit count is the number of fields. %>\n<div\n class=\"otp-demo\"\n id=\"otp-passcode\"\n data-controller=\"stimeo--otp\"\n data-stimeo--otp-pattern-value=\"[0-9]\"\n role=\"group\"\n aria-label=\"<%= t(\"components.otp.demo.label\") %>\"\n>\n\n <div class=\"otp-demo__label\"><%= t(\"components.otp.demo.label\") %></div>\n\n <div class=\"otp-demo__fields\">\n <% 6.times do |i| %>\n <input\n class=\"otp-demo__field\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n data-stimeo--otp-target=\"field\"\n data-action=\"\n input->stimeo--otp#onInput\n keydown->stimeo--otp#onKeydown\n paste->stimeo--otp#onPaste\n pointerdown->stimeo--otp#onPointerDown\n \"\n aria-label=\"<%= t(\"components.otp.demo.digit_label\", position: i + 1) %>\"\n />\n <% end %>\n </div>\n\n <input\n type=\"hidden\"\n data-stimeo--otp-target=\"value\"\n name=\"otp\"\n />\n\n <%# Region for invalid-input / full-width-character warning messages. %>\n <div\n data-stimeo--otp-target=\"error\"\n id=\"otp-error-message\"\n class=\"otp-demo__error\"\n role=\"alert\"\n aria-live=\"polite\"\n hidden\n >\n <span class=\"otp-demo__error-badge\">!</span>\n <span class=\"otp-demo__error-text\">\n <%= t(\"components.otp.demo.invalid_msg\") %>\n </span>\n </div>\n\n <%# The completion message is shown by CSS reading data-state on the root, so the\n demo script only has to fill in the code itself. %>\n <div id=\"otp-complete-message\" class=\"otp-demo__message\">\n <span class=\"otp-demo__success-badge\">✓</span>\n <span class=\"otp-demo__success-text\">\n <%= t(\"components.otp.demo.complete_msg\") %>\n <strong id=\"otp-success-code\"></strong>\n </span>\n </div>\n\n <button type=\"button\" class=\"demo-trigger otp-demo__clear\" data-action=\"stimeo--otp#clear\">\n <%= t(\"components.otp.demo.clear\") %>\n </button>\n</div>\n"
246
+ "source": "<%# Markup for the otp (OTP PIN passcode) demo.\n Provides auto-advance on entry in each field, Backspace to clear, paste and\n autofill splitting, and value sync. The digit count is the number of fields. %>\n<div\n class=\"otp-demo\"\n id=\"otp-passcode\"\n data-controller=\"stimeo--otp\"\n data-stimeo--otp-pattern-value=\"[0-9]\"\n role=\"group\"\n aria-label=\"<%= t(\"components.otp.demo.label\") %>\"\n>\n\n <div class=\"otp-demo__label\"><%= t(\"components.otp.demo.label\") %></div>\n\n <div class=\"otp-demo__fields\">\n <% 6.times do |i| %>\n <input\n class=\"otp-demo__field\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"1\"\n autocomplete=\"one-time-code\"\n data-stimeo--otp-target=\"field\"\n data-action=\"\n input->stimeo--otp#onInput\n keydown->stimeo--otp#onKeydown\n paste->stimeo--otp#onPaste\n pointerdown->stimeo--otp#onPointerDown\n \"\n aria-label=\"<%= t(\"components.otp.demo.digit_label\", position: i + 1) %>\"\n />\n <% end %>\n </div>\n\n <input\n type=\"hidden\"\n data-stimeo--otp-target=\"value\"\n name=\"otp\"\n />\n\n <%# Region for invalid-input / full-width-character warning messages. %>\n <div\n data-stimeo--otp-target=\"error\"\n id=\"otp-error-message\"\n class=\"otp-demo__error\"\n role=\"alert\"\n aria-live=\"polite\"\n hidden\n >\n <span class=\"otp-demo__error-badge\">!</span>\n <span class=\"otp-demo__error-text\">\n <%= t(\"components.otp.demo.invalid_msg\") %>\n </span>\n </div>\n\n <%# The completion message is shown by CSS reading data-state on the root, so the\n demo script only has to fill in the code itself. %>\n <div id=\"otp-complete-message\" class=\"otp-demo__message\">\n <span class=\"otp-demo__success-badge\">✓</span>\n <span class=\"otp-demo__success-text\">\n <%= t(\"components.otp.demo.complete_msg\") %>\n <strong id=\"otp-success-code\"></strong>\n </span>\n </div>\n\n <button type=\"button\" class=\"demo-trigger otp-demo__clear\" data-action=\"click->stimeo--otp#clear\">\n <%= t(\"components.otp.demo.clear\") %>\n </button>\n</div>\n"
247
247
  },
248
248
  "stimeo--overflow-indicator": {
249
249
  "file": "examples/overflow_indicator/_demo.html.erb",
@@ -267,7 +267,7 @@
267
267
  },
268
268
  "stimeo--persist": {
269
269
  "file": "examples/persist/_demo.html.erb",
270
- "source": "<%# Persist demo: type into the fields and the draft is saved to localStorage\n (debounced). Reload or navigate away and back — the values are restored and the\n \"draft restored\" note appears (data-persist-restored). \"Clear draft\" calls the\n clear() action. The library owns the storage and the data hook; this demo only\n styles the restored note. %>\n<div class=\"persist-demo\">\n <form\n class=\"persist\"\n data-controller=\"stimeo--persist\"\n data-stimeo--persist-key-value=\"stimeo-demo-contact\">\n <p class=\"persist__restored\"><%= t(\"components.persist.demo.restored\") %></p>\n <label class=\"persist__field\">\n <span><%= t(\"components.persist.demo.name\") %></span>\n <input type=\"text\" name=\"name\">\n </label>\n <label class=\"persist__field\">\n <span><%= t(\"components.persist.demo.message\") %></span>\n <textarea name=\"message\" rows=\"3\"></textarea>\n </label>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"stimeo--persist#clear\">\n <%= t(\"components.persist.demo.clear\") %>\n </button>\n </form>\n</div>\n"
270
+ "source": "<%# Persist demo: type into the fields and the draft is saved to localStorage\n (debounced). Reload or navigate away and back — the values are restored and the\n \"draft restored\" note appears (data-persist-restored). \"Clear draft\" calls the\n clear() action. The library owns the storage and the data hook; this demo only\n styles the restored note. %>\n<div class=\"persist-demo\">\n <form\n class=\"persist\"\n data-controller=\"stimeo--persist\"\n data-stimeo--persist-key-value=\"stimeo-demo-contact\">\n <p class=\"persist__restored\"><%= t(\"components.persist.demo.restored\") %></p>\n <label class=\"persist__field\">\n <span><%= t(\"components.persist.demo.name\") %></span>\n <input type=\"text\" name=\"name\">\n </label>\n <label class=\"persist__field\">\n <span><%= t(\"components.persist.demo.message\") %></span>\n <textarea name=\"message\" rows=\"3\"></textarea>\n </label>\n <button type=\"button\" class=\"demo-trigger\" data-action=\"click->stimeo--persist#clear\">\n <%= t(\"components.persist.demo.clear\") %>\n </button>\n </form>\n</div>\n"
271
271
  },
272
272
  "stimeo--pointer-drag": {
273
273
  "file": "examples/pointer_drag/_demo.html.erb",