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/form_reset_watcher.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["/**\n * Runs a controller's reconciliation pass after a native form reset has\n * restored the form's own controls.\n *\n * **A microtask alone is not \"after the reset\", and the reason is where the\n * dispatch came from.** `form.reset()` fires the event and restores the\n * controls inside one script frame, so a microtask queued from a listener lands\n * after both. A reset the user triggers — activating a `reset` button — is\n * dispatched by the browser with an empty script stack, so the microtask\n * checkpoint runs the moment the listener returns: still inside the dispatch,\n * before the controls are restored and before a later listener has had its\n * chance to cancel. Reconciling there reads the pre-reset values and treats a\n * cancelled reset as a real one.\n *\n * The event itself tells the two origins apart. `eventPhase` returns to\n * `Event.NONE` at the end of dispatch, so a microtask that sees `NONE` is\n * already past the default action and settles on the spot — that is the\n * scripted path, and its timing is exactly a microtask. A microtask\n * that still sees a dispatch phase is the browser-driven path and settles on\n * the next animation frame instead: the default action completes before that\n * frame, and `defaultPrevented` has reached its final value. A frame is only\n * ever reached from a real user interaction, which requires a rendered\n * document, so the frame cannot starve.\n *\n * Pending frames are held per handle rather than collapsed into one slot: a set\n * needs no branch to stay accurate, and every handle in it is cancellable at\n * teardown.\n *\n * Scope is the timing and the ownership test only. *What* to reconcile — an\n * aggregate, a visibility pass, a re-read of every field — stays in the\n * controller, because no two consumers answer it the same way. Ownership stays\n * there too: a control associated through `form=` is reached by the consumer's\n * own `control.form` read.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #formReset = new FormResetWatcher(\n * (form) => this.fieldTargets.some((field) => field.form === form),\n * () => this.#reconcile.schedule(),\n * );\n *\n * connect() { this.#formReset.observe(); }\n * disconnect() { this.#formReset.disconnect(); }\n * ```\n */\nexport class FormResetWatcher {\n readonly #owns: (form: HTMLFormElement) => boolean;\n readonly #onReset: () => void;\n readonly #frames = new Set<number>();\n #generation = 0;\n\n /**\n * @param owns - whether `form` owns a control this controller derives from.\n * @param onReset - the reconciliation pass, run once per honoured reset.\n */\n constructor(owns: (form: HTMLFormElement) => boolean, onReset: () => void) {\n this.#owns = owns;\n this.#onReset = onReset;\n }\n\n /** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */\n observe(): void {\n document.addEventListener(\"reset\", this.#handleReset, true);\n }\n\n /** Unsubscribes and drops every pending pass; call from `disconnect()`. */\n disconnect(): void {\n document.removeEventListener(\"reset\", this.#handleReset, true);\n this.#generation += 1;\n for (const frame of this.#frames) cancelAnimationFrame(frame);\n this.#frames.clear();\n }\n\n readonly #handleReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#owns(form)) return;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A pass queued before teardown must not reach a disconnected controller.\n if (generation !== this.#generation) return;\n if (event.eventPhase === Event.NONE) {\n this.#settle(event);\n return;\n }\n const frame = requestAnimationFrame(() => {\n this.#frames.delete(frame);\n this.#settle(event);\n });\n this.#frames.add(frame);\n });\n };\n\n /** Runs the pass unless a listener cancelled the reset before it landed. */\n #settle(event: Event): void {\n if (!event.defaultPrevented) this.#onReset();\n }\n}\n","/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, so `dispose()` belongs exactly where\n * the `removeEventListener` block stood. 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 * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { FormResetWatcher } from \"../utils/form_reset_watcher\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * `change` and `reconcile` dispatch\n * `{ checked: boolean, indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }`.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). The `parent`\n * target is optional: a child-only group still exposes its aggregate on the root,\n * while a lone tri-state checkbox can use just `parent` and drive\n * `indeterminate` externally — with no `child` target the parent's own `checked`\n * and `indeterminate` stay the consumer's, because only children are\n * authoritative over them. At most one `parent` may be present.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * All child targets, including disabled ones, participate; omit the target to\n * exclude an input. The cascade does not synthesize native child `change`\n * events — one aggregate custom event describes the action.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root. Dynamic targets, checked-attribute changes, Turbo morphs, and native\n * form resets are reconciled from the children. Callers that assign the live\n * `checked` property directly must dispatch `change`, because property writes\n * are not observable by a `MutationObserver`.\n * - `stimeo--checkbox:change` is dispatched for the two public change actions;\n * `stimeo--checkbox:reconcile` is dispatched instead when a reconciliation —\n * not the user — moves the aggregate. Both carry `{ checked: boolean,\n * indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }` and describe\n * the aggregate even without a parent. Neither fires on connect.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly parentTargets: HTMLInputElement[];\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Collapses every lifecycle signal from one DOM update into one derived pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileFromChildren());\n readonly #listeners = new ListenerSet();\n readonly #formReset = new FormResetWatcher(\n (form) => this.#hasCheckboxOwnedBy(form),\n () => this.#reconcile.schedule(),\n );\n /** Aggregate this root last settled on, so a derived repair is reported once. */\n #committedState: CheckboxState | null = null;\n /** Watches authored checked-attribute changes on retained target elements. */\n readonly #checkedObserver = new MutationObserver((records) => {\n if (records.some((record) => this.#isManagedCheckbox(record.target))) {\n this.#reconcile.schedule();\n }\n });\n\n /** Reflects the initial aggregate and starts retained-DOM reconciliation. */\n override connect(): void {\n this.#reconcile.activate();\n this.#syncFromChildren();\n this.#checkedObserver.observe(this.element, {\n attributes: true,\n attributeFilter: [\"checked\"],\n subtree: true,\n });\n this.#listeners.add(this.element, \"turbo:morph-element\", this.#onMorph);\n this.#formReset.observe();\n }\n\n /** Releases the observer, global reset listener, and every pending reconciliation. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#checkedObserver.disconnect();\n this.#listeners.dispose();\n this.#formReset.disconnect();\n }\n\n /** Reconciles the aggregate for a parent added or replaced at runtime. */\n parentTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a parent target leaves the group. */\n parentTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate for a child added at runtime. */\n childTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a child leaves the group. */\n childTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.#reflect(checked ? \"all\" : \"none\", true);\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n this.#syncFromChildren();\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /**\n * Announces an aggregate this pass derived rather than the user. `change` stays\n * reserved for the two public actions, so automation never reads a repair as an edit.\n */\n #reconcileFromChildren(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"reconcile\", { detail });\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children. Writing state and reporting it are separate so the caller — not\n * a flag threaded through the write — decides which event describes the cause.\n */\n #syncFromChildren(): void {\n // A childless root reads its aggregate off the parent itself, so writing the\n // derived value back would clear a `checked` the consumer drives alongside\n // `indeterminate`. Only the children are authoritative over the parent.\n this.#reflect(this.#aggregate(), this.childTargets.length > 0);\n }\n\n /**\n * Reflects one aggregate state.\n *\n * @param writeParent - whether the state is authoritative over the `parent`\n * target's own `checked` / `indeterminate`.\n */\n #reflect(state: CheckboxState, writeParent: boolean): void {\n if (writeParent && this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n this.#committedState = state;\n }\n\n /** The settled aggregate as event detail, or `null` before anything has settled. */\n #settledDetail(): { checked: boolean; indeterminate: boolean; state: CheckboxState } | null {\n const state = this.#committedState;\n if (state === null) return null;\n return { checked: state === \"all\", indeterminate: state === \"partial\", state };\n }\n\n /** Reconciles retained targets after Turbo has finished morphing their live state. */\n readonly #onMorph = (): void => {\n this.#reconcile.schedule();\n };\n\n /** Whether a form owns at least one current parent or child target. */\n #hasCheckboxOwnedBy(form: HTMLFormElement): boolean {\n return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);\n }\n\n /** Whether an observed attribute mutation belongs to this controller's target set. */\n #isManagedCheckbox(node: Node): boolean {\n return (\n this.parentTargets.some((checkbox) => checkbox === node) ||\n this.childTargets.some((checkbox) => checkbox === node)\n );\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/form_reset_watcher.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["/**\n * Runs a controller's reconciliation pass after a native form reset has\n * restored the form's own controls.\n *\n * **A microtask alone is not \"after the reset\", and the reason is where the\n * dispatch came from.** `form.reset()` fires the event and restores the\n * controls inside one script frame, so a microtask queued from a listener lands\n * after both. A reset the user triggers — activating a `reset` button — is\n * dispatched by the browser with an empty script stack, so the microtask\n * checkpoint runs the moment the listener returns: still inside the dispatch,\n * before the controls are restored and before a later listener has had its\n * chance to cancel. Reconciling there reads the pre-reset values and treats a\n * cancelled reset as a real one.\n *\n * The event itself tells the two origins apart. `eventPhase` returns to\n * `Event.NONE` at the end of dispatch, so a microtask that sees `NONE` is\n * already past the default action and settles on the spot — that is the\n * scripted path, and its timing is exactly a microtask. A microtask\n * that still sees a dispatch phase is the browser-driven path and settles on\n * the next animation frame instead: the default action completes before that\n * frame, and `defaultPrevented` has reached its final value. A frame is only\n * ever reached from a real user interaction, which requires a rendered\n * document, so the frame cannot starve.\n *\n * Pending frames are held per handle rather than collapsed into one slot: a set\n * needs no branch to stay accurate, and every handle in it is cancellable at\n * teardown.\n *\n * Scope is the timing and the ownership test only. *What* to reconcile — an\n * aggregate, a visibility pass, a re-read of every field — stays in the\n * controller, because no two consumers answer it the same way. Ownership stays\n * there too: a control associated through `form=` is reached by the consumer's\n * own `control.form` read.\n *\n * @example\n * ```ts\n * readonly #formReset = new FormResetWatcher(\n * (form) => this.fieldTargets.some((field) => field.form === form),\n * () => this.#reconcile.schedule(),\n * );\n *\n * connect() { this.#formReset.observe(); }\n * disconnect() { this.#formReset.disconnect(); }\n * ```\n */\nexport class FormResetWatcher {\n readonly #owns: (form: HTMLFormElement) => boolean;\n readonly #onReset: () => void;\n readonly #frames = new Set<number>();\n #generation = 0;\n\n /**\n * @param owns - whether `form` owns a control this controller derives from.\n * @param onReset - the reconciliation pass, run once per honoured reset.\n */\n constructor(owns: (form: HTMLFormElement) => boolean, onReset: () => void) {\n this.#owns = owns;\n this.#onReset = onReset;\n }\n\n /** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */\n observe(): void {\n document.addEventListener(\"reset\", this.#handleReset, true);\n }\n\n /** Unsubscribes and drops every pending pass; call from `disconnect()`. */\n disconnect(): void {\n document.removeEventListener(\"reset\", this.#handleReset, true);\n this.#generation += 1;\n for (const frame of this.#frames) cancelAnimationFrame(frame);\n this.#frames.clear();\n }\n\n readonly #handleReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#owns(form)) return;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A pass queued before teardown must not reach a disconnected controller.\n if (generation !== this.#generation) return;\n if (event.eventPhase === Event.NONE) {\n this.#settle(event);\n return;\n }\n const frame = requestAnimationFrame(() => {\n this.#frames.delete(frame);\n this.#settle(event);\n });\n this.#frames.add(frame);\n });\n };\n\n /** Runs the pass unless a listener cancelled the reset before it landed. */\n #settle(event: Event): void {\n if (!event.defaultPrevented) this.#onReset();\n }\n}\n","/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { FormResetWatcher } from \"../utils/form_reset_watcher\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * `change` and `reconcile` dispatch\n * `{ checked: boolean, indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }`.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). The `parent`\n * target is optional: a child-only group still exposes its aggregate on the root,\n * while a lone tri-state checkbox can use just `parent` and drive\n * `indeterminate` externally — with no `child` target the parent's own `checked`\n * and `indeterminate` stay the consumer's, because only children are\n * authoritative over them. At most one `parent` may be present.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * All child targets, including disabled ones, participate; omit the target to\n * exclude an input. The cascade does not synthesize native child `change`\n * events — one aggregate custom event reports a changed aggregate.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root. Dynamic targets, checked-attribute changes, Turbo morphs, and native\n * form resets are reconciled from the children. Callers that assign the live\n * `checked` property directly must dispatch `change`, because property writes\n * are not observable by a `MutationObserver`.\n * - `stimeo--checkbox:change` is dispatched by the two public change actions only\n * when the aggregate differs from the last published one. Pending page writes\n * are included in that confirmation; returning to the published aggregate is\n * silent. Browser-dispatched listeners can reconcile a page write before the\n * action runs, while script dispatch and consecutive Stimulus actions in one\n * listener include it in the action's `change`.\n * `stimeo--checkbox:reconcile` is dispatched instead when a reconciliation —\n * not the user — moves the aggregate. Both carry `{ checked: boolean,\n * indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }` and describe\n * the aggregate even without a parent. Neither fires on connect.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly parentTargets: HTMLInputElement[];\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Collapses every lifecycle signal from one DOM update into one derived pass. */\n readonly #reconcile = new MorphRenderWatcher(() => this.#reconcileFromChildren());\n readonly #formReset = new FormResetWatcher(\n (form) => this.#hasCheckboxOwnedBy(form),\n () => this.#reconcile.schedule(),\n );\n /** Aggregate this root last settled on, so a derived repair is reported once. */\n #committedState: CheckboxState | null = null;\n /** Watches authored checked-attribute changes on retained target elements. */\n readonly #checkedObserver = new MutationObserver((records) => {\n if (records.some((record) => this.#isManagedCheckbox(record.target))) {\n this.#reconcile.schedule();\n }\n });\n\n /** Reflects the initial aggregate and starts retained-DOM reconciliation. */\n override connect(): void {\n this.#syncFromChildren();\n this.#reconcile.observe(this.element);\n this.#checkedObserver.observe(this.element, {\n attributes: true,\n attributeFilter: [\"checked\"],\n subtree: true,\n });\n this.#formReset.observe();\n }\n\n /** Releases the observer, global reset listener, and every pending reconciliation. */\n override disconnect(): void {\n this.#reconcile.disconnect();\n this.#checkedObserver.disconnect();\n this.#formReset.disconnect();\n }\n\n /** Reconciles the aggregate for a parent added or replaced at runtime. */\n parentTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a parent target leaves the group. */\n parentTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate for a child added at runtime. */\n childTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a child leaves the group. */\n childTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const previous = this.#committedState;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.#reflect(checked ? \"all\" : \"none\", true);\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /**\n * Announces an aggregate this pass derived rather than the user. `change` stays\n * reserved for the two public actions, so automation never reads a repair as an edit.\n */\n #reconcileFromChildren(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"reconcile\", { detail });\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children. Writing state and reporting it are separate so the caller — not\n * a flag threaded through the write — decides which event describes the cause.\n */\n #syncFromChildren(): void {\n // A childless root reads its aggregate off the parent itself, so writing the\n // derived value back would clear a `checked` the consumer drives alongside\n // `indeterminate`. Only the children are authoritative over the parent.\n this.#reflect(this.#aggregate(), this.childTargets.length > 0);\n }\n\n /**\n * Reflects one aggregate state.\n *\n * @param writeParent - whether the state is authoritative over the `parent`\n * target's own `checked` / `indeterminate`.\n */\n #reflect(state: CheckboxState, writeParent: boolean): void {\n if (writeParent && this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n this.#committedState = state;\n }\n\n /** The settled aggregate as event detail, or `null` before anything has settled. */\n #settledDetail(): { checked: boolean; indeterminate: boolean; state: CheckboxState } | null {\n const state = this.#committedState;\n if (state === null) return null;\n return { checked: state === \"all\", indeterminate: state === \"partial\", state };\n }\n\n /** Whether a form owns at least one current parent or child target. */\n #hasCheckboxOwnedBy(form: HTMLFormElement): boolean {\n return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);\n }\n\n /** Whether an observed attribute mutation belongs to this controller's target set. */\n #isManagedCheckbox(node: Node): boolean {\n return (\n this.parentTargets.some((checkbox) => checkbox === node) ||\n this.childTargets.some((checkbox) => checkbox === node)\n );\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
|
|
@@ -28,7 +28,7 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
28
28
|
*
|
|
29
29
|
* Values: `text` (copy this instead of reading `source`), `feedbackDuration` (ms the
|
|
30
30
|
* completion state is held; `0` or less arms no timer, so it stands until the next
|
|
31
|
-
* copy — a reconnect
|
|
31
|
+
* copy — a reconnect still clears it), `copiedLabel` /
|
|
32
32
|
* `errorLabel` (what the visible slot shows), `announceCopiedText` /
|
|
33
33
|
* `announceErrorText` (what assistive tech hears; empty announces nothing).
|
|
34
34
|
*
|
|
@@ -43,10 +43,13 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
43
43
|
* well would say everything twice.
|
|
44
44
|
*
|
|
45
45
|
* `copied` and `error` are transient: the timer that clears them belongs to one
|
|
46
|
-
* connection, so a fresh `connect()` that finds either — a
|
|
47
|
-
* in-page move — returns to `idle`,
|
|
48
|
-
*
|
|
49
|
-
*
|
|
46
|
+
* connection, so a fresh `connect()` that finds either — a page Turbo restores from its
|
|
47
|
+
* cache, an in-page move — returns to `idle`, silently. `turbo:before-cache` changes
|
|
48
|
+
* nothing: Turbo also dispatches it on pages that stay (a promoted frame navigation, a
|
|
49
|
+
* `popstate` without Turbo state, a refresh of a cached URL), where the result is still
|
|
50
|
+
* on screen and its return to idle still due. Returning to `idle` empties only a slot that
|
|
51
|
+
* still shows the wording a copy wrote there; a slot the page swapped in, rewrote, or put
|
|
52
|
+
* an element into keeps what it holds.
|
|
50
53
|
*
|
|
51
54
|
* A `copiedLabel` / `errorLabel` changed on the live element while its result is
|
|
52
55
|
* shown rewrites the slot — only while the slot still reads the label the copy wrote
|
|
@@ -84,11 +87,19 @@ declare class ClipboardController extends Controller<HTMLElement> {
|
|
|
84
87
|
default: string;
|
|
85
88
|
};
|
|
86
89
|
};
|
|
90
|
+
static valueConstraints: {
|
|
91
|
+
feedbackDuration: {
|
|
92
|
+
readonly finite: true;
|
|
93
|
+
readonly min: 0;
|
|
94
|
+
readonly max: 2147483647;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
87
97
|
static actions: readonly ["copy"];
|
|
88
98
|
static events: readonly ["copy"];
|
|
89
99
|
readonly sourceTarget: HTMLElement;
|
|
90
100
|
readonly buttonTarget: HTMLElement;
|
|
91
101
|
readonly feedbackTarget: HTMLElement;
|
|
102
|
+
readonly feedbackTargets: HTMLElement[];
|
|
92
103
|
readonly hasSourceTarget: boolean;
|
|
93
104
|
readonly hasButtonTarget: boolean;
|
|
94
105
|
readonly hasFeedbackTarget: boolean;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/clipboard_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/announce.ts
|
|
6
3
|
function announce(message, options = {}) {
|
|
7
4
|
const text = message.trim();
|
|
8
5
|
if (text.length === 0) return;
|
|
@@ -13,79 +10,140 @@ function announce(message, options = {}) {
|
|
|
13
10
|
);
|
|
14
11
|
}
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
function setDefaultAttribute(element, name, value) {
|
|
14
|
+
if (element.hasAttribute(name)) return false;
|
|
15
|
+
element.setAttribute(name, value);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
var ListenerSet = class {
|
|
20
|
+
#abort = new AbortController();
|
|
21
|
+
add(target, type, handler, options) {
|
|
22
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
23
|
+
}
|
|
24
|
+
dispose() {
|
|
25
|
+
this.#abort.abort();
|
|
26
|
+
this.#abort = new AbortController();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
var MicrotaskCoalescer = class {
|
|
31
|
+
#run;
|
|
32
|
+
#queued = false;
|
|
33
|
+
#active = false;
|
|
34
|
+
#generation = 0;
|
|
35
|
+
constructor(run) {
|
|
36
|
+
this.#run = run;
|
|
28
37
|
}
|
|
29
|
-
/** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
|
|
30
38
|
activate() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
this.#active = true;
|
|
40
|
+
}
|
|
41
|
+
cancel() {
|
|
42
|
+
this.#active = false;
|
|
43
|
+
this.#queued = false;
|
|
44
|
+
this.#generation += 1;
|
|
36
45
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
schedule() {
|
|
47
|
+
if (!this.#active || this.#queued) return;
|
|
48
|
+
this.#queued = true;
|
|
49
|
+
const generation = this.#generation;
|
|
50
|
+
queueMicrotask(() => {
|
|
51
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
52
|
+
this.#queued = false;
|
|
53
|
+
this.#run();
|
|
54
|
+
});
|
|
42
55
|
}
|
|
43
56
|
};
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
element
|
|
58
|
+
var MorphRenderWatcher = class {
|
|
59
|
+
#listeners = new ListenerSet();
|
|
60
|
+
#pass;
|
|
61
|
+
#element = null;
|
|
62
|
+
#onMorph = () => this.#pass.schedule();
|
|
63
|
+
constructor(run) {
|
|
64
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
65
|
+
}
|
|
66
|
+
observe(element) {
|
|
67
|
+
if (this.#element === element) return;
|
|
68
|
+
this.disconnect();
|
|
69
|
+
this.#element = element;
|
|
70
|
+
this.#pass.activate();
|
|
71
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
72
|
+
}
|
|
73
|
+
schedule() {
|
|
74
|
+
this.#pass.schedule();
|
|
75
|
+
}
|
|
76
|
+
disconnect() {
|
|
77
|
+
this.#listeners.dispose();
|
|
78
|
+
this.#pass.cancel();
|
|
79
|
+
this.#element = null;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
84
|
+
|
|
85
|
+
var NUMBER_BOUNDS = {
|
|
86
|
+
timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
|
|
87
|
+
function matchesNumberBounds(value, bounds) {
|
|
88
|
+
if (!Number.isFinite(value)) {
|
|
89
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
90
|
+
if (direction === null) return false;
|
|
91
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
92
|
+
}
|
|
93
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
94
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
95
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
96
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
97
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
49
98
|
return true;
|
|
50
99
|
}
|
|
51
100
|
|
|
52
|
-
|
|
101
|
+
function readNumber(raw, fallback, bounds) {
|
|
102
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var NumberValueReader = class {
|
|
106
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
107
|
+
read(owner, name, raw, fallback, bounds) {
|
|
108
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
109
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
110
|
+
this.#lastRejected.delete(name);
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
114
|
+
const literal = owner.element.getAttribute(attribute);
|
|
115
|
+
if (literal === null) {
|
|
116
|
+
this.#lastRejected.delete(name);
|
|
117
|
+
return resolved;
|
|
118
|
+
}
|
|
119
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
120
|
+
this.#lastRejected.set(name, literal);
|
|
121
|
+
console.warn(
|
|
122
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
53
129
|
var TimerRegistry = class {
|
|
54
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
55
130
|
ids = /* @__PURE__ */ new Set();
|
|
56
|
-
/**
|
|
57
|
-
* Cancels a single tracked timer.
|
|
58
|
-
*
|
|
59
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
60
|
-
* registry), so callers can clear defensively without guarding.
|
|
61
|
-
*/
|
|
62
131
|
clear(id) {
|
|
63
132
|
if (this.ids.delete(id)) {
|
|
64
133
|
this.cancel(id);
|
|
65
134
|
}
|
|
66
135
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
69
|
-
* to guarantee no timer outlives the element.
|
|
70
|
-
*/
|
|
71
136
|
clearAll() {
|
|
72
137
|
for (const id of this.ids) {
|
|
73
138
|
this.cancel(id);
|
|
74
139
|
}
|
|
75
140
|
this.ids.clear();
|
|
76
141
|
}
|
|
77
|
-
/** Number of timers currently tracked (pending). */
|
|
78
142
|
get size() {
|
|
79
143
|
return this.ids.size;
|
|
80
144
|
}
|
|
81
145
|
};
|
|
82
146
|
var SafeTimeout = class extends TimerRegistry {
|
|
83
|
-
/**
|
|
84
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
85
|
-
*
|
|
86
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
87
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
88
|
-
*/
|
|
89
147
|
set(callback, delay) {
|
|
90
148
|
const id = this.schedule(() => {
|
|
91
149
|
this.ids.delete(id);
|
|
@@ -102,9 +160,9 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
102
160
|
}
|
|
103
161
|
};
|
|
104
162
|
|
|
105
|
-
// src/controllers/clipboard_controller.ts
|
|
106
163
|
var TRANSIENT_STATES = /* @__PURE__ */ new Set(["copied", "error"]);
|
|
107
|
-
var ClipboardController = class extends Controller {
|
|
164
|
+
var ClipboardController = class _ClipboardController extends Controller {
|
|
165
|
+
#numbers = new NumberValueReader();
|
|
108
166
|
static targets = ["source", "button", "feedback"];
|
|
109
167
|
static values = {
|
|
110
168
|
text: { type: String, default: "" },
|
|
@@ -114,63 +172,39 @@ var ClipboardController = class extends Controller {
|
|
|
114
172
|
announceCopiedText: { type: String, default: "" },
|
|
115
173
|
announceErrorText: { type: String, default: "" }
|
|
116
174
|
};
|
|
175
|
+
static valueConstraints = {
|
|
176
|
+
feedbackDuration: NUMBER_BOUNDS.timer
|
|
177
|
+
};
|
|
117
178
|
static actions = ["copy"];
|
|
118
179
|
static events = ["copy"];
|
|
119
|
-
/**
|
|
120
|
-
* The pending return to idle — the only timer this controller schedules, so
|
|
121
|
-
* `clearAll()` is exactly "drop the auto-clear" and needs no id of its own.
|
|
122
|
-
*/
|
|
123
180
|
#timers = new SafeTimeout();
|
|
124
|
-
/** Returns the completion state to idle for the snapshot Turbo takes. */
|
|
125
|
-
#beforeCache = new BeforeCacheReset(() => this.#rewind());
|
|
126
|
-
/**
|
|
127
|
-
* Whether this connection is still live. `copy()` suspends on the Clipboard API,
|
|
128
|
-
* and a teardown that lands while it is suspended must win: the continuation
|
|
129
|
-
* would otherwise write to an element nobody owns and arm a timer past the
|
|
130
|
-
* `clearAll()` that was supposed to be the last word. Stimulus also runs the label
|
|
131
|
-
* callbacks ahead of `connect()`, while this is still false: a result restored with
|
|
132
|
-
* the markup is `connect()`'s to settle, not theirs.
|
|
133
|
-
*/
|
|
134
181
|
#connected = false;
|
|
182
|
+
#result = null;
|
|
183
|
+
#feedback = null;
|
|
184
|
+
#written = /* @__PURE__ */ new Map();
|
|
185
|
+
#morphRender = new MorphRenderWatcher(() => this.#renderResult());
|
|
135
186
|
connect() {
|
|
136
187
|
this.#connected = true;
|
|
137
188
|
this.#adopt();
|
|
138
|
-
this.#
|
|
189
|
+
this.#result = null;
|
|
190
|
+
this.#feedback = this.hasFeedbackTarget ? {
|
|
191
|
+
element: this.feedbackTarget,
|
|
192
|
+
original: this.feedbackTarget.textContent ?? "",
|
|
193
|
+
text: this.feedbackTarget.textContent ?? ""
|
|
194
|
+
} : null;
|
|
195
|
+
this.#morphRender.observe(this.element);
|
|
139
196
|
}
|
|
140
197
|
disconnect() {
|
|
141
198
|
this.#connected = false;
|
|
142
|
-
this.#
|
|
199
|
+
this.#morphRender.disconnect();
|
|
143
200
|
this.#timers.clearAll();
|
|
144
201
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Follows a `copiedLabel` swapped in place while a successful copy is shown.
|
|
147
|
-
*
|
|
148
|
-
* Render only: nothing is announced or dispatched, and the return to idle keeps the
|
|
149
|
-
* deadline the copy set. The slot changes only while `data-state` is `copied` and it
|
|
150
|
-
* still reads the previous label — the one the copy wrote there — so text someone
|
|
151
|
-
* else put in the slot stays. Stimulus also runs this ahead of `connect()`, which
|
|
152
|
-
* settles a result restored with the markup itself, so nothing is written then.
|
|
153
|
-
*/
|
|
154
202
|
copiedLabelValueChanged(label, previous) {
|
|
155
203
|
this.#followLabel("copied", label, previous);
|
|
156
204
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Follows an `errorLabel` swapped in place while a failed copy is shown, on the same
|
|
159
|
-
* terms as {@link ClipboardController.copiedLabelValueChanged}: only while
|
|
160
|
-
* `data-state` is `error` and the slot still reads the previous label.
|
|
161
|
-
*/
|
|
162
205
|
errorLabelValueChanged(label, previous) {
|
|
163
206
|
this.#followLabel("error", label, previous);
|
|
164
207
|
}
|
|
165
|
-
/**
|
|
166
|
-
* Copies the resolved text and reports the outcome. Bound via `data-action`
|
|
167
|
-
* (click). Dispatches `stimeo--clipboard:copy` with `{ success, text, message }`
|
|
168
|
-
* once per completed attempt — including on failure — so consumers can react
|
|
169
|
-
* either way. `message` is the authored label for that outcome, so a part that
|
|
170
|
-
* shows wording — `stimeo--clipboard:copy->stimeo--toast#show` — needs nothing
|
|
171
|
-
* else; wiring both that and the announcer reads the same result twice.
|
|
172
|
-
* An attempt whose connection ended while it was in flight reports nothing.
|
|
173
|
-
*/
|
|
174
208
|
async copy() {
|
|
175
209
|
const text = this.#resolveText();
|
|
176
210
|
let success = false;
|
|
@@ -185,10 +219,6 @@ var ClipboardController = class extends Controller {
|
|
|
185
219
|
const message = success ? this.copiedLabelValue : this.errorLabelValue;
|
|
186
220
|
this.dispatch("copy", { detail: { success, text, message } });
|
|
187
221
|
}
|
|
188
|
-
/**
|
|
189
|
-
* The text to copy: the explicit `text` value when set, otherwise the source
|
|
190
|
-
* target's current value (inputs/textareas) or text content.
|
|
191
|
-
*/
|
|
192
222
|
#resolveText() {
|
|
193
223
|
if (this.textValue.length > 0) return this.textValue;
|
|
194
224
|
if (!this.hasSourceTarget) return "";
|
|
@@ -198,92 +228,83 @@ var ClipboardController = class extends Controller {
|
|
|
198
228
|
}
|
|
199
229
|
return source.textContent ?? "";
|
|
200
230
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Reads the current state back from the DOM.
|
|
203
|
-
*
|
|
204
|
-
* A `copied` or `error` found at connect time is this controller's own output
|
|
205
|
-
* from a connection that is gone, and so is the timer that would have cleared it
|
|
206
|
-
* — nothing else would ever return the element to `idle`. Any other authored
|
|
207
|
-
* value belongs to the consumer and only a missing attribute takes the default.
|
|
208
|
-
*/
|
|
209
231
|
#adopt() {
|
|
210
232
|
if (this.#inTransientState()) {
|
|
233
|
+
const restored = this.#written.size === 0;
|
|
211
234
|
this.#reset();
|
|
235
|
+
if (restored && this.hasFeedbackTarget) this.feedbackTarget.textContent = "";
|
|
212
236
|
return;
|
|
213
237
|
}
|
|
214
238
|
setDefaultAttribute(this.element, "data-state", "idle");
|
|
215
239
|
}
|
|
216
|
-
/** Whether `data-state` currently holds one of the values this controller writes. */
|
|
217
240
|
#inTransientState() {
|
|
218
241
|
const state = this.element.getAttribute("data-state");
|
|
219
242
|
return state !== null && TRANSIENT_STATES.has(state);
|
|
220
243
|
}
|
|
221
|
-
/**
|
|
222
|
-
* Returns the completion state to idle for the snapshot Turbo is about to take,
|
|
223
|
-
* so a page reached with the Back button does not report a copy that happened
|
|
224
|
-
* before the navigation. Only a state this controller wrote is rewound — an
|
|
225
|
-
* authored one is the consumer's and has to survive into the snapshot, exactly as
|
|
226
|
-
* `connect()` leaves it alone. State only — no `copy` is dispatched, which would
|
|
227
|
-
* claim a fresh copy ran.
|
|
228
|
-
*/
|
|
229
|
-
#rewind() {
|
|
230
|
-
if (!this.#inTransientState()) return;
|
|
231
|
-
this.#timers.clearAll();
|
|
232
|
-
this.#reset();
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Reflects the result, announces it, and schedules the return to idle.
|
|
236
|
-
*
|
|
237
|
-
* @stimeoRenderRoot
|
|
238
|
-
*/
|
|
239
244
|
#reportResult(success) {
|
|
240
|
-
this
|
|
241
|
-
|
|
242
|
-
this.feedbackTarget.textContent = success ? this.copiedLabelValue : this.errorLabelValue;
|
|
243
|
-
}
|
|
245
|
+
this.#result = success;
|
|
246
|
+
this.#renderResult(true);
|
|
244
247
|
this.#announceResult(success);
|
|
245
248
|
this.#scheduleReset();
|
|
246
249
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
#renderResult(force = false) {
|
|
251
|
+
if (this.#result === null) {
|
|
252
|
+
setDefaultAttribute(this.element, "data-state", "idle");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.element.setAttribute("data-state", this.#result ? "copied" : "error");
|
|
256
|
+
if (!this.hasFeedbackTarget) return;
|
|
257
|
+
const element = this.feedbackTarget;
|
|
258
|
+
const held = this.#feedback;
|
|
259
|
+
if (!force && (!held || element !== held.element || element.firstElementChild !== null || element.textContent !== held.text && element.textContent !== held.original))
|
|
260
|
+
return;
|
|
261
|
+
const text = this.#result ? this.copiedLabelValue : this.errorLabelValue;
|
|
262
|
+
this.#feedback = {
|
|
263
|
+
element,
|
|
264
|
+
original: held?.element === element ? held.original : element.textContent ?? "",
|
|
265
|
+
text
|
|
266
|
+
};
|
|
267
|
+
element.textContent = text;
|
|
268
|
+
this.#written.set(element, text);
|
|
269
|
+
}
|
|
253
270
|
#announceResult(success) {
|
|
254
271
|
announce(success ? this.announceCopiedTextValue : this.announceErrorTextValue);
|
|
255
272
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Arms the return to idle for the result just shown.
|
|
258
|
-
*
|
|
259
|
-
* @stimeoRuntimeOnly `feedbackDuration` is the delay of the one timer this call arms, and the
|
|
260
|
-
* reset that timer runs reads no Value.
|
|
261
|
-
*/
|
|
262
273
|
#scheduleReset() {
|
|
263
274
|
this.#timers.clearAll();
|
|
264
|
-
if (this
|
|
265
|
-
this.#timers.set(() => this.#reset(), this
|
|
275
|
+
if (this.#safeFeedbackDuration > 0) {
|
|
276
|
+
this.#timers.set(() => this.#reset(), this.#safeFeedbackDuration);
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Rewrites the slot to `label` while `state` is shown and the slot still reads
|
|
270
|
-
* `previous`, the label this controller wrote for that result.
|
|
271
|
-
*/
|
|
272
279
|
#followLabel(state, label, previous) {
|
|
273
280
|
if (!this.#connected) return;
|
|
274
281
|
if (this.element.getAttribute("data-state") !== state || !this.hasFeedbackTarget) return;
|
|
275
282
|
if (this.feedbackTarget.textContent !== previous) return;
|
|
276
283
|
this.feedbackTarget.textContent = label;
|
|
284
|
+
this.#written.set(this.feedbackTarget, label);
|
|
285
|
+
if (this.#feedback?.element === this.feedbackTarget) this.#feedback.text = label;
|
|
277
286
|
}
|
|
278
|
-
/** Returns to the idle state and empties the completion slot. */
|
|
279
287
|
#reset() {
|
|
288
|
+
this.#result = null;
|
|
280
289
|
this.element.setAttribute("data-state", "idle");
|
|
281
|
-
|
|
282
|
-
this.
|
|
290
|
+
for (const [element, text] of this.#written) {
|
|
291
|
+
if (!this.feedbackTargets.includes(element)) continue;
|
|
292
|
+
if (element.firstElementChild === null && element.textContent === text) {
|
|
293
|
+
element.textContent = "";
|
|
294
|
+
}
|
|
283
295
|
}
|
|
296
|
+
this.#written.clear();
|
|
297
|
+
}
|
|
298
|
+
get #safeFeedbackDuration() {
|
|
299
|
+
return this.#numbers.read(
|
|
300
|
+
this,
|
|
301
|
+
"feedbackDuration",
|
|
302
|
+
this.feedbackDurationValue,
|
|
303
|
+
_ClipboardController.values.feedbackDuration.default,
|
|
304
|
+
_ClipboardController.valueConstraints.feedbackDuration
|
|
305
|
+
);
|
|
284
306
|
}
|
|
285
307
|
};
|
|
286
308
|
|
|
287
309
|
export { ClipboardController };
|
|
288
|
-
//# sourceMappingURL=clipboard_controller.js.map
|
|
289
310
|
//# sourceMappingURL=clipboard_controller.js.map
|