stimeo-ui 0.15.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 +209 -0
- package/README.md +128 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +392 -417
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +34 -5
- package/dist/controllers/accordion_controller.js +228 -28
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +20 -9
- 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 +38 -7
- package/dist/controllers/announcer_controller.js +135 -191
- 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 +295 -110
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +172 -106
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +10 -7
- package/dist/controllers/breadcrumb_controller.js +0 -129
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +22 -4
- package/dist/controllers/bulk_select_controller.js +264 -53
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +109 -28
- package/dist/controllers/calendar_controller.js +346 -180
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +99 -27
- package/dist/controllers/carousel_controller.js +587 -300
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +18 -2
- package/dist/controllers/character_counter_controller.js +271 -143
- 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 +77 -75
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +44 -7
- package/dist/controllers/clipboard_controller.js +187 -108
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +41 -5
- package/dist/controllers/collapsible_controller.js +374 -108
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +47 -14
- package/dist/controllers/color_picker_controller.js +183 -127
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +33 -6
- package/dist/controllers/combobox_controller.js +426 -109
- 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 +994 -350
- 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 +212 -125
- 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 -245
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +36 -3
- package/dist/controllers/context_menu_controller.js +275 -127
- 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 +75 -2
- package/dist/controllers/countdown_controller.js +235 -97
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +93 -15
- package/dist/controllers/currency_input_controller.js +241 -118
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +78 -22
- package/dist/controllers/data_grid_controller.js +229 -135
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +64 -11
- package/dist/controllers/date_range_picker_controller.js +359 -165
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +22 -3
- package/dist/controllers/dialog_controller.js +887 -225
- 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 +80 -142
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +2 -2
- package/dist/controllers/dirty_form_controller.js +26 -47
- 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 -20
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +52 -16
- package/dist/controllers/drawer_controller.js +963 -336
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +39 -3
- package/dist/controllers/dropdown_controller.js +342 -85
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +182 -58
- 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 +426 -229
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +10 -4
- package/dist/controllers/filter_controller.js +78 -36
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +57 -14
- package/dist/controllers/flash_controller.js +328 -211
- 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 -239
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +153 -136
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +29 -96
- 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 +263 -198
- 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 +119 -70
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +65 -4
- package/dist/controllers/hover_card_controller.js +335 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +58 -4
- package/dist/controllers/idle_controller.js +316 -65
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +5 -2
- package/dist/controllers/input_mask_controller.js +116 -61
- 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 -103
- 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 -56
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +84 -10
- package/dist/controllers/listbox_controller.js +413 -153
- 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 -95
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +40 -9
- package/dist/controllers/menu_controller.js +254 -167
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +15 -4
- package/dist/controllers/menubar_controller.js +206 -331
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +159 -71
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +50 -7
- package/dist/controllers/multi_select_controller.js +797 -388
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +19 -1
- package/dist/controllers/navigation_menu_controller.js +156 -231
- 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 +373 -174
- 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 +241 -45
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +62 -10
- package/dist/controllers/number_input_controller.js +387 -227
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +28 -75
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +38 -7
- package/dist/controllers/otp_controller.js +344 -196
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
- package/dist/controllers/overflow_indicator_controller.js +167 -93
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +66 -9
- package/dist/controllers/overflow_menu_controller.js +408 -277
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +52 -15
- package/dist/controllers/pagination_controller.js +342 -120
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +60 -9
- package/dist/controllers/password_reveal_controller.js +424 -99
- 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 +252 -96
- 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 +263 -164
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +46 -4
- package/dist/controllers/popover_controller.js +267 -94
- 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 -72
- 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 +243 -112
- 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 +132 -59
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +19 -8
- package/dist/controllers/radio_group_controller.js +138 -133
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +92 -4
- package/dist/controllers/range_slider_controller.js +245 -112
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +33 -6
- package/dist/controllers/rating_controller.js +429 -194
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +26 -2
- package/dist/controllers/read_more_controller.js +185 -47
- 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 +44 -4
- package/dist/controllers/resizable_controller.js +294 -118
- 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 -221
- 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 -146
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +9 -3
- package/dist/controllers/scrollspy_controller.js +181 -236
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +59 -17
- package/dist/controllers/separator_controller.js +288 -132
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +81 -7
- package/dist/controllers/sidebar_controller.js +1071 -375
- 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 -92
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +72 -8
- package/dist/controllers/slider_controller.js +211 -96
- 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 +163 -54
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +13 -110
- 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 -161
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +24 -17
- package/dist/controllers/step_indicator_controller.js +107 -53
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +43 -9
- package/dist/controllers/stepper_controller.js +171 -48
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
- package/dist/controllers/stick_to_bottom_controller.js +310 -116
- 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 +22 -6
- package/dist/controllers/submit_once_controller.js +294 -192
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +40 -7
- package/dist/controllers/switch_controller.js +107 -43
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +13 -1
- package/dist/controllers/tabs_controller.js +50 -25
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +27 -3
- package/dist/controllers/tags_input_controller.js +290 -149
- 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 +37 -8
- package/dist/controllers/theme_controller.js +148 -80
- 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 +162 -81
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +85 -23
- package/dist/controllers/toast_controller.js +365 -193
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +60 -9
- package/dist/controllers/toggle_group_controller.js +194 -99
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +92 -90
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +52 -2
- package/dist/controllers/tooltip_controller.js +185 -125
- 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 -115
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +59 -18
- package/dist/controllers/tree_view_controller.js +248 -290
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +33 -3
- package/dist/index.js +8649 -8572
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +95 -9
- package/dist/inspector/cli.js +343 -77
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +390 -96
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +45 -45
- package/dist/inspector/manifest.json +1308 -155
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -45
- package/dist/positioning/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -73,6 +73,16 @@ import { Controller } from '@hotwired/stimulus';
|
|
|
73
73
|
* delegated to the opt-in `stimeo-ui/positioning` module (never imported here, so
|
|
74
74
|
* the core stays zero-dependency). For an app command menu with arrow roving and
|
|
75
75
|
* `role="menu"`, use `stimeo--menubar` instead.
|
|
76
|
+
*
|
|
77
|
+
* Each move of a panel's open state is reported: `stimeo--navigation-menu:open`
|
|
78
|
+
* and `stimeo--navigation-menu:close` dispatch
|
|
79
|
+
* `{ reason: StateReason, index: number, panel: HTMLElement }` — `index` is the
|
|
80
|
+
* trigger's position in `triggerTargets` — after the state attributes are
|
|
81
|
+
* written. Both are informational, so neither is cancelable. Switching panels
|
|
82
|
+
* reports the outgoing `close` before the incoming `open`, both with the reason
|
|
83
|
+
* that drove the switch. A call that leaves a panel where it already was, the
|
|
84
|
+
* normalization in {@link connect}, target churn, and {@link disconnect} are all
|
|
85
|
+
* silent.
|
|
76
86
|
*/
|
|
77
87
|
declare class NavigationMenuController extends Controller<HTMLElement> {
|
|
78
88
|
#private;
|
|
@@ -87,7 +97,15 @@ declare class NavigationMenuController extends Controller<HTMLElement> {
|
|
|
87
97
|
default: number;
|
|
88
98
|
};
|
|
89
99
|
};
|
|
100
|
+
static valueConstraints: {
|
|
101
|
+
hoverDelay: {
|
|
102
|
+
readonly finite: true;
|
|
103
|
+
readonly min: 0;
|
|
104
|
+
readonly max: 2147483647;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
90
107
|
static actions: readonly ["onTriggerKeydown", "toggle"];
|
|
108
|
+
static events: readonly ["close", "open"];
|
|
91
109
|
readonly triggerTargets: HTMLElement[];
|
|
92
110
|
readonly panelTargets: HTMLElement[];
|
|
93
111
|
readonly hoverAreaTargets: HTMLElement[];
|
|
@@ -129,7 +147,7 @@ declare class NavigationMenuController extends Controller<HTMLElement> {
|
|
|
129
147
|
* activation (pointer elsewhere) still toggles it closed, and `Escape` and
|
|
130
148
|
* outside clicks dismiss either way.
|
|
131
149
|
*/
|
|
132
|
-
toggle(
|
|
150
|
+
toggle(source: Event | HTMLElement): void;
|
|
133
151
|
/**
|
|
134
152
|
* `ArrowLeft`/`ArrowRight` move focus between triggers (keeping Tab order).
|
|
135
153
|
*
|
|
@@ -1,19 +1,46 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
4
|
+
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
5
|
+
function stateReasonFor(event) {
|
|
6
|
+
if (!event) return "api";
|
|
7
|
+
if (FOCUS_EVENTS.has(event.type)) return "focus";
|
|
8
|
+
if (POINTER_EVENTS.has(event.type)) return "pointer";
|
|
9
|
+
return "user";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function actionSource(source) {
|
|
13
|
+
if (source instanceof HTMLElement) {
|
|
14
|
+
return { event: null, host: source, origin: source, reason: "api" };
|
|
15
|
+
}
|
|
16
|
+
const event = source ?? null;
|
|
17
|
+
return {
|
|
18
|
+
event,
|
|
19
|
+
host: event?.currentTarget instanceof Element ? event.currentTarget : null,
|
|
20
|
+
origin: event?.target instanceof Element ? event.target : null,
|
|
21
|
+
reason: stateReasonFor(event)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
4
24
|
|
|
5
|
-
// src/utils/logical_scroll.ts
|
|
6
25
|
function isRtl(element) {
|
|
7
26
|
return window.getComputedStyle(element).direction === "rtl";
|
|
8
27
|
}
|
|
9
28
|
|
|
10
|
-
// src/utils/arrow_step.ts
|
|
11
29
|
function isReservedArrowChord(event, allow = []) {
|
|
12
30
|
if (!event.key.startsWith("Arrow")) return false;
|
|
13
31
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
14
32
|
}
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
function sharedRegistry(key, create) {
|
|
35
|
+
const symbol = Symbol.for(key);
|
|
36
|
+
const scope = globalThis;
|
|
37
|
+
const existing = scope[symbol];
|
|
38
|
+
if (existing !== void 0) return existing;
|
|
39
|
+
const registry = create();
|
|
40
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
41
|
+
return registry;
|
|
42
|
+
}
|
|
43
|
+
|
|
17
44
|
function claimsWhileFocusWithin(element) {
|
|
18
45
|
return () => {
|
|
19
46
|
const active = element.ownerDocument.activeElement;
|
|
@@ -21,17 +48,11 @@ function claimsWhileFocusWithin(element) {
|
|
|
21
48
|
};
|
|
22
49
|
}
|
|
23
50
|
var EscapeLayer = class _EscapeLayer {
|
|
24
|
-
static #registries =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#claims = null;
|
|
30
|
-
/**
|
|
31
|
-
* Activates this layer at the top of its document's Escape stack, installing
|
|
32
|
-
* the document's shared resolver listener if this is its first layer.
|
|
33
|
-
* Re-activating an already-active layer moves it to the top.
|
|
34
|
-
*/
|
|
51
|
+
static #registries = sharedRegistry(
|
|
52
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
53
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
54
|
+
);
|
|
55
|
+
#active = null;
|
|
35
56
|
activate(ownerDocument = document, options) {
|
|
36
57
|
this.deactivate();
|
|
37
58
|
let registry = _EscapeLayer.#registries.get(ownerDocument);
|
|
@@ -40,44 +61,31 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
40
61
|
_EscapeLayer.#registries.set(ownerDocument, registry);
|
|
41
62
|
ownerDocument.addEventListener("keydown", registry.onKeydown);
|
|
42
63
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.#
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Removes this layer from its document's Escape stack, uninstalling the
|
|
50
|
-
* shared listener when the stack empties. Safe to call when inactive.
|
|
51
|
-
*/
|
|
64
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
65
|
+
registry.stack.push(entry);
|
|
66
|
+
this.#active = { document: ownerDocument, entry };
|
|
67
|
+
}
|
|
52
68
|
deactivate() {
|
|
53
|
-
const
|
|
54
|
-
if (!
|
|
55
|
-
const registry = _EscapeLayer.#registries.get(
|
|
69
|
+
const active = this.#active;
|
|
70
|
+
if (!active) return;
|
|
71
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
56
72
|
if (registry) {
|
|
57
|
-
const index = registry.stack.lastIndexOf(
|
|
73
|
+
const index = registry.stack.lastIndexOf(active.entry);
|
|
58
74
|
if (index >= 0) registry.stack.splice(index, 1);
|
|
59
75
|
if (registry.stack.length === 0) {
|
|
60
|
-
|
|
61
|
-
_EscapeLayer.#registries.delete(
|
|
76
|
+
active.document.removeEventListener("keydown", registry.onKeydown);
|
|
77
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
62
78
|
}
|
|
63
79
|
}
|
|
64
|
-
this.#
|
|
65
|
-
|
|
66
|
-
this.#claims = null;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Whether this active layer would own a press right now: it is the topmost
|
|
70
|
-
* layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
|
|
71
|
-
* and diagnostics — production dismissal goes through the shared listener.
|
|
72
|
-
*/
|
|
80
|
+
this.#active = null;
|
|
81
|
+
}
|
|
73
82
|
get ownsEscape() {
|
|
74
|
-
const
|
|
75
|
-
if (!
|
|
76
|
-
const registry = _EscapeLayer.#registries.get(
|
|
83
|
+
const active = this.#active;
|
|
84
|
+
if (!active) return false;
|
|
85
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
77
86
|
if (!registry) return false;
|
|
78
|
-
return _EscapeLayer.#resolveOwner(registry.stack) ===
|
|
87
|
+
return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
|
|
79
88
|
}
|
|
80
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
81
89
|
static #createRegistry() {
|
|
82
90
|
const registry = {
|
|
83
91
|
stack: [],
|
|
@@ -86,66 +94,91 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
86
94
|
const owner = _EscapeLayer.#resolveOwner(registry.stack);
|
|
87
95
|
if (!owner) return;
|
|
88
96
|
event.preventDefault();
|
|
89
|
-
owner
|
|
97
|
+
owner.onDismiss();
|
|
90
98
|
}
|
|
91
99
|
};
|
|
92
100
|
return registry;
|
|
93
101
|
}
|
|
94
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
95
102
|
static #resolveOwner(stack) {
|
|
96
103
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
97
104
|
const layer = stack[index];
|
|
98
105
|
if (!layer) continue;
|
|
99
|
-
if (layer
|
|
106
|
+
if (layer.claims && !layer.claims()) continue;
|
|
100
107
|
return layer;
|
|
101
108
|
}
|
|
102
109
|
return null;
|
|
103
110
|
}
|
|
104
111
|
};
|
|
105
112
|
|
|
106
|
-
// src/utils/event_owner.ts
|
|
107
113
|
function ownerIndex(candidates, node) {
|
|
108
114
|
if (!(node instanceof Node)) return -1;
|
|
109
115
|
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
119
|
+
|
|
120
|
+
var NUMBER_BOUNDS = {
|
|
121
|
+
timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
|
|
122
|
+
function matchesNumberBounds(value, bounds) {
|
|
123
|
+
if (!Number.isFinite(value)) {
|
|
124
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
125
|
+
if (direction === null) return false;
|
|
126
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
127
|
+
}
|
|
128
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
129
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
130
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
131
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
132
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function readNumber(raw, fallback, bounds) {
|
|
137
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var NumberValueReader = class {
|
|
141
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
142
|
+
read(owner, name, raw, fallback, bounds) {
|
|
143
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
144
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
145
|
+
this.#lastRejected.delete(name);
|
|
146
|
+
return resolved;
|
|
147
|
+
}
|
|
148
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
149
|
+
const literal = owner.element.getAttribute(attribute);
|
|
150
|
+
if (literal === null) {
|
|
151
|
+
this.#lastRejected.delete(name);
|
|
152
|
+
return resolved;
|
|
153
|
+
}
|
|
154
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
155
|
+
this.#lastRejected.set(name, literal);
|
|
156
|
+
console.warn(
|
|
157
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return resolved;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
113
164
|
var TimerRegistry = class {
|
|
114
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
115
165
|
ids = /* @__PURE__ */ new Set();
|
|
116
|
-
/**
|
|
117
|
-
* Cancels a single tracked timer.
|
|
118
|
-
*
|
|
119
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
120
|
-
* registry), so callers can clear defensively without guarding.
|
|
121
|
-
*/
|
|
122
166
|
clear(id) {
|
|
123
167
|
if (this.ids.delete(id)) {
|
|
124
168
|
this.cancel(id);
|
|
125
169
|
}
|
|
126
170
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
129
|
-
* to guarantee no timer outlives the element.
|
|
130
|
-
*/
|
|
131
171
|
clearAll() {
|
|
132
172
|
for (const id of this.ids) {
|
|
133
173
|
this.cancel(id);
|
|
134
174
|
}
|
|
135
175
|
this.ids.clear();
|
|
136
176
|
}
|
|
137
|
-
/** Number of timers currently tracked (pending). */
|
|
138
177
|
get size() {
|
|
139
178
|
return this.ids.size;
|
|
140
179
|
}
|
|
141
180
|
};
|
|
142
181
|
var SafeTimeout = class extends TimerRegistry {
|
|
143
|
-
/**
|
|
144
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
145
|
-
*
|
|
146
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
147
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
148
|
-
*/
|
|
149
182
|
set(callback, delay) {
|
|
150
183
|
const id = this.schedule(() => {
|
|
151
184
|
this.ids.delete(id);
|
|
@@ -162,121 +195,82 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
162
195
|
}
|
|
163
196
|
};
|
|
164
197
|
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
function targetSelector(identifier, name) {
|
|
199
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var NavigationMenuController = class _NavigationMenuController extends Controller {
|
|
203
|
+
#numbers = new NumberValueReader();
|
|
167
204
|
static targets = ["trigger", "panel", "hoverArea"];
|
|
168
205
|
static values = {
|
|
169
206
|
openOnHover: { type: Boolean, default: false },
|
|
170
207
|
hoverDelay: { type: Number, default: 150 }
|
|
171
208
|
};
|
|
209
|
+
static valueConstraints = {
|
|
210
|
+
hoverDelay: NUMBER_BOUNDS.timer
|
|
211
|
+
};
|
|
172
212
|
static actions = ["onTriggerKeydown", "toggle"];
|
|
173
|
-
|
|
213
|
+
static events = ["close", "open"];
|
|
174
214
|
#hoverTimers = new SafeTimeout();
|
|
175
|
-
/**
|
|
176
|
-
* Elements currently carrying hover listeners. Removal always mirrors this
|
|
177
|
-
* set (not a recomputed target snapshot), so a rebuild driven by target churn
|
|
178
|
-
* can never strand a listener on an element that stopped being a target.
|
|
179
|
-
*/
|
|
180
215
|
#hoverWired = /* @__PURE__ */ new Set();
|
|
181
|
-
/**
|
|
182
|
-
* The trigger whose hover region the pointer currently occupies, if any. Only
|
|
183
|
-
* the click semantics under `openOnHover` read it (see {@link toggle}).
|
|
184
|
-
*/
|
|
185
216
|
#hoveredTrigger = null;
|
|
186
|
-
/**
|
|
187
|
-
* Whether the controller is between `connect()` and `disconnect()`. Stimulus
|
|
188
|
-
* disconnects the controller **before** disconnecting its targets, so the
|
|
189
|
-
* target callbacks below also fire during teardown; without this flag their
|
|
190
|
-
* rebuild would re-wire hover listeners onto an already-disconnected nav.
|
|
191
|
-
*/
|
|
192
217
|
#connected = false;
|
|
193
|
-
|
|
218
|
+
#reporting = false;
|
|
194
219
|
#escapeLayer = new EscapeLayer();
|
|
195
|
-
/** Establishes the closed baseline and the dismissal listeners. */
|
|
196
220
|
connect() {
|
|
197
221
|
this.#connected = true;
|
|
198
|
-
this.#closeAll();
|
|
222
|
+
this.#closeAll("api");
|
|
199
223
|
document.addEventListener("click", this.#onOutsideClick, true);
|
|
200
224
|
this.element.addEventListener("focusout", this.#onFocusOut);
|
|
201
225
|
if (this.openOnHoverValue) this.#addHoverListeners();
|
|
226
|
+
this.#reporting = true;
|
|
202
227
|
}
|
|
203
|
-
/** Removes every listener, pending hover timer, and stack membership taken while connected. */
|
|
204
228
|
disconnect() {
|
|
205
229
|
this.#connected = false;
|
|
230
|
+
this.#reporting = false;
|
|
206
231
|
this.#escapeLayer.deactivate();
|
|
207
232
|
document.removeEventListener("click", this.#onOutsideClick, true);
|
|
208
233
|
this.element.removeEventListener("focusout", this.#onFocusOut);
|
|
209
234
|
this.#removeHoverListeners();
|
|
210
235
|
this.#hoverTimers.clearAll();
|
|
211
236
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Follows a runtime flip of `openOnHover` (a morph or a scripted attribute
|
|
214
|
-
* change) by wiring or unwiring the hover listeners in place. Stimulus also
|
|
215
|
-
* fires this once before `connect()`, which the `#connected` guard skips —
|
|
216
|
-
* `connect()` owns the initial wiring.
|
|
217
|
-
*/
|
|
218
237
|
openOnHoverValueChanged() {
|
|
219
238
|
if (!this.#connected) return;
|
|
220
239
|
if (this.openOnHoverValue) this.#addHoverListeners();
|
|
221
240
|
else this.#removeHoverListeners();
|
|
222
241
|
}
|
|
223
|
-
/** Re-wires hover listeners when a target is added after connect (Turbo Streams etc.). */
|
|
224
242
|
triggerTargetConnected() {
|
|
225
243
|
this.#rewireHoverListeners();
|
|
226
244
|
}
|
|
227
|
-
/** Re-wires hover listeners when a target is removed while connected. */
|
|
228
245
|
triggerTargetDisconnected() {
|
|
229
246
|
this.#rewireHoverListeners();
|
|
230
247
|
}
|
|
231
|
-
/** See {@link NavigationMenuController.triggerTargetConnected}. */
|
|
232
248
|
panelTargetConnected() {
|
|
233
249
|
this.#rewireHoverListeners();
|
|
234
250
|
}
|
|
235
|
-
/** See {@link NavigationMenuController.triggerTargetDisconnected}. */
|
|
236
251
|
panelTargetDisconnected() {
|
|
237
252
|
this.#rewireHoverListeners();
|
|
238
253
|
}
|
|
239
|
-
/** See {@link NavigationMenuController.triggerTargetConnected}. */
|
|
240
254
|
hoverAreaTargetConnected() {
|
|
241
255
|
this.#rewireHoverListeners();
|
|
242
256
|
}
|
|
243
|
-
/** See {@link NavigationMenuController.triggerTargetDisconnected}. */
|
|
244
257
|
hoverAreaTargetDisconnected() {
|
|
245
258
|
this.#rewireHoverListeners();
|
|
246
259
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
*
|
|
254
|
-
* Under `openOnHover`, closing a panel the pointer itself is holding open is
|
|
255
|
-
* refused: the pointer stays put, `mouseenter` does not re-fire, and the panel
|
|
256
|
-
* would be unopenable until the pointer left and came back. Keyboard
|
|
257
|
-
* activation (pointer elsewhere) still toggles it closed, and `Escape` and
|
|
258
|
-
* outside clicks dismiss either way.
|
|
259
|
-
*/
|
|
260
|
-
toggle(event) {
|
|
261
|
-
const trigger = event.currentTarget;
|
|
260
|
+
toggle(source) {
|
|
261
|
+
const { host, origin, reason } = actionSource(source);
|
|
262
|
+
const trigger = host?.closest(targetSelector(this.identifier, "trigger"));
|
|
263
|
+
if (!trigger || !this.triggerTargets.includes(trigger)) return;
|
|
264
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
265
|
+
return;
|
|
262
266
|
this.#hoverTimers.clearAll();
|
|
263
267
|
if (!this.#isExpanded(trigger)) {
|
|
264
|
-
this.#openPanel(trigger);
|
|
268
|
+
this.#openPanel(trigger, reason);
|
|
265
269
|
return;
|
|
266
270
|
}
|
|
267
271
|
if (this.openOnHoverValue && this.#hoveredTrigger === trigger) return;
|
|
268
|
-
this.#closePanel(trigger);
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* `ArrowLeft`/`ArrowRight` move focus between triggers (keeping Tab order).
|
|
272
|
-
*
|
|
273
|
-
* Only navigable triggers are destinations, so a `hidden` or disabled item
|
|
274
|
-
* never swallows the press. A modified arrow belongs to the browser or OS
|
|
275
|
-
* (`Alt+←`/`Alt+→` is history back/forward), and with no destination at all
|
|
276
|
-
* (a single-trigger nav) the press is left to the page — `preventDefault()` is
|
|
277
|
-
* called only when focus actually moves, keeping the scroll-suppression
|
|
278
|
-
* contract honest.
|
|
279
|
-
*/
|
|
272
|
+
this.#closePanel(trigger, reason);
|
|
273
|
+
}
|
|
280
274
|
onTriggerKeydown(event) {
|
|
281
275
|
if (event.defaultPrevented) return;
|
|
282
276
|
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
|
|
@@ -292,60 +286,42 @@ var NavigationMenuController = class extends Controller {
|
|
|
292
286
|
const step = event.key === forward ? 1 : -1;
|
|
293
287
|
triggers[(index + step + triggers.length) % triggers.length]?.focus();
|
|
294
288
|
}
|
|
295
|
-
|
|
296
|
-
* Opens `trigger`'s panel, closing any other open panel first. Re-opening an
|
|
297
|
-
* already-open panel only re-asserts the Escape layer: the close/open
|
|
298
|
-
* round-trip would rewrite `aria-expanded` and `hidden` for no state change.
|
|
299
|
-
*/
|
|
300
|
-
#openPanel(trigger) {
|
|
289
|
+
#openPanel(trigger, reason) {
|
|
301
290
|
if (trigger.getAttribute("aria-disabled") === "true") return;
|
|
302
291
|
if (this.#isExpanded(trigger)) {
|
|
303
292
|
this.#syncEscapeLayer();
|
|
304
293
|
return;
|
|
305
294
|
}
|
|
306
|
-
this.#closeAll();
|
|
295
|
+
this.#closeAll(reason);
|
|
307
296
|
const panel = this.#panelFor(trigger);
|
|
308
297
|
if (!panel) return;
|
|
309
298
|
panel.hidden = false;
|
|
310
299
|
trigger.setAttribute("aria-expanded", "true");
|
|
300
|
+
this.#report("open", trigger, panel, reason);
|
|
311
301
|
this.#syncEscapeLayer();
|
|
312
302
|
}
|
|
313
|
-
|
|
314
|
-
|
|
303
|
+
#closePanel(trigger, reason) {
|
|
304
|
+
const was = this.#isExpanded(trigger);
|
|
315
305
|
const panel = this.#panelFor(trigger);
|
|
316
306
|
if (panel) panel.hidden = true;
|
|
317
307
|
trigger.setAttribute("aria-expanded", "false");
|
|
308
|
+
if (was && panel) this.#report("close", trigger, panel, reason);
|
|
318
309
|
this.#syncEscapeLayer();
|
|
319
310
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
* `disabled` control (mirrors `toolbar`). CSS-only visibility cannot be
|
|
331
|
-
* detected headlessly and stays the consumer's responsibility.
|
|
332
|
-
*
|
|
333
|
-
* **`aria-disabled="true"` stays reachable.** APG separates the attributes by
|
|
334
|
-
* intent: `disabled` is for controls a neighbour makes inferable, while
|
|
335
|
-
* `aria-disabled` marks one that must remain *discoverable* — and a nav
|
|
336
|
-
* section the user cannot even arrow to is a section they cannot learn exists.
|
|
337
|
-
* Opening is suppressed separately, so the trigger announces itself and does
|
|
338
|
-
* nothing.
|
|
339
|
-
*/
|
|
311
|
+
#closeAll(reason) {
|
|
312
|
+
for (const trigger of this.triggerTargets) this.#closePanel(trigger, reason);
|
|
313
|
+
}
|
|
314
|
+
#report(name, trigger, panel, reason) {
|
|
315
|
+
if (!this.#reporting) return;
|
|
316
|
+
const index = this.triggerTargets.indexOf(trigger);
|
|
317
|
+
const detail = { reason, index, panel };
|
|
318
|
+
if (name === "open") this.dispatch("open", { detail, cancelable: false });
|
|
319
|
+
else this.dispatch("close", { detail, cancelable: false });
|
|
320
|
+
}
|
|
340
321
|
#isNavigable(trigger) {
|
|
341
322
|
if (trigger.hasAttribute("hidden")) return false;
|
|
342
323
|
return !trigger.disabled;
|
|
343
324
|
}
|
|
344
|
-
/**
|
|
345
|
-
* Aligns Escape-stack membership with the open state: joins (or re-asserts to
|
|
346
|
-
* the top) while a panel is open, leaves once none is. Re-asserting when the
|
|
347
|
-
* user switches panels is deliberate — the nav is again the newest layer.
|
|
348
|
-
*/
|
|
349
325
|
#syncEscapeLayer() {
|
|
350
326
|
if (this.#isAnyOpen) {
|
|
351
327
|
this.#escapeLayer.activate(document, {
|
|
@@ -356,84 +332,49 @@ var NavigationMenuController = class extends Controller {
|
|
|
356
332
|
this.#escapeLayer.deactivate();
|
|
357
333
|
}
|
|
358
334
|
}
|
|
359
|
-
/**
|
|
360
|
-
* Closes any open panel and returns focus to its trigger (Escape path). A
|
|
361
|
-
* pending hover open would otherwise re-open the panel the user just
|
|
362
|
-
* dismissed, so the reservation is dropped with it.
|
|
363
|
-
*/
|
|
364
335
|
#closeAndRestore() {
|
|
365
336
|
const open = this.#openTrigger;
|
|
366
337
|
if (!open) return;
|
|
367
338
|
this.#hoverTimers.clearAll();
|
|
368
|
-
this.#closePanel(open);
|
|
339
|
+
this.#closePanel(open, "escape");
|
|
369
340
|
open.focus();
|
|
370
341
|
}
|
|
371
|
-
/** Closes panels when a click lands outside the nav element. */
|
|
372
342
|
#onOutsideClick = (event) => {
|
|
373
|
-
if (this.#isAnyOpen && !this.element.contains(event.target)) this.#closeAll();
|
|
343
|
+
if (this.#isAnyOpen && !this.element.contains(event.target)) this.#closeAll("outside");
|
|
374
344
|
};
|
|
375
|
-
/**
|
|
376
|
-
* Closes (without restoring focus) when focus leaves the nav for a known
|
|
377
|
-
* external destination. A null/non-Node destination is indeterminate:
|
|
378
|
-
* browsers use it for clicks on non-focusable content and for window
|
|
379
|
-
* deactivation, so those never close the nav here (matching the popover
|
|
380
|
-
* convention) — the outside-click handler decides pointer dismissal, and the
|
|
381
|
-
* Escape stack's body-focus claim keeps keyboard dismissal working.
|
|
382
|
-
*/
|
|
383
345
|
#onFocusOut = (event) => {
|
|
384
346
|
const next = event.relatedTarget;
|
|
385
347
|
if (!(next instanceof Node) || this.element.contains(next)) return;
|
|
386
|
-
this.#closeAll();
|
|
348
|
+
this.#closeAll("focus");
|
|
387
349
|
};
|
|
388
|
-
/** Opens a trigger's panel after the hover delay (hover mode). */
|
|
389
350
|
#onPointerEnter = (event) => {
|
|
390
351
|
const trigger = this.#triggerForHover(event.currentTarget);
|
|
391
352
|
if (!trigger) return;
|
|
392
353
|
this.#hoveredTrigger = trigger;
|
|
393
354
|
this.#hoverTimers.clearAll();
|
|
394
|
-
this.#hoverTimers.set(() => this.#openFromHover(trigger), this
|
|
355
|
+
this.#hoverTimers.set(() => this.#openFromHover(trigger), this.#safeHoverDelay);
|
|
395
356
|
};
|
|
396
|
-
/**
|
|
397
|
-
* Closes the open panel after the hover delay (hover mode) — unless the
|
|
398
|
-
* pointer moved directly into another part of the hover region (an adjacent
|
|
399
|
-
* trigger, panel, or hoverArea): crossing a shared edge must not schedule a
|
|
400
|
-
* spurious close, e.g. a hoverArea whose panel sits outside it as a sibling.
|
|
401
|
-
*/
|
|
402
357
|
#onPointerLeave = (event) => {
|
|
403
358
|
const next = event instanceof MouseEvent ? event.relatedTarget : null;
|
|
404
359
|
if (ownerIndex(this.#hoverElements, next) !== -1) return;
|
|
405
360
|
this.#hoveredTrigger = null;
|
|
406
361
|
this.#hoverTimers.clearAll();
|
|
407
|
-
this.#hoverTimers.set(() => this.#closeFromHover(), this
|
|
362
|
+
this.#hoverTimers.set(() => this.#closeFromHover(), this.#safeHoverDelay);
|
|
408
363
|
};
|
|
409
|
-
/**
|
|
410
|
-
* Hover-driven open. Switching panels would hide the outgoing one, so focus
|
|
411
|
-
* standing inside it is handed back to its trigger first: pointer movement
|
|
412
|
-
* must never send focus to the body (the keyboard user would lose their
|
|
413
|
-
* place). Focus outside the closing panel is left alone.
|
|
414
|
-
*/
|
|
415
364
|
#openFromHover(trigger) {
|
|
416
365
|
if (trigger.getAttribute("aria-disabled") === "true") return;
|
|
417
366
|
const open = this.#openTrigger;
|
|
418
367
|
if (open && open !== trigger && this.#holdsFocus(this.#panelFor(open))) open.focus();
|
|
419
|
-
this.#openPanel(trigger);
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Hover-driven close: skipped while focus sits inside the panel it would hide,
|
|
423
|
-
* for the same no-focus-loss reason (cf. `hover_card`'s delayed close). The
|
|
424
|
-
* panel then stays open until `Escape`, an outside click, or focus leaving the
|
|
425
|
-
* nav dismisses it — all of which restore or keep focus deliberately.
|
|
426
|
-
*/
|
|
368
|
+
this.#openPanel(trigger, "pointer");
|
|
369
|
+
}
|
|
427
370
|
#closeFromHover() {
|
|
428
371
|
const open = this.#openTrigger;
|
|
429
372
|
if (open && this.#holdsFocus(this.#panelFor(open))) return;
|
|
430
|
-
this.#closeAll();
|
|
373
|
+
this.#closeAll("pointer");
|
|
431
374
|
}
|
|
432
|
-
/** Whether `panel` exists and currently contains the focused element. */
|
|
433
375
|
#holdsFocus(panel) {
|
|
434
376
|
return panel?.contains(document.activeElement) ?? false;
|
|
435
377
|
}
|
|
436
|
-
/** Wires hover open/close on each hover element (opt-in), tracking what was wired. */
|
|
437
378
|
#addHoverListeners() {
|
|
438
379
|
for (const element of this.#hoverElements) {
|
|
439
380
|
element.addEventListener("mouseenter", this.#onPointerEnter);
|
|
@@ -441,7 +382,6 @@ var NavigationMenuController = class extends Controller {
|
|
|
441
382
|
this.#hoverWired.add(element);
|
|
442
383
|
}
|
|
443
384
|
}
|
|
444
|
-
/** Removes the hover listeners from exactly the elements that were wired. */
|
|
445
385
|
#removeHoverListeners() {
|
|
446
386
|
for (const element of this.#hoverWired) {
|
|
447
387
|
element.removeEventListener("mouseenter", this.#onPointerEnter);
|
|
@@ -450,25 +390,11 @@ var NavigationMenuController = class extends Controller {
|
|
|
450
390
|
this.#hoverWired.clear();
|
|
451
391
|
this.#hoveredTrigger = null;
|
|
452
392
|
}
|
|
453
|
-
/**
|
|
454
|
-
* Rebuilds the hover wiring from the current targets. Target callbacks call
|
|
455
|
-
* this so items added or removed after connect (e.g. a Turbo Stream append)
|
|
456
|
-
* participate in hover; the wired-set removal keeps the rebuild symmetric.
|
|
457
|
-
* Stimulus fires those callbacks during teardown too, hence the `#connected`
|
|
458
|
-
* guard — a rebuild after `disconnect()` would resurrect the listeners.
|
|
459
|
-
*/
|
|
460
393
|
#rewireHoverListeners() {
|
|
461
394
|
if (!this.#connected || !this.openOnHoverValue) return;
|
|
462
395
|
this.#removeHoverListeners();
|
|
463
396
|
this.#addHoverListeners();
|
|
464
397
|
}
|
|
465
|
-
/**
|
|
466
|
-
* Elements that participate in hover: each `hoverArea`, plus every trigger and
|
|
467
|
-
* panel **not** wrapped by one. A wrapped trigger/panel must defer to its
|
|
468
|
-
* wrapper — its own mouseleave would otherwise schedule a close while the
|
|
469
|
-
* pointer is still inside the area (mouseenter does not re-fire on the wrapper
|
|
470
|
-
* when moving among its descendants), flickering the panel shut.
|
|
471
|
-
*/
|
|
472
398
|
get #hoverElements() {
|
|
473
399
|
const areas = this.hoverAreaTargets;
|
|
474
400
|
const covered = (element) => ownerIndex(areas, element) !== -1;
|
|
@@ -478,11 +404,6 @@ var NavigationMenuController = class extends Controller {
|
|
|
478
404
|
...this.panelTargets.filter((panel) => !covered(panel))
|
|
479
405
|
];
|
|
480
406
|
}
|
|
481
|
-
/**
|
|
482
|
-
* Resolves the trigger for a hovered element: the trigger itself, the trigger
|
|
483
|
-
* controlling a hovered panel, or the first trigger contained in a hovered
|
|
484
|
-
* `hoverArea` wrapper.
|
|
485
|
-
*/
|
|
486
407
|
#triggerForHover(element) {
|
|
487
408
|
if (this.triggerTargets.includes(element)) return element;
|
|
488
409
|
if (this.panelTargets.includes(element)) {
|
|
@@ -493,25 +414,29 @@ var NavigationMenuController = class extends Controller {
|
|
|
493
414
|
}
|
|
494
415
|
return null;
|
|
495
416
|
}
|
|
496
|
-
/** The panel controlled by `trigger` (matched by `aria-controls`/`id`). */
|
|
497
417
|
#panelFor(trigger) {
|
|
498
418
|
const id = trigger.getAttribute("aria-controls");
|
|
499
419
|
return id ? this.panelTargets.find((panel) => panel.id === id) ?? null : null;
|
|
500
420
|
}
|
|
501
|
-
/** Whether `trigger`'s panel is currently expanded. */
|
|
502
421
|
#isExpanded(trigger) {
|
|
503
422
|
return trigger.getAttribute("aria-expanded") === "true";
|
|
504
423
|
}
|
|
505
|
-
/** The trigger whose panel is currently open, if any. */
|
|
506
424
|
get #openTrigger() {
|
|
507
425
|
return this.triggerTargets.find((trigger) => this.#isExpanded(trigger)) ?? null;
|
|
508
426
|
}
|
|
509
|
-
/** Whether any panel is currently open. */
|
|
510
427
|
get #isAnyOpen() {
|
|
511
428
|
return this.#openTrigger !== null;
|
|
512
429
|
}
|
|
430
|
+
get #safeHoverDelay() {
|
|
431
|
+
return this.#numbers.read(
|
|
432
|
+
this,
|
|
433
|
+
"hoverDelay",
|
|
434
|
+
this.hoverDelayValue,
|
|
435
|
+
_NavigationMenuController.values.hoverDelay.default,
|
|
436
|
+
_NavigationMenuController.valueConstraints.hoverDelay
|
|
437
|
+
);
|
|
438
|
+
}
|
|
513
439
|
};
|
|
514
440
|
|
|
515
441
|
export { NavigationMenuController };
|
|
516
|
-
//# sourceMappingURL=navigation_menu_controller.js.map
|
|
517
442
|
//# sourceMappingURL=navigation_menu_controller.js.map
|