stimeo-ui 0.5.0 → 0.7.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 (230) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/controllers/alert_dialog_controller.d.ts +2 -0
  3. package/dist/controllers/alert_dialog_controller.js +75 -4
  4. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  5. package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
  6. package/dist/controllers/aspect_ratio_controller.js +19 -11
  7. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  8. package/dist/controllers/auto_submit_controller.d.ts +2 -0
  9. package/dist/controllers/auto_submit_controller.js.map +1 -1
  10. package/dist/controllers/avatar_controller.d.ts +36 -15
  11. package/dist/controllers/avatar_controller.js +237 -40
  12. package/dist/controllers/avatar_controller.js.map +1 -1
  13. package/dist/controllers/breadcrumb_controller.d.ts +2 -0
  14. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  15. package/dist/controllers/bulk_select_controller.d.ts +2 -0
  16. package/dist/controllers/bulk_select_controller.js.map +1 -1
  17. package/dist/controllers/calendar_controller.d.ts +2 -0
  18. package/dist/controllers/calendar_controller.js.map +1 -1
  19. package/dist/controllers/carousel_controller.d.ts +12 -3
  20. package/dist/controllers/carousel_controller.js +85 -9
  21. package/dist/controllers/carousel_controller.js.map +1 -1
  22. package/dist/controllers/character_counter_controller.d.ts +52 -20
  23. package/dist/controllers/character_counter_controller.js +338 -63
  24. package/dist/controllers/character_counter_controller.js.map +1 -1
  25. package/dist/controllers/checkbox_controller.d.ts +34 -5
  26. package/dist/controllers/checkbox_controller.js +136 -25
  27. package/dist/controllers/checkbox_controller.js.map +1 -1
  28. package/dist/controllers/color_picker_controller.d.ts +10 -1
  29. package/dist/controllers/color_picker_controller.js +35 -9
  30. package/dist/controllers/color_picker_controller.js.map +1 -1
  31. package/dist/controllers/combobox_controller.d.ts +2 -0
  32. package/dist/controllers/combobox_controller.js.map +1 -1
  33. package/dist/controllers/command_palette_controller.d.ts +2 -0
  34. package/dist/controllers/command_palette_controller.js +75 -4
  35. package/dist/controllers/command_palette_controller.js.map +1 -1
  36. package/dist/controllers/conditional_fields_controller.d.ts +42 -14
  37. package/dist/controllers/conditional_fields_controller.js +345 -51
  38. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  39. package/dist/controllers/confirm_controller.d.ts +2 -0
  40. package/dist/controllers/confirm_controller.js +75 -4
  41. package/dist/controllers/confirm_controller.js.map +1 -1
  42. package/dist/controllers/count_up_controller.d.ts +2 -0
  43. package/dist/controllers/count_up_controller.js.map +1 -1
  44. package/dist/controllers/countdown_controller.d.ts +4 -0
  45. package/dist/controllers/countdown_controller.js.map +1 -1
  46. package/dist/controllers/currency_input_controller.d.ts +2 -0
  47. package/dist/controllers/currency_input_controller.js.map +1 -1
  48. package/dist/controllers/data_grid_controller.d.ts +3 -0
  49. package/dist/controllers/data_grid_controller.js.map +1 -1
  50. package/dist/controllers/date_range_picker_controller.d.ts +23 -4
  51. package/dist/controllers/date_range_picker_controller.js +157 -30
  52. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  53. package/dist/controllers/dialog_controller.js +75 -4
  54. package/dist/controllers/dialog_controller.js.map +1 -1
  55. package/dist/controllers/direct_upload_controller.d.ts +56 -24
  56. package/dist/controllers/direct_upload_controller.js +201 -45
  57. package/dist/controllers/direct_upload_controller.js.map +1 -1
  58. package/dist/controllers/dirty_form_controller.d.ts +14 -6
  59. package/dist/controllers/dirty_form_controller.js +192 -29
  60. package/dist/controllers/dirty_form_controller.js.map +1 -1
  61. package/dist/controllers/dismissible_controller.d.ts +2 -0
  62. package/dist/controllers/dismissible_controller.js +83 -18
  63. package/dist/controllers/dismissible_controller.js.map +1 -1
  64. package/dist/controllers/drawer_controller.js +75 -4
  65. package/dist/controllers/drawer_controller.js.map +1 -1
  66. package/dist/controllers/empty_state_controller.d.ts +2 -0
  67. package/dist/controllers/empty_state_controller.js.map +1 -1
  68. package/dist/controllers/file_dropzone_controller.d.ts +9 -1
  69. package/dist/controllers/file_dropzone_controller.js +26 -3
  70. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  71. package/dist/controllers/filter_controller.d.ts +2 -0
  72. package/dist/controllers/filter_controller.js.map +1 -1
  73. package/dist/controllers/flash_controller.d.ts +4 -0
  74. package/dist/controllers/flash_controller.js.map +1 -1
  75. package/dist/controllers/focus_controller.d.ts +4 -3
  76. package/dist/controllers/focus_controller.js +75 -4
  77. package/dist/controllers/focus_controller.js.map +1 -1
  78. package/dist/controllers/form_field_controller.d.ts +50 -10
  79. package/dist/controllers/form_field_controller.js +280 -62
  80. package/dist/controllers/form_field_controller.js.map +1 -1
  81. package/dist/controllers/form_validation_controller.d.ts +10 -8
  82. package/dist/controllers/form_validation_controller.js +208 -83
  83. package/dist/controllers/form_validation_controller.js.map +1 -1
  84. package/dist/controllers/frame_loading_controller.d.ts +2 -0
  85. package/dist/controllers/frame_loading_controller.js.map +1 -1
  86. package/dist/controllers/highlight_controller.d.ts +2 -0
  87. package/dist/controllers/highlight_controller.js.map +1 -1
  88. package/dist/controllers/hover_card_controller.d.ts +2 -2
  89. package/dist/controllers/hover_card_controller.js.map +1 -1
  90. package/dist/controllers/idle_controller.d.ts +5 -3
  91. package/dist/controllers/idle_controller.js +27 -5
  92. package/dist/controllers/idle_controller.js.map +1 -1
  93. package/dist/controllers/input_mask_controller.d.ts +2 -0
  94. package/dist/controllers/input_mask_controller.js.map +1 -1
  95. package/dist/controllers/lazy_frame_controller.d.ts +2 -0
  96. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  97. package/dist/controllers/listbox_controller.d.ts +2 -0
  98. package/dist/controllers/listbox_controller.js.map +1 -1
  99. package/dist/controllers/local_time_controller.d.ts +2 -0
  100. package/dist/controllers/local_time_controller.js.map +1 -1
  101. package/dist/controllers/masonry_controller.d.ts +2 -0
  102. package/dist/controllers/masonry_controller.js.map +1 -1
  103. package/dist/controllers/menubar_controller.js +5 -3
  104. package/dist/controllers/menubar_controller.js.map +1 -1
  105. package/dist/controllers/meter_controller.d.ts +2 -0
  106. package/dist/controllers/meter_controller.js.map +1 -1
  107. package/dist/controllers/multi_select_controller.d.ts +48 -12
  108. package/dist/controllers/multi_select_controller.js +460 -151
  109. package/dist/controllers/multi_select_controller.js.map +1 -1
  110. package/dist/controllers/nested_form_controller.d.ts +2 -0
  111. package/dist/controllers/nested_form_controller.js.map +1 -1
  112. package/dist/controllers/network_status_controller.d.ts +2 -0
  113. package/dist/controllers/network_status_controller.js.map +1 -1
  114. package/dist/controllers/number_input_controller.d.ts +26 -6
  115. package/dist/controllers/number_input_controller.js +317 -51
  116. package/dist/controllers/number_input_controller.js.map +1 -1
  117. package/dist/controllers/otp_controller.d.ts +3 -0
  118. package/dist/controllers/otp_controller.js.map +1 -1
  119. package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
  120. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  121. package/dist/controllers/overflow_menu_controller.d.ts +2 -0
  122. package/dist/controllers/overflow_menu_controller.js +6 -1
  123. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  124. package/dist/controllers/pagination_controller.d.ts +2 -0
  125. package/dist/controllers/pagination_controller.js +35 -1
  126. package/dist/controllers/pagination_controller.js.map +1 -1
  127. package/dist/controllers/password_reveal_controller.d.ts +2 -0
  128. package/dist/controllers/password_reveal_controller.js.map +1 -1
  129. package/dist/controllers/password_strength_controller.d.ts +5 -3
  130. package/dist/controllers/password_strength_controller.js +20 -2
  131. package/dist/controllers/password_strength_controller.js.map +1 -1
  132. package/dist/controllers/persist_controller.d.ts +33 -19
  133. package/dist/controllers/persist_controller.js +449 -120
  134. package/dist/controllers/persist_controller.js.map +1 -1
  135. package/dist/controllers/pointer_drag_controller.d.ts +3 -0
  136. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  137. package/dist/controllers/popover_controller.d.ts +2 -2
  138. package/dist/controllers/popover_controller.js +77 -4
  139. package/dist/controllers/popover_controller.js.map +1 -1
  140. package/dist/controllers/portal_controller.d.ts +3 -2
  141. package/dist/controllers/portal_controller.js.map +1 -1
  142. package/dist/controllers/preview_guard_controller.d.ts +2 -0
  143. package/dist/controllers/preview_guard_controller.js.map +1 -1
  144. package/dist/controllers/progress_controller.d.ts +3 -0
  145. package/dist/controllers/progress_controller.js.map +1 -1
  146. package/dist/controllers/radio_group_controller.d.ts +44 -15
  147. package/dist/controllers/radio_group_controller.js +540 -56
  148. package/dist/controllers/radio_group_controller.js.map +1 -1
  149. package/dist/controllers/rating_controller.d.ts +38 -31
  150. package/dist/controllers/rating_controller.js +276 -89
  151. package/dist/controllers/rating_controller.js.map +1 -1
  152. package/dist/controllers/reading_progress_controller.d.ts +2 -0
  153. package/dist/controllers/reading_progress_controller.js.map +1 -1
  154. package/dist/controllers/resizable_controller.d.ts +2 -0
  155. package/dist/controllers/resizable_controller.js +33 -0
  156. package/dist/controllers/resizable_controller.js.map +1 -1
  157. package/dist/controllers/roving_controller.d.ts +6 -0
  158. package/dist/controllers/roving_controller.js +60 -5
  159. package/dist/controllers/roving_controller.js.map +1 -1
  160. package/dist/controllers/scroll_area_controller.d.ts +25 -11
  161. package/dist/controllers/scroll_area_controller.js +557 -125
  162. package/dist/controllers/scroll_area_controller.js.map +1 -1
  163. package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
  164. package/dist/controllers/scroll_visibility_controller.js +33 -0
  165. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  166. package/dist/controllers/scrollspy_controller.d.ts +2 -0
  167. package/dist/controllers/scrollspy_controller.js.map +1 -1
  168. package/dist/controllers/separator_controller.d.ts +54 -11
  169. package/dist/controllers/separator_controller.js +354 -38
  170. package/dist/controllers/separator_controller.js.map +1 -1
  171. package/dist/controllers/sidebar_controller.js +83 -10
  172. package/dist/controllers/sidebar_controller.js.map +1 -1
  173. package/dist/controllers/skeleton_controller.d.ts +2 -0
  174. package/dist/controllers/skeleton_controller.js.map +1 -1
  175. package/dist/controllers/slider_controller.d.ts +2 -0
  176. package/dist/controllers/slider_controller.js.map +1 -1
  177. package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
  178. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  179. package/dist/controllers/spinner_controller.d.ts +2 -0
  180. package/dist/controllers/spinner_controller.js.map +1 -1
  181. package/dist/controllers/step_indicator_controller.d.ts +2 -0
  182. package/dist/controllers/step_indicator_controller.js.map +1 -1
  183. package/dist/controllers/stepper_controller.d.ts +2 -0
  184. package/dist/controllers/stepper_controller.js.map +1 -1
  185. package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
  186. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  187. package/dist/controllers/sticky_observer_controller.d.ts +2 -0
  188. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  189. package/dist/controllers/submit_once_controller.d.ts +94 -38
  190. package/dist/controllers/submit_once_controller.js +399 -121
  191. package/dist/controllers/submit_once_controller.js.map +1 -1
  192. package/dist/controllers/switch_controller.d.ts +2 -0
  193. package/dist/controllers/switch_controller.js.map +1 -1
  194. package/dist/controllers/tags_input_controller.d.ts +43 -11
  195. package/dist/controllers/tags_input_controller.js +356 -120
  196. package/dist/controllers/tags_input_controller.js.map +1 -1
  197. package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
  198. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  199. package/dist/controllers/theme_controller.d.ts +2 -0
  200. package/dist/controllers/theme_controller.js +8 -6
  201. package/dist/controllers/theme_controller.js.map +1 -1
  202. package/dist/controllers/time_picker_controller.d.ts +44 -13
  203. package/dist/controllers/time_picker_controller.js +296 -107
  204. package/dist/controllers/time_picker_controller.js.map +1 -1
  205. package/dist/controllers/toast_controller.d.ts +4 -0
  206. package/dist/controllers/toast_controller.js.map +1 -1
  207. package/dist/controllers/toggle_group_controller.d.ts +41 -13
  208. package/dist/controllers/toggle_group_controller.js +378 -55
  209. package/dist/controllers/toggle_group_controller.js.map +1 -1
  210. package/dist/controllers/toolbar_controller.js +5 -3
  211. package/dist/controllers/toolbar_controller.js.map +1 -1
  212. package/dist/controllers/tooltip_controller.d.ts +2 -2
  213. package/dist/controllers/tooltip_controller.js.map +1 -1
  214. package/dist/controllers/transition_controller.d.ts +4 -3
  215. package/dist/controllers/transition_controller.js.map +1 -1
  216. package/dist/controllers/tree_view_controller.d.ts +2 -0
  217. package/dist/controllers/tree_view_controller.js +7 -4
  218. package/dist/controllers/tree_view_controller.js.map +1 -1
  219. package/dist/index.js +4963 -1581
  220. package/dist/index.js.map +1 -1
  221. package/dist/inspector/cli.d.ts +123 -6
  222. package/dist/inspector/cli.js +212 -18
  223. package/dist/inspector/cli.js.map +1 -1
  224. package/dist/inspector/cli_bin.js +274 -50
  225. package/dist/inspector/cli_bin.js.map +1 -1
  226. package/dist/inspector/examples.json +15 -15
  227. package/dist/inspector/manifest.json +602 -80
  228. package/dist/positioning/index.d.ts +4 -2
  229. package/dist/positioning/index.js.map +1 -1
  230. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/safe_timeout.ts","../../src/controllers/persist_controller.ts"],"names":[],"mappings":"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","file":"persist_controller.js","sourcesContent":["/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\n\n/** Field controls this controller can persist. */\ntype PersistField = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n\n/** Input types that carry no meaningful, restorable value. */\nconst NON_VALUE_TYPES = new Set([\"file\", \"submit\", \"reset\", \"button\", \"image\"]);\n/** localStorage key prefix so drafts never clobber unrelated app storage. */\nconst STORAGE_PREFIX = \"stimeo--persist:\";\n/** Separator for disambiguating repeated same-name fields (cannot occur in a real field name). */\nconst OCCURRENCE_SEP = \"\\u0000\";\n\n/**\n * Headless draft-autosave behavior: persists a form's field values to\n * `localStorage` and restores them across Turbo navigations and reloads (no APG\n * pattern; a state-holding utility). The Alpine `persist` equivalent.\n *\n * Markup contract (identifier: `stimeo--persist`):\n * <form data-controller=\"stimeo--persist\"\n * data-stimeo--persist-key-value=\"contact-draft\">\n * <input name=\"name\">\n * <textarea name=\"message\"></textarea>\n * </form>\n *\n * On connect it restores any saved values under `key` (falling back to the\n * element's `id`), then debounce-saves on every input/change. Password fields (and\n * anything in `exclude`) are never written. A `clear()` action — or the `clearOn`\n * event (e.g. `submit`) — drops the draft. Restoring never moves focus.\n *\n * @remarks\n * Behavior only. State lives entirely in `localStorage` (no module-scope state), so\n * instances never interfere. The input listener and debounce timer are removed on\n * `disconnect()` (Turbo navigation included), where a pending save is flushed first\n * so an in-flight edit is not lost.\n */\nexport class PersistController extends Controller<HTMLElement> {\n static override targets = [\"field\"];\n static override values = {\n key: { type: String, default: \"\" },\n debounce: { type: Number, default: 400 },\n exclude: { type: Array, default: [\"password\"] },\n clearOn: { type: String, default: \"\" },\n };\n static actions = [\"clear\"] as const;\n static events = [\"restore\", \"save\", \"clear\"] as const;\n\n declare readonly fieldTargets: PersistField[];\n declare readonly hasFieldTarget: boolean;\n\n declare keyValue: string;\n declare debounceValue: number;\n declare excludeValue: string[];\n declare clearOnValue: string;\n\n readonly #timeouts = new SafeTimeout();\n #saveId: number | null = null;\n\n readonly #onInput = (): void => {\n this.#scheduleSave();\n };\n\n readonly #onClearEvent = (): void => {\n this.clear();\n };\n\n override connect(): void {\n if (this.#storageKey === null) return;\n this.#restore();\n this.element.addEventListener(\"input\", this.#onInput);\n this.element.addEventListener(\"change\", this.#onInput);\n if (this.clearOnValue.length > 0) {\n this.element.addEventListener(this.clearOnValue, this.#onClearEvent);\n }\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"input\", this.#onInput);\n this.element.removeEventListener(\"change\", this.#onInput);\n if (this.clearOnValue.length > 0) {\n this.element.removeEventListener(this.clearOnValue, this.#onClearEvent);\n }\n // Flush a pending save so an in-flight edit survives a Turbo navigation.\n if (this.#saveId !== null) {\n this.#timeouts.clear(this.#saveId);\n this.#saveId = null;\n this.#write();\n }\n this.#timeouts.clearAll();\n }\n\n /** Drops the saved draft and clears the restored marker. */\n clear(): void {\n const key = this.#storageKey;\n if (key === null) return;\n if (this.#saveId !== null) {\n this.#timeouts.clear(this.#saveId);\n this.#saveId = null;\n }\n this.#removeItem(key);\n this.element.removeAttribute(\"data-persist-restored\");\n this.dispatch(\"clear\", { detail: { key: this.#logicalKey } });\n }\n\n /** Schedules a debounced save. */\n #scheduleSave(): void {\n if (this.#saveId !== null) this.#timeouts.clear(this.#saveId);\n this.#saveId = this.#timeouts.set(() => {\n this.#saveId = null;\n this.#save();\n }, this.debounceValue);\n }\n\n /** Writes the current values and emits `save`. */\n #save(): void {\n this.#write();\n this.dispatch(\"save\", { detail: { key: this.#logicalKey } });\n }\n\n /** Serializes persistable fields and stores them under the storage key. */\n #write(): void {\n const key = this.#storageKey;\n if (key === null) return;\n const data: Record<string, unknown> = {};\n for (const { field, key: fieldKey } of this.#fieldEntries()) {\n if (field instanceof HTMLInputElement && field.type === \"checkbox\") {\n data[fieldKey] = field.checked;\n } else if (field instanceof HTMLInputElement && field.type === \"radio\") {\n if (field.checked) data[fieldKey] = field.value;\n } else if (field instanceof HTMLSelectElement && field.multiple) {\n data[fieldKey] = Array.from(field.selectedOptions).map((o) => o.value);\n } else {\n data[fieldKey] = field.value;\n }\n }\n this.#setItem(key, JSON.stringify(data));\n }\n\n /** Applies any saved values to the fields, without moving focus. */\n #restore(): void {\n const key = this.#storageKey;\n if (key === null) return;\n const raw = this.#getItem(key);\n if (raw === null) return;\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return;\n }\n let restoredAny = false;\n for (const { field, key: fieldKey } of this.#fieldEntries()) {\n if (!Object.hasOwn(data, fieldKey)) continue;\n this.#applyValue(field, data[fieldKey]);\n restoredAny = true;\n }\n if (restoredAny) {\n this.element.setAttribute(\"data-persist-restored\", \"true\");\n this.dispatch(\"restore\", { detail: { key: this.#logicalKey } });\n }\n }\n\n /** Sets a single field's value from a stored entry. */\n #applyValue(field: PersistField, value: unknown): void {\n if (field instanceof HTMLInputElement && field.type === \"checkbox\") {\n field.checked = Boolean(value);\n } else if (field instanceof HTMLInputElement && field.type === \"radio\") {\n field.checked = field.value === value;\n } else if (field instanceof HTMLSelectElement && field.multiple) {\n const selected = new Set(Array.isArray(value) ? value.map(String) : []);\n for (const option of Array.from(field.options)) {\n option.selected = selected.has(option.value);\n }\n } else {\n field.value = String(value);\n }\n }\n\n /**\n * Persistable fields paired with a stable storage key. Uniquely-named fields key\n * by their name. Repeated same-name fields (e.g. a `tags[]` checkbox group or\n * array text inputs) are disambiguated by DOM-order occurrence — the first keeps\n * its plain `name`, later ones get a NUL-separated index suffix — so each is\n * stored and restored individually instead of the last one clobbering the rest.\n * Radios are the exception: a group intentionally shares one key (one value per\n * name).\n */\n #fieldEntries(): Array<{ field: PersistField; key: string }> {\n const entries: Array<{ field: PersistField; key: string }> = [];\n const occurrence = new Map<string, number>();\n for (const field of this.#fields()) {\n const name = this.#keyOf(field);\n if (name === null) continue;\n if (field instanceof HTMLInputElement && field.type === \"radio\") {\n entries.push({ field, key: name });\n continue;\n }\n const seen = occurrence.get(name) ?? 0;\n occurrence.set(name, seen + 1);\n entries.push({ field, key: seen === 0 ? name : `${name}${OCCURRENCE_SEP}${seen}` });\n }\n return entries;\n }\n\n /** The fields to persist: `field` targets, or the element's named controls. */\n #fields(): PersistField[] {\n const candidates = this.hasFieldTarget\n ? this.fieldTargets\n : Array.from(this.element.querySelectorAll<PersistField>(\"input, textarea, select\"));\n return candidates.filter((field) => this.#persistable(field));\n }\n\n /** Whether a field carries a restorable, non-excluded value. */\n #persistable(field: PersistField): boolean {\n if (this.#keyOf(field) === null) return false;\n const type = field instanceof HTMLInputElement ? field.type : \"\";\n if (NON_VALUE_TYPES.has(type)) return false;\n if (this.excludeValue.includes(type)) return false;\n if (field.name.length > 0 && this.excludeValue.includes(field.name)) return false;\n return true;\n }\n\n /** A stable storage sub-key for a field (its name, else id). */\n #keyOf(field: PersistField): string | null {\n return field.name || field.id || null;\n }\n\n /** The logical key (key Value or element id), or null when neither is set. */\n get #logicalKey(): string | null {\n const key = this.keyValue || this.element.id;\n return key.length > 0 ? key : null;\n }\n\n /** The prefixed localStorage key, or null when persistence is disabled. */\n get #storageKey(): string | null {\n const logical = this.#logicalKey;\n return logical === null ? null : `${STORAGE_PREFIX}${logical}`;\n }\n\n #getItem(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n #setItem(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage full or unavailable (private mode): persistence is best-effort.\n }\n }\n\n #removeItem(key: string): void {\n try {\n window.localStorage.removeItem(key);\n } catch {\n // Ignore: nothing to clear if storage is unavailable.\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/microtask_coalescer.ts","../../src/utils/safe_storage.ts","../../src/utils/safe_timeout.ts","../../src/utils/string_list.ts","../../src/controllers/persist_controller.ts"],"names":[],"mappings":";;;;;AAqDO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;ACzDO,SAAS,iBAAiB,GAAA,EAA2C;AAC1E,EAAA,IAAI;AACF,IAAA,OAAO,EAAE,IAAI,IAAA,EAAM,KAAA,EAAO,OAAO,YAAA,CAAa,OAAA,CAAQ,GAAG,CAAA,EAAE;AAAA,EAC7D,SAAS,KAAA,EAAgB;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAAA,EAC5B;AACF;AASO,SAAS,iBAAA,CAAkB,KAAa,KAAA,EAAoC;AACjF,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,GAAA,EAAK,KAAK,CAAA;AACtC,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,KAAA,CAAA,EAAU;AAAA,EACtC,SAAS,KAAA,EAAgB;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAAA,EAC5B;AACF;AAQO,SAAS,mBAAmB,GAAA,EAAkC;AACnE,EAAA,IAAI;AACF,IAAA,MAAA,CAAO,YAAA,CAAa,WAAW,GAAG,CAAA;AAClC,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,KAAA,CAAA,EAAU;AAAA,EACtC,SAAS,KAAA,EAAgB;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAAA,EAC5B;AACF;;;AC5CA,IAAe,gBAAf,MAA6B;AAAA;AAAA,EAER,GAAA,uBAAU,GAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAczC,MAAM,EAAA,EAAkB;AACtB,IAAA,IAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,EAAE,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAA,GAAiB;AACf,IAAA,KAAA,MAAW,EAAA,IAAM,KAAK,GAAA,EAAK;AACzB,MAAA,IAAA,CAAK,OAAO,EAAE,CAAA;AAAA,IAChB;AACA,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,EACjB;AAAA;AAAA,EAGA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,GAAA,CAAI,IAAA;AAAA,EAClB;AACF,CAAA;AAmBO,IAAM,WAAA,GAAN,cAA0B,aAAA,CAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7C,GAAA,CAAI,UAAsB,KAAA,EAAuB;AAC/C,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,QAAA,CAAS,MAAM;AAC7B,MAAA,IAAA,CAAK,GAAA,CAAI,OAAO,EAAE,CAAA;AAClB,MAAA,QAAA,EAAS;AAAA,IACX,GAAG,KAAK,CAAA;AACR,IAAA,IAAA,CAAK,GAAA,CAAI,IAAI,EAAE,CAAA;AACf,IAAA,OAAO,EAAA;AAAA,EACT;AAAA,EAEU,QAAA,CAAS,UAAsB,KAAA,EAAuB;AAC9D,IAAA,OAAO,MAAA,CAAO,UAAA,CAAW,QAAA,EAAU,KAAK,CAAA;AAAA,EAC1C;AAAA,EAEU,OAAO,EAAA,EAAkB;AACjC,IAAA,MAAA,CAAO,aAAa,EAAE,CAAA;AAAA,EACxB;AACF,CAAA;;;ACxEO,SAAS,eAAA,CAAgB,GAAA,EAAa,QAAA,GAA8B,EAAC,EAAa;AAIvF,EAAA,MAAM,IAAA,GAAO,IAAI,IAAA,EAAK;AACtB,EAAA,IAAI,KAAK,MAAA,KAAW,CAAA,EAAG,OAAO,CAAC,GAAG,QAAQ,CAAA;AAE1C,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EAC1B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,CAAC,GAAG,QAAQ,CAAA;AAAA,EACrB;AAEA,EAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,MAAM,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAA;AAC/C,EAAA,OAAO,OAAO,MAAA,CAAO,CAAC,KAAA,KAA2B,OAAO,UAAU,QAAQ,CAAA;AAC5E;;;ACbA,IAAM,eAAA,uBAAsB,GAAA,CAAI,CAAC,QAAQ,QAAA,EAAU,OAAA,EAAS,QAAA,EAAU,OAAO,CAAC,CAAA;AAE9E,IAAM,eAAA,mBAAkB,IAAI,GAAA,CAAI,CAAC,UAAU,CAAC,CAAA;AAE5C,IAAM,eAAA,GAAkB,CAAC,oBAAA,EAAsB,SAAA,EAAW,MAAM,CAAA;AAEhE,IAAM,cAAA,GAAiB,kBAAA;AAEvB,IAAM,cAAA,GAAiB,IAAA;AAEvB,IAAM,mBAAA,GAAsB,iBAAA;AAC5B,IAAM,gBAAA,GAAmB,GAAA;AAGzB,IAAM,QAAA,GAAW,CAAC,KAAA,KAChB,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA;AAGrE,IAAM,iBAAiB,CAAC,KAAA,KACtB,OAAO,KAAA,KAAU,QAAA,IACjB,OAAO,KAAA,KAAU,SAAA,IAChB,MAAM,OAAA,CAAQ,KAAK,KAAK,KAAA,CAAM,KAAA,CAAM,CAAC,IAAA,KAAS,OAAO,SAAS,QAAQ,CAAA;AAGzE,IAAM,YAAA,GAAe,CAAC,GAAA,KAAuC;AAC3D,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACzB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,QAAA,CAAS,MAAM,CAAA,IAAK,MAAA,CAAO,OAAA,KAAY,CAAA,IAAK,CAAC,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,MAAM,GAAG,OAAO,IAAA;AAEvF,EAAA,MAAM,SAAgC,EAAC;AACvC,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,SAAA,IAAa,OAAO,MAAA,EAAQ;AACrC,IAAA,IAAI,CAAC,QAAA,CAAS,SAAS,CAAA,EAAG,OAAO,IAAA;AACjC,IAAA,MAAM,EAAE,GAAA,EAAK,KAAA,EAAM,GAAI,SAAA;AACvB,IAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,MAAA,KAAW,KAAK,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,IAAA;AACzE,IAAA,IAAI,CAAC,cAAA,CAAe,KAAK,CAAA,EAAG,OAAO,IAAA;AACnC,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,GAAA,EAAK,KAAA,EAAO,CAAA;AAAA,EAC5B;AACA,EAAA,OAAO,EAAE,OAAA,EAAS,CAAA,EAAG,MAAA,EAAO;AAC9B,CAAA;AAGA,IAAM,iBAAiB,CAAC,IAAA,KACtB,gBAAgB,gBAAA,IAChB,IAAA,YAAgB,uBAChB,IAAA,YAAgB,iBAAA;AAiCX,IAAM,iBAAA,GAAN,cAAgC,UAAA,CAAwB;AAAA,EAC7D,OAAgB,OAAA,GAAU,CAAC,OAAO,CAAA;AAAA,EAClC,OAAgB,MAAA,GAAS;AAAA,IACvB,GAAA,EAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACjC,QAAA,EAAU,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,gBAAA,EAAiB;AAAA;AAAA,IAEpD,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA,EAAG;AAAA,IACrC,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,SAAS,EAAA;AAAG,GACvC;AAAA,EACA,OAAO,OAAA,GAAU,CAAC,OAAO,CAAA;AAAA,EACzB,OAAO,MAAA,GAAS,CAAC,SAAA,EAAW,MAAA,EAAQ,SAAS,OAAO,CAAA;AAAA,EAU3C,SAAA,GAAY,IAAI,WAAA,EAAY;AAAA,EAC5B,kBAAkB,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,uBAAuB,CAAA;AAAA,EAC3E,SAAA,GAAY,IAAI,gBAAA,CAAiB,CAAC,YAAY,IAAA,CAAK,YAAA,CAAa,OAAO,CAAC,CAAA;AAAA,EACjF,YAAA,GAAmC,IAAA;AAAA,EACnC,UAAA,GAAa,KAAA;AAAA,EACb,WAAA,GAA6B,IAAA;AAAA,EAC7B,QAAA,GAAkC,IAAA;AAAA,EAClC,kBAAA,GAAoC,IAAA;AAAA,EACpC,SAAA,GAA+B,eAAA;AAAA,EAC/B,YAAA,uBAAmB,OAAA,EAAsB;AAAA,EACzC,cAAA,uBAAqB,GAAA,EAAkB;AAAA,EAE9B,QAAA,GAAW,CAAC,KAAA,KAAuB;AAC1C,IAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AACpB,IAAA,IAAI,CAAC,eAAe,KAAK,CAAA,IAAK,CAAC,IAAA,CAAK,UAAA,CAAW,KAAK,CAAA,EAAG;AACvD,IAAA,IAAI,KAAK,cAAA,IAAkB,CAAC,KAAK,YAAA,CAAa,QAAA,CAAS,KAAK,CAAA,EAAG;AAC/D,IAAA,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,KAAM,IAAA,IAAQ,CAAC,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,IAAA,CAAK,SAAS,CAAA,EAAG;AAC9E,IAAA,IAAA,CAAK,aAAA,EAAc;AAAA,EACrB,CAAA;AAAA,EAES,gBAAgB,MAAY;AACnC,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb,CAAA;AAAA,EAES,OAAA,GAAgB;AACvB,IAAA,IAAI,KAAK,UAAA,EAAY;AACrB,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAClB,IAAA,IAAA,CAAK,WAAA,GAAc,KAAK,kBAAA,EAAmB;AAC3C,IAAA,IAAA,CAAK,SAAA,GAAY,eAAA,CAAgB,IAAA,CAAK,YAAA,EAAc,eAAe,CAAA;AACnE,IAAA,IAAA,CAAK,YAAA,uBAAmB,OAAA,EAAsB;AAC9C,IAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAC1B,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,uBAAuB,CAAA;AAEpD,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,QAAQ,CAAA;AACpD,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAAiB,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAA;AACrD,IAAA,IAAA,CAAK,YAAA,EAAa;AAClB,IAAA,IAAA,CAAK,gBAAgB,QAAA,EAAS;AAC9B,IAAA,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAS;AAAA,MACnC,UAAA,EAAY,IAAA;AAAA,MACZ,eAAA,EAAiB;AAAA,QACf,SAAA;AAAA,QACA,iBAAA;AAAA,QACA,6BAAA;AAAA,QACA,IAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,SAAA,EAAW,IAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,IAAA,CAAK,OAAA,CAAQ,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,QAAQ,CAAA;AACvD,IAAA,IAAA,CAAK,OAAA,CAAQ,mBAAA,CAAoB,QAAA,EAAU,IAAA,CAAK,QAAQ,CAAA;AACxD,IAAA,IAAA,CAAK,cAAA,EAAe;AACpB,IAAA,IAAA,CAAK,UAAU,UAAA,EAAW;AAC1B,IAAA,IAAA,CAAK,gBAAgB,MAAA,EAAO;AAC5B,IAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAC1B,IAAA,IAAA,CAAK,UAAU,QAAA,EAAS;AACxB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,uBAAuB,CAAA;AACpD,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,EAClB;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,WAAA,IAAe,IAAA,CAAK,kBAAA,EAAmB;AAC/D,IAAA,IAAI,eAAe,IAAA,EAAM;AACzB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,IAAA,CAAK,WAAA,CAAY,UAAU,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,IAAA,CAAK,cAAA,CAAe,UAAA,EAAY,QAAA,EAAU,aAAa,CAAA;AACvD,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,uBAAuB,CAAA;AACpD,IAAA,IAAA,CAAK,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,GAAA,EAAK,UAAA,IAAc,CAAA;AAAA,EACxD;AAAA;AAAA,EAGA,mBAAA,GAA4B;AAC1B,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,YAAA,EAAa;AAAA,EACzC;AAAA;AAAA,EAGA,eAAA,GAAwB;AACtB,IAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,iBAAA,EAAkB;AAAA,EAC9C;AAAA;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAI,KAAK,UAAA,IAAc,IAAA,CAAK,YAAA,KAAiB,IAAA,OAAW,aAAA,EAAc;AAAA,EACxE;AAAA;AAAA,EAGA,mBAAA,GAA4B;AAC1B,IAAA,MAAM,WAAW,IAAA,CAAK,SAAA;AACtB,IAAA,IAAA,CAAK,SAAA,GAAY,eAAA,CAAgB,IAAA,CAAK,YAAA,EAAc,eAAe,CAAA;AACnE,IAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AAItB,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,gBAAA,EAAiB,EAAG;AAC3C,MAAA,IAAI,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,IAAA,CAAK,SAAS,CAAA,IAAK,CAAC,IAAA,CAAK,YAAA,CAAa,KAAA,EAAO,QAAQ,CAAA,EAAG;AACnF,QAAA,IAAA,CAAK,cAAA,CAAe,IAAI,KAAK,CAAA;AAAA,MAC/B;AAAA,IACF;AACA,IAAA,IAAA,CAAK,gBAAgB,QAAA,EAAS;AAAA,EAChC;AAAA;AAAA,EAGA,aAAA,GAAsB;AACpB,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,IAAI,eAAe,IAAA,EAAM;AACzB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,MAAM;AAClC,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,IAAA,CAAK,MAAM,UAAU,CAAA;AAAA,IACvB,CAAA,EAAG,KAAK,cAAc,CAAA;AACtB,IAAA,IAAA,CAAK,YAAA,GAAe,EAAE,EAAA,EAAI,UAAA,EAAW;AAAA,EACvC;AAAA;AAAA,EAGA,kBAAA,GAA2B;AACzB,IAAA,MAAM,UAAU,IAAA,CAAK,YAAA;AACrB,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,IAAA,CAAK,SAAA,CAAU,KAAA,CAAM,OAAA,CAAQ,EAAE,CAAA;AAC/B,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAAA,EACtB;AAAA;AAAA,EAGA,iBAAA,GAA0B;AACxB,IAAA,MAAM,UAAU,IAAA,CAAK,YAAA;AACrB,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,IAAA,CAAK,kBAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,KAAA,CAAM,QAAQ,UAAU,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,MAAM,UAAA,EAA6B;AACjC,IAAA,MAAM,OAAA,GAAU,KAAK,UAAA,EAAW;AAChC,IAAA,MAAM,MAAA,GAAS,kBAAkB,IAAA,CAAK,WAAA,CAAY,UAAU,CAAA,EAAG,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AACtF,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,IAAA,CAAK,cAAA,CAAe,UAAA,EAAY,OAAA,EAAS,aAAa,CAAA;AACtD,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,QAAA,CAAS,QAAQ,EAAE,MAAA,EAAQ,EAAE,GAAA,EAAK,UAAA,IAAc,CAAA;AACrD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,UAAA,GAA6B;AAC3B,IAAA,MAAM,SAAgC,EAAC;AACvC,IAAA,KAAA,MAAW,EAAE,KAAA,EAAO,GAAA,EAAI,IAAK,IAAA,CAAK,eAAc,EAAG;AACjD,MAAA,IAAI,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,IAAA,KAAS,UAAA,EAAY;AAClE,QAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,KAAA,EAAO,KAAA,CAAM,SAAS,CAAA;AAAA,MAC3C,CAAA,MAAA,IAAW,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,SAAS,OAAA,EAAS;AACtE,QAAA,IAAI,KAAA,CAAM,SAAS,MAAA,CAAO,IAAA,CAAK,EAAE,GAAA,EAAK,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA;AAAA,MAC5D,CAAA,MAAA,IAAW,KAAA,YAAiB,iBAAA,IAAqB,KAAA,CAAM,QAAA,EAAU;AAC/D,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACV,GAAA;AAAA,UACA,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,eAAe,EAAE,GAAA,CAAI,CAAC,MAAA,KAAW,MAAA,CAAO,KAAK;AAAA,SACtE,CAAA;AAAA,MACH,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,KAAK,EAAE,GAAA,EAAK,KAAA,EAAO,KAAA,CAAM,OAAO,CAAA;AAAA,MACzC;AAAA,IACF;AACA,IAAA,OAAO,EAAE,OAAA,EAAS,CAAA,EAAG,MAAA,EAAO;AAAA,EAC9B;AAAA;AAAA,EAGA,gBAAA,GAAyB;AACvB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAgB,uBAAuB,CAAA;AACpD,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,YAAA,uBAAmB,OAAA,EAAsB;AAC9C,IAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAC1B,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,IAAI,eAAe,IAAA,EAAM;AACvB,MAAA,IAAA,CAAK,uBAAA,EAAwB;AAC7B,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,IAAA,CAAK,WAAA,CAAY,UAAU,CAAC,CAAA;AAC5D,IAAA,IAAI,CAAC,OAAO,EAAA,EAAI;AACd,MAAA,IAAA,CAAK,cAAA,CAAe,UAAA,EAAY,MAAA,EAAQ,aAAa,CAAA;AACrD,MAAA,IAAA,CAAK,uBAAA,EAAwB;AAC7B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,MAAA,CAAO,UAAU,IAAA,EAAM;AACzB,MAAA,IAAA,CAAK,uBAAA,EAAwB;AAC7B,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,YAAA,CAAa,MAAA,CAAO,KAAK,CAAA;AACzC,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,WAAA,CAAY,UAAU,CAAC,CAAA;AAG/D,MAAA,IAAA,CAAK,cAAA,CAAe,UAAA,EAAY,MAAA,EAAQ,iBAAiB,CAAA;AACzD,MAAA,IAAI,CAAC,OAAA,CAAQ,EAAA,OAAS,cAAA,CAAe,UAAA,EAAY,UAAU,aAAa,CAAA;AACxE,MAAA,IAAA,CAAK,uBAAA,EAAwB;AAC7B,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,gBAAgB,IAAI,CAAA;AAAA,EAC3B;AAAA;AAAA,EAGA,qBAAA,GAA8B;AAC5B,IAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAkB,IAAA,CAAK,cAAc,CAAA;AAC1D,IAAA,IAAA,CAAK,eAAe,KAAA,EAAM;AAC1B,IAAA,KAAA,MAAW,EAAE,KAAA,EAAM,IAAK,IAAA,CAAK,eAAc,EAAG;AAC5C,MAAA,IAAI,CAAC,KAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA,EAAG,QAAA,CAAS,IAAI,KAAK,CAAA;AAAA,IACvD;AACA,IAAA,IAAA,CAAK,gBAAgB,QAAQ,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,gBAAgB,QAAA,EAAkD;AAChE,IAAA,MAAM,OAAA,GAAU,KAAK,aAAA,EAAc;AACnC,IAAA,KAAA,MAAW,EAAE,KAAA,EAAM,IAAK,SAAS,IAAA,CAAK,YAAA,CAAa,IAAI,KAAK,CAAA;AAC5D,IAAA,MAAM,UAAU,IAAA,CAAK,QAAA;AACrB,IAAA,IAAI,YAAY,IAAA,EAAM;AACtB,IAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,OAAA,CAAQ,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU,CAAC,KAAA,CAAM,GAAA,EAAK,KAAK,CAAC,CAAC,CAAA;AAC7E,IAAA,MAAM,YAAA,uBAAmB,GAAA,EAAY;AACrC,IAAA,MAAM,eAAA,uBAAsB,GAAA,EAAY;AAExC,IAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,MAAA,IAAI,aAAa,IAAA,IAAQ,CAAC,SAAS,GAAA,CAAI,KAAA,CAAM,KAAK,CAAA,EAAG;AACrD,MAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AACvC,MAAA,IAAI,UAAU,MAAA,EAAW;AACzB,MAAA,MAAM,EAAE,OAAM,GAAI,KAAA;AAElB,MAAA,IAAI,MAAM,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,KAAA,CAAM,SAAS,OAAA,EAAS;AAC3E,QAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,EAAG;AACpC,QAAA,eAAA,CAAgB,GAAA,CAAI,MAAM,GAAG,CAAA;AAC7B,QAAA,MAAM,SAAS,OAAA,CACZ,MAAA;AAAA,UACC,CAAC,EAAE,GAAA,EAAK,KAAA,EAAM,KACZ,GAAA,KAAQ,KAAA,CAAM,GAAA,IAAO,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,IAAA,KAAS;AAAA,UAE5E,GAAA,CAAI,CAAC,EAAE,KAAA,OAAY,KAAyB,CAAA;AAC/C,QAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,MAAA,CAAO,IAAA,CAAK,CAAC,KAAA,KAAU,KAAA,CAAM,KAAA,KAAU,KAAK,CAAA,EAAG;AACjF,QAAA,KAAA,MAAW,KAAA,IAAS,MAAA,EAAQ,KAAA,CAAM,OAAA,GAAU,MAAM,KAAA,KAAU,KAAA;AAC5D,QAAA,YAAA,CAAa,GAAA,CAAI,MAAM,GAAG,CAAA;AAC1B,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,IAAA,CAAK,YAAY,KAAA,CAAM,KAAA,EAAO,KAAK,CAAA,EAAG,YAAA,CAAa,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAAA,IACtE;AAEA,IAAA,IAAI,YAAA,CAAa,OAAO,CAAA,EAAG;AAIzB,MAAA,IAAA,CAAK,UAAU,WAAA,EAAY;AAC3B,MAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,uBAAA,EAAyB,MAAM,CAAA;AACzD,MAAA,IAAA,CAAK,QAAA,CAAS,WAAW,EAAE,MAAA,EAAQ,EAAE,GAAA,EAAK,IAAA,CAAK,WAAA,EAAY,EAAG,CAAA;AAAA,IAChE;AAAA,EACF;AAAA;AAAA,EAGA,WAAA,CAAY,OAAqB,KAAA,EAA8B;AAC7D,IAAA,IAAI,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,IAAA,KAAS,UAAA,EAAY;AAClE,MAAA,IAAI,OAAO,KAAA,KAAU,SAAA,EAAW,OAAO,KAAA;AACvC,MAAA,KAAA,CAAM,OAAA,GAAU,KAAA;AAChB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,KAAA,YAAiB,iBAAA,IAAqB,KAAA,CAAM,QAAA,EAAU;AACxD,MAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,GAAG,OAAO,KAAA;AAClC,MAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,CAAE,GAAA,CAAI,CAAC,MAAA,KAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AACjF,MAAA,MAAM,QAAA,GAAW,MAAM,MAAA,CAAO,CAAC,cAAc,SAAA,CAAU,GAAA,CAAI,SAAS,CAAC,CAAA;AACrE,MAAA,IAAI,MAAM,MAAA,GAAS,CAAA,IAAK,QAAA,CAAS,MAAA,KAAW,GAAG,OAAO,KAAA;AACtD,MAAA,MAAM,cAAA,GAAiB,IAAI,GAAA,CAAI,QAAQ,CAAA;AACvC,MAAA,KAAA,MAAW,MAAA,IAAU,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,EAAG;AAC9C,QAAA,MAAA,CAAO,QAAA,GAAW,cAAA,CAAe,GAAA,CAAI,MAAA,CAAO,KAAK,CAAA;AAAA,MACnD;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,IAAA,IAAI,iBAAiB,iBAAA,EAAmB;AACtC,MAAA,MAAM,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,MAAA,KAAW,MAAA,CAAO,KAAA,KAAU,KAAK,CAAA;AAChF,MAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AAAA,IACtB;AACA,IAAA,KAAA,CAAM,KAAA,GAAQ,KAAA;AACd,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,gBAAA,GAAmC;AACjC,IAAA,OAAO,IAAA,CAAK,cAAA,GACR,IAAA,CAAK,YAAA,CAAa,OAAO,cAAc,CAAA,GACvC,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAA+B,yBAAyB,CAAC,CAAA,CAAE,MAAA;AAAA,MACjF,CAAC,KAAA,KAAU,IAAA,CAAK,UAAA,CAAW,KAAK;AAAA,KAClC;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAA,GAA6D;AAC3D,IAAA,MAAM,UAAuD,EAAC;AAC9D,IAAA,MAAM,WAAyD,EAAC;AAChE,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,gBAAA,EAAiB,EAAG;AAC3C,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA;AAC9B,MAAA,IAAI,IAAA,KAAS,QAAQ,CAAC,IAAA,CAAK,aAAa,KAAA,EAAO,IAAA,CAAK,SAAS,CAAA,EAAG;AAChE,MAAA,QAAA,CAAS,IAAA,CAAK,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAC/B;AAEA,IAAA,MAAM,cAAA,uBAAqB,GAAA,EAAoB;AAC/C,IAAA,MAAM,UAAA,uBAAiB,GAAA,EAAiD;AACxE,IAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAyB;AACzC,MAAA,MAAM,UAAA,GAAa,cAAA,CAAe,GAAA,CAAI,IAAI,CAAA,IAAK,CAAA;AAC/C,MAAA,cAAA,CAAe,GAAA,CAAI,IAAA,EAAM,UAAA,GAAa,CAAC,CAAA;AACvC,MAAA,OAAO,UAAA,KAAe,IAAI,IAAA,GAAO,CAAA,EAAG,IAAI,CAAA,EAAG,cAAc,GAAG,UAAU,CAAA,CAAA;AAAA,IACxE,CAAA;AACA,IAAA,KAAA,MAAW,EAAE,KAAA,EAAO,IAAA,EAAK,IAAK,QAAA,EAAU;AACtC,MAAA,IAAI,KAAA,YAAiB,gBAAA,IAAoB,KAAA,CAAM,IAAA,KAAS,OAAA,EAAS;AAC/D,QAAA,MAAM,SAAS,UAAA,CAAW,GAAA,CAAI,IAAI,CAAA,wBAAS,GAAA,EAAoC;AAC/E,QAAA,UAAA,CAAW,GAAA,CAAI,MAAM,MAAM,CAAA;AAC3B,QAAA,IAAI,GAAA,GAAM,MAAA,CAAO,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA;AAC/B,QAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,UAAA,GAAA,GAAM,SAAS,IAAI,CAAA;AACnB,UAAA,MAAA,CAAO,GAAA,CAAI,KAAA,CAAM,IAAA,EAAM,GAAG,CAAA;AAAA,QAC5B;AACA,QAAA,OAAA,CAAQ,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAK,CAAA;AAC3B,QAAA;AAAA,MACF;AACA,MAAA,OAAA,CAAQ,KAAK,EAAE,KAAA,EAAO,KAAK,QAAA,CAAS,IAAI,GAAG,CAAA;AAAA,IAC7C;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA;AAAA,EAGA,YAAA,CAAa,OAAqB,QAAA,EAAsC;AACtE,IAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,IAAK,gBAAgB,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,EAAG,OAAO,KAAA;AAC/E,IAAA,IAAI,QAAA,CAAS,QAAA,CAAS,KAAA,CAAM,IAAI,GAAG,OAAO,KAAA;AAC1C,IAAA,IAAI,KAAA,CAAM,KAAK,MAAA,GAAS,CAAA,IAAK,SAAS,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG,OAAO,KAAA;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,OAAO,KAAA,EAAoC;AACzC,IAAA,OAAO,KAAA,CAAM,IAAA,IAAQ,KAAA,CAAM,EAAA,IAAM,IAAA;AAAA,EACnC;AAAA;AAAA,EAGA,WAAW,KAAA,EAA8B;AACvC,IAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,sCAAsC,CAAA,KAAM,IAAA,CAAK,OAAA;AAAA,EACxE;AAAA;AAAA,EAGA,uBAAA,GAAgC;AAC9B,IAAA,KAAA,MAAW,EAAE,OAAM,IAAK,IAAA,CAAK,eAAc,EAAG,IAAA,CAAK,YAAA,CAAa,GAAA,CAAI,KAAK,CAAA;AAAA,EAC3E;AAAA;AAAA,EAGA,iBAAA,GAA0B;AACxB,IAAA,MAAM,IAAA,GAAO,KAAK,kBAAA,EAAmB;AACrC,IAAA,IAAI,IAAA,KAAS,KAAK,WAAA,EAAa;AAC/B,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AACnB,IAAA,IAAA,CAAK,gBAAA,EAAiB;AAAA,EACxB;AAAA;AAAA,EAGA,YAAA,GAAqB;AACnB,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,eAAA,CAAgB,IAAA,CAAK,YAAY,CAAA;AACnD,IAAA,IAAI,IAAA,KAAS,KAAK,kBAAA,EAAoB;AACtC,IAAA,IAAA,CAAK,cAAA,EAAe;AACpB,IAAA,IAAI,SAAS,IAAA,EAAM;AACnB,IAAA,IAAA,CAAK,OAAA,CAAQ,gBAAA,CAAiB,IAAA,EAAM,IAAA,CAAK,aAAa,CAAA;AACtD,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAAA,EAC5B;AAAA;AAAA,EAGA,cAAA,GAAuB;AACrB,IAAA,IAAI,IAAA,CAAK,uBAAuB,IAAA,EAAM;AACtC,IAAA,IAAA,CAAK,OAAA,CAAQ,mBAAA,CAAoB,IAAA,CAAK,kBAAA,EAAoB,KAAK,aAAa,CAAA;AAC5E,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAAA,EAC5B;AAAA;AAAA,EAGA,gBAAgB,KAAA,EAA8B;AAC5C,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,EAAK;AACxB,IAAA,OAAO,IAAA,CAAK,SAAS,CAAA,IAAK,CAAC,KAAK,IAAA,CAAK,IAAI,IAAI,IAAA,GAAO,IAAA;AAAA,EACtD;AAAA;AAAA,EAGA,aAAa,OAAA,EAAiC;AAC5C,IAAA,IAAI,aAAA,GAAgB,KAAA;AACpB,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,MAAA,CAAO,SAAS,YAAA,EAAc;AAChC,QAAA,IAAI,OAAO,MAAA,KAAW,IAAA,CAAK,OAAA,IAAW,MAAA,CAAO,kBAAkB,IAAA,EAAM;AACnE,UAAA,aAAA,GAAgB,IAAA;AAAA,QAClB;AACA,QAAA,IAAA,CAAK,0BAAA,CAA2B,MAAA,CAAO,MAAA,EAAQ,MAAA,CAAO,aAAa,CAAA;AACnE,QAAA;AAAA,MACF;AACA,MAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,yBAAyB,IAAI,CAAA;AAAA,IAC1E;AACA,IAAA,IAAI,iBAAiB,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,OAAQ,iBAAA,EAAkB;AACxE,IAAA,IAAA,CAAK,gBAAgB,QAAA,EAAS;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,0BAAA,CAA2B,QAAc,aAAA,EAAoC;AAC3E,IAAA,IAAI,aAAA,KAAkB,mBAAA,IAAuB,MAAA,KAAW,IAAA,CAAK,OAAA,EAAS;AACpE,MAAA,IAAA,CAAK,yBAAyB,MAAM,CAAA;AACpC,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,yBAAyB,MAAM,CAAA;AAAA,EACtC;AAAA;AAAA,EAGA,yBAAyB,IAAA,EAAkB;AACzC,IAAA,IAAI,cAAA,CAAe,IAAI,CAAA,EAAG;AACxB,MAAA,IAAA,CAAK,cAAA,CAAe,IAAI,IAAI,CAAA;AAC5B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,gBAAgB,iBAAA,EAAmB;AACrC,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA;AACpC,MAAA,IAAI,MAAA,EAAQ,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,MAAM,CAAA;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA,EAGA,yBAAyB,IAAA,EAAkB;AACzC,IAAA,IAAA,CAAK,yBAAyB,IAAI,CAAA;AAClC,IAAA,IAAI,EAAE,gBAAgB,OAAA,CAAA,EAAU;AAChC,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,gBAAA,CAA+B,yBAAyB,CAAA,EAAG;AAClF,MAAA,IAAA,CAAK,cAAA,CAAe,IAAI,KAAK,CAAA;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA,EAGA,kBAAA,GAAoC;AAClC,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,OAAA,CAAQ,EAAA;AAC1C,IAAA,OAAO,GAAA,CAAI,MAAA,GAAS,CAAA,GAAI,GAAA,GAAM,IAAA;AAAA,EAChC;AAAA;AAAA,EAGA,YAAY,UAAA,EAA4B;AACtC,IAAA,OAAO,CAAA,EAAG,cAAc,CAAA,EAAG,UAAU,CAAA,CAAA;AAAA,EACvC;AAAA;AAAA,EAGA,IAAI,cAAA,GAAyB;AAC3B,IAAA,MAAM,QAAQ,IAAA,CAAK,aAAA;AACnB,IAAA,OAAO,OAAO,QAAA,CAAS,KAAK,CAAA,IAAK,KAAA,IAAS,IAAI,KAAA,GAAQ,gBAAA;AAAA,EACxD;AAAA;AAAA,EAGA,cAAA,CAAe,GAAA,EAAa,SAAA,EAA6B,MAAA,EAAkC;AACzF,IAAA,IAAA,CAAK,QAAA,CAAS,SAAS,EAAE,MAAA,EAAQ,EAAE,GAAA,EAAK,SAAA,EAAW,MAAA,EAAO,EAAG,CAAA;AAAA,EAC/D;AACF","file":"persist_controller.js","sourcesContent":["/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","/** A successful guarded storage operation. */\nexport interface StorageSuccess<T> {\n readonly ok: true;\n readonly value: T;\n}\n\n/** A guarded storage operation rejected by the browser. */\nexport interface StorageFailure {\n readonly ok: false;\n readonly error: unknown;\n}\n\n/** Result of accessing browser storage without allowing an exception to escape. */\nexport type StorageResult<T> = StorageSuccess<T> | StorageFailure;\n\n/**\n * Guarded `localStorage` access shared by controllers that persist client state.\n *\n * `window.localStorage` can throw while resolving the storage object or while\n * reading, writing, or removing a key. The result keeps an unavailable store\n * distinct from an available store whose key is unset, so callers can expose an\n * accurate success or failure state without duplicating exception guards.\n */\n\n/**\n * Reads `key` from `localStorage`.\n *\n * @param key - The storage key to read.\n * @returns A successful result containing the stored string or `null`, or a\n * failure result when storage is unavailable.\n */\nexport function readLocalStorage(key: string): StorageResult<string | null> {\n try {\n return { ok: true, value: window.localStorage.getItem(key) };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n\n/**\n * Writes `value` under `key` in `localStorage`.\n *\n * @param key - The storage key to write.\n * @param value - The string to store.\n * @returns Success only after `setItem` completes; otherwise the browser error.\n */\nexport function writeLocalStorage(key: string, value: string): StorageResult<void> {\n try {\n window.localStorage.setItem(key, value);\n return { ok: true, value: undefined };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n\n/**\n * Removes `key` from `localStorage`.\n *\n * @param key - The storage key to remove.\n * @returns Success only after `removeItem` completes; otherwise the browser error.\n */\nexport function removeLocalStorage(key: string): StorageResult<void> {\n try {\n window.localStorage.removeItem(key);\n return { ok: true, value: undefined };\n } catch (error: unknown) {\n return { ok: false, error };\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","/**\n * JSON string-list parsing for the controllers that take a list as a Value.\n *\n * Stimulus offers an `Array` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the `<name>ValueChanged` callback — and\n * rethrows on malformed text. The throw propagates out of the observer that runs\n * during connection, so a single unparseable attribute stops the controller from\n * connecting at all: no lifecycle callback runs and the element is left inert\n * rather than degraded. A consumer cannot guard against it either, because the\n * callback that would hold the `try` never runs.\n *\n * Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares. The attribute text is\n * identical either way (`'[\"a\", \"b\"]'`), so markup does not change with the\n * declaration.\n */\n\n/**\n * Parses `raw` as a JSON array of strings.\n *\n * Returns `fallback` when the text is absent, unparseable, or not an array, so a\n * malformed declaration behaves like an omitted one. A parsed array keeps only\n * its string entries: every caller indexes or compares strings, and a stray\n * number would otherwise sit in the list without ever matching.\n *\n * An explicit `\"[]\"` is honoured as an empty list rather than falling back — it\n * is the only way to declare \"none\" against a non-empty default.\n *\n * @param raw - the attribute text, e.g. `'[\"password\"]'`.\n * @param fallback - the list to use when `raw` declares nothing usable.\n */\nexport function parseStringList(raw: string, fallback: readonly string[] = []): string[] {\n // The common case is an undeclared attribute, and `JSON.parse(\"\")` answers it\n // by throwing. Reaching the same fallback without building an exception keeps\n // the callers that ask per element off that path.\n const text = raw.trim();\n if (text.length === 0) return [...fallback];\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [...fallback];\n }\n\n if (!Array.isArray(parsed)) return [...fallback];\n return parsed.filter((entry): entry is string => typeof entry === \"string\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\nimport { readLocalStorage, removeLocalStorage, writeLocalStorage } from \"../utils/safe_storage\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { parseStringList } from \"../utils/string_list\";\n\n/** Field controls this controller can persist. */\ntype PersistField = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;\n\n/** Values that can be restored without coercing an untrusted storage payload. */\ntype PersistValue = string | boolean | string[];\n\n/** One stable field entry in the versioned storage payload. */\ninterface PersistPayloadField {\n readonly key: string;\n readonly value: PersistValue;\n}\n\n/** Versioned draft payload stored under one controller namespace. */\ninterface PersistPayload {\n readonly version: 1;\n readonly fields: PersistPayloadField[];\n}\n\ntype PersistOperation = \"read\" | \"write\" | \"remove\";\ntype PersistErrorReason = \"unavailable\" | \"invalid-payload\";\n\n/** One scheduled write, including the namespace it was created for. */\ninterface PendingSave {\n readonly id: number;\n readonly logicalKey: string;\n}\n\n/** Input types that carry no meaningful, restorable value. */\nconst NON_VALUE_TYPES = new Set([\"file\", \"submit\", \"reset\", \"button\", \"image\"]);\n/** Passwords are never persisted, even when a custom exclusion list is supplied. */\nconst SENSITIVE_TYPES = new Set([\"password\"]);\n/** Field types and names skipped by the default configuration. */\nconst DEFAULT_EXCLUDE = [\"authenticity_token\", \"_method\", \"utf8\"];\n/** localStorage key prefix so drafts never clobber unrelated app storage. */\nconst STORAGE_PREFIX = \"stimeo--persist:\";\n/** Separator for disambiguating repeated same-name fields. */\nconst OCCURRENCE_SEP = \"\\u0000\";\n/** The one observed attribute that can move fields between nested Persist hosts. */\nconst OWNERSHIP_ATTRIBUTE = \"data-controller\";\nconst DEFAULT_DEBOUNCE = 400;\n\n/** Whether a parsed JSON value is a plain object-like record. */\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\n/** Whether a parsed JSON value belongs to the persisted field-value union. */\nconst isPersistValue = (value: unknown): value is PersistValue =>\n typeof value === \"string\" ||\n typeof value === \"boolean\" ||\n (Array.isArray(value) && value.every((item) => typeof item === \"string\"));\n\n/** Parses and validates the complete versioned payload without type assertions. */\nconst parsePayload = (raw: string): PersistPayload | null => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isRecord(parsed) || parsed.version !== 1 || !Array.isArray(parsed.fields)) return null;\n\n const fields: PersistPayloadField[] = [];\n const keys = new Set<string>();\n for (const candidate of parsed.fields) {\n if (!isRecord(candidate)) return null;\n const { key, value } = candidate;\n if (typeof key !== \"string\" || key.length === 0 || keys.has(key)) return null;\n if (!isPersistValue(value)) return null;\n keys.add(key);\n fields.push({ key, value });\n }\n return { version: 1, fields };\n};\n\n/** Whether a node is one of the native controls supported by Persist. */\nconst isPersistField = (node: unknown): node is PersistField =>\n node instanceof HTMLInputElement ||\n node instanceof HTMLTextAreaElement ||\n node instanceof HTMLSelectElement;\n\n/**\n * Headless draft-autosave behavior: persists form controls to `localStorage`\n * and restores them across Turbo navigation and reloads.\n *\n * Markup contract (identifier: `stimeo--persist`):\n * <form data-controller=\"stimeo--persist\"\n * data-stimeo--persist-key-value=\"contact-draft\">\n * <input name=\"name\">\n * <textarea name=\"message\"></textarea>\n * </form>\n *\n * The optional `field` targets restrict persistence to explicitly targeted\n * controls. The public `clear` action removes the active draft. `key`,\n * `debounce`, `exclude`, and `clearOn` Values can change while connected;\n * `clearOn` is an event name whose occurrence invokes `clear`.\n *\n * `restore`, `save`, and `clear` dispatch `{ key: string }`. `error` dispatches\n * `{ key: string, operation: \"read\" | \"write\" | \"remove\", reason:\n * \"unavailable\" | \"invalid-payload\" }`. Restore writes are intentionally\n * silent native-control mutations; consumers synchronize from the bubbling\n * `restore` event without treating a restored draft as user input.\n *\n * @remarks\n * Behavior only — restoration never moves focus. Passwords are never stored;\n * the default exclusion also omits Rails request metadata while preserving\n * application-owned hidden fields. Distinct logical keys isolate instances;\n * instances using the same key intentionally share one last-writer-wins draft.\n * Every listener, timer, observer, and queued reconciliation is released on\n * `disconnect()`, after any pending edit is flushed through the normal save\n * path. A reconnect removes stale restored state before reading storage.\n */\nexport class PersistController extends Controller<HTMLElement> {\n static override targets = [\"field\"];\n static override values = {\n key: { type: String, default: \"\" },\n debounce: { type: Number, default: DEFAULT_DEBOUNCE },\n // A malformed JSON list must fall back without aborting Stimulus connection.\n exclude: { type: String, default: \"\" },\n clearOn: { type: String, default: \"\" },\n };\n static actions = [\"clear\"] as const;\n static events = [\"restore\", \"save\", \"clear\", \"error\"] as const;\n\n declare readonly fieldTargets: Element[];\n declare readonly hasFieldTarget: boolean;\n\n declare keyValue: string;\n declare debounceValue: number;\n declare excludeValue: string;\n declare clearOnValue: string;\n\n readonly #timeouts = new SafeTimeout();\n readonly #restoreDynamic = new MicrotaskCoalescer(() => this.#restoreDynamicFields());\n readonly #observer = new MutationObserver((records) => this.#onMutations(records));\n #pendingSave: PendingSave | null = null;\n #connected = false;\n #logicalKey: string | null = null;\n #payload: PersistPayload | null = null;\n #registeredClearOn: string | null = null;\n #excluded: readonly string[] = DEFAULT_EXCLUDE;\n #knownFields = new WeakSet<PersistField>();\n #forcedRestore = new Set<PersistField>();\n\n readonly #onInput = (event: Event): void => {\n const field = event.target;\n if (!isPersistField(field) || !this.#ownsField(field)) return;\n if (this.hasFieldTarget && !this.fieldTargets.includes(field)) return;\n if (this.#keyOf(field) === null || !this.#persistable(field, this.#excluded)) return;\n this.#scheduleSave();\n };\n\n readonly #onClearEvent = (): void => {\n this.clear();\n };\n\n override connect(): void {\n if (this.#connected) return;\n this.#connected = true;\n this.#logicalKey = this.#resolveLogicalKey();\n this.#excluded = parseStringList(this.excludeValue, DEFAULT_EXCLUDE);\n this.#knownFields = new WeakSet<PersistField>();\n this.#forcedRestore.clear();\n this.element.removeAttribute(\"data-persist-restored\");\n\n this.element.addEventListener(\"input\", this.#onInput);\n this.element.addEventListener(\"change\", this.#onInput);\n this.#syncClearOn();\n this.#restoreDynamic.activate();\n this.#observer.observe(this.element, {\n attributes: true,\n attributeFilter: [\n \"checked\",\n \"data-controller\",\n \"data-stimeo--persist-target\",\n \"id\",\n \"multiple\",\n \"name\",\n \"selected\",\n \"type\",\n \"value\",\n ],\n childList: true,\n subtree: true,\n });\n this.#loadActiveDraft();\n }\n\n override disconnect(): void {\n this.#flushPendingSave();\n this.#connected = false;\n this.element.removeEventListener(\"input\", this.#onInput);\n this.element.removeEventListener(\"change\", this.#onInput);\n this.#unbindClearOn();\n this.#observer.disconnect();\n this.#restoreDynamic.cancel();\n this.#forcedRestore.clear();\n this.#timeouts.clearAll();\n this.element.removeAttribute(\"data-persist-restored\");\n this.#payload = null;\n }\n\n /** Drops the active draft after storage confirms the removal. */\n clear(): void {\n const logicalKey = this.#logicalKey ?? this.#resolveLogicalKey();\n if (logicalKey === null) return;\n this.#cancelPendingSave();\n const result = removeLocalStorage(this.#storageKey(logicalKey));\n if (!result.ok) {\n this.#dispatchError(logicalKey, \"remove\", \"unavailable\");\n return;\n }\n this.#payload = null;\n this.element.removeAttribute(\"data-persist-restored\");\n this.dispatch(\"clear\", { detail: { key: logicalKey } });\n }\n\n /** Rebinds the external clear event when its Value changes. */\n clearOnValueChanged(): void {\n if (this.#connected) this.#syncClearOn();\n }\n\n /** Switches storage namespaces without writing old edits under the new key. */\n keyValueChanged(): void {\n if (this.#connected) this.#switchLogicalKey();\n }\n\n /** Reschedules a pending write against the current debounce delay. */\n debounceValueChanged(): void {\n if (this.#connected && this.#pendingSave !== null) this.#scheduleSave();\n }\n\n /** Restores every field the new exclusion list makes eligible. */\n excludeValueChanged(): void {\n const previous = this.#excluded;\n this.#excluded = parseStringList(this.excludeValue, DEFAULT_EXCLUDE);\n if (!this.#connected) return;\n // A field restored once stays in #knownFields, so releasing it from the exclusion\n // list has to name it explicitly to reach the next batch. Fields the list never\n // covered keep their live value, which is what the next batch already guarantees.\n for (const field of this.#candidateFields()) {\n if (this.#persistable(field, this.#excluded) && !this.#persistable(field, previous)) {\n this.#forcedRestore.add(field);\n }\n }\n this.#restoreDynamic.schedule();\n }\n\n /** Schedules a debounced save. */\n #scheduleSave(): void {\n const logicalKey = this.#logicalKey;\n if (logicalKey === null) return;\n this.#cancelPendingSave();\n const id = this.#timeouts.set(() => {\n this.#pendingSave = null;\n this.#save(logicalKey);\n }, this.#debounceDelay);\n this.#pendingSave = { id, logicalKey };\n }\n\n /** Cancels the currently pending save, if any. */\n #cancelPendingSave(): void {\n const pending = this.#pendingSave;\n if (pending === null) return;\n this.#timeouts.clear(pending.id);\n this.#pendingSave = null;\n }\n\n /** Flushes a pending edit through the same success/error event path as a timer. */\n #flushPendingSave(): void {\n const pending = this.#pendingSave;\n if (pending === null) return;\n this.#cancelPendingSave();\n this.#save(pending.logicalKey);\n }\n\n /** Writes the active payload and emits `save` only after storage succeeds. */\n #save(logicalKey: string): boolean {\n const payload = this.#serialize();\n const result = writeLocalStorage(this.#storageKey(logicalKey), JSON.stringify(payload));\n if (!result.ok) {\n this.#dispatchError(logicalKey, \"write\", \"unavailable\");\n return false;\n }\n this.#payload = payload;\n this.dispatch(\"save\", { detail: { key: logicalKey } });\n return true;\n }\n\n /** Serializes every persistable field into a versioned, typed payload. */\n #serialize(): PersistPayload {\n const fields: PersistPayloadField[] = [];\n for (const { field, key } of this.#fieldEntries()) {\n if (field instanceof HTMLInputElement && field.type === \"checkbox\") {\n fields.push({ key, value: field.checked });\n } else if (field instanceof HTMLInputElement && field.type === \"radio\") {\n if (field.checked) fields.push({ key, value: field.value });\n } else if (field instanceof HTMLSelectElement && field.multiple) {\n fields.push({\n key,\n value: Array.from(field.selectedOptions).map((option) => option.value),\n });\n } else {\n fields.push({ key, value: field.value });\n }\n }\n return { version: 1, fields };\n }\n\n /** Reads, validates, and restores the active namespace. */\n #loadActiveDraft(): void {\n this.element.removeAttribute(\"data-persist-restored\");\n this.#payload = null;\n this.#knownFields = new WeakSet<PersistField>();\n this.#forcedRestore.clear();\n const logicalKey = this.#logicalKey;\n if (logicalKey === null) {\n this.#markCurrentFieldsKnown();\n return;\n }\n\n const result = readLocalStorage(this.#storageKey(logicalKey));\n if (!result.ok) {\n this.#dispatchError(logicalKey, \"read\", \"unavailable\");\n this.#markCurrentFieldsKnown();\n return;\n }\n if (result.value === null) {\n this.#markCurrentFieldsKnown();\n return;\n }\n\n const payload = parsePayload(result.value);\n if (payload === null) {\n const removal = removeLocalStorage(this.#storageKey(logicalKey));\n // Cleanup precedes notification so an error consumer can safely replace\n // the invalid value without that repair being deleted on callback return.\n this.#dispatchError(logicalKey, \"read\", \"invalid-payload\");\n if (!removal.ok) this.#dispatchError(logicalKey, \"remove\", \"unavailable\");\n this.#markCurrentFieldsKnown();\n return;\n }\n this.#payload = payload;\n this.#restoreEntries(null);\n }\n\n /** Restores fields inserted or made eligible after the initial connection. */\n #restoreDynamicFields(): void {\n const selected = new Set<PersistField>(this.#forcedRestore);\n this.#forcedRestore.clear();\n for (const { field } of this.#fieldEntries()) {\n if (!this.#knownFields.has(field)) selected.add(field);\n }\n this.#restoreEntries(selected);\n }\n\n /** Applies the active payload to all fields, or only the supplied candidates. */\n #restoreEntries(selected: ReadonlySet<PersistField> | null): void {\n const entries = this.#fieldEntries();\n for (const { field } of entries) this.#knownFields.add(field);\n const payload = this.#payload;\n if (payload === null) return;\n const savedFields = new Map(payload.fields.map((field) => [field.key, field]));\n const restoredKeys = new Set<string>();\n const processedRadios = new Set<string>();\n\n for (const entry of entries) {\n if (selected !== null && !selected.has(entry.field)) continue;\n const saved = savedFields.get(entry.key);\n if (saved === undefined) continue;\n const { value } = saved;\n\n if (entry.field instanceof HTMLInputElement && entry.field.type === \"radio\") {\n if (processedRadios.has(entry.key)) continue;\n processedRadios.add(entry.key);\n const radios = entries\n .filter(\n ({ key, field }) =>\n key === entry.key && field instanceof HTMLInputElement && field.type === \"radio\",\n )\n .map(({ field }) => field as HTMLInputElement);\n if (typeof value !== \"string\" || !radios.some((radio) => radio.value === value)) continue;\n for (const radio of radios) radio.checked = radio.value === value;\n restoredKeys.add(entry.key);\n continue;\n }\n\n if (this.#applyValue(entry.field, value)) restoredKeys.add(entry.key);\n }\n\n if (restoredKeys.size > 0) {\n // Property writes can reflect to observed attributes in DOM implementations.\n // Drop only records produced by this restore before notifying consumers, so\n // authored mutations made from a restore listener remain observable.\n this.#observer.takeRecords();\n this.element.setAttribute(\"data-persist-restored\", \"true\");\n this.dispatch(\"restore\", { detail: { key: this.#logicalKey } });\n }\n }\n\n /** Applies a type-compatible stored value without coercion. */\n #applyValue(field: PersistField, value: PersistValue): boolean {\n if (field instanceof HTMLInputElement && field.type === \"checkbox\") {\n if (typeof value !== \"boolean\") return false;\n field.checked = value;\n return true;\n }\n if (field instanceof HTMLSelectElement && field.multiple) {\n if (!Array.isArray(value)) return false;\n const available = new Set(Array.from(field.options).map((option) => option.value));\n const selected = value.filter((candidate) => available.has(candidate));\n if (value.length > 0 && selected.length === 0) return false;\n const selectedValues = new Set(selected);\n for (const option of Array.from(field.options)) {\n option.selected = selectedValues.has(option.value);\n }\n return true;\n }\n if (typeof value !== \"string\") return false;\n if (field instanceof HTMLSelectElement) {\n const exists = Array.from(field.options).some((option) => option.value === value);\n if (!exists) return false;\n }\n field.value = value;\n return true;\n }\n\n /** Every control this instance owns, before the exclusion list narrows them. */\n #candidateFields(): PersistField[] {\n return this.hasFieldTarget\n ? this.fieldTargets.filter(isPersistField)\n : Array.from(this.element.querySelectorAll<PersistField>(\"input, textarea, select\")).filter(\n (field) => this.#ownsField(field),\n );\n }\n\n /**\n * Pairs persistable fields with DOM-order occurrence keys. Every HTML radio\n * group (same name and form owner) occupies one shared occurrence slot.\n */\n #fieldEntries(): Array<{ field: PersistField; key: string }> {\n const entries: Array<{ field: PersistField; key: string }> = [];\n const eligible: Array<{ field: PersistField; name: string }> = [];\n for (const field of this.#candidateFields()) {\n const name = this.#keyOf(field);\n if (name === null || !this.#persistable(field, this.#excluded)) continue;\n eligible.push({ field, name });\n }\n\n const nextOccurrence = new Map<string, number>();\n const radioSlots = new Map<string, Map<HTMLFormElement | null, string>>();\n const allocate = (name: string): string => {\n const occurrence = nextOccurrence.get(name) ?? 0;\n nextOccurrence.set(name, occurrence + 1);\n return occurrence === 0 ? name : `${name}${OCCURRENCE_SEP}${occurrence}`;\n };\n for (const { field, name } of eligible) {\n if (field instanceof HTMLInputElement && field.type === \"radio\") {\n const groups = radioSlots.get(name) ?? new Map<HTMLFormElement | null, string>();\n radioSlots.set(name, groups);\n let key = groups.get(field.form);\n if (key === undefined) {\n key = allocate(name);\n groups.set(field.form, key);\n }\n entries.push({ field, key });\n continue;\n }\n entries.push({ field, key: allocate(name) });\n }\n return entries;\n }\n\n /** Whether a field carries a restorable, non-excluded value. */\n #persistable(field: PersistField, excluded: readonly string[]): boolean {\n if (NON_VALUE_TYPES.has(field.type) || SENSITIVE_TYPES.has(field.type)) return false;\n if (excluded.includes(field.type)) return false;\n if (field.name.length > 0 && excluded.includes(field.name)) return false;\n return true;\n }\n\n /** A stable storage sub-key for a field (its name, else id). */\n #keyOf(field: PersistField): string | null {\n return field.name || field.id || null;\n }\n\n /** Whether this instance, rather than a nested Persist host, owns a field. */\n #ownsField(field: PersistField): boolean {\n return field.closest('[data-controller~=\"stimeo--persist\"]') === this.element;\n }\n\n /** Marks all currently eligible fields without applying a payload. */\n #markCurrentFieldsKnown(): void {\n for (const { field } of this.#fieldEntries()) this.#knownFields.add(field);\n }\n\n /** Switches namespace after flushing the old namespace's pending edit. */\n #switchLogicalKey(): void {\n const next = this.#resolveLogicalKey();\n if (next === this.#logicalKey) return;\n this.#flushPendingSave();\n this.#logicalKey = next;\n this.#loadActiveDraft();\n }\n\n /** Synchronizes the exact event name owned by the `clearOn` Value. */\n #syncClearOn(): void {\n const next = this.#validEventName(this.clearOnValue);\n if (next === this.#registeredClearOn) return;\n this.#unbindClearOn();\n if (next === null) return;\n this.element.addEventListener(next, this.#onClearEvent);\n this.#registeredClearOn = next;\n }\n\n /** Removes the event listener using the name that was actually registered. */\n #unbindClearOn(): void {\n if (this.#registeredClearOn === null) return;\n this.element.removeEventListener(this.#registeredClearOn, this.#onClearEvent);\n this.#registeredClearOn = null;\n }\n\n /** Returns a non-whitespace event type, or null when the hook is disabled. */\n #validEventName(value: string): string | null {\n const name = value.trim();\n return name.length > 0 && !/\\s/.test(name) ? name : null;\n }\n\n /** Collects dynamic controls and select-option changes into one restore pass. */\n #onMutations(records: MutationRecord[]): void {\n let rootIdChanged = false;\n for (const record of records) {\n if (record.type === \"attributes\") {\n if (record.target === this.element && record.attributeName === \"id\") {\n rootIdChanged = true;\n }\n this.#collectAttributeCandidate(record.target, record.attributeName);\n continue;\n }\n for (const node of record.addedNodes) this.#collectRestoreCandidate(node);\n }\n if (rootIdChanged && this.keyValue.length === 0) this.#switchLogicalKey();\n this.#restoreDynamic.schedule();\n }\n\n /**\n * Adds the controls one mutated attribute can affect. Only an ownership change on a\n * descendant moves fields between Persist hosts, so that is the single case worth a\n * subtree sweep; every other observed attribute describes one control, and sweeping\n * from its container would re-apply the stored draft over edits still being debounced.\n */\n #collectAttributeCandidate(target: Node, attributeName: string | null): void {\n if (attributeName === OWNERSHIP_ATTRIBUTE && target !== this.element) {\n this.#collectRestoreCandidate(target);\n return;\n }\n this.#collectControlCandidate(target);\n }\n\n /** Adds a native control, or the select that owns a mutated option. */\n #collectControlCandidate(node: Node): void {\n if (isPersistField(node)) {\n this.#forcedRestore.add(node);\n return;\n }\n if (node instanceof HTMLOptionElement) {\n const select = node.closest(\"select\");\n if (select) this.#forcedRestore.add(select);\n }\n }\n\n /** Adds every control inside an inserted or newly owned subtree. */\n #collectRestoreCandidate(node: Node): void {\n this.#collectControlCandidate(node);\n if (!(node instanceof Element)) return;\n for (const field of node.querySelectorAll<PersistField>(\"input, textarea, select\")) {\n this.#forcedRestore.add(field);\n }\n }\n\n /** Resolves the logical key from the Value, falling back to the host id. */\n #resolveLogicalKey(): string | null {\n const key = this.keyValue || this.element.id;\n return key.length > 0 ? key : null;\n }\n\n /** Namespaces one logical key within localStorage. */\n #storageKey(logicalKey: string): string {\n return `${STORAGE_PREFIX}${logicalKey}`;\n }\n\n /** Normalizes invalid debounce Values to the documented default. */\n get #debounceDelay(): number {\n const value = this.debounceValue;\n return Number.isFinite(value) && value >= 0 ? value : DEFAULT_DEBOUNCE;\n }\n\n /** Dispatches one observable storage failure without exposing browser errors. */\n #dispatchError(key: string, operation: PersistOperation, reason: PersistErrorReason): void {\n this.dispatch(\"error\", { detail: { key, operation, reason } });\n }\n}\n"]}
