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 +1 @@
1
- {"version":3,"sources":["../../src/utils/default_attribute.ts","../../src/utils/focus_candidate.ts","../../src/controllers/form_validation_controller.ts"],"names":[],"mappings":"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","file":"form_validation_controller.js","sourcesContent":["/**\n * Adds an attribute default without displacing an authored value.\n *\n * Attribute presence — including an authored empty string — is the ownership\n * boundary. The return value lets a caller remember that it supplied the\n * default when that caller must later restore the authored state.\n *\n * @param element - Element that owns the attribute\n * @param name - Attribute name\n * @param value - Value to write only when the attribute is absent\n * @returns Whether this call added the attribute\n */\nexport function setDefaultAttribute(element: Element, name: string, value: string): boolean {\n if (element.hasAttribute(name)) return false;\n element.setAttribute(name, value);\n return true;\n}\n","/**\n * Whether a disabled `<fieldset>` ancestor actually reaches `control`.\n *\n * HTML exempts the contents of a fieldset's **first direct-child `<legend>`**, and\n * the exemption is per fieldset — a control legal in one legend can still be\n * disabled by a second, outer fieldset, so the walk continues upward.\n *\n * Exported on its own for callers that need the disabled-inheritance rule without\n * the rest of {@link canTakeFocus} — a control's availability check, or a `hidden`\n * walk bounded at a widget root, which is a different rule to compose with.\n */\nexport function inheritsFieldsetDisabled(control: HTMLElement): boolean {\n let fieldset: Element | null = control.closest(\"fieldset[disabled]\");\n while (fieldset) {\n const legend = Array.from(fieldset.children).find((child) => child.tagName === \"LEGEND\");\n if (!legend?.contains(control)) return true;\n fieldset = fieldset.parentElement?.closest(\"fieldset[disabled]\") ?? null;\n }\n return false;\n}\n\n/**\n * Whether an element can actually take focus, checked **before** `focus()` runs.\n *\n * A controller that must move focus off a control it is about to disable or hide\n * picks a destination and calls `focus()` on it. If that destination cannot take\n * focus, the call **fails silently**: `hidden` and natively `disabled` elements\n * swallow it, so the caret stays in the subtree that is disappearing and lands on\n * `<body>` a frame later — the exact outcome the rescue exists to prevent, minus\n * any signal that it happened.\n *\n * Testing after the fact is the obvious alternative and is deliberately not the\n * rule. Reading `document.activeElement` back only works in a real browser, and\n * looping over candidates that way performs a real focus move per failure —\n * observable to assistive technology. Checking first costs nothing and catches\n * the cases that actually occur.\n *\n * **`aria-disabled` is not disqualifying.** It is the attribute an author uses\n * for a control that must stay *discoverable*, and the roving contract keeps\n * such items reachable. Only the four conditions that make the platform refuse\n * focus are checked: `hidden` / `inert` (an ancestor's counts),\n * `input[type=\"hidden\"]`, the native `disabled` property, and `disabled`\n * inherited from an ancestor `fieldset`. CSS-only invisibility is handled by\n * {@link isRenderedForFocus} when a consumer needs sequential-focus semantics.\n *\n * Reading `:disabled` instead of walking the fieldset chain would be shorter, but\n * that pseudo-class is not evaluated consistently outside real browsers and this\n * has to be right headlessly too. happy-dom in particular focuses a `<button>`\n * inside a disabled fieldset where a real engine refuses, so the inheritance is\n * spelled out rather than delegated.\n *\n * What a consumer does when nothing survives is its own call: some fall back to\n * their landmark, while a widget whose caret already sits somewhere legitimate\n * refuses the move outright rather than relocating it.\n *\n * @example\n * ```ts\n * const target = candidates.find(canTakeFocus);\n * if (target) target.focus();\n * else {\n * this.#tabindex.lend(this.element); // nothing left: fall back to the landmark\n * this.element.focus();\n * }\n * ```\n *\n * @param element - the candidate destination\n */\nexport function canTakeFocus(element: HTMLElement): boolean {\n if (element.closest(\"[hidden], [inert]\")) return false;\n if (element instanceof HTMLInputElement && element.type === \"hidden\") return false;\n if (!(\"disabled\" in element)) return true;\n if ((element as HTMLElement & { disabled: boolean }).disabled) return false;\n return !inheritsFieldsetDisabled(element);\n}\n\n/** Elements whose semantics or authored attributes can place them in sequential focus order. */\nexport const TAB_STOP_CANDIDATE_SELECTOR = [\n \"a[href]\",\n \"area[href]\",\n \"button\",\n \"input\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"iframe\",\n \"audio[controls]\",\n \"video[controls]\",\n \"[tabindex]\",\n \"[contenteditable]\",\n].join(\",\");\n\n/** Optional browser visibility API used to exclude CSS-hidden candidates. */\ninterface VisibilityCheckable {\n checkVisibility?: (options?: { visibilityProperty?: boolean }) => boolean;\n}\n\n/** Whether CSS visibility allows an otherwise eligible element to participate in focus order. */\nexport function isRenderedForFocus(element: HTMLElement): boolean {\n const check = (element as HTMLElement & VisibilityCheckable).checkVisibility;\n return typeof check === \"function\" ? check.call(element, { visibilityProperty: true }) : true;\n}\n\n/** Parses an authored `tabindex`; invalid syntax has no explicit focus-order meaning. */\nfunction authoredTabindex(element: HTMLElement): number | null {\n const value = element.getAttribute(\"tabindex\");\n if (value === null || !/^[+-]?\\d+$/.test(value.trim())) return null;\n return Number(value);\n}\n\n/** Whether the element's native semantics place it in sequential focus order. */\nfunction hasNativeTabStop(element: HTMLElement): boolean {\n if (element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement) {\n return element.hasAttribute(\"href\");\n }\n if (\n element instanceof HTMLButtonElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n ) {\n return true;\n }\n if (element instanceof HTMLInputElement) return element.type !== \"hidden\";\n if (element instanceof HTMLIFrameElement) return true;\n if (element.tagName === \"AUDIO\" || element.tagName === \"VIDEO\") {\n return element.hasAttribute(\"controls\");\n }\n if (element instanceof HTMLElement && element.tagName === \"SUMMARY\") {\n const details = element.parentElement;\n return (\n details instanceof HTMLDetailsElement &&\n Array.from(details.children).find((child) => child.tagName === \"SUMMARY\") === element\n );\n }\n return false;\n}\n\n/** Whether an explicit `contenteditable` value creates an editable tab stop. */\nfunction hasEditableTabStop(element: HTMLElement): boolean {\n const value = element.getAttribute(\"contenteditable\")?.toLowerCase();\n return value === \"\" || value === \"true\" || value === \"plaintext-only\";\n}\n\n/**\n * Whether an element is a usable sequential Tab stop right now.\n *\n * Native semantics, authored `tabindex`, editable hosts, inherited disabled state,\n * HTML `hidden`/`inert`, and CSS visibility are evaluated together. `aria-disabled`\n * remains focusable because it communicates unavailability without removing the\n * control from discovery order.\n */\nexport function isTabStop(element: HTMLElement): boolean {\n if (!canTakeFocus(element) || !isRenderedForFocus(element)) return false;\n\n const tabindex = authoredTabindex(element);\n if (tabindex !== null) return tabindex >= 0;\n return hasNativeTabStop(element) || hasEditableTabStop(element);\n}\n\n/** Returns every usable sequential Tab stop below `root` in document order. */\nexport function tabStopsWithin(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(TAB_STOP_CANDIDATE_SELECTOR)).filter(\n isTabStop,\n );\n}\n\n/** Returns the first usable sequential Tab stop below `root`, if one exists. */\nexport function firstTabStop(root: ParentNode): HTMLElement | null {\n for (const candidate of root.querySelectorAll<HTMLElement>(TAB_STOP_CANDIDATE_SELECTOR)) {\n if (isTabStop(candidate)) return candidate;\n }\n return null;\n}\n\n/** Whether `root` contains at least one usable sequential Tab stop. */\nexport function hasTabStop(root: ParentNode): boolean {\n return firstTabStop(root) !== null;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { setDefaultAttribute } from \"../utils/default_attribute\";\nimport { canTakeFocus, firstTabStop, isTabStop } from \"../utils/focus_candidate\";\nimport type { FormFieldController } from \"./form_field_controller\";\n\n/** Native form controls that participate in constraint validation. */\ntype ValidatableControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\n/**\n * Maps each `ValidityState` flag (in priority order) to the kebab-case suffix of\n * its per-constraint message-override attribute. The first flag that is `true`\n * wins, so e.g. `valueMissing` is reported before a stale `patternMismatch`.\n */\nconst CONSTRAINT_MESSAGE_KEYS: ReadonlyArray<readonly [keyof ValidityState, string]> = [\n [\"valueMissing\", \"value-missing\"],\n [\"typeMismatch\", \"type-mismatch\"],\n [\"patternMismatch\", \"pattern-mismatch\"],\n [\"tooShort\", \"too-short\"],\n [\"tooLong\", \"too-long\"],\n [\"rangeUnderflow\", \"range-underflow\"],\n [\"rangeOverflow\", \"range-overflow\"],\n [\"stepMismatch\", \"step-mismatch\"],\n [\"badInput\", \"bad-input\"],\n];\n\n/** Suffix of a generic message override applied to any failing constraint. */\nconst MESSAGE_PART = \"message\";\n/** Suffix opting a control into a declarative custom rule (`\"whitespace\"`). */\nconst DISALLOW_PART = \"disallow\";\n/** Default message when `disallow=\"whitespace\"` fails and no override is given. */\nconst DISALLOW_WHITESPACE_DEFAULT = \"Please enter a value that is not only whitespace.\";\n\n/** Public invalid-state hook owned by `stimeo--form-field`. */\nconst FORM_FIELD_INVALID_ATTR = \"data-stimeo--form-field-invalid\";\n\n/** Identity used to collect controls into one validation field. */\ntype FieldGroupKey = FormFieldController | ValidatableControl | string;\n\n/** A field's validatable controls, grouped so the whole field validates together. */\ninterface FieldGroup {\n readonly key: FieldGroupKey;\n readonly field: FormFieldController | undefined;\n readonly controls: ValidatableControl[];\n}\n\n/** Live form grouping resolved once for a validation operation. */\ninterface ValidationSnapshot {\n readonly groups: Map<FieldGroupKey, FieldGroup>;\n /** `null` is a deliberate miss key so delegated non-control events need one guard. */\n readonly groupByControl: Map<ValidatableControl | null, FieldGroup>;\n}\n\n/**\n * Headless, accessible **form-validation orchestration**.\n *\n * Markup contract (identifier: `stimeo--form-validation`):\n * <form data-controller=\"stimeo--form-validation\"\n * data-stimeo--form-validation-stimeo--form-field-outlet=\"[data-controller~='stimeo--form-field']\">\n * <div data-controller=\"stimeo--form-field\">\n * <label for=\"email\">Email</label>\n * <input id=\"email\" type=\"email\" required\n * data-stimeo--form-field-target=\"control\" />\n * <p hidden data-stimeo--form-field-target=\"error\"></p>\n * </div>\n * <button type=\"submit\">Save</button>\n * </form>\n *\n * Not an APG widget pattern — this is the *timing* layer for **error\n * identification** (WCAG 3.3.1) and **error suggestion** (3.3.3): it decides\n * *when* each control is checked and routes the browser's native\n * `validationMessage` into the field's {@link FormFieldController} error region.\n * The per-field ARIA wiring (`aria-invalid` / `aria-errormessage` /\n * `aria-describedby`) therefore lives in exactly one place — `stimeo--form-field`,\n * reached through a Stimulus **outlet** — and is never re-implemented here.\n *\n * `valid` dispatches `{}`; `invalid` dispatches `{ invalid: HTMLElement[] }`.\n *\n * @remarks\n * Behavior only — validation **rules** stay in the markup (native HTML\n * constraints: `required`, `type`, `pattern`, `min`/`max`, …) or in the consumer's\n * own `setCustomValidity()` calls, which `checkValidity()` surfaces transparently.\n * It sets the form's `novalidate` so it can replace the browser's default error\n * bubbles with accessible in-page visual errors plus the shared Announcer, and\n * restores the attribute on disconnect.\n *\n * Two declarative escape hatches let a field **exceed** native validation with no\n * consumer JS (author them on the control):\n * - **Per-constraint messages** — `data-stimeo--form-validation-message-<constraint>`\n * (`value-missing`, `too-short`, `too-long`, `pattern-mismatch`, `type-mismatch`,\n * `range-overflow`, `range-underflow`, `step-mismatch`, `bad-input`), or a generic\n * `data-stimeo--form-validation-message` fallback, override the shown text per failing\n * `ValidityState` flag — controlled, localizable wording that also fixes headless\n * browsers returning an empty native `validationMessage`. Falls back to native.\n * - **`data-stimeo--form-validation-disallow=\"whitespace\"`** — a built-in custom rule\n * rejecting a value that is blank after trimming (which slips past `required` /\n * `minlength`), wired through `setCustomValidity` so it blocks submit like any\n * native constraint.\n *\n * Behavior provided:\n * - On connect, suppresses native bubbles (`novalidate`, restored on disconnect)\n * and intercepts the form's `submit` in the **capture phase** so an invalid form\n * is cancelled before any other submit handler (e.g. `stimeo--submit-once`)\n * reacts to a submission that will never happen.\n * - On submit, validates every control; if any is invalid it blocks submission,\n * moves focus to the first invalid control (unless `focusInvalid` is `false`),\n * and dispatches `stimeo--form-validation:invalid`. An all-valid form dispatches\n * `:valid` and submits normally.\n * - Validates a field on blur once it has been interacted with (`validateOnBlur`),\n * and re-validates it on input while it is already touched\n * (`revalidateOnInput`) so a shown message clears the moment the value becomes\n * valid — but a pristine field is never eagerly flagged mid-typing.\n *\n * A control with no owning `stimeo--form-field` outlet is still validated (it can\n * block submit and receive focus) but renders no message.\n *\n * Radio groups work unchanged: point the field's `control` target at the\n * `role=\"radiogroup\"` container so the invalid state lands on the group, and the\n * group is reported as a single invalid entry (not one per radio).\n *\n * Rich widgets (listbox, time-picker, …) that keep their committed value in a\n * hidden holder participate by making that holder a **validatable mirror**:\n * `<input type=\"text\" hidden required>` — the `hidden` *attribute*, not\n * `type=\"hidden\"`, which is barred from constraint validation. Native\n * constraints then govern the widget's value with no extra JavaScript. The\n * widget dispatches a bubbling `change` on the mirror when a value is committed\n * (a completed interaction, so it validates immediately), and focus for an\n * invalid mirror is delegated to the field's visible `control` target — the\n * target itself when focusable, else its first focusable descendant.\n *\n * No-JS caveat: a `required` mirror also gates the browser's own pre-Stimulus\n * validation, which cannot surface UI on an invisible control. When the no-JS\n * fallback matters, author `novalidate` on the form (this controller preserves\n * an author-set attribute) so the submission reaches the server's validation.\n */\nexport class FormValidationController extends Controller<HTMLFormElement> {\n /** The generic message override, in the namespace this controller is registered under. */\n get #messageAttribute(): string {\n return `data-${this.identifier}-${MESSAGE_PART}`;\n }\n\n /** Marks the `novalidate` this controller applied, so an authored one is left alone. */\n get #novalidateMarker(): string {\n return `data-${this.identifier}-${FormValidationController.#NOVALIDATE_PART}`;\n }\n\n static override outlets = [\"stimeo--form-field\"];\n static override values = {\n validateOnBlur: { type: Boolean, default: true },\n validateOnChange: { type: Boolean, default: true },\n revalidateOnInput: { type: Boolean, default: true },\n focusInvalid: { type: Boolean, default: true },\n };\n static actions = [\"validate\"] as const;\n static events = [\"valid\", \"invalid\"] as const;\n\n declare readonly stimeoFormFieldOutlets: FormFieldController[];\n declare readonly stimeoFormFieldOutletElements: HTMLElement[];\n\n declare validateOnBlurValue: boolean;\n declare validateOnChangeValue: boolean;\n declare revalidateOnInputValue: boolean;\n declare focusInvalidValue: boolean;\n\n /** Suffix of the marker recording that we added `novalidate`, so we only remove our own. */\n static readonly #NOVALIDATE_PART = \"novalidate\";\n\n /** Object-backed groups already interacted with — the input revalidation gate. */\n readonly #touchedGroups = new WeakSet<FormFieldController | ValidatableControl>();\n\n /** String-backed fallback radio groups already interacted with. */\n readonly #touchedRadioGroups = new Set<string>();\n\n /**\n * Last message this controller wrote through `setCustomValidity`, kept as a\n * two-layer ownership ledger: a value is cleared only while the live message\n * still equals the recorded write. Iterable so disconnect can release every\n * surviving loan without touching a consumer's later custom error.\n */\n readonly #ownedCustomErrors = new Map<ValidatableControl, string>();\n\n /** Last invalid message routed to each field, suppressing duplicate reports. */\n readonly #reportedErrors = new WeakMap<FormFieldController, string>();\n\n /** Exact document that owns the delegated listeners for this connection. */\n #listenerDocument: Document | null = null;\n\n readonly #onSubmit = (event: SubmitEvent): void => {\n if (event.target !== this.element) return;\n const invalid = this.#validateAll();\n if (invalid.length === 0) {\n this.dispatch(\"valid\", { detail: {} });\n return;\n }\n // Cancel the whole submit: preventDefault stops native/Turbo navigation;\n // stopImmediatePropagation keeps later submit handlers (e.g. submit-once's\n // busy state) from acting on a submission that will never happen.\n event.preventDefault();\n event.stopImmediatePropagation();\n const first = invalid[0];\n if (this.focusInvalidValue && first) this.#focusTargetFor(first)?.focus();\n this.dispatch(\"invalid\", { detail: { invalid } });\n };\n\n readonly #onFocusOut = (event: FocusEvent): void => {\n if (!this.validateOnBlurValue) return;\n const snapshot = this.#snapshot();\n const group = snapshot.groupByControl.get(this.#controlFrom(event.target));\n if (!group) return;\n // Focus moving *within* the same field (e.g. between members of a radio\n // group) is not leaving it — defer validation until focus actually exits.\n const related = event.relatedTarget;\n if (group.field && related instanceof Node && group.field.element.contains(related)) return;\n if (snapshot.groupByControl.get(this.#controlFrom(related))?.key === group.key) return;\n this.#markTouched(group.key);\n this.#applyGroup(group);\n };\n\n readonly #onInput = (event: Event): void => {\n if (!this.revalidateOnInputValue) return;\n const group = this.#snapshot().groupByControl.get(this.#controlFrom(event.target));\n // Only re-validate a field the user has already left once, so the first\n // keystroke never eagerly flags a control they are still filling in.\n if (!group || !this.#isTouched(group.key)) return;\n this.#applyGroup(group);\n };\n\n readonly #onChange = (event: Event): void => {\n if (!this.validateOnChangeValue) return;\n const group = this.#snapshot().groupByControl.get(this.#controlFrom(event.target));\n if (!group) return;\n // change marks a *committed* interaction (a picked option, a toggled box, a\n // widget writing its mirror), so unlike input it both touches and validates.\n this.#markTouched(group.key);\n this.#applyGroup(group);\n };\n\n /** Suppresses native bubbles and binds the submit / blur / input listeners. */\n override connect(): void {\n if (setDefaultAttribute(this.element, \"novalidate\", \"\")) {\n this.element.setAttribute(this.#novalidateMarker, \"\");\n }\n // Capture submit before form-bound actions. The other bubbling events live on\n // the same document so controls associated through `form=\"id\"` participate too;\n // #controlFrom rejects every control owned by a different form.\n this.#listenerDocument = this.element.ownerDocument;\n this.#listenerDocument.addEventListener(\"submit\", this.#onSubmit, true);\n this.#listenerDocument.addEventListener(\"focusout\", this.#onFocusOut);\n this.#listenerDocument.addEventListener(\"input\", this.#onInput);\n this.#listenerDocument.addEventListener(\"change\", this.#onChange);\n }\n\n /** Tears down listeners and restores `novalidate` if we added it. */\n override disconnect(): void {\n this.#listenerDocument?.removeEventListener(\"submit\", this.#onSubmit, true);\n this.#listenerDocument?.removeEventListener(\"focusout\", this.#onFocusOut);\n this.#listenerDocument?.removeEventListener(\"input\", this.#onInput);\n this.#listenerDocument?.removeEventListener(\"change\", this.#onChange);\n this.#listenerDocument = null;\n for (const [control, message] of this.#ownedCustomErrors) {\n if (control.validationMessage === message) control.setCustomValidity(\"\");\n }\n this.#ownedCustomErrors.clear();\n this.#touchedRadioGroups.clear();\n if (this.element.hasAttribute(this.#novalidateMarker)) {\n this.element.removeAttribute(\"novalidate\");\n this.element.removeAttribute(this.#novalidateMarker);\n }\n }\n\n /**\n * Validates every control now, rendering or clearing each field's message, and\n * returns whether the whole form is valid. Marks every field/group touched so\n * a later input from any sibling re-validates it. Bound via `data-action`\n * (`#validate`) or callable directly (e.g. before a programmatic submit).\n */\n validate(): boolean {\n return this.#validateAll().length === 0;\n }\n\n /**\n * Validates every control and returns one invalid control per field. Controls\n * are grouped by field first (see {@link #keyFor}) so a field with several\n * controls — a radio group, or a mirror plus its visible control — reflects\n * *all* of them: a valid sibling must never clear an invalid one's message.\n * Each group's first invalid control supplies the message and the focus target.\n */\n #validateAll(): ValidatableControl[] {\n const invalid: ValidatableControl[] = [];\n for (const group of this.#snapshot().groups.values()) {\n this.#markTouched(group.key);\n const firstInvalid = this.#applyGroup(group);\n if (firstInvalid) invalid.push(firstInvalid);\n }\n return invalid;\n }\n\n /** Builds the current field table in one pass over form controls and DOM depth. */\n #snapshot(): ValidationSnapshot {\n const fields = this.#fieldOutlets();\n const groups = new Map<FieldGroupKey, FieldGroup>();\n const groupByControl = new Map<ValidatableControl | null, FieldGroup>();\n\n for (const control of this.#controls) {\n const field = this.#fieldFor(control, fields);\n const key = this.#keyFor(control, field);\n let group = groups.get(key);\n if (!group) {\n group = { key, field, controls: [] };\n groups.set(key, group);\n }\n group.controls.push(control);\n groupByControl.set(control, group);\n }\n\n return { groups, groupByControl };\n }\n\n /** Records that a whole field/group, rather than one sibling control, was visited. */\n #markTouched(key: FieldGroupKey): void {\n if (typeof key === \"string\") {\n this.#touchedRadioGroups.add(key);\n } else {\n this.#touchedGroups.add(key);\n }\n }\n\n #isTouched(key: FieldGroupKey): boolean {\n return typeof key === \"string\"\n ? this.#touchedRadioGroups.has(key)\n : this.#touchedGroups.has(key);\n }\n\n /**\n * Runs native constraint validation across a field's controls and routes the\n * result to its `stimeo--form-field` outlet: the first invalid control's\n * `validationMessage` is shown, an all-valid field is cleared. Returns the\n * first invalid control (for the invalid list / focus), or `null` when valid.\n * Routing goes through the outlet, so the ARIA wiring is never duplicated here.\n */\n #applyGroup(group: FieldGroup): ValidatableControl | null {\n // Apply declarative custom rules (e.g. disallow=\"whitespace\") before reading\n // validity so they participate in checkValidity() like a native constraint.\n for (const control of group.controls) this.#syncCustomValidity(control);\n const firstInvalid = group.controls.find((control) => !control.checkValidity()) ?? null;\n if (group.field) {\n if (firstInvalid) {\n const message = this.#messageFor(firstInvalid);\n const alreadyReported =\n group.field.element.hasAttribute(FORM_FIELD_INVALID_ATTR) &&\n this.#reportedErrors.get(group.field) === message;\n if (!alreadyReported) group.field.setError(message, { focus: false });\n this.#reportedErrors.set(group.field, message);\n } else if (\n group.field.element.hasAttribute(FORM_FIELD_INVALID_ATTR) ||\n this.#reportedErrors.has(group.field)\n ) {\n group.field.clearError();\n this.#reportedErrors.delete(group.field);\n }\n }\n return firstInvalid;\n }\n\n /**\n * Resolves the message to show for an invalid control: a per-constraint\n * override (`data-stimeo--form-validation-message-<constraint>`) for the first failing\n * `ValidityState` flag, then a generic `data-stimeo--form-validation-message`\n * override, then the browser's native `validationMessage`. Authoring an override\n * gives controlled, localizable, theme-able wording with **no consumer JS** —\n * and sidesteps headless browsers that return an empty native message.\n */\n #messageFor(control: ValidatableControl): string {\n // `setCustomValidity()` owns its wording. In particular, a consumer's custom\n // error must not be replaced by a simultaneous native-constraint override.\n if (control.validity.customError) return control.validationMessage;\n for (const [flag, key] of CONSTRAINT_MESSAGE_KEYS) {\n if (control.validity[flag]) {\n return (\n this.#authoredMessage(control, `${this.#messageAttribute}-${key}`) ??\n this.#authoredMessage(control, this.#messageAttribute) ??\n control.validationMessage\n );\n }\n }\n return (\n control.validationMessage || this.#authoredMessage(control, this.#messageAttribute) || \"\"\n );\n }\n\n /**\n * Applies (or clears) a declarative custom constraint via `setCustomValidity`,\n * for controls that opt in with `data-stimeo--form-validation-disallow`. The supported\n * rule is `\"whitespace\"` — a value that is non-empty but blank\n * after trimming (which slips past `required` / `minlength`); its message follows\n * the whitespace-specific → generic → default chain.\n *\n * Don't-clobber-authored-state: an unknown/absent rule is never written, an\n * existing consumer custom error wins, and a controller error is only cleared\n * while the live message still equals the recorded controller write.\n */\n #syncCustomValidity(control: ValidatableControl): void {\n const recorded = this.#ownedCustomErrors.get(control);\n const ownsCurrent = recorded !== undefined && control.validationMessage === recorded;\n\n const violates =\n control.getAttribute(`data-${this.identifier}-${DISALLOW_PART}`) === \"whitespace\" &&\n control.value.length > 0 &&\n control.value.trim() === \"\";\n if (!violates) {\n if (ownsCurrent) control.setCustomValidity(\"\");\n this.#ownedCustomErrors.delete(control);\n return;\n }\n\n // One custom-validity slot exists per control. Preserve a consumer's live\n // entry; once they clear it, the next validation applies the declarative rule.\n if (control.validity.customError && !ownsCurrent) return;\n\n const message =\n this.#authoredMessage(control, `${this.#messageAttribute}-whitespace`) ??\n this.#authoredMessage(control, this.#messageAttribute) ??\n DISALLOW_WHITESPACE_DEFAULT;\n if (!ownsCurrent || recorded !== message) control.setCustomValidity(message);\n this.#ownedCustomErrors.set(control, message);\n }\n\n /** Returns a non-blank authored message, otherwise falls through to a fallback. */\n #authoredMessage(control: ValidatableControl, attribute: string): string | null {\n const message = control.getAttribute(attribute);\n return message && message.trim().length > 0 ? message : null;\n }\n\n /**\n * A grouping key that collects controls belonging to the same field: the owning\n * `stimeo--form-field` when present, else a radio group's shared `name`, else\n * the control itself (always distinct).\n */\n #keyFor(control: ValidatableControl, field: FormFieldController | undefined): FieldGroupKey {\n if (field) return field;\n if (control instanceof HTMLInputElement && control.type === \"radio\" && control.name) {\n return `radio:${control.name}`;\n }\n return control;\n }\n\n /**\n * Where focus should land for an invalid control. A visible control is focused\n * directly — the case for native fields and radios. A validatable mirror\n * (the `hidden` attribute) cannot receive focus, so focus is delegated to the\n * visible widget: the owning field's `control` target when it is itself\n * focusable, else its first focusable descendant (e.g. a roving-tabindex\n * member). Resolved structurally — never by probing `focus()` — so behavior\n * is deterministic and CSS-independent.\n */\n #focusTargetFor(control: ValidatableControl): HTMLElement | null {\n if (canTakeFocus(control)) return control;\n const field = this.#fieldFor(control, this.#fieldOutlets());\n if (!field?.hasControlTarget) return null;\n const root = field.controlTarget;\n if (isTabStop(root)) return root;\n return firstTabStop(root);\n }\n\n /** Pairs each live outlet element with its controller once per operation. */\n #fieldOutlets(): Map<HTMLElement, FormFieldController> {\n const fields = new Map<HTMLElement, FormFieldController>();\n const elements = this.stimeoFormFieldOutletElements;\n const outlets = this.stimeoFormFieldOutlets;\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const outlet = outlets[index];\n if (element && outlet) fields.set(element, outlet);\n }\n return fields;\n }\n\n /** The nearest configured field ancestor of `control`, if any. */\n #fieldFor(\n control: ValidatableControl,\n fields: ReadonlyMap<HTMLElement, FormFieldController>,\n ): FormFieldController | undefined {\n let ancestor: HTMLElement | null = control;\n while (ancestor) {\n const field = fields.get(ancestor);\n if (field) return field;\n ancestor = ancestor.parentElement;\n }\n return undefined;\n }\n\n /** This form's native controls that participate in constraint validation. */\n get #controls(): ValidatableControl[] {\n const controls: ValidatableControl[] = [];\n for (const element of Array.from(this.element.elements)) {\n if (this.#isValidatable(element)) controls.push(element);\n }\n return controls;\n }\n\n /** Narrows an event target to a validatable control. */\n #controlFrom(target: EventTarget | null): ValidatableControl | null {\n return target instanceof Element && this.#isValidatable(target) && target.form === this.element\n ? target\n : null;\n }\n\n #isValidatable(element: Element): element is ValidatableControl {\n return (\n (element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement) &&\n // `willValidate` already excludes disabled, read-only, input[type=hidden],\n // and button-type controls. The `hidden` attribute intentionally does not\n // bar a text mirror from constraint validation.\n element.willValidate\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/default_attribute.ts","../../src/utils/focus_candidate.ts","../../src/utils/listener_set.ts","../../src/controllers/form_validation_controller.ts"],"names":[],"mappings":"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","file":"form_validation_controller.js","sourcesContent":["/**\n * Adds an attribute default without displacing an authored value.\n *\n * Attribute presence — including an authored empty string — is the ownership\n * boundary. The return value lets a caller remember that it supplied the\n * default when that caller must later restore the authored state.\n *\n * @param element - Element that owns the attribute\n * @param name - Attribute name\n * @param value - Value to write only when the attribute is absent\n * @returns Whether this call added the attribute\n */\nexport function setDefaultAttribute(element: Element, name: string, value: string): boolean {\n if (element.hasAttribute(name)) return false;\n element.setAttribute(name, value);\n return true;\n}\n","/**\n * Whether a disabled `<fieldset>` ancestor actually reaches `control`.\n *\n * HTML exempts the contents of a fieldset's **first direct-child `<legend>`**, and\n * the exemption is per fieldset — a control legal in one legend can still be\n * disabled by a second, outer fieldset, so the walk continues upward.\n *\n * Exported on its own for callers that need the disabled-inheritance rule without\n * the rest of {@link canTakeFocus} — a control's availability check, or a `hidden`\n * walk bounded at a widget root, which is a different rule to compose with.\n */\nexport function inheritsFieldsetDisabled(control: HTMLElement): boolean {\n let fieldset: Element | null = control.closest(\"fieldset[disabled]\");\n while (fieldset) {\n const legend = Array.from(fieldset.children).find((child) => child.tagName === \"LEGEND\");\n if (!legend?.contains(control)) return true;\n fieldset = fieldset.parentElement?.closest(\"fieldset[disabled]\") ?? null;\n }\n return false;\n}\n\n/**\n * Whether the engine reports `element` assigned to no slot of the open shadow root its parent\n * hosts: such an element is not in the flat tree, so it and what it holds are rendered nowhere\n * and cannot take focus.\n */\nfunction unslotted(element: Element): boolean {\n return element.assignedSlot === null && element.parentElement?.shadowRoot != null;\n}\n\n/**\n * The parent of `element` in the flat tree: the slot it is assigned to, its parent element,\n * or the host of the shadow root it is a child of; `null` at the top of the document or of a\n * detached tree, and for an element the engine assigns to no slot of the open shadow root its\n * parent hosts, which is outside the flat tree. A closed shadow root does not report its slots,\n * so an element assigned to one climbs to the host.\n */\nexport function flatTreeParent(element: Element): Element | null {\n if (unslotted(element)) return null;\n const parent = element.assignedSlot ?? element.parentNode;\n if (parent instanceof ShadowRoot) return parent.host;\n return parent instanceof Element ? parent : null;\n}\n\n/** Whether `element` is `ancestor` or one of its descendants in the flat tree. */\nexport function flatTreeContains(ancestor: Element, element: Element | null): boolean {\n for (let node = element; node; node = flatTreeParent(node)) {\n if (node === ancestor) return true;\n }\n return false;\n}\n\n/** The nearest of `element` and its flat-tree ancestors that matches `selector`, or `null`. */\nexport function closestInFlatTree(element: Element, selector: string): Element | null {\n for (let node: Element | null = element; node; node = flatTreeParent(node)) {\n if (node.matches(selector)) return node;\n }\n return null;\n}\n\n/**\n * The children of `element` in the flat tree: its open shadow root's children, a slot's\n * assigned elements (its own children when nothing is assigned to it), or its children.\n */\nexport function flatTreeChildren(element: Element): Element[] {\n return ownedScope(element) ?? Array.from(element.children);\n}\n\n/**\n * Whether an element can actually take focus, checked **before** `focus()` runs.\n *\n * A controller that must move focus off a control it is about to disable or hide\n * picks a destination and calls `focus()` on it. If that destination cannot take\n * focus, the call **fails silently**: `hidden` and natively `disabled` elements\n * swallow it, so the caret stays in the subtree that is disappearing and lands on\n * `<body>` a frame later — the exact outcome the rescue exists to prevent, minus\n * any signal that it happened.\n *\n * Testing after the fact is the obvious alternative and is deliberately not the\n * rule. Reading `document.activeElement` back only works in a real browser, and\n * looping over candidates that way performs a real focus move per failure —\n * observable to assistive technology. Checking first costs nothing and catches\n * the cases that actually occur.\n *\n * **`aria-disabled` is not disqualifying.** It is the attribute an author uses\n * for a control that must stay *discoverable*, and the roving contract keeps\n * such items reachable. Only the conditions that make the platform refuse\n * focus are checked: `hidden` / `inert` on the element or a flat-tree ancestor\n * (the host of an open shadow root it sits in, the slot it is assigned to), the\n * element or an ancestor left out of the flat tree (one the engine assigns to no\n * slot of the open shadow root its parent hosts), `input[type=\"hidden\"]`, the\n * native `disabled` property, and `disabled` inherited from an ancestor\n * `fieldset`. CSS-only invisibility is handled by {@link isRenderedForFocus}\n * when a consumer needs sequential-focus semantics.\n *\n * Reading `:disabled` instead of walking the fieldset chain would be shorter, but\n * that pseudo-class is not evaluated consistently outside real browsers and this\n * has to be right headlessly too. happy-dom in particular focuses a `<button>`\n * inside a disabled fieldset where a real engine refuses, so the inheritance is\n * spelled out rather than delegated.\n *\n * What a consumer does when nothing survives is its own call: some fall back to\n * their landmark, while a widget whose caret already sits somewhere legitimate\n * refuses the move outright rather than relocating it.\n *\n * @example\n * ```ts\n * const target = candidates.find(canTakeFocus);\n * if (target) target.focus();\n * else {\n * this.#tabindex.lend(this.element); // nothing left: fall back to the landmark\n * this.element.focus();\n * }\n * ```\n *\n * @param element - the candidate destination\n */\nexport function canTakeFocus(element: HTMLElement): boolean {\n for (let node: Element | null = element; node; node = flatTreeParent(node)) {\n if (unslotted(node) || node.matches(\"[hidden], [inert]\")) return false;\n }\n if (element instanceof HTMLInputElement && element.type === \"hidden\") return false;\n if (!(\"disabled\" in element)) return true;\n if ((element as HTMLElement & { disabled: boolean }).disabled) return false;\n return !inheritsFieldsetDisabled(element);\n}\n\n/** Elements whose semantics or authored attributes can place them in sequential focus order. */\nexport const TAB_STOP_CANDIDATE_SELECTOR = [\n \"a[href]\",\n \"area[href]\",\n \"button\",\n \"input\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"iframe\",\n \"audio[controls]\",\n \"video[controls]\",\n \"[tabindex]\",\n \"[contenteditable]\",\n].join(\",\");\n\n/** Optional browser visibility API used to exclude CSS-hidden candidates. */\ninterface VisibilityCheckable {\n checkVisibility?: (options?: { visibilityProperty?: boolean }) => boolean;\n}\n\n/** Whether CSS visibility allows an otherwise eligible element to participate in focus order. */\nexport function isRenderedForFocus(element: HTMLElement): boolean {\n const check = (element as HTMLElement & VisibilityCheckable).checkVisibility;\n return typeof check === \"function\" ? check.call(element, { visibilityProperty: true }) : true;\n}\n\n/** Parses an authored `tabindex`; invalid syntax has no explicit focus-order meaning. */\nfunction authoredTabindex(element: Element): number | null {\n const value = element.getAttribute(\"tabindex\");\n if (value === null || !/^[+-]?\\d+$/.test(value.trim())) return null;\n return Number(value);\n}\n\n/** Whether the element's native semantics place it in sequential focus order. */\nfunction hasNativeTabStop(element: HTMLElement): boolean {\n if (element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement) {\n return element.hasAttribute(\"href\");\n }\n if (\n element instanceof HTMLButtonElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement\n ) {\n return true;\n }\n if (element instanceof HTMLInputElement) return element.type !== \"hidden\";\n if (element instanceof HTMLIFrameElement) return true;\n if (element.tagName === \"AUDIO\" || element.tagName === \"VIDEO\") {\n return element.hasAttribute(\"controls\");\n }\n if (element instanceof HTMLElement && element.tagName === \"SUMMARY\") {\n const details = element.parentElement;\n return (\n details instanceof HTMLDetailsElement &&\n Array.from(details.children).find((child) => child.tagName === \"SUMMARY\") === element\n );\n }\n return false;\n}\n\n/** Whether an explicit `contenteditable` value creates an editable tab stop. */\nfunction hasEditableTabStop(element: HTMLElement): boolean {\n const value = element.getAttribute(\"contenteditable\")?.toLowerCase();\n return value === \"\" || value === \"true\" || value === \"plaintext-only\";\n}\n\n/**\n * Whether an element is a usable sequential Tab stop right now.\n *\n * Native semantics, authored `tabindex`, editable hosts, inherited disabled state,\n * HTML `hidden`/`inert`, and CSS visibility are evaluated together. `aria-disabled`\n * remains focusable because it communicates unavailability without removing the\n * control from discovery order.\n */\nexport function isTabStop(element: HTMLElement): boolean {\n if (!canTakeFocus(element) || !isRenderedForFocus(element)) return false;\n\n const tabindex = authoredTabindex(element);\n if (tabindex !== null) return tabindex >= 0;\n return hasNativeTabStop(element) || hasEditableTabStop(element);\n}\n\n/** Returns every usable sequential Tab stop below `root` in document order. */\nexport function tabStopsWithin(root: ParentNode): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(TAB_STOP_CANDIDATE_SELECTOR)).filter(\n isTabStop,\n );\n}\n\n/** Returns the first usable sequential Tab stop below `root`, if one exists. */\nexport function firstTabStop(root: ParentNode): HTMLElement | null {\n for (const candidate of root.querySelectorAll<HTMLElement>(TAB_STOP_CANDIDATE_SELECTOR)) {\n if (isTabStop(candidate)) return candidate;\n }\n return null;\n}\n\n/** Whether `root` contains at least one usable sequential Tab stop. */\nexport function hasTabStop(root: ParentNode): boolean {\n return firstTabStop(root) !== null;\n}\n\n/** An element sequential focus navigation can stop on. */\nexport type TabStopElement = HTMLElement | SVGElement;\n\n/** Whether the element is a radio button. */\nfunction isRadio(element: Element | null): element is HTMLInputElement {\n return element instanceof HTMLInputElement && element.type === \"radio\";\n}\n\n/**\n * Whether two radios form one group: the same non-empty `name`, the same form owner and\n * the same tree. A radio without a name is a group of its own.\n */\nfunction sameRadioGroup(a: HTMLInputElement, b: HTMLInputElement): boolean {\n return (\n a.name !== \"\" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode()\n );\n}\n\n/**\n * Whether the element is part of editable content: the nearest ancestor that sets a valid\n * `contenteditable` decides. `false` ends the editable region (a non-editable island\n * inside an editor); an invalid value inherits from further up.\n */\nfunction isInEditableContent(element: Element): boolean {\n for (let node = element.parentElement; node; node = node.parentElement) {\n if (node.getAttribute(\"contenteditable\")?.toLowerCase() === \"false\") return false;\n if (hasEditableTabStop(node)) return true;\n }\n return false;\n}\n\n/** The XLink namespace, where an SVG link may carry its `href`. */\nconst XLINK_NAMESPACE = \"http://www.w3.org/1999/xlink\";\n\n/** Whether the element is an SVG link: an SVG `a` with `href` or `xlink:href`. */\nfunction isSvgLink(element: Element): boolean {\n return (\n element instanceof SVGElement &&\n element.localName === \"a\" &&\n (element.hasAttribute(\"href\") || element.hasAttributeNS(XLINK_NAMESPACE, \"href\"))\n );\n}\n\n/**\n * Whether an image-map area has a rendered image: an `img` in the area's tree whose\n * `usemap` names the area's map (by `name`, or by `id` without one) and that is rendered.\n * The area has no box of its own for {@link isRenderedForFocus} to check.\n */\nfunction hasRenderedImage(area: HTMLAreaElement): boolean {\n const map = area.closest(\"map\");\n const key = map?.name || map?.id;\n if (!map || !key) return false;\n const root = map.getRootNode() as Document | ShadowRoot;\n return Array.from(root.querySelectorAll<HTMLElement>(\"img[usemap]\")).some(\n (image) => image.getAttribute(\"usemap\") === `#${key}` && isRenderedForFocus(image),\n );\n}\n\n/** Whether the element can take focus and has no negative `tabindex`. */\nfunction isFocusableInOrder(element: TabStopElement): boolean {\n const tabindex = authoredTabindex(element);\n return (tabindex === null || tabindex >= 0) && canTakeFocus(element as HTMLElement);\n}\n\n/**\n * The engine's verdict for the stops {@link isTabStop} does not judge the way sequential\n * navigation does, or `null` for every other element: an image-map area with an `href` or\n * its own `tabindex` stops while an image that uses its map is rendered; an object stops\n * while it holds a nested document, as an iframe does; an SVG link stops while it is\n * rendered. Each also needs to take focus and not be taken out of the order with a\n * negative `tabindex`.\n */\nfunction extraStop(element: TabStopElement): boolean | null {\n if (element instanceof HTMLAreaElement) {\n const focusable = element.hasAttribute(\"href\") || authoredTabindex(element) !== null;\n return focusable && isFocusableInOrder(element) && hasRenderedImage(element);\n }\n if (element instanceof HTMLObjectElement) {\n return (\n isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null\n );\n }\n if (isSvgLink(element))\n return isFocusableInOrder(element) && isRenderedForFocus(element as HTMLElement);\n return null;\n}\n\n/**\n * Whether sequential navigation stops on the element: a usable Tab stop\n * ({@link isTabStop}), or one of the stops `extraStop` judges, except a link inside\n * editable content without a `tabindex` of its own, which the engine leaves to editing.\n * Other controls inside an editing host, and the host itself, stay stops.\n */\nfunction isSequentialStop(element: TabStopElement): boolean {\n const stop =\n extraStop(element) ??\n (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element as HTMLElement));\n if (!stop) return false;\n const link =\n element instanceof HTMLAnchorElement ||\n element instanceof HTMLAreaElement ||\n isSvgLink(element);\n return !(link && authoredTabindex(element) === null && isInEditableContent(element));\n}\n\n/**\n * The elements of the focus navigation scope `element` owns: its open shadow root's\n * children, or a slot's assigned elements (its fallback content when nothing is\n * assigned). `null` when it owns no scope. A closed shadow root cannot be read, so its\n * host owns none.\n */\nfunction ownedScope(element: Element): Element[] | null {\n if (element.shadowRoot) return Array.from(element.shadowRoot.children);\n if (element instanceof HTMLSlotElement) {\n const assigned = element.assignedElements();\n return assigned.length > 0 ? assigned : Array.from(element.children);\n }\n return null;\n}\n\n/** One member of a focus navigation scope: its tabindex and the stops it contributes. */\ninterface ScopeEntry {\n readonly tabindex: number;\n readonly stops: TabStopElement[];\n}\n\n/** What a walk of the order records besides the order itself. */\ninterface OrderWalk {\n /** The element a move starts from. */\n readonly from: Element | null;\n /** Every stop, in flat-tree order. */\n readonly tree: TabStopElement[];\n /**\n * How many entries of {@link OrderWalk.tree} come before `from`; `-1` until the walk\n * meets it.\n */\n fromAt: number;\n}\n\n/**\n * Orders one focus navigation scope the way HTML's sequential navigation does: members\n * with a positive `tabindex` first, ascending, ties in tree order, then the rest in tree\n * order. A member that owns a scope (a shadow host, a slot) is followed by that scope's\n * own order; one taken out of the order with a negative `tabindex` takes its scope with it.\n */\nfunction orderScope(elements: Element[], walk: OrderWalk): TabStopElement[] {\n const entries: ScopeEntry[] = [];\n const visit = (element: Element): void => {\n if (element === walk.from) walk.fromAt = walk.tree.length;\n const tabindex = authoredTabindex(element) ?? 0;\n const stop =\n (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element)\n ? element\n : null;\n if (stop) walk.tree.push(stop);\n const owned = ownedScope(element);\n if (owned) {\n if (tabindex >= 0) {\n entries.push({ tabindex, stops: [...(stop ? [stop] : []), ...orderScope(owned, walk)] });\n }\n return;\n }\n if (stop) entries.push({ tabindex, stops: [stop] });\n for (const child of Array.from(element.children)) visit(child);\n };\n for (const element of elements) visit(element);\n const positive = entries.filter((entry) => entry.tabindex > 0);\n positive.sort((a, b) => a.tabindex - b.tabindex);\n const rest = entries.filter((entry) => entry.tabindex === 0);\n return [...positive, ...rest].flatMap((entry) => entry.stops);\n}\n\n/**\n * Whether a candidate passes the radio-group rule the engine applies: a checked radio\n * among the stops always does; an unchecked one only as the first radio of a group with\n * no checked stop, and not while focus is on a radio of that group. `tree` is the walk's\n * stops in flat-tree order, so a group is counted within the walked root only.\n */\nfunction passesRadioRule(\n element: TabStopElement,\n tree: TabStopElement[],\n focusedRadio: HTMLInputElement | null,\n): boolean {\n if (!isRadio(element) || element.checked) return true;\n const group = tree.filter(\n (stop): stop is HTMLInputElement =>\n stop === element || (isRadio(stop) && sameRadioGroup(stop, element)),\n );\n if (group.some((radio) => radio.checked)) return false;\n if (group[0] !== element) return false;\n return focusedRadio === null || !sameRadioGroup(focusedRadio, element);\n}\n\n/**\n * The index in `order` a move from the walk's `from` visits first. A start the walk did\n * not meet is outside: the move enters at the first stop, or the last going backward. A\n * start that is a stop moves one place along the order. Any other start (the root, an\n * element with a negative `tabindex`) moves to the nearest stop after it in tree order,\n * or before it going backward, and wraps when there is none.\n */\nfunction startIndex(\n order: TabStopElement[],\n walk: OrderWalk,\n backward: boolean,\n isStop: (element: TabStopElement) => boolean,\n): number {\n if (walk.fromAt < 0) return backward ? order.length - 1 : 0;\n const at = order.indexOf(walk.from as TabStopElement);\n if (at >= 0) return backward ? at - 1 : at + 1;\n const neighbour = backward\n ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop)\n : walk.tree.slice(walk.fromAt).find(isStop);\n if (neighbour) return order.indexOf(neighbour);\n return backward ? order.length - 1 : 0;\n}\n\n/**\n * The stops below `root` in the order a sequential move from `from` visits them: the\n * next stop first (the previous one when `backward`), wrapping at the ends, with `from`\n * itself left out. Without a `from` — or with one outside `root` — the list starts at the\n * first stop, or at the last one when `backward`.\n *\n * The order is HTML's sequential navigation order within `root`: positive `tabindex`\n * first, ascending, then `tabindex=\"0\"` and native stops in tree order. Open shadow roots\n * are walked in flat-tree order, slots expanded, each scope ordered on its own; a closed\n * shadow root is not read. A radio group, counted within `root`, stops once: on its\n * checked radio when that radio can take focus, even with focus elsewhere in the group,\n * and otherwise on its first radio that can, unless focus is already in the group.\n * A link inside editable content without its own `tabindex` is not a stop. Every stop\n * also passes its applicable focusability and rendering checks.\n *\n * @param root - The element whose descendants are ordered; it is not a stop itself.\n * @param from - Where the move starts, usually {@link deepActiveElement}.\n * @param backward - Whether the move goes backward (`Shift+Tab`).\n */\nexport function sequentialTabStops(\n root: Element,\n from: Element | null = null,\n backward = false,\n): TabStopElement[] {\n const walk: OrderWalk = { from, tree: [], fromAt: from === root ? 0 : -1 };\n const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);\n const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;\n const isStop = (element: TabStopElement): boolean =>\n passesRadioRule(element, walk.tree, focusedRadio);\n const start = startIndex(order, walk, backward, isStop);\n const visited = backward\n ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()]\n : [...order.slice(start), ...order.slice(0, start)];\n return visited.filter((element) => element !== from && isStop(element));\n}\n\n/**\n * The element that holds focus in `ownerDocument`, looking through open shadow roots:\n * `document.activeElement` stops at a shadow host. A closed shadow root cannot be read,\n * so focus inside one reads as its host.\n */\nexport function deepActiveElement(ownerDocument: Document): Element | null {\n let active = ownerDocument.activeElement;\n while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;\n return active;\n}\n","/** 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 { Controller } from \"@hotwired/stimulus\";\nimport { setDefaultAttribute } from \"../utils/default_attribute\";\nimport { canTakeFocus, firstTabStop, isTabStop } from \"../utils/focus_candidate\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport type { FormFieldController } from \"./form_field_controller\";\n\n/** Native form controls that participate in constraint validation. */\ntype ValidatableControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;\n\n/**\n * Maps each `ValidityState` flag (in priority order) to the kebab-case suffix of\n * its per-constraint message-override attribute. The first flag that is `true`\n * wins, so e.g. `valueMissing` is reported before a stale `patternMismatch`.\n */\nconst CONSTRAINT_MESSAGE_KEYS: ReadonlyArray<readonly [keyof ValidityState, string]> = [\n [\"valueMissing\", \"value-missing\"],\n [\"typeMismatch\", \"type-mismatch\"],\n [\"patternMismatch\", \"pattern-mismatch\"],\n [\"tooShort\", \"too-short\"],\n [\"tooLong\", \"too-long\"],\n [\"rangeUnderflow\", \"range-underflow\"],\n [\"rangeOverflow\", \"range-overflow\"],\n [\"stepMismatch\", \"step-mismatch\"],\n [\"badInput\", \"bad-input\"],\n];\n\n/** Suffix of a generic message override applied to any failing constraint. */\nconst MESSAGE_PART = \"message\";\n/** Suffix opting a control into a declarative custom rule (`\"whitespace\"`). */\nconst DISALLOW_PART = \"disallow\";\n/** Default message when `disallow=\"whitespace\"` fails and no override is given. */\nconst DISALLOW_WHITESPACE_DEFAULT = \"Please enter a value that is not only whitespace.\";\n\n/** Public invalid-state hook owned by `stimeo--form-field`. */\nconst FORM_FIELD_INVALID_ATTR = \"data-stimeo--form-field-invalid\";\n\n/** Identity used to collect controls into one validation field. */\ntype FieldGroupKey = FormFieldController | ValidatableControl | string;\n\n/** A field's validatable controls, grouped so the whole field validates together. */\ninterface FieldGroup {\n readonly key: FieldGroupKey;\n readonly field: FormFieldController | undefined;\n readonly controls: ValidatableControl[];\n}\n\n/** Live form grouping resolved once for a validation operation. */\ninterface ValidationSnapshot {\n readonly groups: Map<FieldGroupKey, FieldGroup>;\n /** `null` is a deliberate miss key so delegated non-control events need one guard. */\n readonly groupByControl: Map<ValidatableControl | null, FieldGroup>;\n}\n\n/**\n * Headless, accessible **form-validation orchestration**.\n *\n * Markup contract (identifier: `stimeo--form-validation`):\n * <form data-controller=\"stimeo--form-validation\"\n * data-stimeo--form-validation-stimeo--form-field-outlet=\"[data-controller~='stimeo--form-field']\">\n * <div data-controller=\"stimeo--form-field\">\n * <label for=\"email\">Email</label>\n * <input id=\"email\" type=\"email\" required\n * data-stimeo--form-field-target=\"control\" />\n * <p hidden data-stimeo--form-field-target=\"error\"></p>\n * </div>\n * <button type=\"submit\">Save</button>\n * </form>\n *\n * Not an APG widget pattern — this is the *timing* layer for **error\n * identification** (WCAG 3.3.1) and **error suggestion** (3.3.3): it decides\n * *when* each control is checked and routes the browser's native\n * `validationMessage` into the field's {@link FormFieldController} error region.\n * The per-field ARIA wiring (`aria-invalid` / `aria-errormessage` /\n * `aria-describedby`) therefore lives in exactly one place — `stimeo--form-field`,\n * reached through a Stimulus **outlet** — and is never re-implemented here.\n *\n * `valid` dispatches `{}`; `invalid` dispatches `{ invalid: HTMLElement[] }`.\n *\n * @remarks\n * Behavior only — validation **rules** stay in the markup (native HTML\n * constraints: `required`, `type`, `pattern`, `min`/`max`, …) or in the consumer's\n * own `setCustomValidity()` calls, which `checkValidity()` surfaces transparently.\n * It sets the form's `novalidate` so it can replace the browser's default error\n * bubbles with accessible in-page visual errors plus the shared `stimeo--announcer`, and\n * restores the attribute on disconnect.\n *\n * Two declarative escape hatches let a field **exceed** native validation with no\n * consumer JS (author them on the control):\n * - **Per-constraint messages** — `data-stimeo--form-validation-message-<constraint>`\n * (`value-missing`, `too-short`, `too-long`, `pattern-mismatch`, `type-mismatch`,\n * `range-overflow`, `range-underflow`, `step-mismatch`, `bad-input`), or a generic\n * `data-stimeo--form-validation-message` fallback, override the shown text per failing\n * `ValidityState` flag — controlled, localizable wording that also covers an engine\n * whose native `validationMessage` is empty or generic. Falls back to native.\n * - **`data-stimeo--form-validation-disallow=\"whitespace\"`** — a built-in custom rule\n * rejecting a value that is blank after trimming (which slips past `required` /\n * `minlength`), wired through `setCustomValidity` so it blocks submit like any\n * native constraint.\n *\n * Behavior provided:\n * - On connect, suppresses native bubbles (`novalidate`, restored on disconnect)\n * and intercepts the form's `submit` in the **capture phase** so an invalid form\n * is cancelled before any other submit handler (e.g. `stimeo--submit-once`)\n * reacts to a submission that will never happen.\n * - On submit, validates every control; if any is invalid it blocks submission,\n * moves focus to the first invalid control (unless `focusInvalid` is `false`),\n * and dispatches `stimeo--form-validation:invalid`. An all-valid form dispatches\n * `:valid` and submits normally.\n * - Validates a field on blur once it has been interacted with (`validateOnBlur`),\n * and re-validates it on input while it is already touched\n * (`revalidateOnInput`) so a shown message clears the moment the value becomes\n * valid — but a pristine field is never eagerly flagged mid-typing.\n *\n * A control with no owning `stimeo--form-field` outlet is still validated (it can\n * block submit and receive focus) but renders no message.\n *\n * Radio groups work unchanged: point the field's `control` target at the\n * `role=\"radiogroup\"` container so the invalid state lands on the group, and the\n * group is reported as a single invalid entry (not one per radio).\n *\n * Rich widgets (listbox, time-picker, …) that keep their committed value in a\n * hidden holder participate by making that holder a **validatable mirror**:\n * `<input type=\"text\" hidden required>` — the `hidden` *attribute*, not\n * `type=\"hidden\"`, which is barred from constraint validation. Native\n * constraints then govern the widget's value with no extra JavaScript. The\n * widget dispatches a bubbling `change` on the mirror when a value is committed\n * (a completed interaction, so it validates immediately), and focus for an\n * invalid mirror is delegated to the field's visible `control` target — the\n * target itself when focusable, else its first focusable descendant.\n *\n * No-JS caveat: a `required` mirror also gates the browser's own pre-Stimulus\n * validation, which cannot surface UI on an invisible control. When the no-JS\n * fallback matters, author `novalidate` on the form (this controller preserves\n * an author-set attribute) so the submission reaches the server's validation.\n */\nexport class FormValidationController extends Controller<HTMLFormElement> {\n /** The generic message override, in the namespace this controller is registered under. */\n get #messageAttribute(): string {\n return `data-${this.identifier}-${MESSAGE_PART}`;\n }\n\n /** Marks the `novalidate` this controller applied, so an authored one is left alone. */\n get #novalidateMarker(): string {\n return `data-${this.identifier}-${FormValidationController.#NOVALIDATE_PART}`;\n }\n\n static override outlets = [\"stimeo--form-field\"];\n static override values = {\n validateOnBlur: { type: Boolean, default: true },\n validateOnChange: { type: Boolean, default: true },\n revalidateOnInput: { type: Boolean, default: true },\n focusInvalid: { type: Boolean, default: true },\n };\n static actions = [\"validate\"] as const;\n static events = [\"valid\", \"invalid\"] as const;\n\n declare readonly stimeoFormFieldOutlets: FormFieldController[];\n declare readonly stimeoFormFieldOutletElements: HTMLElement[];\n\n declare validateOnBlurValue: boolean;\n declare validateOnChangeValue: boolean;\n declare revalidateOnInputValue: boolean;\n declare focusInvalidValue: boolean;\n\n /** Suffix of the marker recording that we added `novalidate`, so we only remove our own. */\n static readonly #NOVALIDATE_PART = \"novalidate\";\n\n /** Object-backed groups already interacted with — the input revalidation gate. */\n readonly #touchedGroups = new WeakSet<FormFieldController | ValidatableControl>();\n\n /** String-backed fallback radio groups already interacted with. */\n readonly #touchedRadioGroups = new Set<string>();\n\n /**\n * Last message this controller wrote through `setCustomValidity`, kept as a\n * two-layer ownership ledger: a value is cleared only while the live message\n * still equals the recorded write. Iterable so disconnect can release every\n * surviving loan without touching a consumer's later custom error.\n */\n readonly #ownedCustomErrors = new Map<ValidatableControl, string>();\n\n /** Last invalid message routed to each field, suppressing duplicate reports. */\n readonly #reportedErrors = new WeakMap<FormFieldController, string>();\n\n /**\n * The delegated listeners of one connection, on the document that owned the form\n * when it connected; `disconnect()` releases them from that document.\n */\n readonly #listeners = new ListenerSet();\n\n readonly #onSubmit = (event: SubmitEvent): void => {\n if (event.target !== this.element) return;\n const invalid = this.#validateAll();\n if (invalid.length === 0) {\n this.dispatch(\"valid\", { detail: {} });\n return;\n }\n // Cancel the whole submit: preventDefault stops native/Turbo navigation;\n // stopImmediatePropagation keeps later submit handlers (e.g. submit-once's\n // busy state) from acting on a submission that will never happen.\n event.preventDefault();\n event.stopImmediatePropagation();\n const first = invalid[0];\n if (this.focusInvalidValue && first) this.#focusTargetFor(first)?.focus();\n this.dispatch(\"invalid\", { detail: { invalid } });\n };\n\n readonly #onFocusOut = (event: FocusEvent): void => {\n if (!this.validateOnBlurValue) return;\n const snapshot = this.#snapshot();\n const group = snapshot.groupByControl.get(this.#controlFrom(event.target));\n if (!group) return;\n // Focus moving *within* the same field (e.g. between members of a radio\n // group) is not leaving it — defer validation until focus actually exits.\n const related = event.relatedTarget;\n if (group.field && related instanceof Node && group.field.element.contains(related)) return;\n if (snapshot.groupByControl.get(this.#controlFrom(related))?.key === group.key) return;\n this.#markTouched(group.key);\n this.#applyGroup(group);\n };\n\n readonly #onInput = (event: Event): void => {\n if (!this.revalidateOnInputValue) return;\n const group = this.#snapshot().groupByControl.get(this.#controlFrom(event.target));\n // Only re-validate a field the user has already left once, so the first\n // keystroke never eagerly flags a control they are still filling in.\n if (!group || !this.#isTouched(group.key)) return;\n this.#applyGroup(group);\n };\n\n readonly #onChange = (event: Event): void => {\n if (!this.validateOnChangeValue) return;\n const group = this.#snapshot().groupByControl.get(this.#controlFrom(event.target));\n if (!group) return;\n // change marks a *committed* interaction (a picked option, a toggled box, a\n // widget writing its mirror), so unlike input it both touches and validates.\n this.#markTouched(group.key);\n this.#applyGroup(group);\n };\n\n /** Suppresses native bubbles and binds the submit / blur / input listeners. */\n override connect(): void {\n if (setDefaultAttribute(this.element, \"novalidate\", \"\")) {\n this.element.setAttribute(this.#novalidateMarker, \"\");\n }\n // Capture submit before form-bound actions. The other bubbling events live on\n // the same document so controls associated through `form=\"id\"` participate too;\n // #controlFrom rejects every control owned by a different form.\n const { ownerDocument } = this.element;\n this.#listeners.add(ownerDocument, \"submit\", this.#onSubmit, { capture: true });\n this.#listeners.add(ownerDocument, \"focusout\", this.#onFocusOut);\n this.#listeners.add(ownerDocument, \"input\", this.#onInput);\n this.#listeners.add(ownerDocument, \"change\", this.#onChange);\n }\n\n /** Tears down listeners and restores `novalidate` if we added it. */\n override disconnect(): void {\n this.#listeners.dispose();\n for (const [control, message] of this.#ownedCustomErrors) {\n if (control.validationMessage === message) control.setCustomValidity(\"\");\n }\n this.#ownedCustomErrors.clear();\n if (this.element.hasAttribute(this.#novalidateMarker)) {\n this.element.removeAttribute(\"novalidate\");\n this.element.removeAttribute(this.#novalidateMarker);\n }\n }\n\n /**\n * Validates every control now, rendering or clearing each field's message, and\n * returns whether the whole form is valid. Marks every field/group touched so\n * a later input from any sibling re-validates it. Bound via `data-action`\n * (`#validate`) or callable directly (e.g. before a programmatic submit).\n */\n validate(): boolean {\n return this.#validateAll().length === 0;\n }\n\n /**\n * Validates every control and returns one invalid control per field. Controls\n * are grouped by field first (see {@link #keyFor}) so a field with several\n * controls — a radio group, or a mirror plus its visible control — reflects\n * *all* of them: a valid sibling must never clear an invalid one's message.\n * Each group's first invalid control supplies the message and the focus target.\n */\n #validateAll(): ValidatableControl[] {\n const invalid: ValidatableControl[] = [];\n for (const group of this.#snapshot().groups.values()) {\n this.#markTouched(group.key);\n const firstInvalid = this.#applyGroup(group);\n if (firstInvalid) invalid.push(firstInvalid);\n }\n return invalid;\n }\n\n /** Builds the current field table in one pass over form controls and DOM depth. */\n #snapshot(): ValidationSnapshot {\n const fields = this.#fieldOutlets();\n const groups = new Map<FieldGroupKey, FieldGroup>();\n const groupByControl = new Map<ValidatableControl | null, FieldGroup>();\n\n for (const control of this.#controls) {\n const field = this.#fieldFor(control, fields);\n const key = this.#keyFor(control, field);\n let group = groups.get(key);\n if (!group) {\n group = { key, field, controls: [] };\n groups.set(key, group);\n }\n group.controls.push(control);\n groupByControl.set(control, group);\n }\n\n return { groups, groupByControl };\n }\n\n /** Records that a whole field/group, rather than one sibling control, was visited. */\n #markTouched(key: FieldGroupKey): void {\n if (typeof key === \"string\") {\n this.#touchedRadioGroups.add(key);\n } else {\n this.#touchedGroups.add(key);\n }\n }\n\n #isTouched(key: FieldGroupKey): boolean {\n return typeof key === \"string\"\n ? this.#touchedRadioGroups.has(key)\n : this.#touchedGroups.has(key);\n }\n\n /**\n * Runs native constraint validation across a field's controls and routes the\n * result to its `stimeo--form-field` outlet: the first invalid control's\n * `validationMessage` is shown, an all-valid field is cleared. Returns the\n * first invalid control (for the invalid list / focus), or `null` when valid.\n * Routing goes through the outlet, so the ARIA wiring is never duplicated here.\n */\n #applyGroup(group: FieldGroup): ValidatableControl | null {\n // Apply declarative custom rules (e.g. disallow=\"whitespace\") before reading\n // validity so they participate in checkValidity() like a native constraint.\n for (const control of group.controls) this.#syncCustomValidity(control);\n const firstInvalid = group.controls.find((control) => !control.checkValidity()) ?? null;\n if (group.field) {\n if (firstInvalid) {\n const message = this.#messageFor(firstInvalid);\n const alreadyReported =\n group.field.element.hasAttribute(FORM_FIELD_INVALID_ATTR) &&\n this.#reportedErrors.get(group.field) === message;\n if (!alreadyReported) group.field.setError(message, { focus: false });\n this.#reportedErrors.set(group.field, message);\n } else if (\n group.field.element.hasAttribute(FORM_FIELD_INVALID_ATTR) ||\n this.#reportedErrors.has(group.field)\n ) {\n group.field.clearError();\n this.#reportedErrors.delete(group.field);\n }\n }\n return firstInvalid;\n }\n\n /**\n * Resolves the message to show for an invalid control: a per-constraint\n * override (`data-stimeo--form-validation-message-<constraint>`) for the first failing\n * `ValidityState` flag, then a generic `data-stimeo--form-validation-message`\n * override, then the browser's native `validationMessage`. Authoring an override\n * gives controlled, localizable, theme-able wording with **no consumer JS** —\n * and a message where the engine's native one is empty or generic.\n */\n #messageFor(control: ValidatableControl): string {\n // `setCustomValidity()` owns its wording. In particular, a consumer's custom\n // error must not be replaced by a simultaneous native-constraint override.\n if (control.validity.customError) return control.validationMessage;\n for (const [flag, key] of CONSTRAINT_MESSAGE_KEYS) {\n if (control.validity[flag]) {\n return (\n this.#authoredMessage(control, `${this.#messageAttribute}-${key}`) ??\n this.#authoredMessage(control, this.#messageAttribute) ??\n control.validationMessage\n );\n }\n }\n return (\n control.validationMessage || this.#authoredMessage(control, this.#messageAttribute) || \"\"\n );\n }\n\n /**\n * Applies (or clears) a declarative custom constraint via `setCustomValidity`,\n * for controls that opt in with `data-stimeo--form-validation-disallow`. The supported\n * rule is `\"whitespace\"` — a value that is non-empty but blank\n * after trimming (which slips past `required` / `minlength`); its message follows\n * the whitespace-specific → generic → default chain.\n *\n * Don't-clobber-authored-state: an unknown/absent rule is never written, an\n * existing consumer custom error wins, and a controller error is only cleared\n * while the live message still equals the recorded controller write.\n */\n #syncCustomValidity(control: ValidatableControl): void {\n const recorded = this.#ownedCustomErrors.get(control);\n const ownsCurrent = recorded !== undefined && control.validationMessage === recorded;\n\n const violates =\n control.getAttribute(`data-${this.identifier}-${DISALLOW_PART}`) === \"whitespace\" &&\n control.value.length > 0 &&\n control.value.trim() === \"\";\n if (!violates) {\n if (ownsCurrent) control.setCustomValidity(\"\");\n this.#ownedCustomErrors.delete(control);\n return;\n }\n\n // One custom-validity slot exists per control. Preserve a consumer's live\n // entry; once they clear it, the next validation applies the declarative rule.\n if (control.validity.customError && !ownsCurrent) return;\n\n const message =\n this.#authoredMessage(control, `${this.#messageAttribute}-whitespace`) ??\n this.#authoredMessage(control, this.#messageAttribute) ??\n DISALLOW_WHITESPACE_DEFAULT;\n if (!ownsCurrent || recorded !== message) control.setCustomValidity(message);\n this.#ownedCustomErrors.set(control, message);\n }\n\n /** Returns a non-blank authored message, otherwise falls through to a fallback. */\n #authoredMessage(control: ValidatableControl, attribute: string): string | null {\n const message = control.getAttribute(attribute);\n return message && message.trim().length > 0 ? message : null;\n }\n\n /**\n * A grouping key that collects controls belonging to the same field: the owning\n * `stimeo--form-field` when present, else a radio group's shared `name`, else\n * the control itself (always distinct).\n */\n #keyFor(control: ValidatableControl, field: FormFieldController | undefined): FieldGroupKey {\n if (field) return field;\n if (control instanceof HTMLInputElement && control.type === \"radio\" && control.name) {\n return `radio:${control.name}`;\n }\n return control;\n }\n\n /**\n * Where focus should land for an invalid control. A visible control is focused\n * directly — the case for native fields and radios. A validatable mirror\n * (the `hidden` attribute) cannot receive focus, so focus is delegated to the\n * visible widget: the owning field's `control` target when it is itself\n * focusable, else its first focusable descendant (e.g. a roving-tabindex\n * member). Resolved structurally — never by probing `focus()` — so behavior\n * is deterministic and CSS-independent.\n */\n #focusTargetFor(control: ValidatableControl): HTMLElement | null {\n if (canTakeFocus(control)) return control;\n const field = this.#fieldFor(control, this.#fieldOutlets());\n if (!field?.hasControlTarget) return null;\n const root = field.controlTarget;\n if (isTabStop(root)) return root;\n return firstTabStop(root);\n }\n\n /** Pairs each live outlet element with its controller once per operation. */\n #fieldOutlets(): Map<HTMLElement, FormFieldController> {\n const fields = new Map<HTMLElement, FormFieldController>();\n const elements = this.stimeoFormFieldOutletElements;\n const outlets = this.stimeoFormFieldOutlets;\n for (let index = 0; index < elements.length; index++) {\n const element = elements[index];\n const outlet = outlets[index];\n if (element && outlet) fields.set(element, outlet);\n }\n return fields;\n }\n\n /** The nearest configured field ancestor of `control`, if any. */\n #fieldFor(\n control: ValidatableControl,\n fields: ReadonlyMap<HTMLElement, FormFieldController>,\n ): FormFieldController | undefined {\n let ancestor: HTMLElement | null = control;\n while (ancestor) {\n const field = fields.get(ancestor);\n if (field) return field;\n ancestor = ancestor.parentElement;\n }\n return undefined;\n }\n\n /** This form's native controls that participate in constraint validation. */\n get #controls(): ValidatableControl[] {\n const controls: ValidatableControl[] = [];\n for (const element of Array.from(this.element.elements)) {\n if (this.#isValidatable(element)) controls.push(element);\n }\n return controls;\n }\n\n /** Narrows an event target to a validatable control. */\n #controlFrom(target: EventTarget | null): ValidatableControl | null {\n return target instanceof Element && this.#isValidatable(target) && target.form === this.element\n ? target\n : null;\n }\n\n #isValidatable(element: Element): element is ValidatableControl {\n return (\n (element instanceof HTMLInputElement ||\n element instanceof HTMLSelectElement ||\n element instanceof HTMLTextAreaElement) &&\n // `willValidate` already excludes disabled, read-only, input[type=hidden],\n // and button-type controls. The `hidden` attribute intentionally does not\n // bar a text mirror from constraint validation.\n element.willValidate\n );\n }\n}\n"]}
@@ -19,22 +19,31 @@ import { Controller } from '@hotwired/stimulus';
19
19
  * net so the state never sticks). `minDuration` keeps the skeleton up long enough to
