bits-ui 1.0.0-next.90 → 1.0.0-next.92
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/dist/bits/alert-dialog/components/alert-dialog-content.svelte +8 -5
- package/dist/bits/command/command.svelte.d.ts +0 -1
- package/dist/bits/command/command.svelte.js +50 -61
- package/dist/bits/command/components/command.svelte +3 -2
- package/dist/bits/command/compute-command-score.d.ts +26 -0
- package/dist/bits/command/{command-score.js → compute-command-score.js} +47 -15
- package/dist/bits/command/index.d.ts +1 -0
- package/dist/bits/command/index.js +1 -0
- package/dist/bits/command/types.d.ts +2 -1
- package/dist/bits/dialog/components/dialog-content.svelte +8 -5
- package/dist/bits/index.d.ts +1 -1
- package/dist/bits/index.js +1 -1
- package/dist/bits/menubar/components/menubar-content-static.svelte +9 -0
- package/dist/bits/menubar/components/menubar-content.svelte +9 -0
- package/dist/bits/menubar/menubar.svelte.d.ts +7 -3
- package/dist/bits/menubar/menubar.svelte.js +10 -2
- package/dist/bits/navigation-menu/components/navigation-menu-content-impl.svelte +90 -0
- package/dist/bits/navigation-menu/components/navigation-menu-content-impl.svelte.d.ts +13 -0
- package/dist/bits/navigation-menu/components/navigation-menu-content.svelte +18 -57
- package/dist/bits/navigation-menu/components/navigation-menu-content.svelte.d.ts +1 -1
- package/dist/bits/navigation-menu/components/navigation-menu-indicator-impl.svelte +32 -0
- package/dist/bits/navigation-menu/components/navigation-menu-indicator-impl.svelte.d.ts +4 -0
- package/dist/bits/navigation-menu/components/navigation-menu-indicator.svelte +7 -19
- package/dist/bits/navigation-menu/components/navigation-menu-list.svelte +10 -8
- package/dist/bits/navigation-menu/components/navigation-menu-sub.svelte +44 -0
- package/dist/bits/navigation-menu/components/navigation-menu-sub.svelte.d.ts +4 -0
- package/dist/bits/navigation-menu/components/navigation-menu-trigger.svelte +3 -3
- package/dist/bits/navigation-menu/components/navigation-menu-viewport.svelte +3 -3
- package/dist/bits/navigation-menu/exports.d.ts +2 -1
- package/dist/bits/navigation-menu/exports.js +1 -0
- package/dist/bits/navigation-menu/navigation-menu.svelte.d.ts +209 -201
- package/dist/bits/navigation-menu/navigation-menu.svelte.js +572 -621
- package/dist/bits/navigation-menu/types.d.ts +18 -2
- package/dist/bits/select/select.svelte.d.ts +2 -2
- package/dist/bits/select/select.svelte.js +27 -8
- package/dist/bits/utilities/dismissible-layer/use-dismissable-layer.svelte.js +1 -1
- package/dist/bits/utilities/focus-scope/use-focus-scope.svelte.js +2 -2
- package/dist/bits/utilities/popper-layer/popper-layer.svelte +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/events.d.ts +1 -1
- package/dist/internal/events.js +2 -2
- package/dist/internal/previous-with-init.svelte.d.ts +11 -0
- package/dist/internal/previous-with-init.svelte.js +21 -0
- package/dist/internal/tabbable.d.ts +1 -0
- package/dist/internal/tabbable.js +1 -1
- package/dist/internal/use-arrow-navigation.d.ts +0 -1
- package/dist/internal/use-arrow-navigation.js +2 -2
- package/package.json +1 -1
- package/dist/bits/command/command-score.d.ts +0 -1
|
@@ -1,20 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on Radix UI's Navigation Menu
|
|
3
|
+
* https://www.radix-ui.com/docs/primitives/components/navigation-menu
|
|
4
|
+
*/
|
|
5
|
+
import { afterSleep, afterTick, box, useRefById, } from "svelte-toolbelt";
|
|
6
|
+
import { Context, useDebounce, watch } from "runed";
|
|
1
7
|
import { untrack } from "svelte";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
8
|
+
import { SvelteMap } from "svelte/reactivity";
|
|
9
|
+
import { useId } from "../../shared/index.js";
|
|
10
|
+
import { getAriaExpanded, getDataDisabled, getDataOpenClosed, getDataOrientation, } from "../../internal/attrs.js";
|
|
11
|
+
import { noop } from "../../internal/noop.js";
|
|
4
12
|
import { getTabbableCandidates } from "../../internal/focus.js";
|
|
5
|
-
import { getAriaExpanded, getAriaHidden, getDataDisabled, getDataOpenClosed, getDataOrientation, getDisabled, } from "../../internal/attrs.js";
|
|
6
|
-
import { useId } from "../../internal/use-id.js";
|
|
7
13
|
import { kbd } from "../../internal/kbd.js";
|
|
14
|
+
import { CustomEventDispatcher } from "../../internal/events.js";
|
|
15
|
+
import { useRovingFocus } from "../../internal/use-roving-focus.svelte.js";
|
|
8
16
|
import { useArrowNavigation } from "../../internal/use-arrow-navigation.js";
|
|
9
17
|
import { boxAutoReset } from "../../internal/box-auto-reset.svelte.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
const NavigationMenuRootContext = new Context("NavigationMenu.Root");
|
|
13
|
-
const NavigationMenuMenuContext = new Context("NavigationMenu.Root | NavigationMenu.Sub");
|
|
14
|
-
const NavigationMenuListContext = new Context("NavigationMenu.List");
|
|
15
|
-
const NavigationMenuItemContext = new Context("NavigationMenu.Item");
|
|
16
|
-
const NavigationMenuContentContext = new Context("NavigationMenu.Content");
|
|
18
|
+
import { useResizeObserver } from "../../internal/use-resize-observer.svelte.js";
|
|
19
|
+
import { isElement } from "../../internal/is.js";
|
|
17
20
|
const NAVIGATION_MENU_ROOT_ATTR = "data-navigation-menu-root";
|
|
21
|
+
const NAVIGATION_MENU_ATTR = "data-navigation-menu";
|
|
18
22
|
const NAVIGATION_MENU_SUB_ATTR = "data-navigation-menu-sub";
|
|
19
23
|
const NAVIGATION_MENU_ITEM_ATTR = "data-navigation-menu-item";
|
|
20
24
|
const NAVIGATION_MENU_INDICATOR_ATTR = "data-navigation-menu-indicator";
|
|
@@ -22,517 +26,407 @@ const NAVIGATION_MENU_LIST_ATTR = "data-navigation-menu-list";
|
|
|
22
26
|
const NAVIGATION_MENU_TRIGGER_ATTR = "data-navigation-menu-trigger";
|
|
23
27
|
const NAVIGATION_MENU_CONTENT_ATTR = "data-navigation-menu-content";
|
|
24
28
|
const NAVIGATION_MENU_LINK_ATTR = "data-navigation-menu-link";
|
|
25
|
-
class
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
44
|
-
this.skipDelayDuration = props.skipDelayDuration;
|
|
45
|
-
this.orientation = props.orientation;
|
|
46
|
-
this.dir = props.dir;
|
|
47
|
-
this.value = props.value;
|
|
48
|
-
this.rootRef = props.ref;
|
|
49
|
-
this.onTriggerEnter = this.onTriggerEnter.bind(this);
|
|
50
|
-
this.onTriggerLeave = this.onTriggerLeave.bind(this);
|
|
51
|
-
this.onContentEnter = this.onContentEnter.bind(this);
|
|
52
|
-
this.onContentLeave = this.onContentLeave.bind(this);
|
|
53
|
-
this.onItemSelect = this.onItemSelect.bind(this);
|
|
54
|
-
this.onItemDismiss = this.onItemDismiss.bind(this);
|
|
55
|
-
useRefById({
|
|
56
|
-
id: this.id,
|
|
57
|
-
ref: this.rootRef,
|
|
58
|
-
});
|
|
59
|
-
$effect(() => {
|
|
60
|
-
this.value.current;
|
|
61
|
-
untrack(() => {
|
|
62
|
-
const curr = this.value.current;
|
|
63
|
-
const isOpen = curr !== "";
|
|
64
|
-
const hasSkipDelayDuration = this.skipDelayDuration.current > 0;
|
|
65
|
-
if (isOpen) {
|
|
66
|
-
window.clearTimeout(this.skipDelayTimer);
|
|
67
|
-
if (hasSkipDelayDuration)
|
|
68
|
-
this.isOpenDelayed = false;
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
window.clearTimeout(this.skipDelayTimer);
|
|
72
|
-
this.skipDelayTimer = window.setTimeout(() => (this.isOpenDelayed = true), this.skipDelayDuration.current);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
$effect(() => {
|
|
77
|
-
return () => {
|
|
78
|
-
window.clearTimeout(this.openTimer);
|
|
79
|
-
window.clearTimeout(this.closeTimer);
|
|
80
|
-
window.clearTimeout(this.skipDelayTimer);
|
|
81
|
-
};
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
#startCloseTimer() {
|
|
85
|
-
window.clearTimeout(this.closeTimer);
|
|
86
|
-
this.closeTimer = window.setTimeout(() => this.#setValue(""), 150);
|
|
87
|
-
}
|
|
88
|
-
#handleOpen(itemValue) {
|
|
89
|
-
window.clearTimeout(this.closeTimer);
|
|
90
|
-
this.#setValue(itemValue);
|
|
91
|
-
}
|
|
92
|
-
handleClose() {
|
|
93
|
-
this.onItemDismiss();
|
|
94
|
-
this.onContentLeave();
|
|
29
|
+
class NavigationMenuProviderState {
|
|
30
|
+
opts;
|
|
31
|
+
indicatorTrackRef = box(null);
|
|
32
|
+
viewportRef = box(null);
|
|
33
|
+
viewportContent = new SvelteMap();
|
|
34
|
+
onTriggerEnter;
|
|
35
|
+
onTriggerLeave = noop;
|
|
36
|
+
onContentEnter = noop;
|
|
37
|
+
onContentLeave = noop;
|
|
38
|
+
onItemSelect;
|
|
39
|
+
onItemDismiss;
|
|
40
|
+
constructor(opts) {
|
|
41
|
+
this.opts = opts;
|
|
42
|
+
this.onTriggerEnter = opts.onTriggerEnter;
|
|
43
|
+
this.onTriggerLeave = opts.onTriggerLeave ?? noop;
|
|
44
|
+
this.onContentEnter = opts.onContentEnter ?? noop;
|
|
45
|
+
this.onContentLeave = opts.onContentLeave ?? noop;
|
|
46
|
+
this.onItemDismiss = opts.onItemDismiss;
|
|
47
|
+
this.onItemSelect = opts.onItemSelect;
|
|
95
48
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
49
|
+
}
|
|
50
|
+
class NavigationMenuRootState {
|
|
51
|
+
opts;
|
|
52
|
+
provider;
|
|
53
|
+
previousValue = box("");
|
|
54
|
+
isDelaySkipped;
|
|
55
|
+
#derivedDelay = $derived.by(() => {
|
|
56
|
+
const isOpen = this.opts?.value?.current !== "";
|
|
57
|
+
if (isOpen || this.isDelaySkipped.current) {
|
|
58
|
+
// 150 for user to switch trigger or move into content view
|
|
59
|
+
return 100;
|
|
102
60
|
}
|
|
103
61
|
else {
|
|
104
|
-
this.
|
|
105
|
-
window.clearTimeout(this.closeTimer);
|
|
106
|
-
this.#setValue(itemValue);
|
|
107
|
-
}, this.delayDuration.current);
|
|
62
|
+
return this.opts.delayDuration.current;
|
|
108
63
|
}
|
|
64
|
+
});
|
|
65
|
+
constructor(opts) {
|
|
66
|
+
this.opts = opts;
|
|
67
|
+
this.isDelaySkipped = boxAutoReset(false, this.opts.skipDelayDuration.current);
|
|
68
|
+
useRefById(opts);
|
|
69
|
+
this.provider = useNavigationMenuProvider({
|
|
70
|
+
value: this.opts.value,
|
|
71
|
+
previousValue: this.previousValue,
|
|
72
|
+
dir: this.opts.dir,
|
|
73
|
+
orientation: this.opts.orientation,
|
|
74
|
+
rootNavigationMenuRef: this.opts.ref,
|
|
75
|
+
isRootMenu: true,
|
|
76
|
+
onTriggerEnter: (itemValue) => {
|
|
77
|
+
this.#onTriggerEnter(itemValue);
|
|
78
|
+
},
|
|
79
|
+
onTriggerLeave: this.#onTriggerLeave,
|
|
80
|
+
onContentEnter: this.#onContentEnter,
|
|
81
|
+
onContentLeave: this.#onContentLeave,
|
|
82
|
+
onItemSelect: this.#onItemSelect,
|
|
83
|
+
onItemDismiss: this.#onItemDismiss,
|
|
84
|
+
});
|
|
109
85
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
this
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
this.#handleOpen(itemValue);
|
|
86
|
+
#debouncedFn = useDebounce((val) => {
|
|
87
|
+
// passing `undefined` meant to reset the debounce timer
|
|
88
|
+
if (typeof val === "string") {
|
|
89
|
+
this.setValue(val);
|
|
117
90
|
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.#
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
91
|
+
}, () => this.#derivedDelay);
|
|
92
|
+
#onTriggerEnter = (itemValue) => {
|
|
93
|
+
this.#debouncedFn(itemValue);
|
|
94
|
+
};
|
|
95
|
+
#onTriggerLeave = () => {
|
|
96
|
+
this.isDelaySkipped.current = false;
|
|
97
|
+
this.#debouncedFn("");
|
|
98
|
+
};
|
|
99
|
+
#onContentEnter = () => {
|
|
100
|
+
this.#debouncedFn();
|
|
101
|
+
};
|
|
102
|
+
#onContentLeave = () => {
|
|
103
|
+
this.#debouncedFn("");
|
|
104
|
+
};
|
|
105
|
+
#onItemSelect = (itemValue) => {
|
|
106
|
+
this.setValue(itemValue);
|
|
107
|
+
};
|
|
108
|
+
#onItemDismiss = () => {
|
|
109
|
+
this.setValue("");
|
|
110
|
+
};
|
|
111
|
+
setValue = (newValue) => {
|
|
112
|
+
this.previousValue.current = this.opts.value.current;
|
|
113
|
+
this.opts.value.current = newValue;
|
|
114
|
+
};
|
|
136
115
|
props = $derived.by(() => ({
|
|
137
|
-
id: this.id.current,
|
|
138
|
-
"
|
|
139
|
-
|
|
140
|
-
dir: this.dir.current,
|
|
116
|
+
id: this.opts.id.current,
|
|
117
|
+
"data-orientation": getDataOrientation(this.opts.orientation.current),
|
|
118
|
+
dir: this.opts.dir.current,
|
|
141
119
|
[NAVIGATION_MENU_ROOT_ATTR]: "",
|
|
120
|
+
[NAVIGATION_MENU_ATTR]: "",
|
|
142
121
|
}));
|
|
143
122
|
}
|
|
144
|
-
class NavigationMenuMenuState {
|
|
145
|
-
isRoot = $state(false);
|
|
146
|
-
rootNavigationId;
|
|
147
|
-
dir;
|
|
148
|
-
orientation;
|
|
149
|
-
value;
|
|
150
|
-
previousValue;
|
|
151
|
-
onTriggerEnter;
|
|
152
|
-
onTriggerLeave;
|
|
153
|
-
onContentEnter;
|
|
154
|
-
onContentLeave;
|
|
155
|
-
onItemSelect;
|
|
156
|
-
onItemDismiss;
|
|
157
|
-
viewportNode = $state(null);
|
|
158
|
-
indicatorTrackNode = $state(null);
|
|
159
|
-
viewportContentId = box.with(() => undefined);
|
|
160
|
-
root;
|
|
161
|
-
triggerRefs = new Set();
|
|
162
|
-
constructor(props, root) {
|
|
163
|
-
this.isRoot = props.isRoot;
|
|
164
|
-
this.rootNavigationId = props.rootNavigationId;
|
|
165
|
-
this.dir = props.dir;
|
|
166
|
-
this.orientation = props.orientation;
|
|
167
|
-
this.value = props.value;
|
|
168
|
-
this.onTriggerEnter = props.onTriggerEnter;
|
|
169
|
-
this.onTriggerLeave = props.onTriggerLeave;
|
|
170
|
-
this.onContentEnter = props.onContentEnter;
|
|
171
|
-
this.onContentLeave = props.onContentLeave;
|
|
172
|
-
this.onItemSelect = props.onItemSelect;
|
|
173
|
-
this.onItemDismiss = props.onItemDismiss;
|
|
174
|
-
this.root = root;
|
|
175
|
-
this.previousValue = props.previousValue;
|
|
176
|
-
}
|
|
177
|
-
registerTrigger(ref) {
|
|
178
|
-
this.triggerRefs.add(ref);
|
|
179
|
-
}
|
|
180
|
-
deRegisterTrigger(ref) {
|
|
181
|
-
this.triggerRefs.delete(ref);
|
|
182
|
-
}
|
|
183
|
-
getTriggerNodes() {
|
|
184
|
-
return Array.from(this.triggerRefs)
|
|
185
|
-
.map((ref) => ref.current)
|
|
186
|
-
.filter((node) => Boolean(node));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
123
|
class NavigationMenuSubState {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.id = props.id;
|
|
208
|
-
this.rootNavigationId = root.id;
|
|
209
|
-
this.dir = root.dir;
|
|
210
|
-
this.orientation = props.orientation;
|
|
211
|
-
this.value = props.value;
|
|
212
|
-
this.root = root;
|
|
213
|
-
this.ref = props.ref;
|
|
214
|
-
useRefById({
|
|
215
|
-
id: this.id,
|
|
216
|
-
ref: this.ref,
|
|
124
|
+
opts;
|
|
125
|
+
context;
|
|
126
|
+
previousValue = box("");
|
|
127
|
+
constructor(opts, context) {
|
|
128
|
+
this.opts = opts;
|
|
129
|
+
this.context = context;
|
|
130
|
+
useRefById(opts);
|
|
131
|
+
useNavigationMenuProvider({
|
|
132
|
+
isRootMenu: false,
|
|
133
|
+
value: this.opts.value,
|
|
134
|
+
dir: this.context.opts.dir,
|
|
135
|
+
orientation: this.opts.orientation,
|
|
136
|
+
rootNavigationMenuRef: this.opts.ref,
|
|
137
|
+
onTriggerEnter: this.setValue,
|
|
138
|
+
onItemSelect: this.setValue,
|
|
139
|
+
onItemDismiss: () => this.setValue(""),
|
|
140
|
+
previousValue: this.previousValue,
|
|
217
141
|
});
|
|
218
142
|
}
|
|
219
|
-
|
|
220
|
-
this.value.current =
|
|
221
|
-
}
|
|
222
|
-
onItemSelect(itemValue) {
|
|
223
|
-
this.value.current = itemValue;
|
|
224
|
-
}
|
|
225
|
-
registerTrigger(ref) {
|
|
226
|
-
this.triggerRefs.add(ref);
|
|
227
|
-
}
|
|
228
|
-
deRegisterTrigger(ref) {
|
|
229
|
-
this.triggerRefs.delete(ref);
|
|
230
|
-
}
|
|
231
|
-
getTriggerNodes() {
|
|
232
|
-
return Array.from(this.triggerRefs)
|
|
233
|
-
.map((ref) => ref.current)
|
|
234
|
-
.filter((node) => Boolean(node));
|
|
235
|
-
}
|
|
143
|
+
setValue = (newValue) => {
|
|
144
|
+
this.opts.value.current = newValue;
|
|
145
|
+
};
|
|
236
146
|
props = $derived.by(() => ({
|
|
237
|
-
id: this.id.current,
|
|
238
|
-
"data-orientation": getDataOrientation(this.orientation.current),
|
|
147
|
+
id: this.opts.id.current,
|
|
148
|
+
"data-orientation": getDataOrientation(this.opts.orientation.current),
|
|
239
149
|
[NAVIGATION_MENU_SUB_ATTR]: "",
|
|
150
|
+
[NAVIGATION_MENU_ATTR]: "",
|
|
240
151
|
}));
|
|
241
152
|
}
|
|
242
153
|
class NavigationMenuListState {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
154
|
+
opts;
|
|
155
|
+
context;
|
|
156
|
+
wrapperId = box(useId());
|
|
157
|
+
wrapperRef = box(null);
|
|
158
|
+
listTriggers = $state.raw([]);
|
|
248
159
|
rovingFocusGroup;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this
|
|
252
|
-
this
|
|
253
|
-
|
|
254
|
-
this.rovingFocusGroup = useRovingFocus({
|
|
255
|
-
rootNodeId: this.#id,
|
|
256
|
-
candidateAttr: NAVIGATION_MENU_TRIGGER_ATTR,
|
|
257
|
-
candidateSelector: `:is([${NAVIGATION_MENU_TRIGGER_ATTR}], [data-list-link]):not([data-disabled])`,
|
|
258
|
-
loop: box.with(() => false),
|
|
259
|
-
orientation: this.menu.orientation,
|
|
260
|
-
});
|
|
160
|
+
wrapperMounted = $state(false);
|
|
161
|
+
constructor(opts, context) {
|
|
162
|
+
this.opts = opts;
|
|
163
|
+
this.context = context;
|
|
164
|
+
useRefById(opts);
|
|
261
165
|
useRefById({
|
|
262
|
-
id: this
|
|
263
|
-
ref: this
|
|
264
|
-
});
|
|
265
|
-
useRefById({
|
|
266
|
-
id: this.indicatorTrackId,
|
|
267
|
-
ref: this.indicatorTrackRef,
|
|
166
|
+
id: this.wrapperId,
|
|
167
|
+
ref: this.wrapperRef,
|
|
268
168
|
onRefChange: (node) => {
|
|
269
|
-
this.
|
|
169
|
+
this.context.indicatorTrackRef.current = node;
|
|
270
170
|
},
|
|
271
|
-
deps: () =>
|
|
171
|
+
deps: () => this.wrapperMounted,
|
|
172
|
+
});
|
|
173
|
+
this.rovingFocusGroup = useRovingFocus({
|
|
174
|
+
rootNodeId: opts.id,
|
|
175
|
+
candidateAttr: NAVIGATION_MENU_ITEM_ATTR,
|
|
176
|
+
candidateSelector: `:is([${NAVIGATION_MENU_TRIGGER_ATTR}], [data-list-link]):not([data-disabled])`,
|
|
177
|
+
loop: box.with(() => false),
|
|
178
|
+
orientation: this.context.opts.orientation,
|
|
272
179
|
});
|
|
273
180
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
181
|
+
registerTrigger(trigger) {
|
|
182
|
+
if (trigger)
|
|
183
|
+
this.listTriggers.push(trigger);
|
|
184
|
+
return () => {
|
|
185
|
+
this.listTriggers = this.listTriggers.filter((t) => t.id !== trigger.id);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
wrapperProps = $derived.by(() => ({
|
|
189
|
+
id: this.wrapperId.current,
|
|
279
190
|
}));
|
|
280
191
|
props = $derived.by(() => ({
|
|
281
|
-
id: this
|
|
282
|
-
"data-orientation": getDataOrientation(this.
|
|
192
|
+
id: this.opts.id.current,
|
|
193
|
+
"data-orientation": getDataOrientation(this.context.opts.orientation.current),
|
|
283
194
|
[NAVIGATION_MENU_LIST_ATTR]: "",
|
|
284
195
|
}));
|
|
285
196
|
}
|
|
286
|
-
class NavigationMenuItemState {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
value;
|
|
197
|
+
export class NavigationMenuItemState {
|
|
198
|
+
opts;
|
|
199
|
+
listContext;
|
|
290
200
|
contentNode = $state(null);
|
|
291
201
|
triggerNode = $state(null);
|
|
292
|
-
focusProxyRef = box(null);
|
|
293
202
|
focusProxyNode = $state(null);
|
|
294
|
-
focusProxyId = box(useId());
|
|
295
203
|
restoreContentTabOrder = noop;
|
|
296
|
-
wasEscapeClose =
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
this.
|
|
304
|
-
this.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
useRefById({
|
|
308
|
-
id: this.id,
|
|
309
|
-
ref: this.#ref,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
handleContentEntry(side = "start") {
|
|
204
|
+
wasEscapeClose = false;
|
|
205
|
+
contentId = $derived.by(() => this.contentNode?.id);
|
|
206
|
+
triggerId = $derived.by(() => this.triggerNode?.id);
|
|
207
|
+
contentChildren = box(undefined);
|
|
208
|
+
contentChild = box(undefined);
|
|
209
|
+
contentProps = box({});
|
|
210
|
+
constructor(opts, listContext) {
|
|
211
|
+
this.opts = opts;
|
|
212
|
+
this.listContext = listContext;
|
|
213
|
+
}
|
|
214
|
+
#handleContentEntry = (side = "start") => {
|
|
313
215
|
if (!this.contentNode)
|
|
314
216
|
return;
|
|
315
217
|
this.restoreContentTabOrder();
|
|
316
218
|
const candidates = getTabbableCandidates(this.contentNode);
|
|
317
|
-
if (candidates.length)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
else {
|
|
322
|
-
candidates[candidates.length - 1]?.focus();
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
handleContentExit() {
|
|
219
|
+
if (candidates.length)
|
|
220
|
+
focusFirst(side === "start" ? candidates : candidates.reverse());
|
|
221
|
+
};
|
|
222
|
+
#handleContentExit = () => {
|
|
327
223
|
if (!this.contentNode)
|
|
328
224
|
return;
|
|
329
225
|
const candidates = getTabbableCandidates(this.contentNode);
|
|
330
|
-
if (candidates.length)
|
|
226
|
+
if (candidates.length)
|
|
331
227
|
this.restoreContentTabOrder = removeFromTabOrder(candidates);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
onContentFocusOutside = this
|
|
337
|
-
onRootContentClose = this.handleContentExit;
|
|
228
|
+
};
|
|
229
|
+
onEntryKeydown = this.#handleContentEntry;
|
|
230
|
+
onFocusProxyEnter = this.#handleContentEntry;
|
|
231
|
+
onRootContentClose = this.#handleContentExit;
|
|
232
|
+
onContentFocusOutside = this.#handleContentExit;
|
|
338
233
|
props = $derived.by(() => ({
|
|
339
|
-
id: this.id.current,
|
|
234
|
+
id: this.opts.id.current,
|
|
340
235
|
[NAVIGATION_MENU_ITEM_ATTR]: "",
|
|
341
236
|
}));
|
|
342
237
|
}
|
|
343
238
|
class NavigationMenuTriggerState {
|
|
344
|
-
|
|
345
|
-
|
|
239
|
+
opts;
|
|
240
|
+
focusProxyId = box(useId());
|
|
241
|
+
focusProxyRef = box(null);
|
|
242
|
+
context;
|
|
243
|
+
itemContext;
|
|
244
|
+
listContext;
|
|
245
|
+
hasPointerMoveOpened = box(false);
|
|
246
|
+
wasClickClose = false;
|
|
247
|
+
open = $derived.by(() => this.itemContext.opts.value.current === this.context.opts.value.current);
|
|
346
248
|
focusProxyMounted = $state(false);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
constructor(props, item) {
|
|
354
|
-
this.#id = props.id;
|
|
355
|
-
this.#ref = props.ref;
|
|
356
|
-
this.item = item;
|
|
357
|
-
this.menu = item.menu;
|
|
358
|
-
this.disabled = props.disabled;
|
|
249
|
+
constructor(opts, context) {
|
|
250
|
+
this.opts = opts;
|
|
251
|
+
this.hasPointerMoveOpened = boxAutoReset(false, 300);
|
|
252
|
+
this.context = context.provider;
|
|
253
|
+
this.itemContext = context.item;
|
|
254
|
+
this.listContext = context.list;
|
|
359
255
|
useRefById({
|
|
360
|
-
|
|
361
|
-
ref: this.#ref,
|
|
256
|
+
...opts,
|
|
362
257
|
onRefChange: (node) => {
|
|
363
|
-
this.
|
|
258
|
+
this.itemContext.triggerNode = node;
|
|
364
259
|
},
|
|
365
260
|
});
|
|
366
261
|
useRefById({
|
|
367
|
-
id: this.
|
|
368
|
-
ref: this.
|
|
262
|
+
id: this.focusProxyId,
|
|
263
|
+
ref: this.focusProxyRef,
|
|
369
264
|
onRefChange: (node) => {
|
|
370
|
-
this.
|
|
265
|
+
this.itemContext.focusProxyNode = node;
|
|
371
266
|
},
|
|
372
267
|
deps: () => this.focusProxyMounted,
|
|
373
268
|
});
|
|
374
|
-
|
|
375
|
-
this.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
269
|
+
watch(() => this.opts.ref.current, () => {
|
|
270
|
+
const node = this.opts.ref.current;
|
|
271
|
+
if (!node)
|
|
272
|
+
return;
|
|
273
|
+
return this.listContext.registerTrigger(node);
|
|
379
274
|
});
|
|
380
|
-
this.onpointerenter = this.onpointerenter.bind(this);
|
|
381
|
-
this.onpointermove = this.onpointermove.bind(this);
|
|
382
|
-
this.onpointerleave = this.onpointerleave.bind(this);
|
|
383
|
-
this.onclick = this.onclick.bind(this);
|
|
384
|
-
this.onkeydown = this.onkeydown.bind(this);
|
|
385
275
|
}
|
|
386
|
-
onpointerenter(_) {
|
|
276
|
+
onpointerenter = (_) => {
|
|
387
277
|
this.wasClickClose = false;
|
|
388
|
-
this.
|
|
389
|
-
}
|
|
390
|
-
onpointermove(
|
|
391
|
-
if (
|
|
392
|
-
return;
|
|
393
|
-
if (this.disabled.current ||
|
|
278
|
+
this.itemContext.wasEscapeClose = false;
|
|
279
|
+
};
|
|
280
|
+
onpointermove = whenMouse(() => {
|
|
281
|
+
if (this.opts.disabled.current ||
|
|
394
282
|
this.wasClickClose ||
|
|
395
|
-
this.
|
|
396
|
-
this.hasPointerMoveOpened.current)
|
|
283
|
+
this.itemContext.wasEscapeClose ||
|
|
284
|
+
this.hasPointerMoveOpened.current) {
|
|
397
285
|
return;
|
|
398
|
-
|
|
286
|
+
}
|
|
287
|
+
this.context.onTriggerEnter(this.itemContext.opts.value.current);
|
|
399
288
|
this.hasPointerMoveOpened.current = true;
|
|
400
|
-
}
|
|
401
|
-
onpointerleave(
|
|
402
|
-
if (
|
|
289
|
+
});
|
|
290
|
+
onpointerleave = whenMouse(() => {
|
|
291
|
+
if (this.opts.disabled.current)
|
|
403
292
|
return;
|
|
404
|
-
this.
|
|
293
|
+
this.context.onTriggerLeave();
|
|
405
294
|
this.hasPointerMoveOpened.current = false;
|
|
406
|
-
}
|
|
407
|
-
onclick(_) {
|
|
408
|
-
// if opened via pointer move, we prevent
|
|
295
|
+
});
|
|
296
|
+
onclick = (_) => {
|
|
297
|
+
// if opened via pointer move, we prevent the click event
|
|
409
298
|
if (this.hasPointerMoveOpened.current)
|
|
410
299
|
return;
|
|
411
300
|
if (this.open) {
|
|
412
|
-
this.
|
|
301
|
+
this.context.onItemSelect("");
|
|
413
302
|
}
|
|
414
303
|
else {
|
|
415
|
-
this.
|
|
304
|
+
this.context.onItemSelect(this.itemContext.opts.value.current);
|
|
416
305
|
}
|
|
417
306
|
this.wasClickClose = this.open;
|
|
418
|
-
}
|
|
419
|
-
onkeydown(e) {
|
|
420
|
-
const verticalEntryKey = this.
|
|
421
|
-
const entryKey = {
|
|
422
|
-
horizontal: kbd.ARROW_DOWN,
|
|
423
|
-
vertical: verticalEntryKey,
|
|
424
|
-
}[this.menu.orientation.current];
|
|
307
|
+
};
|
|
308
|
+
onkeydown = (e) => {
|
|
309
|
+
const verticalEntryKey = this.context.opts.dir.current === "rtl" ? kbd.ARROW_LEFT : kbd.ARROW_RIGHT;
|
|
310
|
+
const entryKey = { horizontal: kbd.ARROW_DOWN, vertical: verticalEntryKey }[this.context.opts.orientation.current];
|
|
425
311
|
if (this.open && e.key === entryKey) {
|
|
426
|
-
this.
|
|
312
|
+
this.itemContext.onEntryKeydown();
|
|
313
|
+
// prevent focus group from handling the event
|
|
427
314
|
e.preventDefault();
|
|
428
315
|
return;
|
|
429
316
|
}
|
|
430
|
-
this.
|
|
431
|
-
}
|
|
317
|
+
this.itemContext.listContext.rovingFocusGroup.handleKeydown(this.opts.ref.current, e);
|
|
318
|
+
};
|
|
319
|
+
focusProxyOnFocus = (e) => {
|
|
320
|
+
const content = this.itemContext.contentNode;
|
|
321
|
+
const prevFocusedElement = e.relatedTarget;
|
|
322
|
+
const wasTriggerFocused = this.opts.ref.current && prevFocusedElement === this.opts.ref.current;
|
|
323
|
+
const wasFocusFromContent = content?.contains(prevFocusedElement);
|
|
324
|
+
if (wasTriggerFocused || !wasFocusFromContent) {
|
|
325
|
+
this.itemContext.onFocusProxyEnter(wasTriggerFocused ? "start" : "end");
|
|
326
|
+
}
|
|
327
|
+
};
|
|
432
328
|
props = $derived.by(() => ({
|
|
433
|
-
id: this
|
|
434
|
-
disabled:
|
|
435
|
-
"data-disabled": getDataDisabled(this.disabled.current),
|
|
329
|
+
id: this.opts.id.current,
|
|
330
|
+
disabled: this.opts.disabled.current,
|
|
331
|
+
"data-disabled": getDataDisabled(Boolean(this.opts.disabled.current)),
|
|
436
332
|
"data-state": getDataOpenClosed(this.open),
|
|
333
|
+
"data-value": this.itemContext.opts.value.current,
|
|
437
334
|
"aria-expanded": getAriaExpanded(this.open),
|
|
438
|
-
"aria-controls": this.
|
|
439
|
-
"
|
|
440
|
-
onpointerenter: this.onpointerenter,
|
|
335
|
+
"aria-controls": this.itemContext.contentId,
|
|
336
|
+
[NAVIGATION_MENU_TRIGGER_ATTR]: "",
|
|
441
337
|
onpointermove: this.onpointermove,
|
|
442
338
|
onpointerleave: this.onpointerleave,
|
|
339
|
+
onpointerenter: this.onpointerenter,
|
|
443
340
|
onclick: this.onclick,
|
|
444
341
|
onkeydown: this.onkeydown,
|
|
445
|
-
[NAVIGATION_MENU_TRIGGER_ATTR]: "",
|
|
446
342
|
}));
|
|
447
|
-
|
|
448
|
-
id: this.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const wasFocusFromContent = this.item.contentNode?.contains(prevFocusedElement);
|
|
455
|
-
if (wasTriggerFocused || !wasFocusFromContent) {
|
|
456
|
-
e.preventDefault();
|
|
457
|
-
this.item.onFocusProxyEnter(wasTriggerFocused ? "start" : "end");
|
|
458
|
-
}
|
|
459
|
-
},
|
|
343
|
+
focusProxyProps = $derived.by(() => ({
|
|
344
|
+
id: this.focusProxyId.current,
|
|
345
|
+
tabindex: 0,
|
|
346
|
+
onfocus: this.focusProxyOnFocus,
|
|
347
|
+
}));
|
|
348
|
+
restructureSpanProps = $derived.by(() => ({
|
|
349
|
+
"aria-owns": this.itemContext.contentId,
|
|
460
350
|
}));
|
|
461
351
|
}
|
|
352
|
+
const LINK_SELECT_EVENT = new CustomEventDispatcher("bitsLinkSelect", {
|
|
353
|
+
bubbles: true,
|
|
354
|
+
cancelable: true,
|
|
355
|
+
});
|
|
356
|
+
const ROOT_CONTENT_DISMISS_EVENT = new CustomEventDispatcher("bitsRootContentDismiss", {
|
|
357
|
+
cancelable: true,
|
|
358
|
+
bubbles: true,
|
|
359
|
+
});
|
|
462
360
|
class NavigationMenuLinkState {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
this.onSelect
|
|
474
|
-
|
|
475
|
-
this.item = item;
|
|
476
|
-
useRefById({
|
|
477
|
-
id: this.#id,
|
|
478
|
-
ref: this.#ref,
|
|
479
|
-
});
|
|
480
|
-
this.onclick = this.onclick.bind(this);
|
|
481
|
-
this.onkeydown = this.onkeydown.bind(this);
|
|
482
|
-
}
|
|
483
|
-
onclick(e) {
|
|
484
|
-
const linkSelectEvent = new CustomEvent("navigationMenu.linkSelect", {
|
|
485
|
-
bubbles: true,
|
|
486
|
-
cancelable: true,
|
|
487
|
-
});
|
|
488
|
-
this.onSelect.current(linkSelectEvent);
|
|
361
|
+
opts;
|
|
362
|
+
context;
|
|
363
|
+
isFocused = $state(false);
|
|
364
|
+
constructor(opts, context) {
|
|
365
|
+
this.opts = opts;
|
|
366
|
+
this.context = context;
|
|
367
|
+
useRefById(opts);
|
|
368
|
+
}
|
|
369
|
+
onclick = (e) => {
|
|
370
|
+
const currTarget = e.currentTarget;
|
|
371
|
+
LINK_SELECT_EVENT.listen(currTarget, (e) => this.opts.onSelect.current(e), { once: true });
|
|
372
|
+
const linkSelectEvent = LINK_SELECT_EVENT.dispatch(currTarget);
|
|
489
373
|
if (!linkSelectEvent.defaultPrevented && !e.metaKey) {
|
|
490
|
-
|
|
374
|
+
ROOT_CONTENT_DISMISS_EVENT.dispatch(currTarget);
|
|
491
375
|
}
|
|
492
|
-
}
|
|
493
|
-
onkeydown(e) {
|
|
494
|
-
this.item.
|
|
495
|
-
|
|
376
|
+
};
|
|
377
|
+
onkeydown = (e) => {
|
|
378
|
+
if (this.context.item.contentNode)
|
|
379
|
+
return;
|
|
380
|
+
this.context.item.listContext.rovingFocusGroup.handleKeydown(this.opts.ref.current, e);
|
|
381
|
+
};
|
|
382
|
+
onfocus = (_) => {
|
|
383
|
+
this.isFocused = true;
|
|
384
|
+
};
|
|
385
|
+
onblur = (_) => {
|
|
386
|
+
this.isFocused = false;
|
|
387
|
+
};
|
|
496
388
|
props = $derived.by(() => ({
|
|
497
|
-
id: this
|
|
498
|
-
"data-active": this.active.current ? "" : undefined,
|
|
499
|
-
"aria-current": this.active.current ? "page" : undefined,
|
|
500
|
-
"data-
|
|
389
|
+
id: this.opts.id.current,
|
|
390
|
+
"data-active": this.opts.active.current ? "" : undefined,
|
|
391
|
+
"aria-current": this.opts.active.current ? "page" : undefined,
|
|
392
|
+
"data-focused": this.isFocused ? "" : undefined,
|
|
501
393
|
onclick: this.onclick,
|
|
502
|
-
|
|
503
|
-
|
|
394
|
+
onkeydown: this.onkeydown,
|
|
395
|
+
onfocus: this.onfocus,
|
|
396
|
+
onblur: this.onblur,
|
|
397
|
+
[NAVIGATION_MENU_LINK_ATTR]: "",
|
|
504
398
|
}));
|
|
505
399
|
}
|
|
506
400
|
class NavigationMenuIndicatorState {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
401
|
+
context;
|
|
402
|
+
isVisible = $derived.by(() => Boolean(this.context.opts.value.current));
|
|
403
|
+
constructor(context) {
|
|
404
|
+
this.context = context;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
class NavigationMenuIndicatorImplState {
|
|
408
|
+
opts;
|
|
409
|
+
context;
|
|
410
|
+
listContext;
|
|
411
|
+
position = $state.raw(null);
|
|
412
|
+
isHorizontal = $derived.by(() => this.context.opts.orientation.current === "horizontal");
|
|
413
|
+
isVisible = $derived.by(() => !!this.context.opts.value.current);
|
|
414
|
+
activeTrigger = $derived.by(() => {
|
|
415
|
+
const items = this.listContext.listTriggers;
|
|
416
|
+
const triggerNode = items.find((item) => item.getAttribute("data-value") === this.context.opts.value.current);
|
|
417
|
+
return triggerNode ?? null;
|
|
418
|
+
});
|
|
419
|
+
shouldRender = $derived.by(() => this.position !== null);
|
|
420
|
+
constructor(opts, context) {
|
|
421
|
+
this.opts = opts;
|
|
422
|
+
this.context = context.provider;
|
|
423
|
+
this.listContext = context.list;
|
|
424
|
+
useResizeObserver(() => this.activeTrigger, this.handlePositionChange);
|
|
425
|
+
useResizeObserver(() => this.context.indicatorTrackRef.current, this.handlePositionChange);
|
|
518
426
|
useRefById({
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
onRefChange: (node) => {
|
|
522
|
-
this.menu.viewportNode = node;
|
|
523
|
-
},
|
|
524
|
-
});
|
|
525
|
-
$effect(() => {
|
|
526
|
-
const triggerNodes = this.menu.getTriggerNodes();
|
|
527
|
-
const triggerNode = triggerNodes.find((node) => node.dataset.value === this.menu.value.current);
|
|
528
|
-
if (triggerNode) {
|
|
529
|
-
untrack(() => {
|
|
530
|
-
this.activeTrigger = triggerNode;
|
|
531
|
-
});
|
|
532
|
-
}
|
|
427
|
+
...opts,
|
|
428
|
+
deps: () => this.context.opts.value.current,
|
|
533
429
|
});
|
|
534
|
-
useResizeObserver(() => this.activeTrigger, this.handlePositionChange);
|
|
535
|
-
useResizeObserver(() => this.menu.indicatorTrackNode, this.handlePositionChange);
|
|
536
430
|
}
|
|
537
431
|
handlePositionChange = () => {
|
|
538
432
|
if (!this.activeTrigger)
|
|
@@ -547,127 +441,160 @@ class NavigationMenuIndicatorState {
|
|
|
547
441
|
};
|
|
548
442
|
};
|
|
549
443
|
props = $derived.by(() => ({
|
|
550
|
-
|
|
444
|
+
id: this.opts.id.current,
|
|
551
445
|
"data-state": this.isVisible ? "visible" : "hidden",
|
|
552
|
-
"data-orientation": getDataOrientation(this.
|
|
446
|
+
"data-orientation": getDataOrientation(this.context.opts.orientation.current),
|
|
553
447
|
style: {
|
|
554
448
|
position: "absolute",
|
|
555
449
|
...(this.isHorizontal
|
|
556
450
|
? {
|
|
557
451
|
left: 0,
|
|
558
|
-
width:
|
|
559
|
-
transform: this.position
|
|
560
|
-
? `translateX(${this.position.offset}px)`
|
|
561
|
-
: undefined,
|
|
452
|
+
width: `${this.position?.size}px`,
|
|
453
|
+
transform: `translateX(${this.position?.offset}px)`,
|
|
562
454
|
}
|
|
563
455
|
: {
|
|
564
456
|
top: 0,
|
|
565
|
-
height:
|
|
566
|
-
transform: this.position
|
|
567
|
-
? `translateY(${this.position.offset}px)`
|
|
568
|
-
: undefined,
|
|
457
|
+
height: `${this.position?.size}px`,
|
|
458
|
+
transform: `translateY(${this.position?.offset}px)`,
|
|
569
459
|
}),
|
|
570
460
|
},
|
|
571
461
|
[NAVIGATION_MENU_INDICATOR_ATTR]: "",
|
|
572
462
|
}));
|
|
573
463
|
}
|
|
574
464
|
class NavigationMenuContentState {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
465
|
+
opts;
|
|
466
|
+
context;
|
|
467
|
+
itemContext;
|
|
468
|
+
listContext;
|
|
469
|
+
open = $derived.by(() => this.itemContext.opts.value.current === this.context.opts.value.current);
|
|
470
|
+
mounted = $state(false);
|
|
471
|
+
value = $derived.by(() => this.itemContext.opts.value.current);
|
|
472
|
+
// We persist the last active content value as the viewport may be animating out
|
|
473
|
+
// and we want the content to remain mounted for the lifecycle of the viewport.
|
|
474
|
+
isLastActiveValue = $derived.by(() => {
|
|
475
|
+
if (this.context.viewportRef.current) {
|
|
476
|
+
if (!this.context.opts.value.current && this.context.opts.previousValue.current) {
|
|
477
|
+
return (this.context.opts.previousValue.current === this.itemContext.opts.value.current);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return false;
|
|
481
|
+
});
|
|
482
|
+
constructor(opts, context) {
|
|
483
|
+
this.opts = opts;
|
|
484
|
+
this.context = context.provider;
|
|
485
|
+
this.itemContext = context.item;
|
|
486
|
+
this.listContext = context.list;
|
|
592
487
|
useRefById({
|
|
593
|
-
|
|
594
|
-
ref: this.contentRef,
|
|
488
|
+
...opts,
|
|
595
489
|
onRefChange: (node) => {
|
|
596
|
-
this.
|
|
490
|
+
this.itemContext.contentNode = node;
|
|
597
491
|
},
|
|
598
|
-
deps: () => this.
|
|
492
|
+
deps: () => this.mounted,
|
|
599
493
|
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
494
|
+
}
|
|
495
|
+
onpointerenter = (_) => {
|
|
496
|
+
this.context.onContentEnter();
|
|
497
|
+
};
|
|
498
|
+
onpointerleave = whenMouse(() => {
|
|
499
|
+
this.context.onContentLeave();
|
|
500
|
+
});
|
|
501
|
+
props = $derived.by(() => ({
|
|
502
|
+
id: this.opts.id.current,
|
|
503
|
+
onpointerenter: this.onpointerenter,
|
|
504
|
+
onpointerleave: this.onpointerleave,
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
class NavigationMenuContentImplState {
|
|
508
|
+
opts;
|
|
509
|
+
itemContext;
|
|
510
|
+
context;
|
|
511
|
+
listContext;
|
|
512
|
+
prevMotionAttribute = $state(null);
|
|
513
|
+
motionAttribute = $derived.by(() => {
|
|
514
|
+
const items = this.listContext.listTriggers;
|
|
515
|
+
const values = items.map((item) => item.getAttribute("data-value")).filter(Boolean);
|
|
516
|
+
if (this.context.opts.dir.current === "rtl")
|
|
517
|
+
values.reverse();
|
|
518
|
+
const index = values.indexOf(this.context.opts.value.current);
|
|
519
|
+
const prevIndex = values.indexOf(this.context.opts.previousValue.current);
|
|
520
|
+
const isSelected = this.itemContext.opts.value.current === this.context.opts.value.current;
|
|
521
|
+
const wasSelected = prevIndex === values.indexOf(this.itemContext.opts.value.current);
|
|
522
|
+
// We only want to update selected and the last selected content
|
|
523
|
+
// this avoids animations being interrupted outside of that range
|
|
524
|
+
if (!isSelected && !wasSelected)
|
|
525
|
+
return untrack(() => this.prevMotionAttribute);
|
|
526
|
+
const attribute = (() => {
|
|
527
|
+
// Don't provide a direction on the initial open
|
|
528
|
+
if (index !== prevIndex) {
|
|
529
|
+
// If we're moving to this item from another
|
|
530
|
+
if (isSelected && prevIndex !== -1)
|
|
531
|
+
return index > prevIndex ? "from-end" : "from-start";
|
|
532
|
+
// If we're leaving this item for another
|
|
533
|
+
if (wasSelected && index !== -1)
|
|
534
|
+
return index > prevIndex ? "to-start" : "to-end";
|
|
616
535
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
536
|
+
// Otherwise we're entering from closed or leaving the list
|
|
537
|
+
// entirely and should not animate in any direction
|
|
538
|
+
return null;
|
|
539
|
+
})();
|
|
540
|
+
untrack(() => (this.prevMotionAttribute = attribute));
|
|
541
|
+
return attribute;
|
|
542
|
+
});
|
|
543
|
+
constructor(opts, itemContext) {
|
|
544
|
+
this.opts = opts;
|
|
545
|
+
this.itemContext = itemContext;
|
|
546
|
+
this.listContext = itemContext.listContext;
|
|
547
|
+
this.context = itemContext.listContext.context;
|
|
548
|
+
useRefById({
|
|
549
|
+
...opts,
|
|
550
|
+
deps: () => this.context.opts.value.current,
|
|
551
|
+
});
|
|
552
|
+
watch([
|
|
553
|
+
() => this.itemContext.opts.value.current,
|
|
554
|
+
() => this.itemContext.triggerNode,
|
|
555
|
+
() => this.opts.ref.current,
|
|
556
|
+
], () => {
|
|
557
|
+
const content = this.opts.ref.current;
|
|
558
|
+
if (!(content && this.context.opts.isRootMenu))
|
|
559
|
+
return;
|
|
560
|
+
const handleClose = () => {
|
|
561
|
+
this.context.onItemDismiss();
|
|
562
|
+
this.itemContext.onRootContentClose();
|
|
563
|
+
if (content.contains(document.activeElement)) {
|
|
564
|
+
this.itemContext.triggerNode?.focus();
|
|
628
565
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
this.motionAttribute = attribute;
|
|
566
|
+
};
|
|
567
|
+
const removeListener = ROOT_CONTENT_DISMISS_EVENT.listen(content, handleClose);
|
|
568
|
+
return () => {
|
|
569
|
+
removeListener();
|
|
570
|
+
};
|
|
635
571
|
});
|
|
636
|
-
this.onFocusOutside = this.onFocusOutside.bind(this);
|
|
637
|
-
this.onInteractOutside = this.onInteractOutside.bind(this);
|
|
638
|
-
this.onEscapeKeydown = this.onEscapeKeydown.bind(this);
|
|
639
|
-
this.onkeydown = this.onkeydown.bind(this);
|
|
640
572
|
}
|
|
641
|
-
onFocusOutside(e) {
|
|
642
|
-
this.
|
|
573
|
+
onFocusOutside = (e) => {
|
|
574
|
+
this.itemContext.onContentFocusOutside();
|
|
643
575
|
const target = e.target;
|
|
644
|
-
// only dismiss content when focus moves outside the menu
|
|
645
|
-
if (this.
|
|
576
|
+
// only dismiss content when focus moves outside of the menu
|
|
577
|
+
if (this.context.opts.rootNavigationMenuRef.current?.contains(target)) {
|
|
646
578
|
e.preventDefault();
|
|
647
|
-
}
|
|
648
|
-
else {
|
|
649
|
-
this.menu.root.handleClose();
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
onInteractOutside(e) {
|
|
653
|
-
if (e.defaultPrevented)
|
|
654
579
|
return;
|
|
655
|
-
const target = e.target;
|
|
656
|
-
const isTrigger = this.menu.getTriggerNodes().some((node) => node.contains(target));
|
|
657
|
-
const isRootViewport = this.menu.isRoot && this.menu.viewportNode?.contains(target);
|
|
658
|
-
if (isTrigger || isRootViewport || !this.menu.isRoot) {
|
|
659
|
-
e.preventDefault();
|
|
660
580
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
581
|
+
this.context.onItemDismiss();
|
|
582
|
+
};
|
|
583
|
+
onInteractOutside = (e) => {
|
|
664
584
|
const target = e.target;
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
585
|
+
const isTrigger = this.listContext.listTriggers.some((trigger) => trigger.contains(target));
|
|
586
|
+
const isRootViewport = this.context.opts.isRootMenu && this.context.viewportRef.current?.contains(target);
|
|
587
|
+
if (isTrigger || isRootViewport || !this.context.opts.isRootMenu)
|
|
588
|
+
e.preventDefault();
|
|
669
589
|
};
|
|
670
|
-
onkeydown(e) {
|
|
590
|
+
onkeydown = (e) => {
|
|
591
|
+
// prevent parent menus handling sub-menu keydown events
|
|
592
|
+
const target = e.target;
|
|
593
|
+
if (!isElement(target))
|
|
594
|
+
return;
|
|
595
|
+
if (target.closest(`[${NAVIGATION_MENU_ATTR}]`) !==
|
|
596
|
+
this.context.opts.rootNavigationMenuRef.current)
|
|
597
|
+
return;
|
|
671
598
|
const isMetaKey = e.altKey || e.ctrlKey || e.metaKey;
|
|
672
599
|
const isTabKey = e.key === kbd.TAB && !isMetaKey;
|
|
673
600
|
const candidates = getTabbableCandidates(e.currentTarget);
|
|
@@ -687,63 +614,78 @@ class NavigationMenuContentState {
|
|
|
687
614
|
// If we can't focus that means we're at the edges
|
|
688
615
|
// so focus the proxy and let browser handle
|
|
689
616
|
// tab/shift+tab keypress on the proxy instead
|
|
690
|
-
this.
|
|
617
|
+
handleProxyFocus(this.itemContext.focusProxyNode);
|
|
691
618
|
return;
|
|
692
619
|
}
|
|
693
620
|
}
|
|
694
|
-
|
|
621
|
+
let activeEl = document.activeElement;
|
|
622
|
+
if (this.itemContext.contentNode) {
|
|
623
|
+
const focusedNode = this.itemContext.contentNode.querySelector("[data-focused]");
|
|
624
|
+
if (focusedNode) {
|
|
625
|
+
activeEl = focusedNode;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (activeEl === this.itemContext.triggerNode)
|
|
629
|
+
return;
|
|
630
|
+
const newSelectedElement = useArrowNavigation(e, activeEl, undefined, {
|
|
695
631
|
itemsArray: candidates,
|
|
696
632
|
attributeName: `[${NAVIGATION_MENU_LINK_ATTR}]`,
|
|
697
633
|
loop: false,
|
|
698
634
|
enableIgnoredElement: true,
|
|
699
635
|
});
|
|
700
636
|
newSelectedElement?.focus();
|
|
701
|
-
}
|
|
637
|
+
};
|
|
638
|
+
onEscapeKeydown = (_) => {
|
|
639
|
+
this.context.onItemDismiss();
|
|
640
|
+
this.itemContext.triggerNode?.focus();
|
|
641
|
+
// prevent the dropdown from reopening after the escape key has been pressed
|
|
642
|
+
this.itemContext.wasEscapeClose = true;
|
|
643
|
+
};
|
|
702
644
|
props = $derived.by(() => ({
|
|
703
|
-
id: this.id.current,
|
|
704
|
-
"aria-labelledby": this.
|
|
705
|
-
"data-motion": this.motionAttribute,
|
|
706
|
-
"data-
|
|
707
|
-
"data-
|
|
708
|
-
[NAVIGATION_MENU_CONTENT_ATTR]: "",
|
|
709
|
-
style: {
|
|
710
|
-
pointerEvents: !this.open && this.menu.isRoot ? "none" : undefined,
|
|
711
|
-
},
|
|
645
|
+
id: this.opts.id.current,
|
|
646
|
+
"aria-labelledby": this.itemContext.triggerId,
|
|
647
|
+
"data-motion": this.motionAttribute ?? undefined,
|
|
648
|
+
"data-orientation": getDataOrientation(this.context.opts.orientation.current),
|
|
649
|
+
"data-state": getDataOpenClosed(this.context.opts.value.current === this.itemContext.opts.value.current),
|
|
712
650
|
onkeydown: this.onkeydown,
|
|
651
|
+
[NAVIGATION_MENU_CONTENT_ATTR]: "",
|
|
713
652
|
}));
|
|
714
653
|
}
|
|
715
654
|
class NavigationMenuViewportState {
|
|
716
|
-
|
|
717
|
-
|
|
655
|
+
opts;
|
|
656
|
+
context;
|
|
657
|
+
open = $derived.by(() => !!this.context.opts.value.current);
|
|
718
658
|
size = $state(null);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
constructor(
|
|
724
|
-
this.
|
|
725
|
-
this.
|
|
726
|
-
this.viewportRef = props.ref;
|
|
659
|
+
contentNode = $state(null);
|
|
660
|
+
viewportWidth = $derived.by(() => (this.size ? `${this.size.width}px` : undefined));
|
|
661
|
+
viewportHeight = $derived.by(() => (this.size ? `${this.size.height}px` : undefined));
|
|
662
|
+
activeContentValue = $derived.by(() => this.context.opts.value.current);
|
|
663
|
+
constructor(opts, context) {
|
|
664
|
+
this.opts = opts;
|
|
665
|
+
this.context = context;
|
|
727
666
|
useRefById({
|
|
728
|
-
|
|
729
|
-
ref: this.viewportRef,
|
|
667
|
+
...opts,
|
|
730
668
|
onRefChange: (node) => {
|
|
731
|
-
this.
|
|
669
|
+
this.context.viewportRef.current = node;
|
|
732
670
|
},
|
|
733
671
|
deps: () => this.open,
|
|
734
672
|
});
|
|
735
|
-
|
|
736
|
-
this.open;
|
|
737
|
-
this.activeContentValue;
|
|
738
|
-
const currentNode = untrack(() => this.viewportRef.current);
|
|
739
|
-
if (!currentNode)
|
|
740
|
-
return;
|
|
673
|
+
watch([() => this.activeContentValue, () => this.open], () => {
|
|
741
674
|
afterTick(() => {
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
675
|
+
const currNode = this.context.viewportRef.current;
|
|
676
|
+
if (!currNode)
|
|
677
|
+
return;
|
|
678
|
+
const el = currNode.querySelector("[data-state=open]")
|
|
679
|
+
?.children?.[0] ?? null;
|
|
680
|
+
this.contentNode = el;
|
|
745
681
|
});
|
|
746
682
|
});
|
|
683
|
+
/**
|
|
684
|
+
* Update viewport size to match the active content node.
|
|
685
|
+
* We prefer offset dimensions over `getBoundingClientRect` as the latter respects CSS transform.
|
|
686
|
+
* For example, if content animates in from `scale(0.5)` the dimensions would be anything
|
|
687
|
+
* from `0.5` to `1` of the intended size.
|
|
688
|
+
*/
|
|
747
689
|
useResizeObserver(() => this.contentNode, () => {
|
|
748
690
|
if (this.contentNode) {
|
|
749
691
|
this.size = {
|
|
@@ -752,80 +694,75 @@ class NavigationMenuViewportState {
|
|
|
752
694
|
};
|
|
753
695
|
}
|
|
754
696
|
});
|
|
755
|
-
this.onpointerenter = this.onpointerenter.bind(this);
|
|
756
|
-
this.onpointerleave = this.onpointerleave.bind(this);
|
|
757
|
-
}
|
|
758
|
-
onpointerenter(_) {
|
|
759
|
-
this.menu.onContentEnter?.();
|
|
760
|
-
}
|
|
761
|
-
onpointerleave(e) {
|
|
762
|
-
if (e.pointerType !== "mouse")
|
|
763
|
-
return;
|
|
764
|
-
this.menu.onContentLeave?.();
|
|
765
697
|
}
|
|
766
698
|
props = $derived.by(() => ({
|
|
767
|
-
id: this.id.current,
|
|
699
|
+
id: this.opts.id.current,
|
|
768
700
|
"data-state": getDataOpenClosed(this.open),
|
|
769
|
-
"data-orientation": getDataOrientation(this.
|
|
701
|
+
"data-orientation": getDataOrientation(this.context.opts.orientation.current),
|
|
770
702
|
style: {
|
|
771
|
-
pointerEvents: !this.open && this.
|
|
772
|
-
"--bits-navigation-menu-viewport-width": this.
|
|
773
|
-
|
|
774
|
-
: undefined,
|
|
775
|
-
"--bits-navigation-menu-viewport-height": this.size
|
|
776
|
-
? `${this.size.height}px`
|
|
777
|
-
: undefined,
|
|
703
|
+
pointerEvents: !this.open && this.context.opts.isRootMenu ? "none" : undefined,
|
|
704
|
+
"--bits-navigation-menu-viewport-width": this.viewportWidth,
|
|
705
|
+
"--bits-navigation-menu-viewport-height": this.viewportHeight,
|
|
778
706
|
},
|
|
779
|
-
onpointerenter: this.
|
|
780
|
-
onpointerleave: this.
|
|
707
|
+
onpointerenter: this.context.onContentEnter,
|
|
708
|
+
onpointerleave: this.context.onContentLeave,
|
|
781
709
|
}));
|
|
782
710
|
}
|
|
711
|
+
const NavigationMenuProviderContext = new Context("NavigationMenu.Root");
|
|
712
|
+
export const NavigationMenuItemContext = new Context("NavigationMenu.Item");
|
|
713
|
+
const NavigationMenuListContext = new Context("NavigationMenu.List");
|
|
714
|
+
const NavigationMenuContentContext = new Context("NavigationMenu.Content");
|
|
783
715
|
export function useNavigationMenuRoot(props) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
onTriggerEnter: rootState.onTriggerEnter,
|
|
792
|
-
onItemSelect: rootState.onItemSelect,
|
|
793
|
-
onItemDismiss: rootState.onItemDismiss,
|
|
794
|
-
onContentEnter: rootState.onContentEnter,
|
|
795
|
-
onContentLeave: rootState.onContentLeave,
|
|
796
|
-
onTriggerLeave: rootState.onTriggerLeave,
|
|
797
|
-
previousValue: rootState.previousValue,
|
|
798
|
-
}, rootState);
|
|
799
|
-
NavigationMenuMenuContext.set(menuState);
|
|
800
|
-
return NavigationMenuRootContext.set(rootState);
|
|
716
|
+
return new NavigationMenuRootState(props);
|
|
717
|
+
}
|
|
718
|
+
export function useNavigationMenuProvider(props) {
|
|
719
|
+
return NavigationMenuProviderContext.set(new NavigationMenuProviderState(props));
|
|
720
|
+
}
|
|
721
|
+
export function useNavigationMenuSub(props) {
|
|
722
|
+
return new NavigationMenuSubState(props, NavigationMenuProviderContext.get());
|
|
801
723
|
}
|
|
802
724
|
export function useNavigationMenuList(props) {
|
|
803
|
-
return NavigationMenuListContext.set(new NavigationMenuListState(props,
|
|
725
|
+
return NavigationMenuListContext.set(new NavigationMenuListState(props, NavigationMenuProviderContext.get()));
|
|
804
726
|
}
|
|
805
727
|
export function useNavigationMenuItem(props) {
|
|
806
|
-
|
|
807
|
-
|
|
728
|
+
return NavigationMenuItemContext.set(new NavigationMenuItemState(props, NavigationMenuListContext.get()));
|
|
729
|
+
}
|
|
730
|
+
export function useNavigationMenuIndicatorImpl(props) {
|
|
731
|
+
return new NavigationMenuIndicatorImplState(props, {
|
|
732
|
+
provider: NavigationMenuProviderContext.get(),
|
|
733
|
+
list: NavigationMenuListContext.get(),
|
|
734
|
+
});
|
|
808
735
|
}
|
|
809
736
|
export function useNavigationMenuTrigger(props) {
|
|
810
|
-
return new NavigationMenuTriggerState(props,
|
|
737
|
+
return new NavigationMenuTriggerState(props, {
|
|
738
|
+
provider: NavigationMenuProviderContext.get(),
|
|
739
|
+
item: NavigationMenuItemContext.get(),
|
|
740
|
+
list: NavigationMenuListContext.get(),
|
|
741
|
+
});
|
|
811
742
|
}
|
|
812
743
|
export function useNavigationMenuContent(props) {
|
|
813
|
-
return NavigationMenuContentContext.set(new NavigationMenuContentState(props,
|
|
744
|
+
return NavigationMenuContentContext.set(new NavigationMenuContentState(props, {
|
|
745
|
+
provider: NavigationMenuProviderContext.get(),
|
|
746
|
+
item: NavigationMenuItemContext.get(),
|
|
747
|
+
list: NavigationMenuListContext.get(),
|
|
748
|
+
}));
|
|
814
749
|
}
|
|
815
|
-
export function
|
|
816
|
-
return new
|
|
750
|
+
export function useNavigationMenuLink(props) {
|
|
751
|
+
return new NavigationMenuLinkState(props, {
|
|
752
|
+
provider: NavigationMenuProviderContext.get(),
|
|
753
|
+
item: NavigationMenuItemContext.get(),
|
|
754
|
+
});
|
|
817
755
|
}
|
|
818
|
-
export function
|
|
819
|
-
return new
|
|
756
|
+
export function useNavigationMenuContentImpl(props, itemState) {
|
|
757
|
+
return new NavigationMenuContentImplState(props, itemState ?? NavigationMenuItemContext.get());
|
|
820
758
|
}
|
|
821
|
-
export function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
return new NavigationMenuLinkState(props, NavigationMenuItemContext.get());
|
|
759
|
+
export function useNavigationMenuViewport(props) {
|
|
760
|
+
return new NavigationMenuViewportState(props, NavigationMenuProviderContext.get());
|
|
761
|
+
}
|
|
762
|
+
export function useNavigationMenuIndicator() {
|
|
763
|
+
return new NavigationMenuIndicatorState(NavigationMenuProviderContext.get());
|
|
827
764
|
}
|
|
828
|
-
|
|
765
|
+
//
|
|
829
766
|
function focusFirst(candidates) {
|
|
830
767
|
const previouslyFocusedElement = document.activeElement;
|
|
831
768
|
return candidates.some((candidate) => {
|
|
@@ -848,20 +785,34 @@ function removeFromTabOrder(candidates) {
|
|
|
848
785
|
});
|
|
849
786
|
};
|
|
850
787
|
}
|
|
851
|
-
function
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
788
|
+
function whenMouse(handler) {
|
|
789
|
+
return (e) => (e.pointerType === "mouse" ? handler(e) : undefined);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
*
|
|
793
|
+
* We apply the `aria-hidden` attribute to elements that should not be visible to screen readers
|
|
794
|
+
* under specific circumstances, mostly when in a "modal" context or when they are strictly for
|
|
795
|
+
* utility purposes, like the focus guards.
|
|
796
|
+
*
|
|
797
|
+
* When these elements receive focus before we can remove the aria-hidden attribute, we need to
|
|
798
|
+
* handle the focus in a way that does not cause an error to be logged.
|
|
799
|
+
*
|
|
800
|
+
* This function handles the focus of the guard element first by momentary removing the
|
|
801
|
+
* `aria-hidden` attribute, focusing the guard (which will cause something else to focus), and then
|
|
802
|
+
* restoring the attribute.
|
|
803
|
+
*/
|
|
804
|
+
function handleProxyFocus(guard, focusOptions) {
|
|
805
|
+
if (!guard)
|
|
806
|
+
return;
|
|
807
|
+
const ariaHidden = guard.getAttribute("aria-hidden");
|
|
808
|
+
guard.removeAttribute("aria-hidden");
|
|
809
|
+
guard.focus(focusOptions);
|
|
810
|
+
afterSleep(0, () => {
|
|
811
|
+
if (ariaHidden === null) {
|
|
812
|
+
guard.setAttribute("aria-hidden", "");
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
guard.setAttribute("aria-hidden", ariaHidden);
|
|
865
816
|
}
|
|
866
817
|
});
|
|
867
818
|
}
|