@@ -25,6 +25,9 @@ import { Controller } from '@hotwired/stimulus';
25
25
  * `keyboardStep` px each, Space/Enter drops (`end`) and Escape cancels — so every
26
26
  * consumer gets a keyboard path for free.
27
27
  *
28
+ * `start` dispatches `{ x, y, pointerType }`; `move` dispatches `{ dx, dy, x, y, pointerType }`;
29
+ * `end` dispatches `{ dx, dy, pointerType }`; `cancel` dispatches `{ pointerType }`.
30
+ *
28
31
  * @remarks
29
32
  * Behavior only: consumers own all visuals (ghost, placeholder, transforms) by
30
33
  * reacting to the events and the `data-dragging` / `data-grabbed` hooks. The one
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/arrow_step.ts","../../src/utils/detach_gate.ts","../../src/controllers/pointer_drag_controller.ts"],"names":[],"mappings":"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","file":"pointer_drag_controller.js","sourcesContent":["import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Shared \"in-page move vs real detach\" discriminator for `disconnect()`.\n *\n * A mid-session `disconnect()` whose element is still in the document is\n * ambiguous. It can be:\n *\n * - an **in-page move** — a consumer (sortable, a teleport) re-inserted the\n * element; Stimulus reconnects the SAME instance in the same mutation batch\n * and the controller's runtime state must survive;\n * - a **detach that keeps the element** — `data-controller` no longer lists the\n * identifier (a Turbo 8 morph), or the element moved outside the observed\n * root (a scoped `Application.start(root)`, a shadow root); no reconnect will\n * ever come and the controller must tear down or it leaks its sessions,\n * document listeners, and moved nodes.\n *\n * Neither available signal decides this alone. A synchronous `data-controller`\n * token check catches the morph case but misses an observed-root exit (the\n * token stays, the reconnect never comes). Deferring teardown one microtask and\n * letting `connect()` cancel it catches the root exit, but leans on Stimulus\n * reconnecting moved elements within the same mutation batch — internal\n * behavior, not a public contract. {@link DetachGate} composes both: the token\n * check is the synchronous fast path (no probe, no reliance on batching), the\n * microtask probe is the fallback for the genuinely ambiguous remainder.\n *\n * Policy stays with the consumer (the `SafeTimeout` / `FocusTrap` lineage —\n * this util owns the discrimination only):\n *\n * - **Probe** ({@link disconnected}) when the teardown must eventually happen —\n * live drag sessions, document listeners, teleported *content*. Call\n * {@link cancel} from `connect()` (a reconnect means in-page move) and from\n * the head of any direct teardown path (see below).\n * - **Keep** (bare {@link DetachGate.isDetached}) when undoing the ambiguous\n * case would fight the controller's own effect: a controller that moves its\n * OWN element (portal's no-`content` form) exits a scoped observed root as\n * its normal job — a probe-driven teardown would restore the element into\n * the root, reconnect, re-teleport, and disconnect again, forever. Such\n * controllers keep their state on an ambiguous disconnect and tear down only\n * on {@link DetachGate.isDetached}.\n *\n * Event convention for probe consumers: teardown into a dead (detached) tree is\n * silent — consumers restore from their own `connect()`; a detach that keeps the\n * tree alive must end an in-flight session with a `cancel` event, or composing\n * consumers strand their session bookkeeping.\n */\n\n/** The slice of a Stimulus controller the gate inspects. */\nexport interface DetachGateHost {\n readonly element: Element;\n readonly identifier: string;\n}\n\n/**\n * Per-controller-instance gate deciding whether a `disconnect()` is a real\n * detach. Hold one per controller (`#gate = new DetachGate()`) — Stimulus\n * reuses the instance across reconnects, so the pending probe state carries\n * over exactly as the protocol needs.\n *\n * @example\n * ```ts\n * #gate = new DetachGate();\n *\n * connect(): void {\n * this.#gate.cancel(); // a reconnect: the element moved in-page\n * // …\n * }\n *\n * disconnect(): void {\n * this.#gate.disconnected(this, () => this.#teardown());\n * }\n *\n * #teardown(): void {\n * this.#gate.cancel(); // disarm a still-queued probe (double-run guard)\n * // …\n * }\n * ```\n */\nexport class DetachGate {\n /** Set while a probe is queued, waiting for a reconnect to cancel it. */\n #pending = false;\n\n /**\n * True while a probe is queued — the last disconnect was ambiguous and no\n * reconnect has cancelled it yet. Read it from `connect()` to tell the\n * reconnect half of an in-page move from a first connect: a controller whose\n * initialisation restarts a measurement (a min-duration floor, an elapsed\n * counter) must skip it for the move, where nothing actually restarted.\n */\n get pending(): boolean {\n return this.#pending;\n }\n\n /**\n * True when the disconnect is definitely a real detach — the element left\n * the document, or `data-controller` no longer lists the identifier. False\n * means ambiguous (in-page move or observed-root exit), NOT \"alive\".\n */\n static isDetached(host: DetachGateHost): boolean {\n if (!host.element.isConnected) return true;\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return !tokens.includes(host.identifier);\n }\n\n /**\n * Call from `disconnect()`: runs `teardown` synchronously on a definite\n * detach (fast path), otherwise defers it one microtask — a reconnect\n * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.\n * One microtask is the whole probe window: Stimulus reconnects a moved\n * element within the same mutation batch, before the checkpoint drains.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n if (DetachGate.isDetached(host)) {\n // Also disarms a probe a previous ambiguous disconnect left queued\n // (defer, element removed, disconnect again) — exactly one teardown.\n this.#pending = false;\n teardown();\n return;\n }\n this.#pending = true;\n queueMicrotask(() => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n });\n }\n\n /**\n * Disarms a pending probe. Call from `connect()` (the reconnect that proves\n * an in-page move) and from the head of any teardown path not routed through\n * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can\n * never run the teardown a second time.\n */\n cancel(): void {\n this.#pending = false;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { DetachGate } from \"../utils/detach_gate\";\n\n/** How a drag session was initiated; surfaced in every event detail. */\ntype DragPointerType = \"mouse\" | \"touch\" | \"pen\" | \"keyboard\";\n\n/** A pointer drag in flight (from `pointerdown` until up/cancel). */\ninterface PointerSession {\n pointerId: number;\n handle: HTMLElement;\n originX: number;\n originY: number;\n /** True once the movement passed `threshold` and `start` was dispatched. */\n started: boolean;\n /** Last axis-filtered deltas, reported again by `end`. */\n dx: number;\n dy: number;\n pointerType: DragPointerType;\n}\n\n/** A keyboard \"grabbed\" session (Space/Enter on the handle until drop/cancel). */\ninterface KeyboardSession {\n handle: HTMLElement;\n /** Cumulative synthetic deltas accumulated by the arrow keys. */\n dx: number;\n dy: number;\n}\n\n/**\n * Headless **pointer-drag primitive**: a normalized pointer/touch/mouse drag\n * lifecycle with a built-in keyboard alternative. It deliberately does NOT decide\n * what a drag *means* (reorder, dismiss, resize…) — it only emits a clean,\n * accessibility-friendly drag signal that higher-level behaviors (sortable,\n * swipe-actions, bottom-sheet, split-pane…) compose from. No dedicated APG\n * pattern exists for drag-and-drop; the keyboard model below is the accepted\n * alternative-input technique (WCAG 2.1.1 / 2.5.7 Dragging Movements). Core\n * (zero dependencies).\n *\n * Markup contract (identifier: `stimeo--pointer-drag`):\n * <li data-controller=\"stimeo--pointer-drag\"\n * data-stimeo--pointer-drag-axis-value=\"y\">\n * <button data-stimeo--pointer-drag-target=\"handle\" aria-label=\"Reorder\">⠿</button>\n * </li>\n *\n * The `handle` target is optional — without one, the controller element itself\n * is the handle. Pointer flow: `pointerdown` on a handle arms the drag; once the\n * axis-filtered movement exceeds `threshold`, `start` fires, every subsequent\n * movement fires `move` (deltas cumulative from the origin), and release fires\n * `end` (`pointercancel` → `cancel`). Keyboard flow: Space/Enter on the handle\n * grabs (`start`, `data-grabbed`), the arrow keys emit synthetic `move` events of\n * `keyboardStep` px each, Space/Enter drops (`end`) and Escape cancels — so every\n * consumer gets a keyboard path for free.\n *\n * @remarks\n * Behavior only: consumers own all visuals (ghost, placeholder, transforms) by\n * reacting to the events and the `data-dragging` / `data-grabbed` hooks. The one\n * deliberate exception is the opt-in `follow` value for the \"just move the\n * element\" case: it applies the in-flight deltas to the element's CSS\n * `translate` (a property independent of `transform`, so authored transforms\n * survive), commits the offset on drop and restores it on cancel — the exact\n * boilerplate every simple consumer would otherwise re-write. With `follow` the\n * element's inline `translate` belongs to this controller (`connect()` re-reads\n * a committed `<x>px <y>px` offset, so Turbo restores stay consistent). ARIA 1.1\n * deprecated `aria-grabbed`/`aria-dropeffect`, so the grabbed state is\n * published as `data-grabbed` and the *meaning* of a drag must be announced by\n * the consumer (pair with `stimeo--announcer`). `touch-action` on the handles is\n * derived from `axis` (marker-guarded, authored values win) so the page does not\n * pan mid-drag. Deltas are physical (client coordinates): RTL semantics belong\n * to the consumer. `disconnect()` (Turbo navigation included) tears down any\n * active session — except across an in-page move, which the session survives\n * (the full lifecycle contract lives on `disconnect()`); `connect()` clears\n * stale hooks a Turbo cache restore may have preserved (a drag cannot survive\n * a navigation).\n */\nexport class PointerDragController extends Controller<HTMLElement> {\n static override targets = [\"handle\"];\n static override values = {\n axis: { type: String, default: \"both\" },\n threshold: { type: Number, default: 3 },\n keyboardStep: { type: Number, default: 10 },\n disabled: { type: Boolean, default: false },\n follow: { type: Boolean, default: false },\n };\n static events = [\"start\", \"move\", \"end\", \"cancel\"] as const;\n\n declare readonly handleTargets: HTMLElement[];\n declare readonly hasHandleTarget: boolean;\n declare axisValue: string;\n declare thresholdValue: number;\n declare keyboardStepValue: number;\n declare disabledValue: boolean;\n declare followValue: boolean;\n\n /** Marker attribute recording that this controller set the touch-action. */\n static readonly #TOUCH_ACTION_MARKER = \"data-pointer-drag-touch-action\";\n /** Marker attribute recording that this controller set `tabindex`. */\n static readonly #TABINDEX_MARKER = \"data-pointer-drag-tabindex\";\n\n #pointer: PointerSession | null = null;\n #keyboard: KeyboardSession | null = null;\n /** Committed follow offset from past drops; a new drag's deltas add onto it. */\n #followBase = { x: 0, y: 0 };\n /** Aborts in-progress pointer-drag listeners on drag end / teardown. */\n #dragAbort: AbortController | null = null;\n /** Decides whether a mid-session disconnect() is an in-page move or a detach. */\n readonly #gate = new DetachGate();\n\n override connect(): void {\n // A reconnect disarms the probe a mid-session disconnect() armed — the\n // element was moved in-page and the session survives (see disconnect).\n this.#gate.cancel();\n // Transient drag state cannot survive a navigation: clear hooks a Turbo\n // cache restore may have snapshotted mid-drag (idempotent reconnect). But a\n // session preserved across an in-page move keeps its hooks.\n if (!this.#pointer) this.element.removeAttribute(\"data-dragging\");\n if (!this.#keyboard) this.element.removeAttribute(\"data-grabbed\");\n // Follow mode owns the element's inline `translate`: re-read a committed\n // offset so reconnects (Turbo cache restore included) keep accumulating from\n // where the element visually sits. A session surviving an in-page move keeps\n // its base — mid-drag inline state must not be committed as a new base.\n if (this.followValue && !this.#pointer && !this.#keyboard) {\n this.#followBase = this.#parseFollowBase();\n }\n // Delegated on the container so dynamically added handles need no per-element\n // data-action.\n this.element.addEventListener(\"pointerdown\", this.#onPointerDown);\n this.element.addEventListener(\"keydown\", this.#onKeydown);\n if (!this.hasHandleTarget) this.#prepareHandle(this.element);\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"pointerdown\", this.#onPointerDown);\n this.element.removeEventListener(\"keydown\", this.#onKeydown);\n // A mid-session disconnect on a STILL-connected element is ambiguous: an\n // in-page move (a consumer like sortable re-inserts the item mid-grab —\n // Stimulus reconnects the same instance in the same mutation batch, and\n // the session must survive) vs a real detach that keeps the element\n // (`data-controller` removed, Turbo morph, element moved outside the\n // observed root). DetachGate discriminates: the token check is the\n // synchronous fast path (a morph's teardown — and its `cancel` — fires in\n // the same tick), the one-microtask probe cancelled by connect() covers\n // the rest — a detached-in-place controller never leaks its session or\n // document listeners. Probe only while a session needs protecting; an\n // idle controller tears down synchronously either way.\n if (this.#pointer || this.#keyboard) {\n this.#gate.disconnected(this, () => this.#teardown());\n return;\n }\n this.#teardown();\n }\n\n /** Prepares handles added at runtime (touch-action + focusability). */\n handleTargetConnected(handle: HTMLElement): void {\n this.#prepareHandle(handle);\n }\n\n handleTargetDisconnected(handle: HTMLElement): void {\n // A handle REMOVED mid-session can never deliver its keydown (the delegated\n // listener sits on the container it left), so the session would leak — and\n // the one-session guard would then reject every future pointerdown. End it\n // silently: the DOM is changing under the user, same as disconnect. But\n // target callbacks ALSO fire when the CONTROLLER disconnects (Stimulus\n // stops the target observer — including the in-page move a live session\n // must survive): a handle still inside the element was not removed, so\n // that case is left to disconnect()'s deferred probe.\n if (!this.element.contains(handle)) {\n if (this.#pointer?.handle === handle) this.#endPointerSession();\n if (this.#keyboard?.handle === handle) this.#clearKeyboardSession();\n }\n this.#restoreHandle(handle);\n }\n\n /** Re-derives the handles' touch-action when the axis changes. */\n axisValueChanged(): void {\n for (const handle of this.#handles()) {\n if (handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)) {\n handle.style.touchAction = this.#touchActionForAxis();\n }\n }\n }\n\n /** Cancels any in-flight session when the controller is disabled mid-drag. */\n disabledValueChanged(): void {\n if (!this.disabledValue) return;\n if (this.#pointer?.started || this.#keyboard) {\n this.#dispatchCancel(this.#keyboard ? \"keyboard\" : (this.#pointer?.pointerType ?? \"mouse\"));\n }\n this.#teardownSessions();\n }\n\n /** Arms a pointer drag on a handle; `start` waits for the threshold. */\n readonly #onPointerDown = (event: PointerEvent): void => {\n // One session at a time: ignore a second pointerdown (multi-touch, an\n // errant tap, a grabbed keyboard session) so an in-flight drag is never\n // silently overwritten and its captured pointer orphaned.\n if (this.disabledValue || this.#pointer || this.#keyboard || event.button !== 0) return;\n const handle = this.#handleFor(event.target);\n if (!handle) return;\n\n // Suppress text selection / native image drag; restore the focus the\n // suppressed default would have given, so Escape and the keyboard path\n // stay reachable right after a pointer interaction (WCAG 2.1.1).\n event.preventDefault();\n handle.focus();\n\n this.#pointer = {\n pointerId: event.pointerId,\n handle,\n originX: event.clientX,\n originY: event.clientY,\n started: false,\n dx: 0,\n dy: 0,\n pointerType: this.#pointerTypeOf(event),\n };\n\n // Pointer capture keeps fast drags delivering to the handle even when the\n // pointer strays outside it — but a consumer may re-insert the dragged\n // element mid-drag (sortable's live reorder), which silently releases the\n // capture. Tracking listeners therefore live on the DOCUMENT (captured\n // events bubble there; uncaptured ones fire there anyway), and the\n // AbortController releases them on drag end and disconnect() (Turbo included).\n handle.setPointerCapture(event.pointerId);\n this.#dragAbort?.abort();\n const abort = new AbortController();\n this.#dragAbort = abort;\n document.addEventListener(\"pointermove\", this.#onPointerMove, { signal: abort.signal });\n document.addEventListener(\"pointerup\", this.#onPointerUp, { signal: abort.signal });\n document.addEventListener(\"pointercancel\", this.#onPointerCancel, { signal: abort.signal });\n };\n\n readonly #onPointerMove = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n\n const [dx, dy] = this.#filterAxis(\n event.clientX - session.originX,\n event.clientY - session.originY,\n );\n if (!session.started) {\n if (Math.hypot(dx, dy) < this.thresholdValue) return;\n session.started = true;\n this.element.setAttribute(\"data-dragging\", \"true\");\n this.dispatch(\"start\", {\n detail: { x: event.clientX, y: event.clientY, pointerType: session.pointerType },\n });\n }\n\n session.dx = dx;\n session.dy = dy;\n this.#followMove(dx, dy);\n this.dispatch(\"move\", {\n detail: { dx, dy, x: event.clientX, y: event.clientY, pointerType: session.pointerType },\n });\n };\n\n readonly #onPointerUp = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n const { started, dx, dy, pointerType } = session;\n this.#endPointerSession();\n // Below the threshold the gesture was a plain click, not a drag: stay silent.\n if (!started) return;\n this.#followCommit(dx, dy);\n this.dispatch(\"end\", { detail: { dx, dy, pointerType } });\n };\n\n /** OS gesture / scroll takeover interrupted the drag: cancel, don't drop. */\n readonly #onPointerCancel = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n const { started, pointerType } = session;\n this.#endPointerSession();\n if (started) this.#dispatchCancel(pointerType);\n };\n\n /** Keyboard alternative: Space/Enter grab & drop, arrows move, Escape cancels. */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n if (this.disabledValue) return;\n const handle = this.#handleFor(event.target);\n if (!handle) return;\n\n // A descendant widget that already claimed the key (a nested roving list, a\n // segmented field inside the handle) must not ALSO drive the drag —\n // composition depends on this yield. This controller is the \"grabbed drag\n // handle\" that the other yields name as the widget they defer to, so it owes\n // the same courtesy downward.\n if (event.defaultPrevented) return;\n // A chorded arrow belongs to the browser. \"Someone already consumed it\"\n // outranks that, so it is checked after the yield above.\n if (isReservedArrowChord(event)) return;\n\n // Escape cancels whichever session is live (pointer drag or keyboard grab).\n if (event.key === \"Escape\") {\n // A press an inner handler already owned is yielded at the top of this\n // handler. What is checked here is the IME half: a press during a\n // composition steers the conversion, not the drag.\n if (event.isComposing) return;\n if (this.#pointer?.started) {\n const { pointerType } = this.#pointer;\n this.#teardownSessions();\n event.preventDefault();\n this.#dispatchCancel(pointerType);\n } else if (this.#keyboard) {\n this.#teardownSessions();\n event.preventDefault();\n this.#dispatchCancel(\"keyboard\");\n }\n return;\n }\n\n if (event.key === \" \" || event.key === \"Enter\") {\n // A live pointer drag owns the session: pressing Space mid-drag (the\n // pointerdown focused the handle) must not start a parallel grab.\n if (this.#pointer) return;\n event.preventDefault();\n if (this.#keyboard) this.#dropKeyboard();\n else this.#grabKeyboard(handle);\n return;\n }\n\n if (!this.#keyboard) return;\n const step = this.#keyboardDelta(event.key);\n if (!step) return;\n // Consume every arrow while grabbed (even on a locked axis) so the page\n // never scrolls mid-grab; only allowed-axis arrows emit a move.\n event.preventDefault();\n const [dx, dy] = this.#filterAxis(step[0], step[1]);\n if (dx === 0 && dy === 0) return;\n this.#keyboard.dx += dx;\n this.#keyboard.dy += dy;\n this.#followMove(this.#keyboard.dx, this.#keyboard.dy);\n this.dispatch(\"move\", {\n detail: {\n dx: this.#keyboard.dx,\n dy: this.#keyboard.dy,\n x: this.#keyboard.dx,\n y: this.#keyboard.dy,\n pointerType: \"keyboard\",\n },\n });\n };\n\n /** Enters the grabbed mode: `data-grabbed` on element + handle, then `start`. */\n #grabKeyboard(handle: HTMLElement): void {\n this.#keyboard = { handle, dx: 0, dy: 0 };\n this.element.setAttribute(\"data-grabbed\", \"true\");\n handle.setAttribute(\"data-grabbed\", \"true\");\n this.dispatch(\"start\", { detail: { x: 0, y: 0, pointerType: \"keyboard\" } });\n }\n\n #dropKeyboard(): void {\n const session = this.#keyboard;\n if (!session) return;\n this.#clearKeyboardSession();\n this.#followCommit(session.dx, session.dy);\n this.dispatch(\"end\", { detail: { dx: session.dx, dy: session.dy, pointerType: \"keyboard\" } });\n }\n\n /** Maps an arrow key to a raw (unfiltered) `keyboardStep` delta. */\n #keyboardDelta(key: string): [number, number] | null {\n const step = this.keyboardStepValue;\n switch (key) {\n case \"ArrowRight\":\n return [step, 0];\n case \"ArrowLeft\":\n return [-step, 0];\n case \"ArrowDown\":\n return [0, step];\n case \"ArrowUp\":\n return [0, -step];\n default:\n return null;\n }\n }\n\n /** Zeroes the delta on the locked axis (`axis` = x | y | both). */\n #filterAxis(dx: number, dy: number): [number, number] {\n if (this.axisValue === \"x\") return [dx, 0];\n if (this.axisValue === \"y\") return [0, dy];\n return [dx, dy];\n }\n\n /** Releases capture + listeners and clears the pointer session and its hook. */\n #endPointerSession(): void {\n this.#releasePointerCapture();\n this.#pointer = null;\n this.#dragAbort?.abort();\n this.#dragAbort = null;\n this.element.removeAttribute(\"data-dragging\");\n }\n\n /** Releases the pointer capture the active session set (idempotent, safe). */\n #releasePointerCapture(): void {\n const session = this.#pointer;\n if (session?.handle.hasPointerCapture(session.pointerId)) {\n session.handle.releasePointerCapture(session.pointerId);\n }\n }\n\n #clearKeyboardSession(): void {\n this.#keyboard?.handle.removeAttribute(\"data-grabbed\");\n this.#keyboard = null;\n this.element.removeAttribute(\"data-grabbed\");\n }\n\n /**\n * Full teardown for a controller whose element is going away or lost its\n * controller. Into a dead (detached) tree it is silent — consumers restore\n * from their own `connect()`, not from a cancel event. But a detach that\n * KEEPS the element (Turbo morph, `data-controller` removed) leaves the\n * consumers alive and listening: an in-flight session must end in `cancel`\n * there, or a composer like sortable strands its one-at-a-time session\n * bookkeeping forever.\n */\n #teardown(): void {\n // Disarm any probe still queued: without this, an immediate teardown\n // (element removed right after a deferring disconnect) would let the\n // orphaned probe run #teardown a second time — a double cancel.\n this.#gate.cancel();\n const interrupted = this.#pointer?.started\n ? this.#pointer.pointerType\n : this.#keyboard\n ? (\"keyboard\" as const)\n : null;\n this.#teardownSessions();\n for (const handle of this.#handles()) this.#restoreHandle(handle);\n if (interrupted && this.element.isConnected) this.#dispatchCancel(interrupted);\n }\n\n /**\n * Silently tears down whatever session is live (disconnect / disabled /\n * Escape). Composed from the two single-session teardowns so pointer and\n * keyboard cleanup cannot diverge as either path evolves.\n */\n #teardownSessions(): void {\n this.#endPointerSession();\n this.#clearKeyboardSession();\n }\n\n #dispatchCancel(pointerType: DragPointerType): void {\n // Every cancel path funnels here, so follow's snap-back can never be missed\n // (Escape, pointercancel, mid-session disable, detach-that-keeps-the-element).\n this.#followReset();\n this.dispatch(\"cancel\", { detail: { pointerType } });\n }\n\n /** Applies the in-flight offset to the element's `translate` (follow only). */\n #followMove(dx: number, dy: number): void {\n if (!this.followValue) return;\n this.#applyFollow(this.#followBase.x + dx, this.#followBase.y + dy);\n }\n\n /** Folds a drop's deltas into the committed base offset (follow only). */\n #followCommit(dx: number, dy: number): void {\n if (!this.followValue) return;\n this.#followBase = { x: this.#followBase.x + dx, y: this.#followBase.y + dy };\n this.#applyFollow(this.#followBase.x, this.#followBase.y);\n }\n\n /** Snaps back to the committed position (follow only) — the cancel contract. */\n #followReset(): void {\n if (!this.followValue) return;\n this.#applyFollow(this.#followBase.x, this.#followBase.y);\n }\n\n /**\n * Writes the offset as the standalone CSS `translate` property — independent\n * of `transform`, so an authored transform (scale, rotate…) is never\n * clobbered. At the origin the property is removed to keep the DOM clean.\n */\n #applyFollow(x: number, y: number): void {\n if (x === 0 && y === 0) this.element.style.removeProperty(\"translate\");\n else this.element.style.setProperty(\"translate\", `${x}px ${y}px`);\n }\n\n /**\n * Reads a committed follow offset back from the inline `translate`. Only the\n * controller's own `<x>px <y>px` shape is recognized; anything else (an\n * authored percentage, `none`, an empty string) starts the base at 0 — follow\n * mode owns this property, per the class contract.\n */\n #parseFollowBase(): { x: number; y: number } {\n const raw = this.element.style.getPropertyValue(\"translate\").trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)px(?:\\s+(-?\\d+(?:\\.\\d+)?)px)?$/.exec(raw);\n if (!match) return { x: 0, y: 0 };\n return { x: Number(match[1]), y: Number(match[2] ?? \"0\") };\n }\n\n /** The active handles: the `handle` targets, else the element itself. */\n #handles(): HTMLElement[] {\n return this.hasHandleTarget ? this.handleTargets : [this.element];\n }\n\n /** Resolves the handle owning an event target (the handle or a descendant). */\n #handleFor(target: EventTarget | null): HTMLElement | null {\n const node = target as Node | null;\n if (!node) return null;\n return this.#handles().find((handle) => handle === node || handle.contains(node)) ?? null;\n }\n\n #pointerTypeOf(event: PointerEvent): DragPointerType {\n const type = event.pointerType;\n if (type === \"touch\" || type === \"pen\") return type;\n return \"mouse\";\n }\n\n /** `touch-action` that lets the page keep panning on the locked axis only. */\n #touchActionForAxis(): string {\n if (this.axisValue === \"x\") return \"pan-y\";\n if (this.axisValue === \"y\") return \"pan-x\";\n return \"none\";\n }\n\n /**\n * Establishes the handle contract: a `touch-action` derived from `axis`\n * (marker-guarded so an authored inline value wins and reconnects stay\n * idempotent) and focusability for the keyboard path (`tabindex=\"0\"` only when\n * the author supplied none — prefer a real `<button>` handle). Both additions\n * are marker-owned so `#restoreHandle` reverts them symmetrically on teardown.\n */\n #prepareHandle(handle: HTMLElement): void {\n if (\n handle.style.touchAction === \"\" ||\n handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)\n ) {\n handle.style.touchAction = this.#touchActionForAxis();\n handle.setAttribute(PointerDragController.#TOUCH_ACTION_MARKER, \"true\");\n }\n if (handle.tabIndex < 0 && !handle.hasAttribute(\"tabindex\")) {\n handle.setAttribute(\"tabindex\", \"0\");\n handle.setAttribute(PointerDragController.#TABINDEX_MARKER, \"true\");\n }\n }\n\n /** Reverts the marker-owned touch-action + tabindex (authored values untouched). */\n #restoreHandle(handle: HTMLElement): void {\n if (handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)) {\n handle.style.touchAction = \"\";\n handle.removeAttribute(PointerDragController.#TOUCH_ACTION_MARKER);\n }\n if (handle.hasAttribute(PointerDragController.#TABINDEX_MARKER)) {\n handle.removeAttribute(PointerDragController.#TABINDEX_MARKER);\n // Only remove what is verifiably still ours and safe to remove: another\n // owner (e.g. a roving list) may have rewritten the value since, and\n // stripping tabindex off the focused element would blur it to <body>\n // (losing the user's place — worse than leaking one tab stop).\n if (handle.getAttribute(\"tabindex\") === \"0\" && document.activeElement !== handle) {\n handle.removeAttribute(\"tabindex\");\n }\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/arrow_step.ts","../../src/utils/detach_gate.ts","../../src/controllers/pointer_drag_controller.ts"],"names":[],"mappings":"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","file":"pointer_drag_controller.js","sourcesContent":["import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n","/**\n * Shared \"in-page move vs real detach\" discriminator for `disconnect()`.\n *\n * A mid-session `disconnect()` whose element is still in the document is\n * ambiguous. It can be:\n *\n * - an **in-page move** — a consumer (sortable, a teleport) re-inserted the\n * element; Stimulus reconnects the SAME instance in the same mutation batch\n * and the controller's runtime state must survive;\n * - a **detach that keeps the element** — `data-controller` no longer lists the\n * identifier (a Turbo 8 morph), or the element moved outside the observed\n * root (a scoped `Application.start(root)`, a shadow root); no reconnect will\n * ever come and the controller must tear down or it leaks its sessions,\n * document listeners, and moved nodes.\n *\n * Neither available signal decides this alone. A synchronous `data-controller`\n * token check catches the morph case but misses an observed-root exit (the\n * token stays, the reconnect never comes). Deferring teardown one microtask and\n * letting `connect()` cancel it catches the root exit, but leans on Stimulus\n * reconnecting moved elements within the same mutation batch — internal\n * behavior, not a public contract. {@link DetachGate} composes both: the token\n * check is the synchronous fast path (no probe, no reliance on batching), the\n * microtask probe is the fallback for the genuinely ambiguous remainder.\n *\n * Policy stays with the consumer (the `SafeTimeout` / `FocusTrap` lineage —\n * this util owns the discrimination only):\n *\n * - **Probe** ({@link disconnected}) when the teardown must eventually happen —\n * live drag sessions, document listeners, teleported *content*. Call\n * {@link cancel} from `connect()` (a reconnect means in-page move) and from\n * the head of any direct teardown path (see below).\n * - **Keep** (bare {@link DetachGate.isDetached}) when undoing the ambiguous\n * case would fight the controller's own effect: a controller that moves its\n * OWN element (portal's no-`content` form) exits a scoped observed root as\n * its normal job — a probe-driven teardown would restore the element into\n * the root, reconnect, re-teleport, and disconnect again, forever. Such\n * controllers keep their state on an ambiguous disconnect and tear down only\n * on {@link DetachGate.isDetached}.\n *\n * Event convention for probe consumers: teardown into a dead (detached) tree is\n * silent — consumers restore from their own `connect()`; a detach that keeps the\n * tree alive must end an in-flight session with a `cancel` event, or composing\n * consumers strand their session bookkeeping.\n */\n\n/** The slice of a Stimulus controller the gate inspects. */\nexport interface DetachGateHost {\n readonly element: Element;\n readonly identifier: string;\n}\n\n/**\n * Per-controller-instance gate deciding whether a `disconnect()` is a real\n * detach. Hold one per controller (`#gate = new DetachGate()`) — Stimulus\n * reuses the instance across reconnects, so the pending probe state carries\n * over exactly as the protocol needs.\n *\n * @example\n * ```ts\n * #gate = new DetachGate();\n *\n * connect(): void {\n * this.#gate.cancel(); // a reconnect: the element moved in-page\n * // …\n * }\n *\n * disconnect(): void {\n * this.#gate.disconnected(this, () => this.#teardown());\n * }\n *\n * #teardown(): void {\n * this.#gate.cancel(); // disarm a still-queued probe (double-run guard)\n * // …\n * }\n * ```\n */\nexport class DetachGate {\n /** Set while a probe is queued, waiting for a reconnect to cancel it. */\n #pending = false;\n\n /**\n * True while a probe is queued — the last disconnect was ambiguous and no\n * reconnect has cancelled it yet. Read it from `connect()` to tell the\n * reconnect half of an in-page move from a first connect: a controller whose\n * initialisation restarts a measurement (a min-duration floor, an elapsed\n * counter) must skip it for the move, where nothing actually restarted.\n */\n get pending(): boolean {\n return this.#pending;\n }\n\n /**\n * True when the disconnect is definitely a real detach — the element left\n * the document, or `data-controller` no longer lists the identifier. False\n * means ambiguous (in-page move or observed-root exit), NOT \"alive\".\n */\n static isDetached(host: DetachGateHost): boolean {\n if (!host.element.isConnected) return true;\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return !tokens.includes(host.identifier);\n }\n\n /**\n * Call from `disconnect()`: runs `teardown` synchronously on a definite\n * detach (fast path), otherwise defers it one microtask — a reconnect\n * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.\n * One microtask is the whole probe window: Stimulus reconnects a moved\n * element within the same mutation batch, before the checkpoint drains.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n if (DetachGate.isDetached(host)) {\n // Also disarms a probe a previous ambiguous disconnect left queued\n // (defer, element removed, disconnect again) — exactly one teardown.\n this.#pending = false;\n teardown();\n return;\n }\n this.#pending = true;\n queueMicrotask(() => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n });\n }\n\n /**\n * Disarms a pending probe. Call from `connect()` (the reconnect that proves\n * an in-page move) and from the head of any teardown path not routed through\n * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can\n * never run the teardown a second time.\n */\n cancel(): void {\n this.#pending = false;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { DetachGate } from \"../utils/detach_gate\";\n\n/** How a drag session was initiated; surfaced in every event detail. */\ntype DragPointerType = \"mouse\" | \"touch\" | \"pen\" | \"keyboard\";\n\n/** A pointer drag in flight (from `pointerdown` until up/cancel). */\ninterface PointerSession {\n pointerId: number;\n handle: HTMLElement;\n originX: number;\n originY: number;\n /** True once the movement passed `threshold` and `start` was dispatched. */\n started: boolean;\n /** Last axis-filtered deltas, reported again by `end`. */\n dx: number;\n dy: number;\n pointerType: DragPointerType;\n}\n\n/** A keyboard \"grabbed\" session (Space/Enter on the handle until drop/cancel). */\ninterface KeyboardSession {\n handle: HTMLElement;\n /** Cumulative synthetic deltas accumulated by the arrow keys. */\n dx: number;\n dy: number;\n}\n\n/**\n * Headless **pointer-drag primitive**: a normalized pointer/touch/mouse drag\n * lifecycle with a built-in keyboard alternative. It deliberately does NOT decide\n * what a drag *means* (reorder, dismiss, resize…) — it only emits a clean,\n * accessibility-friendly drag signal that higher-level behaviors (sortable,\n * swipe-actions, bottom-sheet, split-pane…) compose from. No dedicated APG\n * pattern exists for drag-and-drop; the keyboard model below is the accepted\n * alternative-input technique (WCAG 2.1.1 / 2.5.7 Dragging Movements). Core\n * (zero dependencies).\n *\n * Markup contract (identifier: `stimeo--pointer-drag`):\n * <li data-controller=\"stimeo--pointer-drag\"\n * data-stimeo--pointer-drag-axis-value=\"y\">\n * <button data-stimeo--pointer-drag-target=\"handle\" aria-label=\"Reorder\">⠿</button>\n * </li>\n *\n * The `handle` target is optional — without one, the controller element itself\n * is the handle. Pointer flow: `pointerdown` on a handle arms the drag; once the\n * axis-filtered movement exceeds `threshold`, `start` fires, every subsequent\n * movement fires `move` (deltas cumulative from the origin), and release fires\n * `end` (`pointercancel` → `cancel`). Keyboard flow: Space/Enter on the handle\n * grabs (`start`, `data-grabbed`), the arrow keys emit synthetic `move` events of\n * `keyboardStep` px each, Space/Enter drops (`end`) and Escape cancels — so every\n * consumer gets a keyboard path for free.\n *\n * `start` dispatches `{ x, y, pointerType }`; `move` dispatches `{ dx, dy, x, y, pointerType }`;\n * `end` dispatches `{ dx, dy, pointerType }`; `cancel` dispatches `{ pointerType }`.\n *\n * @remarks\n * Behavior only: consumers own all visuals (ghost, placeholder, transforms) by\n * reacting to the events and the `data-dragging` / `data-grabbed` hooks. The one\n * deliberate exception is the opt-in `follow` value for the \"just move the\n * element\" case: it applies the in-flight deltas to the element's CSS\n * `translate` (a property independent of `transform`, so authored transforms\n * survive), commits the offset on drop and restores it on cancel — the exact\n * boilerplate every simple consumer would otherwise re-write. With `follow` the\n * element's inline `translate` belongs to this controller (`connect()` re-reads\n * a committed `<x>px <y>px` offset, so Turbo restores stay consistent). ARIA 1.1\n * deprecated `aria-grabbed`/`aria-dropeffect`, so the grabbed state is\n * published as `data-grabbed` and the *meaning* of a drag must be announced by\n * the consumer (pair with `stimeo--announcer`). `touch-action` on the handles is\n * derived from `axis` (marker-guarded, authored values win) so the page does not\n * pan mid-drag. Deltas are physical (client coordinates): RTL semantics belong\n * to the consumer. `disconnect()` (Turbo navigation included) tears down any\n * active session — except across an in-page move, which the session survives\n * (the full lifecycle contract lives on `disconnect()`); `connect()` clears\n * stale hooks a Turbo cache restore may have preserved (a drag cannot survive\n * a navigation).\n */\nexport class PointerDragController extends Controller<HTMLElement> {\n static override targets = [\"handle\"];\n static override values = {\n axis: { type: String, default: \"both\" },\n threshold: { type: Number, default: 3 },\n keyboardStep: { type: Number, default: 10 },\n disabled: { type: Boolean, default: false },\n follow: { type: Boolean, default: false },\n };\n static events = [\"start\", \"move\", \"end\", \"cancel\"] as const;\n\n declare readonly handleTargets: HTMLElement[];\n declare readonly hasHandleTarget: boolean;\n declare axisValue: string;\n declare thresholdValue: number;\n declare keyboardStepValue: number;\n declare disabledValue: boolean;\n declare followValue: boolean;\n\n /** Marker attribute recording that this controller set the touch-action. */\n static readonly #TOUCH_ACTION_MARKER = \"data-pointer-drag-touch-action\";\n /** Marker attribute recording that this controller set `tabindex`. */\n static readonly #TABINDEX_MARKER = \"data-pointer-drag-tabindex\";\n\n #pointer: PointerSession | null = null;\n #keyboard: KeyboardSession | null = null;\n /** Committed follow offset from past drops; a new drag's deltas add onto it. */\n #followBase = { x: 0, y: 0 };\n /** Aborts in-progress pointer-drag listeners on drag end / teardown. */\n #dragAbort: AbortController | null = null;\n /** Decides whether a mid-session disconnect() is an in-page move or a detach. */\n readonly #gate = new DetachGate();\n\n override connect(): void {\n // A reconnect disarms the probe a mid-session disconnect() armed — the\n // element was moved in-page and the session survives (see disconnect).\n this.#gate.cancel();\n // Transient drag state cannot survive a navigation: clear hooks a Turbo\n // cache restore may have snapshotted mid-drag (idempotent reconnect). But a\n // session preserved across an in-page move keeps its hooks.\n if (!this.#pointer) this.element.removeAttribute(\"data-dragging\");\n if (!this.#keyboard) this.element.removeAttribute(\"data-grabbed\");\n // Follow mode owns the element's inline `translate`: re-read a committed\n // offset so reconnects (Turbo cache restore included) keep accumulating from\n // where the element visually sits. A session surviving an in-page move keeps\n // its base — mid-drag inline state must not be committed as a new base.\n if (this.followValue && !this.#pointer && !this.#keyboard) {\n this.#followBase = this.#parseFollowBase();\n }\n // Delegated on the container so dynamically added handles need no per-element\n // data-action.\n this.element.addEventListener(\"pointerdown\", this.#onPointerDown);\n this.element.addEventListener(\"keydown\", this.#onKeydown);\n if (!this.hasHandleTarget) this.#prepareHandle(this.element);\n }\n\n override disconnect(): void {\n this.element.removeEventListener(\"pointerdown\", this.#onPointerDown);\n this.element.removeEventListener(\"keydown\", this.#onKeydown);\n // A mid-session disconnect on a STILL-connected element is ambiguous: an\n // in-page move (a consumer like sortable re-inserts the item mid-grab —\n // Stimulus reconnects the same instance in the same mutation batch, and\n // the session must survive) vs a real detach that keeps the element\n // (`data-controller` removed, Turbo morph, element moved outside the\n // observed root). DetachGate discriminates: the token check is the\n // synchronous fast path (a morph's teardown — and its `cancel` — fires in\n // the same tick), the one-microtask probe cancelled by connect() covers\n // the rest — a detached-in-place controller never leaks its session or\n // document listeners. Probe only while a session needs protecting; an\n // idle controller tears down synchronously either way.\n if (this.#pointer || this.#keyboard) {\n this.#gate.disconnected(this, () => this.#teardown());\n return;\n }\n this.#teardown();\n }\n\n /** Prepares handles added at runtime (touch-action + focusability). */\n handleTargetConnected(handle: HTMLElement): void {\n this.#prepareHandle(handle);\n }\n\n handleTargetDisconnected(handle: HTMLElement): void {\n // A handle REMOVED mid-session can never deliver its keydown (the delegated\n // listener sits on the container it left), so the session would leak — and\n // the one-session guard would then reject every future pointerdown. End it\n // silently: the DOM is changing under the user, same as disconnect. But\n // target callbacks ALSO fire when the CONTROLLER disconnects (Stimulus\n // stops the target observer — including the in-page move a live session\n // must survive): a handle still inside the element was not removed, so\n // that case is left to disconnect()'s deferred probe.\n if (!this.element.contains(handle)) {\n if (this.#pointer?.handle === handle) this.#endPointerSession();\n if (this.#keyboard?.handle === handle) this.#clearKeyboardSession();\n }\n this.#restoreHandle(handle);\n }\n\n /** Re-derives the handles' touch-action when the axis changes. */\n axisValueChanged(): void {\n for (const handle of this.#handles()) {\n if (handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)) {\n handle.style.touchAction = this.#touchActionForAxis();\n }\n }\n }\n\n /** Cancels any in-flight session when the controller is disabled mid-drag. */\n disabledValueChanged(): void {\n if (!this.disabledValue) return;\n if (this.#pointer?.started || this.#keyboard) {\n this.#dispatchCancel(this.#keyboard ? \"keyboard\" : (this.#pointer?.pointerType ?? \"mouse\"));\n }\n this.#teardownSessions();\n }\n\n /** Arms a pointer drag on a handle; `start` waits for the threshold. */\n readonly #onPointerDown = (event: PointerEvent): void => {\n // One session at a time: ignore a second pointerdown (multi-touch, an\n // errant tap, a grabbed keyboard session) so an in-flight drag is never\n // silently overwritten and its captured pointer orphaned.\n if (this.disabledValue || this.#pointer || this.#keyboard || event.button !== 0) return;\n const handle = this.#handleFor(event.target);\n if (!handle) return;\n\n // Suppress text selection / native image drag; restore the focus the\n // suppressed default would have given, so Escape and the keyboard path\n // stay reachable right after a pointer interaction (WCAG 2.1.1).\n event.preventDefault();\n handle.focus();\n\n this.#pointer = {\n pointerId: event.pointerId,\n handle,\n originX: event.clientX,\n originY: event.clientY,\n started: false,\n dx: 0,\n dy: 0,\n pointerType: this.#pointerTypeOf(event),\n };\n\n // Pointer capture keeps fast drags delivering to the handle even when the\n // pointer strays outside it — but a consumer may re-insert the dragged\n // element mid-drag (sortable's live reorder), which silently releases the\n // capture. Tracking listeners therefore live on the DOCUMENT (captured\n // events bubble there; uncaptured ones fire there anyway), and the\n // AbortController releases them on drag end and disconnect() (Turbo included).\n handle.setPointerCapture(event.pointerId);\n this.#dragAbort?.abort();\n const abort = new AbortController();\n this.#dragAbort = abort;\n document.addEventListener(\"pointermove\", this.#onPointerMove, { signal: abort.signal });\n document.addEventListener(\"pointerup\", this.#onPointerUp, { signal: abort.signal });\n document.addEventListener(\"pointercancel\", this.#onPointerCancel, { signal: abort.signal });\n };\n\n readonly #onPointerMove = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n\n const [dx, dy] = this.#filterAxis(\n event.clientX - session.originX,\n event.clientY - session.originY,\n );\n if (!session.started) {\n if (Math.hypot(dx, dy) < this.thresholdValue) return;\n session.started = true;\n this.element.setAttribute(\"data-dragging\", \"true\");\n this.dispatch(\"start\", {\n detail: { x: event.clientX, y: event.clientY, pointerType: session.pointerType },\n });\n }\n\n session.dx = dx;\n session.dy = dy;\n this.#followMove(dx, dy);\n this.dispatch(\"move\", {\n detail: { dx, dy, x: event.clientX, y: event.clientY, pointerType: session.pointerType },\n });\n };\n\n readonly #onPointerUp = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n const { started, dx, dy, pointerType } = session;\n this.#endPointerSession();\n // Below the threshold the gesture was a plain click, not a drag: stay silent.\n if (!started) return;\n this.#followCommit(dx, dy);\n this.dispatch(\"end\", { detail: { dx, dy, pointerType } });\n };\n\n /** OS gesture / scroll takeover interrupted the drag: cancel, don't drop. */\n readonly #onPointerCancel = (event: PointerEvent): void => {\n const session = this.#pointer;\n if (!session || event.pointerId !== session.pointerId) return;\n const { started, pointerType } = session;\n this.#endPointerSession();\n if (started) this.#dispatchCancel(pointerType);\n };\n\n /** Keyboard alternative: Space/Enter grab & drop, arrows move, Escape cancels. */\n readonly #onKeydown = (event: KeyboardEvent): void => {\n if (this.disabledValue) return;\n const handle = this.#handleFor(event.target);\n if (!handle) return;\n\n // A descendant widget that already claimed the key (a nested roving list, a\n // segmented field inside the handle) must not ALSO drive the drag —\n // composition depends on this yield. This controller is the \"grabbed drag\n // handle\" that the other yields name as the widget they defer to, so it owes\n // the same courtesy downward.\n if (event.defaultPrevented) return;\n // A chorded arrow belongs to the browser. \"Someone already consumed it\"\n // outranks that, so it is checked after the yield above.\n if (isReservedArrowChord(event)) return;\n\n // Escape cancels whichever session is live (pointer drag or keyboard grab).\n if (event.key === \"Escape\") {\n // A press an inner handler already owned is yielded at the top of this\n // handler. What is checked here is the IME half: a press during a\n // composition steers the conversion, not the drag.\n if (event.isComposing) return;\n if (this.#pointer?.started) {\n const { pointerType } = this.#pointer;\n this.#teardownSessions();\n event.preventDefault();\n this.#dispatchCancel(pointerType);\n } else if (this.#keyboard) {\n this.#teardownSessions();\n event.preventDefault();\n this.#dispatchCancel(\"keyboard\");\n }\n return;\n }\n\n if (event.key === \" \" || event.key === \"Enter\") {\n // A live pointer drag owns the session: pressing Space mid-drag (the\n // pointerdown focused the handle) must not start a parallel grab.\n if (this.#pointer) return;\n event.preventDefault();\n if (this.#keyboard) this.#dropKeyboard();\n else this.#grabKeyboard(handle);\n return;\n }\n\n if (!this.#keyboard) return;\n const step = this.#keyboardDelta(event.key);\n if (!step) return;\n // Consume every arrow while grabbed (even on a locked axis) so the page\n // never scrolls mid-grab; only allowed-axis arrows emit a move.\n event.preventDefault();\n const [dx, dy] = this.#filterAxis(step[0], step[1]);\n if (dx === 0 && dy === 0) return;\n this.#keyboard.dx += dx;\n this.#keyboard.dy += dy;\n this.#followMove(this.#keyboard.dx, this.#keyboard.dy);\n this.dispatch(\"move\", {\n detail: {\n dx: this.#keyboard.dx,\n dy: this.#keyboard.dy,\n x: this.#keyboard.dx,\n y: this.#keyboard.dy,\n pointerType: \"keyboard\",\n },\n });\n };\n\n /** Enters the grabbed mode: `data-grabbed` on element + handle, then `start`. */\n #grabKeyboard(handle: HTMLElement): void {\n this.#keyboard = { handle, dx: 0, dy: 0 };\n this.element.setAttribute(\"data-grabbed\", \"true\");\n handle.setAttribute(\"data-grabbed\", \"true\");\n this.dispatch(\"start\", { detail: { x: 0, y: 0, pointerType: \"keyboard\" } });\n }\n\n #dropKeyboard(): void {\n const session = this.#keyboard;\n if (!session) return;\n this.#clearKeyboardSession();\n this.#followCommit(session.dx, session.dy);\n this.dispatch(\"end\", { detail: { dx: session.dx, dy: session.dy, pointerType: \"keyboard\" } });\n }\n\n /** Maps an arrow key to a raw (unfiltered) `keyboardStep` delta. */\n #keyboardDelta(key: string): [number, number] | null {\n const step = this.keyboardStepValue;\n switch (key) {\n case \"ArrowRight\":\n return [step, 0];\n case \"ArrowLeft\":\n return [-step, 0];\n case \"ArrowDown\":\n return [0, step];\n case \"ArrowUp\":\n return [0, -step];\n default:\n return null;\n }\n }\n\n /** Zeroes the delta on the locked axis (`axis` = x | y | both). */\n #filterAxis(dx: number, dy: number): [number, number] {\n if (this.axisValue === \"x\") return [dx, 0];\n if (this.axisValue === \"y\") return [0, dy];\n return [dx, dy];\n }\n\n /** Releases capture + listeners and clears the pointer session and its hook. */\n #endPointerSession(): void {\n this.#releasePointerCapture();\n this.#pointer = null;\n this.#dragAbort?.abort();\n this.#dragAbort = null;\n this.element.removeAttribute(\"data-dragging\");\n }\n\n /** Releases the pointer capture the active session set (idempotent, safe). */\n #releasePointerCapture(): void {\n const session = this.#pointer;\n if (session?.handle.hasPointerCapture(session.pointerId)) {\n session.handle.releasePointerCapture(session.pointerId);\n }\n }\n\n #clearKeyboardSession(): void {\n this.#keyboard?.handle.removeAttribute(\"data-grabbed\");\n this.#keyboard = null;\n this.element.removeAttribute(\"data-grabbed\");\n }\n\n /**\n * Full teardown for a controller whose element is going away or lost its\n * controller. Into a dead (detached) tree it is silent — consumers restore\n * from their own `connect()`, not from a cancel event. But a detach that\n * KEEPS the element (Turbo morph, `data-controller` removed) leaves the\n * consumers alive and listening: an in-flight session must end in `cancel`\n * there, or a composer like sortable strands its one-at-a-time session\n * bookkeeping forever.\n */\n #teardown(): void {\n // Disarm any probe still queued: without this, an immediate teardown\n // (element removed right after a deferring disconnect) would let the\n // orphaned probe run #teardown a second time — a double cancel.\n this.#gate.cancel();\n const interrupted = this.#pointer?.started\n ? this.#pointer.pointerType\n : this.#keyboard\n ? (\"keyboard\" as const)\n : null;\n this.#teardownSessions();\n for (const handle of this.#handles()) this.#restoreHandle(handle);\n if (interrupted && this.element.isConnected) this.#dispatchCancel(interrupted);\n }\n\n /**\n * Silently tears down whatever session is live (disconnect / disabled /\n * Escape). Composed from the two single-session teardowns so pointer and\n * keyboard cleanup cannot diverge as either path evolves.\n */\n #teardownSessions(): void {\n this.#endPointerSession();\n this.#clearKeyboardSession();\n }\n\n #dispatchCancel(pointerType: DragPointerType): void {\n // Every cancel path funnels here, so follow's snap-back can never be missed\n // (Escape, pointercancel, mid-session disable, detach-that-keeps-the-element).\n this.#followReset();\n this.dispatch(\"cancel\", { detail: { pointerType } });\n }\n\n /** Applies the in-flight offset to the element's `translate` (follow only). */\n #followMove(dx: number, dy: number): void {\n if (!this.followValue) return;\n this.#applyFollow(this.#followBase.x + dx, this.#followBase.y + dy);\n }\n\n /** Folds a drop's deltas into the committed base offset (follow only). */\n #followCommit(dx: number, dy: number): void {\n if (!this.followValue) return;\n this.#followBase = { x: this.#followBase.x + dx, y: this.#followBase.y + dy };\n this.#applyFollow(this.#followBase.x, this.#followBase.y);\n }\n\n /** Snaps back to the committed position (follow only) — the cancel contract. */\n #followReset(): void {\n if (!this.followValue) return;\n this.#applyFollow(this.#followBase.x, this.#followBase.y);\n }\n\n /**\n * Writes the offset as the standalone CSS `translate` property — independent\n * of `transform`, so an authored transform (scale, rotate…) is never\n * clobbered. At the origin the property is removed to keep the DOM clean.\n */\n #applyFollow(x: number, y: number): void {\n if (x === 0 && y === 0) this.element.style.removeProperty(\"translate\");\n else this.element.style.setProperty(\"translate\", `${x}px ${y}px`);\n }\n\n /**\n * Reads a committed follow offset back from the inline `translate`. Only the\n * controller's own `<x>px <y>px` shape is recognized; anything else (an\n * authored percentage, `none`, an empty string) starts the base at 0 — follow\n * mode owns this property, per the class contract.\n */\n #parseFollowBase(): { x: number; y: number } {\n const raw = this.element.style.getPropertyValue(\"translate\").trim();\n const match = /^(-?\\d+(?:\\.\\d+)?)px(?:\\s+(-?\\d+(?:\\.\\d+)?)px)?$/.exec(raw);\n if (!match) return { x: 0, y: 0 };\n return { x: Number(match[1]), y: Number(match[2] ?? \"0\") };\n }\n\n /** The active handles: the `handle` targets, else the element itself. */\n #handles(): HTMLElement[] {\n return this.hasHandleTarget ? this.handleTargets : [this.element];\n }\n\n /** Resolves the handle owning an event target (the handle or a descendant). */\n #handleFor(target: EventTarget | null): HTMLElement | null {\n const node = target as Node | null;\n if (!node) return null;\n return this.#handles().find((handle) => handle === node || handle.contains(node)) ?? null;\n }\n\n #pointerTypeOf(event: PointerEvent): DragPointerType {\n const type = event.pointerType;\n if (type === \"touch\" || type === \"pen\") return type;\n return \"mouse\";\n }\n\n /** `touch-action` that lets the page keep panning on the locked axis only. */\n #touchActionForAxis(): string {\n if (this.axisValue === \"x\") return \"pan-y\";\n if (this.axisValue === \"y\") return \"pan-x\";\n return \"none\";\n }\n\n /**\n * Establishes the handle contract: a `touch-action` derived from `axis`\n * (marker-guarded so an authored inline value wins and reconnects stay\n * idempotent) and focusability for the keyboard path (`tabindex=\"0\"` only when\n * the author supplied none — prefer a real `<button>` handle). Both additions\n * are marker-owned so `#restoreHandle` reverts them symmetrically on teardown.\n */\n #prepareHandle(handle: HTMLElement): void {\n if (\n handle.style.touchAction === \"\" ||\n handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)\n ) {\n handle.style.touchAction = this.#touchActionForAxis();\n handle.setAttribute(PointerDragController.#TOUCH_ACTION_MARKER, \"true\");\n }\n if (handle.tabIndex < 0 && !handle.hasAttribute(\"tabindex\")) {\n handle.setAttribute(\"tabindex\", \"0\");\n handle.setAttribute(PointerDragController.#TABINDEX_MARKER, \"true\");\n }\n }\n\n /** Reverts the marker-owned touch-action + tabindex (authored values untouched). */\n #restoreHandle(handle: HTMLElement): void {\n if (handle.hasAttribute(PointerDragController.#TOUCH_ACTION_MARKER)) {\n handle.style.touchAction = \"\";\n handle.removeAttribute(PointerDragController.#TOUCH_ACTION_MARKER);\n }\n if (handle.hasAttribute(PointerDragController.#TABINDEX_MARKER)) {\n handle.removeAttribute(PointerDragController.#TABINDEX_MARKER);\n // Only remove what is verifiably still ours and safe to remove: another\n // owner (e.g. a roving list) may have rewritten the value since, and\n // stripping tabindex off the focused element would blur it to <body>\n // (losing the user's place — worse than leaking one tab stop).\n if (handle.getAttribute(\"tabindex\") === \"0\" && document.activeElement !== handle) {\n handle.removeAttribute(\"tabindex\");\n }\n }\n }\n}\n"]}
@@ -42,8 +42,8 @@ import { Controller } from '@hotwired/stimulus';
42
42
  * A `focusout` with no destination is ignored because it also occurs for clicks
