stimeo-ui 0.15.0 → 0.17.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 (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +212 -125
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +373 -174
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/authored_integer.ts","../../src/utils/blur_deferral.ts","../../src/utils/safe_timeout.ts","../../src/cable/consumer.ts","../../src/cable/live_counter_controller.ts","../../src/utils/keyed_timers.ts","../../src/cable/presence_controller.ts","../../src/cable/typing_indicator_controller.ts","../../src/cable/index.ts"],"names":["Controller","template"],"mappings":";;;;;;AAoBO,SAAS,QAAA,CAAS,OAAA,EAAiB,OAAA,GAAmC,EAAC,EAAS;AACrF,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,EAAK;AAC1B,EAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACvB,EAAA,MAAA,CAAO,aAAA;AAAA,IACL,IAAI,YAAY,4BAAA,EAA8B;AAAA,MAC5C,QAAQ,EAAE,OAAA,EAAS,MAAM,SAAA,EAAW,OAAA,CAAQ,cAAc,IAAA;AAAK,KAChE;AAAA,GACH;AACF;AAUO,SAAS,YAAA,CAAa,UAAkB,MAAA,EAAiD;AAC9F,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,6BAAA,EAA+B,CAAC,OAAO,IAAA,KAAiB;AAC9E,IAAA,MAAM,WAAA,GAAc,OAAO,IAAI,CAAA;AAC/B,IAAA,OAAO,WAAA,KAAgB,MAAA,GAAY,KAAA,GAAQ,MAAA,CAAO,WAAW,CAAA;AAAA,EAC/D,CAAC,CAAA;AACH;;;AC1CA,IAAM,UAAA,GAAa,wBAAA;AAEnB,IAAM,WAAA,GAAc,QAAA;AAwBb,SAAS,gBAAgB,IAAA,EAA6B;AAC3D,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA;AACjC,EAAA,IAAI,UAAA,KAAe,MAAM,OAAO,IAAA;AAEhC,EAAA,MAAM,KAAK,UAAA,CAAW,KAAA;AACtB,EAAA,MAAM,MAAA,GACJ,EAAA,GAAK,CAAA,IAAK,IAAA,CAAK,KAAK,CAAC,CAAA,KAAM,GAAA,KAAQ,EAAA,KAAO,KAAK,WAAA,CAAY,IAAA,CAAK,IAAA,CAAK,EAAA,GAAK,CAAC,CAAW,CAAA,CAAA;AAExF,EAAA,MAAM,OAAO,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAA;AAC3C,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,CAAC,CAAA,CAAE,MAAM,KAAK,CAAA;AAChC,EAAA,MAAM,aAAa,IAAA,CAAK,CAAC,EAAE,KAAA,CAAM,MAAM,KAAK,EAAC;AAI7C,EAAA,MAAM,IAAA,GAAO,KAAK,CAAC,CAAA;AACnB,EAAA,MAAM,QAAQ,IAAA,CAAK,MAAA,IAAU,KAAK,CAAC,IAAA,CAAK,WAAW,GAAG,CAAA;AACtD,EAAA,IAAI,MAAA,GAAS,IAAA;AACb,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,KAAA,IAAS,IAAI,IAAA,CAAK,MAAA,EAAQ,KAAK,CAAA,EAAG;AAChD,IAAA,IAAI,UAAA,CAAW,CAAA,GAAI,CAAC,CAAA,EAAG,MAAA,KAAW,KAAK,IAAA,CAAK,CAAC,CAAA,EAAG,MAAA,KAAW,CAAA,EAAG;AAC9D,IAAA,MAAA,IAAU,KAAK,CAAC,CAAA;AAAA,EAClB;AAEA,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA;AAC5C,EAAA,OAAO,MAAA,GAAS,CAAC,SAAA,IAAa,CAAA,GAAI,SAAA;AACpC;;;ACCO,IAAM,eAAN,MAAwD;AAAA;AAAA,EAEpD,QAAA,uBAAe,GAAA,EAAmB;AAAA;AAAA,EAElC,UAAA;AAAA;AAAA,EAGT,YAAY,SAAA,EAAiC;AAC3C,IAAA,IAAA,CAAK,UAAA,GAAa,SAAA;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,QAAA,CAAS,IAAA;AAAA,EACvB;AAAA;AAAA,EAGA,IAAI,QAAA,GAAgB;AAClB,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA;AAAA,EACjC;AAAA;AAAA,EAGA,IAAI,OAAA,EAAqB;AACvB,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,OAAA,EAAkB;AACtB,IAAA,IAAI,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA,EAAG;AAChC,IAAA,MAAM,SAAS,MAAY;AACzB,MAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AACpB,MAAA,IAAA,CAAK,WAAW,OAAO,CAAA;AAAA,IACzB,CAAA;AACA,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAA,EAAS,MAAM,CAAA;AACjC,IAAA,OAAA,CAAQ,gBAAA,CAAiB,QAAQ,MAAM,CAAA;AAAA,EACzC;AAAA;AAAA,EAGA,UAAU,OAAA,EAAkB;AAC1B,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,QAAA,EAAU;AACnC,MAAA,IAAI,OAAA,KAAY,OAAA,EAAS,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA;AAAA,IAC/C;AACA,IAAA,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EACpB;AAAA;AAAA,EAGA,QAAQ,OAAA,EAAkB;AACxB,IAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,KAAA,MAAW,OAAA,IAAW,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC3D;AAAA;AAAA,EAGA,QAAQ,OAAA,EAAkB;AACxB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,OAAO,CAAA;AACxC,IAAA,IAAI,MAAA,EAAQ,OAAA,CAAQ,mBAAA,CAAoB,MAAA,EAAQ,MAAM,CAAA;AACtD,IAAA,IAAA,CAAK,QAAA,CAAS,OAAO,OAAO,CAAA;AAAA,EAC9B;AACF,CAAA;;;AC1FO,IAAM,kBAAA,GAAqB,UAAA;AASlC,IAAe,gBAAf,MAA6B;AAAA;AAAA,EAER,GAAA,uBAAU,GAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAczC,MAAM,EAAA,EAAkB;AACtB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAA,GAAiB;AACf,IAAA,KAAA,MAAW,EAAA,IAAM,KAAK,GAAA,EAAK;AACzB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AACA,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,EACjB;AAAA;AAAA,EAGA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,GAAA,CAAI,IAAA;AAAA,EAClB;AACF,CAAA;AAmBO,IAAM,WAAA,GAAN,cAA0B,aAAA,CAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7C,GAAA,CAAI,UAAsB,KAAA,EAAuB;AAC/C,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,QAAA,CAAS,MAAM;AAC7B,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,EAAE,CAAA;AAClB,MAAA,QAAA,EAAS;AAAA,IACX,GAAG,KAAK,CAAA;AACR,IAAA,IAAA,CAAK,GAAA,CAAI,IAAI,EAAE,CAAA;AACf,IAAA,OAAO,EAAA;AAAA,EACT;AAAA,EAEU,QAAA,CAAS,UAAsB,KAAA,EAAuB;AAC9D,IAAA,OAAO,MAAA,CAAO,UAAA,CAAW,QAAA,EAAU,KAAK,CAAA;AAAA,EAC1C;AAAA,EAEU,OAAO,EAAA,EAAkB;AACjC,IAAA,MAAA,CAAO,aAAa,EAAE,CAAA;AAAA,EACxB;AACF,CAAA;AAoBO,IAAM,YAAA,GAAN,cAA2B,aAAA,CAAc;AAAA;AAAA,EAE9C,GAAA,CAAI,UAAsB,KAAA,EAAuB;AAC/C,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,KAAK,CAAA;AACxC,IAAA,IAAA,CAAK,GAAA,CAAI,IAAI,EAAE,CAAA;AACf,IAAA,OAAO,EAAA;AAAA,EACT;AAAA,EAEU,QAAA,CAAS,UAAsB,KAAA,EAAuB;AAC9D,IAAA,OAAO,MAAA,CAAO,WAAA,CAAY,QAAA,EAAU,KAAK,CAAA;AAAA,EAC3C;AAAA,EAEU,OAAO,EAAA,EAAkB;AACjC,IAAA,MAAA,CAAO,cAAc,EAAE,CAAA;AAAA,EACzB;AACF,CAAA;ACzFO,SAAS,wBAAwB,GAAA,EAAsC;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAkB,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACtC,IAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,MAAA,KAAW,IAAA,IAAQ,MAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,OAAO,EAAC;AACpF,IAAA,OAAO,MAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,EAAC;AAAA,EACV;AACF;AASA,IAAI,cAAA,GAAuC,IAAA;AAUpC,SAAS,iBAAiB,QAAA,EAAsC;AACrE,EAAA,cAAA,GAAiB,QAAA;AACnB;AAMO,SAAS,gBAAA,GAAkC;AAChD,EAAA,IAAI,CAAC,cAAA,EAAgB,cAAA,GAAiB,cAAA,EAAe;AACrD,EAAA,OAAO,cAAA;AACT;AAuCA,IAAM,mBAAA,uBAA0B,OAAA,EAAwD;AAMjF,SAAS,aAAa,OAAA,EAAmD;AAC9E,EAAA,OAAO,IAAA,CAAK,UAAU,OAAO,OAAA,KAAY,WAAW,EAAE,OAAA,KAAY,OAAO,CAAA;AAC3E;AA0BO,SAAS,2BAAA,CACd,SACA,KAAA,EAC4B;AAC5B,EAAA,MAAM,WAAW,gBAAA,EAAiB;AAClC,EAAA,IAAI,QAAA,GAAW,mBAAA,CAAoB,GAAA,CAAI,QAAQ,CAAA;AAC/C,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,QAAA,uBAAe,GAAA,EAAI;AACnB,IAAA,mBAAA,CAAoB,GAAA,CAAI,UAAU,QAAQ,CAAA;AAAA,EAC5C;AACA,EAAA,MAAM,UAAA,GAAa,aAAa,OAAO,CAAA;AACvC,EAAA,MAAM,MAAA,GAAiB,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAM;AAE/C,EAAA,IAAI,MAAA,GAAS,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AACpC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,GAAS,sBAAA,CAAuB,UAAU,OAAO,CAAA;AACjD,IAAA,QAAA,CAAS,GAAA,CAAI,YAAY,MAAM,CAAA;AAAA,EACjC,CAAA,MAAA,IAAW,MAAA,CAAO,QAAA,IAAY,MAAA,CAAO,SAAA,EAAW;AAE9C,IAAA,MAAM,IAAA,GAAO,MAAA;AACb,IAAA,MAAA,CAAO,OAAA,GAAU,IAAA;AACjB,IAAA,cAAA,CAAe,MAAM;AACnB,MAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACrB,MAAA,MAAA,CAAO,OAAA,GAAU,KAAA;AACjB,MAAA,IAAI,IAAA,CAAK,QAAA,EAAU,KAAA,CAAM,QAAA,IAAW;AAAA,iBACzB,SAAA,IAAY;AAAA,IACzB,CAAC,CAAA;AAAA,EACH;AACA,EAAA,MAAA,CAAO,OAAA,CAAQ,IAAI,MAAM,CAAA;AACzB,EAAA,MAAM,KAAA,GAAQ,MAAA;AACd,EAAA,MAAM,UAAA,GAAa,QAAA;AACnB,EAAA,IAAI,MAAA,GAAS,IAAA;AAEb,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAC,MAAA,EAAQ,IAAA,KAAS,MAAM,YAAA,CAAa,OAAA,CAAQ,QAAQ,IAAI,CAAA;AAAA,IAClE,aAAa,MAAM;AACjB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,MAAA,GAAS,KAAA;AAET,MAAA,MAAA,CAAO,OAAA,GAAU,KAAA;AACjB,MAAA,KAAA,CAAM,OAAA,CAAQ,OAAO,MAAM,CAAA;AAC3B,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG;AAC5B,QAAA,UAAA,CAAW,OAAO,UAAU,CAAA;AAC5B,QAAA,KAAA,CAAM,aAAa,WAAA,EAAY;AAAA,MACjC;AAAA,IACF,CAAA;AAAA,IACA,IAAI,SAAA,GAAY;AACd,MAAA,OAAO,KAAA,CAAM,SAAA;AAAA,IACf,CAAA;AAAA,IACA,IAAI,QAAA,GAAW;AACb,MAAA,OAAO,KAAA,CAAM,QAAA;AAAA,IACf;AAAA,GACF;AACF;AAGA,SAAS,sBAAA,CACP,UACA,OAAA,EACoB;AACpB,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAEhC,EAAA,MAAM,MAAA,GAAS,CAAC,OAAA,KAA2D;AACzE,IAAA,KAAA,MAAW,MAAA,IAAU,CAAC,GAAG,OAAO,CAAA,EAAG;AACjC,MAAA,MAAA,CAAO,OAAA,GAAU,KAAA;AACjB,MAAA,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,IACtB;AAAA,EACF,CAAA;AACA,EAAA,MAAM,MAAA,GAA6B;AAAA,IACjC,OAAA;AAAA,IACA,SAAA,EAAW,KAAA;AAAA,IACX,QAAA,EAAU,KAAA;AAAA,IACV,YAAA,EAAc,QAAA,CAAS,aAAA,CAAc,MAAA,CAAO,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA,MAInD,WAAW,MAAM;AACf,QAAA,IAAI,OAAO,QAAA,EAAU;AACrB,QAAA,MAAA,CAAO,SAAA,GAAY,IAAA;AACnB,QAAA,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,SAAA,IAAa,CAAA;AAAA,MACvC,CAAA;AAAA,MACA,cAAc,MAAM;AAClB,QAAA,IAAI,OAAO,QAAA,EAAU;AACrB,QAAA,MAAA,CAAO,SAAA,GAAY,KAAA;AACnB,QAAA,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,YAAA,IAAgB,CAAA;AAAA,MAC1C,CAAA;AAAA,MACA,UAAU,MAAM;AACd,QAAA,MAAA,CAAO,SAAA,GAAY,KAAA;AACnB,QAAA,MAAA,CAAO,QAAA,GAAW,IAAA;AAClB,QAAA,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,QAAA,IAAY,CAAA;AAAA,MACtC,CAAA;AAAA,MACA,QAAA,EAAU,CAAC,IAAA,KAAkB,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,QAAA,GAAW,IAAI,CAAC;AAAA,KACtE;AAAA,GACH;AACA,EAAA,OAAO,MAAA;AACT;;;ACtPA,IAAM,eAAA,GAAkB,4BAAA;AAoEjB,IAAM,qBAAA,GAAN,MAAM,sBAAA,SAA8B,UAAA,CAAwB;AAAA,EACjE,OAAgB,OAAA,GAAU,CAAC,OAAA,EAAS,SAAS,CAAA;AAAA,EAC7C,OAAgB,MAAA,GAAS;AAAA,IACvB,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACrC,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACpC,EAAA,EAAI,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAChC,YAAA,EAAc,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA;AAAG,GAC5C;AAAA;AAAA,EAEA,OAAO,cAAA,GAAiB,GAAA;AAAA,EACxB,OAAO,OAAA,GAAU,CAAC,WAAW,CAAA;AAAA,EAC7B,OAAO,MAAA,GAAS,CAAC,QAAQ,CAAA;AAAA;AAAA,EAWzB,UAAmC,EAAC;AAAA,EAEpC,aAAA,GAAmD,IAAA;AAAA,EACnD,UAAA,GAAa,KAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQJ,eAAyB,EAAC;AAAA;AAAA,EAE1B,gBAAA,GAAmB,IAAI,YAAA,CAAa,CAAC,YAAY,IAAA,CAAK,YAAA,CAAa,OAAO,CAAC,CAAA;AAAA,EAC3E,OAAA,GAAU,IAAI,WAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASnC,kBAAA,GAA2B;AACzB,IAAA,IAAA,CAAK,OAAA,GAAU,uBAAA,CAAwB,IAAA,CAAK,WAAW,CAAA;AACvD,IAAA,IAAA,CAAK,YAAA,EAAa;AAAA,EACpB;AAAA;AAAA,EAGA,mBAAA,GAA4B;AAC1B,IAAA,IAAA,CAAK,YAAA,EAAa;AAAA,EACpB;AAAA,EAES,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,IAAA,CAAK,UAAA,EAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,4BAA4B,CAAA;AACzD,IAAA,IAAA,CAAK,aAAa,MAAA,GAAS,CAAA;AAC3B,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA,IAAA,CAAK,aAAA,GAAgB,KAAK,KAAA,EAAM;AAAA,IAClC;AAGA,IAAA,IAAA,CAAK,aAAA,EAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,KAAA,GAA2C;AACzC,IAAA,IAAI;AACF,MAAA,OAAO,2BAAA,CAA4B,KAAK,WAAA,EAAa;AAAA,QACnD,SAAA,EAAW,MAAM,IAAA,CAAK,aAAA,EAAc;AAAA;AAAA;AAAA;AAAA;AAAA,QAKpC,YAAA,EAAc,MAAM,IAAA,CAAK,aAAA,EAAc;AAAA;AAAA;AAAA;AAAA,QAIvC,UAAU,MAAM;AACd,UAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,4BAAA,EAA8B,MAAM,CAAA;AAC9D,UAAA,IAAA,CAAK,aAAA,EAAc;AAAA,QACrB,CAAA;AAAA,QACA,QAAA,EAAU,CAAC,IAAA,KAAkB,IAAA,CAAK,YAAY,IAAI;AAAA,OACnD,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,IAAA,CAAK,gEAAgE,KAAK,CAAA;AAClF,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,WAAA,GAAuC;AACzC,IAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAU,GAAG,IAAA,KAAS,IAAA,CAAK,OAAA;AAC5C,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,GAAG,IAAA,EAAK;AAAA,EAC/C;AAAA;AAAA,EAGA,YAAA,GAAqB;AACnB,IAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AACtB,IAAA,IAAA,CAAK,eAAe,WAAA,EAAY;AAChC,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAA,CAAK,UAAA,EAAW;AAAA,EAClB;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,iBAAiB,UAAA,EAAW;AACjC,IAAA,IAAA,CAAK,eAAe,WAAA,EAAY;AAChC,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,4BAA4B,CAAA;AAEzD,IAAA,KAAA,MAAW,OAAA,IAAW,IAAA,CAAK,cAAA,EAAgB,IAAA,CAAK,SAAS,OAAO,CAAA;AAAA,EAClE;AAAA;AAAA,EAGA,uBAAuB,MAAA,EAA2B;AAChD,IAAA,IAAA,CAAK,aAAa,MAAM,CAAA;AAAA,EAC1B;AAAA;AAAA,EAGA,0BAA0B,MAAA,EAA2B;AACnD,IAAA,IAAA,CAAK,gBAAA,CAAiB,QAAQ,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,KAAA,EAAuD;AAM/D,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAElB,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,KAAA,EAAO,MAAA,EAAQ,SAAS,CAAC,CAAA;AAC5C,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,aAAA,CAAc,GAAG,IAAI,GAAA,GAAM,CAAA;AAKhD,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,QAAA,GAAW,KAAK,CAAA;AAAA,IACnC,CAAA,MAAA,IAAW,IAAA,CAAK,OAAA,KAAY,EAAA,EAAI;AAC9B,MAAA,IAAA,CAAK,YAAA,CAAa,KAAK,KAAK,CAAA;AAC5B,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,QAAA,GAAW,KAAK,CAAA;AAAA,IACnC;AACA,IAAA,IAAA,CAAK,aAAA,EAAe,QAAQ,WAAA,EAAa,EAAE,IAAI,IAAA,CAAK,OAAA,EAAS,OAAO,CAAA;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAY,IAAA,EAAqB;AAC/B,IAAA,MAAM,OAAA,GAAU,IAAA;AAChB,IAAA,IAAI,MAAA,CAAO,aAAA,CAAc,OAAA,EAAS,KAAK,CAAA,EAAG;AAExC,MAAA,IAAA,CAAK,aAAa,MAAA,GAAS,CAAA;AAC3B,MAAA,IAAA,CAAK,UAAA,CAAW,SAAS,KAAe,CAAA;AACxC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,MAAA,CAAO,aAAA,CAAc,OAAA,EAAS,KAAK,CAAA,EAAG;AACxC,MAAA,MAAM,QAAQ,OAAA,EAAS,KAAA;AACvB,MAAA,MAAM,GAAA,GACJ,OAAO,OAAA,EAAS,EAAA,KAAO,QAAA,IAAY,QAAQ,EAAA,KAAO,EAAA,IAAM,OAAA,CAAQ,EAAA,KAAO,IAAA,CAAK,OAAA;AAI9E,MAAA,MAAM,QAAQ,GAAA,GAAM,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,KAAK,CAAA,GAAI,EAAA;AACvD,MAAA,IAAI,UAAU,EAAA,EAAI;AAChB,QAAA,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AACjC,QAAA;AAAA,MACF;AACA,MAAA,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,QAAA,GAAW,KAAK,CAAA;AAAA,IACvC;AAAA,EACF;AAAA;AAAA,EAGA,IAAI,MAAA,GAAkB;AACpB,IAAA,OAAO,KAAK,YAAA,KAAiB,EAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,IAAI,MAAA,GAAkB;AACpB,IAAA,OAAO,IAAA,CAAK,MAAA,KAAW,IAAA,CAAK,aAAA,EAAe,SAAA,IAAa,KAAA,CAAA;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAA,GAAsB;AACpB,IAAA,KAAA,MAAW,OAAA,IAAW,IAAA,CAAK,cAAA,EAAgB,IAAA,CAAK,aAAa,OAAO,CAAA;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,OAAA,EAA4B;AACvC,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAA,CAAK,gBAAA,CAAiB,QAAQ,OAAO,CAAA;AACrC,MAAA,IAAA,CAAK,SAAS,OAAO,CAAA;AACrB,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAA,CAAQ,YAAA,CAAa,UAAU,CAAA,EAAG;AAItC,IAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,QAAA,CAAS,aAAa,CAAA,EAAG;AAC5C,MAAA,IAAA,CAAK,gBAAA,CAAiB,MAAM,OAAO,CAAA;AACnC,MAAA;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,YAAA,CAAa,YAAY,EAAE,CAAA;AACnC,IAAA,OAAA,CAAQ,YAAA,CAAa,iBAAiB,EAAE,CAAA;AAAA,EAC1C;AAAA;AAAA,EAGA,SAAS,OAAA,EAA4B;AACnC,IAAA,IAAI,CAAC,OAAA,CAAQ,YAAA,CAAa,eAAe,CAAA,EAAG;AAC5C,IAAA,OAAA,CAAQ,gBAAgB,UAAU,CAAA;AAClC,IAAA,OAAA,CAAQ,gBAAgB,eAAe,CAAA;AAAA,EACzC;AAAA;AAAA,EAGA,IAAI,QAAA,GAAwB;AAC1B,IAAA,OAAO,IAAA,CAAK,cAAA,GAAiB,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,OAAA;AAAA,EACvD;AAAA;AAAA,EAGA,IAAI,QAAA,GAAmB;AAGrB,IAAA,OAAO,eAAA,CAAgB,IAAA,CAAK,QAAA,CAAS,WAAA,IAAe,EAAE,CAAA,IAAK,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAA,EAAwB;AAC7B,IAAA,IAAI,KAAA,KAAU,IAAA,CAAK,QAAA,EAAU,OAAO,KAAA;AACpC,IAAA,IAAA,CAAK,QAAA,CAAS,WAAA,GAAc,MAAA,CAAO,KAAK,CAAA;AACxC,IAAA,IAAA,CAAK,SAAS,QAAA,EAAU,EAAE,QAAQ,EAAE,KAAA,IAAS,CAAA;AAC7C,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAW,KAAA,EAAqB;AAC9B,IAAA,IAAI,KAAK,MAAA,CAAO,KAAK,CAAA,EAAG,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAU,KAAA,EAAqB;AAC7B,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,MAAM;AACrB,MAAA,QAAA,CAAS,aAAa,IAAA,CAAK,iBAAA,EAAmB,EAAE,KAAA,EAAO,CAAC,CAAA;AAAA,IAC1D,CAAA,EAAG,uBAAsB,cAAc,CAAA;AAAA,EACzC;AACF;;;ACpWO,IAAM,cAAN,MAAqB;AAAA,EACjB,OAAA,GAAU,IAAI,WAAA,EAAY;AAAA;AAAA,EAG1B,QAAA,uBAAe,GAAA,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvC,GAAA,CAAI,GAAA,EAAQ,QAAA,EAAsB,KAAA,EAAqB;AACrD,IAAA,IAAA,CAAK,MAAM,GAAG,CAAA;AACd,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAM;AAChC,MAAA,IAAA,CAAK,QAAA,CAAS,OAAO,GAAG,CAAA;AACxB,MAAA,QAAA,EAAS;AAAA,IACX,GAAG,KAAK,CAAA;AACR,IAAA,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAA,EAAK,EAAE,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,GAAA,EAAc;AAClB,IAAA,IAAA,CAAK,QAAQ,KAAA,CAAM,IAAA,CAAK,SAAS,GAAA,CAAI,GAAG,KAAK,EAAE,CAAA;AAC/C,IAAA,IAAA,CAAK,QAAA,CAAS,OAAO,GAAG,CAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,SAAS,KAAA,EAAM;AAAA,EACtB;AAAA;AAAA,EAGA,IAAI,GAAA,EAAiB;AACnB,IAAA,OAAO,IAAA,CAAK,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAAA,EAC9B;AACF,CAAA;;;ACjEA,IAAM,kBAAA,GAAqB,GAAA;AAG3B,IAAM,oBAAA,GAAuB,IAAA;AAG7B,IAAM,kBAAA,GAAqB,GAAA;AAa3B,IAAM,QAAA,uBAAe,GAAA,EAAoB;AA4ElC,IAAM,kBAAA,GAAN,MAAM,mBAAA,SAA2BA,UAAAA,CAAwB;AAAA,EAC9D,OAAgB,OAAA,GAAU,CAAC,OAAA,EAAS,QAAQ,UAAU,CAAA;AAAA,EACtD,OAAgB,MAAA,GAAS;AAAA,IACvB,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACrC,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACpC,EAAA,EAAI,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAChC,IAAA,EAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAClC,SAAA,EAAW,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,oBAAA,EAAqB;AAAA,IACzD,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,kBAAA,EAAmB;AAAA,IACrD,gBAAA,EAAkB,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAC9C,iBAAA,EAAmB,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA;AAAG,GACjD;AAAA,EACA,OAAO,MAAA,GAAS,CAAC,MAAA,EAAQ,SAAS,QAAQ,CAAA;AAAA;AAAA,EAkB1C,OAAgB,cAAA,GAAiB,GAAA;AAAA;AAAA,EAGjC,UAAmC,EAAC;AAAA,EAEpC,aAAA,GAAmD,IAAA;AAAA;AAAA,EAE1C,MAAA,uBAAa,GAAA,EAAkB;AAAA;AAAA,EAE/B,OAAA,GAAU,IAAI,WAAA,EAAoB;AAAA,EAClC,OAAA,GAAU,IAAI,WAAA,EAAY;AAAA,EAC1B,UAAA,GAAa,IAAI,YAAA,EAAa;AAAA;AAAA,EAEvC,aAAA,GAAgB,CAAA;AAAA;AAAA,EAEhB,cAAA,GAAgC,IAAA;AAAA;AAAA,EAEhC,WAAA,GAA6B,IAAA;AAAA;AAAA,EAE7B,WAAA,GAAc,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQd,kBAAA,GAA2B;AACzB,IAAA,IAAA,CAAK,OAAA,GAAU,uBAAA,CAAwB,IAAA,CAAK,WAAW,CAAA;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,WAAA,GAAuC;AACzC,IAAA,MAAM,EAAE,OAAA,EAAS,SAAA,EAAW,GAAG,IAAA,KAAS,IAAA,CAAK,OAAA;AAC7C,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,GAAG,IAAA,EAAK;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,IAAI,UAAA,GAAqB;AACvB,IAAA,MAAM,WAAW,IAAA,CAAK,cAAA;AACtB,IAAA,OAAO,MAAA,CAAO,SAAS,QAAQ,CAAA,IAAK,WAAW,CAAA,IAAK,QAAA,IAAY,qBAC5D,QAAA,GACA,oBAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,QAAA,GAAmB;AACrB,IAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,IAAA,OAAO,MAAA,CAAO,SAAS,QAAQ,CAAA,IAAK,WAAW,CAAA,IAAK,QAAA,IAAY,qBAC5D,QAAA,GACA,kBAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAA,GAA4B;AAC1B,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,UAAA,CAAW,gBAAA,CAAiB,oBAAoB,CAAA,EAAG;AAC1E,MAAA,KAAA,CAAM,MAAA,EAAO;AAAA,IACf;AACA,IAAA,KAAA,MAAW,CAAC,EAAA,EAAI,IAAI,CAAA,IAAK,IAAA,CAAK,QAAQ,IAAA,CAAK,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,IAAI,CAAA;AAAA,EACvE;AAAA;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAA,CAAK,YAAY,WAAA,GAAc,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,EACpE;AAAA,EAES,OAAA,GAAgB;AAKvB,IAAA,IAAA,CAAK,MAAA,EAAO;AACZ,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,wBAAwB,CAAA;AAIrD,IAAA,IAAI,KAAK,cAAA,EAAgB,IAAA,CAAK,YAAY,WAAA,GAAc,IAAA,CAAK,cAAc,CAAC,CAAA;AAE5E,IAAA,IAAI,CAAC,KAAK,YAAA,EAAc;AACxB,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,IAAA,CAAK,aAAA,GAAgB,2BAAA,CAA4B,IAAA,CAAK,WAAA,EAAa;AAAA;AAAA;AAAA;AAAA,MAIjE,SAAA,EAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAAA;AAAA;AAAA,MAGlC,UAAU,MAAM;AACd,QAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,wBAAA,EAA0B,MAAM,CAAA;AAAA,MAC5D,CAAA;AAAA,MACA,QAAA,EAAU,CAAC,IAAA,KAAkB,IAAA,CAAK,YAAY,IAAI;AAAA,KACnD,CAAA;AACD,IAAA,IAAA,CAAK,UAAA,CAAW,IAAI,MAAM,IAAA,CAAK,QAAQ,IAAI,CAAA,EAAG,KAAK,UAAU,CAAA;AAC7D,IAAA,MAAA,CAAO,gBAAA,CAAiB,UAAA,EAAY,IAAA,CAAK,WAAW,CAAA;AAAA,EACtD;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,MAAA,CAAO,mBAAA,CAAoB,UAAA,EAAY,IAAA,CAAK,WAAW,CAAA;AAGvD,IAAA,IAAI,IAAA,CAAK,eAAA,EAAgB,EAAG,IAAA,CAAK,gBAAA,EAAiB;AAClD,IAAA,IAAA,CAAK,eAAe,WAAA,EAAY;AAChC,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AACzB,IAAA,IAAA,CAAK,MAAA,EAAO;AACZ,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,wBAAwB,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAA,GAAsB;AACpB,IAAA,IAAA,CAAK,WAAA,GAAc,CAAA,EAAG,YAAA,CAAa,IAAA,CAAK,WAAW,CAAC;AAAA,EAAK,KAAK,OAAO,CAAA,CAAA;AACrE,IAAA,QAAA,CAAS,GAAA,CAAI,KAAK,WAAA,EAAA,CAAc,QAAA,CAAS,IAAI,IAAA,CAAK,WAAW,CAAA,IAAK,CAAA,IAAK,CAAC,CAAA;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAA,GAA2B;AACzB,IAAA,MAAM,MAAM,IAAA,CAAK,WAAA;AACjB,IAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,IAAA,MAAM,SAAA,GAAA,CAAa,QAAA,CAAS,GAAA,CAAI,GAAG,KAAK,CAAA,IAAK,CAAA;AAC7C,IAAA,IAAI,SAAA,GAAY,CAAA,EAAG,QAAA,CAAS,GAAA,CAAI,KAAK,SAAS,CAAA;AAAA,SACzC,QAAA,CAAS,OAAO,GAAG,CAAA;AACxB,IAAA,OAAO,SAAA,IAAa,CAAA;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAAA,GAAyB;AACvB,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,SAAA,IAAa,CAAC,KAAK,OAAA,EAAS;AACrD,IAAA,IAAA,CAAK,aAAA,CAAc,QAAQ,QAAA,EAAU,EAAE,IAAI,IAAA,CAAK,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,CAAA;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWS,cAAc,MAAY;AACjC,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,QAAQ,KAAA,EAAsB;AAC5B,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,SAAA,IAAa,CAAC,KAAK,OAAA,EAAS;AACrD,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,IAAA,GAAO,kBAAA,IAAsB,GAAA,GAAM,IAAA,CAAK,aAAA,CAAA;AAC9C,IAAA,IAAI,CAAC,KAAA,IAAS,IAAA,GAAO,CAAA,EAAG;AACtB,MAAA,IAAI,IAAA,CAAK,mBAAmB,IAAA,EAAM;AAChC,QAAA,IAAA,CAAK,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAM;AAC3C,UAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,UAAA,IAAA,CAAK,QAAQ,IAAI,CAAA;AAAA,QACnB,GAAG,IAAI,CAAA;AAAA,MACT;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,GAAA;AACrB,IAAA,IAAA,CAAK,aAAA,CAAc,OAAA,CAAQ,QAAA,EAAU,EAAE,EAAA,EAAI,KAAK,OAAA,EAAS,IAAA,EAAM,IAAA,CAAK,SAAA,EAAW,CAAA;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,IAAA,EAAqB;AAC/B,IAAA,MAAM,MAAA,GAAS,IAAA;AACf,IAAA,MAAM,KAAK,MAAA,EAAQ,EAAA;AACnB,IAAA,IAAI,OAAO,EAAA,KAAO,QAAA,IAAY,OAAO,EAAA,IAAM,EAAA,KAAO,KAAK,OAAA,EAAS;AAEhE,IAAA,IAAI,MAAA,EAAQ,YAAY,IAAA,EAAM;AAC5B,MAAA,IAAA,CAAK,MAAM,EAAE,CAAA;AACb,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAO,OAAO,MAAA,EAAQ,IAAA,KAAS,QAAA,GAAW,OAAO,IAAA,GAAO,EAAA;AAC9D,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA;AACnC,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,EAAA,EAAI,MAAM,KAAK,KAAA,CAAM,EAAE,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AACxD,IAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,EAAA,EAAI,EAAE,MAAM,CAAA;AAE5B,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,IAAA,CAAK,YAAA,CAAa,IAAI,IAAI,CAAA;AAC1B,MAAA,IAAA,CAAK,OAAA,EAAQ;AACb,MAAA,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,qBAAA,EAAuB,IAAI,CAAA;AAC/C,MAAA,IAAA,CAAK,QAAA,CAAS,QAAQ,EAAE,MAAA,EAAQ,EAAE,EAAA,EAAI,IAAA,IAAQ,CAAA;AAC9C,MAAA,IAAA,CAAK,QAAQ,KAAK,CAAA;AAAA,IACpB,CAAA,MAAA,IAAW,QAAA,CAAS,IAAA,KAAS,IAAA,EAAM;AACjC,MAAA,IAAA,CAAK,YAAA,CAAa,IAAI,IAAI,CAAA;AAC1B,MAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,EAAA,EAAkB;AACtB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA;AAC/B,IAAA,IAAI,SAAS,MAAA,EAAW;AACxB,IAAA,IAAA,CAAK,OAAA,CAAQ,MAAM,EAAE,CAAA;AACrB,IAAA,IAAA,CAAK,MAAA,CAAO,OAAO,EAAE,CAAA;AACrB,IAAA,IAAA,CAAK,aAAa,EAAE,CAAA;AACpB,IAAA,IAAA,CAAK,OAAA,EAAQ;AACb,IAAA,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,sBAAA,EAAwB,IAAA,CAAK,IAAI,CAAA;AACrD,IAAA,IAAA,CAAK,SAAS,OAAA,EAAS,EAAE,QAAQ,EAAE,EAAA,IAAM,CAAA;AAAA,EAC3C;AAAA;AAAA,EAGA,OAAA,GAAgB;AACd,IAAA,MAAM,QAAQ,CAAC,GAAG,KAAK,MAAA,CAAO,OAAA,EAAS,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,EAAA,EAAI,IAAI,CAAA,MAAO,EAAE,IAAI,IAAA,EAAM,IAAA,CAAK,MAAK,CAAE,CAAA;AACtF,IAAA,IAAA,CAAK,QAAQ,YAAA,CAAa,cAAA,EAAgB,MAAM,MAAA,GAAS,CAAA,GAAI,SAAS,OAAO,CAAA;AAC7E,IAAA,IAAA,CAAK,QAAQ,YAAA,CAAa,oBAAA,EAAsB,MAAA,CAAO,KAAA,CAAM,MAAM,CAAC,CAAA;AACpE,IAAA,IAAI,IAAA,CAAK,gBAAgB,IAAA,CAAK,WAAA,CAAY,cAAc,IAAA,CAAK,aAAA,CAAc,MAAM,MAAM,CAAA;AACvF,IAAA,IAAA,CAAK,SAAS,QAAA,EAAU,EAAE,QAAQ,EAAE,KAAA,IAAS,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,SAAA,CAAU,UAAkB,IAAA,EAAoB;AAC9C,IAAA,MAAM,OAAA,GAAU,aAAa,QAAA,EAAU,EAAE,MAAM,KAAA,EAAO,IAAA,CAAK,MAAA,CAAO,IAAA,EAAM,CAAA;AACxE,IAAA,IAAI,OAAA,CAAQ,IAAA,EAAK,KAAM,EAAA,EAAI;AAC3B,IAAA,IAAI,KAAK,WAAA,KAAgB,IAAA,OAAW,OAAA,CAAQ,KAAA,CAAM,KAAK,WAAW,CAAA;AAClE,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAM;AACxC,MAAA,QAAA,CAAS,OAAO,CAAA;AAChB,MAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AAAA,IACrB,CAAA,EAAG,oBAAmB,cAAc,CAAA;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,cAAc,KAAA,EAAuB;AACnC,IAAA,MAAM,SAAA,GAAY,KAAK,WAAA,CAAY,OAAA;AACnC,IAAA,MAAM,QAAA,GAAA,CACH,KAAA,KAAU,CAAA,GAAI,SAAA,CAAU,IAAA,GAAO,KAAA,KAAU,CAAA,GAAI,SAAA,CAAU,GAAA,GAAM,SAAA,CAAU,KAAA,KACxE,SAAA,CAAU,KAAA;AACZ,IAAA,OAAO,QAAA,GAAW,SAAS,OAAA,CAAQ,UAAA,EAAY,OAAO,KAAK,CAAC,CAAA,GAAI,MAAA,CAAO,KAAK,CAAA;AAAA,EAC9E;AAAA;AAAA,EAGA,YAAA,CAAa,IAAY,IAAA,EAAoB;AAC3C,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,IAAiB,CAAC,KAAK,iBAAA,EAAmB;AACpD,IAAA,MAAM,OAAO,IAAA,CAAK,cAAA,CAAe,OAAA,CAAQ,iBAAA,EAAmB,UAAU,IAAI,CAAA;AAC1E,IAAA,IAAI,EAAE,gBAAgB,OAAA,CAAA,EAAU;AAChC,IAAA,IAAA,CAAK,YAAA,CAAa,oBAAoB,EAAE,CAAA;AACxC,IAAA,IAAA,CAAK,SAAA,CAAU,MAAM,IAAI,CAAA;AACzB,IAAA,IAAA,CAAK,UAAA,CAAW,YAAY,IAAI,CAAA;AAAA,EAClC;AAAA,EAEA,YAAA,CAAa,IAAY,IAAA,EAAoB;AAC3C,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA;AAC9B,IAAA,IAAI,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,IAAI,CAAA;AAAA,EACrC;AAAA,EAEA,aAAa,EAAA,EAAkB;AAC7B,IAAA,IAAA,CAAK,SAAA,CAAU,EAAE,CAAA,EAAG,MAAA,EAAO;AAAA,EAC7B;AAAA,EAEA,UAAU,EAAA,EAA4B;AACpC,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,OAAO,IAAA;AAEhC,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,UAAA,CAAW,gBAAA,CAAiB,oBAAoB,CAAA,EAAG;AAC1E,MAAA,IAAI,KAAA,CAAM,YAAA,CAAa,kBAAkB,CAAA,KAAM,IAAI,OAAO,KAAA;AAAA,IAC5D;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,SAAA,CAAU,MAAe,IAAA,EAAoB;AAC3C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,gBAAA,CAAiB,sBAAsB,CAAA;AAC1D,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,EAAO,IAAA,CAAK,WAAA,GAAc,IAAA;AAC7C,IAAA,IAAI,MAAM,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,YAAA,CAAa,oBAAoB,CAAA,EAAG;AACjE,MAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AAAA,IACrB;AAAA,EACF;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAClB,IAAA,IAAA,CAAK,aAAA,GAAgB,CAAA;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,cAAc,CAAA;AAC3C,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,oBAAoB,CAAA;AACjD,IAAA,IAAI,IAAA,CAAK,cAAA,EAAgB,IAAA,CAAK,WAAA,CAAY,WAAA,GAAc,EAAA;AACxD,IAAA,IAAI,KAAK,aAAA,EAAe;AACtB,MAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,UAAA,CAAW,gBAAA,CAAiB,oBAAoB,CAAA,EAAG;AAC1E,QAAA,KAAA,CAAM,MAAA,EAAO;AAAA,MACf;AAAA,IACF;AAAA,EACF;AACF;ACleA,IAAM,eAAA,GAAkB,GAAA;AAExB,IAAM,gBAAA,GAAmB,GAAA;AAYzB,IAAM,UAAA,GAAa,CAAC,QAAA,EAAkB,MAAA,KACpC,QAAA,CAAS,OAAA,CAAQ,0BAAA,EAA4B,CAAC,KAAA,EAAO,KAAA,KAAkB,MAAA,CAAO,KAAK,KAAK,KAAK,CAAA;AAyDxF,IAAM,yBAAA,GAAN,MAAM,0BAAA,SAAkCA,UAAAA,CAAwB;AAAA,EACrE,OAAgB,OAAA,GAAU,CAAC,OAAA,EAAS,QAAQ,CAAA;AAAA,EAC5C,OAAgB,MAAA,GAAS;AAAA,IACvB,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACrC,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACpC,IAAA,EAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAClC,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,eAAA,EAAgB;AAAA;AAAA;AAAA;AAAA,IAIlD,QAAA,EAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,gBAAA,EAAiB;AAAA,IACpD,eAAA,EAAiB,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IAC7C,gBAAA,EAAkB,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA;AAAG,GAChD;AAAA,EACA,OAAO,MAAA,GAAS,CAAC,QAAQ,CAAA;AAAA;AAAA,EAazB,OAAgB,cAAA,GAAiB,GAAA;AAAA;AAAA,EAGjC,UAAmC,EAAC;AAAA,EAEpC,aAAA,GAAmD,IAAA;AAAA;AAAA,EAE1C,OAAA,uBAAc,GAAA,EAAY;AAAA;AAAA,EAE1B,OAAA,GAAU,IAAI,WAAA,EAAoB;AAAA,EAClC,OAAA,GAAU,IAAI,WAAA,EAAY;AAAA;AAAA,EAEnC,WAAA,GAAc,CAAA;AAAA;AAAA,EAEd,WAAA,GAA6B,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,kBAAA,GAA2B;AACzB,IAAA,IAAA,CAAK,OAAA,GAAU,uBAAA,CAAwB,IAAA,CAAK,WAAW,CAAA;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,qBAAA,GAA8B;AAC5B,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,IAAA,GAAO,CAAA,OAAQ,MAAA,EAAO;AAAA,EACzC;AAAA,EAES,OAAA,GAAgB;AAIvB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,aAAa,CAAA;AAC1C,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,gCAAgC,CAAA;AAC7D,IAAA,IAAI,IAAA,CAAK,eAAA,EAAiB,IAAA,CAAK,YAAA,CAAa,WAAA,GAAc,EAAA;AAI1D,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,QAAQ,CAAA;AACpD,IAAA,IAAI,KAAK,YAAA,EAAc;AAIrB,MAAA,IAAA,CAAK,aAAA,GAAgB,2BAAA;AAAA,QACnB,EAAE,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,GAAG,KAAK,OAAA,EAAQ;AAAA,QAC9C;AAAA;AAAA;AAAA,UAGE,UAAU,MAAM;AACd,YAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,gCAAA,EAAkC,MAAM,CAAA;AAAA,UACpE,CAAA;AAAA,UACA,QAAA,EAAU,CAAC,IAAA,KAAkB,IAAA,CAAK,YAAY,IAAI;AAAA;AACpD,OACF;AAAA,IACF;AAAA,EACF;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,OAAA,CAAQ,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,QAAQ,CAAA;AACvD,IAAA,IAAA,CAAK,eAAe,WAAA,EAAY;AAChC,IAAA,IAAA,CAAK,aAAA,GAAgB,IAAA;AACrB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,QAAQ,QAAA,EAAS;AACtB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AACnB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,aAAa,CAAA;AAC1C,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,gCAAgC,CAAA;AAC7D,IAAA,IAAI,IAAA,CAAK,eAAA,EAAiB,IAAA,CAAK,YAAA,CAAa,WAAA,GAAc,EAAA;AAC1D,IAAA,IAAA,CAAK,WAAA,GAAc,CAAA;AAAA,EACrB;AAAA;AAAA,EAGS,WAAW,MAAY;AAI9B,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,SAAA,EAAW;AACpC,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,IAAI,GAAA,GAAM,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,SAAA,EAAW;AAC7C,IAAA,IAAA,CAAK,WAAA,GAAc,GAAA;AACnB,IAAA,IAAA,CAAK,cAAc,OAAA,CAAQ,QAAA,EAAU,EAAE,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAC/D,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,IAAA,EAAqB;AAC/B,IAAA,MAAM,OAAQ,IAAA,EAAoC,IAAA;AAClD,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,SAAS,EAAA,IAAM,IAAA,KAAS,KAAK,SAAA,EAAW;AAExE,IAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,CAAK,OAAA,CAAQ,IAAI,IAAI,CAAA;AACpC,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,IAAI,CAAA;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,IAAA,EAAM,MAAM,KAAK,QAAA,CAAS,IAAI,CAAA,EAAG,IAAA,CAAK,QAAQ,CAAA;AAC/D,IAAA,IAAI,KAAA,OAAY,OAAA,EAAQ;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,IAAI,QAAA,GAAmB;AACrB,IAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,IAAA,OAAO,MAAA,CAAO,SAAS,QAAQ,CAAA,IAAK,YAAY,CAAA,IAAK,QAAA,IAAY,qBAC7D,QAAA,GACA,eAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,SAAA,GAAoB;AACtB,IAAA,MAAM,WAAW,IAAA,CAAK,aAAA;AACtB,IAAA,OAAO,OAAO,QAAA,CAAS,QAAQ,CAAA,IAAK,QAAA,IAAY,IAAI,QAAA,GAAW,gBAAA;AAAA,EACjE;AAAA,EAEA,SAAS,IAAA,EAAoB;AAC3B,IAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,EACf;AAAA;AAAA,EAGA,OAAA,GAAgB;AACd,IAAA,MAAM,KAAA,GAAQ,KAAK,MAAA,EAAO;AAC1B,IAAA,IAAA,CAAK,UAAU,KAAK,CAAA;AACpB,IAAA,IAAA,CAAK,SAAS,QAAA,EAAU,EAAE,QAAQ,EAAE,KAAA,IAAS,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAU,KAAA,EAAuB;AAC/B,IAAA,IAAI,KAAK,WAAA,KAAgB,IAAA,OAAW,OAAA,CAAQ,KAAA,CAAM,KAAK,WAAW,CAAA;AAClE,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AAExB,IAAA,MAAM,UACJ,KAAA,CAAM,MAAA,KAAW,CAAA,GACb,YAAA,CAAa,KAAK,oBAAA,EAAsB,EAAE,IAAA,EAAM,KAAA,CAAM,CAAC,CAAA,IAAK,EAAA,EAAI,CAAA,GAChE,YAAA,CAAa,KAAK,qBAAA,EAAuB;AAAA,MACvC,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAAA,MACtB,OAAO,KAAA,CAAM;AAAA,KACd,CAAA;AAEP,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,MAAM;AACxC,MAAA,QAAA,CAAS,OAAO,CAAA;AAChB,MAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AAAA,IACrB,CAAA,EAAG,2BAA0B,cAAc,CAAA;AAAA,EAC7C;AAAA;AAAA,EAGA,MAAA,GAAmB;AACjB,IAAA,MAAM,KAAA,GAAQ,CAAC,GAAG,IAAA,CAAK,OAAO,CAAA;AAC9B,IAAA,IAAA,CAAK,QAAQ,YAAA,CAAa,aAAA,EAAe,MAAM,MAAA,GAAS,CAAA,GAAI,SAAS,OAAO,CAAA;AAC5E,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,IAAA,CAAK,YAAA,CAAa,WAAA,GAAc,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA;AAAA,IACrD;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,KAAA,EAAyB;AAChC,IAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,EAAA;AAC/B,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AAC9B,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,MAAA,MAAMC,SAAAA,GAAW,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,GAAA;AAC3C,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,CAAC,CAAA,IAAK,EAAA;AACzB,MAAA,OAAOA,SAAAA,GAAW,WAAWA,SAAAA,EAAU,EAAE,MAAM,CAAA,GAAI,GAAG,IAAI,CAAA,gBAAA,CAAA;AAAA,IAC5D;AACA,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,IAAA;AAC3C,IAAA,OAAO,QAAA,GACH,UAAA,CAAW,QAAA,EAAU,EAAE,OAAO,MAAA,EAAQ,KAAA,EAAO,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA,EAAG,CAAA,GACnE,GAAG,MAAM,CAAA,iBAAA,CAAA;AAAA,EACf;AACF;;;AClRO,IAAM,gBAAA,GAAmB;AAAA,EAC9B,sBAAA,EAAwB,qBAAA;AAAA,EACxB,kBAAA,EAAoB,kBAAA;AAAA,EACpB,0BAAA,EAA4B;AAC9B;AAMO,SAAS,cAAc,WAAA,EAAgC;AAC5D,EAAA,KAAA,MAAW,CAAC,UAAA,EAAY,UAAU,KAAK,MAAA,CAAO,OAAA,CAAQ,gBAAgB,CAAA,EAAG;AACvE,IAAA,WAAA,CAAY,QAAA,CAAS,YAAY,UAAU,CAAA;AAAA,EAC7C;AACF","file":"index.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/** Digits plus the characters that may separate them inside one number. */\nconst TOKEN_BODY = /\\d[\\d.,'’’    _]*\\d|\\d/;\n/** A `-` opens a token only against one of these on its left. */\nconst SIGN_OPENER = /[\\s([]/;\n\n/**\n * Reads the integer that a human-formatted string displays.\n *\n * Takes the first numeric token, drops group separators, and truncates any\n * fractional part toward zero — so `\"1,200 users\"`, `\"$1,299.99\"` and\n * `\"1.299,99\"` read 1200, 1299 and 1299 without asking the platform which\n * locale is in play. A separator counts as grouping only where the digits that\n * follow it form a run of exactly three and the leading run could head a\n * grouped number; anything else is the decimal point, and the digits after it\n * are the fraction.\n *\n * `-` counts as a sign only where it opens the token — at the start of the\n * string, or after whitespace or an opening bracket — so a hyphen inside a\n * label (`\"Sign-ups: 1,200\"`, `\"Top-10 users\"`) never becomes one.\n *\n * `\"1.200\"` is genuinely ambiguous (1200 grouped, 1.2 with a decimal point) and\n * reads as 1200: display numbers group far more often than they carry a\n * fraction in exactly three digits.\n *\n * Returns `null` when the string holds no digits at all. What that means is the\n * caller's to decide — starting nothing, or falling back to a base value.\n */\nexport function authoredInteger(text: string): number | null {\n const firstDigit = /\\d/.exec(text);\n if (firstDigit === null) return null;\n\n const at = firstDigit.index;\n const signed =\n at > 0 && text[at - 1] === \"-\" && (at === 1 || SIGN_OPENER.test(text[at - 2] as string));\n\n const body = TOKEN_BODY.exec(text.slice(at)) as RegExpExecArray;\n const runs = body[0].split(/\\D+/);\n const separators = body[0].match(/\\D+/g) ?? [];\n\n // The leading run heads a grouped number only when it is short enough to be a\n // group itself and carries no leading zero; \"0.500\" and \"012,3\" are decimals.\n const lead = runs[0] as string;\n const heads = lead.length <= 3 && !lead.startsWith(\"0\");\n let digits = lead;\n for (let i = 1; heads && i < runs.length; i += 1) {\n if (separators[i - 1]?.length !== 1 || runs[i]?.length !== 3) break;\n digits += runs[i];\n }\n\n const magnitude = Number.parseInt(digits, 10);\n return signed ? -magnitude || 0 : magnitude;\n}\n","/**\n * Shared bookkeeping for updates that must wait until an element loses focus.\n *\n * A control that becomes redundant while the user is standing on it cannot be\n * removed from the page immediately: hiding a focused element (or turning it\n * into a native `disabled` one) drops focus to `<body>`, stranding a keyboard\n * user mid-widget. The fix every affected controller reached for is the same —\n * keep the element reachable, hold the destructive update, listen for `blur`,\n * and apply it then.\n *\n * {@link BlurDeferral} owns *only* the registry part of that: which elements are\n * currently holding an update back, attaching and detaching the `blur` listener,\n * and guaranteeing teardown. It is deliberately **policy-free** — what the\n * interim state looks like (`aria-disabled`, an ownership marker, nothing at\n * all), and what the deferred update actually is, stay in the controller, which\n * receives the element back through its release callback.\n *\n * Three contracts are worth stating up front, because each one is a hazard the\n * consumers would otherwise hit:\n *\n * - **The release callback fires only on a real `blur`.** `release()` /\n * `releaseAll()` *cancel* a deferral; they do not complete it. Conflating the\n * two makes `overflow-indicator` disable a button that just left the boundary.\n * - **`elements` is a snapshot.** Iterating the live key set while releasing\n * inside the loop is the failure this registry exists to prevent.\n * - **The entry is detached before the callback runs**, so a controller that\n * decides the update is still unsafe can simply defer again.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and contracts belong on the members.\n *\n * @example\n * ```ts\n * readonly #deferred = new BlurDeferral<HTMLElement>((trigger) => {\n * if (this.#connected) this.#evaluate();\n * });\n *\n * #hide(trigger: HTMLElement) {\n * if (document.activeElement === trigger) {\n * this.#deferred.deferOnly(trigger); // stays visible until blur\n * return;\n * }\n * this.#deferred.releaseAll();\n * trigger.hidden = true;\n * }\n *\n * disconnect() {\n * this.#deferred.releaseAll();\n * }\n * ```\n */\nexport class BlurDeferral<T extends HTMLElement = HTMLElement> {\n /** Elements currently holding an update back, mapped to their `blur` listener. */\n readonly #pending = new Map<T, () => void>();\n /** Called after a pending element blurs and has been detached. */\n readonly #onRelease: (element: T) => void;\n\n /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */\n constructor(onRelease: (element: T) => void) {\n this.#onRelease = onRelease;\n }\n\n /** Number of elements currently holding an update back. */\n get size(): number {\n return this.#pending.size;\n }\n\n /** Snapshot of the pending elements, safe to iterate while releasing them. */\n get elements(): T[] {\n return [...this.#pending.keys()];\n }\n\n /** Whether `element` is currently holding an update back. */\n has(element: T): boolean {\n return this.#pending.has(element);\n }\n\n /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */\n defer(element: T): void {\n if (this.#pending.has(element)) return;\n const onBlur = (): void => {\n this.#detach(element);\n this.#onRelease(element);\n };\n this.#pending.set(element, onBlur);\n element.addEventListener(\"blur\", onBlur);\n }\n\n /** Defers `element` as the only pending entry, cancelling any others. */\n deferOnly(element: T): void {\n for (const pending of this.elements) {\n if (pending !== element) this.#detach(pending);\n }\n this.defer(element);\n }\n\n /** Cancels `element`'s deferral without completing it; no-ops when not pending. */\n release(element: T): void {\n this.#detach(element);\n }\n\n /** Cancels every deferral without completing any of them. */\n releaseAll(): void {\n for (const element of this.elements) this.#detach(element);\n }\n\n /** Removes the `blur` listener for `element` and forgets it. */\n #detach(element: T): void {\n const onBlur = this.#pending.get(element);\n if (onBlur) element.removeEventListener(\"blur\", onBlur);\n this.#pending.delete(element);\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { createConsumer } from \"@rails/actioncable\";\n\n/**\n * Minimal structural view of an Action Cable subscription — the two members the\n * server-bound controllers use. Keeping our own narrow interface (instead of\n * re-exporting the full `@rails/actioncable` types) lets consumers hand us any\n * structurally compatible object, including test doubles.\n */\nexport interface CableSubscription {\n /** Invokes a channel action on the server (`ChannelName#action`). */\n perform(action: string, data?: Record<string, unknown>): void;\n /** Cancels the subscription (the channel's `unsubscribed` runs server-side). */\n unsubscribe(): void;\n}\n\n/** The mixin a controller passes to {@link CableConsumer.subscriptions}' create. */\nexport interface CableSubscriptionMixin {\n /** Called once the subscription is confirmed (`perform` is deliverable). */\n connected?(): void;\n /**\n * Called when the connection drops — perform() is silently undeliverable\n * until Action Cable reconnects and re-confirms (`connected` fires again).\n */\n disconnected?(): void;\n /** Called when the server refuses the subscription (it will never confirm). */\n rejected?(): void;\n /** Called with each broadcast the channel transmits to this client. */\n received?(data: unknown): void;\n}\n\n/** Minimal structural view of an Action Cable consumer (the websocket owner). */\nexport interface CableConsumer {\n subscriptions: {\n create(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n ): CableSubscription;\n };\n}\n\n/**\n * Parses the extra identifier parameters a subscription is declared with.\n *\n * Stimulus offers an `Object` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the controller's `connect()` — and rethrows on\n * malformed text. The throw propagates out of the observer, so a single\n * unparseable attribute stops the subscription from ever being created and\n * leaves a controller that is connected but deaf, with nothing on the element to\n * say so. Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares: the identifier falls back\n * to the channel alone. The attribute text is identical either way\n * (`'{\"room\":\"chat_42\"}'`), so markup does not change with the declaration.\n *\n * Only a JSON object survives. An array, a bare number, a string or `null` cannot\n * name identifier parameters, and spreading one into the identifier would either\n * do nothing or produce index keys no channel can reproduce server-side.\n */\nexport function parseSubscriptionParams(raw: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/**\n * The shared consumer. Deliberately module-scoped: an Action Cable consumer is\n * *connection infrastructure* (one websocket per app, the Rails\n * `channels/consumer.js` convention), not UI state — it must survive Turbo\n * navigations, so it is deliberately not rebuilt per `connect()` the way\n * DOM-derived state is.\n */\nlet sharedConsumer: CableConsumer | null = null;\n\n/**\n * Replaces (or clears, with `null`) the shared Action Cable consumer.\n *\n * Call this once at boot when the app already owns a consumer (the usual\n * `app/javascript/channels/consumer.js`), so the server-bound controllers reuse\n * its websocket instead of opening a second one. Tests use it to inject a\n * double. With `null`, the next {@link getCableConsumer} lazily re-creates one.\n */\nexport function setCableConsumer(consumer: CableConsumer | null): void {\n sharedConsumer = consumer;\n}\n\n/**\n * The shared Action Cable consumer, lazily created on first use via\n * `createConsumer()` (which reads the standard `action_cable_meta_tag` URL).\n */\nexport function getCableConsumer(): CableConsumer {\n if (!sharedConsumer) sharedConsumer = createConsumer();\n return sharedConsumer;\n}\n\n/**\n * A {@link CableSubscription} that also tracks its confirmation lifecycle.\n * Action Cable silently drops a `perform()` sent before the subscription is\n * confirmed or while the connection is down — every server-bound controller\n * must therefore gate its sends (and their local side effects: optimistic\n * updates, throttle bookkeeping) on {@link confirmed}.\n */\nexport interface ConfirmedCableSubscription extends CableSubscription {\n /** True while the subscription is confirmed — `perform()` is deliverable. */\n readonly confirmed: boolean;\n /** True once the server refused the subscription (it will never confirm). */\n readonly rejected: boolean;\n}\n\n/** One caller of {@link createConfirmedSubscription} riding a shared wire subscription. */\ninterface Member {\n readonly mixin: CableSubscriptionMixin;\n /**\n * True while a lifecycle callback the member missed (the identifier was already\n * confirmed or refused when it joined) is still owed to it. Any callback that arrives\n * from the wire in the meantime supersedes the owed one.\n */\n catchUp: boolean;\n}\n\n/** The one wire subscription an identifier has on a consumer, plus who rides it. */\ninterface SharedSubscription {\n readonly subscription: CableSubscription;\n readonly members: Set<Member>;\n confirmed: boolean;\n rejected: boolean;\n}\n\n/**\n * Wire subscriptions by identifier, per consumer. Keyed weakly so a replaced consumer\n * (see {@link setCableConsumer}) takes its bookkeeping with it.\n */\nconst sharedSubscriptions = new WeakMap<CableConsumer, Map<string, SharedSubscription>>();\n\n/**\n * The identifier Action Cable derives for a channel descriptor: the JSON of the\n * params object, a bare channel name standing for `{ channel }`.\n */\nexport function identifierOf(channel: string | Record<string, unknown>): string {\n return JSON.stringify(typeof channel === \"string\" ? { channel } : channel);\n}\n\n/**\n * Creates a subscription on the shared consumer with confirmation tracking\n * layered over the caller's mixin: `connected` / `disconnected` / `rejected`\n * flip the {@link ConfirmedCableSubscription.confirmed} flag *before* the\n * caller's own handler runs (so a handler reading `subscription.confirmed`\n * sees the post-transition state), and `received` passes straight through.\n *\n * **Callers with the same identifier (channel + params) share one wire\n * subscription.** The server confirms an identifier once and silently ignores a\n * repeated `subscribe` for it, so a second wire subscription would never be\n * confirmed — its sends would stay gated and the client would keep re-sending\n * `subscribe` until the next reconnect. Instead every caller is a member of the\n * identifier's one subscription: lifecycle callbacks and broadcasts fan out to all\n * members, a member joining an already confirmed (or refused) identifier is told\n * so on the next microtask — after its caller has stored the returned\n * subscription, the way a callback from the wire would arrive — and the wire is\n * unsubscribed when the last member leaves.\n *\n * This is deliberately a *tracker*, not an automatic `perform()` gate: the\n * controllers must skip the local side effects that surround a send (an\n * optimistic DOM bump, a throttle timestamp) together with the send itself,\n * which only the call site can decide — so they check `confirmed` and bail\n * before any of it.\n */\nexport function createConfirmedSubscription(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n): ConfirmedCableSubscription {\n const consumer = getCableConsumer();\n let registry = sharedSubscriptions.get(consumer);\n if (!registry) {\n registry = new Map();\n sharedSubscriptions.set(consumer, registry);\n }\n const identifier = identifierOf(channel);\n const member: Member = { mixin, catchUp: false };\n\n let shared = registry.get(identifier);\n if (!shared) {\n shared = openSharedSubscription(consumer, channel);\n registry.set(identifier, shared);\n } else if (shared.rejected || shared.confirmed) {\n // Missed lifecycle: deliver it once the caller holds the returned subscription.\n const owed = shared;\n member.catchUp = true;\n queueMicrotask(() => {\n if (!member.catchUp) return;\n member.catchUp = false;\n if (owed.rejected) mixin.rejected?.();\n else mixin.connected?.();\n });\n }\n shared.members.add(member);\n const owner = shared;\n const registered = registry;\n let active = true;\n\n return {\n perform: (action, data) => owner.subscription.perform(action, data),\n unsubscribe: () => {\n if (!active) return;\n active = false;\n // Nothing is owed to a member that left: a catch-up still queued must not fire.\n member.catchUp = false;\n owner.members.delete(member);\n if (owner.members.size === 0) {\n registered.delete(identifier);\n owner.subscription.unsubscribe();\n }\n },\n get confirmed() {\n return owner.confirmed;\n },\n get rejected() {\n return owner.rejected;\n },\n };\n}\n\n/** Opens the wire subscription for an identifier and wires the fan-out to its members. */\nfunction openSharedSubscription(\n consumer: CableConsumer,\n channel: string | Record<string, unknown>,\n): SharedSubscription {\n const members = new Set<Member>();\n // A callback from the wire supersedes whatever a late member was still owed.\n const fanOut = (deliver: (mixin: CableSubscriptionMixin) => void): void => {\n for (const member of [...members]) {\n member.catchUp = false;\n deliver(member.mixin);\n }\n };\n const shared: SharedSubscription = {\n members,\n confirmed: false,\n rejected: false,\n subscription: consumer.subscriptions.create(channel, {\n // A refusal is final: Action Cable never confirms a rejected subscription,\n // so a late connected/disconnected (only possible from a misbehaving\n // consumer double) must not reopen the gate `rejected` promised shut.\n connected: () => {\n if (shared.rejected) return;\n shared.confirmed = true;\n fanOut((mixin) => mixin.connected?.());\n },\n disconnected: () => {\n if (shared.rejected) return;\n shared.confirmed = false;\n fanOut((mixin) => mixin.disconnected?.());\n },\n rejected: () => {\n shared.confirmed = false;\n shared.rejected = true;\n fanOut((mixin) => mixin.rejected?.());\n },\n received: (data: unknown) => fanOut((mixin) => mixin.received?.(data)),\n }),\n };\n return shared;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { authoredInteger } from \"../utils/authored_integer\";\nimport { BlurDeferral } from \"../utils/blur_deferral\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/**\n * Marker on triggers this controller disabled: `disabled` is a shared\n * attribute, so only marked ones are ever re-enabled — an authored-disabled\n * trigger stays untouched. The marker lives in the DOM rather than in a lease\n * held by the instance, so it survives a Turbo cache snapshot.\n */\nconst DISABLED_MARKER = \"data-live-counter-disabled\";\n\n/**\n * Headless **live counter** — a *server-bound* behavior: a number bound to an\n * Action Cable stream (likes, views, active users) that ticks across every\n * connected client. A local action applies an **optimistic** increment\n * immediately, then reconciles with the broadcast; the own echo of a delta\n * broadcast is deduped so the increment is never applied twice. Ships in the\n * opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * Markup contract (identifier: `stimeo--live-counter`):\n * <div data-controller=\"stimeo--live-counter\"\n * data-stimeo--live-counter-channel-value=\"LikesChannel\"\n * data-stimeo--live-counter-params-value='{\"post\":42}'\n * data-stimeo--live-counter-id-value=\"<%= SecureRandom.uuid %>\">\n * <span data-stimeo--live-counter-target=\"value\">128</span>\n * <button type=\"button\" data-action=\"click->stimeo--live-counter#increment\"\n * data-stimeo--live-counter-target=\"trigger\">♥</button>\n * </div>\n *\n * Wire contract — the broadcast is either **authoritative** or a **delta**:\n * `{ count: 129 }` sets the absolute value (naturally idempotent; preferred —\n * the server owns the number), while `{ delta: 1, by: \"17\" }` adds to it,\n * skipped when `by` matches this client's `id` **and its amount matches an\n * optimistic bump this client is still waiting to see echoed**. A counter\n * holding no such guess — a sibling on the page, this user's other tab, one\n * stepping by a different amount — applies the delta like any other, so every\n * display converges. Only whole numbers are accepted: the\n * display is the state, and a value that cannot be read back from text is not\n * a count. `increment` performs `increment` on the channel with `{ id, delta }`;\n * the server persists and broadcasts. Without an own `id` the delta echo is\n * indistinguishable from a foreign delta, so the optimistic bump is skipped and\n * the broadcast applies the increment exactly once — set `id` (any per-client,\n * per-tab string, e.g. `SecureRandom.uuid`) for optimistic UX. Comparing `by`\n * to `id` is an echo guard, not authentication — identity belongs to the server.\n *\n * `change` dispatches `{ count }`.\n *\n * @remarks\n * Behavior only — the displayed number IS the state, and the **DOM is the\n * source of truth**: the server renders the initial count into the `value`\n * target (the element itself without one), so a Turbo cache restore needs no\n * reconciliation and `connect()` only (re)subscribes. Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * increments are dropped before confirmation AND while the connection is down\n * (`disconnected` shuts the gate until Action Cable re-confirms), so the\n * display never advances past what the server can receive. The gate reads the\n * declaration, not the subscription object: while a `channel` is declared it\n * stays shut until that subscription confirms, including the window where one\n * has yet to be opened. A refused\n * subscription publishes the `data-live-counter-rejected` hook (cleared on\n * `connect()` — rejection is transient server state) so the consumer's CSS can\n * disable the trigger. Optional `trigger` targets make that declarative: they\n * carry the real `disabled` attribute exactly while an increment would be\n * dropped (before confirmation, during an outage, after a rejection) — no CSS\n * required, and a disabled form control is announced as such by AT. An\n * authored `disabled` (set by the consumer for its own reasons) is respected:\n * only a disabled this controller applied — tracked via a marker attribute —\n * is ever lifted, and it is given back when the controller or the target goes\n * away. A trigger that holds focus keeps it: the disable waits for it to blur,\n * since taking focus away mid-outage strands a keyboard user. Channel-less\n * (local-only) counters never disable their triggers. `announceText` is opt-in\n * and empty by default; when set, reconciled counts reach the page's shared\n * announcer, debounced so a burst is one message. The subscription follows the\n * declaration — a `channel` or `params` change moves it — and is released on\n * `disconnect()` (Turbo navigation included) along with any pending\n * announcement.\n */\nexport class LiveCounterController extends Controller<HTMLElement> {\n static override targets = [\"value\", \"trigger\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n announceText: { type: String, default: \"\" },\n };\n /** Collapses a burst of broadcasts into one announcement. */\n static #announceDelay = 200;\n static actions = [\"increment\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly hasValueTarget: boolean;\n declare readonly valueTarget: HTMLElement;\n declare readonly triggerTargets: HTMLElement[];\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare announceTextValue: string;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n #connected = false;\n /**\n * The optimistic bumps this client has yet to see echoed back, in the order it\n * sent them. An echo belongs to the send that caused it, and the amount is what\n * names that send: a counter that guessed nothing — or guessed a different\n * amount — applies the delta like any other, so siblings and other tabs\n * converge whatever step each of them uses.\n */\n readonly #outstanding: number[] = [];\n /** Holds a disable back while the trigger owns focus, so focus is never taken away. */\n readonly #focusedTriggers = new BlurDeferral((trigger) => this.#syncTrigger(trigger));\n readonly #timers = new SafeTimeout();\n\n /**\n * Re-parses the identifier parameters and moves the subscription to the\n * identifier they now name.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n this.#resubscribe();\n }\n\n /** Moves the subscription when the declaration names a different channel. */\n channelValueChanged(): void {\n this.#resubscribe();\n }\n\n override connect(): void {\n this.#connected = true;\n this.#subscribe();\n }\n\n /**\n * Opens the subscription the declaration names, and reflects the gate.\n *\n * The rejected hook is transient server state: a Turbo cache snapshot must not\n * resurrect it — the fresh subscription re-decides it. An outstanding guess\n * belongs to the identifier that is going away, so it is dropped with it.\n */\n #subscribe(): void {\n this.element.removeAttribute(\"data-live-counter-rejected\");\n this.#outstanding.length = 0;\n if (this.channelValue) {\n this.#subscription = this.#open();\n }\n // Also covers a Turbo cache restore that snapshotted a disabled trigger:\n // the fresh (unconfirmed or absent) subscription re-decides the state.\n this.#syncTriggers();\n }\n\n /**\n * Opens the wire subscription, or returns null when it cannot be opened.\n *\n * A consumer that cannot be built (no Action Cable meta tag, a boot-order\n * mistake) would otherwise throw out of `connect()` and leave the element\n * half-wired: no gate reflected, and a trigger that looks live. The gate reads\n * the declaration, so a null subscription keeps it shut and the display never\n * moves past what the server can receive.\n */\n #open(): ConfirmedCableSubscription | null {\n try {\n return createConfirmedSubscription(this.#descriptor, {\n connected: () => this.#syncTriggers(),\n // A drop closes the send window (the shared subscription tracks it)\n // until Action Cable reconnects and re-confirms — an increment during\n // the outage would bump the display while its perform() is silently\n // discarded by the closed socket.\n disconnected: () => this.#syncTriggers(),\n // The server refused the subscription (auth, bad params): the gate\n // stays shut for good, and the hook lets the consumer's CSS disable\n // or hide the trigger instead of leaving a silently dead button.\n rejected: () => {\n this.element.setAttribute(\"data-live-counter-rejected\", \"true\");\n this.#syncTriggers();\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n } catch (error) {\n console.warn(\"Stimeo UI: the live counter could not open its subscription.\", error);\n return null;\n }\n }\n\n /**\n * The identifier this declaration names.\n *\n * `channel` comes first so the identifier text matches what Action Cable\n * builds, and a `channel` key inside `params` cannot displace the declared\n * one — `params` names *additional* identifier parameters.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _ignored, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /** Moves to the identifier the declaration now names. */\n #resubscribe(): void {\n if (!this.#connected) return; // value callbacks run before connect()\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#subscribe();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#timers.clearAll();\n this.#focusedTriggers.releaseAll();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.element.removeAttribute(\"data-live-counter-rejected\");\n // `disabled` is borrowed while the gate is shut: give back what is ours.\n for (const trigger of this.triggerTargets) this.#release(trigger);\n }\n\n /** Late-added triggers (e.g. via a Turbo Stream) pick up the current gate. */\n triggerTargetConnected(target: HTMLElement): void {\n this.#syncTrigger(target);\n }\n\n /** A trigger that stops being one keeps nothing this controller lent it. */\n triggerTargetDisconnected(target: HTMLElement): void {\n this.#focusedTriggers.release(target);\n this.#release(target);\n }\n\n /**\n * Optimistic local increment: bumps the display immediately, then asks the\n * server to persist and broadcast. The step comes from the action param\n * (`data-stimeo--live-counter-delta-param`), default 1. Bound via `data-action`.\n */\n increment(event?: Event & { params?: { delta?: number } }): void {\n // Before the confirmed subscription — and while the connection is down —\n // a perform() is silently dropped by Action Cable: bumping the display\n // then would diverge from the server, so the whole increment is dropped\n // (screen and server stay consistent). A declared channel with no live\n // subscription is the same window, so the gate reads the declaration.\n if (!this.#ready) return;\n // Action params arrive as authored strings; normalize and ignore garbage.\n const raw = Number(event?.params?.delta ?? 1);\n const delta = Number.isSafeInteger(raw) ? raw : 1;\n // Channel-less (local-only) counters have no echo coming, so they always bump\n // and keep no guess: nothing would ever cancel one. Without an own `id` the\n // delta echo cannot be deduped either, so the optimistic bump is skipped and\n // the broadcast applies the increment exactly once.\n if (this.#local) {\n this.#write(this.#current + delta);\n } else if (this.idValue !== \"\") {\n this.#outstanding.push(delta);\n this.#write(this.#current + delta);\n }\n this.#subscription?.perform(\"increment\", { id: this.idValue, delta });\n }\n\n /**\n * Reconciles a broadcast: absolute `count` wins; the echo of this client's own\n * guess is skipped.\n *\n * Only whole numbers reach the display. The wire carries values another client\n * named and the server relayed, and the display is the state this controller\n * reads back — a fraction, an infinity or a NaN would be written as text that\n * reads back as a different number, or as none at all.\n */\n #onReceived(data: unknown): void {\n const message = data as { count?: unknown; delta?: unknown; by?: unknown } | null;\n if (Number.isSafeInteger(message?.count)) {\n // Server truth settles every guess this client was still waiting on.\n this.#outstanding.length = 0;\n this.#reconcile(message?.count as number);\n return;\n }\n if (Number.isSafeInteger(message?.delta)) {\n const delta = message?.delta as number;\n const own =\n typeof message?.by === \"string\" && message.by !== \"\" && message.by === this.idValue;\n // An echo cancels the one guess it was the answer to, matched by amount. A\n // counter holding no guess of that size — a sibling on the page, this\n // user's other tab, a step someone else chose — applies it.\n const guess = own ? this.#outstanding.indexOf(delta) : -1;\n if (guess !== -1) {\n this.#outstanding.splice(guess, 1);\n return;\n }\n this.#reconcile(this.#current + delta);\n }\n }\n\n /** True when no channel is declared: the local-only mode, with no server to diverge from. */\n get #local(): boolean {\n return this.channelValue === \"\";\n }\n\n /** True while an increment would go through (local-only counters always are). */\n get #ready(): boolean {\n return this.#local || (this.#subscription?.confirmed ?? false);\n }\n\n /**\n * Reflects the send gate onto the optional `trigger` targets as the real\n * `disabled` attribute — the declarative alternative to styling off the\n * `data-live-counter-rejected` hook (a disabled control is also skipped by\n * keyboard focus and announced by AT, which CSS alone cannot do).\n */\n #syncTriggers(): void {\n for (const trigger of this.triggerTargets) this.#syncTrigger(trigger);\n }\n\n /**\n * `disabled` is a shared attribute: only disable what is currently enabled\n * (marking it ours), and only lift a disabled carrying our marker — so an\n * authored-disabled trigger (\"disabled until valid\", say) is never\n * re-enabled by the gate. A marked disabled restored from a Turbo cache\n * snapshot is recognized as ours and lifted once the gate opens.\n */\n #syncTrigger(trigger: HTMLElement): void {\n if (this.#ready) {\n this.#focusedTriggers.release(trigger);\n this.#release(trigger);\n return;\n }\n if (trigger.hasAttribute(\"disabled\")) return;\n // Disabling the control that holds focus drops it to the document body,\n // stranding a keyboard user mid-interaction. Hold the disable until it\n // blurs; the gate already refuses the increment either way.\n if (trigger.contains(document.activeElement)) {\n this.#focusedTriggers.defer(trigger);\n return;\n }\n trigger.setAttribute(\"disabled\", \"\");\n trigger.setAttribute(DISABLED_MARKER, \"\");\n }\n\n /** Gives back a `disabled` this controller lent, leaving an authored one alone. */\n #release(trigger: HTMLElement): void {\n if (!trigger.hasAttribute(DISABLED_MARKER)) return;\n trigger.removeAttribute(\"disabled\");\n trigger.removeAttribute(DISABLED_MARKER);\n }\n\n /** The displayed element: the `value` target, else the controller element. */\n get #display(): HTMLElement {\n return this.hasValueTarget ? this.valueTarget : this.element;\n }\n\n /** The current count, parsed from the DOM (the single source of truth). */\n get #current(): number {\n // A formatted server-rendered value (\"1,200 likes\") reads as the number it\n // displays; a display holding no number at all counts as zero.\n return authoredInteger(this.#display.textContent ?? \"\") ?? 0;\n }\n\n /**\n * Writes a whole count — every caller has already established that it is one —\n * and reports whether the display actually moved.\n */\n #write(count: number): boolean {\n if (count === this.#current) return false;\n this.#display.textContent = String(count);\n this.dispatch(\"change\", { detail: { count } });\n return true;\n }\n\n /**\n * Settles the display on a count the server stated, announcing what it settled\n * on. Announcing the optimistic bump instead would read a guess out and then\n * correct it, so the announcement rides this path only.\n */\n #reconcile(count: number): void {\n if (this.#write(count)) this.#announce(count);\n }\n\n /**\n * Hands a reconciled count to the page's shared announcer.\n *\n * Debounced: a burst of broadcasts is one announcement, not one per message,\n * and only the last one is ever sent. Wording comes from the consumer and\n * {@link announce} drops an empty message, so an undeclared template announces\n * nothing at all — a count that ticks constantly should not interrupt a reader\n * unless the page asked for it.\n */\n #announce(count: number): void {\n this.#timers.clearAll();\n this.#timers.set(() => {\n announce(fillTemplate(this.announceTextValue, { count }));\n }, LiveCounterController.#announceDelay);\n }\n}\n","import { SafeTimeout } from \"./safe_timeout\";\n\n/**\n * Keeps at most one pending timer per key, and releases them all together.\n *\n * A region, a row, a message or a speaker that is armed again while its earlier\n * timer is still pending must end up with exactly one timer deciding when its\n * state ends. Two timers on one key means the earlier deadline wins: it cuts the\n * new emphasis short, clears text written after it, or drops a speaker who is\n * still typing — and then reports an ending for a state that is already gone.\n * Arming through this registry cancels the key's earlier timer in the same step,\n * and no timer id ever reaches the caller.\n *\n * An entry lives exactly as long as its timer: it goes as the timer fires, and\n * the whole ledger goes on `clearAll()`. That is a correctness rule, not\n * tidiness — a platform may hand an id released by `clearTimeout` to the next\n * timer it creates, so an entry that outlived its timer would let a later\n * `clear()` on that key cancel whatever timer now holds the id, on some other\n * key entirely.\n *\n * The ledger is a plain `Map`, and the key may be anything. Weak keys would\n * retain nothing less: while a timer is pending the platform already holds the\n * key through the callback, and the entry is gone the moment the timer is.\n *\n * Scope is the one-per-key timer only. What the timer means, and the roster of\n * keys it is armed for, stay with the consumer, so a second kind of timer on the\n * same keys (a transition wait, an animation frame) is a second registry, and a\n * timer that belongs to no key stays on a plain `SafeTimeout`.\n *\n * @example\n * ```ts\n * readonly #expiry = new KeyedTimers<string>();\n *\n * #onSignal(name: string): void {\n * this.#expiry.set(name, () => this.#drop(name), this.#timeout);\n * }\n *\n * disconnect(): void {\n * this.#expiry.clearAll();\n * }\n * ```\n */\nexport class KeyedTimers<K> {\n readonly #timers = new SafeTimeout();\n\n /** The pending timer of each key; an entry lives exactly as long as its timer. */\n readonly #pending = new Map<K, number>();\n\n /**\n * Arms `callback` after `delay` ms for `key`, cancelling the timer `key` had\n * pending. The entry is dropped before the callback runs, so the callback sees\n * the key unarmed and may arm it again for the next round.\n */\n set(key: K, callback: () => void, delay: number): void {\n this.clear(key);\n const id = this.#timers.set(() => {\n this.#pending.delete(key);\n callback();\n }, delay);\n this.#pending.set(key, id);\n }\n\n /**\n * Cancels `key`'s pending timer, if it has one.\n *\n * A timer id is a positive integer, so `-1` stands for \"nothing pending\" and\n * the registry ignores an id it does not own — the unarmed case needs no\n * branch of its own, and no other key's timer can be reached from here.\n */\n clear(key: K): void {\n this.#timers.clear(this.#pending.get(key) ?? -1);\n this.#pending.delete(key);\n }\n\n /**\n * Cancels every pending timer and forgets every key. Call this from a\n * controller's `disconnect()` so no timer, and no entry, outlives the element.\n */\n clearAll(): void {\n this.#timers.clearAll();\n this.#pending.clear();\n }\n\n /** Whether `key` has a timer pending. */\n has(key: K): boolean {\n return this.#pending.has(key);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { KeyedTimers } from \"../utils/keyed_timers\";\nimport { MAX_TIMER_DELAY_MS, SafeInterval, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** A peer currently present (another client in the same room). */\ninterface Peer {\n name: string;\n}\n\n/**\n * Minimum ms between outgoing beacons. The steady state is the `heartbeat`\n * interval; this guard only bounds the *extra* beacons sent in response to\n * newly seen peers (see the roster-convergence note in the class doc), so a\n * burst of joiners cannot make this client flood the channel.\n */\nconst BEACON_THROTTLE_MS = 2000;\n\n/** Heartbeat period used when the declared one names no interval. */\nconst DEFAULT_HEARTBEAT_MS = 15_000;\n\n/** Peer expiry used when the declared one names no delay. */\nconst DEFAULT_TIMEOUT_MS = 40_000;\n\n/**\n * How many connected controllers currently speak for each peer, keyed by the\n * channel identifier and the peer's own id.\n *\n * The leaving notice says that a *peer* is gone, so only the last element\n * claiming that peer may send it. The shared subscription counts the members of\n * a wire identifier, which is a different set: elements claiming other ids —\n * and the other cable controllers — ride the same wire, and letting them speak\n * for this peer would either drop a peer that is still here or keep a departed\n * one in every roster until it expires.\n */\nconst speakers = new Map<string, number>();\n\n/**\n * Headless **presence** — a *server-bound* behavior: online dots / a\n * \"who's viewing this\" stack, bound to an Action Cable channel. Like\n * `stimeo--typing-indicator`, the server stays a trivial rebroadcast channel\n * with **zero presence state**: every client heartbeats an `appear` beacon\n * (`{ id, name }`), and each client expires peers it has not heard from for\n * `timeout` ms. Leaving (`{ id, leaving: true }`) is broadcast best-effort on\n * `disconnect()` and on `pagehide` (tab close / hard navigation, where\n * `disconnect()` never runs); a lost notice is caught by the expiry. Ships in\n * the opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * The notice speaks for a *peer*, not for a wire subscription: on `disconnect()`\n * only the last element claiming a given `id` on a given identifier sends it, so a\n * sibling that stays mounted keeps the peer in every roster. `pagehide` takes the\n * whole page with it, so there every element sends and peers absorb the duplicates.\n *\n * Roster convergence: a late joiner would otherwise see peers only as their\n * next heartbeats arrive, so on hearing a beacon from an *unknown* peer, each\n * client re-announces itself (at most one such answer every 2s) — the\n * roster converges in one round-trip instead of one heartbeat period.\n *\n * Markup contract (identifier: `stimeo--presence`):\n * <div data-controller=\"stimeo--presence\"\n * data-stimeo--presence-channel-value=\"PresenceChannel\"\n * data-stimeo--presence-params-value='{\"room\":\"doc_7\"}'\n * data-stimeo--presence-id-value=\"17\" data-stimeo--presence-name-value=\"Alice\">\n * <span data-stimeo--presence-target=\"count\" data-other=\"%{count} viewing\"></span>\n * <ul data-stimeo--presence-target=\"list\"></ul>\n * <template data-stimeo--presence-target=\"template\">\n * <li><span data-presence-name></span></li>\n * </template>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class PresenceChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"presence:#{params[:room]}\"\n * def appear(data) = ActionCable.server.broadcast(\"presence:#{params[:room]}\",\n * { id: data[\"id\"], name: data[\"name\"], leaving: data[\"leaving\"] })\n * end\n *\n * `join` dispatches `{ id, name }`; `leave` dispatches `{ id }`; `change` dispatches\n * `{ users }`.\n *\n * @remarks\n * Behavior only — the dot/stack look is the consumer's CSS, keyed off the\n * `data-present` / `data-present-count` hooks. The roster counts and renders\n * **other** clients (`id` ≠ own `id`); rendering is optional: given a `list` +\n * `template` pair, one clone per peer is appended (elements marked\n * `data-presence-name` receive the name; the clone root is tagged\n * `data-presence-id`), and the `count` target renders through localizable\n * `data-zero` / `data-one` / `data-other` templates (`%{count}`). Richer\n * per-user rendering (avatars, links) belongs to the consumer via the `join` /\n * `leave` / `change` events or a server-rendered Turbo Stream. A `template` holds\n * one root element: the clone appended for a peer, and the node removed when the\n * peer goes, are that same element. Roster changes reach assistive tech only when\n * the consumer opts in through `announceJoinText` / `announceLeaveText` — reading\n * out every entry and exit of a busy room is noise, so silence is the default. The `id`\n * comparison is display-level echo suppression, not authentication — identity\n * belongs to the server. Sending tracks the full subscription lifecycle (via\n * the shared confirmation-aware subscription): beacons — heartbeats, the\n * convergence answer, the leaving notice — are dropped before confirmation and\n * while the connection is down, where Action Cable would discard them anyway\n * (and a discarded beacon must not burn the convergence throttle); `connected`\n * re-fires on every reconnect and force-beacons, so the roster self-heals. A\n * refused subscription publishes the `data-presence-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Presence is transient: `connect()` clears\n * whatever a Turbo cache snapshot preserved (hooks + rendered clones), renders\n * the known-empty count (the `data-present*` hooks stay absent until the first\n * beacon), and the stream re-populates; a `count` or `list` target swapped in by a\n * Stream or a morph arrives empty and is drawn from the roster as it connects; the subscription, heartbeat interval,\n * per-peer expiry timers, and the `pagehide` listener are all released on\n * `disconnect()` (Turbo navigation included).\n */\nexport class PresenceController extends Controller<HTMLElement> {\n static override targets = [\"count\", \"list\", \"template\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n heartbeat: { type: Number, default: DEFAULT_HEARTBEAT_MS },\n timeout: { type: Number, default: DEFAULT_TIMEOUT_MS },\n announceJoinText: { type: String, default: \"\" },\n announceLeaveText: { type: String, default: \"\" },\n };\n static events = [\"join\", \"leave\", \"change\"] as const;\n\n declare readonly hasCountTarget: boolean;\n declare readonly countTarget: HTMLElement;\n declare readonly hasListTarget: boolean;\n declare readonly listTarget: HTMLElement;\n declare readonly hasTemplateTarget: boolean;\n declare readonly templateTarget: HTMLTemplateElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare nameValue: string;\n declare heartbeatValue: number;\n declare timeoutValue: number;\n declare announceJoinTextValue: string;\n declare announceLeaveTextValue: string;\n\n /** Delay (ms) before one roster change is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Present peers keyed by id (insertion order = join order). */\n readonly #peers = new Map<string, Peer>();\n /** Each peer's auto-expiry timer, restarted by every beacon from that peer. */\n readonly #expiry = new KeyedTimers<string>();\n readonly #timers = new SafeTimeout();\n readonly #intervals = new SafeInterval();\n /** Epoch ms of the last outgoing beacon, for the convergence throttle. */\n #lastBeaconAt = 0;\n /** Pending trailing-edge convergence beacon (at most one queued). */\n #pendingBeacon: number | null = null;\n /** The one outstanding announcement, so a newer roster change supersedes it. */\n #announceId: number | null = null;\n /** The `speakers` key this element holds, recomputed on every `connect()`. */\n #speakerKey = \"\";\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * The identifier this declaration names. `params` *adds* to the identifier, so a\n * `channel` key inside it names an extra parameter and never replaces the channel\n * the element declares.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _declared, ...rest } = this.#params;\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * The beacon period, in ms: a finite, positive number a timer can hold. Anything\n * else names no interval — `setInterval` reads `NaN`, a negative value, `Infinity`\n * and a value past {@link MAX_TIMER_DELAY_MS} alike as \"as often as possible\", so\n * the declaration would flood the channel rather than heartbeat on it. Zero is out\n * for the same reason, which is why this bound is tighter than the non-negative one\n * a throttle or a plain delay can use. Such a declaration falls back to the default.\n */\n get #heartbeat(): number {\n const declared = this.heartbeatValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_HEARTBEAT_MS;\n }\n\n /**\n * The silence after which a peer is dropped, in ms: a finite, positive number a\n * timer can hold. Anything else names no delay and every peer expires in the task\n * that records it, so the roster could never hold anyone. Such a declaration falls\n * back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared > 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Draws the roster into a list target, which arrives empty from a Turbo Stream\n * replacement or a morph. Stimulus runs this before `connect()` on the first mount\n * too, where the roster is still empty and clearing whatever a cache snapshot\n * preserved is what `connect()` goes on to do anyway.\n */\n listTargetConnected(): void {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n for (const [id, peer] of this.#peers) this.#appendClone(id, peer.name);\n }\n\n /** Paints the roster size into a count target that arrives blank, for the same reason. */\n countTargetConnected(): void {\n this.countTarget.textContent = this.#countMessage(this.#peers.size);\n }\n\n override connect(): void {\n // Presence is transient: drop whatever a Turbo cache snapshot preserved\n // (hooks + rendered clones); the live stream re-populates the roster.\n // Rejection is transient server state too — the fresh subscription below\n // re-decides the hook.\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n // The roster is known-empty here, so the count target can say so right\n // away instead of sitting blank until the first roster change. The\n // data-present* hooks intentionally stay absent until the first beacon.\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(0);\n\n if (!this.channelValue) return;\n this.#claimSpeaker();\n this.#subscription = createConfirmedSubscription(this.#descriptor, {\n // The first beacon must wait for the confirmed subscription — a\n // perform() before that is silently dropped by Action Cable. Fires\n // again on every reconnect, so the roster self-heals after an outage.\n connected: () => this.#beacon(true),\n // The server refused the subscription: no beacon will ever go through,\n // and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-presence-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n this.#intervals.set(() => this.#beacon(true), this.#heartbeat);\n window.addEventListener(\"pagehide\", this.#onPageHide);\n }\n\n override disconnect(): void {\n window.removeEventListener(\"pagehide\", this.#onPageHide);\n // Best-effort graceful leave, and only from the last element speaking for\n // this peer; a lost notice is caught by peers' expiry timers instead.\n if (this.#releaseSpeaker()) this.#sendLeaveNotice();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#intervals.clearAll();\n this.#reset();\n this.element.removeAttribute(\"data-presence-rejected\");\n }\n\n /**\n * Registers this element as one of the voices for its peer, so a sibling on the\n * same channel claiming the same `id` keeps the peer present until they all go.\n * Whether the peer is worth announcing at all is {@link #sendLeaveNotice}'s\n * question, so an element with no own `id` claims a voice like any other and is\n * simply never heard.\n */\n #claimSpeaker(): void {\n this.#speakerKey = `${identifierOf(this.#descriptor)}\\n${this.idValue}`;\n speakers.set(this.#speakerKey, (speakers.get(this.#speakerKey) ?? 0) + 1);\n }\n\n /**\n * Gives up the claim, reporting whether this element was the last voice for the\n * peer. The key is spent here: an element that goes on to connect without a\n * channel claims nothing, and a stale key would spend a voice its siblings own.\n */\n #releaseSpeaker(): boolean {\n const key = this.#speakerKey;\n this.#speakerKey = \"\";\n const remaining = (speakers.get(key) ?? 1) - 1;\n if (remaining > 0) speakers.set(key, remaining);\n else speakers.delete(key);\n return remaining <= 0;\n }\n\n /**\n * Sends the best-effort leaving notice (skipped without an own `id`, and\n * outside the confirmed window, where Action Cable would discard it anyway).\n */\n #sendLeaveNotice(): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n this.#subscription.perform(\"appear\", { id: this.idValue, leaving: true });\n }\n\n /**\n * `pagehide` covers the leaves `disconnect()` cannot see: closing the tab or\n * a hard (non-Turbo) navigation destroys the page without running Stimulus\n * teardown, so this listener is the only chance to announce them. Best-effort\n * by nature (the socket may close before the frame flushes); peers' expiry\n * timers stay the safety net. If the page enters the bfcache and is restored\n * instead, the next heartbeat re-announces this client, so an over-eager\n * leave self-heals.\n */\n readonly #onPageHide = (): void => {\n this.#sendLeaveNotice();\n };\n\n /**\n * Broadcasts this client's beacon (skipped without an own `id`). A throttled\n * convergence answer is deferred to the trailing edge rather than dropped —\n * otherwise a peer joining right after a heartbeat would not learn about\n * this client until the next full heartbeat period.\n *\n * Gated on the confirmed subscription: before confirmation and during an\n * outage Action Cable silently discards perform(), so a beacon sent then is\n * pure waste — worse, it would burn `#lastBeaconAt` and delay the next real\n * convergence answer by up to the throttle window. The heartbeat interval\n * keeps ticking regardless; `connected` re-fires on reconfirm and\n * force-beacons immediately, so a gated tick is never missed for long.\n */\n #beacon(force: boolean): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n const now = Date.now();\n const wait = BEACON_THROTTLE_MS - (now - this.#lastBeaconAt);\n if (!force && wait > 0) {\n if (this.#pendingBeacon === null) {\n this.#pendingBeacon = this.#timers.set(() => {\n this.#pendingBeacon = null;\n this.#beacon(true);\n }, wait);\n }\n return;\n }\n this.#lastBeaconAt = now;\n this.#subscription.perform(\"appear\", { id: this.idValue, name: this.nameValue });\n }\n\n /**\n * Tracks a broadcast beacon: upserts the peer (restarting its expiry timer),\n * removes it on a `leaving` notice, and re-announces this client when the\n * peer was unknown (roster convergence — see the class doc).\n */\n #onReceived(data: unknown): void {\n const beacon = data as { id?: unknown; name?: unknown; leaving?: unknown } | null;\n const id = beacon?.id;\n if (typeof id !== \"string\" || id === \"\" || id === this.idValue) return;\n\n if (beacon?.leaving === true) {\n this.#drop(id);\n return;\n }\n\n const name = typeof beacon?.name === \"string\" ? beacon.name : \"\";\n const existing = this.#peers.get(id);\n this.#expiry.set(id, () => this.#drop(id), this.#timeout);\n this.#peers.set(id, { name });\n\n if (existing === undefined) {\n this.#appendClone(id, name);\n this.#render();\n this.#announce(this.announceJoinTextValue, name);\n this.dispatch(\"join\", { detail: { id, name } });\n this.#beacon(false); // answer an unknown peer so its roster converges\n } else if (existing.name !== name) {\n this.#updateClone(id, name);\n this.#render();\n }\n }\n\n /** Removes a peer (expiry or graceful leave) and reflects the change. */\n #drop(id: string): void {\n const peer = this.#peers.get(id);\n if (peer === undefined) return;\n this.#expiry.clear(id);\n this.#peers.delete(id);\n this.#removeClone(id);\n this.#render();\n this.#announce(this.announceLeaveTextValue, peer.name);\n this.dispatch(\"leave\", { detail: { id } });\n }\n\n /** Reflects the roster onto the hooks + count target and emits `change`. */\n #render(): void {\n const users = [...this.#peers.entries()].map(([id, peer]) => ({ id, name: peer.name }));\n this.element.setAttribute(\"data-present\", users.length > 0 ? \"true\" : \"false\");\n this.element.setAttribute(\"data-present-count\", String(users.length));\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(users.length);\n this.dispatch(\"change\", { detail: { users } });\n }\n\n /**\n * Hands one roster change to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per peer — reading\n * out every entry and exit of a busy room is the noise the default avoids. Wording\n * comes from the consumer (`{name}` / `{count}`, where the count is the roster\n * after the change), and an undeclared template does nothing at all — not even\n * cancel an announcement already pending for the other direction.\n */\n #announce(template: string, name: string): void {\n const message = fillTemplate(template, { name, count: this.#peers.size });\n if (message.trim() === \"\") return;\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, PresenceController.#announceDelay);\n }\n\n /**\n * Builds the count copy. Localizable through `data-zero` / `data-one` /\n * `data-other` templates on the count target (`%{count}`); the bare number is\n * the fallback (copy-free, so nothing to localize by default).\n */\n #countMessage(count: number): string {\n const templates = this.countTarget.dataset;\n const template =\n (count === 0 ? templates.zero : count === 1 ? templates.one : templates.other) ??\n templates.other;\n return template ? template.replace(\"%{count}\", String(count)) : String(count);\n }\n\n /** Appends one template clone for a newly present peer (list + template only). */\n #appendClone(id: string, name: string): void {\n if (!this.hasListTarget || !this.hasTemplateTarget) return;\n const root = this.templateTarget.content.firstElementChild?.cloneNode(true);\n if (!(root instanceof Element)) return;\n root.setAttribute(\"data-presence-id\", id);\n this.#fillName(root, name);\n this.listTarget.appendChild(root);\n }\n\n #updateClone(id: string, name: string): void {\n const root = this.#cloneFor(id);\n if (root) this.#fillName(root, name);\n }\n\n #removeClone(id: string): void {\n this.#cloneFor(id)?.remove();\n }\n\n #cloneFor(id: string): Element | null {\n if (!this.hasListTarget) return null;\n // Attribute selectors cannot escape arbitrary ids reliably; match manually.\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n if (child.getAttribute(\"data-presence-id\") === id) return child;\n }\n return null;\n }\n\n /** Writes the peer's name into the clone's `data-presence-name` slots. */\n #fillName(root: Element, name: string): void {\n const slots = root.querySelectorAll(\"[data-presence-name]\");\n for (const slot of slots) slot.textContent = name;\n if (slots.length === 0 && root.hasAttribute(\"data-presence-name\")) {\n root.textContent = name;\n }\n }\n\n /** Clears the transient roster state (connect reset + disconnect teardown). */\n #reset(): void {\n this.#timers.clearAll();\n this.#expiry.clearAll();\n this.#pendingBeacon = null;\n this.#announceId = null;\n this.#peers.clear();\n this.#lastBeaconAt = 0;\n this.element.removeAttribute(\"data-present\");\n this.element.removeAttribute(\"data-present-count\");\n if (this.hasCountTarget) this.countTarget.textContent = \"\";\n if (this.hasListTarget) {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { KeyedTimers } from \"../utils/keyed_timers\";\nimport { MAX_TIMER_DELAY_MS, SafeTimeout } from \"../utils/safe_timeout\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** Milliseconds of silence after which a typer is dropped. */\nconst DEFAULT_TIMEOUT = 3000;\n/** Minimum milliseconds between outgoing typing signals. */\nconst DEFAULT_THROTTLE = 2000;\n\n/**\n * Fills `%{token}` placeholders in a status template, in one pass.\n *\n * The values come off the wire — another client's display name — so they must never\n * reach a replacement *string*, where `$&`, `` $` ``, `$'` and `$$` expand into the\n * template's own text. A single pass also stops a name that happens to contain a token\n * from being re-substituted by a later one. Every occurrence is filled; a token this\n * call has no value for is left as authored, which keeps a typo visible instead of\n * blanking the word.\n */\nconst fillTokens = (template: string, values: Record<string, string>): string =>\n template.replace(/%\\{(name|names|count)\\}/g, (match, token: string) => values[token] ?? match);\n\n/**\n * Headless **typing indicator** — a *server-bound* behavior: a controller whose\n * state lives on the server stream, not in client memory. Typing in the composer\n * throttle-broadcasts a `typing` signal over an Action Cable channel; received\n * signals from *other* clients render \"X is typing…\" into a visible `status` slot\n * and auto-clear after `timeout` ms of silence. The whole behavior is HTML + a\n * broadcast — no app JS, no client store, no reconciler. Ships in the opt-in\n * `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer); the core stays\n * zero-dep.\n *\n * Markup contract (identifier: `stimeo--typing-indicator`):\n * <div data-controller=\"stimeo--typing-indicator\"\n * data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n * data-stimeo--typing-indicator-params-value='{\"room\":\"chat_42\"}'\n * data-stimeo--typing-indicator-name-value=\"Alice\"\n * data-stimeo--typing-indicator-announce-one-text-value=\"{name} is typing\">\n * <textarea data-stimeo--typing-indicator-target=\"input\"></textarea>\n * <p data-stimeo--typing-indicator-target=\"status\"\n * data-one=\"%{name} is typing…\" data-many=\"%{names} are typing…\"></p>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class TypingChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"typing:#{params[:room]}\"\n * def typing(data) = ActionCable.server.broadcast(\"typing:#{params[:room]}\",\n * { name: data[\"name\"] })\n * end\n *\n * The `status` target is a plain visible slot, not a live region: assistive tech is\n * reached through the page's shared announcer instead, so a set that settles is sent\n * there as `announceOneText` / `announceManyText` (`{name}` / `{names}` / `{count}`,\n * debounced). Both default to empty, and an empty message announces nothing — a page\n * that wants the indicator seen but not heard simply leaves them off.\n *\n * `change` dispatches `{ names }`.\n *\n * @remarks\n * Behavior only — the indicator's look is the consumer's CSS, keyed off the\n * `data-typing` hook; the copy is localizable through the `data-one` /\n * `data-many` templates (`%{name}` / `%{names}` / `%{count}`, terse English\n * fallback — the same channel design as `stimeo--sortable`). The own echo is\n * dropped by comparing the broadcast `name` against `name` (a same-name guard,\n * not authentication — identity belongs to the server). Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * signals are dropped before confirmation and while the connection is down, so\n * an outage never burns the throttle window on undeliverable sends. A refused\n * subscription publishes the `data-typing-indicator-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Typing state is transient by nature:\n * `connect()` resets the hook and the status slot (a Turbo cache snapshot must\n * not resurrect a stale \"X is typing…\"), and re-population happens naturally\n * from the stream. The subscription, the per-typer timers, and the delegated\n * `input` listener are all released on `disconnect()` (Turbo navigation\n * included).\n */\nexport class TypingIndicatorController extends Controller<HTMLElement> {\n static override targets = [\"input\", \"status\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n timeout: { type: Number, default: DEFAULT_TIMEOUT },\n // Must stay below `timeout`: the throttle is leading-edge only (no trailing\n // send), so a receiver's display survives continuous typing only while a\n // fresh signal lands within its timeout window.\n throttle: { type: Number, default: DEFAULT_THROTTLE },\n announceOneText: { type: String, default: \"\" },\n announceManyText: { type: String, default: \"\" },\n };\n static events = [\"change\"] as const;\n\n declare readonly hasStatusTarget: boolean;\n declare readonly statusTarget: HTMLElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare nameValue: string;\n declare timeoutValue: number;\n declare throttleValue: number;\n declare announceOneTextValue: string;\n declare announceManyTextValue: string;\n\n /** Delay (ms) before one settled typer set is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n /** Identifier parameters parsed once from their declaration, never in the hot path. */\n #params: Record<string, unknown> = {};\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Names currently typing (other clients), in the order their first signal arrived. */\n readonly #typers = new Set<string>();\n /** Each typer's auto-clear timer, restarted by every further signal from that name. */\n readonly #expiry = new KeyedTimers<string>();\n readonly #timers = new SafeTimeout();\n /** Epoch ms of the last broadcast, for leading-edge throttling. */\n #lastSentAt = 0;\n /** The one outstanding announcement, so a newer set supersedes it. */\n #announceId: number | null = null;\n\n /**\n * Re-parses the identifier parameters when the declaration changes.\n *\n * A malformed declaration falls back to no parameters, so the identifier keeps\n * naming the channel instead of the subscription never being created at all.\n */\n paramsValueChanged(): void {\n this.#params = parseSubscriptionParams(this.paramsValue);\n }\n\n /**\n * Paints the current copy into a `status` target that arrived at runtime.\n *\n * No event: the set of typers did not change, only the element that displays it.\n * Without this a region swapped in mid-conversation stays empty while `data-typing`\n * still says someone is typing, leaving the state in the visual hook alone. An empty\n * set needs no paint — a fresh region already shows it.\n */\n statusTargetConnected(): void {\n if (this.#typers.size > 0) this.#paint();\n }\n\n override connect(): void {\n // Typing state is transient: a Turbo cache snapshot must not resurrect a\n // stale indicator, and the live stream re-populates naturally. Rejection is\n // transient server state too — the fresh subscription re-decides the hook.\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n\n // Delegated on the container so the composer needs no per-input data-action\n // (and swapped/appended inputs keep working).\n this.element.addEventListener(\"input\", this.#onInput);\n if (this.channelValue) {\n // Confirmation tracking (connected / disconnected / rejected) lives in\n // the shared subscription; #onInput gates on its `confirmed` so an\n // outage doesn't burn the throttle window on dropped sends.\n this.#subscription = createConfirmedSubscription(\n { channel: this.channelValue, ...this.#params },\n {\n // The server refused the subscription: the send gate stays shut for\n // good, and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-typing-indicator-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n },\n );\n }\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"input\", this.#onInput);\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#timers.clearAll();\n this.#expiry.clearAll();\n this.#announceId = null;\n this.#typers.clear();\n this.element.removeAttribute(\"data-typing\");\n this.element.removeAttribute(\"data-typing-indicator-rejected\");\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n this.#lastSentAt = 0;\n }\n\n /** Throttled (leading-edge) broadcast of this client's typing signal. */\n readonly #onInput = (): void => {\n // Before the confirmed subscription a perform() is silently dropped by\n // Action Cable — and recording #lastSentAt would ALSO throttle away the\n // first real send, so bail without touching the throttle state.\n if (!this.#subscription?.confirmed) return;\n const now = Date.now();\n if (now - this.#lastSentAt < this.#throttle) return;\n this.#lastSentAt = now;\n this.#subscription.perform(\"typing\", { name: this.nameValue });\n };\n\n /**\n * Tracks a broadcast typer. The own echo is dropped (same `name`); every\n * further signal from a name restarts its auto-clear timer, so the indicator\n * survives continuous typing and clears `timeout` ms after the last signal.\n */\n #onReceived(data: unknown): void {\n const name = (data as { name?: unknown } | null)?.name;\n if (typeof name !== \"string\" || name === \"\" || name === this.nameValue) return;\n\n const added = !this.#typers.has(name);\n this.#typers.add(name);\n this.#expiry.set(name, () => this.#untrack(name), this.#timeout);\n if (added) this.#render();\n }\n\n /**\n * The silence after which a typer is dropped, in ms: a finite, non-negative number\n * a timer can hold. Anything else names no delay — `setTimeout` reads `NaN`, a\n * negative value and `Infinity` alike as \"now\", and a value past\n * {@link MAX_TIMER_DELAY_MS} overflows to the same place — so the typer would\n * vanish in the same task it appeared and the indicator could never be seen. Such\n * a declaration falls back to the default.\n */\n get #timeout(): number {\n const declared = this.timeoutValue;\n return Number.isFinite(declared) && declared >= 0 && declared <= MAX_TIMER_DELAY_MS\n ? declared\n : DEFAULT_TIMEOUT;\n }\n\n /**\n * The minimum gap between outgoing signals, in ms: a finite, non-negative number.\n * Anything else names no interval, and the gate then settles the same way at every\n * keystroke — `NaN` and a negative gap leave it open, so every keystroke broadcasts,\n * while `Infinity` is never exceeded, so nothing is ever sent. Such a declaration\n * falls back to the default.\n */\n get #throttle(): number {\n const declared = this.throttleValue;\n return Number.isFinite(declared) && declared >= 0 ? declared : DEFAULT_THROTTLE;\n }\n\n #untrack(name: string): void {\n this.#typers.delete(name);\n this.#render();\n }\n\n /** Reflects the typer set onto the display, announces it, and emits `change`. */\n #render(): void {\n const names = this.#paint();\n this.#announce(names);\n this.dispatch(\"change\", { detail: { names } });\n }\n\n /**\n * Hands the settled typer set to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per name. Only a\n * non-empty set is announced — that typing stopped is not news worth interrupting\n * a reader for, and the visible copy already clears. Wording comes from the\n * consumer, and {@link announce} drops an empty message, so an undeclared template\n * announces nothing at all.\n */\n #announce(names: string[]): void {\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = null;\n if (names.length === 0) return;\n\n const message =\n names.length === 1\n ? fillTemplate(this.announceOneTextValue, { name: names[0] ?? \"\" })\n : fillTemplate(this.announceManyTextValue, {\n names: names.join(\", \"),\n count: names.length,\n });\n\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, TypingIndicatorController.#announceDelay);\n }\n\n /** Writes the current typer set onto the hook and the status slot. */\n #paint(): string[] {\n const names = [...this.#typers];\n this.element.setAttribute(\"data-typing\", names.length > 0 ? \"true\" : \"false\");\n if (this.hasStatusTarget) {\n this.statusTarget.textContent = this.#message(names);\n }\n return names;\n }\n\n /**\n * Builds the status copy. Localizable through `data-one` / `data-many`\n * templates on the status target (`%{name}` / `%{names}` / `%{count}`); terse\n * English is the fallback.\n */\n #message(names: string[]): string {\n if (names.length === 0) return \"\";\n const joined = names.join(\", \");\n if (names.length === 1) {\n const template = this.statusTarget.dataset.one;\n const name = names[0] ?? \"\";\n return template ? fillTokens(template, { name }) : `${name} is typing…`;\n }\n const template = this.statusTarget.dataset.many;\n return template\n ? fillTokens(template, { names: joined, count: String(names.length) })\n : `${joined} are typing…`;\n }\n}\n","import type { Application } from \"@hotwired/stimulus\";\nimport { LiveCounterController } from \"./live_counter_controller\";\nimport { PresenceController } from \"./presence_controller\";\nimport { TypingIndicatorController } from \"./typing_indicator_controller\";\n\nexport {\n type CableConsumer,\n type CableSubscription,\n type CableSubscriptionMixin,\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n getCableConsumer,\n setCableConsumer,\n} from \"./consumer\";\nexport { LiveCounterController, PresenceController, TypingIndicatorController };\n\n/**\n * Opt-in **server-bound** behaviors for Stimeo — controllers whose state lives\n * on an Action Cable / Turbo stream rather than in client memory: presence,\n * typing indicators, live counters.\n *\n * **Why this is a separate entry point.** The core library is zero-runtime-dep:\n * `import \"stimeo-ui\"` pulls in nothing but `@hotwired/stimulus`. Binding the\n * DOM to a server stream genuinely needs `@rails/actioncable`, so — exactly\n * like `stimeo-ui/positioning` and `@floating-ui/dom` — that cost is opt-in:\n * this module lives at `stimeo-ui/cable`, `@rails/actioncable` is an *optional*\n * peer, and nothing in the core imports it.\n *\n * When the app already owns a consumer (`app/javascript/channels/consumer.js`),\n * hand it over once at boot so no second websocket is opened:\n *\n * ```ts\n * import consumer from \"./channels/consumer\";\n * import { registerCable, setCableConsumer } from \"stimeo-ui/cable\";\n *\n * setCableConsumer(consumer);\n * registerCable(application);\n * ```\n */\nexport const cableControllers = {\n \"stimeo--live-counter\": LiveCounterController,\n \"stimeo--presence\": PresenceController,\n \"stimeo--typing-indicator\": TypingIndicatorController,\n} as const;\n\n/**\n * Registers the opt-in server-bound controllers on a Stimulus Application\n * (additive to `registerStimeo`, mirroring `registerPositioning`).\n */\nexport function registerCable(application: Application): void {\n for (const [identifier, controller] of Object.entries(cableControllers)) {\n application.register(identifier, controller);\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/announce.ts","../../src/utils/authored_integer.ts","../../src/utils/blur_deferral.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/listener_set.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/timer_bounds.ts","../../src/utils/safe_timeout.ts","../../src/utils/transient_hooks.ts","../../src/utils/shared_registry.ts","../../src/cable/consumer.ts","../../src/cable/live_counter_controller.ts","../../src/utils/keyed_timers.ts","../../src/utils/number_bounds.ts","../../src/utils/coerce.ts","../../src/utils/number_value.ts","../../src/utils/template_row.ts","../../src/cable/presence_controller.ts","../../src/cable/typing_indicator_controller.ts","../../src/cable/index.ts"],"names":["TRANSIENT","Controller","template"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Sends one message to the page's shared `stimeo--announcer`.\n *\n * A component that has to reach assistive tech does not carry a live region of its\n * own: a region only announces what changes *after* assistive tech already knows\n * about it, which a region that appears (or is un-hidden) with its message cannot\n * satisfy. The one region that can is the announcer sitting in the page from the\n * start, so state changes are handed to it as an event and it does the reading.\n *\n * The event goes to `window` because the announcer is usually a sibling high in the\n * document rather than an ancestor of the component dispatching it.\n *\n * Wording comes from the consumer — the library ships no English strings — so an\n * empty message is silently dropped and nothing is announced.\n *\n * @example\n * ```ts\n * announce(this.announceTextValue, { assertive: false });\n * ```\n */\nexport function announce(message: string, options: { assertive?: boolean } = {}): void {\n const text = message.trim();\n if (text.length === 0) return;\n window.dispatchEvent(\n new CustomEvent(\"stimeo--announcer:announce\", {\n detail: { message: text, assertive: options.assertive === true },\n }),\n );\n}\n\n/**\n * Fills `{name}` placeholders in an announcement template from `values`.\n *\n * The same substitution the value-text templates use, so a consumer writes\n * `\"{percent}% complete\"` in one attribute and gets the same rules everywhere. A\n * placeholder with no matching entry is left as authored rather than blanked, which\n * keeps a typo visible instead of silently swallowing the word.\n */\nexport function fillTemplate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{([a-zA-Z][a-zA-Z0-9]*)\\}/g, (match, name: string) => {\n const replacement = values[name];\n return replacement === undefined ? match : String(replacement);\n });\n}\n","/** Digits plus the characters that may separate them inside one number. */\nconst TOKEN_BODY = /\\d[\\d.,'’’    _]*\\d|\\d/;\n/** A `-` opens a token only against one of these on its left. */\nconst SIGN_OPENER = /[\\s([]/;\n\n/**\n * Reads the integer that a human-formatted string displays.\n *\n * Takes the first numeric token, drops group separators, and truncates any\n * fractional part toward zero — so `\"1,200 users\"`, `\"$1,299.99\"` and\n * `\"1.299,99\"` read 1200, 1299 and 1299 without asking the platform which\n * locale is in play. A separator counts as grouping only where the digits that\n * follow it form a run of exactly three and the leading run could head a\n * grouped number; anything else is the decimal point, and the digits after it\n * are the fraction.\n *\n * `-` counts as a sign only where it opens the token — at the start of the\n * string, or after whitespace or an opening bracket — so a hyphen inside a\n * label (`\"Sign-ups: 1,200\"`, `\"Top-10 users\"`) never becomes one.\n *\n * `\"1.200\"` is genuinely ambiguous (1200 grouped, 1.2 with a decimal point) and\n * reads as 1200: display numbers group far more often than they carry a\n * fraction in exactly three digits.\n *\n * Returns `null` when the string holds no digits at all. What that means is the\n * caller's to decide — starting nothing, or falling back to a base value.\n */\nexport function authoredInteger(text: string): number | null {\n const firstDigit = /\\d/.exec(text);\n if (firstDigit === null) return null;\n\n const at = firstDigit.index;\n const signed =\n at > 0 && text[at - 1] === \"-\" && (at === 1 || SIGN_OPENER.test(text[at - 2] as string));\n\n const body = TOKEN_BODY.exec(text.slice(at)) as RegExpExecArray;\n const runs = body[0].split(/\\D+/);\n const separators = body[0].match(/\\D+/g) ?? [];\n\n // The leading run heads a grouped number only when it is short enough to be a\n // group itself and carries no leading zero; \"0.500\" and \"012,3\" are decimals.\n const lead = runs[0] as string;\n const heads = lead.length <= 3 && !lead.startsWith(\"0\");\n let digits = lead;\n for (let i = 1; heads && i < runs.length; i += 1) {\n if (separators[i - 1]?.length !== 1 || runs[i]?.length !== 3) break;\n digits += runs[i];\n }\n\n const magnitude = Number.parseInt(digits, 10);\n return signed ? -magnitude || 0 : magnitude;\n}\n","/**\n * Shared bookkeeping for updates that must wait until an element loses focus.\n *\n * A control that becomes redundant while the user is standing on it cannot be\n * removed from the page immediately: hiding a focused element (or turning it\n * into a native `disabled` one) drops focus to `<body>`, stranding a keyboard\n * user mid-widget. The fix every affected controller reached for is the same —\n * keep the element reachable, hold the destructive update, listen for `blur`,\n * and apply it then.\n *\n * {@link BlurDeferral} owns *only* the registry part of that: which elements are\n * currently holding an update back, attaching and detaching the `blur` listener,\n * and guaranteeing teardown. It is deliberately **policy-free** — what the\n * interim state looks like (`aria-disabled`, an ownership marker, nothing at\n * all), and what the deferred update actually is, stay in the controller, which\n * receives the element back through its release callback.\n *\n * Three contracts are worth stating up front, because each one is a hazard the\n * consumers would otherwise hit:\n *\n * - **The release callback fires only on a real `blur`.** `release()` /\n * `releaseAll()` *cancel* a deferral; they do not complete it. Conflating the\n * two makes `overflow-indicator` disable a button that just left the boundary.\n * - **`elements` is a snapshot.** Iterating the live key set while releasing\n * inside the loop is the failure this registry exists to prevent.\n * - **The entry is detached before the callback runs**, so a controller that\n * decides the update is still unsafe can simply defer again.\n *\n * @example\n * ```ts\n * readonly #deferred = new BlurDeferral<HTMLElement>((trigger) => {\n * if (this.#connected) this.#evaluate();\n * });\n *\n * #hide(trigger: HTMLElement) {\n * if (document.activeElement === trigger) {\n * this.#deferred.deferOnly(trigger); // stays visible until blur\n * return;\n * }\n * this.#deferred.releaseAll();\n * trigger.hidden = true;\n * }\n *\n * disconnect() {\n * this.#deferred.releaseAll();\n * }\n * ```\n */\nexport class BlurDeferral<T extends HTMLElement = HTMLElement> {\n /** Elements currently holding an update back, mapped to their `blur` listener. */\n readonly #pending = new Map<T, () => void>();\n /** Called after a pending element blurs and has been detached. */\n readonly #onRelease: (element: T) => void;\n\n /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */\n constructor(onRelease: (element: T) => void) {\n this.#onRelease = onRelease;\n }\n\n /** Number of elements currently holding an update back. */\n get size(): number {\n return this.#pending.size;\n }\n\n /** Snapshot of the pending elements, safe to iterate while releasing them. */\n get elements(): T[] {\n return [...this.#pending.keys()];\n }\n\n /** Whether `element` is currently holding an update back. */\n has(element: T): boolean {\n return this.#pending.has(element);\n }\n\n /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */\n defer(element: T): void {\n if (this.#pending.has(element)) return;\n const onBlur = (): void => {\n this.#detach(element);\n this.#onRelease(element);\n };\n this.#pending.set(element, onBlur);\n element.addEventListener(\"blur\", onBlur);\n }\n\n /** Defers `element` as the only pending entry, cancelling any others. */\n deferOnly(element: T): void {\n for (const pending of this.elements) {\n if (pending !== element) this.#detach(pending);\n }\n this.defer(element);\n }\n\n /** Cancels `element`'s deferral without completing it; no-ops when not pending. */\n release(element: T): void {\n this.#detach(element);\n }\n\n /** Cancels every deferral without completing any of them. */\n releaseAll(): void {\n for (const element of this.elements) this.#detach(element);\n }\n\n /** Removes the `blur` listener for `element` and forgets it. */\n #detach(element: T): void {\n const onBlur = this.#pending.get(element);\n if (onBlur) element.removeEventListener(\"blur\", onBlur);\n this.#pending.delete(element);\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","/**\n * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\nexport { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","/**\n * Drops the transient state hooks a connection inherited, so the cycle it opens\n * starts from a DOM that claims nothing.\n *\n * A transient hook is an attribute a controller writes to say \"this is happening\n * right now\" — a drag in progress, a form with unsaved edits, a refused\n * subscription. Its lifetime belongs to the interaction, not to the element, and\n * the two come apart whenever the element outlives the instance that wrote it: a\n * Turbo cache snapshot taken mid-interaction restores the hook with the markup,\n * and an element re-inserted elsewhere in the page carries it along. Nothing\n * takes such a hook off, because the instance that owned it is gone; consumer\n * CSS then keeps claiming a state no one is in.\n *\n * **`turbo:before-cache` cannot do this job**, which is why the pass belongs in\n * `connect()`: Turbo also dispatches that event on pages that stay, where the hook\n * is still true, and an in-page move — the other half of the population — never\n * reaches it, so a hook that arrived with a moved element would survive.\n * `connect()` covers both, because both end in a connection.\n *\n * Scope is the **drop** only. What the fresh cycle should say instead is the\n * consumer's: a value re-read from the live DOM, a roster refilled by the\n * stream, a measurement retaken from the current scroll position, a slot emptied\n * of the last result. Those answers differ per consumer and none of them is a\n * hook removal, so the pass stops where every consumer agrees.\n *\n * A declaration names one group of hooks and carries no per-instance state, so\n * it belongs at module scope, shared by every instance the page connects. A\n * consumer writes as many as it has groups — hooks it drops under different\n * conditions cannot share one. The exception is a hook whose name is only known\n * at runtime, such as one built from the registered identifier: that\n * declaration is a field, because the name is.\n *\n * **A drop is not always right.** The population splits on one question: can the\n * state a hook describes outlive the instance that wrote it? Where it cannot — a\n * drag ends with the pointer, a subscription dies with its socket — dropping on\n * every connection is correct. Where it can — a submission in flight is not\n * interrupted by an in-page move — only a connection that follows a real restore\n * may drop, and the consumer answers \"did a session survive?\" with the signal it\n * already holds for its own teardown (a live-session field, a `DetachGate`\n * probe). The drop then sits behind that answer, as the second example shows.\n *\n * @example Unconditional — the state cannot outlive the instance.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-sortable-dragging\"] });\n *\n * connect(): void {\n * TRANSIENT.reset(this.element);\n * }\n * ```\n *\n * @example Gated — an in-page move must not drop a live state.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-optimistic\"] });\n *\n * connect(): void {\n * const restored = !this.#connected && !this.#gate.pending;\n * this.#gate.cancel();\n * this.#connected = true;\n * if (restored) TRANSIENT.reset(this.element);\n * }\n * ```\n */\n\n/** What a consumer declares once about the hooks its interaction owns. */\nexport interface TransientHooksOptions {\n /**\n * The attribute names a connection may find written by an earlier one. Hooks\n * a consumer drops under different conditions belong to different\n * declarations, so each one can be reset on its own.\n */\n readonly attributes: readonly string[];\n}\n\n/**\n * One consumer's declaration of the hooks its interaction owns, dropped from an\n * element by {@link TransientHooks.reset}.\n */\nexport class TransientHooks {\n readonly #attributes: readonly string[];\n\n constructor(options: TransientHooksOptions) {\n this.#attributes = options.attributes;\n }\n\n /** Drops the declared hooks from `element`; what it carries besides stays. */\n reset(element: Element): void {\n for (const attribute of this.#attributes) element.removeAttribute(attribute);\n }\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { createConsumer } from \"@rails/actioncable\";\nimport { sharedRegistry } from \"../utils/shared_registry\";\n\n/**\n * Minimal structural view of an Action Cable subscription — the two members the\n * server-bound controllers use. Keeping our own narrow interface (instead of\n * re-exporting the full `@rails/actioncable` types) lets consumers hand us any\n * structurally compatible object, including test doubles.\n */\nexport interface CableSubscription {\n /** Invokes a channel action on the server (`ChannelName#action`). */\n perform(action: string, data?: Record<string, unknown>): void;\n /** Cancels the subscription (the channel's `unsubscribed` runs server-side). */\n unsubscribe(): void;\n}\n\n/** The mixin a controller passes to {@link CableConsumer.subscriptions}' create. */\nexport interface CableSubscriptionMixin {\n /** Called once the subscription is confirmed (`perform` is deliverable). */\n connected?(): void;\n /**\n * Called when the connection drops — perform() is silently undeliverable\n * until Action Cable reconnects and re-confirms (`connected` fires again).\n */\n disconnected?(): void;\n /** Called when the server refuses the subscription (it will never confirm). */\n rejected?(): void;\n /** Called with each broadcast the channel transmits to this client. */\n received?(data: unknown): void;\n}\n\n/** Minimal structural view of an Action Cable consumer (the websocket owner). */\nexport interface CableConsumer {\n subscriptions: {\n create(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n ): CableSubscription;\n };\n}\n\n/**\n * Parses the extra identifier parameters a subscription is declared with.\n *\n * Stimulus offers an `Object` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the controller's `connect()` — and rethrows on\n * malformed text. The throw propagates out of the observer, so a single\n * unparseable attribute stops the subscription from ever being created and\n * leaves a controller that is connected but deaf, with nothing on the element to\n * say so. Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares: the identifier falls back\n * to the channel alone. The attribute text is identical either way\n * (`'{\"room\":\"chat_42\"}'`), so markup does not change with the declaration.\n *\n * Only a JSON object survives. An array, a bare number, a string or `null` cannot\n * name identifier parameters, and spreading one into the identifier would either\n * do nothing or produce index keys no channel can reproduce server-side.\n */\nexport function parseSubscriptionParams(raw: string): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return {};\n return parsed as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/**\n * The shared consumer. An Action Cable consumer is\n * *connection infrastructure* (one websocket per app, the Rails\n * `channels/consumer.js` convention), not UI state — it must survive Turbo\n * navigations, so it is deliberately not rebuilt per `connect()` the way\n * DOM-derived state is.\n */\nconst consumerRegistry = sharedRegistry<{ consumer: CableConsumer | null }>(\n \"stimeo-ui.cable-consumer.registry.v1\",\n () => ({ consumer: null }),\n);\n\n/**\n * Replaces (or clears, with `null`) the shared Action Cable consumer.\n *\n * Call this once at boot when the app already owns a consumer (the usual\n * `app/javascript/channels/consumer.js`), so the server-bound controllers reuse\n * its websocket instead of opening a second one. Tests use it to inject a\n * double. With `null`, the next {@link getCableConsumer} lazily re-creates one.\n */\nexport function setCableConsumer(consumer: CableConsumer | null): void {\n consumerRegistry.consumer = consumer;\n}\n\n/**\n * The shared Action Cable consumer, lazily created on first use via\n * `createConsumer()` (which reads the standard `action_cable_meta_tag` URL).\n */\nexport function getCableConsumer(): CableConsumer {\n if (!consumerRegistry.consumer) consumerRegistry.consumer = createConsumer();\n return consumerRegistry.consumer;\n}\n\n/**\n * A {@link CableSubscription} that also tracks its confirmation lifecycle.\n * Action Cable silently drops a `perform()` sent before the subscription is\n * confirmed or while the connection is down — every server-bound controller\n * must therefore gate its sends (and their local side effects: optimistic\n * updates, throttle bookkeeping) on {@link confirmed}.\n */\nexport interface ConfirmedCableSubscription extends CableSubscription {\n /** True while the subscription is confirmed — `perform()` is deliverable. */\n readonly confirmed: boolean;\n /** True once the server refused the subscription (it will never confirm). */\n readonly rejected: boolean;\n}\n\n/** One caller of {@link createConfirmedSubscription} riding a shared wire subscription. */\ninterface Member {\n readonly mixin: CableSubscriptionMixin;\n /**\n * True while a lifecycle callback the member missed (the identifier was already\n * confirmed or refused when it joined) is still owed to it. Any callback that arrives\n * from the wire in the meantime supersedes the owed one.\n */\n catchUp: boolean;\n}\n\n/** The one wire subscription an identifier has on a consumer, plus who rides it. */\ninterface SharedSubscription {\n readonly subscription: CableSubscription;\n readonly members: Set<Member>;\n confirmed: boolean;\n rejected: boolean;\n}\n\n/**\n * Wire subscriptions by identifier, per consumer. Keyed weakly so a replaced consumer\n * (see {@link setCableConsumer}) takes its bookkeeping with it.\n */\nconst sharedSubscriptions = sharedRegistry(\n \"stimeo-ui.cable-subscriptions.registry.v1\",\n () => new WeakMap<CableConsumer, Map<string, SharedSubscription>>(),\n);\n\n/**\n * The identifier Action Cable derives for a channel descriptor: the JSON of the\n * params object, a bare channel name standing for `{ channel }`.\n */\nexport function identifierOf(channel: string | Record<string, unknown>): string {\n return JSON.stringify(typeof channel === \"string\" ? { channel } : channel);\n}\n\n/**\n * Creates a subscription on the shared consumer with confirmation tracking\n * layered over the caller's mixin: `connected` / `disconnected` / `rejected`\n * flip the {@link ConfirmedCableSubscription.confirmed} flag *before* the\n * caller's own handler runs (so a handler reading `subscription.confirmed`\n * sees the post-transition state), and `received` passes straight through.\n *\n * **Callers with the same identifier (channel + params) share one wire\n * subscription.** The server confirms an identifier once and silently ignores a\n * repeated `subscribe` for it, so a second wire subscription would never be\n * confirmed — its sends would stay gated and the client would keep re-sending\n * `subscribe` until the next reconnect. Instead every caller is a member of the\n * identifier's one subscription: lifecycle callbacks and broadcasts fan out to all\n * members, a member joining an already confirmed (or refused) identifier is told\n * so on the next microtask — after its caller has stored the returned\n * subscription, the way a callback from the wire would arrive — and the wire is\n * unsubscribed when the last member leaves.\n *\n * This is deliberately a *tracker*, not an automatic `perform()` gate: the\n * controllers must skip the local side effects that surround a send (an\n * optimistic DOM bump, a throttle timestamp) together with the send itself,\n * which only the call site can decide — so they check `confirmed` and bail\n * before any of it.\n */\nexport function createConfirmedSubscription(\n channel: string | Record<string, unknown>,\n mixin: CableSubscriptionMixin,\n): ConfirmedCableSubscription {\n const consumer = getCableConsumer();\n let registry = sharedSubscriptions.get(consumer);\n if (!registry) {\n registry = new Map();\n sharedSubscriptions.set(consumer, registry);\n }\n const identifier = identifierOf(channel);\n const member: Member = { mixin, catchUp: false };\n\n let shared = registry.get(identifier);\n if (!shared) {\n shared = openSharedSubscription(consumer, channel);\n registry.set(identifier, shared);\n } else if (shared.rejected || shared.confirmed) {\n // Missed lifecycle: deliver it once the caller holds the returned subscription.\n const owed = shared;\n member.catchUp = true;\n queueMicrotask(() => {\n if (!member.catchUp) return;\n member.catchUp = false;\n if (owed.rejected) mixin.rejected?.();\n else mixin.connected?.();\n });\n }\n shared.members.add(member);\n const owner = shared;\n const registered = registry;\n let active = true;\n\n return {\n perform: (action, data) => owner.subscription.perform(action, data),\n unsubscribe: () => {\n if (!active) return;\n active = false;\n // Nothing is owed to a member that left: a catch-up still queued must not fire.\n member.catchUp = false;\n owner.members.delete(member);\n if (owner.members.size === 0) {\n registered.delete(identifier);\n owner.subscription.unsubscribe();\n }\n },\n get confirmed() {\n return owner.confirmed;\n },\n get rejected() {\n return owner.rejected;\n },\n };\n}\n\n/** Opens the wire subscription for an identifier and wires the fan-out to its members. */\nfunction openSharedSubscription(\n consumer: CableConsumer,\n channel: string | Record<string, unknown>,\n): SharedSubscription {\n const members = new Set<Member>();\n // A callback from the wire supersedes whatever a late member was still owed.\n const fanOut = (deliver: (mixin: CableSubscriptionMixin) => void): void => {\n for (const member of [...members]) {\n member.catchUp = false;\n deliver(member.mixin);\n }\n };\n const shared: SharedSubscription = {\n members,\n confirmed: false,\n rejected: false,\n subscription: consumer.subscriptions.create(channel, {\n // A refusal is final: Action Cable never confirms a rejected subscription,\n // so a late connected/disconnected (only possible from a misbehaving\n // consumer double) must not reopen the gate `rejected` promised shut.\n connected: () => {\n if (shared.rejected) return;\n shared.confirmed = true;\n fanOut((mixin) => mixin.connected?.());\n },\n disconnected: () => {\n if (shared.rejected) return;\n shared.confirmed = false;\n fanOut((mixin) => mixin.disconnected?.());\n },\n rejected: () => {\n shared.confirmed = false;\n shared.rejected = true;\n fanOut((mixin) => mixin.rejected?.());\n },\n received: (data: unknown) => fanOut((mixin) => mixin.received?.(data)),\n }),\n };\n return shared;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { authoredInteger } from \"../utils/authored_integer\";\nimport { BlurDeferral } from \"../utils/blur_deferral\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { TransientHooks } from \"../utils/transient_hooks\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** The hook a connection may find written by an earlier, now-gone one. */\nconst TRANSIENT = new TransientHooks({ attributes: [\"data-live-counter-rejected\"] });\n\n/**\n * Marker on triggers this controller disabled: `disabled` is a shared\n * attribute, so only marked ones are ever re-enabled — an authored-disabled\n * trigger stays untouched. The marker lives in the DOM rather than in a lease\n * held by the instance, so it survives a Turbo cache snapshot.\n */\nconst DISABLED_MARKER = \"data-live-counter-disabled\";\n\n/**\n * Headless **live counter** — a *server-bound* behavior: a number bound to an\n * Action Cable stream (likes, views, active users) that ticks across every\n * connected client. A local action applies an **optimistic** increment\n * immediately, then reconciles with the broadcast; the own echo of a delta\n * broadcast is deduped so the increment is never applied twice. Ships in the\n * opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * Markup contract (identifier: `stimeo--live-counter`):\n * <div data-controller=\"stimeo--live-counter\"\n * data-stimeo--live-counter-channel-value=\"LikesChannel\"\n * data-stimeo--live-counter-params-value='{\"post\":42}'\n * data-stimeo--live-counter-id-value=\"<%= SecureRandom.uuid %>\">\n * <span data-stimeo--live-counter-target=\"value\">128</span>\n * <button type=\"button\" data-action=\"click->stimeo--live-counter#increment\"\n * data-stimeo--live-counter-target=\"trigger\">♥</button>\n * </div>\n *\n * Wire contract — the broadcast is either **authoritative** or a **delta**:\n * `{ count: 129 }` sets the absolute value (naturally idempotent; preferred —\n * the server owns the number), while `{ delta: 1, by: \"17\" }` adds to it,\n * skipped when `by` matches this client's `id` **and its amount matches an\n * optimistic bump this client is still waiting to see echoed**. A counter\n * holding no such guess — a sibling on the page, this user's other tab, one\n * stepping by a different amount — applies the delta like any other, so every\n * display converges. Only whole numbers are accepted: the\n * display is the state, and a value that cannot be read back from text is not\n * a count. `increment` performs `increment` on the channel with `{ id, delta }`;\n * the server persists and broadcasts. Without an own `id` the delta echo is\n * indistinguishable from a foreign delta, so the optimistic bump is skipped and\n * the broadcast applies the increment exactly once — set `id` (any per-client,\n * per-tab string, e.g. `SecureRandom.uuid`) for optimistic UX. Comparing `by`\n * to `id` is an echo guard, not authentication — identity belongs to the server.\n *\n * `change` dispatches `{ count }`.\n *\n * @remarks\n * Behavior only — the displayed number IS the state, and the **DOM is the\n * source of truth**: the server renders the initial count into the `value`\n * target (the element itself without one), so a Turbo cache restore needs no\n * reconciliation and `connect()` only (re)subscribes. Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * increments are dropped before confirmation AND while the connection is down\n * (`disconnected` shuts the gate until Action Cable re-confirms), so the\n * display never advances past what the server can receive. The gate reads the\n * declaration, not the subscription object: while a `channel` is declared it\n * stays shut until that subscription confirms, including the window where one\n * has yet to be opened. A refused\n * subscription publishes the `data-live-counter-rejected` hook (cleared on\n * `connect()` — rejection is transient server state) so the consumer's CSS can\n * disable the trigger. Optional `trigger` targets make that declarative: they\n * carry the real `disabled` attribute exactly while an increment would be\n * dropped (before confirmation, during an outage, after a rejection) — no CSS\n * required, and a disabled form control is announced as such by AT. An\n * authored `disabled` (set by the consumer for its own reasons) is respected:\n * only a disabled this controller applied — tracked via a marker attribute —\n * is ever lifted, and it is given back when the controller or the target goes\n * away. A trigger that holds focus keeps it: the disable waits for it to blur,\n * since taking focus away mid-outage strands a keyboard user. Channel-less\n * (local-only) counters never disable their triggers. `announceText` is opt-in\n * and empty by default; when set, reconciled counts reach the page's shared\n * announcer, debounced so a burst is one message. The subscription follows the\n * declaration — a `channel` or `params` change moves it, once per batch of\n * changes and only when the identifier they name differs from the one it was\n * opened for — and is released on `disconnect()` (Turbo navigation included)\n * along with any pending announcement.\n */\nexport class LiveCounterController extends Controller<HTMLElement> {\n static override targets = [\"value\", \"trigger\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n announceText: { type: String, default: \"\" },\n };\n /** Collapses a burst of broadcasts into one announcement. */\n static #announceDelay = 200;\n static actions = [\"increment\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly hasValueTarget: boolean;\n declare readonly valueTarget: HTMLElement;\n declare readonly triggerTargets: HTMLElement[];\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare announceTextValue: string;\n\n #subscription: ConfirmedCableSubscription | null = null;\n /** Collapses the `channel` / `params` callbacks of one batch into one move. */\n readonly #follow = new MicrotaskCoalescer(() => this.#followDeclaration());\n /** The identifier the subscription was last opened for. */\n #identifier = \"\";\n readonly #morphRender = new MorphRenderWatcher(() => this.#syncTriggers());\n /**\n * The optimistic bumps this client has yet to see echoed back, in the order it\n * sent them. An echo belongs to the send that caused it, and the amount is what\n * names that send: a counter that guessed nothing — or guessed a different\n * amount — applies the delta like any other, so siblings and other tabs\n * converge whatever step each of them uses.\n */\n readonly #outstanding: number[] = [];\n /** Holds a disable back while the trigger owns focus, so focus is never taken away. */\n readonly #focusedTriggers = new BlurDeferral((trigger) => this.#syncTrigger(trigger));\n readonly #timers = new SafeTimeout();\n\n /** Moves the subscription to the identifier the parameters now name. */\n paramsValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Moves the subscription when the declaration names a different channel. */\n channelValueChanged(): void {\n this.#follow.schedule();\n }\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#follow.activate();\n this.#subscribe();\n }\n\n /**\n * Opens the subscription the declaration names, and reflects the gate.\n *\n * The rejected hook is transient server state: a Turbo cache snapshot must not\n * resurrect it — the fresh subscription re-decides it. An outstanding guess\n * belongs to the identifier that is going away, so it is dropped with it.\n *\n * @stimeoRuntimeOnly `channel` and `params` name the identifier this call opens, and whether it\n * opens one at all; the rejected hook it clears does not depend on them.\n */\n #subscribe(): void {\n TRANSIENT.reset(this.element);\n this.#outstanding.length = 0;\n const descriptor = this.#descriptor;\n this.#identifier = identifierOf(descriptor);\n if (this.channelValue) {\n this.#subscription = this.#open(descriptor);\n }\n // Also covers a Turbo cache restore that snapshotted a disabled trigger:\n // the fresh (unconfirmed or absent) subscription re-decides the state.\n this.#syncTriggers();\n }\n\n /**\n * Opens the wire subscription, or returns null when it cannot be opened.\n *\n * A consumer that cannot be built (no Action Cable meta tag, a boot-order\n * mistake) would otherwise throw out of `connect()` and leave the element\n * half-wired: no gate reflected, and a trigger that looks live. The gate reads\n * the declaration, so a null subscription keeps it shut and the display never\n * moves past what the server can receive.\n *\n * @stimeoRuntimeOnly `id` is read by the handlers it wires, as each broadcast arrives.\n */\n #open(descriptor: Record<string, unknown>): ConfirmedCableSubscription | null {\n try {\n return createConfirmedSubscription(descriptor, {\n connected: () => this.#syncTriggers(),\n // A drop closes the send window (the shared subscription tracks it)\n // until Action Cable reconnects and re-confirms — an increment during\n // the outage would bump the display while its perform() is silently\n // discarded by the closed socket.\n disconnected: () => this.#syncTriggers(),\n // The server refused the subscription (auth, bad params): the gate\n // stays shut for good, and the hook lets the consumer's CSS disable\n // or hide the trigger instead of leaving a silently dead button.\n rejected: () => {\n this.element.setAttribute(\"data-live-counter-rejected\", \"true\");\n this.#syncTriggers();\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n } catch (error) {\n console.warn(\"Stimeo UI: the live counter could not open its subscription.\", error);\n return null;\n }\n }\n\n /**\n * The identifier this declaration names.\n *\n * `channel` comes first so the identifier text matches what Action Cable\n * builds, and a `channel` key inside `params` cannot displace the declared\n * one — `params` names *additional* identifier parameters. A malformed `params`\n * declaration falls back to no parameters, so the identifier keeps naming the\n * channel instead of the subscription never being created at all.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _ignored, ...rest } = parseSubscriptionParams(this.paramsValue);\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * Moves to the identifier the declaration now names, once the batch of `channel` /\n * `params` changes has settled. A callback that repeats the identifier — the same\n * parameters spelled differently, or a Value written back unchanged — keeps the\n * subscription, its gate and the guesses outstanding on it.\n *\n * @stimeoRuntimeOnly `channel` and `params` are compared with the identifier the subscription\n * was opened for.\n */\n #followDeclaration(): void {\n if (identifierOf(this.#descriptor) === this.#identifier) return;\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#subscribe();\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#follow.cancel();\n this.#timers.clearAll();\n this.#focusedTriggers.releaseAll();\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n TRANSIENT.reset(this.element);\n // `disabled` is borrowed while the gate is shut: give back what is ours.\n for (const trigger of this.triggerTargets) this.#release(trigger);\n }\n\n /** Late-added triggers (e.g. via a Turbo Stream) pick up the current gate. */\n triggerTargetConnected(target: HTMLElement): void {\n this.#syncTrigger(target);\n }\n\n /** A trigger that stops being one keeps nothing this controller lent it. */\n triggerTargetDisconnected(target: HTMLElement): void {\n this.#focusedTriggers.release(target);\n this.#release(target);\n }\n\n /**\n * Optimistic local increment: bumps the display immediately, then asks the\n * server to persist and broadcast. The step comes from the action param\n * (`data-stimeo--live-counter-delta-param`), default 1. Bound via `data-action`.\n */\n increment(event?: Event & { params?: { delta?: number } }): void {\n // Before the confirmed subscription — and while the connection is down —\n // a perform() is silently dropped by Action Cable: bumping the display\n // then would diverge from the server, so the whole increment is dropped\n // (screen and server stay consistent). A declared channel with no live\n // subscription is the same window, so the gate reads the declaration.\n if (!this.#ready) return;\n // Action params arrive as authored strings; normalize and ignore garbage.\n const raw = Number(event?.params?.delta ?? 1);\n const delta = Number.isSafeInteger(raw) ? raw : 1;\n // Channel-less (local-only) counters have no echo coming, so they always bump\n // and keep no guess: nothing would ever cancel one. Without an own `id` the\n // delta echo cannot be deduped either, so the optimistic bump is skipped and\n // the broadcast applies the increment exactly once.\n if (this.#local) {\n this.#write(this.#current + delta);\n } else if (this.idValue !== \"\") {\n this.#outstanding.push(delta);\n this.#write(this.#current + delta);\n }\n this.#subscription?.perform(\"increment\", { id: this.idValue, delta });\n }\n\n /**\n * Reconciles a broadcast: absolute `count` wins; the echo of this client's own\n * guess is skipped.\n *\n * Only whole numbers reach the display. The wire carries values another client\n * named and the server relayed, and the display is the state this controller\n * reads back — a fraction, an infinity or a NaN would be written as text that\n * reads back as a different number, or as none at all.\n */\n #onReceived(data: unknown): void {\n const message = data as { count?: unknown; delta?: unknown; by?: unknown } | null;\n if (Number.isSafeInteger(message?.count)) {\n // Server truth settles every guess this client was still waiting on.\n this.#outstanding.length = 0;\n this.#reconcile(message?.count as number);\n return;\n }\n if (Number.isSafeInteger(message?.delta)) {\n const delta = message?.delta as number;\n const own =\n typeof message?.by === \"string\" && message.by !== \"\" && message.by === this.idValue;\n // An echo cancels the one guess it was the answer to, matched by amount. A\n // counter holding no guess of that size — a sibling on the page, this\n // user's other tab, a step someone else chose — applies it.\n const guess = own ? this.#outstanding.indexOf(delta) : -1;\n if (guess !== -1) {\n this.#outstanding.splice(guess, 1);\n return;\n }\n this.#reconcile(this.#current + delta);\n }\n }\n\n /** True when no channel is declared: the local-only mode, with no server to diverge from. */\n get #local(): boolean {\n return this.channelValue === \"\";\n }\n\n /** True while an increment would go through (local-only counters always are). */\n get #ready(): boolean {\n return this.#local || (this.#subscription?.confirmed ?? false);\n }\n\n /**\n * Reflects the send gate onto the optional `trigger` targets as the real\n * `disabled` attribute — the declarative alternative to styling off the\n * `data-live-counter-rejected` hook (a disabled control is also skipped by\n * keyboard focus and announced by AT, which CSS alone cannot do).\n */\n #syncTriggers(): void {\n for (const trigger of this.triggerTargets) this.#syncTrigger(trigger);\n }\n\n /**\n * `disabled` is a shared attribute: only disable what is currently enabled\n * (marking it ours), and only lift a disabled carrying our marker — so an\n * authored-disabled trigger (\"disabled until valid\", say) is never\n * re-enabled by the gate. A marked disabled restored from a Turbo cache\n * snapshot is recognized as ours and lifted once the gate opens.\n *\n * @stimeoRenderRoot\n */\n #syncTrigger(trigger: HTMLElement): void {\n if (this.#ready) {\n this.#focusedTriggers.release(trigger);\n this.#release(trigger);\n return;\n }\n if (trigger.hasAttribute(\"disabled\")) return;\n // Disabling the control that holds focus drops it to the document body,\n // stranding a keyboard user mid-interaction. Hold the disable until it\n // blurs; the gate already refuses the increment either way.\n if (trigger.contains(document.activeElement)) {\n this.#focusedTriggers.defer(trigger);\n return;\n }\n trigger.setAttribute(\"disabled\", \"\");\n trigger.setAttribute(DISABLED_MARKER, \"\");\n }\n\n /** Gives back a `disabled` this controller lent, leaving an authored one alone. */\n #release(trigger: HTMLElement): void {\n if (!trigger.hasAttribute(DISABLED_MARKER)) return;\n trigger.removeAttribute(\"disabled\");\n trigger.removeAttribute(DISABLED_MARKER);\n }\n\n /** The displayed element: the `value` target, else the controller element. */\n get #display(): HTMLElement {\n return this.hasValueTarget ? this.valueTarget : this.element;\n }\n\n /** The current count, parsed from the DOM (the single source of truth). */\n get #current(): number {\n // A formatted server-rendered value (\"1,200 likes\") reads as the number it\n // displays; a display holding no number at all counts as zero.\n return authoredInteger(this.#display.textContent ?? \"\") ?? 0;\n }\n\n /**\n * Writes a whole count — every caller has already established that it is one —\n * and reports whether the display actually moved.\n */\n #write(count: number): boolean {\n if (count === this.#current) return false;\n this.#display.textContent = String(count);\n this.dispatch(\"change\", { detail: { count } });\n return true;\n }\n\n /**\n * Settles the display on a count the server stated, announcing what it settled\n * on. Announcing the optimistic bump instead would read a guess out and then\n * correct it, so the announcement rides this path only.\n *\n * @stimeoRuntimeOnly The count shown is the value received; `announceText` words the one\n * announcement of this change.\n */\n #reconcile(count: number): void {\n if (this.#write(count)) this.#announce(count);\n }\n\n /**\n * Hands a reconciled count to the page's shared announcer.\n *\n * Debounced: a burst of broadcasts is one announcement, not one per message,\n * and only the last one is ever sent. Wording comes from the consumer and\n * {@link announce} drops an empty message, so an undeclared template announces\n * nothing at all — a count that ticks constantly should not interrupt a reader\n * unless the page asked for it.\n */\n #announce(count: number): void {\n this.#timers.clearAll();\n this.#timers.set(() => {\n announce(fillTemplate(this.announceTextValue, { count }));\n }, LiveCounterController.#announceDelay);\n }\n}\n","import { SafeTimeout } from \"./safe_timeout\";\n\n/**\n * Keeps at most one pending timer per key, and releases them all together.\n *\n * A region, a row, a message or a speaker that is armed again while its earlier\n * timer is still pending must end up with exactly one timer deciding when its\n * state ends. Two timers on one key means the earlier deadline wins: it cuts the\n * new emphasis short, clears text written after it, or drops a speaker who is\n * still typing — and then reports an ending for a state that is already gone.\n * Arming through this registry cancels the key's earlier timer in the same step,\n * and no timer id ever reaches the caller.\n *\n * An entry lives exactly as long as its timer: it goes as the timer fires, and\n * the whole ledger goes on `clearAll()`. That is a correctness rule, not\n * tidiness — a platform may hand an id released by `clearTimeout` to the next\n * timer it creates, so an entry that outlived its timer would let a later\n * `clear()` on that key cancel whatever timer now holds the id, on some other\n * key entirely.\n *\n * The ledger is a plain `Map`, and the key may be anything. Weak keys would\n * retain nothing less: while a timer is pending the platform already holds the\n * key through the callback, and the entry is gone the moment the timer is.\n *\n * Scope is the one-per-key timer only. What the timer means, and the roster of\n * keys it is armed for, stay with the consumer, so a second kind of timer on the\n * same keys (a transition wait, an animation frame) is a second registry, and a\n * timer that belongs to no key stays on a plain `SafeTimeout`.\n *\n * @example\n * ```ts\n * readonly #expiry = new KeyedTimers<string>();\n *\n * #onSignal(name: string): void {\n * this.#expiry.set(name, () => this.#drop(name), this.#timeout);\n * }\n *\n * disconnect(): void {\n * this.#expiry.clearAll();\n * }\n * ```\n */\nexport class KeyedTimers<K> {\n readonly #timers = new SafeTimeout();\n\n /** The pending timer of each key; an entry lives exactly as long as its timer. */\n readonly #pending = new Map<K, number>();\n\n /**\n * Arms `callback` after `delay` ms for `key`, cancelling the timer `key` had\n * pending. The entry is dropped before the callback runs, so the callback sees\n * the key unarmed and may arm it again for the next round.\n */\n set(key: K, callback: () => void, delay: number): void {\n this.clear(key);\n const id = this.#timers.set(() => {\n this.#pending.delete(key);\n callback();\n }, delay);\n this.#pending.set(key, id);\n }\n\n /**\n * Cancels `key`'s pending timer, if it has one.\n *\n * A timer id is a positive integer, so `-1` stands for \"nothing pending\" and\n * the registry ignores an id it does not own — the unarmed case needs no\n * branch of its own, and no other key's timer can be reached from here.\n */\n clear(key: K): void {\n this.#timers.clear(this.#pending.get(key) ?? -1);\n this.#pending.delete(key);\n }\n\n /**\n * Cancels every pending timer and forgets every key. Call this from a\n * controller's `disconnect()` so no timer, and no entry, outlives the element.\n */\n clearAll(): void {\n this.#timers.clearAll();\n this.#pending.clear();\n }\n\n /** Whether `key` has a timer pending. */\n has(key: K): boolean {\n return this.#pending.has(key);\n }\n}\n","import { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/** Semantic constraints on an already decoded number, independent of the DOM. */\nexport interface NumberBounds {\n /** Finite numbers are required, including when this field is omitted. */\n readonly finite?: true;\n /** Inclusive lower endpoint. */\n readonly min?: number;\n /** Inclusive upper endpoint. */\n readonly max?: number;\n /** Exclusive lower endpoint. */\n readonly exclusiveMin?: number;\n /** Require a number without a fractional part. */\n readonly integer?: boolean;\n /** Exact decoded numbers accepted by a discrete contract. */\n readonly allowedValues?: readonly number[];\n /** JSON-safe exceptions to finite-number checking for unbounded endpoints. */\n readonly allowInfinity?: \"negative\" | \"positive\" | \"both\";\n}\n\n/** One constraint for each Number Value, including shorthand declarations. */\nexport type NumberValueConstraints<Values> = {\n readonly [Key in keyof Values as Values[Key] extends\n | NumberConstructor\n | { type: NumberConstructor }\n ? Key\n : never]: NumberBounds;\n};\n\n/** Reusable semantic domains; fractional inputs remain fractional unless excluded. */\nexport const NUMBER_BOUNDS = {\n finite: { finite: true },\n nonNegative: { finite: true, min: 0 },\n positive: { finite: true, exclusiveMin: 0 },\n nonNegativeInteger: { finite: true, min: 0, integer: true },\n positiveInteger: { finite: true, exclusiveMin: 0, integer: true },\n lowerBound: { finite: true, allowInfinity: \"negative\" },\n upperBound: { finite: true, allowInfinity: \"positive\" },\n timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS },\n positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS },\n} as const satisfies Record<string, NumberBounds>;\n\n/**\n * Checks all declared bounds without rounding, clamping, or coercing the input.\n * Infinity exceptions relax only finite checking; every other bound still applies.\n */\nexport function matchesNumberBounds(value: number, bounds: NumberBounds): boolean {\n if (!Number.isFinite(value)) {\n const direction = value === Infinity ? \"positive\" : value === -Infinity ? \"negative\" : null;\n if (direction === null) return false;\n if (bounds.allowInfinity !== \"both\" && bounds.allowInfinity !== direction) return false;\n }\n if (bounds.min !== undefined && value < bounds.min) return false;\n if (bounds.max !== undefined && value > bounds.max) return false;\n if (bounds.exclusiveMin !== undefined && value <= bounds.exclusiveMin) return false;\n if (bounds.integer && !Number.isInteger(value)) return false;\n if (bounds.allowedValues !== undefined && !bounds.allowedValues.includes(value)) return false;\n return true;\n}\n\n/** Decodes a Number Value literal; action params use their own JSON decoder. */\nexport function decodeNumberValue(raw: string): number {\n return Number(raw.replace(/_/g, \"\"));\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus decodes action params as JSON when possible and leaves other text\n * untouched. CustomEvent detail can also supply a number or numeric string.\n * The reader accepts those two types while rejecting blank and non-finite input.\n */\n\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/**\n * Reads a finite number or nonblank numeric string; other input returns `null`.\n * Blank strings cannot silently reset a value to zero.\n */\nexport function toFiniteNumber(raw: unknown): number | null {\n if (typeof raw !== \"number\" && typeof raw !== \"string\") return null;\n if (typeof raw === \"string\" && raw.trim().length === 0) return null;\n const value = Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Returns an accepted decoded Number Value or the caller's fallback unchanged.\n * The caller owns the fallback contract; this reader does not parse or write a Value.\n */\nexport function readNumber(raw: number, fallback: number, bounds: NumberBounds): number {\n return matchesNumberBounds(raw, bounds) ? raw : fallback;\n}\n","import { readNumber } from \"./coerce\";\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** The instance identity carried by a Number Value read. */\nexport interface NumberValueOwner {\n readonly identifier: string;\n readonly element: Element;\n}\n\n/** Reads current declarations at the point of use without rewriting their attributes. */\nexport class NumberValueReader {\n /** The last rejected literal per Value; no history grows for an unchanged instance. */\n readonly #lastRejected = new Map<string, string>();\n\n /** Resolves the declared number using its own fallback and shared class contract. */\n read(\n owner: NumberValueOwner,\n name: string,\n raw: number,\n fallback: number,\n bounds: NumberBounds,\n ): number {\n const resolved = readNumber(raw, fallback, bounds);\n if (matchesNumberBounds(raw, bounds)) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;\n const literal = owner.element.getAttribute(attribute);\n if (literal === null) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n if (this.#lastRejected.get(name) !== literal) {\n this.#lastRejected.set(name, literal);\n console.warn(\n `Stimeo UI: \"${owner.identifier}\" has an invalid number Value \"${name}\" declaration ${JSON.stringify(literal)}; using ${fallback}.`,\n );\n }\n return resolved;\n }\n}\n","import { fillTemplate } from \"./announce\";\nimport { matchingPart } from \"./element_part\";\nimport { targetSelector } from \"./target_selector\";\n\n/**\n * Clones the first element of a `<template>`, and nothing else.\n *\n * A template is authored across lines, so its content holds whitespace text nodes\n * around that element — and a second element, where one is written. Cloning the\n * element on its own makes it the whole unit the caller appends, so the same node\n * a later `remove()` takes back is everything that went in, and the parent's child\n * list stays equal to its element list.\n *\n * `null` when the content has no element at all.\n */\nexport function cloneTemplateRoot(template: HTMLTemplateElement): HTMLElement | null {\n const root = template.content.firstElementChild;\n return root instanceof HTMLElement ? (root.cloneNode(true) as HTMLElement) : null;\n}\n\n/** What a consumer declares once about the rows it builds from a `<template>`. */\nexport interface TemplateRowOptions<R extends string, O extends string> {\n /** The controller identifier the row's parts are declared against. */\n readonly identifier: string;\n /** Target name the row itself carries. */\n readonly root: string;\n /** Target names the row must contain; the first one missing is the one reported. */\n readonly required: readonly R[];\n /** Target names the row may contain; a missing one resolves to `null`. */\n readonly optional?: readonly O[];\n /** Target name of the `<button>` whose authored `aria-label` names the row. */\n readonly button: string;\n /** What the widget did not do, e.g. `\"added no tag\"`. */\n readonly outcome: string;\n /** What the author wrote it in, e.g. `\"chip template\"`. */\n readonly noun: string;\n}\n\n/** One row: the element to append, the parts inside it, and its named button. */\nexport interface TemplateRowParts<R extends string, O extends string> {\n readonly root: HTMLElement;\n readonly slots: Readonly<Record<R, HTMLElement> & Record<O, HTMLElement | null>>;\n readonly button: HTMLButtonElement;\n}\n\n/**\n * Builds one row from an authored `<template>`, naming an unusable one once per connection.\n *\n * **The row is the template's only element, and it carries {@link\n * TemplateRowOptions.root} itself.** Cloning it alone ({@link cloneTemplateRoot})\n * makes what the caller appends exactly what a later `remove()` takes back; a row\n * wrapped in another element, or standing beside a second one, would leave that\n * other element behind on every removal — or never be rendered at all — so such a\n * template is refused rather than half-built.\n *\n * Parts resolve through the caller's own namespace, derived from the identifier\n * the controller is registered under, so a registration under any name reaches\n * the attributes that registration renders.\n *\n * A template that cannot produce a row yields `null` and changes nothing: what a\n * refused row means for the selection, the field, or the announcement stays with\n * the caller. The diagnostic names what is wrong — the element count, or the first\n * missing part — because the only other symptom is a widget that silently adds\n * nothing, and the causes a static check cannot see — a server-rendered template,\n * a name that renders empty from a missing translation — would have no diagnostic\n * anywhere. It is written once per connection, so one authoring mistake cannot\n * flood the console.\n *\n * @example\n * ```ts\n * readonly #rows = new TemplateRow({\n * identifier: this.identifier,\n * root: \"tag\",\n * required: [\"label\"],\n * button: \"remove\",\n * outcome: \"added no tag\",\n * noun: \"chip template\",\n * });\n *\n * connect(): void {\n * this.#rows.connect();\n * }\n * ```\n */\nexport class TemplateRow<R extends string, O extends string = never> {\n readonly #options: TemplateRowOptions<R, O>;\n #warned = false;\n\n constructor(options: TemplateRowOptions<R, O>) {\n this.#options = options;\n }\n\n /** The attribute selector for one declared part, in this controller's namespace. */\n selector(name: string): string {\n return targetSelector(this.#options.identifier, name);\n }\n\n /** Re-arms the once-per-connection diagnostic. */\n connect(): void {\n this.#warned = false;\n }\n\n /**\n * Names one missing part on the console, at most once per connection, and\n * returns `null` so a caller can hand it straight back.\n */\n report(missing: string): null {\n return this.#say(`lacks ${missing}`);\n }\n\n /** Writes one diagnostic per connection, saying what the template got wrong. */\n #say(problem: string): null {\n if (this.#warned) return null;\n this.#warned = true;\n const { identifier, outcome, noun } = this.#options;\n console.warn(`Stimeo UI: \"${identifier}\" ${outcome} because its ${noun} ${problem}.`);\n return null;\n }\n\n /**\n * Clones the row and resolves its parts, or names what the template got wrong\n * and returns `null`. `values` fills the button's authored `aria-label`.\n */\n instantiate(\n template: HTMLTemplateElement,\n values: Record<string, string | number>,\n ): TemplateRowParts<R, O> | null {\n const { root: rootName, required, optional, button: buttonName } = this.#options;\n const root = cloneTemplateRoot(template);\n if (!root?.matches(this.selector(rootName))) {\n return this.report(`${this.#article(rootName)} \"${rootName}\" root`);\n }\n // One element, and it is the row: anything beside it would be left behind by\n // a removal that takes the row back, which is the accumulation this contract\n // exists to stop. The row here is the one that was asked for, so the count is\n // what the diagnostic names — a missing part would point at the wrong element.\n const count = template.content.children.length;\n if (count !== 1) {\n return this.#say(`holds ${count} elements, and the row has to be its only one`);\n }\n // Required parts are resolved before they are written, so the declared shape\n // holds for every row returned at all — a missing one leaves through `report`\n // instead. The accumulator is keyed loosely because that ordering is what\n // makes the narrower return type true, and no index signature can say so.\n const slots: Record<string, HTMLElement | null> = {};\n for (const name of required) {\n const part = this.#resolve(root, name);\n if (!part) return this.report(`${this.#article(name)} \"${name}\" target`);\n slots[name] = part;\n }\n for (const name of optional ?? []) slots[name] = this.#resolve(root, name);\n const named = this.#resolve(root, buttonName);\n if (!(named instanceof HTMLButtonElement)) {\n return this.report(`${this.#article(buttonName)} \"${buttonName}\" target <button>`);\n }\n const button = named;\n const name = button.getAttribute(\"aria-label\")?.trim() ?? \"\";\n if (name === \"\") return this.report(`a non-empty aria-label on its \"${buttonName}\" target`);\n button.setAttribute(\"aria-label\", fillTemplate(name, values));\n return { root, slots, button } as TemplateRowParts<R, O>;\n }\n\n /** The row may be its own part; otherwise the first descendant carrying the target. */\n #resolve(root: HTMLElement, name: string): HTMLElement | null {\n return matchingPart(root, this.selector(name));\n }\n\n #article(name: string): string {\n return /^[aeiou]/i.test(name) ? \"an\" : \"a\";\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { KeyedTimers } from \"../utils/keyed_timers\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\nimport { SafeInterval, SafeTimeout } from \"../utils/safe_timeout\";\nimport { sharedRegistry } from \"../utils/shared_registry\";\nimport { cloneTemplateRoot } from \"../utils/template_row\";\nimport { TransientHooks } from \"../utils/transient_hooks\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** The hooks a connection may find written by an earlier, now-gone one. */\nconst TRANSIENT = new TransientHooks({\n attributes: [\"data-present\", \"data-present-count\", \"data-presence-rejected\"],\n});\n\n/** A peer currently present (another client in the same room). */\ninterface Peer {\n name: string;\n}\n\n/**\n * Minimum ms between outgoing beacons. The steady state is the `heartbeat`\n * interval; this guard only bounds the *extra* beacons sent in response to\n * newly seen peers (see the roster-convergence note in the class doc), so a\n * burst of joiners cannot make this client flood the channel.\n */\nconst BEACON_THROTTLE_MS = 2000;\n\n/** Heartbeat period used when the declared one names no interval. */\nconst DEFAULT_HEARTBEAT_MS = 15_000;\n\n/** Peer expiry used when the declared one names no delay. */\nconst DEFAULT_TIMEOUT_MS = 40_000;\n\n/**\n * How many connected controllers currently speak for each peer, keyed by the\n * channel identifier and the peer's own id.\n *\n * The leaving notice says that a *peer* is gone, so only the last element\n * claiming that peer may send it. The shared subscription counts the members of\n * a wire identifier, which is a different set: elements claiming other ids —\n * and the other cable controllers — ride the same wire, and letting them speak\n * for this peer would either drop a peer that is still here or keep a departed\n * one in every roster until it expires.\n */\nconst speakers = sharedRegistry(\"stimeo-ui.presence.registry.v1\", () => new Map<string, number>());\n\n/**\n * Headless **presence** — a *server-bound* behavior: online dots / a\n * \"who's viewing this\" stack, bound to an Action Cable channel. Like\n * `stimeo--typing-indicator`, the server stays a trivial rebroadcast channel\n * with **zero presence state**: every client heartbeats an `appear` beacon\n * (`{ id, name }`), and each client expires peers it has not heard from for\n * `timeout` ms. Leaving (`{ id, leaving: true }`) is broadcast best-effort on\n * `disconnect()` and on `pagehide` (tab close / hard navigation, where\n * `disconnect()` never runs); a lost notice is caught by the expiry. Ships in\n * the opt-in `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer).\n *\n * The notice speaks for a *peer*, not for a wire subscription: on `disconnect()`\n * only the last element claiming a given `id` on a given identifier sends it, so a\n * sibling that stays mounted keeps the peer in every roster. `pagehide` takes the\n * whole page with it, so there every element sends and peers absorb the duplicates.\n *\n * Roster convergence: a late joiner would otherwise see peers only as their\n * next heartbeats arrive, so on hearing a beacon from an *unknown* peer, each\n * client re-announces itself (at most one such answer every 2s) — the\n * roster converges in one round-trip instead of one heartbeat period.\n *\n * Markup contract (identifier: `stimeo--presence`):\n * <div data-controller=\"stimeo--presence\"\n * data-stimeo--presence-channel-value=\"PresenceChannel\"\n * data-stimeo--presence-params-value='{\"room\":\"doc_7\"}'\n * data-stimeo--presence-id-value=\"17\" data-stimeo--presence-name-value=\"Alice\">\n * <span data-stimeo--presence-target=\"count\" data-other=\"%{count} viewing\"></span>\n * <ul data-stimeo--presence-target=\"list\"></ul>\n * <template data-stimeo--presence-target=\"template\">\n * <li><span data-presence-name></span></li>\n * </template>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class PresenceChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"presence:#{params[:room]}\"\n * def appear(data) = ActionCable.server.broadcast(\"presence:#{params[:room]}\",\n * { id: data[\"id\"], name: data[\"name\"], leaving: data[\"leaving\"] })\n * end\n *\n * `join` dispatches `{ id, name }`; `leave` dispatches `{ id }`; `change` dispatches\n * `{ users }`.\n *\n * @remarks\n * Behavior only — the dot/stack look is the consumer's CSS, keyed off the\n * `data-present` / `data-present-count` hooks. The roster counts and renders\n * **other** clients (`id` ≠ own `id`); rendering is optional: given a `list` +\n * `template` pair, one clone per peer is appended (elements marked\n * `data-presence-name` receive the name; the clone root is tagged\n * `data-presence-id`), and the `count` target renders through localizable\n * `data-zero` / `data-one` / `data-other` templates (`%{count}`). Richer\n * per-user rendering (avatars, links) belongs to the consumer via the `join` /\n * `leave` / `change` events or a server-rendered Turbo Stream. A `template` holds\n * one root element: the clone appended for a peer, and the node removed when the\n * peer goes, are that same element. Roster changes reach assistive tech only when\n * the consumer opts in through `announceJoinText` / `announceLeaveText` — reading\n * out every entry and exit of a busy room is noise, so silence is the default. The `id`\n * comparison is display-level echo suppression, not authentication — identity\n * belongs to the server. Sending tracks the full subscription lifecycle (via\n * the shared confirmation-aware subscription): beacons — heartbeats, the\n * convergence answer, the leaving notice — are dropped before confirmation and\n * while the connection is down, where Action Cable would discard them anyway\n * (and a discarded beacon must not burn the convergence throttle); `connected`\n * re-fires on every reconnect and force-beacons, so the roster self-heals. A\n * refused subscription publishes the `data-presence-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Presence is transient: `connect()` clears\n * whatever a Turbo cache snapshot preserved (hooks + rendered clones), renders\n * the known-empty count (the `data-present*` hooks stay absent until the first\n * beacon), and the stream re-populates; a `count` or `list` target swapped in by a\n * Stream or a morph arrives empty and is drawn from the roster as it connects; the subscription, heartbeat interval,\n * per-peer expiry timers, and the `pagehide` listener are all released on\n * `disconnect()` (Turbo navigation included).\n *\n * The live resources follow the declaration; Values changed together move them\n * once, after the batch, and nothing moves before `connect()` or after\n * `disconnect()`. A `channel` or `params` change moves the subscription: the room\n * being left gets the leaving notice under the same rules as `disconnect()`\n * (confirmed, an own `id`, the last voice for it), and its roster, expiry timers,\n * pending beacon and announcement, throttle window and rejected hook are dropped\n * without an event, as a fresh connection would, since none of them is a fact of\n * the new room. An `id` change keeps the subscription and its roster: the old id's\n * voice is released once (the last voice leaves), the new id beacons at once —\n * which also stands in for a convergence answer still queued — and a peer already\n * listed under the new id is this client now, so it stops being counted without a\n * `leave`: it went nowhere. A `heartbeat` change re-arms the running interval,\n * counted from the change. `timeout` arms each peer's expiry when its beacon\n * arrives, so a change applies from that peer's next beacon and never moves a\n * deadline already set.\n */\nexport class PresenceController extends Controller<HTMLElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override targets = [\"count\", \"list\", \"template\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n id: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n heartbeat: { type: Number, default: DEFAULT_HEARTBEAT_MS },\n timeout: { type: Number, default: DEFAULT_TIMEOUT_MS },\n announceJoinText: { type: String, default: \"\" },\n announceLeaveText: { type: String, default: \"\" },\n };\n\n static valueConstraints = {\n heartbeat: NUMBER_BOUNDS.positiveTimer,\n timeout: NUMBER_BOUNDS.positiveTimer,\n } satisfies NumberValueConstraints<typeof PresenceController.values>;\n static events = [\"join\", \"leave\", \"change\"] as const;\n\n declare readonly hasCountTarget: boolean;\n declare readonly countTarget: HTMLElement;\n declare readonly hasListTarget: boolean;\n declare readonly listTarget: HTMLElement;\n declare readonly hasTemplateTarget: boolean;\n declare readonly templateTarget: HTMLTemplateElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare idValue: string;\n declare nameValue: string;\n declare heartbeatValue: number;\n declare timeoutValue: number;\n declare announceJoinTextValue: string;\n declare announceLeaveTextValue: string;\n\n /** Delay (ms) before one roster change is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n #subscription: ConfirmedCableSubscription | null = null;\n readonly #follow = new MicrotaskCoalescer(() => this.#followDeclaration());\n /** The identifier the resources were last built for. */\n #identifier = \"\";\n /** The period the running heartbeat was armed with. */\n #period = 0;\n /** Present peers keyed by id (insertion order = join order). */\n readonly #peers = new Map<string, Peer>();\n /** Each peer's auto-expiry timer, restarted by every beacon from that peer. */\n readonly #expiry = new KeyedTimers<string>();\n readonly #timers = new SafeTimeout();\n readonly #intervals = new SafeInterval();\n /** Epoch ms of the last outgoing beacon, for the convergence throttle. */\n #lastBeaconAt = 0;\n /** Pending trailing-edge convergence beacon (at most one queued). */\n #pendingBeacon: number | null = null;\n /** The one outstanding announcement, so a newer roster change supersedes it. */\n #announceId: number | null = null;\n /**\n * Whether this controller is between `connect()` and `disconnect()`. Stimulus\n * delivers every target's departure after `disconnect()` has blanked the count\n * and emptied the list, and drawing from there would overwrite what teardown left.\n */\n #connected = false;\n /** The `speakers` key this element holds, recomputed on every claim. */\n #speakerKey = \"\";\n /** The id that key was claimed for, which the leaving notice names. */\n #speakerId = \"\";\n\n /** Moves the subscription to the declared channel. */\n channelValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Moves the subscription to the identifier the parameters name. */\n paramsValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Moves this element's voice to the declared id. */\n idValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Re-arms a running heartbeat on the declared period. */\n heartbeatValueChanged(): void {\n this.#follow.schedule();\n }\n\n /**\n * The identifier this declaration names. `params` adds identifier parameters;\n * its `channel` key is ignored, so the element's declared channel is preserved.\n * A malformed `params` declaration falls back to no\n * parameters, so the identifier keeps naming the channel instead of the\n * subscription never being created at all.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _declared, ...rest } = parseSubscriptionParams(this.paramsValue);\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * The beacon period, in ms: a finite, positive number a timer can hold. Anything\n * else names no interval — `setInterval` reads `NaN`, a negative value, `Infinity`\n * and a value past the maximum platform timer delay alike as \"as often as possible\", so\n * the declaration would flood the channel rather than heartbeat on it. Zero is out\n * for the same reason, which is why this bound is tighter than the non-negative one\n * a throttle or a plain delay can use. Such a declaration falls back to the default.\n */\n get #heartbeat(): number {\n return this.#safeHeartbeat;\n }\n\n /**\n * The silence after which a peer is dropped, in ms: a finite, positive number a\n * timer can hold. Anything else names no delay and every peer expires in the task\n * that records it, so the roster could never hold anyone. Such a declaration falls\n * back to the default.\n */\n get #timeout(): number {\n return this.#safeTimeout;\n }\n\n /**\n * Draws the roster into a list target, which arrives empty from a Turbo Stream\n * replacement or a morph. Stimulus runs this before `connect()` on the first mount\n * too, where the roster is still empty and clearing whatever a cache snapshot\n * preserved is what `connect()` goes on to do anyway.\n */\n listTargetConnected(): void {\n this.#drawList();\n }\n\n /** Draws the roster into the list target that stays when an earlier one leaves. */\n listTargetDisconnected(): void {\n if (this.#connected) this.#drawList();\n }\n\n /** Paints the roster size into a count target that arrives blank, for the same reason. */\n countTargetConnected(): void {\n this.#drawCount();\n }\n\n /** Paints the roster size into the count target that stays when an earlier one leaves. */\n countTargetDisconnected(): void {\n if (this.#connected) this.#drawCount();\n }\n\n override connect(): void {\n this.#connected = true;\n this.#follow.activate();\n this.#start();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#follow.cancel();\n this.#close();\n this.#reset();\n }\n\n /** Starts a cycle from the declaration on a known-empty roster. */\n #start(): void {\n // Presence is transient: drop whatever a Turbo cache snapshot preserved\n // (hooks + rendered clones); the live stream re-populates the roster.\n // Rejection is transient server state too — the fresh subscription below\n // re-decides the hook.\n this.#reset();\n // The roster is known-empty here, so the count target can say so right\n // away instead of sitting blank until the first roster change. The\n // data-present* hooks intentionally stay absent until the first beacon.\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(0);\n this.#open();\n }\n\n /**\n * Opens the subscription, voice, heartbeat and `pagehide` listener; none without a channel.\n *\n * @stimeoRuntimeOnly `channel`, `params`, `id` and `heartbeat` name what this call opens;\n * `name` is read by the handlers it wires.\n */\n #open(): void {\n const descriptor = this.#descriptor;\n this.#identifier = identifierOf(descriptor);\n if (!this.channelValue) return;\n this.#claimSpeaker();\n this.#subscription = createConfirmedSubscription(descriptor, {\n // The first beacon must wait for the confirmed subscription — a\n // perform() before that is silently dropped by Action Cable. Fires\n // again on every reconnect, so the roster self-heals after an outage.\n connected: () => this.#beacon(true),\n // The server refused the subscription: no beacon will ever go through,\n // and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-presence-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n this.#armHeartbeat();\n window.addEventListener(\"pagehide\", this.#onPageHide);\n }\n\n /** Releases what {@link #open} acquired. */\n #close(): void {\n window.removeEventListener(\"pagehide\", this.#onPageHide);\n // Best-effort graceful leave, and only from the last element speaking for\n // this peer; a lost notice is caught by peers' expiry timers instead.\n this.#sendLeaveNotice(this.#releaseSpeaker());\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n this.#intervals.clearAll();\n }\n\n /** Arms the heartbeat on the declared period, counted from now. */\n #armHeartbeat(): void {\n this.#intervals.clearAll();\n this.#period = this.#heartbeat;\n this.#intervals.set(() => this.#beacon(true), this.#period);\n }\n\n /**\n * Rebuilds what the declaration changed: a new identifier restarts the cycle,\n * otherwise only the voice or the heartbeat moves.\n *\n * @stimeoRuntimeOnly `channel`, `params`, `id` and `heartbeat` are compared with what the\n * resources were built from.\n */\n #followDeclaration(): void {\n if (identifierOf(this.#descriptor) !== this.#identifier) {\n this.#close();\n this.#start();\n } else if (this.#subscription) {\n if (this.idValue !== this.#speakerId) this.#moveVoice();\n if (this.#heartbeat !== this.#period) this.#armHeartbeat();\n }\n }\n\n /**\n * Leaves as the old id and beacons as the new one, on the same subscription.\n *\n * @stimeoRuntimeOnly `id` names the voice this call moves and `name` the beacon it sends.\n */\n #moveVoice(): void {\n this.#sendLeaveNotice(this.#releaseSpeaker());\n this.#claimSpeaker();\n this.#forget(this.#speakerId);\n this.#timers.clear(this.#pendingBeacon ?? -1);\n this.#pendingBeacon = null;\n this.#beacon(true);\n }\n\n /**\n * Registers this element as one of the voices for its peer, so a sibling on the\n * same channel claiming the same `id` keeps the peer present until they all go.\n * Whether the peer is worth announcing at all is {@link #sendLeaveNotice}'s\n * question, so an element with no own `id` claims a voice like any other and is\n * simply never heard.\n */\n #claimSpeaker(): void {\n this.#speakerId = this.idValue;\n this.#speakerKey = `${this.#identifier}\\n${this.#speakerId}`;\n speakers.set(this.#speakerKey, (speakers.get(this.#speakerKey) ?? 0) + 1);\n }\n\n /**\n * Gives up the claim, returning the id this element was the last voice for, else\n * `\"\"`. The key is spent here: an element that goes on to connect without a\n * channel claims nothing, and a stale key would spend a voice its siblings own.\n */\n #releaseSpeaker(): string {\n const key = this.#speakerKey;\n const id = this.#speakerId;\n this.#speakerKey = \"\";\n this.#speakerId = \"\";\n const remaining = (speakers.get(key) ?? 1) - 1;\n if (remaining > 0) speakers.set(key, remaining);\n else speakers.delete(key);\n return remaining > 0 ? \"\" : id;\n }\n\n /**\n * Sends the best-effort leaving notice for `id` (skipped for an empty one, and\n * outside the confirmed window, where Action Cable would discard it anyway).\n */\n #sendLeaveNotice(id: string): void {\n if (!this.#subscription?.confirmed || !id) return;\n this.#subscription.perform(\"appear\", { id, leaving: true });\n }\n\n /**\n * `pagehide` covers the leaves `disconnect()` cannot see: closing the tab or\n * a hard (non-Turbo) navigation destroys the page without running Stimulus\n * teardown, so this listener is the only chance to announce them. Best-effort\n * by nature (the socket may close before the frame flushes); peers' expiry\n * timers stay the safety net. If the page enters the bfcache and is restored\n * instead, the next heartbeat re-announces this client, so an over-eager\n * leave self-heals.\n */\n readonly #onPageHide = (): void => {\n this.#sendLeaveNotice(this.#speakerId);\n };\n\n /**\n * Broadcasts this client's beacon (skipped without an own `id`). A throttled\n * convergence answer is deferred to the trailing edge rather than dropped —\n * otherwise a peer joining right after a heartbeat would not learn about\n * this client until the next full heartbeat period.\n *\n * Gated on the confirmed subscription: before confirmation and during an\n * outage Action Cable silently discards perform(), so a beacon sent then is\n * pure waste — worse, it would burn `#lastBeaconAt` and delay the next real\n * convergence answer by up to the throttle window. The heartbeat interval\n * keeps ticking regardless; `connected` re-fires on reconfirm and\n * force-beacons immediately, so a gated tick is never missed for long.\n */\n #beacon(force: boolean): void {\n if (!this.#subscription?.confirmed || !this.idValue) return;\n const now = Date.now();\n const wait = BEACON_THROTTLE_MS - (now - this.#lastBeaconAt);\n if (!force && wait > 0) {\n if (this.#pendingBeacon === null) {\n this.#pendingBeacon = this.#timers.set(() => {\n this.#pendingBeacon = null;\n this.#beacon(true);\n }, wait);\n }\n return;\n }\n this.#lastBeaconAt = now;\n this.#subscription.perform(\"appear\", { id: this.idValue, name: this.nameValue });\n }\n\n /**\n * Tracks a broadcast beacon: upserts the peer (restarting its expiry timer),\n * removes it on a `leaving` notice, and re-announces this client when the\n * peer was unknown (roster convergence — see the class doc).\n *\n * @stimeoRuntimeOnly `id` filters this client's own echo, `timeout` arms one peer's expiry,\n * `name` answers one beacon and the texts word one announcement.\n */\n #onReceived(data: unknown): void {\n const beacon = data as { id?: unknown; name?: unknown; leaving?: unknown } | null;\n const id = beacon?.id;\n if (typeof id !== \"string\" || id === \"\" || id === this.idValue) return;\n\n if (beacon?.leaving === true) {\n this.#drop(id);\n return;\n }\n\n const name = typeof beacon?.name === \"string\" ? beacon.name : \"\";\n const existing = this.#peers.get(id);\n this.#expiry.set(id, () => this.#drop(id), this.#timeout);\n this.#peers.set(id, { name });\n\n if (existing === undefined) {\n this.#appendClone(id, name);\n this.#render();\n this.#announce(this.announceJoinTextValue, name);\n this.dispatch(\"join\", { detail: { id, name } });\n this.#beacon(false); // answer an unknown peer so its roster converges\n } else if (existing.name !== name) {\n this.#updateClone(id, name);\n this.#render();\n }\n }\n\n /**\n * Removes a peer (expiry or graceful leave) and reflects the change.\n *\n * @stimeoRuntimeOnly `announceLeaveText` words the one announcement this departure makes.\n */\n #drop(id: string): void {\n const peer = this.#peers.get(id);\n if (peer === undefined) return;\n this.#expiry.clear(id);\n this.#peers.delete(id);\n this.#removeClone(id);\n this.#render();\n this.#announce(this.announceLeaveTextValue, peer.name);\n this.dispatch(\"leave\", { detail: { id } });\n }\n\n /** Drops a peer that stopped being another client, reporting no departure. */\n #forget(id: string): void {\n if (!this.#peers.delete(id)) return;\n this.#expiry.clear(id);\n this.#removeClone(id);\n this.#paint();\n }\n\n /**\n * Reflects the roster onto the hooks + count target and emits `change`.\n */\n #render(): void {\n this.dispatch(\"change\", { detail: { users: this.#paint() } });\n }\n\n /** Writes the roster onto the hooks + count target. */\n #paint(): Array<{ id: string; name: string }> {\n const users = [...this.#peers.entries()].map(([id, peer]) => ({ id, name: peer.name }));\n this.element.setAttribute(\"data-present\", users.length > 0 ? \"true\" : \"false\");\n this.element.setAttribute(\"data-present-count\", String(users.length));\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(users.length);\n return users;\n }\n\n /**\n * Hands one roster change to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per peer — reading\n * out every entry and exit of a busy room is the noise the default avoids. Wording\n * comes from the consumer (`{name}` / `{count}`, where the count is the roster\n * after the change), and an undeclared template does nothing at all — not even\n * cancel an announcement already pending for the other direction.\n */\n #announce(template: string, name: string): void {\n const message = fillTemplate(template, { name, count: this.#peers.size });\n if (message.trim() === \"\") return;\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, PresenceController.#announceDelay);\n }\n\n /**\n * Builds the count copy. Localizable through `data-zero` / `data-one` /\n * `data-other` templates on the count target (`%{count}`); the bare number is\n * the fallback (copy-free, so nothing to localize by default).\n */\n #countMessage(count: number): string {\n const templates = this.countTarget.dataset;\n const template =\n (count === 0 ? templates.zero : count === 1 ? templates.one : templates.other) ??\n templates.other;\n return template ? template.replace(\"%{count}\", String(count)) : String(count);\n }\n\n /** Replaces the clones in the list target with one per present peer. */\n #drawList(): void {\n if (!this.hasListTarget) return;\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n for (const [id, peer] of this.#peers) this.#appendClone(id, peer.name);\n }\n\n /** Writes the roster size into the count target. */\n #drawCount(): void {\n if (this.hasCountTarget) this.countTarget.textContent = this.#countMessage(this.#peers.size);\n }\n\n /** Appends one template clone for a newly present peer (list + template only). */\n #appendClone(id: string, name: string): void {\n if (!this.hasListTarget || !this.hasTemplateTarget) return;\n const root = cloneTemplateRoot(this.templateTarget);\n if (!root) return;\n root.setAttribute(\"data-presence-id\", id);\n this.#fillName(root, name);\n this.listTarget.appendChild(root);\n }\n\n #updateClone(id: string, name: string): void {\n const root = this.#cloneFor(id);\n if (root) this.#fillName(root, name);\n }\n\n #removeClone(id: string): void {\n this.#cloneFor(id)?.remove();\n }\n\n #cloneFor(id: string): Element | null {\n if (!this.hasListTarget) return null;\n // Attribute selectors cannot escape arbitrary ids reliably; match manually.\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n if (child.getAttribute(\"data-presence-id\") === id) return child;\n }\n return null;\n }\n\n /** Writes the peer's name into the clone's `data-presence-name` slots. */\n #fillName(root: Element, name: string): void {\n const slots = root.querySelectorAll(\"[data-presence-name]\");\n for (const slot of slots) slot.textContent = name;\n if (slots.length === 0 && root.hasAttribute(\"data-presence-name\")) {\n root.textContent = name;\n }\n }\n\n /** Clears the transient roster state (every cycle's start + disconnect teardown). */\n #reset(): void {\n this.#timers.clearAll();\n this.#expiry.clearAll();\n this.#pendingBeacon = null;\n this.#announceId = null;\n this.#peers.clear();\n this.#lastBeaconAt = 0;\n TRANSIENT.reset(this.element);\n if (this.hasCountTarget) this.countTarget.textContent = \"\";\n if (this.hasListTarget) {\n for (const child of this.listTarget.querySelectorAll(\"[data-presence-id]\")) {\n child.remove();\n }\n }\n }\n /** Current `heartbeat` declaration resolved against its numeric contract. */\n get #safeHeartbeat(): number {\n return this.#numbers.read(\n this,\n \"heartbeat\",\n this.heartbeatValue,\n PresenceController.values.heartbeat.default,\n PresenceController.valueConstraints.heartbeat,\n );\n }\n\n /** Current `timeout` declaration resolved against its numeric contract. */\n get #safeTimeout(): number {\n return this.#numbers.read(\n this,\n \"timeout\",\n this.timeoutValue,\n PresenceController.values.timeout.default,\n PresenceController.valueConstraints.timeout,\n );\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { announce, fillTemplate } from \"../utils/announce\";\nimport { KeyedTimers } from \"../utils/keyed_timers\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { TransientHooks } from \"../utils/transient_hooks\";\nimport {\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n identifierOf,\n parseSubscriptionParams,\n} from \"./consumer\";\n\n/** The hooks a connection may find written by an earlier, now-gone one. */\nconst TRANSIENT = new TransientHooks({\n attributes: [\"data-typing\", \"data-typing-indicator-rejected\"],\n});\n\n/** Milliseconds of silence after which a typer is dropped. */\nconst DEFAULT_TIMEOUT = 3000;\n/** Minimum milliseconds between outgoing typing signals. */\nconst DEFAULT_THROTTLE = 2000;\n\n/**\n * Fills `%{token}` placeholders in a status template, in one pass.\n *\n * The values come off the wire — another client's display name — so they must never\n * reach a replacement *string*, where `$&`, `` $` ``, `$'` and `$$` expand into the\n * template's own text. A single pass also stops a name that happens to contain a token\n * from being re-substituted by a later one. Every occurrence is filled; a token this\n * call has no value for is left as authored, which keeps a typo visible instead of\n * blanking the word.\n */\nconst fillTokens = (template: string, values: Record<string, string>): string =>\n template.replace(/%\\{(name|names|count)\\}/g, (match, token: string) => values[token] ?? match);\n\n/**\n * Headless **typing indicator** — a *server-bound* behavior: a controller whose\n * state lives on the server stream, not in client memory. Typing in the composer\n * throttle-broadcasts a `typing` signal over an Action Cable channel; received\n * signals from *other* clients render \"X is typing…\" into a visible `status` slot\n * and auto-clear after `timeout` ms of silence. The whole behavior is HTML + a\n * broadcast — no app JS, no client store, no reconciler. Ships in the opt-in\n * `stimeo-ui/cable` subpath (`@rails/actioncable` optional peer); the core stays\n * zero-dep.\n *\n * Markup contract (identifier: `stimeo--typing-indicator`):\n * <div data-controller=\"stimeo--typing-indicator\"\n * data-stimeo--typing-indicator-channel-value=\"TypingChannel\"\n * data-stimeo--typing-indicator-params-value='{\"room\":\"chat_42\"}'\n * data-stimeo--typing-indicator-name-value=\"Alice\"\n * data-stimeo--typing-indicator-announce-one-text-value=\"{name} is typing\">\n * <textarea data-stimeo--typing-indicator-target=\"input\"></textarea>\n * <p data-stimeo--typing-indicator-target=\"status\"\n * data-one=\"%{name} is typing…\" data-many=\"%{names} are typing…\"></p>\n * </div>\n *\n * Server contract (a trivial rebroadcast channel):\n * class TypingChannel < ApplicationCable::Channel\n * def subscribed = stream_from \"typing:#{params[:room]}\"\n * def typing(data) = ActionCable.server.broadcast(\"typing:#{params[:room]}\",\n * { name: data[\"name\"] })\n * end\n *\n * The `status` target is a plain visible slot, not a live region: assistive tech is\n * reached through the page's shared announcer instead, so a set that settles is sent\n * there as `announceOneText` / `announceManyText` (`{name}` / `{names}` / `{count}`,\n * debounced). Both default to empty, and an empty message announces nothing — a page\n * that wants the indicator seen but not heard simply leaves them off.\n *\n * `change` dispatches `{ names }`.\n *\n * @remarks\n * Behavior only — the indicator's look is the consumer's CSS, keyed off the\n * `data-typing` hook; the copy is localizable through the `data-one` /\n * `data-many` templates (`%{name}` / `%{names}` / `%{count}`, terse English\n * fallback — the same channel design as `stimeo--sortable`). The own echo is\n * dropped by comparing the broadcast `name` against `name` (a same-name guard,\n * not authentication — identity belongs to the server). Sending tracks the full\n * subscription lifecycle (via the shared confirmation-aware subscription):\n * signals are dropped before confirmation and while the connection is down, so\n * an outage never burns the throttle window on undeliverable sends. A refused\n * subscription publishes the `data-typing-indicator-rejected` hook (cleared on\n * `connect()` — rejection is transient server state), mirroring\n * `data-live-counter-rejected`. Typing state is transient by nature:\n * `connect()` resets the hook and the status slot (a Turbo cache snapshot must\n * not resurrect a stale \"X is typing…\"), and re-population happens naturally\n * from the stream. The subscription, the per-typer timers, and the delegated\n * `input` listener are all released on `disconnect()` (Turbo navigation\n * included).\n *\n * The declared `channel` names the subscription: `params` *adds* identifier\n * parameters except `channel`, which is ignored. Without a declared channel\n * nothing is subscribed whatever `params` holds.\n *\n * The subscription follows the declaration: a `channel` or `params` change moves\n * it — once, after the batch, however many of the two changed together, and never\n * before `connect()` or after `disconnect()` — and drops the typers, their expiry\n * timers, a pending announcement, the throttle window and the rejected hook\n * without an event, as a fresh connection would, since none of them is a fact of\n * the new room. A `name` change keeps the subscription and the typers, except one\n * shown under the new name: that is this client now, so it is dropped with its\n * expiry and the display redrawn, without a `change` — it did not stop typing.\n * The own echo is still filtered by the current `name`. `timeout` arms each\n * typer's expiry when its signal arrives, so a change applies from that typer's\n * next signal and never moves a deadline already set.\n */\nexport class TypingIndicatorController extends Controller<HTMLElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override targets = [\"input\", \"status\"];\n static override values = {\n channel: { type: String, default: \"\" },\n params: { type: String, default: \"\" },\n name: { type: String, default: \"\" },\n timeout: { type: Number, default: DEFAULT_TIMEOUT },\n // Must stay below `timeout`: the throttle is leading-edge only (no trailing\n // send), so a receiver's display survives continuous typing only while a\n // fresh signal lands within its timeout window.\n throttle: { type: Number, default: DEFAULT_THROTTLE },\n announceOneText: { type: String, default: \"\" },\n announceManyText: { type: String, default: \"\" },\n };\n\n static valueConstraints = {\n timeout: NUMBER_BOUNDS.timer,\n throttle: NUMBER_BOUNDS.nonNegative,\n } satisfies NumberValueConstraints<typeof TypingIndicatorController.values>;\n static events = [\"change\"] as const;\n\n declare readonly hasStatusTarget: boolean;\n declare readonly statusTarget: HTMLElement;\n declare channelValue: string;\n declare paramsValue: string;\n declare nameValue: string;\n declare timeoutValue: number;\n declare throttleValue: number;\n declare announceOneTextValue: string;\n declare announceManyTextValue: string;\n\n /** Delay (ms) before one settled typer set is sent to the shared announcer. */\n static readonly #announceDelay = 200;\n\n #subscription: ConfirmedCableSubscription | null = null;\n readonly #follow = new MicrotaskCoalescer(() => this.#followDeclaration());\n /** The identifier the subscription was last built for. */\n #identifier = \"\";\n /** Names currently typing (other clients), in the order their first signal arrived. */\n readonly #typers = new Set<string>();\n /** Each typer's auto-clear timer, restarted by every further signal from that name. */\n readonly #expiry = new KeyedTimers<string>();\n readonly #timers = new SafeTimeout();\n /** Epoch ms of the last broadcast, for leading-edge throttling. */\n #lastSentAt = 0;\n /** The one outstanding announcement, so a newer set supersedes it. */\n #announceId: number | null = null;\n /**\n * Whether this controller is between `connect()` and `disconnect()`. Stimulus\n * delivers target callbacks before `connect()` and after `disconnect()`, where\n * the status is teardown's to leave empty.\n */\n #connected = false;\n\n /** Moves the subscription to the declared channel. */\n channelValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Moves the subscription to the identifier the parameters name. */\n paramsValueChanged(): void {\n this.#follow.schedule();\n }\n\n /** Stops showing a typer under the name this client now declares. */\n nameValueChanged(): void {\n this.#follow.schedule();\n }\n\n /**\n * The identifier this declaration names: the declared channel plus the other\n * parameters, whose own `channel` key is left out. A malformed `params`\n * declaration falls back to no parameters, so the identifier keeps naming the\n * channel instead of the subscription never being created at all.\n */\n get #descriptor(): Record<string, unknown> {\n const { channel: _declared, ...rest } = parseSubscriptionParams(this.paramsValue);\n return { channel: this.channelValue, ...rest };\n }\n\n /**\n * Paints the current copy into a `status` target that arrived at runtime.\n *\n * No event: the set of typers did not change, only the element that displays it.\n * Without this a region swapped in mid-conversation stays empty while `data-typing`\n * still says someone is typing, leaving the state in the visual hook alone. An empty\n * set paints the empty copy, so a region that arrives with an old one is cleared.\n */\n statusTargetConnected(): void {\n if (this.#connected) this.#paintStatus([...this.#typers]);\n }\n\n /** Paints the current copy, empty when nobody types, into the `status` target that stays. */\n statusTargetDisconnected(): void {\n if (this.#connected) this.#paintStatus([...this.#typers]);\n }\n\n override connect(): void {\n this.#connected = true;\n // Typing state is transient: a Turbo cache snapshot must not resurrect a\n // stale indicator, and the live stream re-populates naturally. Rejection is\n // transient server state too — the fresh subscription re-decides the hook.\n this.#reset();\n\n // Delegated on the container so the composer needs no per-input data-action\n // (and swapped/appended inputs keep working).\n this.element.addEventListener(\"input\", this.#onInput);\n this.#follow.activate();\n this.#open();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#follow.cancel();\n this.element.removeEventListener(\"input\", this.#onInput);\n this.#close();\n this.#reset();\n }\n\n /**\n * Opens the subscription the declaration names, or none without a channel.\n *\n * @stimeoRuntimeOnly `channel` and `params` name the one subscription this call opens.\n */\n #open(): void {\n const descriptor = this.#descriptor;\n this.#identifier = identifierOf(descriptor);\n if (!this.channelValue) return;\n // Confirmation tracking (connected / disconnected / rejected) lives in the\n // shared subscription; #onInput gates on its `confirmed` so an outage\n // doesn't burn the throttle window on dropped sends.\n this.#subscription = createConfirmedSubscription(descriptor, {\n // The server refused the subscription: the send gate stays shut for\n // good, and the hook lets the consumer's CSS reflect the dead stream.\n rejected: () => {\n this.element.setAttribute(\"data-typing-indicator-rejected\", \"true\");\n },\n received: (data: unknown) => this.#onReceived(data),\n });\n }\n\n /** Releases the subscription {@link #open} opened. */\n #close(): void {\n this.#subscription?.unsubscribe();\n this.#subscription = null;\n }\n\n /**\n * Restarts the cycle when the declaration names a different identifier, and\n * otherwise drops a typer shown under this client's own name.\n *\n * @stimeoRuntimeOnly `channel` and `params` are compared with what the subscription was\n * built from, and `name` picks the typer that is this client now.\n */\n #followDeclaration(): void {\n if (identifierOf(this.#descriptor) !== this.#identifier) {\n this.#close();\n this.#reset();\n this.#open();\n } else {\n this.#forget(this.nameValue);\n }\n }\n\n /** Drops a typer that stopped being another client, reporting no change. */\n #forget(name: string): void {\n if (!this.#typers.delete(name)) return;\n this.#expiry.clear(name);\n this.#paint();\n }\n\n /** Clears the transient typing state (every cycle's start + disconnect teardown). */\n #reset(): void {\n this.#timers.clearAll();\n this.#expiry.clearAll();\n this.#announceId = null;\n this.#typers.clear();\n TRANSIENT.reset(this.element);\n if (this.hasStatusTarget) this.statusTarget.textContent = \"\";\n this.#lastSentAt = 0;\n }\n\n /** Throttled (leading-edge) broadcast of this client's typing signal. */\n readonly #onInput = (): void => {\n // Before the confirmed subscription a perform() is silently dropped by\n // Action Cable — and recording #lastSentAt would ALSO throttle away the\n // first real send, so bail without touching the throttle state.\n if (!this.#subscription?.confirmed) return;\n const now = Date.now();\n if (now - this.#lastSentAt < this.#throttle) return;\n this.#lastSentAt = now;\n this.#subscription.perform(\"typing\", { name: this.nameValue });\n };\n\n /**\n * Tracks a broadcast typer. The own echo is dropped (same `name`); every\n * further signal from a name restarts its auto-clear timer, so the indicator\n * survives continuous typing and clears `timeout` ms after the last signal.\n *\n * @stimeoRuntimeOnly `name` filters this client's own echo and `timeout` arms one typer's expiry;\n * the typers shown come from what is received.\n */\n #onReceived(data: unknown): void {\n const name = (data as { name?: unknown } | null)?.name;\n if (typeof name !== \"string\" || name === \"\" || name === this.nameValue) return;\n\n const added = !this.#typers.has(name);\n this.#typers.add(name);\n this.#expiry.set(name, () => this.#untrack(name), this.#timeout);\n if (added) this.#render();\n }\n\n /**\n * The silence after which a typer is dropped, in ms: a finite, non-negative number\n * a timer can hold. Anything else names no delay — `setTimeout` reads `NaN`, a\n * negative value and `Infinity` alike as \"now\", and a value past\n * the maximum platform timer delay overflows to the same place — so the typer would\n * vanish in the same task it appeared and the indicator could never be seen. Such\n * a declaration falls back to the default.\n */\n get #timeout(): number {\n return this.#safeTimeout;\n }\n\n /**\n * The minimum gap between outgoing signals, in ms: a finite, non-negative number.\n * Anything else names no interval, and the gate then settles the same way at every\n * keystroke — `NaN` and a negative gap leave it open, so every keystroke broadcasts,\n * while `Infinity` is never exceeded, so nothing is ever sent. Such a declaration\n * falls back to the default.\n */\n get #throttle(): number {\n return this.#safeThrottle;\n }\n\n #untrack(name: string): void {\n this.#typers.delete(name);\n this.#render();\n }\n\n /**\n * Reflects the typer set onto the display, announces it, and emits `change`.\n */\n #render(): void {\n const names = this.#paint();\n this.#announce(names);\n this.dispatch(\"change\", { detail: { names } });\n }\n\n /**\n * Hands the settled typer set to the page's shared announcer.\n *\n * Debounced: a burst of arrivals is one announcement, not one per name. Only a\n * non-empty set is announced — that typing stopped is not news worth interrupting\n * a reader for, and the visible copy already clears. Wording comes from the\n * consumer, and {@link announce} drops an empty message, so an undeclared template\n * announces nothing at all.\n *\n * @stimeoRuntimeOnly The wording Values are read to compose one announcement; the\n * visible typer list is rendered from the set, not from them.\n */\n #announce(names: string[]): void {\n if (this.#announceId !== null) this.#timers.clear(this.#announceId);\n this.#announceId = null;\n if (names.length === 0) return;\n\n const message =\n names.length === 1\n ? fillTemplate(this.announceOneTextValue, { name: names[0] ?? \"\" })\n : fillTemplate(this.announceManyTextValue, {\n names: names.join(\", \"),\n count: names.length,\n });\n\n this.#announceId = this.#timers.set(() => {\n announce(message);\n this.#announceId = null;\n }, TypingIndicatorController.#announceDelay);\n }\n\n /** Writes the current typer set onto the hook and the status slot. */\n #paint(): string[] {\n const names = [...this.#typers];\n this.element.setAttribute(\"data-typing\", names.length > 0 ? \"true\" : \"false\");\n this.#paintStatus(names);\n return names;\n }\n\n /** Writes the copy for `names` into the status slot, when there is one. */\n #paintStatus(names: string[]): void {\n if (this.hasStatusTarget) this.statusTarget.textContent = this.#message(names);\n }\n\n /**\n * Builds the status copy. Localizable through `data-one` / `data-many`\n * templates on the status target (`%{name}` / `%{names}` / `%{count}`); terse\n * English is the fallback.\n */\n #message(names: string[]): string {\n if (names.length === 0) return \"\";\n const joined = names.join(\", \");\n if (names.length === 1) {\n const template = this.statusTarget.dataset.one;\n const name = names[0] ?? \"\";\n return template ? fillTokens(template, { name }) : `${name} is typing…`;\n }\n const template = this.statusTarget.dataset.many;\n return template\n ? fillTokens(template, { names: joined, count: String(names.length) })\n : `${joined} are typing…`;\n }\n /** Current `timeout` declaration resolved against its numeric contract. */\n get #safeTimeout(): number {\n return this.#numbers.read(\n this,\n \"timeout\",\n this.timeoutValue,\n TypingIndicatorController.values.timeout.default,\n TypingIndicatorController.valueConstraints.timeout,\n );\n }\n\n /** Current `throttle` declaration resolved against its numeric contract. */\n get #safeThrottle(): number {\n return this.#numbers.read(\n this,\n \"throttle\",\n this.throttleValue,\n TypingIndicatorController.values.throttle.default,\n TypingIndicatorController.valueConstraints.throttle,\n );\n }\n}\n","import type { Application } from \"@hotwired/stimulus\";\nimport { LiveCounterController } from \"./live_counter_controller\";\nimport { PresenceController } from \"./presence_controller\";\nimport { TypingIndicatorController } from \"./typing_indicator_controller\";\n\nexport {\n type CableConsumer,\n type CableSubscription,\n type CableSubscriptionMixin,\n type ConfirmedCableSubscription,\n createConfirmedSubscription,\n getCableConsumer,\n setCableConsumer,\n} from \"./consumer\";\nexport { LiveCounterController, PresenceController, TypingIndicatorController };\n\n/**\n * Opt-in **server-bound** behaviors for Stimeo — controllers whose state lives\n * on an Action Cable / Turbo stream rather than in client memory: presence,\n * typing indicators, live counters.\n *\n * **Why this is a separate entry point.** The core library is zero-runtime-dep:\n * `import \"stimeo-ui\"` pulls in nothing but `@hotwired/stimulus`. Binding the\n * DOM to a server stream genuinely needs `@rails/actioncable`, so — exactly\n * like `stimeo-ui/positioning` and `@floating-ui/dom` — that cost is opt-in:\n * this module lives at `stimeo-ui/cable`, `@rails/actioncable` is an *optional*\n * peer, and nothing in the core imports it.\n *\n * When the app already owns a consumer (`app/javascript/channels/consumer.js`),\n * hand it over once at boot so no second websocket is opened:\n *\n * ```ts\n * import consumer from \"./channels/consumer\";\n * import { registerCable, setCableConsumer } from \"stimeo-ui/cable\";\n *\n * setCableConsumer(consumer);\n * registerCable(application);\n * ```\n */\nexport const cableControllers = {\n \"stimeo--live-counter\": LiveCounterController,\n \"stimeo--presence\": PresenceController,\n \"stimeo--typing-indicator\": TypingIndicatorController,\n} as const;\n\n/**\n * Registers the opt-in server-bound controllers on a Stimulus Application\n * (additive to `registerStimeo`, mirroring `registerPositioning`).\n */\nexport function registerCable(application: Application): void {\n for (const [identifier, controller] of Object.entries(cableControllers)) {\n application.register(identifier, controller);\n }\n}\n"]}