stimeo-ui 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +106 -1
- package/README.md +19 -10
- package/dist/cable/index.d.ts +9 -8
- package/dist/cable/index.js +22 -14
- package/dist/cable/index.js.map +1 -1
- package/dist/coerce-JtZVNG5h.d.ts +20 -0
- package/dist/controllers/accordion_controller.js +14 -6
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.js +145 -118
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +5 -3
- package/dist/controllers/announcer_controller.js +113 -17
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.js +185 -60
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +55 -43
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +3 -3
- package/dist/controllers/bulk_select_controller.js +64 -50
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +15 -12
- package/dist/controllers/calendar_controller.js +55 -53
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +9 -5
- package/dist/controllers/carousel_controller.js +124 -86
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +27 -6
- package/dist/controllers/character_counter_controller.js +291 -95
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.js +6 -0
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.js +118 -27
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +1 -1
- package/dist/controllers/collapsible_controller.js +52 -49
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.js +29 -7
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +1 -1
- package/dist/controllers/combobox_controller.js +210 -71
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +9 -9
- package/dist/controllers/command_palette_controller.js +289 -140
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +145 -58
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js +145 -118
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +49 -14
- package/dist/controllers/context_menu_controller.js +138 -73
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +2 -2
- package/dist/controllers/countdown_controller.js +109 -21
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +75 -11
- package/dist/controllers/currency_input_controller.js +446 -72
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +10 -8
- package/dist/controllers/data_grid_controller.js +134 -28
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +12 -9
- package/dist/controllers/date_range_picker_controller.js +93 -72
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +4 -5
- package/dist/controllers/dialog_controller.js +145 -118
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +1 -1
- package/dist/controllers/direct_upload_controller.js +24 -14
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +6 -1
- package/dist/controllers/dirty_form_controller.js +68 -31
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.js +64 -46
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +145 -118
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.js +76 -63
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.js +199 -62
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +13 -6
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +3 -0
- package/dist/controllers/file_dropzone_controller.js +55 -48
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +9 -2
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -4
- package/dist/controllers/flash_controller.js +66 -33
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +3 -3
- package/dist/controllers/focus_controller.js +39 -14
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +52 -43
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +3 -3
- package/dist/controllers/form_validation_controller.js +18 -11
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +13 -12
- package/dist/controllers/frame_loading_controller.js +55 -48
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +5 -4
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +4 -1
- package/dist/controllers/hover_card_controller.js +67 -61
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +6 -5
- package/dist/controllers/idle_controller.js +10 -6
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +30 -13
- package/dist/controllers/input_mask_controller.js +411 -24
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +3 -0
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +3 -2
- package/dist/controllers/lazy_frame_controller.js +15 -3
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +10 -7
- package/dist/controllers/listbox_controller.js +260 -79
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +8 -6
- package/dist/controllers/local_time_controller.js +48 -13
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.js +11 -4
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +24 -5
- package/dist/controllers/menu_controller.js +138 -82
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +49 -7
- package/dist/controllers/menubar_controller.js +65 -19
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +13 -20
- package/dist/controllers/meter_controller.js +270 -24
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +17 -15
- package/dist/controllers/multi_select_controller.js +251 -94
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +19 -5
- package/dist/controllers/navigation_menu_controller.js +67 -19
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.js +98 -64
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.js +70 -52
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +24 -6
- package/dist/controllers/number_input_controller.js +344 -94
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.d.ts +8 -5
- package/dist/controllers/optimistic_controller.js +29 -11
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +35 -4
- package/dist/controllers/otp_controller.js +502 -104
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.js +6 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +10 -9
- package/dist/controllers/overflow_menu_controller.js +136 -21
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +3 -2
- package/dist/controllers/pagination_controller.js +47 -14
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +5 -4
- package/dist/controllers/password_reveal_controller.js +52 -49
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +1 -1
- package/dist/controllers/password_strength_controller.js +13 -6
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.js +48 -128
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -8
- package/dist/controllers/pointer_drag_controller.js +249 -23
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +3 -3
- package/dist/controllers/popover_controller.js +82 -65
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.js +6 -0
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.js +14 -3
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +9 -19
- package/dist/controllers/progress_controller.js +271 -27
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +11 -8
- package/dist/controllers/radio_group_controller.js +167 -80
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +4 -3
- package/dist/controllers/range_slider_controller.js +27 -7
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.js +93 -57
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.js +10 -6
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.js +22 -5
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +4 -4
- package/dist/controllers/relative_time_controller.js +75 -11
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +3 -3
- package/dist/controllers/reset_on_restore_controller.js +6 -0
- package/dist/controllers/reset_on_restore_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +3 -0
- package/dist/controllers/resizable_controller.js +55 -22
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +3 -3
- package/dist/controllers/roving_controller.js +10 -4
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.js +129 -88
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +3 -3
- package/dist/controllers/scroll_restore_controller.js +14 -2
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +93 -60
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.js +11 -5
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +1 -1
- package/dist/controllers/separator_controller.js +59 -43
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +1 -1
- package/dist/controllers/sidebar_controller.js +150 -122
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.js +55 -48
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -1
- package/dist/controllers/slider_controller.js +27 -6
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +13 -5
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.d.ts +4 -4
- package/dist/controllers/sortable_controller.js +27 -14
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.js +55 -48
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +4 -4
- package/dist/controllers/step_indicator_controller.js +6 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -3
- package/dist/controllers/stepper_controller.js +6 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +3 -1
- package/dist/controllers/stick_to_bottom_controller.js +19 -8
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.js +49 -43
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +2 -1
- package/dist/controllers/submit_once_controller.js +145 -85
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +7 -4
- package/dist/controllers/switch_controller.js +172 -28
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +7 -4
- package/dist/controllers/tabs_controller.js +6 -1
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +4 -3
- package/dist/controllers/tags_input_controller.js +157 -28
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +1 -1
- package/dist/controllers/textarea_autosize_controller.js +40 -19
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +3 -2
- package/dist/controllers/theme_controller.js +10 -4
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +38 -5
- package/dist/controllers/time_picker_controller.js +192 -54
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -5
- package/dist/controllers/toast_controller.js +58 -27
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +2 -3
- package/dist/controllers/toggle_group_controller.js +158 -56
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.d.ts +8 -6
- package/dist/controllers/toolbar_controller.js +150 -21
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +4 -1
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +1 -1
- package/dist/controllers/tree_view_controller.js +134 -25
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2451 -1312
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +15 -11
- package/dist/inspector/cli.js +16 -3
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +53 -8
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +13 -13
- package/dist/inspector/manifest.json +19 -5
- package/dist/positioning/index.d.ts +7 -7
- package/dist/positioning/index.js +6 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +10 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/declared_value.ts","../../src/utils/detach_gate.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/shared_registry.ts","../../src/controllers/portal_controller.ts"],"names":[],"mappings":"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","file":"portal_controller.js","sourcesContent":["/**\n * Readers for the String Values whose text can only be checked by handing it to\n * a parser: a CSS selector, a regular-expression source, a JSON object.\n *\n * Stimulus reads each of them as an ordinary string, so the controller connects\n * and the flaw surfaces later, inside the first handler that consumes the value\n * (a `SyntaxError` from `new RegExp`, a `DOMException` from the selector engine),\n * where it takes the whole handler down on every event. Parsing here, once, in\n * the `<name>ValueChanged` callback keeps the failure local to the declaration:\n * an unreadable value falls back to its default, the element stays alive, and\n * the hot path only ever receives a value that parsed.\n *\n * Which default a broken declaration falls back to is the caller's contract, so\n * every reader takes (or returns) the fallback rather than choosing one.\n */\n\n/**\n * The result of `parse(raw)`, or `fallback` when `parse` throws.\n *\n * Any exception counts as \"does not parse\": the parsers these Values feed\n * (`RegExp`, `JSON.parse`, the selector engine) all report a malformed input by\n * throwing, and none of them throws for another reason.\n */\nexport function parseDeclared<T>(raw: string, parse: (raw: string) => T, fallback: T): T {\n try {\n return parse(raw);\n } catch {\n return fallback;\n }\n}\n\n/** What {@link validSelector} needs of the element it probes with. */\nexport interface SelectorProbe {\n matches(selector: string): boolean;\n}\n\n/**\n * `raw` when it is a non-empty selector the DOM accepts, otherwise `fallback`.\n *\n * `element.matches` is the probe: the engine parses the selector and throws for\n * one it cannot read. Whether the selector actually matches `element` plays no\n * part, so a selector aimed at another element still passes.\n *\n * The parameter names the one method the probe uses rather than `Element`, so\n * this module carries no DOM type and the readers below stay importable from\n * the Node-side Inspector, which checks the same declarations statically.\n */\nexport function validSelector(element: SelectorProbe, raw: string, fallback: string): string {\n if (raw.length === 0) return fallback;\n return parseDeclared(\n raw,\n (selector) => {\n element.matches(selector);\n return selector;\n },\n fallback,\n );\n}\n\n/** How `compileRegExp` wraps a source before compiling it. */\nexport type RegExpAnchor = \"none\" | \"exact\";\n\n/**\n * `source` compiled as a `RegExp`, or `null` when it does not compile.\n *\n * `\"exact\"` wraps the source as `^(?:source)$`, so the whole input has to match\n * and an alternation inside the source cannot escape the anchors. The source is\n * compiled on its own first, because an unbalanced source can be made to parse\n * by the wrapper's own parentheses — `0)|(1` becomes `^(?:0)|(1)$` — which would\n * accept a declaration that is not a regular expression and leave half of it\n * outside the anchors. The default that replaces a broken source is the\n * caller's, so `null` is returned rather than a fallback pattern.\n */\nexport function compileRegExp(source: string, anchor: RegExpAnchor = \"none\"): RegExp | null {\n return parseDeclared(\n source,\n (text) => {\n const bare = new RegExp(text);\n return anchor === \"exact\" ? new RegExp(`^(?:${text})$`) : bare;\n },\n null,\n );\n}\n\n/**\n * The JSON object `raw` declares, or `null` when the text does not parse or\n * parses to something other than a plain object (`null`, an array, a scalar).\n * Values are returned as parsed; narrowing them is the caller's contract.\n */\nexport function parseJsonObject(raw: string): Record<string, unknown> | null {\n const parsed = parseDeclared<unknown>(raw, (text) => JSON.parse(text), null);\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n return parsed as Record<string, unknown>;\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 * An element out of the document is a real detach, with one exception: a\n * `data-turbo-permanent` element, or one inside it, that Turbo carries to the\n * next page. Turbo takes it out of the old `<body>`, which Stimulus answers with\n * a `disconnect()` while it is out of the document, and puts it into the new one\n * a few microtasks later in the same task, where the same instance connects\n * again. Such a disconnect is probed until the next task instead.\n *\n * Policy stays with the consumer; 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 * dismiss(): void {\n * this.#gate.cancel(); // a direct path: disarm a probe a disconnect queued\n * this.#teardown();\n * }\n *\n * #teardown(): void {\n * // … (`disconnected` has already disarmed the probe on its own path)\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 return !host.element.isConnected || !DetachGate.#listed(host);\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. An\n * element Turbo carries to the next page is probed until the next task, by\n * which time Turbo has put it back.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n const carried = DetachGate.#carried(host);\n if (!carried && 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 const probe = (): void => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n };\n if (carried) setTimeout(probe, 0);\n else queueMicrotask(probe);\n }\n\n /**\n * Whether `host` is out of the document inside a `data-turbo-permanent` element, its\n * identifier still listed: where Turbo holds an element it carries to the next page.\n */\n static #carried(host: DetachGateHost): boolean {\n const element = host.element;\n return (\n !element.isConnected &&\n element.closest(\"[data-turbo-permanent]\") !== null &&\n DetachGate.#listed(host)\n );\n }\n\n /** Whether the element's `data-controller` still lists the identifier. */\n static #listed(host: DetachGateHost): boolean {\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return tokens.includes(host.identifier);\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","/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { validSelector } from \"../utils/declared_value\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { sharedRegistry } from \"../utils/shared_registry\";\n\n/** Where the moved node sits inside its destination. */\ntype PortalPosition = \"append\" | \"prepend\";\n\n/** One live teleport: what moved, where it came from, where it went, and who owns it. */\ninterface PortalState {\n node: HTMLElement;\n placeholder: Comment;\n owner: PortalController;\n /** The destination element the node was last placed in. */\n destination: Element;\n /** The end of that destination the node was placed at. */\n position: PortalPosition;\n /**\n * The `content` targets the element held when this teleport began, the node included.\n * None of them counts as arriving, so a second authored target never replaces the node.\n */\n held: ReadonlySet<Element>;\n}\n\n/**\n * Teleport bookkeeping keyed by the controller element (stable across the\n * connect/disconnect churn some DOM runtimes emit when an observed element is moved).\n * Holds the moved node, its placeholder, where it was placed, and the owner — the\n * instance that connected last. Only the owner finishes a probe-driven teardown, so an\n * instance the element has already replaced cannot rewind a teleport the live one holds.\n * The no-`content` form needs no owner check: it restores only on a definite detach,\n * where no successor comes.\n */\nconst portalState = sharedRegistry(\n \"stimeo-ui.portal-state.registry.v1\",\n () => new WeakMap<Element, PortalState>(),\n);\n\n/**\n * Controller elements that have held a `content` target, keyed by the element for the\n * same reason as the teleport bookkeeping. Such an element keeps the `content` form: while\n * it holds no content target it teleports nothing, rather than taking itself for the node.\n */\nconst contentForms = sharedRegistry(\n \"stimeo-ui.portal-content-forms.registry.v1\",\n () => new WeakSet<Element>(),\n);\n\n/**\n * Nodes a replacement retired: their teleport ended so that an arriving target could take\n * their place. One that `restore` sent back into the source reports itself as a target\n * again, yet it neither replaces the node that replaced it nor is teleported again, so a\n * replacement never goes back and forth. A node the page took out of the document is not\n * one, even when a target arrives in the same pass: put back, it is teleported again.\n */\nconst displaced = sharedRegistry(\n \"stimeo-ui.portal-displaced.registry.v1\",\n () => new WeakSet<Element>(),\n);\n\n/**\n * Whether the page took the moved node out of the document itself: the node is not\n * connected, and it is outside the destination it was placed in. A node whose\n * destination left the document with the node still inside is not one; it follows the\n * declaration to the destination that matches now.\n */\nfunction takenByPage(state: PortalState): boolean {\n return !state.node.isConnected && !state.destination.contains(state.node);\n}\n\n/** `ParentNode.moveBefore` (Chromium 133+): relocates a node without removing it first. */\ninterface MovableParent {\n moveBefore?: (node: Node, child: Node | null) => void;\n}\n\n/**\n * Inserts `node` into `parent` before `before`, preferring `moveBefore`. A plain\n * insertion removes the node first, and the browser then drops the focus a descendant\n * held to `<body>`; `moveBefore` keeps it, along with the rest of the node's state.\n */\nfunction relocate(parent: ParentNode, node: Node, before: Node | null): void {\n const move = (parent as ParentNode & MovableParent).moveBefore;\n if (typeof move === \"function\") {\n try {\n move.call(parent, node, before);\n return;\n } catch {\n // Not movable in place (another root, a detached tree): insert instead.\n }\n }\n parent.insertBefore(node, before);\n}\n\n/**\n * Headless **portal / teleport**: moves an element to another place in the DOM (e.g.\n * directly under `body`) on connect and cleans up on disconnect — the shared substrate\n * for overlays that must escape an ancestor's `overflow: hidden`, `transform`, or\n * stacking context (no APG pattern; a DOM utility).\n *\n * Markup contract (identifier: `stimeo--portal`):\n * <div data-controller=\"stimeo--portal\" data-stimeo--portal-to-value=\"body\">\n * <div data-stimeo--portal-target=\"content\">Teleported content</div>\n * </div>\n *\n * Moves `content` (or `this.element` when no `content` target) into the first element\n * matching `to` (default `body`), `append`ed or `prepend`ed per `position`. A comment\n * placeholder records the original spot so `disconnect()` can return the node there\n * (when `restore`) — or remove it — leaving no orphan behind. The moved node carries\n * `data-portaled`, which every placement pass aligns with where the node is: `true` while\n * it sits in its destination. A move the portal is not told about leaves the attribute as\n * it was until the next pass. A `to` the engine cannot read — malformed or empty — falls\n * back to the default; a well-formed selector matching nothing moves nothing.\n *\n * `to` and `position` are followed while connected: a new declaration moves the node\n * into the newly matched destination, or to the other end of the same one, and the\n * placeholder recorded by the first move stays where it is. A declaration that names\n * no destination the node can take — nothing matches, or it is the node itself or one\n * of its descendants — puts the node back before its placeholder, whatever `restore`\n * says. Changes that arrive together are applied as one move, and a Turbo morph of the\n * element re-applies the declaration (restoring `data-portaled` on a node that stays put).\n * A pass also moves a teleported node the page put somewhere outside its destination —\n * back into the source, say — into the destination again, keeping the recorded\n * placeholder, or recording the node's spot when that placeholder is gone. The moves\n * prefer `moveBefore`, so a focused control inside the node keeps focus where the engine\n * supports it.\n *\n * A `content` target that arrives while connected is placed by the same pass. When a\n * node is teleported already, the arriving one replaces it: the replaced node's teleport\n * ends the way `disconnect()` ends one, going back before its placeholder when `restore`\n * is on and the placeholder is still there, or being removed, and `unmount` is reported\n * before the new node's `mount`.\n *\n * A node the page removed from the document is never put back, neither by a placement\n * nor by `disconnect()`: the teleport ends instead, dropping the placeholder and\n * reporting `unmount`, and `content` that arrives with or after that ending is placed in\n * the same pass. An element that has held a `content` target never teleports itself while\n * it holds none, and a no-`content` teleport keeps the element whatever targets appear\n * inside it.\n *\n * `mount` dispatches `{ destination }` each time the node is placed in a destination;\n * `unmount` dispatches `{}` when it goes back to its placeholder or is removed.\n *\n * @remarks\n * Behavior only — no positioning (pair with `stimeo-ui/positioning`) and no focus\n * trapping (pair with `stimeo--focus` or the overlay). Moving a Stimulus element within\n * the same document re-fires connect/disconnect; `DetachGate` reads that pair as an\n * in-page move rather than a detach, which is what keeps the move safe. For Turbo\n * compatibility prefer the `content`-target form: the controller then stays on the\n * in-place source, so its `disconnect()` fires when the original container is replaced\n * and the teleported node is restored/removed rather than orphaned under `body`. The\n * move is idempotent (guarded by the element-keyed bookkeeping) and reversed on\n * `disconnect()` (Turbo\n * navigation included). The \"in-page move vs real detach\" split on `disconnect()` is\n * `DetachGate`; in the `content` form the source element may even leave a\n * scoped application's observed root and the content is still restored, while the\n * no-`content` form teleporting itself out of the observed root is fire-and-forget\n * by design (see `disconnect()`).\n */\nexport class PortalController extends Controller<HTMLElement> {\n static override targets = [\"content\"];\n static override values = {\n to: { type: String, default: \"body\" },\n position: { type: String, default: \"append\" },\n restore: { type: Boolean, default: true },\n };\n static events = [\"mount\", \"unmount\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly contentTargets: HTMLElement[];\n declare readonly hasContentTarget: boolean;\n\n declare toValue: string;\n declare positionValue: string;\n declare restoreValue: boolean;\n\n /** Decides whether a `disconnect()` is an in-page move or a real detach. */\n readonly #gate = new DetachGate();\n\n /** The `to` declaration after validation; the default when it cannot be parsed. */\n #toSelector = \"body\";\n\n /** Whether the controller is between `connect()` and `disconnect()`. */\n #connected = false;\n\n /**\n * Re-places the node once per batch of `to` / `position` changes, arriving `content`\n * targets and element morphs. Its window opens in `connect()` and closes in\n * `disconnect()`, so a callback Stimulus delivers ahead of `connect()` or during teardown\n * moves nothing.\n */\n readonly #replace = new MorphRenderWatcher(() => this.#place());\n\n /**\n * Validates the destination declaration once, keeping only a selector the engine can\n * read, and re-places the node.\n *\n * A selector reads back as an ordinary string, so a malformed one survives until it is\n * handed to the DOM — where it would take the whole teleport down and leave the part\n * silently doing nothing. Falling back to the default puts the node somewhere visible\n * instead, which reads as a mistake and can be traced back to the declaration.\n */\n toValueChanged(): void {\n this.#toSelector = validSelector(this.element, this.toValue, \"body\");\n this.#replace.schedule();\n }\n\n /** Re-places the node at the end of its destination that `position` now names. */\n positionValueChanged(): void {\n this.#replace.schedule();\n }\n\n /**\n * Places a `content` target that arrives while connected, in the pass the other changes\n * of its batch share; it replaces a node teleported already. Stimulus reports the targets\n * it finds ahead of `connect()`, which places them itself, and a node the teleport sends\n * home reports itself too; the pass finds such a node where the declaration puts it, or\n * retired by the node that replaced it, and moves nothing.\n */\n contentTargetConnected(): void {\n this.#replace.schedule();\n }\n\n override connect(): void {\n // A reconnect proves an in-page move of the source element: disarm the\n // probe the mid-move disconnect() deferred (see disconnect).\n this.#gate.cancel();\n // A later instance for the same element takes ownership, so the teardown belongs\n // to whichever one is still connected.\n const existing = portalState.get(this.element);\n if (existing) existing.owner = this;\n this.#connected = true;\n // Places the node on a first connect; on a reconnect it only moves the node when\n // the declaration changed while the controller was disconnected.\n this.#place();\n this.#replace.observe(this.element);\n // An `unmount` listener may have torn the controller down during the placement; the\n // window it closed then stays closed.\n if (!this.#connected) this.#replace.disconnect();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#replace.disconnect();\n const state = portalState.get(this.element);\n if (!state) return;\n if (state.node === this.element) {\n // No-`content` form: the teleported node IS the controller element, so the\n // teleport itself may exit a scoped application's observed root — the controller\n // doing its job, not a detach. A probe-driven restore would re-enter the root,\n // reconnect, re-teleport and disconnect again, forever. So an ambiguous\n // disconnect (in the document, identifier still listed — also the churn a\n // self-move emits in some runtimes) KEEPS the teleport, and only a definite\n // detach — the element left the DOM, or `data-controller` no longer lists us\n // (a Turbo 8 morph) — restores. The cost, by design: a teleport that left the\n // observed root is fire-and-forget (Stimulus never fires for it again).\n if (!DetachGate.isDetached(this)) return;\n this.#restore(state);\n return;\n }\n // `content` form: the controller element stays put, so its own teleport emits no\n // churn — an ambiguous disconnect means the SOURCE element moved. In-page, the\n // same-batch reconnect cancels the probe and the teleport survives; out of the\n // observed root, no reconnect comes and the probe restores, so the content is\n // never stranded at the destination with a dead owner.\n this.#gate.disconnected(this, () => {\n const current = portalState.get(this.element);\n if (current?.owner === this) this.#restore(current);\n });\n }\n\n /**\n * Puts the node where `to` and `position` declare now.\n *\n * Without a teleport yet, a destination the node can take gets it (with a fresh\n * placeholder). With one, the node moves when the resolved destination element or the\n * position differs from where it was placed, or when the node sits outside its\n * destination — a re-delivered declaration only puts `data-portaled` back — and goes\n * back before its placeholder when the declaration names no destination the node can\n * take.\n *\n * A teleport ends first when the page took its node out of the document, or when another\n * `content` target arrived to replace it; the arriving target, or whatever `content` the\n * element holds by then, is placed in the same pass. An `unmount` listener that tears\n * the controller down leaves the rest of the pass undone, and one that takes the arriving\n * target out of the element leaves it where the listener put it.\n *\n * @stimeoRenderRoot\n */\n #place(): void {\n let state = portalState.get(this.element);\n let node = state?.node ?? null;\n if (state) {\n const arrival = this.#arrival(state);\n const taken = takenByPage(state);\n if (arrival || taken) {\n if (arrival && !taken) displaced.add(state.node);\n this.#restore(state);\n if (!this.#connected) return;\n state = undefined;\n // A listener may have taken the arriving target away again.\n node = arrival !== null && this.element.contains(arrival) ? arrival : null;\n }\n }\n node ??= this.#source();\n if (!node) return;\n const destination = this.#destination();\n if (!destination || destination === node || node.contains(destination)) {\n if (state) this.#unmount(state, true);\n return;\n }\n const position: PortalPosition = this.positionValue === \"prepend\" ? \"prepend\" : \"append\";\n const placed = this.#placedIn(node, destination);\n if (state?.destination === destination && state.position === position && placed) {\n node.setAttribute(\"data-portaled\", \"true\");\n return;\n }\n\n if (state) {\n // A node the page moved out of its destination, whose recorded spot is gone, takes the\n // spot it occupies now as home.\n if (!state.placeholder.isConnected && !this.#placedIn(node, state.destination)) {\n state.placeholder = this.#markHome(node);\n }\n state.destination = destination;\n state.position = position;\n } else {\n const placeholder = this.#markHome(node);\n const held = new Set<Element>(this.contentTargets);\n portalState.set(this.element, {\n node,\n placeholder,\n owner: this,\n destination,\n position,\n held,\n });\n }\n relocate(destination, node, position === \"prepend\" ? destination.firstChild : null);\n node.setAttribute(\"data-portaled\", \"true\");\n this.dispatch(\"mount\", { detail: { destination } });\n }\n\n /**\n * Whether `node` sits in `destination`: inside it, at any depth, and — in the `content`\n * form — not back inside the source, which a destination such as `body` may contain. A\n * destination inside the source holds the node inside the source too.\n */\n #placedIn(node: HTMLElement, destination: Element): boolean {\n if (!destination.contains(node)) return false;\n return (\n node === this.element || !this.element.contains(node) || this.element.contains(destination)\n );\n }\n\n /** Leaves a placeholder at the spot `node` occupies now, and returns it. */\n #markHome(node: HTMLElement): Comment {\n const placeholder = document.createComment(\"stimeo--portal\");\n node.parentNode?.insertBefore(placeholder, node);\n return placeholder;\n }\n\n /**\n * A `content` target in the element that arrived to replace the node teleported now: one\n * outside that node (which may carry targets of its own), not held when this teleport\n * began, and not displaced before. A no-`content` teleport has none: the element itself is the\n * node, and every target of the element lies inside it.\n */\n #arrival(state: PortalState): HTMLElement | null {\n const { node, held } = state;\n return (\n this.contentTargets.find(\n (target) => !node.contains(target) && !held.has(target) && !displaced.has(target),\n ) ?? null\n );\n }\n\n /**\n * The node a new teleport takes: the first `content` target a replacement has not retired,\n * or the element itself when it has never held one. An element that has held a content\n * target and holds none now takes nothing, and neither does one whose targets were all\n * retired: a node that another target replaced is not teleported again, which leaves\n * the page to render new content.\n */\n #source(): HTMLElement | null {\n if (this.hasContentTarget) {\n contentForms.add(this.element);\n return this.contentTargets.find((target) => !displaced.has(target)) ?? null;\n }\n return contentForms.has(this.element) ? null : this.element;\n }\n\n /**\n * Ends the teleport on disconnect, or when another node replaces it: the node goes back\n * when `restore` is on, unless the page took it out of the document, where it stays.\n *\n * @stimeoRuntimeOnly `restore` decides whether this one unmount puts the node back or drops it.\n */\n #restore(state: PortalState): void {\n this.#unmount(state, this.restoreValue && !takenByPage(state));\n }\n\n /**\n * Clears the bookkeeping first, then puts the node back before its placeholder when\n * `home` and the placeholder is still in a tree — or removes it — and reports `unmount`.\n */\n #unmount(state: PortalState, home: boolean): void {\n portalState.delete(this.element);\n const { node, placeholder } = state;\n\n node.removeAttribute(\"data-portaled\");\n const origin = placeholder.parentNode;\n if (home && origin) {\n relocate(origin, node, placeholder);\n } else {\n node.remove();\n }\n placeholder.remove();\n this.dispatch(\"unmount\", { detail: {} });\n }\n\n /** Resolves the destination from the validated `to` selector. */\n #destination(): Element | null {\n return document.querySelector(this.#toSelector);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/declared_value.ts","../../src/utils/detach_gate.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/shared_registry.ts","../../src/controllers/portal_controller.ts"],"names":[],"mappings":"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","file":"portal_controller.js","sourcesContent":["/**\n * Readers for the String Values whose text can only be checked by handing it to\n * a parser: a CSS selector, a regular-expression source, a JSON object.\n *\n * Stimulus reads each of them as an ordinary string, so the controller connects\n * and the flaw surfaces later, inside the first handler that consumes the value\n * (a `SyntaxError` from `new RegExp`, a `DOMException` from the selector engine),\n * where it takes the whole handler down on every event. Parsing here, once, in\n * the `<name>ValueChanged` callback keeps the failure local to the declaration:\n * an unreadable value falls back to its default, the element stays alive, and\n * the hot path only ever receives a value that parsed.\n *\n * Which default a broken declaration falls back to is the caller's contract, so\n * every reader takes (or returns) the fallback rather than choosing one.\n */\n\n/**\n * The result of `parse(raw)`, or `fallback` when `parse` throws.\n *\n * Any exception counts as \"does not parse\": the parsers these Values feed\n * (`RegExp`, `JSON.parse`, the selector engine) all report a malformed input by\n * throwing, and none of them throws for another reason.\n */\nexport function parseDeclared<T>(raw: string, parse: (raw: string) => T, fallback: T): T {\n try {\n return parse(raw);\n } catch {\n return fallback;\n }\n}\n\n/** What {@link validSelector} needs of the element it probes with. */\nexport interface SelectorProbe {\n matches(selector: string): boolean;\n}\n\n/**\n * `raw` when it is a non-empty selector the DOM accepts, otherwise `fallback`.\n *\n * `element.matches` is the probe: the engine parses the selector and throws for\n * one it cannot read. Whether the selector actually matches `element` plays no\n * part, so a selector aimed at another element still passes.\n *\n * The parameter names the one method the probe uses rather than `Element`, so\n * this module carries no DOM type and the readers below stay importable from\n * the Node-side Inspector, which checks the same declarations statically.\n */\nexport function validSelector(element: SelectorProbe, raw: string, fallback: string): string {\n if (raw.length === 0) return fallback;\n return parseDeclared(\n raw,\n (selector) => {\n element.matches(selector);\n return selector;\n },\n fallback,\n );\n}\n\n/** How `compileRegExp` wraps a source before compiling it. */\nexport type RegExpAnchor = \"none\" | \"exact\";\n\n/**\n * `source` compiled as a `RegExp`, or `null` when it does not compile.\n *\n * `\"exact\"` wraps the source as `^(?:source)$`, so the whole input has to match\n * and an alternation inside the source cannot escape the anchors. The source is\n * compiled on its own first, because an unbalanced source can be made to parse\n * by the wrapper's own parentheses — `0)|(1` becomes `^(?:0)|(1)$` — which would\n * accept a declaration that is not a regular expression and leave half of it\n * outside the anchors. The default that replaces a broken source is the\n * caller's, so `null` is returned rather than a fallback pattern.\n */\nexport function compileRegExp(source: string, anchor: RegExpAnchor = \"none\"): RegExp | null {\n return parseDeclared(\n source,\n (text) => {\n const bare = new RegExp(text);\n return anchor === \"exact\" ? new RegExp(`^(?:${text})$`) : bare;\n },\n null,\n );\n}\n\n/**\n * The JSON object `raw` declares, or `null` when the text does not parse or\n * parses to something other than a plain object (`null`, an array, a scalar).\n * Values are returned as parsed; narrowing them is the caller's contract.\n */\nexport function parseJsonObject(raw: string): Record<string, unknown> | null {\n const parsed = parseDeclared<unknown>(raw, (text) => JSON.parse(text), null);\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n return parsed as Record<string, unknown>;\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 * An element out of the document is a real detach, with one exception: a\n * `data-turbo-permanent` element, or one inside it, that Turbo carries to the\n * next page. Turbo takes it out of the old `<body>`, which Stimulus answers with\n * a `disconnect()` while it is out of the document, and puts it into the new one\n * a few microtasks later in the same task, where the same instance connects\n * again. Such a disconnect is probed until the next task instead.\n *\n * Policy stays with the consumer; 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 * dismiss(): void {\n * this.#gate.cancel(); // a direct path: disarm a probe a disconnect queued\n * this.#teardown();\n * }\n *\n * #teardown(): void {\n * // … (`disconnected` has already disarmed the probe on its own path)\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 return !host.element.isConnected || !DetachGate.#listed(host);\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. An\n * element Turbo carries to the next page is probed until the next task, by\n * which time Turbo has put it back.\n */\n disconnected(host: DetachGateHost, teardown: () => void): void {\n const carried = DetachGate.#carried(host);\n if (!carried && 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 const probe = (): void => {\n if (!this.#pending) return;\n this.#pending = false;\n teardown();\n };\n if (carried) setTimeout(probe, 0);\n else queueMicrotask(probe);\n }\n\n /**\n * Whether `host` is out of the document inside a `data-turbo-permanent` element, its\n * identifier still listed: where Turbo holds an element it carries to the next page.\n */\n static #carried(host: DetachGateHost): boolean {\n const element = host.element;\n return (\n !element.isConnected &&\n element.closest(\"[data-turbo-permanent]\") !== null &&\n DetachGate.#listed(host)\n );\n }\n\n /** Whether the element's `data-controller` still lists the identifier. */\n static #listed(host: DetachGateHost): boolean {\n const tokens = (host.element.getAttribute(\"data-controller\") ?? \"\").split(/\\s+/);\n return tokens.includes(host.identifier);\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","/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Whether a requested pass has yet to run. */\n get pending(): boolean {\n return this.#queued;\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","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Whether a pass requested by a morph or {@link schedule} has yet to run. */\n get pending(): boolean {\n return this.#pass.pending;\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { validSelector } from \"../utils/declared_value\";\nimport { DetachGate } from \"../utils/detach_gate\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { sharedRegistry } from \"../utils/shared_registry\";\n\n/** Where the moved node sits inside its destination. */\ntype PortalPosition = \"append\" | \"prepend\";\n\n/** One live teleport: what moved, where it came from, where it went, and who owns it. */\ninterface PortalState {\n node: HTMLElement;\n placeholder: Comment;\n owner: PortalController;\n /** The destination element the node was last placed in. */\n destination: Element;\n /** The end of that destination the node was placed at. */\n position: PortalPosition;\n /**\n * The `content` targets the element held when this teleport began, the node included.\n * None of them counts as arriving, so a second authored target never replaces the node.\n */\n held: ReadonlySet<Element>;\n}\n\n/**\n * Teleport bookkeeping keyed by the controller element (stable across the\n * connect/disconnect churn some DOM runtimes emit when an observed element is moved).\n * Holds the moved node, its placeholder, where it was placed, and the owner — the\n * instance that connected last. Only the owner finishes a probe-driven teardown, so an\n * instance the element has already replaced cannot rewind a teleport the live one holds.\n * The no-`content` form needs no owner check: it restores only on a definite detach,\n * where no successor comes.\n */\nconst portalState = sharedRegistry(\n \"stimeo-ui.portal-state.registry.v1\",\n () => new WeakMap<Element, PortalState>(),\n);\n\n/**\n * Controller elements that have held a `content` target, keyed by the element for the\n * same reason as the teleport bookkeeping. Such an element keeps the `content` form: while\n * it holds no content target it teleports nothing, rather than taking itself for the node.\n */\nconst contentForms = sharedRegistry(\n \"stimeo-ui.portal-content-forms.registry.v1\",\n () => new WeakSet<Element>(),\n);\n\n/**\n * Nodes a replacement retired: their teleport ended so that an arriving target could take\n * their place. One that `restore` sent back into the source reports itself as a target\n * again, yet it neither replaces the node that replaced it nor is teleported again, so a\n * replacement never goes back and forth. A node the page took out of the document is not\n * one, even when a target arrives in the same pass: put back, it is teleported again.\n */\nconst displaced = sharedRegistry(\n \"stimeo-ui.portal-displaced.registry.v1\",\n () => new WeakSet<Element>(),\n);\n\n/**\n * Whether the page took the moved node out of the document itself: the node is not\n * connected, and it is outside the destination it was placed in. A node whose\n * destination left the document with the node still inside is not one; it follows the\n * declaration to the destination that matches now.\n */\nfunction takenByPage(state: PortalState): boolean {\n return !state.node.isConnected && !state.destination.contains(state.node);\n}\n\n/** `ParentNode.moveBefore` (Chromium 133+): relocates a node without removing it first. */\ninterface MovableParent {\n moveBefore?: (node: Node, child: Node | null) => void;\n}\n\n/**\n * Inserts `node` into `parent` before `before`, preferring `moveBefore`. A plain\n * insertion removes the node first, and the browser then drops the focus a descendant\n * held to `<body>`; `moveBefore` keeps it, along with the rest of the node's state.\n */\nfunction relocate(parent: ParentNode, node: Node, before: Node | null): void {\n const move = (parent as ParentNode & MovableParent).moveBefore;\n if (typeof move === \"function\") {\n try {\n move.call(parent, node, before);\n return;\n } catch {\n // Not movable in place (another root, a detached tree): insert instead.\n }\n }\n parent.insertBefore(node, before);\n}\n\n/**\n * Headless **portal / teleport**: moves an element to another place in the DOM (e.g.\n * directly under `body`) on connect and cleans up on disconnect — the shared substrate\n * for overlays that must escape an ancestor's `overflow: hidden`, `transform`, or\n * stacking context (no APG pattern; a DOM utility).\n *\n * Markup contract (identifier: `stimeo--portal`):\n * <div data-controller=\"stimeo--portal\" data-stimeo--portal-to-value=\"body\">\n * <div data-stimeo--portal-target=\"content\">Teleported content</div>\n * </div>\n *\n * Moves `content` (or `this.element` when no `content` target) into the first element\n * matching `to` (default `body`), `append`ed or `prepend`ed per `position`. A comment\n * placeholder records the original spot so `disconnect()` can return the node there\n * (when `restore`) — or remove it — leaving no orphan behind. The moved node carries\n * `data-portaled`, which every placement pass aligns with where the node is: `true` while\n * it sits in its destination. A move the portal is not told about leaves the attribute as\n * it was until the next pass. A `to` the engine cannot read — malformed or empty — falls\n * back to the default; a well-formed selector matching nothing moves nothing.\n *\n * `to` and `position` are followed while connected: a new declaration moves the node\n * into the newly matched destination, or to the other end of the same one, and the\n * placeholder recorded by the first move stays where it is. A declaration that names\n * no destination the node can take — nothing matches, or it is the node itself or one\n * of its descendants — puts the node back before its placeholder, whatever `restore`\n * says. Changes that arrive together are applied as one move, and a Turbo morph of the\n * element re-applies the declaration (restoring `data-portaled` on a node that stays put).\n * A pass also moves a teleported node the page put somewhere outside its destination —\n * back into the source, say — into the destination again, keeping the recorded\n * placeholder, or recording the node's spot when that placeholder is gone. The moves\n * prefer `moveBefore`, so a focused control inside the node keeps focus where the engine\n * supports it.\n *\n * A `content` target that arrives while connected is placed by the same pass. When a\n * node is teleported already, the arriving one replaces it: the replaced node's teleport\n * ends the way `disconnect()` ends one, going back before its placeholder when `restore`\n * is on and the placeholder is still there, or being removed, and `unmount` is reported\n * before the new node's `mount`.\n *\n * A node the page removed from the document is never put back, neither by a placement\n * nor by `disconnect()`: the teleport ends instead, dropping the placeholder and\n * reporting `unmount`, and `content` that arrives with or after that ending is placed in\n * the same pass. An element that has held a `content` target never teleports itself while\n * it holds none, and a no-`content` teleport keeps the element whatever targets appear\n * inside it.\n *\n * `mount` dispatches `{ destination }` each time the node is placed in a destination;\n * `unmount` dispatches `{}` when it goes back to its placeholder or is removed.\n *\n * @remarks\n * Behavior only — no positioning (pair with `stimeo-ui/positioning`) and no focus\n * trapping (pair with `stimeo--focus` or the overlay). Moving a Stimulus element within\n * the same document re-fires connect/disconnect; `DetachGate` reads that pair as an\n * in-page move rather than a detach, which is what keeps the move safe. For Turbo\n * compatibility prefer the `content`-target form: the controller then stays on the\n * in-place source, so its `disconnect()` fires when the original container is replaced\n * and the teleported node is restored/removed rather than orphaned under `body`. The\n * move is idempotent (guarded by the element-keyed bookkeeping) and reversed on\n * `disconnect()` (Turbo\n * navigation included). The \"in-page move vs real detach\" split on `disconnect()` is\n * `DetachGate`; in the `content` form the source element may even leave a\n * scoped application's observed root and the content is still restored, while the\n * no-`content` form teleporting itself out of the observed root is fire-and-forget\n * by design (see `disconnect()`).\n */\nexport class PortalController extends Controller<HTMLElement> {\n static override targets = [\"content\"];\n static override values = {\n to: { type: String, default: \"body\" },\n position: { type: String, default: \"append\" },\n restore: { type: Boolean, default: true },\n };\n static events = [\"mount\", \"unmount\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly contentTargets: HTMLElement[];\n declare readonly hasContentTarget: boolean;\n\n declare toValue: string;\n declare positionValue: string;\n declare restoreValue: boolean;\n\n /** Decides whether a `disconnect()` is an in-page move or a real detach. */\n readonly #gate = new DetachGate();\n\n /** The `to` declaration after validation; the default when it cannot be parsed. */\n #toSelector = \"body\";\n\n /** Whether the controller is between `connect()` and `disconnect()`. */\n #connected = false;\n\n /**\n * Re-places the node once per batch of `to` / `position` changes, arriving `content`\n * targets and element morphs. Its window opens in `connect()` and closes in\n * `disconnect()`, so a callback Stimulus delivers ahead of `connect()` or during teardown\n * moves nothing.\n */\n readonly #replace = new MorphRenderWatcher(() => this.#place());\n\n /**\n * Validates the destination declaration once, keeping only a selector the engine can\n * read, and re-places the node.\n *\n * A selector reads back as an ordinary string, so a malformed one survives until it is\n * handed to the DOM — where it would take the whole teleport down and leave the part\n * silently doing nothing. Falling back to the default puts the node somewhere visible\n * instead, which reads as a mistake and can be traced back to the declaration.\n */\n toValueChanged(): void {\n this.#toSelector = validSelector(this.element, this.toValue, \"body\");\n this.#replace.schedule();\n }\n\n /** Re-places the node at the end of its destination that `position` now names. */\n positionValueChanged(): void {\n this.#replace.schedule();\n }\n\n /**\n * Places a `content` target that arrives while connected, in the pass the other changes\n * of its batch share; it replaces a node teleported already. Stimulus reports the targets\n * it finds ahead of `connect()`, which places them itself, and a node the teleport sends\n * home reports itself too; the pass finds such a node where the declaration puts it, or\n * retired by the node that replaced it, and moves nothing.\n */\n contentTargetConnected(): void {\n this.#replace.schedule();\n }\n\n override connect(): void {\n // A reconnect proves an in-page move of the source element: disarm the\n // probe the mid-move disconnect() deferred (see disconnect).\n this.#gate.cancel();\n // A later instance for the same element takes ownership, so the teardown belongs\n // to whichever one is still connected.\n const existing = portalState.get(this.element);\n if (existing) existing.owner = this;\n this.#connected = true;\n // Places the node on a first connect; on a reconnect it only moves the node when\n // the declaration changed while the controller was disconnected.\n this.#place();\n this.#replace.observe(this.element);\n // An `unmount` listener may have torn the controller down during the placement; the\n // window it closed then stays closed.\n if (!this.#connected) this.#replace.disconnect();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#replace.disconnect();\n const state = portalState.get(this.element);\n if (!state) return;\n if (state.node === this.element) {\n // No-`content` form: the teleported node IS the controller element, so the\n // teleport itself may exit a scoped application's observed root — the controller\n // doing its job, not a detach. A probe-driven restore would re-enter the root,\n // reconnect, re-teleport and disconnect again, forever. So an ambiguous\n // disconnect (in the document, identifier still listed — also the churn a\n // self-move emits in some runtimes) KEEPS the teleport, and only a definite\n // detach — the element left the DOM, or `data-controller` no longer lists us\n // (a Turbo 8 morph) — restores. The cost, by design: a teleport that left the\n // observed root is fire-and-forget (Stimulus never fires for it again).\n if (!DetachGate.isDetached(this)) return;\n this.#restore(state);\n return;\n }\n // `content` form: the controller element stays put, so its own teleport emits no\n // churn — an ambiguous disconnect means the SOURCE element moved. In-page, the\n // same-batch reconnect cancels the probe and the teleport survives; out of the\n // observed root, no reconnect comes and the probe restores, so the content is\n // never stranded at the destination with a dead owner.\n this.#gate.disconnected(this, () => {\n const current = portalState.get(this.element);\n if (current?.owner === this) this.#restore(current);\n });\n }\n\n /**\n * Puts the node where `to` and `position` declare now.\n *\n * Without a teleport yet, a destination the node can take gets it (with a fresh\n * placeholder). With one, the node moves when the resolved destination element or the\n * position differs from where it was placed, or when the node sits outside its\n * destination — a re-delivered declaration only puts `data-portaled` back — and goes\n * back before its placeholder when the declaration names no destination the node can\n * take.\n *\n * A teleport ends first when the page took its node out of the document, or when another\n * `content` target arrived to replace it; the arriving target, or whatever `content` the\n * element holds by then, is placed in the same pass. An `unmount` listener that tears\n * the controller down leaves the rest of the pass undone, and one that takes the arriving\n * target out of the element leaves it where the listener put it.\n *\n * @stimeoRenderRoot\n */\n #place(): void {\n let state = portalState.get(this.element);\n let node = state?.node ?? null;\n if (state) {\n const arrival = this.#arrival(state);\n const taken = takenByPage(state);\n if (arrival || taken) {\n if (arrival && !taken) displaced.add(state.node);\n this.#restore(state);\n if (!this.#connected) return;\n state = undefined;\n // A listener may have taken the arriving target away again.\n node = arrival !== null && this.element.contains(arrival) ? arrival : null;\n }\n }\n node ??= this.#source();\n if (!node) return;\n const destination = this.#destination();\n if (!destination || destination === node || node.contains(destination)) {\n if (state) this.#unmount(state, true);\n return;\n }\n const position: PortalPosition = this.positionValue === \"prepend\" ? \"prepend\" : \"append\";\n const placed = this.#placedIn(node, destination);\n if (state?.destination === destination && state.position === position && placed) {\n node.setAttribute(\"data-portaled\", \"true\");\n return;\n }\n\n if (state) {\n // A node the page moved out of its destination, whose recorded spot is gone, takes the\n // spot it occupies now as home.\n if (!state.placeholder.isConnected && !this.#placedIn(node, state.destination)) {\n state.placeholder = this.#markHome(node);\n }\n state.destination = destination;\n state.position = position;\n } else {\n const placeholder = this.#markHome(node);\n const held = new Set<Element>(this.contentTargets);\n portalState.set(this.element, {\n node,\n placeholder,\n owner: this,\n destination,\n position,\n held,\n });\n }\n relocate(destination, node, position === \"prepend\" ? destination.firstChild : null);\n node.setAttribute(\"data-portaled\", \"true\");\n this.dispatch(\"mount\", { detail: { destination } });\n }\n\n /**\n * Whether `node` sits in `destination`: inside it, at any depth, and — in the `content`\n * form — not back inside the source, which a destination such as `body` may contain. A\n * destination inside the source holds the node inside the source too.\n */\n #placedIn(node: HTMLElement, destination: Element): boolean {\n if (!destination.contains(node)) return false;\n return (\n node === this.element || !this.element.contains(node) || this.element.contains(destination)\n );\n }\n\n /** Leaves a placeholder at the spot `node` occupies now, and returns it. */\n #markHome(node: HTMLElement): Comment {\n const placeholder = document.createComment(\"stimeo--portal\");\n node.parentNode?.insertBefore(placeholder, node);\n return placeholder;\n }\n\n /**\n * A `content` target in the element that arrived to replace the node teleported now: one\n * outside that node (which may carry targets of its own), not held when this teleport\n * began, and not displaced before. A no-`content` teleport has none: the element itself is the\n * node, and every target of the element lies inside it.\n */\n #arrival(state: PortalState): HTMLElement | null {\n const { node, held } = state;\n return (\n this.contentTargets.find(\n (target) => !node.contains(target) && !held.has(target) && !displaced.has(target),\n ) ?? null\n );\n }\n\n /**\n * The node a new teleport takes: the first `content` target a replacement has not retired,\n * or the element itself when it has never held one. An element that has held a content\n * target and holds none now takes nothing, and neither does one whose targets were all\n * retired: a node that another target replaced is not teleported again, which leaves\n * the page to render new content.\n */\n #source(): HTMLElement | null {\n if (this.hasContentTarget) {\n contentForms.add(this.element);\n return this.contentTargets.find((target) => !displaced.has(target)) ?? null;\n }\n return contentForms.has(this.element) ? null : this.element;\n }\n\n /**\n * Ends the teleport on disconnect, or when another node replaces it: the node goes back\n * when `restore` is on, unless the page took it out of the document, where it stays.\n *\n * @stimeoRuntimeOnly `restore` decides whether this one unmount puts the node back or drops it.\n */\n #restore(state: PortalState): void {\n this.#unmount(state, this.restoreValue && !takenByPage(state));\n }\n\n /**\n * Clears the bookkeeping first, then puts the node back before its placeholder when\n * `home` and the placeholder is still in a tree — or removes it — and reports `unmount`.\n */\n #unmount(state: PortalState, home: boolean): void {\n portalState.delete(this.element);\n const { node, placeholder } = state;\n\n node.removeAttribute(\"data-portaled\");\n const origin = placeholder.parentNode;\n if (home && origin) {\n relocate(origin, node, placeholder);\n } else {\n node.remove();\n }\n placeholder.remove();\n this.dispatch(\"unmount\", { detail: {} });\n }\n\n /** Resolves the destination from the validated `to` selector. */\n #destination(): Element | null {\n return document.querySelector(this.#toSelector);\n }\n}\n"]}
|
|
@@ -27,6 +27,9 @@ var MicrotaskCoalescer = class {
|
|
|
27
27
|
this.#queued = false;
|
|
28
28
|
this.#generation += 1;
|
|
29
29
|
}
|
|
30
|
+
get pending() {
|
|
31
|
+
return this.#queued;
|
|
32
|
+
}
|
|
30
33
|
schedule() {
|
|
31
34
|
if (!this.#active || this.#queued) return;
|
|
32
35
|
this.#queued = true;
|
|
@@ -57,6 +60,9 @@ var MorphRenderWatcher = class {
|
|
|
57
60
|
schedule() {
|
|
58
61
|
this.#pass.schedule();
|
|
59
62
|
}
|
|
63
|
+
get pending() {
|
|
64
|
+
return this.#pass.pending;
|
|
65
|
+
}
|
|
60
66
|
disconnect() {
|
|
61
67
|
this.#listeners.dispose();
|
|
62
68
|
this.#pass.cancel();
|
|
@@ -64,6 +70,13 @@ var MorphRenderWatcher = class {
|
|
|
64
70
|
}
|
|
65
71
|
};
|
|
66
72
|
|
|
73
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
74
|
+
if (element.getAttribute(name) === value) return false;
|
|
75
|
+
if (value === null) element.removeAttribute(name);
|
|
76
|
+
else element.setAttribute(name, value);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
function sharedRegistry(key, create) {
|
|
68
81
|
const symbol = Symbol.for(key);
|
|
69
82
|
const scope = globalThis;
|
|
@@ -203,9 +216,7 @@ var StylePropertyLease = class _StylePropertyLease {
|
|
|
203
216
|
);
|
|
204
217
|
});
|
|
205
218
|
for (const [name, recorded] of records) {
|
|
206
|
-
|
|
207
|
-
if (recorded === null) element.removeAttribute(name);
|
|
208
|
-
else element.setAttribute(name, recorded);
|
|
219
|
+
setAttributeIfChanged(element, name, recorded);
|
|
209
220
|
}
|
|
210
221
|
}
|
|
211
222
|
#carries(element, hold) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/shared_registry.ts","../../src/utils/style_property_lease.ts","../../src/utils/transient_hooks.ts","../../src/controllers/preview_guard_controller.ts"],"names":[],"mappings":"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","file":"preview_guard_controller.js","sourcesContent":["/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { sharedRegistry } from \"./shared_registry\";\n\n/** What a lease's record says: the authored declaration, the last write and the leases beneath. */\ninterface StylePropertyLeaseRecord {\n readonly originalValue: string;\n readonly originalPriority: string;\n readonly writtenValue: string | null;\n readonly writtenPriority: string;\n /** Whether it hands back what the lease right beneath it wrote last. */\n readonly linked: boolean;\n /** The records of the other owners' leases beneath it, nearest first. */\n readonly beneath: readonly string[];\n}\n\n/** One lease's hold on one element's property, in the stack every lease of it shares. */\ninterface StyleHold {\n /** The lease holding it; `null` for a hold read from a copy's record until its lease takes it. */\n lease: object | null;\n readonly record: string;\n writtenValue: string | null;\n writtenPriority: string;\n /** What it hands back with nothing beneath it: the author's declaration. */\n readonly originalValue: string;\n readonly originalPriority: string;\n /** Whether it hands back what the hold beneath it wrote last instead. */\n linked: boolean;\n}\n\n/** The holds on each element by property, and every lease's record name by property. */\ninterface StylePropertyLeaseRegistry {\n readonly stacks: WeakMap<HTMLElement, Map<string, StyleHold[]>>;\n readonly names: Map<string, Set<string>>;\n}\n\n/**\n * Temporarily controls one inline CSS property across a changing set of elements.\n *\n * Authored value and priority are restored only while the declaration still matches\n * the last leased write. A later consumer write therefore wins.\n *\n * **Every lease of a property shares one stack of holds per element**, whatever its owner\n * and whichever file of the package constructed it (the stacks are one per page): the holds\n * sit in the order their leases first wrote the element, each with the declaration it wrote\n * last. The element's declaration is the highest hold's that carries it, and the page's when\n * none does. A write reaches the element unless a hold above the writer carries the element's\n * declaration, so the top hold always shows its writes and the page's declaration gives way to\n * the next write of any hold. A return gives a declaration back only from the hold the\n * element's declaration is: the one the hold beneath it wrote last, or the author's once\n * nothing is beneath it; any other return only leaves the stack. Holds that wrote the same\n * declaration are told apart by the stack: the higher one keeps it. A first write over a top\n * hold whose declaration the page has replaced takes the element's declaration as the\n * author's. Two instances of one controller that write the same property on\n * `document.documentElement` stack the same way.\n *\n * **The stack travels with the element.** While a hold carries a declaration other than the\n * author's, lies on another hold or is covered by one, the element carries its owner's record\n * `data-<owner>-style-<property>-lease`: JSON of the author's value and priority, the value\n * and priority the hold wrote last (`null` for a removal), `false` where the hold hands back\n * the author's declaration rather than the latest one of the hold beneath it, and the records\n * of every hold beneath it, nearest first. Returning the last lease of an owner removes its\n * record. A copy of the element — a page Turbo restores from its cache — therefore carries the\n * stack, read back under the record names of the leases constructed so far and ordered by the\n * records each hold lists beneath it, and each lease takes the hold its owner's record\n * describes over in place. Complete records for distinct owners preserve their order across\n * a copy. Records describe owners, not instances: one owner holding the element more than\n * once leaves only its topmost hold's record, which the first of its leases to write or return\n * on the copy takes. Its lower holds and positions are absent; mutually listed owners cannot\n * express their full live order. A hold that showed the author's declaration alone carries\n * no record and is taken by its first write on the copy. Missing or unreadable records also\n * leave their holds unknown, so copying the DOM does not promise identical decisions for\n * every live stack. A new stack with no readable record takes the current declaration as the\n * author's; a morph can remove records while existing holds remain in memory until returned.\n *\n * The lease has no lifecycle of its own, so it never subscribes to document events.\n */\nexport class StylePropertyLease<T extends HTMLElement = HTMLElement> {\n /** The holds on each element, bottom first, and the record names; one per page. */\n static readonly #registry = sharedRegistry(\n \"stimeo-ui.style-property-lease.registry.v1\",\n (): StylePropertyLeaseRegistry => ({ stacks: new WeakMap(), names: new Map() }),\n );\n readonly #property: string;\n readonly #record: string;\n readonly #held = new Set<T>();\n\n /**\n * @param property - The CSS property whose temporary values this lease owns.\n * @param owner - Names the record on the element; the controller's identifier.\n */\n constructor(property: string, owner: string) {\n this.#property = property;\n this.#record = `data-${owner}-style-${property}-lease`;\n const names = StylePropertyLease.#registry.names;\n names.set(property, (names.get(property) ?? new Set<string>()).add(this.#record));\n }\n\n /** Writes or removes the leased declaration while preserving its authored value. */\n write(element: T, value: string | null, priority = \"\"): void {\n const stack = this.#stack(element);\n const hold = this.#take(stack) ?? this.#acquire(element, stack);\n this.#held.add(element);\n const shows = !this.#covered(element, stack, hold);\n hold.writtenValue = value;\n hold.writtenPriority = value === null ? \"\" : priority;\n this.#mark(element, stack);\n if (shows) this.#show(element, value, hold.writtenPriority);\n }\n\n /**\n * Returns one lease, also on a restored copy, to the declaration the hold beneath it wrote\n * last or the author's, without overwriting a declaration a consumer wrote since.\n */\n return(element: T): void {\n const held = this.#held.delete(element);\n const known = StylePropertyLease.#registry.stacks.get(element)?.has(this.#property);\n const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];\n const hold = this.#take(stack);\n if (!hold) return;\n const owns = this.#carries(element, hold) && !this.#covered(element, stack, hold);\n const index = stack.indexOf(hold);\n stack.splice(index, 1);\n const above = stack[index];\n if (above) above.linked &&= hold.linked;\n this.#mark(element, stack);\n if (!owns) return;\n const below = hold.linked ? stack[index - 1] : undefined;\n if (below) this.#show(element, below.writtenValue, below.writtenPriority);\n else this.#show(element, hold.originalValue, hold.originalPriority);\n }\n\n /** Returns every outstanding declaration lease. */\n returnAll(): void {\n for (const element of Array.from(this.#held)) this.return(element);\n }\n\n /** The record `name` on `element`, or `null` when it is none a lease could write. */\n #recorded(element: T, name: string): StylePropertyLeaseRecord | null {\n try {\n const parsed: unknown = JSON.parse(element.getAttribute(name) ?? \"null\");\n if (!Array.isArray(parsed)) return null;\n const [originalValue, originalPriority, writtenValue, writtenPriority, ...rest] =\n parsed as unknown[];\n if (typeof originalValue !== \"string\" || typeof originalPriority !== \"string\") return null;\n if (typeof writtenValue !== \"string\" && writtenValue !== null) return null;\n if (typeof writtenPriority !== \"string\") return null;\n const linked = rest[0] !== false;\n const beneath = linked ? rest : rest.slice(1);\n if (!beneath.every((under): under is string => typeof under === \"string\")) return null;\n return { originalValue, originalPriority, writtenValue, writtenPriority, linked, beneath };\n } catch {\n return null;\n }\n }\n\n /** The element's stack, read from its records the first time. */\n #stack(element: T): StyleHold[] {\n const stacks = StylePropertyLease.#registry.stacks;\n const byProperty = stacks.get(element) ?? new Map<string, StyleHold[]>();\n stacks.set(element, byProperty);\n const stack = byProperty.get(this.#property) ?? this.#seed(element);\n byProperty.set(this.#property, stack);\n return stack;\n }\n\n /** The holds a copy's records describe, each above every hold its record lists beneath. */\n #seed(element: T): StyleHold[] {\n const found: (StylePropertyLeaseRecord & { readonly record: string })[] = [];\n for (const record of StylePropertyLease.#registry.names.get(this.#property) ?? []) {\n const read = this.#recorded(element, record);\n if (read) found.push({ ...read, record });\n }\n const depth = ({ beneath }: (typeof found)[number]) =>\n beneath.filter((name) => found.some((other) => other.record === name)).length;\n return found\n .sort((one, other) => depth(one) - depth(other))\n .map((entry, index) => ({\n lease: null,\n record: entry.record,\n writtenValue: entry.writtenValue,\n writtenPriority: entry.writtenPriority,\n originalValue: entry.originalValue,\n originalPriority: entry.originalPriority,\n linked: entry.linked && index > 0,\n }));\n }\n\n /** Its hold in `stack`, taking over one read from its record. */\n #take(stack: StyleHold[]): StyleHold | undefined {\n const hold =\n stack.find((held) => held.lease === this) ??\n stack.find((held) => held.lease === null && held.record === this.#record);\n if (hold) hold.lease = this;\n return hold;\n }\n\n /** A new hold on top, over the top hold while the element carries its declaration. */\n #acquire(element: T, stack: StyleHold[]): StyleHold {\n const top = stack.at(-1);\n const over = top && this.#carries(element, top) ? top : undefined;\n const style = element.style;\n const hold: StyleHold = {\n lease: this,\n record: this.#record,\n writtenValue: null,\n writtenPriority: \"\",\n originalValue: over ? over.originalValue : style.getPropertyValue(this.#property),\n originalPriority: over ? over.originalPriority : style.getPropertyPriority(this.#property),\n linked: over !== undefined,\n };\n stack.push(hold);\n return hold;\n }\n\n /** Whether a hold above `hold` carries the element's declaration, then that hold's. */\n #covered(element: T, stack: readonly StyleHold[], hold: StyleHold): boolean {\n return stack.slice(stack.indexOf(hold) + 1).some((above) => this.#carries(element, above));\n }\n\n /** Leaves each owner's record of its topmost hold, or none where nothing needs one. */\n #mark(element: T, stack: readonly StyleHold[]): void {\n const records = new Map<string, string | null>([[this.#record, null]]);\n stack.forEach((hold, index) => {\n const names = stack.slice(0, index).map((under) => under.record);\n const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);\n const marker = hold.linked || beneath.length === 0 ? [] : [false];\n const { originalValue, originalPriority, writtenValue, writtenPriority } = hold;\n records.set(\n hold.record,\n (writtenValue ?? \"\") === originalValue &&\n writtenPriority === originalPriority &&\n beneath.length === 0 &&\n index === stack.length - 1\n ? null\n : JSON.stringify([\n originalValue,\n originalPriority,\n writtenValue,\n writtenPriority,\n ...marker,\n ...beneath,\n ]),\n );\n });\n for (const [name, recorded] of records) {\n if (element.getAttribute(name) === recorded) continue;\n if (recorded === null) element.removeAttribute(name);\n else element.setAttribute(name, recorded);\n }\n }\n\n /** Whether the element carries the declaration `hold` wrote last. */\n #carries(element: T, hold: StyleHold): boolean {\n return this.#holds(element, hold.writtenValue ?? \"\", hold.writtenPriority);\n }\n\n /** Whether the inline declaration is `value` with `priority`. */\n #holds(element: T, value: string, priority: string): boolean {\n const style = element.style;\n return (\n style.getPropertyValue(this.#property) === value &&\n style.getPropertyPriority(this.#property) === priority\n );\n }\n\n /** Writes the declaration, or removes it for `null`, unless the element already carries it. */\n #show(element: T, value: string | null, priority: string): void {\n if (this.#holds(element, value ?? \"\", priority)) return;\n if (value === null) element.style.removeProperty(this.#property);\n else element.style.setProperty(this.#property, value, priority);\n }\n}\n","/**\n * Drops the transient state hooks a connection inherited, so the cycle it opens\n * starts from a DOM that claims nothing.\n *\n * A transient hook is an attribute a controller writes to say \"this is happening\n * right now\" — a drag in progress, a form with unsaved edits, a refused\n * subscription. Its lifetime belongs to the interaction, not to the element, and\n * the two come apart whenever the element outlives the instance that wrote it: a\n * Turbo cache snapshot taken mid-interaction restores the hook with the markup,\n * and an element re-inserted elsewhere in the page carries it along. Nothing\n * takes such a hook off, because the instance that owned it is gone; consumer\n * CSS then keeps claiming a state no one is in.\n *\n * **`turbo:before-cache` cannot do this job**, which is why the pass belongs in\n * `connect()`: Turbo also dispatches that event on pages that stay, where the hook\n * is still true, and an in-page move — the other half of the population — never\n * reaches it, so a hook that arrived with a moved element would survive.\n * `connect()` covers both, because both end in a connection.\n *\n * Scope is the **drop** only. What the fresh cycle should say instead is the\n * consumer's: a value re-read from the live DOM, a roster refilled by the\n * stream, a measurement retaken from the current scroll position, a slot emptied\n * of the last result. Those answers differ per consumer and none of them is a\n * hook removal, so the pass stops where every consumer agrees.\n *\n * A declaration names one group of hooks and carries no per-instance state, so\n * it belongs at module scope, shared by every instance the page connects. A\n * consumer writes as many as it has groups — hooks it drops under different\n * conditions cannot share one. The exception is a hook whose name is only known\n * at runtime, such as one built from the registered identifier: that\n * declaration is a field, because the name is.\n *\n * **A drop is not always right.** The population splits on one question: can the\n * state a hook describes outlive the instance that wrote it? Where it cannot — a\n * drag ends with the pointer, a subscription dies with its socket — dropping on\n * every connection is correct. Where it can — a submission in flight is not\n * interrupted by an in-page move — only a connection that follows a real restore\n * may drop, and the consumer answers \"did a session survive?\" with the signal it\n * already holds for its own teardown (a live-session field, a `DetachGate`\n * probe). The drop then sits behind that answer, as the second example shows.\n *\n * @example Unconditional — the state cannot outlive the instance.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-sortable-dragging\"] });\n *\n * connect(): void {\n * TRANSIENT.reset(this.element);\n * }\n * ```\n *\n * @example Gated — an in-page move must not drop a live state.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-optimistic\"] });\n *\n * connect(): void {\n * const restored = !this.#connected && !this.#gate.pending;\n * this.#gate.cancel();\n * this.#connected = true;\n * if (restored) TRANSIENT.reset(this.element);\n * }\n * ```\n */\n\n/** What a consumer declares once about the hooks its interaction owns. */\nexport interface TransientHooksOptions {\n /**\n * The attribute names a connection may find written by an earlier one. Hooks\n * a consumer drops under different conditions belong to different\n * declarations, so each one can be reset on its own.\n */\n readonly attributes: readonly string[];\n}\n\n/**\n * One consumer's declaration of the hooks its interaction owns, dropped from an\n * element by {@link TransientHooks.reset}.\n */\nexport class TransientHooks {\n readonly #attributes: readonly string[];\n\n constructor(options: TransientHooksOptions) {\n this.#attributes = options.attributes;\n }\n\n /** Drops the declared hooks from `element`; what it carries besides stays. */\n reset(element: Element): void {\n for (const attribute of this.#attributes) element.removeAttribute(attribute);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { StylePropertyLease } from \"../utils/style_property_lease\";\nimport { TransientHooks } from \"../utils/transient_hooks\";\n\n/** The hook a connection may find written by an earlier, now-gone one. */\nconst TRANSIENT = new TransientHooks({ attributes: [\"data-preview-hidden\"] });\n\n/** Suffix of the record of the content a placeholder displaced; see the class remarks. */\nconst CONTENT_RECORD = \"content\";\n\n/**\n * Headless **preview guard** (Hotwire-specific): hides or placeholders a volatile element\n * — a balance, a notification count, a live timestamp — *only while Turbo is showing a\n * preview* (`html[data-turbo-preview]`), so a stale cached snapshot does not briefly flash\n * the old value on a back/restore visit. No APG pattern; it keeps displayed information\n * accurate and hands focus back if guarding took it.\n *\n * Markup contract (identifier: `stimeo--preview-guard`):\n * <span data-controller=\"stimeo--preview-guard\"\n * data-stimeo--preview-guard-placeholder-value=\"—\">¥123,456</span>\n *\n * Watches `<html>` for the `data-turbo-preview` attribute with a `MutationObserver`.\n * While it is present the element is guarded, and `placeholder` alone decides how: empty\n * (the default) makes it `visibility: hidden` — its box is kept, so nothing shifts — and\n * any other value takes its place as the element's content. The element carries\n * `data-preview-hidden` and emits `hide`; when the preview clears it is restored and\n * `show` fires. A `placeholder` changed while guarded re-guards to match it.\n *\n * `hide` and `show` dispatch `{}`.\n *\n * @remarks\n * Behavior only — restoring the *fresh* value is the normal render's job, not this\n * controller's (it just un-hides what was there). State is derived from the DOM (no\n * module-scope state), and the guard is reversible in three ways that do not depend on\n * each other: the child nodes a placeholder displaced are held and put back intact, the\n * inline `visibility` is leased so an authored declaration — and one the consumer writes\n * while guarded — survives, and a fresh `connect()` clears a `data-preview-hidden` it\n * finds already on the element. A placeholder also records the markup it displaced on\n * the element as `data-<identifier>-content` (a JSON string), and the `visibility` lease\n * leaves its own record, so a copy of the element taken while guarded — a page Turbo\n * restores from its cache — gets its content or its `visibility` back from the fresh\n * `connect()` that finds them. Nothing comes down on `turbo:before-cache`,\n * which Turbo also dispatches on pages that stay (a state-less `popstate`, a refresh of\n * a cached URL), where the preview is still on screen; nor on `disconnect()`, so an\n * in-page move keeps the guard instead of flashing the stale value between the two\n * lifecycle calls — and the instance that comes back re-forms it rather than reading\n * the hook as someone else's leftover.\n *\n * Focus is only ever handed back, never taken: an element that held focus when the guard\n * went up gets it back on show, and focus resting anywhere else is left alone.\n */\nexport class PreviewGuardController extends Controller<HTMLElement> {\n static override values = {\n placeholder: { type: String, default: \"\" },\n };\n static events = [\"hide\", \"show\"] as const;\n\n declare placeholderValue: string;\n\n readonly #visibility = new StylePropertyLease(\"visibility\", this.identifier);\n\n #observer: MutationObserver | null = null;\n #connected = false;\n #hidden = false;\n /** Child nodes a placeholder displaced; non-null marks that content — not visibility — was swapped. */\n #savedNodes: DocumentFragment | null = null;\n /** The descendant that held focus when the guard went up, so show can hand it back. */\n #focused: HTMLElement | null = null;\n\n readonly #morphRender = new MorphRenderWatcher(() => {\n this.#sync();\n if (this.#hidden) this.#reguard();\n });\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#connected = true;\n // The hook means \"the connection now running has this element guarded\", and which of\n // the two connects is happening is only knowable from the instance. A restored\n // snapshot brings a fresh controller to an element that still carries the hook, with\n // no instance left holding what was underneath it, so that one puts back the content\n // a placeholder recorded and drops the hook. An in-page move brings the *same*\n // instance back still holding the guard, so that one re-forms instead — against the\n // `placeholder` as it stands now, since a morph can swap it while the element is\n // detached, where the value callback has no connection to act on.\n if (this.#hidden) {\n this.#reguard();\n } else {\n if (this.element.hasAttribute(\"data-preview-hidden\")) this.#restoreRecordedContent();\n this.#visibility.return(this.element);\n TRANSIENT.reset(this.element);\n }\n if (typeof MutationObserver !== \"undefined\") {\n this.#observer = new MutationObserver(() => this.#sync());\n this.#observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-turbo-preview\"],\n });\n }\n // Sync once in case we connect while a preview is already on screen.\n this.#sync();\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#connected = false;\n this.#observer?.disconnect();\n this.#observer = null;\n }\n\n /**\n * Re-guards to match a `placeholder` changed at runtime — the value is the content on\n * display while the guard is up, so a morph that swaps it must not leave the old one.\n */\n placeholderValueChanged(): void {\n if (!this.#connected || !this.#hidden) return;\n this.#reguard();\n }\n\n /**\n * Reflects the current `data-turbo-preview` state onto the element.\n *\n * @stimeoRenderRoot\n */\n #sync(): void {\n const previewing = document.documentElement.hasAttribute(\"data-turbo-preview\");\n if (previewing && !this.#hidden) this.#hide();\n else if (!previewing && this.#hidden) this.#show();\n }\n\n #hide(): void {\n this.#focused = this.#focusedInside();\n this.#applyGuard();\n this.dispatch(\"hide\", { detail: {} });\n }\n\n /** Puts the guard up in the form the current `placeholder` calls for. */\n #applyGuard(): void {\n this.#hidden = true;\n if (this.placeholderValue === \"\") {\n this.#visibility.write(this.element, \"hidden\");\n } else {\n this.element.setAttribute(this.#contentRecord, JSON.stringify(this.element.innerHTML));\n this.#savedNodes = document.createDocumentFragment();\n this.#savedNodes.append(...this.element.childNodes);\n this.element.textContent = this.placeholderValue;\n }\n this.element.setAttribute(\"data-preview-hidden\", \"true\");\n }\n\n /** The record of the content a placeholder displaced, in this controller's namespace. */\n get #contentRecord(): string {\n return `data-${this.identifier}-${CONTENT_RECORD}`;\n }\n\n /**\n * Puts back the content a copy of a guarded element records — no instance holds its\n * nodes any more — and drops the record.\n */\n #restoreRecordedContent(): void {\n const raw = this.element.getAttribute(this.#contentRecord) ?? \"null\";\n this.element.removeAttribute(this.#contentRecord);\n let content: unknown = null;\n try {\n content = JSON.parse(raw);\n } catch {\n // A record that is not JSON holds no content.\n }\n if (typeof content === \"string\") this.element.innerHTML = content;\n }\n\n /**\n * Re-forms a guard that is already up so it matches the current `placeholder`.\n *\n * No event: `hide` reports that the guard went up, and it has not come down. Swapping\n * one stand-in text for another writes only that text — putting the held content back\n * first would reconnect the whole subtree for an instant. Only a change of *form* —\n * to or from the empty placeholder — has to revert, and the focus the guard is holding\n * carries across it rather than being handed back and taken again.\n *\n * @stimeoRenderRoot\n */\n #reguard(): void {\n this.element.setAttribute(\"data-preview-hidden\", \"true\");\n if (this.#savedNodes && this.placeholderValue !== \"\") {\n this.element.textContent = this.placeholderValue;\n return;\n }\n const held = this.#focused;\n this.#revert();\n this.#applyGuard();\n this.#focused = held;\n }\n\n #show(): void {\n this.#restore();\n this.dispatch(\"show\", { detail: {} });\n }\n\n /** Reverts the guard and hands focus back. */\n #restore(): void {\n this.#revert();\n this.#refocus();\n }\n\n /**\n * Puts the element back the way the guard found it.\n *\n * Every step is a no-op on an element this controller never guarded: the lease returns\n * only declarations it recorded, and the hook is removed whether or not it is there.\n */\n #revert(): void {\n this.#hidden = false;\n if (this.#savedNodes) {\n this.element.textContent = \"\";\n this.element.append(this.#savedNodes);\n this.#savedNodes = null;\n this.element.removeAttribute(this.#contentRecord);\n } else {\n this.#visibility.return(this.element);\n }\n this.element.removeAttribute(\"data-preview-hidden\");\n }\n\n /**\n * The focused element the guard is about to make unfocusable, if any.\n *\n * The element itself counts as well as its descendants: guarding takes focus either way\n * — a `visibility: hidden` subtree cannot hold it, and a placeholder displaces the nodes\n * outright — so the browser drops focus to `<body>` the moment the guard goes up.\n */\n #focusedInside(): HTMLElement | null {\n const active = document.activeElement;\n return active instanceof HTMLElement && this.element.contains(active) ? active : null;\n }\n\n /**\n * Hands focus back to the element the guard took it from.\n *\n * Only when that element is still in the document and focus has not moved on since —\n * anything else is the user's or another controller's, and putting it back would be\n * taking it.\n */\n #refocus(): void {\n const target = this.#focused;\n this.#focused = null;\n if (!target?.isConnected || document.activeElement !== document.body) return;\n target.focus();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/attribute_write.ts","../../src/utils/shared_registry.ts","../../src/utils/style_property_lease.ts","../../src/utils/transient_hooks.ts","../../src/controllers/preview_guard_controller.ts"],"names":[],"mappings":"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","file":"preview_guard_controller.js","sourcesContent":["/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Whether a requested pass has yet to run. */\n get pending(): boolean {\n return this.#queued;\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","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Whether a pass requested by a morph or {@link schedule} has yet to run. */\n get pending(): boolean {\n return this.#pass.pending;\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","/**\n * Reflects a serialized attribute value only when the current value differs.\n *\n * A same-value `setAttribute` still queues a mutation record for every observer, so an\n * unchanged value writes nothing.\n *\n * @param element - The element whose attribute is reflected.\n * @param name - The attribute name, using the non-namespaced DOM API.\n * @param value - The serialized value, or null to remove the attribute.\n * @param beforeWrite - Called only for a change, before the DOM mutation, so the\n * caller can record ownership or identify its own observer records.\n * @returns Whether an attribute was written or removed.\n */\nexport function setAttributeIfChanged(\n element: Element,\n name: string,\n value: string | null,\n beforeWrite?: () => void,\n): boolean {\n if (element.getAttribute(name) === value) return false;\n beforeWrite?.();\n if (value === null) element.removeAttribute(name);\n else element.setAttribute(name, value);\n return true;\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { setAttributeIfChanged } from \"./attribute_write\";\nimport { sharedRegistry } from \"./shared_registry\";\n\n/** What a lease's record says: the authored declaration, the last write and the leases beneath. */\ninterface StylePropertyLeaseRecord {\n readonly originalValue: string;\n readonly originalPriority: string;\n readonly writtenValue: string | null;\n readonly writtenPriority: string;\n /** Whether it hands back what the lease right beneath it wrote last. */\n readonly linked: boolean;\n /** The records of the other owners' leases beneath it, nearest first. */\n readonly beneath: readonly string[];\n}\n\n/** One lease's hold on one element's property, in the stack every lease of it shares. */\ninterface StyleHold {\n /** The lease holding it; `null` for a hold read from a copy's record until its lease takes it. */\n lease: object | null;\n readonly record: string;\n writtenValue: string | null;\n writtenPriority: string;\n /** What it hands back with nothing beneath it: the author's declaration. */\n readonly originalValue: string;\n readonly originalPriority: string;\n /** Whether it hands back what the hold beneath it wrote last instead. */\n linked: boolean;\n}\n\n/** The holds on each element by property, and every lease's record name by property. */\ninterface StylePropertyLeaseRegistry {\n readonly stacks: WeakMap<HTMLElement, Map<string, StyleHold[]>>;\n readonly names: Map<string, Set<string>>;\n}\n\n/**\n * Temporarily controls one inline CSS property across a changing set of elements.\n *\n * Authored value and priority are restored only while the declaration still matches\n * the last leased write. A later consumer write therefore wins.\n *\n * **Every lease of a property shares one stack of holds per element**, whatever its owner\n * and whichever file of the package constructed it (the stacks are one per page): the holds\n * sit in the order their leases first wrote the element, each with the declaration it wrote\n * last. The element's declaration is the highest hold's that carries it, and the page's when\n * none does. A write reaches the element unless a hold above the writer carries the element's\n * declaration, so the top hold always shows its writes and the page's declaration gives way to\n * the next write of any hold. A return gives a declaration back only from the hold the\n * element's declaration is: the one the hold beneath it wrote last, or the author's once\n * nothing is beneath it; any other return only leaves the stack. Holds that wrote the same\n * declaration are told apart by the stack: the higher one keeps it. A first write over a top\n * hold whose declaration the page has replaced takes the element's declaration as the\n * author's. Two instances of one controller that write the same property on\n * `document.documentElement` stack the same way.\n *\n * **The stack travels with the element.** While a hold carries a declaration other than the\n * author's, lies on another hold or is covered by one, the element carries its owner's record\n * `data-<owner>-style-<property>-lease`: JSON of the author's value and priority, the value\n * and priority the hold wrote last (`null` for a removal), `false` where the hold hands back\n * the author's declaration rather than the latest one of the hold beneath it, and the records\n * of every hold beneath it, nearest first. Returning the last lease of an owner removes its\n * record. A copy of the element — a page Turbo restores from its cache — therefore carries the\n * stack, read back under the record names of the leases constructed so far and ordered by the\n * records each hold lists beneath it, and each lease takes the hold its owner's record\n * describes over in place. Complete records for distinct owners preserve their order across\n * a copy. Records describe owners, not instances: one owner holding the element more than\n * once leaves only its topmost hold's record, which the first of its leases to write or return\n * on the copy takes. Its lower holds and positions are absent; mutually listed owners cannot\n * express their full live order. A hold that showed the author's declaration alone carries\n * no record and is taken by its first write on the copy. Missing or unreadable records also\n * leave their holds unknown, so copying the DOM does not promise identical decisions for\n * every live stack. A new stack with no readable record takes the current declaration as the\n * author's; a morph can remove records while existing holds remain in memory until returned.\n *\n * The lease has no lifecycle of its own, so it never subscribes to document events.\n */\nexport class StylePropertyLease<T extends HTMLElement = HTMLElement> {\n /** The holds on each element, bottom first, and the record names; one per page. */\n static readonly #registry = sharedRegistry(\n \"stimeo-ui.style-property-lease.registry.v1\",\n (): StylePropertyLeaseRegistry => ({ stacks: new WeakMap(), names: new Map() }),\n );\n readonly #property: string;\n readonly #record: string;\n readonly #held = new Set<T>();\n\n /**\n * @param property - The CSS property whose temporary values this lease owns.\n * @param owner - Names the record on the element; the controller's identifier.\n */\n constructor(property: string, owner: string) {\n this.#property = property;\n this.#record = `data-${owner}-style-${property}-lease`;\n const names = StylePropertyLease.#registry.names;\n names.set(property, (names.get(property) ?? new Set<string>()).add(this.#record));\n }\n\n /** Writes or removes the leased declaration while preserving its authored value. */\n write(element: T, value: string | null, priority = \"\"): void {\n const stack = this.#stack(element);\n const hold = this.#take(stack) ?? this.#acquire(element, stack);\n this.#held.add(element);\n const shows = !this.#covered(element, stack, hold);\n hold.writtenValue = value;\n hold.writtenPriority = value === null ? \"\" : priority;\n this.#mark(element, stack);\n if (shows) this.#show(element, value, hold.writtenPriority);\n }\n\n /**\n * Returns one lease, also on a restored copy, to the declaration the hold beneath it wrote\n * last or the author's, without overwriting a declaration a consumer wrote since.\n */\n return(element: T): void {\n const held = this.#held.delete(element);\n const known = StylePropertyLease.#registry.stacks.get(element)?.has(this.#property);\n const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];\n const hold = this.#take(stack);\n if (!hold) return;\n const owns = this.#carries(element, hold) && !this.#covered(element, stack, hold);\n const index = stack.indexOf(hold);\n stack.splice(index, 1);\n const above = stack[index];\n if (above) above.linked &&= hold.linked;\n this.#mark(element, stack);\n if (!owns) return;\n const below = hold.linked ? stack[index - 1] : undefined;\n if (below) this.#show(element, below.writtenValue, below.writtenPriority);\n else this.#show(element, hold.originalValue, hold.originalPriority);\n }\n\n /** Returns every outstanding declaration lease. */\n returnAll(): void {\n for (const element of Array.from(this.#held)) this.return(element);\n }\n\n /** The record `name` on `element`, or `null` when it is none a lease could write. */\n #recorded(element: T, name: string): StylePropertyLeaseRecord | null {\n try {\n const parsed: unknown = JSON.parse(element.getAttribute(name) ?? \"null\");\n if (!Array.isArray(parsed)) return null;\n const [originalValue, originalPriority, writtenValue, writtenPriority, ...rest] =\n parsed as unknown[];\n if (typeof originalValue !== \"string\" || typeof originalPriority !== \"string\") return null;\n if (typeof writtenValue !== \"string\" && writtenValue !== null) return null;\n if (typeof writtenPriority !== \"string\") return null;\n const linked = rest[0] !== false;\n const beneath = linked ? rest : rest.slice(1);\n if (!beneath.every((under): under is string => typeof under === \"string\")) return null;\n return { originalValue, originalPriority, writtenValue, writtenPriority, linked, beneath };\n } catch {\n return null;\n }\n }\n\n /** The element's stack, read from its records the first time. */\n #stack(element: T): StyleHold[] {\n const stacks = StylePropertyLease.#registry.stacks;\n const byProperty = stacks.get(element) ?? new Map<string, StyleHold[]>();\n stacks.set(element, byProperty);\n const stack = byProperty.get(this.#property) ?? this.#seed(element);\n byProperty.set(this.#property, stack);\n return stack;\n }\n\n /** The holds a copy's records describe, each above every hold its record lists beneath. */\n #seed(element: T): StyleHold[] {\n const found: (StylePropertyLeaseRecord & { readonly record: string })[] = [];\n for (const record of StylePropertyLease.#registry.names.get(this.#property) ?? []) {\n const read = this.#recorded(element, record);\n if (read) found.push({ ...read, record });\n }\n const depth = ({ beneath }: (typeof found)[number]) =>\n beneath.filter((name) => found.some((other) => other.record === name)).length;\n return found\n .sort((one, other) => depth(one) - depth(other))\n .map((entry, index) => ({\n lease: null,\n record: entry.record,\n writtenValue: entry.writtenValue,\n writtenPriority: entry.writtenPriority,\n originalValue: entry.originalValue,\n originalPriority: entry.originalPriority,\n linked: entry.linked && index > 0,\n }));\n }\n\n /** Its hold in `stack`, taking over one read from its record. */\n #take(stack: StyleHold[]): StyleHold | undefined {\n const hold =\n stack.find((held) => held.lease === this) ??\n stack.find((held) => held.lease === null && held.record === this.#record);\n if (hold) hold.lease = this;\n return hold;\n }\n\n /** A new hold on top, over the top hold while the element carries its declaration. */\n #acquire(element: T, stack: StyleHold[]): StyleHold {\n const top = stack.at(-1);\n const over = top && this.#carries(element, top) ? top : undefined;\n const style = element.style;\n const hold: StyleHold = {\n lease: this,\n record: this.#record,\n writtenValue: null,\n writtenPriority: \"\",\n originalValue: over ? over.originalValue : style.getPropertyValue(this.#property),\n originalPriority: over ? over.originalPriority : style.getPropertyPriority(this.#property),\n linked: over !== undefined,\n };\n stack.push(hold);\n return hold;\n }\n\n /** Whether a hold above `hold` carries the element's declaration, then that hold's. */\n #covered(element: T, stack: readonly StyleHold[], hold: StyleHold): boolean {\n return stack.slice(stack.indexOf(hold) + 1).some((above) => this.#carries(element, above));\n }\n\n /** Leaves each owner's record of its topmost hold, or none where nothing needs one. */\n #mark(element: T, stack: readonly StyleHold[]): void {\n const records = new Map<string, string | null>([[this.#record, null]]);\n stack.forEach((hold, index) => {\n const names = stack.slice(0, index).map((under) => under.record);\n const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);\n const marker = hold.linked || beneath.length === 0 ? [] : [false];\n const { originalValue, originalPriority, writtenValue, writtenPriority } = hold;\n records.set(\n hold.record,\n (writtenValue ?? \"\") === originalValue &&\n writtenPriority === originalPriority &&\n beneath.length === 0 &&\n index === stack.length - 1\n ? null\n : JSON.stringify([\n originalValue,\n originalPriority,\n writtenValue,\n writtenPriority,\n ...marker,\n ...beneath,\n ]),\n );\n });\n for (const [name, recorded] of records) {\n setAttributeIfChanged(element, name, recorded);\n }\n }\n\n /** Whether the element carries the declaration `hold` wrote last. */\n #carries(element: T, hold: StyleHold): boolean {\n return this.#holds(element, hold.writtenValue ?? \"\", hold.writtenPriority);\n }\n\n /** Whether the inline declaration is `value` with `priority`. */\n #holds(element: T, value: string, priority: string): boolean {\n const style = element.style;\n return (\n style.getPropertyValue(this.#property) === value &&\n style.getPropertyPriority(this.#property) === priority\n );\n }\n\n /** Writes the declaration, or removes it for `null`, unless the element already carries it. */\n #show(element: T, value: string | null, priority: string): void {\n if (this.#holds(element, value ?? \"\", priority)) return;\n if (value === null) element.style.removeProperty(this.#property);\n else element.style.setProperty(this.#property, value, priority);\n }\n}\n","/**\n * Drops the transient state hooks a connection inherited, so the cycle it opens\n * starts from a DOM that claims nothing.\n *\n * A transient hook is an attribute a controller writes to say \"this is happening\n * right now\" — a drag in progress, a form with unsaved edits, a refused\n * subscription. Its lifetime belongs to the interaction, not to the element, and\n * the two come apart whenever the element outlives the instance that wrote it: a\n * Turbo cache snapshot taken mid-interaction restores the hook with the markup,\n * and an element re-inserted elsewhere in the page carries it along. Nothing\n * takes such a hook off, because the instance that owned it is gone; consumer\n * CSS then keeps claiming a state no one is in.\n *\n * **`turbo:before-cache` cannot do this job**, which is why the pass belongs in\n * `connect()`: Turbo also dispatches that event on pages that stay, where the hook\n * is still true, and an in-page move — the other half of the population — never\n * reaches it, so a hook that arrived with a moved element would survive.\n * `connect()` covers both, because both end in a connection.\n *\n * Scope is the **drop** only. What the fresh cycle should say instead is the\n * consumer's: a value re-read from the live DOM, a roster refilled by the\n * stream, a measurement retaken from the current scroll position, a slot emptied\n * of the last result. Those answers differ per consumer and none of them is a\n * hook removal, so the pass stops where every consumer agrees.\n *\n * A declaration names one group of hooks and carries no per-instance state, so\n * it belongs at module scope, shared by every instance the page connects. A\n * consumer writes as many as it has groups — hooks it drops under different\n * conditions cannot share one. The exception is a hook whose name is only known\n * at runtime, such as one built from the registered identifier: that\n * declaration is a field, because the name is.\n *\n * **A drop is not always right.** The population splits on one question: can the\n * state a hook describes outlive the instance that wrote it? Where it cannot — a\n * drag ends with the pointer, a subscription dies with its socket — dropping on\n * every connection is correct. Where it can — a submission in flight is not\n * interrupted by an in-page move — only a connection that follows a real restore\n * may drop, and the consumer answers \"did a session survive?\" with the signal it\n * already holds for its own teardown (a live-session field, a `DetachGate`\n * probe). The drop then sits behind that answer, as the second example shows.\n *\n * @example Unconditional — the state cannot outlive the instance.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-sortable-dragging\"] });\n *\n * connect(): void {\n * TRANSIENT.reset(this.element);\n * }\n * ```\n *\n * @example Gated — an in-page move must not drop a live state.\n * ```ts\n * const TRANSIENT = new TransientHooks({ attributes: [\"data-optimistic\"] });\n *\n * connect(): void {\n * const restored = !this.#connected && !this.#gate.pending;\n * this.#gate.cancel();\n * this.#connected = true;\n * if (restored) TRANSIENT.reset(this.element);\n * }\n * ```\n */\n\n/** What a consumer declares once about the hooks its interaction owns. */\nexport interface TransientHooksOptions {\n /**\n * The attribute names a connection may find written by an earlier one. Hooks\n * a consumer drops under different conditions belong to different\n * declarations, so each one can be reset on its own.\n */\n readonly attributes: readonly string[];\n}\n\n/**\n * One consumer's declaration of the hooks its interaction owns, dropped from an\n * element by {@link TransientHooks.reset}.\n */\nexport class TransientHooks {\n readonly #attributes: readonly string[];\n\n constructor(options: TransientHooksOptions) {\n this.#attributes = options.attributes;\n }\n\n /** Drops the declared hooks from `element`; what it carries besides stays. */\n reset(element: Element): void {\n for (const attribute of this.#attributes) element.removeAttribute(attribute);\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { StylePropertyLease } from \"../utils/style_property_lease\";\nimport { TransientHooks } from \"../utils/transient_hooks\";\n\n/** The hook a connection may find written by an earlier, now-gone one. */\nconst TRANSIENT = new TransientHooks({ attributes: [\"data-preview-hidden\"] });\n\n/** Suffix of the record of the content a placeholder displaced; see the class remarks. */\nconst CONTENT_RECORD = \"content\";\n\n/**\n * Headless **preview guard** (Hotwire-specific): hides or placeholders a volatile element\n * — a balance, a notification count, a live timestamp — *only while Turbo is showing a\n * preview* (`html[data-turbo-preview]`), so a stale cached snapshot does not briefly flash\n * the old value on a back/restore visit. No APG pattern; it keeps displayed information\n * accurate and hands focus back if guarding took it.\n *\n * Markup contract (identifier: `stimeo--preview-guard`):\n * <span data-controller=\"stimeo--preview-guard\"\n * data-stimeo--preview-guard-placeholder-value=\"—\">¥123,456</span>\n *\n * Watches `<html>` for the `data-turbo-preview` attribute with a `MutationObserver`.\n * While it is present the element is guarded, and `placeholder` alone decides how: empty\n * (the default) makes it `visibility: hidden` — its box is kept, so nothing shifts — and\n * any other value takes its place as the element's content. The element carries\n * `data-preview-hidden` and emits `hide`; when the preview clears it is restored and\n * `show` fires. A `placeholder` changed while guarded re-guards to match it.\n *\n * `hide` and `show` dispatch `{}`.\n *\n * @remarks\n * Behavior only — restoring the *fresh* value is the normal render's job, not this\n * controller's (it just un-hides what was there). State is derived from the DOM (no\n * module-scope state), and the guard is reversible in three ways that do not depend on\n * each other: the child nodes a placeholder displaced are held and put back intact, the\n * inline `visibility` is leased so an authored declaration — and one the consumer writes\n * while guarded — survives, and a fresh `connect()` clears a `data-preview-hidden` it\n * finds already on the element. A placeholder also records the markup it displaced on\n * the element as `data-<identifier>-content` (a JSON string), and the `visibility` lease\n * leaves its own record, so a copy of the element taken while guarded — a page Turbo\n * restores from its cache — gets its content or its `visibility` back from the fresh\n * `connect()` that finds them. Nothing comes down on `turbo:before-cache`,\n * which Turbo also dispatches on pages that stay (a state-less `popstate`, a refresh of\n * a cached URL), where the preview is still on screen; nor on `disconnect()`, so an\n * in-page move keeps the guard instead of flashing the stale value between the two\n * lifecycle calls — and the instance that comes back re-forms it rather than reading\n * the hook as someone else's leftover.\n *\n * Focus is only ever handed back, never taken: an element that held focus when the guard\n * went up gets it back on show, and focus resting anywhere else is left alone.\n */\nexport class PreviewGuardController extends Controller<HTMLElement> {\n static override values = {\n placeholder: { type: String, default: \"\" },\n };\n static events = [\"hide\", \"show\"] as const;\n\n declare placeholderValue: string;\n\n readonly #visibility = new StylePropertyLease(\"visibility\", this.identifier);\n\n #observer: MutationObserver | null = null;\n #connected = false;\n #hidden = false;\n /**\n * Child nodes a placeholder displaced; non-null marks that content — not visibility —\n * was swapped.\n */\n #savedNodes: DocumentFragment | null = null;\n /** The descendant that held focus when the guard went up, so show can hand it back. */\n #focused: HTMLElement | null = null;\n\n readonly #morphRender = new MorphRenderWatcher(() => {\n this.#sync();\n if (this.#hidden) this.#reguard();\n });\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#connected = true;\n // The hook means \"the connection now running has this element guarded\", and which of\n // the two connects is happening is only knowable from the instance. A restored\n // snapshot brings a fresh controller to an element that still carries the hook, with\n // no instance left holding what was underneath it, so that one puts back the content\n // a placeholder recorded and drops the hook. An in-page move brings the *same*\n // instance back still holding the guard, so that one re-forms instead — against the\n // `placeholder` as it stands now, since a morph can swap it while the element is\n // detached, where the value callback has no connection to act on.\n if (this.#hidden) {\n this.#reguard();\n } else {\n if (this.element.hasAttribute(\"data-preview-hidden\")) this.#restoreRecordedContent();\n this.#visibility.return(this.element);\n TRANSIENT.reset(this.element);\n }\n if (typeof MutationObserver !== \"undefined\") {\n this.#observer = new MutationObserver(() => this.#sync());\n this.#observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-turbo-preview\"],\n });\n }\n // Sync once in case we connect while a preview is already on screen.\n this.#sync();\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#connected = false;\n this.#observer?.disconnect();\n this.#observer = null;\n }\n\n /**\n * Re-guards to match a `placeholder` changed at runtime — the value is the content on\n * display while the guard is up, so a morph that swaps it must not leave the old one.\n */\n placeholderValueChanged(): void {\n if (!this.#connected || !this.#hidden) return;\n this.#reguard();\n }\n\n /**\n * Reflects the current `data-turbo-preview` state onto the element.\n *\n * @stimeoRenderRoot\n */\n #sync(): void {\n const previewing = document.documentElement.hasAttribute(\"data-turbo-preview\");\n if (previewing && !this.#hidden) this.#hide();\n else if (!previewing && this.#hidden) this.#show();\n }\n\n #hide(): void {\n this.#focused = this.#focusedInside();\n this.#applyGuard();\n this.dispatch(\"hide\", { detail: {} });\n }\n\n /** Puts the guard up in the form the current `placeholder` calls for. */\n #applyGuard(): void {\n this.#hidden = true;\n if (this.placeholderValue === \"\") {\n this.#visibility.write(this.element, \"hidden\");\n } else {\n this.element.setAttribute(this.#contentRecord, JSON.stringify(this.element.innerHTML));\n this.#savedNodes = document.createDocumentFragment();\n this.#savedNodes.append(...this.element.childNodes);\n this.element.textContent = this.placeholderValue;\n }\n this.element.setAttribute(\"data-preview-hidden\", \"true\");\n }\n\n /** The record of the content a placeholder displaced, in this controller's namespace. */\n get #contentRecord(): string {\n return `data-${this.identifier}-${CONTENT_RECORD}`;\n }\n\n /**\n * Puts back the content a copy of a guarded element records — no instance holds its\n * nodes any more — and drops the record.\n */\n #restoreRecordedContent(): void {\n const raw = this.element.getAttribute(this.#contentRecord) ?? \"null\";\n this.element.removeAttribute(this.#contentRecord);\n let content: unknown = null;\n try {\n content = JSON.parse(raw);\n } catch {\n // A record that is not JSON holds no content.\n }\n if (typeof content === \"string\") this.element.innerHTML = content;\n }\n\n /**\n * Re-forms a guard that is already up so it matches the current `placeholder`.\n *\n * No event: `hide` reports that the guard went up, and it has not come down. Swapping\n * one stand-in text for another writes only that text — putting the held content back\n * first would reconnect the whole subtree for an instant. Only a change of *form* —\n * to or from the empty placeholder — has to revert, and the focus the guard is holding\n * carries across it rather than being handed back and taken again.\n *\n * @stimeoRenderRoot\n */\n #reguard(): void {\n this.element.setAttribute(\"data-preview-hidden\", \"true\");\n if (this.#savedNodes && this.placeholderValue !== \"\") {\n this.element.textContent = this.placeholderValue;\n return;\n }\n const held = this.#focused;\n this.#revert();\n this.#applyGuard();\n this.#focused = held;\n }\n\n #show(): void {\n this.#restore();\n this.dispatch(\"show\", { detail: {} });\n }\n\n /** Reverts the guard and hands focus back. */\n #restore(): void {\n this.#revert();\n this.#refocus();\n }\n\n /**\n * Puts the element back the way the guard found it.\n *\n * Every step is a no-op on an element this controller never guarded: the lease returns\n * only declarations it recorded, and the hook is removed whether or not it is there.\n */\n #revert(): void {\n this.#hidden = false;\n if (this.#savedNodes) {\n this.element.textContent = \"\";\n this.element.append(this.#savedNodes);\n this.#savedNodes = null;\n this.element.removeAttribute(this.#contentRecord);\n } else {\n this.#visibility.return(this.element);\n }\n this.element.removeAttribute(\"data-preview-hidden\");\n }\n\n /**\n * The focused element the guard is about to make unfocusable, if any.\n *\n * The element itself counts as well as its descendants: guarding takes focus either way\n * — a `visibility: hidden` subtree cannot hold it, and a placeholder displaces the nodes\n * outright — so the browser drops focus to `<body>` the moment the guard goes up.\n */\n #focusedInside(): HTMLElement | null {\n const active = document.activeElement;\n return active instanceof HTMLElement && this.element.contains(active) ? active : null;\n }\n\n /**\n * Hands focus back to the element the guard took it from.\n *\n * Only when that element is still in the document and focus has not moved on since —\n * anything else is the user's or another controller's, and putting it back would be\n * taking it.\n */\n #refocus(): void {\n const target = this.#focused;\n this.#focused = null;\n if (!target?.isConnected || document.activeElement !== document.body) return;\n target.focus();\n }\n}\n"]}
|