43
43
  * on non-focusable content and when the browser window loses focus.
44
44
  * - Opt-in **dismiss on scroll** (`closeOnScroll`): while open, scrolling a tracked
45
- * scroll-parent ancestor (or the window) closes the panel — the Radix / floating-ui
46
- * convention. Closes without restoring focus (like the modeless `focusout` path)
45
+ * scroll-parent ancestor (or the window) closes the panel, the usual convention for
46
+ * anchored popups. Closes without restoring focus (like the modeless `focusout` path)
47
47
  * so the close never fights the user's scroll. Off by default.
48
48
  */
49
49
  declare class PopoverController extends Controller<HTMLElement> {
@@ -92,6 +92,81 @@ var EscapeLayer = class _EscapeLayer {
92
92
  }
93
93
  };
94
94
 
95
+ // src/utils/focus_candidate.ts
96
+ function inheritsFieldsetDisabled(control) {
97
+ let fieldset = control.closest("fieldset[disabled]");
98
+ while (fieldset) {
99
+ const legend = Array.from(fieldset.children).find((child) => child.tagName === "LEGEND");
100
+ if (!legend?.contains(control)) return true;
101
+ fieldset = fieldset.parentElement?.closest("fieldset[disabled]") ?? null;
102
+ }
103
+ return false;
104
+ }
105
+ function canTakeFocus(element) {
106
+ if (element.closest("[hidden], [inert]")) return false;
107
+ if (element instanceof HTMLInputElement && element.type === "hidden") return false;
108
+ if (!("disabled" in element)) return true;
109
+ if (element.disabled) return false;
110
+ return !inheritsFieldsetDisabled(element);
111
+ }
112
+ var TAB_STOP_CANDIDATE_SELECTOR = [
113
+ "a[href]",
114
+ "area[href]",
115
+ "button",
116
+ "input",
117
+ "select",
118
+ "textarea",
119
+ "summary",
120
+ "iframe",
121
+ "audio[controls]",
122
+ "video[controls]",
123
+ "[tabindex]",
124
+ "[contenteditable]"
125
+ ].join(",");
126
+ function isRenderedForFocus(element) {
127
+ const check = element.checkVisibility;
128
+ return typeof check === "function" ? check.call(element, { visibilityProperty: true }) : true;
129
+ }
130
+ function authoredTabindex(element) {
131
+ const value = element.getAttribute("tabindex");
132
+ if (value === null || !/^[+-]?\d+$/.test(value.trim())) return null;
133
+ return Number(value);
134
+ }
135
+ function hasNativeTabStop(element) {
136
+ if (element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement) {
137
+ return element.hasAttribute("href");
138
+ }
139
+ if (element instanceof HTMLButtonElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
140
+ return true;
141
+ }
142
+ if (element instanceof HTMLInputElement) return element.type !== "hidden";
143
+ if (element instanceof HTMLIFrameElement) return true;
144
+ if (element.tagName === "AUDIO" || element.tagName === "VIDEO") {
145
+ return element.hasAttribute("controls");
146
+ }
147
+ if (element instanceof HTMLElement && element.tagName === "SUMMARY") {
148
+ const details = element.parentElement;
149
+ return details instanceof HTMLDetailsElement && Array.from(details.children).find((child) => child.tagName === "SUMMARY") === element;
150
+ }
151
+ return false;
152
+ }
153
+ function hasEditableTabStop(element) {
154
+ const value = element.getAttribute("contenteditable")?.toLowerCase();
155
+ return value === "" || value === "true" || value === "plaintext-only";
156
+ }
157
+ function isTabStop(element) {
158
+ if (!canTakeFocus(element) || !isRenderedForFocus(element)) return false;
159
+ const tabindex = authoredTabindex(element);
160
+ if (tabindex !== null) return tabindex >= 0;
161
+ return hasNativeTabStop(element) || hasEditableTabStop(element);
162
+ }
163
+ function firstTabStop(root) {
164
+ for (const candidate of root.querySelectorAll(TAB_STOP_CANDIDATE_SELECTOR)) {
165
+ if (isTabStop(candidate)) return candidate;
166
+ }
167
+ return null;
168
+ }
169
+
95
170
  // src/utils/scroll_dismiss.ts