20
20
  * avoid a flicker.
21
21
  *
22
- * `start`, `end`, and `reconcile` dispatch `{}`. The last of those reports that
23
- * the Turbo cache rewind ended a load the frame never finished — `end` would
24
- * claim the frame arrived.
22
+ * `start`, `end`, and `reconcile` dispatch `{}`. The last of those reports that a
23
+ * connection found the frame marked `data-frame-loading` with no load of its own behind
24
+ * it — a page restored from the Turbo cache in the middle of a load, which can no
25
+ * longer arrive. `end` would claim the frame arrived.
25
26
  *
26
27
  * @remarks
27
- * Behavior only — it ships no skeleton markup or styling (pair with Skeleton/CSS);
28
- * loading is held purely in `aria-busy` / `data-frame-loading` and the optional
29
- * targets' `hidden`. The `content` target is marked `inert` while loading to block
30
- * double-submits, and focus inside the frame is explicitly blurred then restored
31
- * (when `restoreFocus`) so it is testable without relying on emergent `inert`
32
- * focus behavior. Listeners are torn down on `disconnect()` (Turbo navigation
33
- * included) along with the `MinDurationFloor` holding the finish back, kept
34
- * across an in-page move by `DetachGate`. A detach that keeps the element
35
- * returns the frame to its idle form, as does `BeforeCacheReset` for the
36
- * snapshot a cached page freezes; a frame render that swaps the targets mid-load
37
- * re-arms them.
28
+ * Behavior only — it ships no skeleton markup or styling (pair with `stimeo--skeleton`
29
+ * or the consumer's CSS); loading is held purely in `aria-busy` / `data-frame-loading`
30
+ * and the optional targets' `hidden`. The `content` target is marked `inert` while
31
+ * loading to block double-submits, and focus inside the frame is explicitly blurred
32
+ * then restored (when `restoreFocus`) so it is testable without relying on emergent
33
+ * `inert` focus behavior. Every one of those attributes is leased, so finishing a load gives
34
+ * back what the author wrote — an authored `aria-busy="false"` stays — and leaves a
35
+ * value the page wrote since alone, and the author's value is recorded on the element
36
+ * for a copy of it to find: a connection with no load of its own gives the hooks a copy
37
+ * of the page carries back to the author, before it reports `reconcile`. Listeners are
38
+ * torn down on `disconnect()` (Turbo navigation included) along with the
39
+ * `MinDurationFloor` holding the finish back, kept across an in-page move and a
40
+ * `data-turbo-permanent` frame Turbo carries to the next page by `DetachGate`. A
41
+ * detach that keeps the element returns the frame to its idle form; a frame render
42
+ * that swaps the targets mid-load re-arms them. A load is never abandoned on
43
+ * `turbo:before-cache`, which Turbo also dispatches on pages that stay — among them
44
+ * the promotion of this very frame's navigation to a page visit, dispatched after
45
+ * `turbo:frame-load` — so a finish held back by `minDuration` still ends the load,
46
+ * announces it and restores focus.
38
47
  */
39
48
  declare class FrameLoadingController extends Controller<HTMLElement> {
40
49
  #private;
@@ -57,10 +66,20 @@ declare class FrameLoadingController extends Controller<HTMLElement> {
57
66
  default: boolean;
58
67
  };
59
68
  };
69
+ static valueConstraints: {
70
+ minDuration: {
71
+ readonly finite: true;
72
+ readonly min: 0;
73
+ readonly max: 2147483647;
74
+ };
75
+ };
60
76
  static events: readonly ["start", "end", "reconcile"];
61
77
  readonly contentTarget: HTMLElement;
62
78
  readonly skeletonTarget: HTMLElement;
63
79
  readonly overlayTarget: HTMLElement;
80
+ readonly contentTargets: HTMLElement[];
81
+ readonly skeletonTargets: HTMLElement[];
82
+ readonly overlayTargets: HTMLElement[];
64
83
  readonly hasContentTarget: boolean;
65
84
  readonly hasSkeletonTarget: boolean;
66
85
  readonly hasOverlayTarget: boolean;
@@ -77,14 +96,28 @@ declare class FrameLoadingController extends Controller<HTMLElement> {
77
96
  * knows the frame is still busy.
78
97
  */
79
98
  skeletonTargetConnected(): void;
99
+ /**
100
+ * Shows the `skeleton` that stays when an earlier one leaves mid-load, hiding the
101
+ * departing one first when this controller revealed it. A departure that still
102
+ * resolves as a target is an in-page move and changes nothing.
103
+ */
104
+ skeletonTargetDisconnected(skeleton: HTMLElement): void;
80
105
  /** Re-shows an `overlay` that arrived mid-load — the same swap as the skeleton. */
81
106
  overlayTargetConnected(): void;
107
+ /** Shows the `overlay` that stays when an earlier one leaves mid-load, as the skeleton does. */
108
+ overlayTargetDisconnected(overlay: HTMLElement): void;
82
109
  /**
83
110
  * Re-blocks a `content` that arrived mid-load, so the stale copy stays unusable.
84
111
  * The element that left is released first and ownership is then decided afresh, so
85
112
  * an `inert` the replacement authored stays the consumer's.
86
113
  */
87
114
  contentTargetConnected(): void;
115
+ /**
116
+ * Blocks the `content` that stays when an earlier one leaves mid-load, releasing
117
+ * the departing one first. A departure that still resolves as a target is an
118
+ * in-page move and changes nothing.
119
+ */
120
+ contentTargetDisconnected(content: HTMLElement): void;
88
121
  }
89
122
 
90
123
  export { FrameLoadingController };