stimeo-ui 0.16.0 → 0.18.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 (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  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 +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  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 +300 -171
  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 +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  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 +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  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 +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  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 +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  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 +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  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 +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  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 +396 -192
  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 +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,19 +1,47 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/textarea_autosize_controller.ts
3
+ var ListenerSet = class {
4
+ #abort = new AbortController();
5
+ add(target, type, handler, options) {
6
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
7
+ }
8
+ dispose() {
9
+ this.#abort.abort();
10
+ this.#abort = new AbortController();
11
+ }
12
+ };
13
+
14
+ var FontLoadWatcher = class {
15
+ #listeners = new ListenerSet();
16
+ #onSettled;
17
+ #fonts = null;
18
+ constructor(run) {
19
+ this.#onSettled = () => run();
20
+ }
21
+ observe(element) {
22
+ const fonts = element.ownerDocument.fonts ?? null;
23
+ if (fonts === this.#fonts) return;
24
+ this.disconnect();
25
+ if (!fonts) return;
26
+ this.#fonts = fonts;
27
+ this.#listeners.add(fonts, "loadingdone", this.#onSettled);
28
+ this.#listeners.add(fonts, "loadingerror", this.#onSettled);
29
+ }
30
+ disconnect() {
31
+ this.#listeners.dispose();
32
+ this.#fonts = null;
33
+ }
34
+ };
4
35
 
5
- // src/utils/layout_observer.ts
6
36
  var LayoutObserver = class {
7
37
  #callback;
8
38
  #resizeObserverFactory;
9
39
  #resizeObserver = null;
10
40
  #observingViewport = false;
11
41
  #loadContainer = null;
12
- /** Stable bound handler so add/removeEventListener target the same reference. */
13
42
  #handleViewportResize = () => {
14
43
  this.#callback();
15
44
  };
16
- /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
17
45
  #handleDescendantLoad = () => {
18
46
  this.#callback();
19
47
  };
@@ -21,11 +49,6 @@ var LayoutObserver = class {
21
49
  this.#callback = callback;
22
50
  this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
23
51
  }
24
- /**
25
- * Starts observing an element's size. Repeated calls observe additional
26
- * elements through the same shared observer. No-ops when no
27
- * `ResizeObserver` implementation is available.
28
- */
29
52
  observe(element) {
30
53
  if (!this.#resizeObserverFactory) return;
31
54
  if (!this.#resizeObserver) {
@@ -35,47 +58,28 @@ var LayoutObserver = class {
35
58
  }
36
59
  this.#resizeObserver.observe(element);
37
60
  }
38
- /** Stops observing a single element while leaving any others in place. */
39
61
  unobserve(element) {
40
62
  this.#resizeObserver?.unobserve(element);
41
63
  }
42
- /** Starts observing viewport resizes. Idempotent: the listener is added once. */
43
64
  observeViewport() {
44
65
  if (this.#observingViewport) return;
45
66
  this.#observingViewport = true;
46
67
  window.addEventListener("resize", this.#handleViewportResize);
47
68
  }
48
- /** Stops observing viewport resizes without affecting element observation. */
49
69
  unobserveViewport() {
50
70
  if (!this.#observingViewport) return;
51
71
  this.#observingViewport = false;
52
72
  window.removeEventListener("resize", this.#handleViewportResize);
53
73
  }
54
- /**
55
- * Starts reporting a `load` from anywhere inside `container` — an image or a
56
- * frame settling changes the box it sits in, and it measures as zero high until
57
- * then. `load` does not bubble, so the subscription is a capture-phase listener
58
- * on the container itself and nothing the caller spells.
59
- *
60
- * **One container at a time.** A further call moves the observation, so a widget
61
- * whose content element is swapped at runtime releases the element it let go by
62
- * naming the new one — there is no second place for the release to drift from.
63
- */
64
74
  observeDescendantLoads(container) {
65
75
  this.unobserveDescendantLoads();
66
76
  this.#loadContainer = container;
67
77
  container.addEventListener("load", this.#handleDescendantLoad, true);
68
78
  }
69
- /** Stops reporting descendant loads without affecting element or viewport observation. */
70
79
  unobserveDescendantLoads() {
71
80
  this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
72
81
  this.#loadContainer = null;
73
82
  }
74
- /**
75
- * Releases every observation: disconnects the {@link ResizeObserver} and removes
76
- * the viewport and descendant-load listeners. Safe to call multiple times. Call
77
- * this from a controller's `disconnect()`.
78
- */
79
83
  disconnect() {
80
84
  this.#resizeObserver?.disconnect();
81
85
  this.#resizeObserver = null;
@@ -84,29 +88,70 @@ var LayoutObserver = class {
84
88
  }
85
89
  };
86
90
 
87
- // src/controllers/textarea_autosize_controller.ts
91
+ var NUMBER_BOUNDS = {
92
+ nonNegative: { finite: true, min: 0 }};
93
+ function matchesNumberBounds(value, bounds) {
94
+ if (!Number.isFinite(value)) {
95
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
96
+ if (direction === null) return false;
97
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
98
+ }
99
+ if (bounds.min !== void 0 && value < bounds.min) return false;
100
+ if (bounds.max !== void 0 && value > bounds.max) return false;
101
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
102
+ if (bounds.integer && !Number.isInteger(value)) return false;
103
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
104
+ return true;
105
+ }
106
+
107
+ function readNumber(raw, fallback, bounds) {
108
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
109
+ }
110
+
111
+ var NumberValueReader = class {
112
+ #lastRejected = /* @__PURE__ */ new Map();
113
+ read(owner, name, raw, fallback, bounds) {
114
+ const resolved = readNumber(raw, fallback, bounds);
115
+ if (matchesNumberBounds(raw, bounds)) {
116
+ this.#lastRejected.delete(name);
117
+ return resolved;
118
+ }
119
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
120
+ const literal = owner.element.getAttribute(attribute);
121
+ if (literal === null) {
122
+ this.#lastRejected.delete(name);
123
+ return resolved;
124
+ }
125
+ if (this.#lastRejected.get(name) !== literal) {
126
+ this.#lastRejected.set(name, literal);
127
+ console.warn(
128
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
129
+ );
130
+ }
131
+ return resolved;
132
+ }
133
+ };
134
+
88
135
  function px(value) {
89
136
  const n = Number.parseFloat(value);
90
137
  return Number.isNaN(n) ? 0 : n;
91
138
  }
92
- var TextareaAutosizeController = class extends Controller {
139
+ var TextareaAutosizeController = class _TextareaAutosizeController extends Controller {
140
+ #numbers = new NumberValueReader();
93
141
  static values = {
94
142
  minRows: { type: Number, default: 1 },
95
143
  maxRows: { type: Number, default: 0 }
96
144
  };
145
+ static valueConstraints = {
146
+ minRows: { finite: true, min: 1 },
147
+ maxRows: NUMBER_BOUNDS.nonNegative
148
+ };
97
149
  static actions = ["resize"];
98
150
  static events = ["resize"];
99
151
  #lastHeight = -1;
100
152
  #lastWidth = -1;
101
153
  #started = false;
102
154
  #pending = false;
103
- #fonts = null;
104
- /**
105
- * Content triggers arriving while the box is collapsed (`display: none`
106
- * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would
107
- * clamp the kept height to the `minRows` floor and dispatch a bogus
108
- * `resize`. The next report from a rendered box flushes the deferral.
109
- */
110
155
  #remeasure = () => {
111
156
  if (this.element.clientWidth === 0) {
112
157
  this.#pending = true;
@@ -114,16 +159,7 @@ var TextareaAutosizeController = class extends Controller {
114
159
  }
115
160
  this.resize();
116
161
  };
117
- /**
118
- * Re-measures when the content width changed (re-wrapping, including a
119
- * horizontal padding change under `border-box`), the applied inline height
120
- * was stripped (a Turbo morph syncing attributes from server HTML that has no
121
- * `style`), or a content trigger was deferred while the box was collapsed.
122
- * This instance's own height writes match none of these signals, so they
123
- * cannot re-enter; an authored inline height (e.g. a user dragging the native
124
- * resize handle) is left alone until the next content trigger. Reports from a
125
- * still-collapsed box are skipped (nothing is measurable there).
126
- */
162
+ #fonts = new FontLoadWatcher(this.#remeasure);
127
163
  #layout = new LayoutObserver(() => {
128
164
  if (this.element.clientWidth === 0) return;
129
165
  const width = this.#contentWidth();
@@ -132,32 +168,29 @@ var TextareaAutosizeController = class extends Controller {
132
168
  this.#lastWidth = width;
133
169
  this.resize();
134
170
  });
171
+ #listeners = new ListenerSet();
135
172
  connect() {
136
173
  this.#lastHeight = Math.round(px(this.element.style.height));
137
174
  this.#lastWidth = this.#contentWidth();
138
- this.element.addEventListener("input", this.#remeasure);
139
- this.element.addEventListener("change", this.#remeasure);
175
+ this.#listeners.add(this.element, "input", this.#remeasure);
176
+ this.#listeners.add(this.element, "change", this.#remeasure);
140
177
  this.#layout.observe(this.element);
141
- this.#bindFonts();
178
+ this.#fonts.observe(this.element);
142
179
  this.#started = true;
143
180
  this.resize();
144
181
  }
145
182
  disconnect() {
146
183
  this.#started = false;
147
- this.element.removeEventListener("input", this.#remeasure);
148
- this.element.removeEventListener("change", this.#remeasure);
184
+ this.#listeners.dispose();
149
185
  this.#layout.disconnect();
150
- this.#unbindFonts();
186
+ this.#fonts.disconnect();
151
187
  }
152
- /** Re-clamps when application code or a Turbo morph changes `minRows`. */
153
188
  minRowsValueChanged() {
154
189
  if (this.#started) this.#remeasure();
155
190
  }
156
- /** Re-clamps when application code or a Turbo morph changes `maxRows`. */
157
191
  maxRowsValueChanged() {
158
192
  if (this.#started) this.#remeasure();
159
193
  }
160
- /** Re-measures the content and applies the clamped height. */
161
194
  resize() {
162
195
  this.#pending = false;
163
196
  const el = this.element;
@@ -169,10 +202,10 @@ var TextareaAutosizeController = class extends Controller {
169
202
  el.style.height = "auto";
170
203
  const contentHeight = Math.max(0, el.scrollHeight - paddingV);
171
204
  const rows = Math.max(1, Math.round(contentHeight / lineHeight));
172
- let targetContent = Math.max(contentHeight, this.minRowsValue * lineHeight);
205
+ let targetContent = Math.max(contentHeight, this.#safeMinRows * lineHeight);
173
206
  let atMax = false;
174
- if (this.maxRowsValue > 0) {
175
- const maxContent = this.maxRowsValue * lineHeight;
207
+ if (this.#safeMaxRows > 0) {
208
+ const maxContent = this.#safeMaxRows * lineHeight;
176
209
  if (targetContent > maxContent) {
177
210
  targetContent = maxContent;
178
211
  atMax = true;
@@ -193,34 +226,35 @@ var TextareaAutosizeController = class extends Controller {
193
226
  this.dispatch("resize", { detail: { height, rows } });
194
227
  }
195
228
  }
196
- /** Content-box width — the wrapping input, unaffected by this instance's height writes. */
197
229
  #contentWidth() {
198
230
  const style = window.getComputedStyle(this.element);
199
231
  return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));
200
232
  }
201
- /** Resolved line height, falling back to ~1.2× font-size when `normal`. */
202
233
  #lineHeight(style) {
203
234
  const lh = px(style.lineHeight);
204
235
  if (lh > 0) return lh;
205
236
  const fontSize = px(style.fontSize);
206
237
  return fontSize > 0 ? fontSize * 1.2 : 16;
207
238
  }
208
- /** Subscribes to font completion when the current engine exposes `document.fonts`. */
209
- #bindFonts() {
210
- const fonts = this.element.ownerDocument.fonts;
211
- if (!fonts) return;
212
- this.#fonts = fonts;
213
- fonts.addEventListener("loadingdone", this.#remeasure);
214
- fonts.addEventListener("loadingerror", this.#remeasure);
239
+ get #safeMinRows() {
240
+ return this.#numbers.read(
241
+ this,
242
+ "minRows",
243
+ this.minRowsValue,
244
+ _TextareaAutosizeController.values.minRows.default,
245
+ _TextareaAutosizeController.valueConstraints.minRows
246
+ );
215
247
  }
216
- /** Removes the font completion subscriptions. */
217
- #unbindFonts() {
218
- this.#fonts?.removeEventListener("loadingdone", this.#remeasure);
219
- this.#fonts?.removeEventListener("loadingerror", this.#remeasure);
220
- this.#fonts = null;
248
+ get #safeMaxRows() {
249
+ return this.#numbers.read(
250
+ this,
251
+ "maxRows",
252
+ this.maxRowsValue,
253
+ _TextareaAutosizeController.values.maxRows.default,
254
+ _TextareaAutosizeController.valueConstraints.maxRows
255
+ );
221
256
  }
222
257
  };
223
258
 
224
259
  export { TextareaAutosizeController };
225
- //# sourceMappingURL=textarea_autosize_controller.js.map
226
260
  //# sourceMappingURL=textarea_autosize_controller.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/layout_observer.ts","../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":"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","file":"textarea_autosize_controller.js","sourcesContent":["/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\n\n/** Parses a CSS pixel length, defaulting to 0 for `auto` / empty / `normal`. */\nfunction px(value: string): number {\n const n = Number.parseFloat(value);\n return Number.isNaN(n) ? 0 : n;\n}\n\n/** The event surface used by `document.fonts` without requiring it in older engines. */\ninterface FontEventSource {\n addEventListener(type: string, listener: EventListener): void;\n removeEventListener(type: string, listener: EventListener): void;\n}\n\n/**\n * Headless autosize behavior for a `<textarea>`: grows the element to fit its\n * content (and clamps to `minRows` / `maxRows`, scrolling past the max). No APG\n * pattern; supports WCAG 1.4.4 by following the text as it grows.\n *\n * Markup contract (identifier: `stimeo--textarea-autosize`):\n * <textarea\n * data-controller=\"stimeo--textarea-autosize\"\n * data-stimeo--textarea-autosize-max-rows-value=\"10\"></textarea>\n *\n * Each re-measure collapses the element (`height:auto`), reads `scrollHeight`,\n * and sets an explicit pixel height clamped between `minRows` and `maxRows` (in\n * line-height units), toggling internal scrolling and the `data-at-max-rows`\n * hook at the cap. Height-only changes preserve focus and caret.\n *\n * Re-measure triggers: `connect()`; `input` and `change` on the element\n * (`change` covers programmatic writers that announce the write with a bubbling\n * event, the convention Stimeo's own field-writing controllers follow); element\n * size reports via the shared `LayoutObserver` — width changes\n * (re-wrapping) and the applied inline height being stripped (a Turbo morph\n * syncing attributes); font loading settling on `document.fonts`; runtime\n * `minRows` / `maxRows` changes; and the explicit `resize` action. A silent\n * `.value` assignment produces no DOM signal, so callers follow it with\n * `resize` or dispatch `input` / `change`.\n *\n * `resize` dispatches `{ height, rows }` only when the applied height differs\n * from the height this instance last applied. `connect()` adopts a height left\n * on the element by a previous connection (Turbo restore / morph), so restoring\n * an unchanged element stays silent.\n *\n * @remarks\n * Behavior only — the height is written to the element's own inline style, never\n * a CSS class. State lives on the element (no module-scope state), so `connect()`\n * re-measures after a Turbo navigation / morph. Every subscription (`input` /\n * `change` listeners, the layout observer, the font listeners) is released on\n * `disconnect()` (Turbo navigation included). Measurement is synchronous, so\n * there is no pending animation frame to tear down. The layout callback\n * re-measures only when the element's width changed or the applied inline\n * height vanished, so the controller's own height writes do not re-enter it.\n */\nexport class TextareaAutosizeController extends Controller<HTMLTextAreaElement> {\n static override values = {\n minRows: { type: Number, default: 1 },\n maxRows: { type: Number, default: 0 },\n };\n static actions = [\"resize\"] as const;\n static events = [\"resize\"] as const;\n\n declare minRowsValue: number;\n declare maxRowsValue: number;\n\n #lastHeight = -1;\n #lastWidth = -1;\n #started = false;\n #pending = false;\n #fonts: FontEventSource | null = null;\n\n /**\n * Content triggers arriving while the box is collapsed (`display: none`\n * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would\n * clamp the kept height to the `minRows` floor and dispatch a bogus\n * `resize`. The next report from a rendered box flushes the deferral.\n */\n readonly #remeasure = (): void => {\n if (this.element.clientWidth === 0) {\n this.#pending = true;\n return;\n }\n this.resize();\n };\n\n /**\n * Re-measures when the content width changed (re-wrapping, including a\n * horizontal padding change under `border-box`), the applied inline height\n * was stripped (a Turbo morph syncing attributes from server HTML that has no\n * `style`), or a content trigger was deferred while the box was collapsed.\n * This instance's own height writes match none of these signals, so they\n * cannot re-enter; an authored inline height (e.g. a user dragging the native\n * resize handle) is left alone until the next content trigger. Reports from a\n * still-collapsed box are skipped (nothing is measurable there).\n */\n readonly #layout = new LayoutObserver(() => {\n if (this.element.clientWidth === 0) return;\n const width = this.#contentWidth();\n const stripped = this.element.style.height === \"\";\n if (width === this.#lastWidth && !stripped && !this.#pending) return;\n this.#lastWidth = width;\n this.resize();\n });\n\n override connect(): void {\n // Adopt a height applied by a previous connection (kept by Turbo restore /\n // morph) so re-measuring an unchanged element does not re-dispatch `resize`.\n this.#lastHeight = Math.round(px(this.element.style.height));\n this.#lastWidth = this.#contentWidth();\n this.element.addEventListener(\"input\", this.#remeasure);\n this.element.addEventListener(\"change\", this.#remeasure);\n this.#layout.observe(this.element);\n this.#bindFonts();\n this.#started = true;\n this.resize();\n }\n\n override disconnect(): void {\n this.#started = false;\n this.element.removeEventListener(\"input\", this.#remeasure);\n this.element.removeEventListener(\"change\", this.#remeasure);\n this.#layout.disconnect();\n this.#unbindFonts();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `minRows`. */\n minRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `maxRows`. */\n maxRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-measures the content and applies the clamped height. */\n resize(): void {\n this.#pending = false;\n const el = this.element;\n const style = window.getComputedStyle(el);\n const lineHeight = this.#lineHeight(style);\n const paddingV = px(style.paddingTop) + px(style.paddingBottom);\n const borderV = px(style.borderTopWidth) + px(style.borderBottomWidth);\n const borderBox = style.boxSizing === \"border-box\";\n\n // Collapse first so scrollHeight reflects the content, not the prior height.\n el.style.height = \"auto\";\n const contentHeight = Math.max(0, el.scrollHeight - paddingV);\n const rows = Math.max(1, Math.round(contentHeight / lineHeight));\n\n let targetContent = Math.max(contentHeight, this.minRowsValue * lineHeight);\n let atMax = false;\n if (this.maxRowsValue > 0) {\n const maxContent = this.maxRowsValue * lineHeight;\n if (targetContent > maxContent) {\n targetContent = maxContent;\n atMax = true;\n }\n }\n\n const boxExtra = borderBox ? paddingV + borderV : 0;\n const height = Math.round(targetContent + boxExtra);\n el.style.height = `${height}px`;\n el.style.overflowY = atMax ? \"auto\" : \"hidden\";\n\n if (atMax) {\n el.setAttribute(\"data-at-max-rows\", \"true\");\n } else {\n el.removeAttribute(\"data-at-max-rows\");\n }\n el.style.setProperty(\"--stimeo--textarea-rows\", String(rows));\n\n if (height !== this.#lastHeight) {\n this.#lastHeight = height;\n this.dispatch(\"resize\", { detail: { height, rows } });\n }\n }\n\n /** Content-box width — the wrapping input, unaffected by this instance's height writes. */\n #contentWidth(): number {\n const style = window.getComputedStyle(this.element);\n return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));\n }\n\n /** Resolved line height, falling back to ~1.2× font-size when `normal`. */\n #lineHeight(style: CSSStyleDeclaration): number {\n const lh = px(style.lineHeight);\n if (lh > 0) return lh;\n const fontSize = px(style.fontSize);\n return fontSize > 0 ? fontSize * 1.2 : 16;\n }\n\n /** Subscribes to font completion when the current engine exposes `document.fonts`. */\n #bindFonts(): void {\n const fonts = (this.element.ownerDocument as Document & { fonts?: FontEventSource }).fonts;\n if (!fonts) return;\n this.#fonts = fonts;\n fonts.addEventListener(\"loadingdone\", this.#remeasure);\n fonts.addEventListener(\"loadingerror\", this.#remeasure);\n }\n\n /** Removes the font completion subscriptions. */\n #unbindFonts(): void {\n this.#fonts?.removeEventListener(\"loadingdone\", this.#remeasure);\n this.#fonts?.removeEventListener(\"loadingerror\", this.#remeasure);\n this.#fonts = null;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/font_load_watcher.ts","../../src/utils/layout_observer.ts","../../src/utils/number_bounds.ts","../../src/utils/coerce.ts","../../src/utils/number_value.ts","../../src/controllers/textarea_autosize_controller.ts"],"names":[],"mappings":"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","file":"textarea_autosize_controller.js","sourcesContent":["/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\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.\n * {@link 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 * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\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, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\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. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): 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\";\n\n/**\n * Follows font loading completion in an element's current owner document.\n *\n * Both `loadingdone` and `loadingerror` run the supplied callback synchronously.\n * Measurement and notification policy stay with the caller. Observing the same\n * source preserves its registrations; changing the source releases the former\n * one, including when the next document has no fonts API. Each instance owns its\n * callback identity so independent watchers can safely share a callback.\n */\nexport class FontLoadWatcher {\n readonly #listeners = new ListenerSet();\n readonly #onSettled: EventListener;\n #fonts: EventTarget | null = null;\n\n /** @param run - the consumer's synchronous font-completion measurement. */\n constructor(run: () => void) {\n this.#onSettled = () => run();\n }\n\n /** Follows the element's current font source; a missing API leaves no subscription. */\n observe(element: Element): void {\n const fonts = element.ownerDocument.fonts ?? null;\n if (fonts === this.#fonts) return;\n this.disconnect();\n if (!fonts) return;\n this.#fonts = fonts;\n this.#listeners.add(fonts, \"loadingdone\", this.#onSettled);\n this.#listeners.add(fonts, \"loadingerror\", this.#onSettled);\n }\n\n /** Releases the held source synchronously; repeated calls and later observation are safe. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#fonts = null;\n }\n}\n","/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\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/** Numeric update input, accepting decoded action params before CustomEvent detail. */\nexport type NumericValueEvent = Event & {\n params?: { amount?: unknown };\n detail?: { value?: unknown };\n};\n\n/** Reads the nullish action-param/detail precedence without falling back from invalid input. */\nexport function eventNumber(event: NumericValueEvent): number | null {\n return toFiniteNumber(event.params?.amount ?? event.detail?.value);\n}\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 { Controller } from \"@hotwired/stimulus\";\nimport { FontLoadWatcher } from \"../utils/font_load_watcher\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\n\n/** Parses a CSS pixel length, defaulting to 0 for `auto` / empty / `normal`. */\nfunction px(value: string): number {\n const n = Number.parseFloat(value);\n return Number.isNaN(n) ? 0 : n;\n}\n\n/**\n * Headless autosize behavior for a `<textarea>`: grows the element to fit its\n * content (and clamps to `minRows` / `maxRows`, scrolling past the max). No APG\n * pattern; supports WCAG 1.4.4 by following the text as it grows.\n *\n * Markup contract (identifier: `stimeo--textarea-autosize`):\n * <textarea\n * data-controller=\"stimeo--textarea-autosize\"\n * data-stimeo--textarea-autosize-max-rows-value=\"10\"></textarea>\n *\n * Each re-measure collapses the element (`height:auto`), reads `scrollHeight`,\n * and sets an explicit pixel height clamped between `minRows` and `maxRows` (in\n * line-height units), toggling internal scrolling and the `data-at-max-rows`\n * hook at the cap. Height-only changes preserve focus and caret.\n *\n * Re-measure triggers: `connect()`; `input` and `change` on the element\n * (`change` covers programmatic writers that announce the write with a bubbling\n * event, the convention Stimeo's own field-writing controllers follow); element\n * size reports via the shared `LayoutObserver` — width changes\n * (re-wrapping) and the applied inline height being stripped (a Turbo morph\n * syncing attributes); font loading settling on `ownerDocument.fonts`; runtime\n * `minRows` / `maxRows` changes; and the explicit `resize` action. A silent\n * `.value` assignment produces no DOM signal, so callers follow it with\n * `resize` or dispatch `input` / `change`.\n *\n * `resize` dispatches `{ height, rows }` only when the applied height differs\n * from the height this instance last applied. `connect()` adopts a height left\n * on the element by a previous connection (Turbo restore / morph), so restoring\n * an unchanged element stays silent.\n *\n * @remarks\n * Behavior only — the height is written to the element's own inline style, never\n * a CSS class. State lives on the element (no module-scope state), so `connect()`\n * re-measures after a Turbo navigation / morph. Every subscription (`input` /\n * `change` listeners, the layout observer, the font listeners) is released on\n * `disconnect()` (Turbo navigation included). Measurement is synchronous, so\n * there is no pending animation frame to tear down. The layout callback\n * re-measures only when the element's width changed or the applied inline\n * height vanished, so the controller's own height writes do not re-enter it.\n */\nexport class TextareaAutosizeController extends Controller<HTMLTextAreaElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override values = {\n minRows: { type: Number, default: 1 },\n maxRows: { type: Number, default: 0 },\n };\n\n static valueConstraints = {\n minRows: { finite: true, min: 1 },\n maxRows: NUMBER_BOUNDS.nonNegative,\n } satisfies NumberValueConstraints<typeof TextareaAutosizeController.values>;\n static actions = [\"resize\"] as const;\n static events = [\"resize\"] as const;\n\n declare minRowsValue: number;\n declare maxRowsValue: number;\n\n #lastHeight = -1;\n #lastWidth = -1;\n #started = false;\n #pending = false;\n\n /**\n * Content triggers arriving while the box is collapsed (`display: none`\n * ancestors) are deferred: `scrollHeight` is 0 there, so measuring would\n * clamp the kept height to the `minRows` floor and dispatch a bogus\n * `resize`. The next report from a rendered box flushes the deferral.\n */\n readonly #remeasure = (): void => {\n if (this.element.clientWidth === 0) {\n this.#pending = true;\n return;\n }\n this.resize();\n };\n readonly #fonts = new FontLoadWatcher(this.#remeasure);\n\n /**\n * Re-measures when the content width changed (re-wrapping, including a\n * horizontal padding change under `border-box`), the applied inline height\n * was stripped (a Turbo morph syncing attributes from server HTML that has no\n * `style`), or a content trigger was deferred while the box was collapsed.\n * This instance's own height writes match none of these signals, so they\n * cannot re-enter; an authored inline height (e.g. a user dragging the native\n * resize handle) is left alone until the next content trigger. Reports from a\n * still-collapsed box are skipped (nothing is measurable there).\n */\n readonly #layout = new LayoutObserver(() => {\n if (this.element.clientWidth === 0) return;\n const width = this.#contentWidth();\n const stripped = this.element.style.height === \"\";\n if (width === this.#lastWidth && !stripped && !this.#pending) return;\n this.#lastWidth = width;\n this.resize();\n });\n\n /** The listeners of one connection, released together on `disconnect()`. */\n readonly #listeners = new ListenerSet();\n\n override connect(): void {\n // Adopt a height applied by a previous connection (kept by Turbo restore /\n // morph) so re-measuring an unchanged element does not re-dispatch `resize`.\n this.#lastHeight = Math.round(px(this.element.style.height));\n this.#lastWidth = this.#contentWidth();\n this.#listeners.add(this.element, \"input\", this.#remeasure);\n this.#listeners.add(this.element, \"change\", this.#remeasure);\n this.#layout.observe(this.element);\n this.#fonts.observe(this.element);\n this.#started = true;\n this.resize();\n }\n\n override disconnect(): void {\n this.#started = false;\n this.#listeners.dispose();\n this.#layout.disconnect();\n this.#fonts.disconnect();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `minRows`. */\n minRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-clamps when application code or a Turbo morph changes `maxRows`. */\n maxRowsValueChanged(): void {\n if (this.#started) this.#remeasure();\n }\n\n /** Re-measures the content and applies the clamped height. */\n resize(): void {\n this.#pending = false;\n const el = this.element;\n const style = window.getComputedStyle(el);\n const lineHeight = this.#lineHeight(style);\n const paddingV = px(style.paddingTop) + px(style.paddingBottom);\n const borderV = px(style.borderTopWidth) + px(style.borderBottomWidth);\n const borderBox = style.boxSizing === \"border-box\";\n\n // Collapse first so scrollHeight reflects the content, not the prior height.\n el.style.height = \"auto\";\n const contentHeight = Math.max(0, el.scrollHeight - paddingV);\n const rows = Math.max(1, Math.round(contentHeight / lineHeight));\n\n let targetContent = Math.max(contentHeight, this.#safeMinRows * lineHeight);\n let atMax = false;\n if (this.#safeMaxRows > 0) {\n const maxContent = this.#safeMaxRows * lineHeight;\n if (targetContent > maxContent) {\n targetContent = maxContent;\n atMax = true;\n }\n }\n\n const boxExtra = borderBox ? paddingV + borderV : 0;\n const height = Math.round(targetContent + boxExtra);\n el.style.height = `${height}px`;\n el.style.overflowY = atMax ? \"auto\" : \"hidden\";\n\n if (atMax) {\n el.setAttribute(\"data-at-max-rows\", \"true\");\n } else {\n el.removeAttribute(\"data-at-max-rows\");\n }\n el.style.setProperty(\"--stimeo--textarea-rows\", String(rows));\n\n if (height !== this.#lastHeight) {\n this.#lastHeight = height;\n this.dispatch(\"resize\", { detail: { height, rows } });\n }\n }\n\n /** Content-box width — the wrapping input, unaffected by this instance's height writes. */\n #contentWidth(): number {\n const style = window.getComputedStyle(this.element);\n return Math.max(0, this.element.clientWidth - px(style.paddingLeft) - px(style.paddingRight));\n }\n\n /** Resolved line height, falling back to ~1.2× font-size when `normal`. */\n #lineHeight(style: CSSStyleDeclaration): number {\n const lh = px(style.lineHeight);\n if (lh > 0) return lh;\n const fontSize = px(style.fontSize);\n return fontSize > 0 ? fontSize * 1.2 : 16;\n }\n\n /** Current `minRows` declaration resolved against its numeric contract. */\n get #safeMinRows(): number {\n return this.#numbers.read(\n this,\n \"minRows\",\n this.minRowsValue,\n TextareaAutosizeController.values.minRows.default,\n TextareaAutosizeController.valueConstraints.minRows,\n );\n }\n\n /** Current `maxRows` declaration resolved against its numeric contract. */\n get #safeMaxRows(): number {\n return this.#numbers.read(\n this,\n \"maxRows\",\n this.maxRowsValue,\n TextareaAutosizeController.values.maxRows.default,\n TextareaAutosizeController.valueConstraints.maxRows,\n );\n }\n}\n"]}
@@ -8,6 +8,9 @@ type ThemeMode = "light" | "dark" | "system";
8
8
  * effective theme onto the root for the consumer's CSS. Ships no colors; only state
9
9
  * hooks.
10
10
  *
11
+ * Toggle resolves persisted preference before the declared mode, including system
12
+ * preference, so it inverts the effective current theme.
13
+ *
11
14
  * Two markup contracts (identifier: `stimeo--theme`):
12
15
  *
13
16
  * Canonical 3-value radiogroup (use this when `system` is offered):
@@ -28,30 +31,33 @@ type ThemeMode = "light" | "dark" | "system";
28
31
  * <button data-controller="stimeo--theme" data-action="click->stimeo--theme#toggle"
29
32
  * aria-pressed="false">Dark mode</button>
30
33
  *
31
- * `change` dispatches `{ mode, resolved }` when a selection or the OS preference moves
32
- * one of the two. `reconcile` dispatches the same `{ mode, resolved }` when a `mode`
34
+ * `change` dispatches `{ mode, resolved, reason }` when a selection or the OS preference moves
35
+ * one of the two. `reconcile` dispatches `{ mode, resolved }` when a `mode`
33
36
  * declaration that changes after connect moves one of them — the declaration
34
- * itself, or a stored choice that outranks it. Connecting is silent.
37
+ * itself, or a stored choice that outranks it — or a `storageKey` change brings the
38
+ * choice saved under the new key. Connecting is silent.
35
39
  *
36
40
  * @remarks
37
41
  * Behavior only — the actual palette is the consumer's CSS keyed off `data-theme`
38
42
  * on the root. It applies `data-theme` (the *resolved* light/dark) and a matching
39
43
  * `color-scheme` to the `target` element (`<html>` by default), keeps the radiogroup
40
44
  * `aria-checked` + roving tabindex (APG radio) or the single button's `aria-pressed`
41
- * in sync. Arrow/Home/End move focus across the radiogroup as the APG radio pattern
42
- * requires; applying a theme never moves focus. The `prefers-color-scheme` listener is attached on
45
+ * in sync. Arrow keys move focus and the selection across the radiogroup as the APG radio
46
+ * pattern requires, and Home/End move both to the first/last option; applying a theme never
47
+ * moves focus. The `prefers-color-scheme` listener is attached on
43
48
  * `connect()` and removed on `disconnect()` (Turbo included). FOUC avoidance for the
44
49
  * very first paint is an inline `<head>` snippet, not this controller. A `mode`
45
50
  * declaration rewritten after connect is followed, but never over a stored choice
46
- * ({@link ThemeController.modeValueChanged}).
51
+ * ({@link ThemeController.modeValueChanged}), and so is a `storageKey`
52
+ * ({@link ThemeController.storageKeyValueChanged}).
47
53
  *
48
54
  * Every declaration is validated where it enters, and an unreadable one falls back
49
55
  * to that Value's default rather than taking the widget with it: a `mode` outside
50
56
  * the three modes reads as `system`, and a `target` that is not a parsable selector
51
57
  * reads as `html`. An option whose `data-value` is outside the three can be focused
52
- * but never becomes the selection, so no more than one option is ever checked —
53
- * and none at all when the resolved mode matches no option, where the first one
54
- * keeps the Tab stop.
58
+ * but never becomes the selection. Every option matching the selected mode is
59
+ * checked, and the first matching option keeps the Tab stop. When no option
60
+ * matches, none is checked and the first option keeps the Tab stop.
55
61
  *
56
62
  * The radiogroup stays one Tab stop through `RovingTabindex`, re-derived
57
63
  * whenever an option enters or leaves, so a set rendered after connect or swapped
@@ -97,6 +103,13 @@ declare class ThemeController extends Controller<HTMLElement> {
97
103
  * first, so the next selection is compared with what is on screen.
98
104
  */
99
105
  modeValueChanged(): void;
106
+ /**
107
+ * Follows a `storageKey` rewritten after connect. A valid choice saved under the new
108
+ * key outranks the mode on screen as at connect — `mode` is written to it and a moved
109
+ * pair is reported as `reconcile` — and without one the mode stays. Nothing is copied
110
+ * or written; the next selection is saved under the new key.
111
+ */
112
+ storageKeyValueChanged(): void;
100
113
  /** Re-derives the single Tab stop and ARIA for an option set that changed. */
101
114
  optionTargetConnected(): void;
102
115
  /** Re-derives them again when an option leaves, so a Tab stop always remains. */
@@ -107,9 +120,9 @@ declare class ThemeController extends Controller<HTMLElement> {
107
120
  * Read through the same lane that decides which option is checked, so the two
108
121
  * can never disagree about what an option declares.
109
122
  */
110
- set(event: Event): void;
123
+ set(source: Event | HTMLElement): void;
111
124
  /** Toggles light↔dark for the 2-value single-button contract. */
112
- toggle(): void;
125
+ toggle(event?: Event): void;
113
126
  }
114
127
 
115
128
  export { ThemeController };