96
171
  function observeScrollDismiss(element, onScroll) {
97
172
  const targets = [...scrollParents(element), window];
@@ -118,7 +193,7 @@ function isScrollable(element) {
118
193
  }
119
194
 
120
195
  // src/controllers/popover_controller.ts
121
- var PopoverController = class _PopoverController extends Controller {
196
+ var PopoverController = class extends Controller {
122
197
  static targets = ["trigger", "panel"];
123
198
  static values = {
124
199
  closeOnScroll: { type: Boolean, default: false }
@@ -128,8 +203,6 @@ var PopoverController = class _PopoverController extends Controller {
128
203
  #stopScrollDismiss = null;
129
204
  /** Escape-stack membership while open; the shared resolver dismisses via it. */
130
205
  #escapeLayer = new EscapeLayer();
131
- /** Selector for natively focusable elements used to find the first one. */
132
- static #FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
133
206
  /** Starts closed and registers the standing dismissal listeners. */
134
207
  connect() {
135
208
  this.close();
@@ -181,7 +254,7 @@ var PopoverController = class _PopoverController extends Controller {
181
254
  }
182
255
  /** Moves focus to the first focusable element in the panel, or the panel itself. */
183
256
  #focusFirst() {
184
- const first = this.panelTarget.querySelector(_PopoverController.#FOCUSABLE);
257
+ const first = firstTabStop(this.panelTarget);
185
258
  if (first) {
186
259
  first.focus();
187
260
  return;