stimeo-ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +32 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +166 -124
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +352 -182
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +418 -207
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +134 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +395 -145
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/safe_timeout.ts","../../src/utils/state_regions.ts","../../src/utils/string_list.ts","../../src/controllers/idle_controller.ts"],"names":[],"mappings":"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","file":"idle_controller.js","sourcesContent":["/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\n/**\n * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","/** What a consumer declares once about the regions one boolean state reveals. */\nexport interface StateRegionsOptions {\n /** Returns the declared regions shown while the state holds. */\n readonly whenTrue: () => readonly HTMLElement[];\n /**\n * Returns the declared regions shown while the state does not hold. Omitted by a\n * state whose other side has nothing of its own to show.\n */\n readonly whenFalse?: () => readonly HTMLElement[];\n}\n\n/**\n * Owns `hidden` on the regions a boolean state reveals.\n *\n * A region is a **declared** target, so the consumer decides whether one exists at\n * all; where it does, which side is shown is a pure function of the state and the\n * controller writes it unconditionally. An authored `hidden` is therefore not read\n * back — the first reflection after a connection settles it, and a restored DOM\n * cannot leave a region contradicting the state it belongs to.\n *\n * The regions arrive as the caller's own target arrays, which Stimulus has already\n * scoped to this instance: a nested controller registered under the same identifier\n * keeps its own regions. `host` narrows that set further, to the regions inside one\n * trigger or item, so a widget with several of them reflects each independently.\n *\n * **Two sides are a pair.** Where the state has a `whenFalse` side and only one half\n * sits inside `host`, the half that is there is left in view: hiding it would take\n * the only label with it, and the static check names the missing one instead. What\n * the author wrote on a lone half is theirs and stays — only a half this instance\n * took out of view is given back, which is what a pair losing one side at runtime\n * leaves behind.\n *\n * `hidden` moves only when it changes, so a consumer watching its own subtree for\n * attribute mutations sees one record per transition rather than one per reflection.\n *\n * @example\n * ```ts\n * readonly #labels = new StateRegions({\n * whenTrue: () => this.expandedLabelTargets,\n * whenFalse: () => this.collapsedLabelTargets,\n * });\n *\n * #reflect(): void {\n * this.#labels.reflect(this.triggerTarget, this.#expanded);\n * }\n * ```\n */\nexport class StateRegions {\n readonly #whenTrue: () => readonly HTMLElement[];\n readonly #whenFalse: (() => readonly HTMLElement[]) | null;\n readonly #taken = new WeakSet<HTMLElement>();\n\n constructor(options: StateRegionsOptions) {\n this.#whenTrue = options.whenTrue;\n this.#whenFalse = options.whenFalse ?? null;\n }\n\n /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */\n reflect(host: Element, isTrue: boolean): void {\n const shown = this.#inside(host, this.#whenTrue());\n if (!this.#whenFalse) {\n for (const region of shown) this.#write(region, !isTrue);\n return;\n }\n const hiddenSide = this.#inside(host, this.#whenFalse());\n if (shown.length === 0 || hiddenSide.length === 0) {\n for (const region of shown) this.#give(region);\n for (const region of hiddenSide) this.#give(region);\n return;\n }\n for (const region of shown) this.#write(region, !isTrue);\n for (const region of hiddenSide) this.#write(region, isTrue);\n }\n\n /** Writes `hidden` where it moves, noting which regions it takes out of view. */\n #write(region: HTMLElement, hidden: boolean): void {\n if (hidden) this.#taken.add(region);\n else this.#taken.delete(region);\n if (region.hidden !== hidden) region.hidden = hidden;\n }\n\n /** Returns a region this instance hid; one it never hid keeps what it carries. */\n #give(region: HTMLElement): void {\n if (this.#taken.delete(region)) region.hidden = false;\n }\n\n /** The declared regions that sit within `host`, which may be the host itself. */\n #inside(host: Element, regions: readonly HTMLElement[]): HTMLElement[] {\n return regions.filter((region) => host.contains(region));\n }\n}\n","/**\n * JSON string-list parsing for the controllers that take a list as a Value.\n *\n * Stimulus offers an `Array` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the `<name>ValueChanged` callback — and\n * rethrows on malformed text. The throw propagates out of the observer that runs\n * during connection, so a single unparseable attribute stops the controller from\n * connecting at all: no lifecycle callback runs and the element is left inert\n * rather than degraded. A consumer cannot guard against it either, because the\n * callback that would hold the `try` never runs.\n *\n * Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares. The attribute text is\n * identical either way (`'[\"a\", \"b\"]'`), so markup does not change with the\n * declaration.\n */\n\n/**\n * Parses `raw` as a JSON array of strings.\n *\n * Returns `fallback` when the text is absent, unparseable, or not an array, so a\n * malformed declaration behaves like an omitted one. A parsed array keeps only\n * its string entries: every caller indexes or compares strings, and a stray\n * number would otherwise sit in the list without ever matching.\n *\n * An explicit `\"[]\"` is honoured as an empty list rather than falling back — it\n * is the only way to declare \"none\" against a non-empty default.\n *\n * @param raw - the attribute text, e.g. `'[\"password\"]'`.\n * @param fallback - the list to use when `raw` declares nothing usable.\n */\nexport function parseStringList(raw: string, fallback: readonly string[] = []): string[] {\n // The common case is an undeclared attribute, and `JSON.parse(\"\")` answers it\n // by throwing. Reaching the same fallback without building an exception keeps\n // the callers that ask per element off that path.\n const text = raw.trim();\n if (text.length === 0) return [...fallback];\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [...fallback];\n }\n\n if (!Array.isArray(parsed)) return [...fallback];\n return parsed.filter((entry): entry is string => typeof entry === \"string\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { SafeTimeout } from \"../utils/safe_timeout\";\nimport { StateRegions } from \"../utils/state_regions\";\nimport { parseStringList } from \"../utils/string_list\";\n\n/** Activity signals watched unless the consumer declares its own list. */\nconst DEFAULT_ACTIVITY_EVENTS = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"wheel\",\n \"touchstart\",\n \"scroll\",\n];\n\n/**\n * Headless inactivity / session-timeout detector: fires `idle` after `timeout` ms\n * with no user activity, an optional `prompt` `promptBefore` ms earlier, and\n * `active` when the user returns (no dedicated APG pattern; supports WCAG 2.2.1 by\n * giving the app a warning hook before a timeout).\n *\n * Markup contract (identifier: `stimeo--idle`):\n * <body data-controller=\"stimeo--idle\"\n * data-stimeo--idle-timeout-value=\"900000\"\n * data-stimeo--idle-prompt-before-value=\"60000\"\n * data-action=\"stimeo--idle:prompt->session#warn\n * stimeo--idle:idle->session#logout\">\n * <p data-stimeo--idle-target=\"prompt\" hidden>Your session expires in a minute.</p>\n * <p data-stimeo--idle-target=\"idle\" hidden>You have been signed out.</p>\n * </body>\n *\n * Activity events (`events`, passive) are watched on `document` with capture so\n * non-bubbling ones like `scroll` are seen anywhere; returning to a hidden tab\n * (`visibilitychange` → visible) counts as activity too. The controller element carries\n * `data-prompt` while the warning stands and `data-idle` once the timeout is reached,\n * and the optional `prompt` / `idle` targets are the regions those phases reveal:\n * declared where the page has something to show, shown while their phase holds and\n * hidden otherwise, whatever visibility the markup was authored with.\n *\n * `prompt` dispatches `{ remaining }`; `idle` and `active` dispatch `{}`.\n *\n * @remarks\n * Behavior only — it renders no warning UI (pair with Dialog/Confirm) and never\n * touches the server session. Timers are owned by `SafeTimeout` and the\n * listeners are removed on `disconnect()` (Turbo navigation included). Place one on\n * the root element. Every visit reconnects the controller and re-arms the timeout\n * from that moment — `data-turbo-permanent` keeps the element, not the elapsed count.\n */\nexport class IdleController extends Controller<HTMLElement> {\n static override values = {\n timeout: { type: Number, default: 900_000 },\n promptBefore: { type: Number, default: 0 },\n // A JSON list read through `parseStringList` rather than Stimulus's `Array`\n // type: that reader throws out of the value observer before any callback\n // runs, so one malformed attribute would stop the detector connecting.\n events: { type: String, default: \"\" },\n };\n static override targets = [\"prompt\", \"idle\"];\n static events = [\"prompt\", \"idle\", \"active\"] as const;\n\n declare timeoutValue: number;\n declare promptBeforeValue: number;\n declare eventsValue: string;\n declare readonly promptTargets: HTMLElement[];\n declare readonly idleTargets: HTMLElement[];\n\n /** The regions of the warning window, revealed alongside `data-prompt`. */\n readonly #promptRegions = new StateRegions({ whenTrue: () => this.promptTargets });\n /** The regions of the elapsed timeout, revealed alongside `data-idle`. */\n readonly #idleRegions = new StateRegions({ whenTrue: () => this.idleTargets });\n\n readonly #timeouts = new SafeTimeout();\n #idle = false;\n #prompted = false;\n /** Timestamp of the last activity; the timers self-reschedule against it. */\n #lastActivity = 0;\n /**\n * Activity types actually registered on `document`, so `disconnect()` unbinds the\n * same set even when `events` changed while connected (a Turbo morph can rewrite\n * the Value in place, and the removal must match the registration, not the Value).\n */\n #boundEvents: string[] = [];\n\n readonly #onActivity = (): void => {\n // Hot path (fires on every mousemove/scroll/wheel): just record the time. The\n // prompt/idle timers re-check this when they fire and reschedule if needed, so we\n // never tear down and re-create timers on each event (no per-event timer churn).\n this.#lastActivity = Date.now();\n if (this.#idle || this.#prompted) {\n // We were already idle/prompted, so the timers have lapsed — wake and re-arm.\n this.#idle = false;\n this.#prompted = false;\n this.#reflect();\n this.dispatch(\"active\", { detail: {} });\n this.#arm();\n }\n };\n\n readonly #onVisibility = (): void => {\n // Returning to the tab is activity; leaving it keeps the clock running (being\n // away counts toward the timeout).\n if (document.visibilityState === \"visible\") this.#onActivity();\n };\n\n override connect(): void {\n // Connecting always starts a fresh cycle (#arm() re-bases the clock), so the phase\n // that arrived with the DOM — a restored Turbo snapshot, a moved element — describes\n // a period this instance is not in. Reflecting the fresh cycle drops it; leaving it\n // would claim the user is idle for the whole next active window with no `active` to\n // correct it. Normalizing is not a transition, so nothing is dispatched here.\n this.#idle = false;\n this.#prompted = false;\n this.#reflect();\n this.#boundEvents = parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS);\n for (const type of this.#boundEvents) {\n document.addEventListener(type, this.#onActivity, { passive: true, capture: true });\n }\n document.addEventListener(\"visibilitychange\", this.#onVisibility);\n this.#arm();\n }\n\n override disconnect(): void {\n for (const type of this.#boundEvents) {\n document.removeEventListener(type, this.#onActivity, { capture: true });\n }\n this.#boundEvents = [];\n document.removeEventListener(\"visibilitychange\", this.#onVisibility);\n this.#timeouts.clearAll();\n }\n\n /**\n * Schedules the prompt and idle checks from the current activity baseline.\n *\n * @stimeoRuntimeOnly `timeout` and `promptBefore` time the checks this call arms.\n */\n #arm(): void {\n this.#timeouts.clearAll();\n this.#lastActivity = Date.now();\n const { promptBeforeValue: prompt, timeoutValue: timeout } = this;\n if (prompt > 0 && prompt < timeout) {\n this.#timeouts.set(() => this.#checkPrompt(), timeout - prompt);\n }\n this.#timeouts.set(() => this.#checkIdle(), timeout);\n }\n\n /**\n * Idle-timer callback: go idle only if there has genuinely been no activity for\n * `timeout`; otherwise reschedule for the remaining time. This lets activity events\n * stay O(1) (a timestamp write) while the deadline still tracks the last activity.\n *\n * @stimeoRuntimeOnly `timeout` sets the deadline this one check compares against; the phase it\n * shows follows the elapsed time.\n */\n #checkIdle(): void {\n const remaining = this.timeoutValue - (Date.now() - this.#lastActivity);\n if (remaining > 0) {\n this.#timeouts.set(() => this.#checkIdle(), remaining);\n return;\n }\n this.#idle = true;\n this.#prompted = false;\n this.#reflect();\n this.dispatch(\"idle\", { detail: {} });\n }\n\n /**\n * Prompt-timer callback: warn at `promptBefore` before the idle deadline. A window\n * narrowed while the cycle runs can push the warning onto the deadline or past it,\n * and the phase it belongs to is the one before idle, so a lapsed cycle keeps the\n * phase it reached. The next window opens with the cycle that activity arms.\n *\n * @stimeoRuntimeOnly `timeout` and `promptBefore` set the deadlines this one check compares\n * against; the phase it shows follows the elapsed time.\n */\n #checkPrompt(): void {\n if (this.#idle) return;\n const remaining =\n this.timeoutValue - this.promptBeforeValue - (Date.now() - this.#lastActivity);\n if (remaining > 0) {\n this.#timeouts.set(() => this.#checkPrompt(), remaining);\n return;\n }\n this.#prompted = true;\n this.#reflect();\n this.dispatch(\"prompt\", { detail: { remaining: this.promptBeforeValue } });\n }\n\n /** Applies the current phase to a warning region inserted or replaced at runtime. */\n promptTargetConnected(): void {\n this.#promptRegions.reflect(this.element, this.#prompted);\n }\n\n /** Applies the current phase to an idle region inserted or replaced at runtime. */\n idleTargetConnected(): void {\n this.#idleRegions.reflect(this.element, this.#idle);\n }\n\n /**\n * Writes the phase to the element and to the declared regions. Both hooks and both\n * regions are a pure function of the two flags, so every place that moves a flag ends\n * here and the markup can never disagree with the phase the detector is in.\n */\n #reflect(): void {\n const { element } = this;\n if (this.#prompted) element.setAttribute(\"data-prompt\", \"true\");\n else element.removeAttribute(\"data-prompt\");\n if (this.#idle) element.setAttribute(\"data-idle\", \"true\");\n else element.removeAttribute(\"data-idle\");\n this.#promptRegions.reflect(element, this.#prompted);\n this.#idleRegions.reflect(element, this.#idle);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/timer_bounds.ts","../../src/utils/number_bounds.ts","../../src/utils/coerce.ts","../../src/utils/number_value.ts","../../src/utils/safe_timeout.ts","../../src/utils/shared_registry.ts","../../src/utils/state_regions.ts","../../src/utils/string_list.ts","../../src/controllers/idle_controller.ts"],"names":[],"mappings":"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","file":"idle_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 * Largest delay a timer can hold: the platform stores it in a 32-bit signed\n * integer, and anything larger overflows to `1`, so a delay meant to be far in\n * the future fires almost immediately. A declared delay above this bound names\n * no delay at all, and a controller reading one falls back to its default.\n */\nexport const MAX_TIMER_DELAY_MS = 2_147_483_647;\n","import { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/** Semantic constraints on an already decoded number, independent of the DOM. */\nexport interface NumberBounds {\n /** Finite numbers are required, including when this field is omitted. */\n readonly finite?: true;\n /** Inclusive lower endpoint. */\n readonly min?: number;\n /** Inclusive upper endpoint. */\n readonly max?: number;\n /** Exclusive lower endpoint. */\n readonly exclusiveMin?: number;\n /** Require a number without a fractional part. */\n readonly integer?: boolean;\n /** Exact decoded numbers accepted by a discrete contract. */\n readonly allowedValues?: readonly number[];\n /** JSON-safe exceptions to finite-number checking for unbounded endpoints. */\n readonly allowInfinity?: \"negative\" | \"positive\" | \"both\";\n}\n\n/** One constraint for each Number Value, including shorthand declarations. */\nexport type NumberValueConstraints<Values> = {\n readonly [Key in keyof Values as Values[Key] extends\n | NumberConstructor\n | { type: NumberConstructor }\n ? Key\n : never]: NumberBounds;\n};\n\n/** Reusable semantic domains; fractional inputs remain fractional unless excluded. */\nexport const NUMBER_BOUNDS = {\n finite: { finite: true },\n nonNegative: { finite: true, min: 0 },\n positive: { finite: true, exclusiveMin: 0 },\n nonNegativeInteger: { finite: true, min: 0, integer: true },\n positiveInteger: { finite: true, exclusiveMin: 0, integer: true },\n lowerBound: { finite: true, allowInfinity: \"negative\" },\n upperBound: { finite: true, allowInfinity: \"positive\" },\n timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS },\n positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS },\n} as const satisfies Record<string, NumberBounds>;\n\n/**\n * Checks all declared bounds without rounding, clamping, or coercing the input.\n * Infinity exceptions relax only finite checking; every other bound still applies.\n */\nexport function matchesNumberBounds(value: number, bounds: NumberBounds): boolean {\n if (!Number.isFinite(value)) {\n const direction = value === Infinity ? \"positive\" : value === -Infinity ? \"negative\" : null;\n if (direction === null) return false;\n if (bounds.allowInfinity !== \"both\" && bounds.allowInfinity !== direction) return false;\n }\n if (bounds.min !== undefined && value < bounds.min) return false;\n if (bounds.max !== undefined && value > bounds.max) return false;\n if (bounds.exclusiveMin !== undefined && value <= bounds.exclusiveMin) return false;\n if (bounds.integer && !Number.isInteger(value)) return false;\n if (bounds.allowedValues !== undefined && !bounds.allowedValues.includes(value)) return false;\n return true;\n}\n\n/** Decodes a Number Value literal; action params use their own JSON decoder. */\nexport function decodeNumberValue(raw: string): number {\n return Number(raw.replace(/_/g, \"\"));\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus decodes action params as JSON when possible and leaves other text\n * untouched. CustomEvent detail can also supply a number or numeric string.\n * The reader accepts those two types while rejecting blank and non-finite input.\n */\n\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/**\n * Reads a finite number or nonblank numeric string; other input returns `null`.\n * Blank strings cannot silently reset a value to zero.\n */\nexport function toFiniteNumber(raw: unknown): number | null {\n if (typeof raw !== \"number\" && typeof raw !== \"string\") return null;\n if (typeof raw === \"string\" && raw.trim().length === 0) return null;\n const value = Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Returns an accepted decoded Number Value or the caller's fallback unchanged.\n * The caller owns the fallback contract; this reader does not parse or write a Value.\n */\nexport function readNumber(raw: number, fallback: number, bounds: NumberBounds): number {\n return matchesNumberBounds(raw, bounds) ? raw : fallback;\n}\n","import { readNumber } from \"./coerce\";\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** The instance identity carried by a Number Value read. */\nexport interface NumberValueOwner {\n readonly identifier: string;\n readonly element: Element;\n}\n\n/** Reads current declarations at the point of use without rewriting their attributes. */\nexport class NumberValueReader {\n /** The last rejected literal per Value; no history grows for an unchanged instance. */\n readonly #lastRejected = new Map<string, string>();\n\n /** Resolves the declared number using its own fallback and shared class contract. */\n read(\n owner: NumberValueOwner,\n name: string,\n raw: number,\n fallback: number,\n bounds: NumberBounds,\n ): number {\n const resolved = readNumber(raw, fallback, bounds);\n if (matchesNumberBounds(raw, bounds)) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;\n const literal = owner.element.getAttribute(attribute);\n if (literal === null) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n if (this.#lastRejected.get(name) !== literal) {\n this.#lastRejected.set(name, literal);\n console.warn(\n `Stimeo UI: \"${owner.identifier}\" has an invalid number Value \"${name}\" declaration ${JSON.stringify(literal)}; using ${fallback}.`,\n );\n }\n return resolved;\n }\n}\n","/**\n * Self-cleaning timer registries shared by Stimeo controllers.\n *\n * Stimulus controllers frequently schedule `setTimeout` / `setInterval` work\n * (auto-dismiss, debouncing, polling). When the element leaves the DOM — a\n * Turbo Drive navigation, a Turbo Stream replacement, or any `disconnect()` —\n * orphaned timers keep firing against a detached controller, leaking memory and\n * mutating stale state. {@link SafeTimeout} and {@link SafeInterval} track every\n * timer they create so a single {@link TimerRegistry.clearAll | clearAll()} call\n * in `disconnect()` tears them all down.\n *\n * These are intentionally low-level primitives: they own *registration and\n * cleanup only*. Higher-level policy (pause/resume, remaining-time accounting)\n * stays in the individual controllers so per-widget semantics are not flattened\n * into a lowest-common-denominator helper.\n */\n\nexport { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/**\n * Shared registry bookkeeping for the timeout/interval variants.\n *\n * Subclasses provide the scheduling primitive ({@link schedule}) and its matching\n * canceller ({@link cancel}); this base owns the set of live ids plus the\n * per-id and bulk teardown shared by both.\n */\nabstract class TimerRegistry {\n /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */\n protected readonly ids = new Set<number>();\n\n /** Schedules the underlying platform timer and returns its id. */\n protected abstract schedule(callback: () => void, delay: number): number;\n\n /** Cancels the underlying platform timer for `id`. */\n protected abstract cancel(id: number): void;\n\n /**\n * Cancels a single tracked timer.\n *\n * No-ops if the id is unknown (already cleared, fired, or never owned by this\n * registry), so callers can clear defensively without guarding.\n */\n clear(id: number): void {\n if (this.ids.delete(id)) {\n this.cancel(id);\n }\n }\n\n /**\n * Cancels every tracked timer. Call this from a controller's `disconnect()`\n * to guarantee no timer outlives the element.\n */\n clearAll(): void {\n for (const id of this.ids) {\n this.cancel(id);\n }\n this.ids.clear();\n }\n\n /** Number of timers currently tracked (pending). */\n get size(): number {\n return this.ids.size;\n }\n}\n\n/**\n * `setTimeout` wrapper that auto-forgets each timer once it fires and supports\n * bulk teardown on disconnect.\n *\n * @example\n * ```ts\n * #timers = new SafeTimeout();\n *\n * connect() {\n * this.#timers.set(() => this.dismiss(), 5000);\n * }\n *\n * disconnect() {\n * this.#timers.clearAll();\n * }\n * ```\n */\nexport class SafeTimeout extends TimerRegistry {\n /**\n * Schedules `callback` after `delay` ms and returns the timer id.\n *\n * The id is removed from the registry automatically when the timeout fires,\n * so {@link TimerRegistry.size | size} reflects only still-pending timers.\n */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(() => {\n this.ids.delete(id);\n callback();\n }, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setTimeout(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearTimeout(id);\n }\n}\n\n/**\n * `setInterval` wrapper that tracks every interval for bulk teardown on\n * disconnect. Unlike {@link SafeTimeout}, intervals are retained until they are\n * explicitly cleared because they fire repeatedly.\n *\n * @example\n * ```ts\n * #intervals = new SafeInterval();\n *\n * connect() {\n * this.#intervals.set(() => this.tick(), 1000);\n * }\n *\n * disconnect() {\n * this.#intervals.clearAll();\n * }\n * ```\n */\nexport class SafeInterval extends TimerRegistry {\n /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */\n set(callback: () => void, delay: number): number {\n const id = this.schedule(callback, delay);\n this.ids.add(id);\n return id;\n }\n\n protected schedule(callback: () => void, delay: number): number {\n return window.setInterval(callback, delay);\n }\n\n protected cancel(id: number): void {\n window.clearInterval(id);\n }\n}\n","/**\n * 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 consumer declares once about the regions one boolean state reveals. */\nexport interface StateRegionsOptions {\n /** Returns the declared regions shown while the state holds. */\n readonly whenTrue: () => readonly HTMLElement[];\n /**\n * Returns the declared regions shown while the state does not hold. Omitted by a\n * state whose other side has nothing of its own to show.\n */\n readonly whenFalse?: () => readonly HTMLElement[];\n}\n\n/** One instance's hold on one region's `hidden`, in the stack every instance shares. */\ninterface RegionHold {\n /** The instance holding it; `null` for a hold read from a copy's record until it is taken. */\n instance: object | null;\n readonly record: string;\n /** What the author wrote: the `hidden` it hands back with nothing beneath it. */\n readonly authored: string | null;\n /** Whether the visibility it wrote last hides the region. */\n written: boolean;\n /** Whether it hands back what the hold beneath it wrote last instead. */\n linked: boolean;\n}\n\n/** What an instance's record on a region says. */\ninterface RegionRecord {\n readonly authored: string | null;\n readonly written: boolean;\n readonly linked: boolean;\n /** The records of the other owners' holds beneath it, nearest first. */\n readonly beneath: readonly string[];\n}\n\n/** The holds on each region and every instance's record name. */\ninterface StateRegionsRegistry {\n readonly stacks: WeakMap<HTMLElement, RegionHold[]>;\n readonly names: Set<string>;\n}\n\n/**\n * Owns `hidden` on the regions a boolean state reveals.\n *\n * A region is a **declared** target, so the consumer decides whether one exists at\n * all; where it does, which side is shown is a pure function of the state and the\n * controller writes it unconditionally. An authored `hidden` is therefore not read\n * back — the first reflection after a connection settles it, and a restored DOM\n * cannot leave a region contradicting the state it belongs to.\n *\n * The regions arrive as the caller's own target arrays, which Stimulus has already\n * scoped to this instance: a nested controller registered under the same identifier\n * keeps its own regions. `host` narrows that set further, to the regions inside one\n * trigger or item, so a widget with several of them reflects each independently.\n *\n * **Two sides are a pair.** Where the state has a `whenFalse` side and only one half\n * sits inside `host`, the half that is there is left in view: hiding it would take\n * the only label with it, and the static check names the missing one instead. What\n * the author wrote on a lone half is theirs and stays — only a half this instance\n * took out of view is given back, which is what a pair losing one side at runtime\n * leaves behind.\n *\n * `hidden` moves only where it changes, and the record below only where what it says changes:\n * a transition costs a consumer watching its own subtree for attribute mutations two of them on\n * each region it moves — `hidden` and the record — and a reflection that moves nothing costs\n * none. A write that moves no `hidden` — the page moved the region first, or another\n * instance's hold lies above this one — changes only the record.\n *\n * **Every instance shares one stack of holds per region**, whatever its owner and whichever\n * file of the package constructed it (the stacks are one per page): the holds sit in the order\n * their instances first wrote the region, each with the visibility it wrote last. The region's\n * visibility is the highest hold's that carries it, and the page's when none does. A write\n * moves `hidden` unless a hold above the writer carries the region's visibility, so the top\n * hold always shows its writes and the page's visibility gives way to the next write of any\n * hold. A release gives a visibility back only from the hold the region's visibility is: the\n * one the hold beneath it wrote last, or the author's `hidden` once nothing is beneath it; any\n * other release only leaves the stack. Holds that wrote the same visibility are told apart by\n * the stack: the higher one keeps it. A first write over a top hold whose visibility the page\n * has replaced takes the region's `hidden` as the author's. Two instances of one owner on one\n * region stack the same way.\n *\n * **The stack travels with the region.** While the region carries anything but what the author\n * wrote under its top hold, or a hold lies on another hold or is covered by one, the region\n * carries that hold's owner's record `data-<owner>-hidden-region`: JSON of what the author\n * wrote (a string, or `null` for an absent attribute), whether the visibility written last\n * hides the region, `false` where the hold hands back the author's `hidden` rather than the\n * latest visibility of the hold beneath it, and the records of every hold beneath it, nearest\n * first; the last release of an owner removes it. A copy of the region — a page Turbo restores\n * from its cache — therefore carries the stack, read back under the record names of the\n * instances constructed so far and ordered by the records each hold lists beneath it, and an\n * instance that reflects, gives back or releases the copy takes the hold its owner's record\n * describes over in place. Complete records for distinct owners preserve their order across\n * a copy, including release without a new reflection. Records describe owners, not instances:\n * one owner holding the region more than once leaves only its topmost hold's record, which\n * the first of its instances to write or release on the copy takes. Its lower holds and\n * positions are absent; mutually listed owners cannot express their full live order. A hold\n * that showed the author's `hidden` alone carries no record and is taken by its first write\n * on the copy. Missing or unreadable records also leave their holds unknown, so copying the\n * DOM does not promise identical decisions for every live stack. A new stack with no readable\n * record takes the current value as the author's; a morph can remove records while existing\n * holds remain in memory until released.\n *\n * @example\n * ```ts\n * readonly #labels = new StateRegions(\n * {\n * whenTrue: () => this.expandedLabelTargets,\n * whenFalse: () => this.collapsedLabelTargets,\n * },\n * this.identifier,\n * );\n *\n * #reflect(): void {\n * this.#labels.reflect(this.triggerTarget, this.#expanded);\n * }\n * ```\n */\nexport class StateRegions {\n /** The holds on each region, bottom first, and the record names; one per page. */\n static readonly #registry = sharedRegistry(\n \"stimeo-ui.state-regions.registry.v1\",\n (): StateRegionsRegistry => ({ stacks: new WeakMap(), names: new Set() }),\n );\n readonly #whenTrue: () => readonly HTMLElement[];\n readonly #whenFalse: (() => readonly HTMLElement[]) | null;\n readonly #record: string;\n\n /**\n * @param options - The declared regions of each side.\n * @param owner - Names the record on a region; the controller's identifier.\n */\n constructor(options: StateRegionsOptions, owner: string) {\n this.#whenTrue = options.whenTrue;\n this.#whenFalse = options.whenFalse ?? null;\n this.#record = `data-${owner}-hidden-region`;\n StateRegions.#registry.names.add(this.#record);\n }\n\n /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */\n reflect(host: Element, isTrue: boolean): void {\n const shown = this.#inside(host, this.#whenTrue());\n if (!this.#whenFalse) {\n for (const region of shown) this.#write(region, !isTrue);\n return;\n }\n const hiddenSide = this.#inside(host, this.#whenFalse());\n if (shown.length === 0 || hiddenSide.length === 0) {\n for (const region of shown) this.#give(region);\n for (const region of hiddenSide) this.#give(region);\n return;\n }\n for (const region of shown) this.#write(region, !isTrue);\n for (const region of hiddenSide) this.#write(region, isTrue);\n }\n\n /**\n * Restores the regions this instance wrote inside `host` to what the hold beneath it wrote last\n * or the authored `hidden`, unless changed since; found by scan, as the declared lists stop\n * resolving with targets.\n */\n release(host: Element): void {\n for (const region of [host, ...host.querySelectorAll(\"*\")]) {\n if (!(region instanceof HTMLElement)) continue;\n const stack = this.#held(region);\n const hold = stack && this.#take(stack);\n if (!stack || !hold) continue;\n const owns = region.hidden === hold.written && !this.#covered(region, 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 if (owns) {\n const below = hold.linked ? stack[index - 1] : undefined;\n if (below) {\n if (region.hidden !== below.written) region.hidden = below.written;\n } else if (region.getAttribute(\"hidden\") !== hold.authored) {\n if (hold.authored === null) region.removeAttribute(\"hidden\");\n else region.setAttribute(\"hidden\", hold.authored);\n }\n }\n this.#mark(region, stack);\n }\n }\n\n /** Writes `hidden` where it moves, unless a hold above this one carries the region's. */\n #write(region: HTMLElement, hidden: boolean): void {\n const stack = this.#stack(region);\n const hold = this.#take(stack) ?? this.#acquire(region, stack);\n const shows = !this.#covered(region, stack, hold);\n hold.written = hidden;\n if (shows && region.hidden !== hidden) region.hidden = hidden;\n this.#mark(region, stack);\n }\n\n /** Returns a region this instance hid; one it never hid keeps what it carries. */\n #give(region: HTMLElement): void {\n const stack = this.#held(region);\n if (stack && this.#take(stack)?.written) this.#write(region, false);\n }\n\n /** The region's stack, while it has one or carries this owner's record. */\n #held(region: HTMLElement): RegionHold[] | undefined {\n return StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record)\n ? this.#stack(region)\n : undefined;\n }\n\n /** The region's stack, read from its records the first time. */\n #stack(region: HTMLElement): RegionHold[] {\n const stacks = StateRegions.#registry.stacks;\n const stack = stacks.get(region) ?? this.#seed(region);\n stacks.set(region, stack);\n return stack;\n }\n\n /** The holds a copy's records describe, each above every hold its record lists beneath. */\n #seed(region: HTMLElement): RegionHold[] {\n const found: (RegionRecord & { readonly record: string })[] = [];\n for (const record of StateRegions.#registry.names) {\n const read = this.#recorded(region, 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(({ record, authored, written, linked }, index) => ({\n instance: null,\n record,\n authored,\n written,\n linked: linked && index > 0,\n }));\n }\n\n /** Its hold in `stack`, taking over one read from its record. */\n #take(stack: RegionHold[]): RegionHold | undefined {\n const hold =\n stack.find((held) => held.instance === this) ??\n stack.find((held) => held.instance === null && held.record === this.#record);\n if (hold) hold.instance = this;\n return hold;\n }\n\n /** Whether a hold above `hold` carries the region's visibility, then that hold's. */\n #covered(region: HTMLElement, stack: readonly RegionHold[], hold: RegionHold): boolean {\n return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);\n }\n\n /** A new hold on top, over the top hold while the region shows what it wrote. */\n #acquire(region: HTMLElement, stack: RegionHold[]): RegionHold {\n const top = stack.at(-1);\n const over = top && region.hidden === top.written ? top : undefined;\n const hold: RegionHold = {\n instance: this,\n record: this.#record,\n authored: over ? over.authored : region.getAttribute(\"hidden\"),\n written: false,\n linked: over !== undefined,\n };\n stack.push(hold);\n return hold;\n }\n\n /** Leaves each owner's record of its topmost hold, or none where nothing needs one. */\n #mark(region: HTMLElement, stack: readonly RegionHold[]): 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 records.set(\n hold.record,\n region.getAttribute(\"hidden\") === hold.authored &&\n beneath.length === 0 &&\n index === stack.length - 1\n ? null\n : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath]),\n );\n });\n for (const [name, recorded] of records) {\n if (region.getAttribute(name) === recorded) continue;\n if (recorded === null) region.removeAttribute(name);\n else region.setAttribute(name, recorded);\n }\n }\n\n /**\n * The author's `hidden`, the visibility written last, whether it hands back the visibility\n * beneath it (`false` before the names when it does not) and the records beneath that the\n * record `name` on `region` holds.\n */\n #recorded(region: HTMLElement, name: string): RegionRecord | null {\n try {\n const parsed: unknown = JSON.parse(region.getAttribute(name) ?? \"null\");\n if (!Array.isArray(parsed)) return null;\n const [authored, written, ...rest] = parsed as unknown[];\n if (authored !== null && typeof authored !== \"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 typeof written === \"boolean\" ? { authored, written, linked, beneath } : null;\n } catch {\n return null;\n }\n }\n\n /** The declared regions that sit within `host`, which may be the host itself. */\n #inside(host: Element, regions: readonly HTMLElement[]): HTMLElement[] {\n return regions.filter((region) => host.contains(region));\n }\n}\n","/**\n * JSON string-list parsing for the controllers that take a list as a Value.\n *\n * Stimulus offers an `Array` Value type, but its reader runs `JSON.parse` inside\n * the value observer — **before** the `<name>ValueChanged` callback — and\n * rethrows on malformed text. The throw propagates out of the observer that runs\n * during connection, so a single unparseable attribute stops the controller from\n * connecting at all: no lifecycle callback runs and the element is left inert\n * rather than degraded. A consumer cannot guard against it either, because the\n * callback that would hold the `try` never runs.\n *\n * Declaring the attribute as a `String` Value and parsing it here keeps a\n * malformed declaration local to the value it declares. The attribute text is\n * identical either way (`'[\"a\", \"b\"]'`), so markup does not change with the\n * declaration.\n */\n\n/**\n * Parses `raw` as a JSON array of strings.\n *\n * Returns `fallback` when the text is absent, unparseable, or not an array, so a\n * malformed declaration behaves like an omitted one. A parsed array keeps only\n * its string entries: every caller indexes or compares strings, and a stray\n * number would otherwise sit in the list without ever matching.\n *\n * An explicit `\"[]\"` is honoured as an empty list rather than falling back — it\n * is the only way to declare \"none\" against a non-empty default.\n *\n * @param raw - the attribute text, e.g. `'[\"password\"]'`.\n * @param fallback - the list to use when `raw` declares nothing usable.\n */\nexport function parseStringList(raw: string, fallback: readonly string[] = []): string[] {\n // The common case is an undeclared attribute, and `JSON.parse(\"\")` answers it\n // by throwing. Reaching the same fallback without building an exception keeps\n // the callers that ask per element off that path.\n const text = raw.trim();\n if (text.length === 0) return [...fallback];\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return [...fallback];\n }\n\n if (!Array.isArray(parsed)) return [...fallback];\n return parsed.filter((entry): entry is string => typeof entry === \"string\");\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\nimport { MAX_TIMER_DELAY_MS, SafeTimeout } from \"../utils/safe_timeout\";\nimport { StateRegions } from \"../utils/state_regions\";\nimport { parseStringList } from \"../utils/string_list\";\n\n/** Activity signals watched unless the consumer declares its own list. */\nconst DEFAULT_ACTIVITY_EVENTS = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"wheel\",\n \"touchstart\",\n \"scroll\",\n];\n\n/**\n * Headless inactivity / session-timeout detector: fires `idle` after `timeout` ms\n * with no user activity, an optional `prompt` `promptBefore` ms earlier, and\n * `active` when the user returns (no dedicated APG pattern; supports WCAG 2.2.1 by\n * giving the app a warning hook before a timeout).\n *\n * Markup contract (identifier: `stimeo--idle`):\n * <body data-controller=\"stimeo--idle\"\n * data-stimeo--idle-timeout-value=\"900000\"\n * data-stimeo--idle-prompt-before-value=\"60000\"\n * data-action=\"stimeo--idle:prompt->session#warn\n * stimeo--idle:idle->session#logout\">\n * <p data-stimeo--idle-target=\"prompt\" hidden>Your session expires in a minute.</p>\n * <p data-stimeo--idle-target=\"idle\" hidden>You have been signed out.</p>\n * </body>\n *\n * Activity events (`events`, passive) are watched on `document` with capture so\n * non-bubbling ones like `scroll` are seen anywhere; returning to a hidden tab\n * (`visibilitychange` → visible) counts as activity too. The controller element carries\n * `data-prompt` while the warning stands and `data-idle` once the timeout is reached,\n * and the optional `prompt` / `idle` targets are the regions those phases reveal:\n * declared where the page has something to show, shown while their phase holds and\n * hidden otherwise, whatever visibility the markup was authored with.\n *\n * `prompt` dispatches `{ remaining }`, the milliseconds left before `idle`: `promptBefore`\n * when the warning comes on schedule, and less when a declaration changed mid-cycle has\n * already put the cycle inside the warning window. `idle` and `active` dispatch `{}`.\n *\n * The three Values follow a change made while connected (a Turbo morph, a script).\n * `events` swaps the watched set: the old listeners go and the declared ones are added\n * once. `timeout` and `promptBefore` move the pending checks to the deadlines the new\n * declaration sets from the same last activity, so the time already spent inactive\n * counts; a deadline the elapsed time has already passed is acted on at once, and a\n * cycle past both deadlines goes idle without a warning for a window that is over. A\n * phase already reached stays: a raised warning goes only on activity or at the\n * timeout, and an idle detector schedules nothing until activity arms the next cycle\n * with the declaration in force.\n *\n * @remarks\n * Behavior only — it renders no warning UI (pair with Dialog/Confirm) and never\n * touches the server session. Timers are owned by `SafeTimeout` and the\n * listeners are removed on `disconnect()` (Turbo navigation included). Place one on\n * the root element. Every visit reconnects the controller and re-arms the timeout\n * from that moment — `data-turbo-permanent` keeps the element, not the elapsed count.\n */\nexport class IdleController extends Controller<HTMLElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override values = {\n timeout: { type: Number, default: 900_000 },\n promptBefore: { type: Number, default: 0 },\n // A JSON list read through `parseStringList` rather than Stimulus's `Array`\n // type: that reader throws out of the value observer before any callback\n // runs, so one malformed attribute would stop the detector connecting.\n events: { type: String, default: \"\" },\n };\n\n static valueConstraints = {\n timeout: NUMBER_BOUNDS.positiveTimer,\n promptBefore: NUMBER_BOUNDS.nonNegative,\n } satisfies NumberValueConstraints<typeof IdleController.values>;\n static override targets = [\"prompt\", \"idle\"];\n static events = [\"prompt\", \"idle\", \"active\"] as const;\n\n declare timeoutValue: number;\n declare promptBeforeValue: number;\n declare eventsValue: string;\n declare readonly promptTargets: HTMLElement[];\n declare readonly idleTargets: HTMLElement[];\n\n /** The regions of the warning window, revealed alongside `data-prompt`. */\n readonly #promptRegions = new StateRegions(\n { whenTrue: () => this.promptTargets },\n this.identifier,\n );\n /** The regions of the elapsed timeout, revealed alongside `data-idle`. */\n readonly #idleRegions = new StateRegions({ whenTrue: () => this.idleTargets }, this.identifier);\n\n readonly #timeouts = new SafeTimeout();\n #idle = false;\n #prompted = false;\n /** Timestamp of the last activity; the timers self-reschedule against it. */\n #lastActivity = 0;\n /** Whether the controller is between `connect()` and `disconnect()`. */\n #connected = false;\n /**\n * The activity listeners on `document`. Releasing them goes through the signal they\n * were registered with, so the removal always matches the registration — capture flag\n * included — whatever `events` says by then.\n */\n readonly #activityListeners = new ListenerSet();\n /** The `visibilitychange` listener, held for the whole connection. */\n readonly #visibilityListeners = new ListenerSet();\n /** Activity types the listeners were last registered for, which a new `events` is compared with. */\n #boundEvents: string[] = [];\n\n readonly #onActivity = (): void => {\n // Hot path (fires on every mousemove/scroll/wheel): just record the time. The\n // prompt/idle timers re-check this when they fire and reschedule if needed, so we\n // never tear down and re-create timers on each event (no per-event timer churn).\n this.#lastActivity = Date.now();\n if (this.#idle || this.#prompted) {\n // We were already idle/prompted, so the timers have lapsed — wake and re-arm.\n this.#idle = false;\n this.#prompted = false;\n this.#reflect();\n this.dispatch(\"active\", { detail: {} });\n this.#arm();\n }\n };\n\n readonly #onVisibility = (): void => {\n // Returning to the tab is activity; leaving it keeps the clock running (being\n // away counts toward the timeout).\n if (document.visibilityState === \"visible\") this.#onActivity();\n };\n\n override connect(): void {\n // Connecting always starts a fresh cycle (#arm() re-bases the clock), so the phase\n // that arrived with the DOM — a restored Turbo snapshot, a moved element — describes\n // a period this instance is not in. Reflecting the fresh cycle drops it; leaving it\n // would claim the user is idle for the whole next active window with no `active` to\n // correct it. Normalizing is not a transition, so nothing is dispatched here.\n this.#connected = true;\n this.#idle = false;\n this.#prompted = false;\n this.#reflect();\n this.#bindActivity(parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS));\n this.#visibilityListeners.add(document, \"visibilitychange\", this.#onVisibility);\n this.#arm();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.#activityListeners.dispose();\n this.#visibilityListeners.dispose();\n this.#timeouts.clearAll();\n }\n\n /**\n * Swaps the watched activity set for the one a runtime change declares. A declaration\n * that parses to the list already registered changes nothing. Stimulus also calls this\n * ahead of `connect()`, which registers the list itself.\n */\n eventsValueChanged(): void {\n if (!this.#connected) return;\n const types = parseStringList(this.eventsValue, DEFAULT_ACTIVITY_EVENTS);\n if (\n types.length === this.#boundEvents.length &&\n types.every((type, index) => type === this.#boundEvents[index])\n )\n return;\n this.#bindActivity(types);\n }\n\n /**\n * Moves the pending checks to the deadlines a runtime change of `timeout` sets, from the\n * same last activity. Stimulus also calls this ahead of `connect()`, which arms the cycle\n * itself.\n */\n timeoutValueChanged(): void {\n if (this.#connected) this.#schedule();\n }\n\n /**\n * Moves the pending warning to the deadline a runtime change of `promptBefore` sets, from\n * the same last activity. Stimulus also calls this ahead of `connect()`, which arms the\n * cycle itself.\n */\n promptBeforeValueChanged(): void {\n if (this.#connected) this.#schedule();\n }\n\n /** Replaces the registered activity listeners with one passive capture listener per type. */\n #bindActivity(types: string[]): void {\n this.#activityListeners.dispose();\n for (const type of types) {\n this.#activityListeners.add(document, type, this.#onActivity, {\n passive: true,\n capture: true,\n });\n }\n this.#boundEvents = types;\n }\n\n /** Starts a cycle: the activity baseline is now, and the checks are scheduled from it. */\n #arm(): void {\n this.#lastActivity = Date.now();\n this.#schedule();\n }\n\n /**\n * Schedules the pending checks from the last activity and the current declarations,\n * replacing whatever was pending. A deadline already behind the elapsed time is checked\n * at once. The idle check is scheduled first, so when both deadlines have passed it runs\n * first and the warning check finds the cycle over. A warning that comes due at once\n * reports the time left from now rather than the whole window. A raised warning is not\n * scheduled again, and an idle detector schedules nothing.\n *\n * @stimeoRuntimeOnly `timeout` and `promptBefore` time the checks this call schedules.\n */\n #schedule(): void {\n this.#timeouts.clearAll();\n if (this.#idle) return;\n const elapsed = Date.now() - this.#lastActivity;\n const timeout = this.#safeTimeout;\n const prompt = this.#safePromptBefore;\n this.#timeouts.set(() => this.#checkIdle(), this.#delayFor(timeout - elapsed));\n if (this.#prompted || prompt <= 0 || prompt >= timeout) return;\n const left = Math.min(prompt, timeout - elapsed);\n this.#timeouts.set(() => this.#checkPrompt(left), this.#delayFor(timeout - prompt - elapsed));\n }\n\n /** The platform delay for a deadline `remaining` ms away: at once when past, capped when far. */\n #delayFor(remaining: number): number {\n return Math.min(Math.max(remaining, 0), MAX_TIMER_DELAY_MS);\n }\n\n /**\n * Idle-timer callback: go idle only if there has genuinely been no activity for\n * `timeout`; otherwise reschedule for the remaining time. This lets activity events\n * stay O(1) (a timestamp write) while the deadline still tracks the last activity.\n * A clock rewind can put that deadline beyond one platform timer; capped waits\n * recheck the same baseline without advancing the idle transition.\n *\n * @stimeoRuntimeOnly `timeout` sets the deadline this one check compares against; the phase it\n * shows follows the elapsed time.\n */\n #checkIdle(): void {\n const remaining = this.#safeTimeout - (Date.now() - this.#lastActivity);\n if (remaining > 0) {\n this.#timeouts.set(() => this.#checkIdle(), Math.min(remaining, MAX_TIMER_DELAY_MS));\n return;\n }\n this.#idle = true;\n this.#prompted = false;\n this.#reflect();\n this.dispatch(\"idle\", { detail: {} });\n }\n\n /**\n * Prompt-timer callback: warn at `promptBefore` before the idle deadline. A window\n * narrowed while the cycle runs can push the warning onto the deadline or past it,\n * and the phase it belongs to is the one before idle, so a lapsed cycle keeps the\n * phase it reached. The next window opens with the cycle that activity arms.\n * Clock rewinds use capped waits while retaining the same warning deadline.\n *\n * @param left - the time before idle that the warning reports; a check that has to wait\n * again reports the whole window when it comes due.\n * @stimeoRuntimeOnly `timeout` and `promptBefore` set the deadlines this one check compares\n * against; the phase it shows follows the elapsed time.\n */\n #checkPrompt(left = this.#safePromptBefore): void {\n if (this.#idle) return;\n const remaining =\n this.#safeTimeout - this.#safePromptBefore - (Date.now() - this.#lastActivity);\n if (remaining > 0) {\n this.#timeouts.set(() => this.#checkPrompt(), Math.min(remaining, MAX_TIMER_DELAY_MS));\n return;\n }\n this.#prompted = true;\n this.#reflect();\n this.dispatch(\"prompt\", { detail: { remaining: left } });\n }\n\n /** Applies the current phase to a warning region inserted or replaced at runtime. */\n promptTargetConnected(): void {\n this.#promptRegions.reflect(this.element, this.#prompted);\n }\n\n /** Applies the current phase to an idle region inserted or replaced at runtime. */\n idleTargetConnected(): void {\n this.#idleRegions.reflect(this.element, this.#idle);\n }\n\n /**\n * Writes the phase to the element and to the declared regions. Both hooks and both\n * regions are a pure function of the two flags, so every place that moves a flag ends\n * here and the markup can never disagree with the phase the detector is in.\n */\n #reflect(): void {\n const { element } = this;\n if (this.#prompted) element.setAttribute(\"data-prompt\", \"true\");\n else element.removeAttribute(\"data-prompt\");\n if (this.#idle) element.setAttribute(\"data-idle\", \"true\");\n else element.removeAttribute(\"data-idle\");\n this.#promptRegions.reflect(element, this.#prompted);\n this.#idleRegions.reflect(element, this.#idle);\n }\n /** Current `timeout` declaration resolved against its numeric contract. */\n get #safeTimeout(): number {\n return this.#numbers.read(\n this,\n \"timeout\",\n this.timeoutValue,\n IdleController.values.timeout.default,\n IdleController.valueConstraints.timeout,\n );\n }\n\n /** Current `promptBefore` declaration resolved against its numeric contract. */\n get #safePromptBefore(): number {\n return this.#numbers.read(\n this,\n \"promptBefore\",\n this.promptBeforeValue,\n IdleController.values.promptBefore.default,\n IdleController.valueConstraints.promptBefore,\n );\n }\n}\n"]}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/input_mask_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/composition_tracker.ts
|
|
6
3
|
var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
7
4
|
"insertCompositionText",
|
|
8
5
|
"insertFromComposition",
|
|
@@ -14,13 +11,11 @@ var CompositionTracker = class {
|
|
|
14
11
|
#activeTargets = /* @__PURE__ */ new Set();
|
|
15
12
|
#onStart;
|
|
16
13
|
#onEnd;
|
|
17
|
-
/** The field whose confirming `input` is still owed, while the window is open. */
|
|
18
14
|
#confirmedTarget = null;
|
|
19
15
|
constructor(options = {}) {
|
|
20
16
|
this.#onStart = options.onStart;
|
|
21
17
|
this.#onEnd = options.onEnd;
|
|
22
18
|
}
|
|
23
|
-
/** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
|
|
24
19
|
observe(target) {
|
|
25
20
|
if (this.#observedTargets.has(target)) return;
|
|
26
21
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
@@ -28,7 +23,6 @@ var CompositionTracker = class {
|
|
|
28
23
|
target.addEventListener("keydown", this.#handleKeydown);
|
|
29
24
|
this.#observedTargets.add(target);
|
|
30
25
|
}
|
|
31
|
-
/** Stops tracking one target and clears any active composition it owned. */
|
|
32
26
|
unobserve(target) {
|
|
33
27
|
if (!this.#observedTargets.delete(target)) return;
|
|
34
28
|
target.removeEventListener("compositionstart", this.#handleStart);
|
|
@@ -37,7 +31,6 @@ var CompositionTracker = class {
|
|
|
37
31
|
this.#activeTargets.delete(target);
|
|
38
32
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
39
33
|
}
|
|
40
|
-
/** Releases every listener and clears state so reconnect starts cleanly. */
|
|
41
34
|
disconnect() {
|
|
42
35
|
for (const target of this.#observedTargets) {
|
|
43
36
|
target.removeEventListener("compositionstart", this.#handleStart);
|
|
@@ -48,16 +41,9 @@ var CompositionTracker = class {
|
|
|
48
41
|
this.#activeTargets.clear();
|
|
49
42
|
this.#confirmedTarget = null;
|
|
50
43
|
}
|
|
51
|
-
/** True when lifecycle tracking or the current event reports composition. */
|
|
52
44
|
isComposing(event) {
|
|
53
45
|
return this.#activeTargets.size > 0 || event?.isComposing === true;
|
|
54
46
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Whether `event` is the `input` echoing the composition just confirmed.
|
|
57
|
-
*
|
|
58
|
-
* Asking closes the window either way, so one confirmation is folded at most
|
|
59
|
-
* once and a consumer asks once per `input`.
|
|
60
|
-
*/
|
|
61
47
|
consumesConfirmedInput(event) {
|
|
62
48
|
const confirmed = this.#confirmedTarget;
|
|
63
49
|
this.#confirmedTarget = null;
|
|
@@ -75,13 +61,11 @@ var CompositionTracker = class {
|
|
|
75
61
|
this.#confirmedTarget = event.target;
|
|
76
62
|
this.#onEnd?.(event);
|
|
77
63
|
};
|
|
78
|
-
/** A key on an observed field opens an edit of its own, so no echo is owed. */
|
|
79
64
|
#handleKeydown = () => {
|
|
80
65
|
this.#confirmedTarget = null;
|
|
81
66
|
};
|
|
82
67
|
};
|
|
83
68
|
|
|
84
|
-
// src/utils/declared_value.ts
|
|
85
69
|
function parseDeclared(raw, parse, fallback) {
|
|
86
70
|
try {
|
|
87
71
|
return parse(raw);
|
|
@@ -105,7 +89,6 @@ function parseJsonObject(raw) {
|
|
|
105
89
|
return parsed;
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
// src/utils/focus_candidate.ts
|
|
109
92
|
function inheritsFieldsetDisabled(control) {
|
|
110
93
|
let fieldset = control.closest("fieldset[disabled]");
|
|
111
94
|
while (fieldset) {
|
|
@@ -116,7 +99,6 @@ function inheritsFieldsetDisabled(control) {
|
|
|
116
99
|
return false;
|
|
117
100
|
}
|
|
118
101
|
|
|
119
|
-
// src/utils/half_width.ts
|
|
120
102
|
var FULL_WIDTH_SHIFT = 65248;
|
|
121
103
|
function halfWidthChar(char) {
|
|
122
104
|
if (char >= "\uFF01" && char <= "\uFF5E") {
|
|
@@ -125,7 +107,70 @@ function halfWidthChar(char) {
|
|
|
125
107
|
return char === "\u3000" ? " " : char;
|
|
126
108
|
}
|
|
127
109
|
|
|
128
|
-
|
|
110
|
+
var ListenerSet = class {
|
|
111
|
+
#abort = new AbortController();
|
|
112
|
+
add(target, type, handler, options) {
|
|
113
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
114
|
+
}
|
|
115
|
+
dispose() {
|
|
116
|
+
this.#abort.abort();
|
|
117
|
+
this.#abort = new AbortController();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
var MicrotaskCoalescer = class {
|
|
122
|
+
#run;
|
|
123
|
+
#queued = false;
|
|
124
|
+
#active = false;
|
|
125
|
+
#generation = 0;
|
|
126
|
+
constructor(run) {
|
|
127
|
+
this.#run = run;
|
|
128
|
+
}
|
|
129
|
+
activate() {
|
|
130
|
+
this.#active = true;
|
|
131
|
+
}
|
|
132
|
+
cancel() {
|
|
133
|
+
this.#active = false;
|
|
134
|
+
this.#queued = false;
|
|
135
|
+
this.#generation += 1;
|
|
136
|
+
}
|
|
137
|
+
schedule() {
|
|
138
|
+
if (!this.#active || this.#queued) return;
|
|
139
|
+
this.#queued = true;
|
|
140
|
+
const generation = this.#generation;
|
|
141
|
+
queueMicrotask(() => {
|
|
142
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
143
|
+
this.#queued = false;
|
|
144
|
+
this.#run();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
var MorphRenderWatcher = class {
|
|
150
|
+
#listeners = new ListenerSet();
|
|
151
|
+
#pass;
|
|
152
|
+
#element = null;
|
|
153
|
+
#onMorph = () => this.#pass.schedule();
|
|
154
|
+
constructor(run) {
|
|
155
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
156
|
+
}
|
|
157
|
+
observe(element) {
|
|
158
|
+
if (this.#element === element) return;
|
|
159
|
+
this.disconnect();
|
|
160
|
+
this.#element = element;
|
|
161
|
+
this.#pass.activate();
|
|
162
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
163
|
+
}
|
|
164
|
+
schedule() {
|
|
165
|
+
this.#pass.schedule();
|
|
166
|
+
}
|
|
167
|
+
disconnect() {
|
|
168
|
+
this.#listeners.dispose();
|
|
169
|
+
this.#pass.cancel();
|
|
170
|
+
this.#element = null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
129
174
|
var DEFAULT_TOKENS = {
|
|
130
175
|
"9": "\\d",
|
|
131
176
|
a: "[A-Za-z]",
|
|
@@ -208,7 +253,6 @@ function tokenCharOffset(tokenFlags, n) {
|
|
|
208
253
|
return -1;
|
|
209
254
|
}
|
|
210
255
|
var InputMaskController = class extends Controller {
|
|
211
|
-
/** The attribute above, in the namespace this controller is registered under. */
|
|
212
256
|
get #unmaskAttribute() {
|
|
213
257
|
return `data-${this.identifier}-${UNMASK_ATTR}`;
|
|
214
258
|
}
|
|
@@ -219,19 +263,10 @@ var InputMaskController = class extends Controller {
|
|
|
219
263
|
};
|
|
220
264
|
static actions = ["format"];
|
|
221
265
|
static events = ["change", "reconcile"];
|
|
222
|
-
/** The value this controller last committed, and the baseline both events compare. */
|
|
223
266
|
#lastValue = null;
|
|
224
267
|
#started = false;
|
|
225
|
-
/** Validated token map; the hot path never parses the `tokens` declaration. */
|
|
226
268
|
#tokens = compileTokens("");
|
|
227
|
-
/** The field's text when the running composition began. */
|
|
228
269
|
#textBeforeComposition = "";
|
|
229
|
-
/**
|
|
230
|
-
* Holds mid-composition input so the IME's uncommitted text is never rewritten,
|
|
231
|
-
* and formats the confirmed text once when the composition ends. A composition
|
|
232
|
-
* that ends on the text it began with committed nothing, so a re-format then is
|
|
233
|
-
* the page's: the mask changed while it ran.
|
|
234
|
-
*/
|
|
235
270
|
#composition = new CompositionTracker({
|
|
236
271
|
onStart: () => {
|
|
237
272
|
this.#textBeforeComposition = this.element.value;
|
|
@@ -241,7 +276,6 @@ var InputMaskController = class extends Controller {
|
|
|
241
276
|
this.#reformat(committedNothing ? "reconcile" : "edit");
|
|
242
277
|
}
|
|
243
278
|
});
|
|
244
|
-
/** Re-parses the declaration and re-formats under the tokens it now selects. */
|
|
245
279
|
tokensValueChanged() {
|
|
246
280
|
this.#tokens = compileTokens(this.tokensValue);
|
|
247
281
|
if (this.#started) this.#reformat("reconcile");
|
|
@@ -249,7 +283,6 @@ var InputMaskController = class extends Controller {
|
|
|
249
283
|
patternValueChanged() {
|
|
250
284
|
if (this.#started) this.#reformat("reconcile");
|
|
251
285
|
}
|
|
252
|
-
/** Clears a sink it stops maintaining so a submit cannot carry a stale raw value. */
|
|
253
286
|
unmaskToHiddenValueChanged() {
|
|
254
287
|
if (!this.#started) return;
|
|
255
288
|
if (!this.unmaskToHiddenValue) {
|
|
@@ -258,27 +291,23 @@ var InputMaskController = class extends Controller {
|
|
|
258
291
|
}
|
|
259
292
|
this.#reformat("reconcile");
|
|
260
293
|
}
|
|
294
|
+
#morph = new MorphRenderWatcher(() => this.#reformat("reconcile"));
|
|
261
295
|
connect() {
|
|
296
|
+
this.#morph.observe(this.element);
|
|
262
297
|
this.#started = true;
|
|
263
298
|
this.#composition.observe(this.element);
|
|
264
299
|
this.#lastValue = this.element.value;
|
|
265
300
|
this.#reformat("reconcile");
|
|
266
301
|
}
|
|
267
302
|
disconnect() {
|
|
303
|
+
this.#morph.disconnect();
|
|
268
304
|
this.#started = false;
|
|
269
305
|
this.#composition.disconnect();
|
|
270
306
|
}
|
|
271
|
-
/** Formats the field on input, preserving the caret. Bound via `data-action`. */
|
|
272
307
|
format(event) {
|
|
273
308
|
if (this.#composition.isComposing(event)) return;
|
|
274
309
|
this.#reformat("edit", deletionOf(event));
|
|
275
310
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Core reformat: mask the current value, restore the caret, sync the sink and
|
|
278
|
-
* the state hooks, and report a moved value under the event `cause` selects.
|
|
279
|
-
*
|
|
280
|
-
* @stimeoRenderRoot
|
|
281
|
-
*/
|
|
282
311
|
#reformat(cause, deletion = null) {
|
|
283
312
|
if (this.#composition.isComposing()) return;
|
|
284
313
|
const input = this.element;
|
|
@@ -309,8 +338,10 @@ var InputMaskController = class extends Controller {
|
|
|
309
338
|
this.#lastValue = raw;
|
|
310
339
|
return;
|
|
311
340
|
}
|
|
312
|
-
input.value
|
|
313
|
-
|
|
341
|
+
if (input.value !== result.masked) {
|
|
342
|
+
input.value = result.masked;
|
|
343
|
+
this.#restoreCaret(result.tokenFlags, anchor);
|
|
344
|
+
}
|
|
314
345
|
this.#publish(result, result.masked);
|
|
315
346
|
if (result.masked === this.#lastValue) return;
|
|
316
347
|
this.#lastValue = result.masked;
|
|
@@ -318,7 +349,6 @@ var InputMaskController = class extends Controller {
|
|
|
318
349
|
if (cause === "edit") this.dispatch("change", { detail });
|
|
319
350
|
else this.dispatch("reconcile", { detail });
|
|
320
351
|
}
|
|
321
|
-
/** Syncs the raw-value sink and the state hooks for the `shown` field text. */
|
|
322
352
|
#publish(result, shown) {
|
|
323
353
|
if (this.unmaskToHiddenValue) {
|
|
324
354
|
const sink = this.#resolveSink();
|
|
@@ -327,15 +357,9 @@ var InputMaskController = class extends Controller {
|
|
|
327
357
|
this.#flag("data-mask-complete", result.complete);
|
|
328
358
|
this.#flag("data-mask-empty", shown.length === 0);
|
|
329
359
|
}
|
|
330
|
-
/**
|
|
331
|
-
* How many token slots the text before the caret fills. Masking that prefix is
|
|
332
|
-
* what makes a rejected character — or a literal that also matches a token —
|
|
333
|
-
* count exactly as the rendering counts it, so the caret cannot drift.
|
|
334
|
-
*/
|
|
335
360
|
#significantBefore(prefix) {
|
|
336
361
|
return applyMask(prefix, this.patternValue, this.#tokens).unmasked.length;
|
|
337
362
|
}
|
|
338
|
-
/** Places the caret after the `n`-th token char (skipping following literals). */
|
|
339
363
|
#restoreCaret(tokenFlags, n) {
|
|
340
364
|
let position;
|
|
341
365
|
if (n <= 0) {
|
|
@@ -357,23 +381,6 @@ var InputMaskController = class extends Controller {
|
|
|
357
381
|
} catch {
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
|
-
/**
|
|
361
|
-
* The hidden raw-value sink for this input, resolved so several masked inputs
|
|
362
|
-
* can coexist in one form:
|
|
363
|
-
*
|
|
364
|
-
* 1. **Explicit pairing** — a sink whose attribute value names this input's
|
|
365
|
-
* `id` (`data-stimeo--input-mask-unmask="zip"`).
|
|
366
|
-
* 2. **Nearest container** — otherwise, walking up from the input, the first
|
|
367
|
-
* *value-less* sink in the closest ancestor. Wrapped input+sink pairs each
|
|
368
|
-
* find their own sink, and a single form-level sink is found the same
|
|
369
|
-
* way. A sink claimed by another input's id is never matched here.
|
|
370
|
-
*
|
|
371
|
-
* Both steps only ever consider the associated form's own controls, so a sink
|
|
372
|
-
* belonging to a different form (or to none) is never written, while one the
|
|
373
|
-
* `form` attribute associates from elsewhere in the document still resolves —
|
|
374
|
-
* for either the input or the sink, neither of which needs to contain or be
|
|
375
|
-
* contained by the form. A form-less input reads the document instead.
|
|
376
|
-
*/
|
|
377
384
|
#resolveSink() {
|
|
378
385
|
const candidates = this.#sinkCandidates();
|
|
379
386
|
const id = this.element.id;
|
|
@@ -389,12 +396,6 @@ var InputMaskController = class extends Controller {
|
|
|
389
396
|
}
|
|
390
397
|
return null;
|
|
391
398
|
}
|
|
392
|
-
/**
|
|
393
|
-
* Every sink the input's form owns, in document order — one collection read per
|
|
394
|
-
* resolution, so the cost tracks the form's own controls and not the depth of
|
|
395
|
-
* the markup around it. `form.elements` lists controls the `form` attribute
|
|
396
|
-
* associates from anywhere in the document, and lists nothing another form owns.
|
|
397
|
-
*/
|
|
398
399
|
#sinkCandidates() {
|
|
399
400
|
const form = this.element.form;
|
|
400
401
|
if (form === null) {
|
|
@@ -406,7 +407,6 @@ var InputMaskController = class extends Controller {
|
|
|
406
407
|
(element) => element instanceof HTMLInputElement && element.hasAttribute(this.#unmaskAttribute)
|
|
407
408
|
);
|
|
408
409
|
}
|
|
409
|
-
/** Sets a boolean `data-*` flag to `"true"` when `on`, else removes it. */
|
|
410
410
|
#flag(name, on) {
|
|
411
411
|
if (on) this.element.setAttribute(name, "true");
|
|
412
412
|
else this.element.removeAttribute(name);
|
|
@@ -414,5 +414,4 @@ var InputMaskController = class extends Controller {
|
|
|
414
414
|
};
|
|
415
415
|
|
|
416
416
|
export { InputMaskController, applyMask };
|
|
417
|
-
//# sourceMappingURL=input_mask_controller.js.map
|
|
418
417
|
//# sourceMappingURL=input_mask_controller.js.map
|