@fluixi-ui/navigation-menu 0.2.0-alpha.18 → 0.2.0-alpha.20
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/index.js +2 -11
- package/dist/navigation-menu.js +543 -407
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
package/dist/navigation-menu.js
CHANGED
|
@@ -1,428 +1,564 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createComponent, createNativeElement, delegateEvents, handleRef, holeContent, holeEnd, holeScope, insert, mergeProps, splitProps, spread, templateNode } from "@fluixi/dom";
|
|
2
|
+
import { batch, createEffect, createMemo, createRenderEffect, createSignal, onCleanup } from "@fluixi/reactive/signal";
|
|
3
3
|
import { fxProps } from "@fluixi-ui/fx";
|
|
4
4
|
import { Show, useRef } from "@fluixi/core";
|
|
5
|
-
import {
|
|
6
|
-
import { createCollection,
|
|
7
|
-
|
|
5
|
+
import { composeEventHandlers, composeId, createControllableSignal, createId, createSafeContext, dataAttr, focusWithoutScrolling } from "@fluixi-ui/kit";
|
|
6
|
+
import { Portal, createCollection, createDismissableLayer, createFloatingPosition, createResizeObserver } from "@fluixi-ui/primitives";
|
|
7
|
+
//#region src/navigation-menu.tsx
|
|
8
|
+
var _fxTmpl$0 = "<li data-navigation-menu-item=\"\"><!--fx--><!--fx/--></li>";
|
|
8
9
|
delegateEvents(["click", "keydown"]);
|
|
9
|
-
|
|
10
|
+
var [NavigationMenuProvider, useNavigationMenuContext] = createSafeContext("NavigationMenu");
|
|
11
|
+
/**
|
|
12
|
+
* Root of a navigation menu — a row (or column) of links, some of which reveal a
|
|
13
|
+
* rich panel of further links on hover or activation. Only one panel is open at a
|
|
14
|
+
* time; hovering between triggers switches instantly, and the panels are plain
|
|
15
|
+
* navigation regions (links), not action menus.
|
|
16
|
+
*
|
|
17
|
+
* Add a {@link NavigationMenuViewport} to route every panel into one shared
|
|
18
|
+
* surface that morphs between items; omit it and each panel floats under its own
|
|
19
|
+
* trigger.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import {
|
|
24
|
+
* NavigationMenu, NavigationMenuList, NavigationMenuItem,
|
|
25
|
+
* NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink,
|
|
26
|
+
* NavigationMenuViewport,
|
|
27
|
+
* } from '@fluixi-ui/navigation-menu';
|
|
28
|
+
*
|
|
29
|
+
* <NavigationMenu aria-label="Main">
|
|
30
|
+
* <NavigationMenuList>
|
|
31
|
+
* <NavigationMenuItem value="products">
|
|
32
|
+
* <NavigationMenuTrigger>Products</NavigationMenuTrigger>
|
|
33
|
+
* <NavigationMenuContent>
|
|
34
|
+
* <NavigationMenuLink href="/analytics">Analytics</NavigationMenuLink>
|
|
35
|
+
* <NavigationMenuLink href="/automation">Automation</NavigationMenuLink>
|
|
36
|
+
* </NavigationMenuContent>
|
|
37
|
+
* </NavigationMenuItem>
|
|
38
|
+
* <NavigationMenuItem value="pricing">
|
|
39
|
+
* <NavigationMenuLink href="/pricing">Pricing</NavigationMenuLink>
|
|
40
|
+
* </NavigationMenuItem>
|
|
41
|
+
* </NavigationMenuList>
|
|
42
|
+
* <NavigationMenuViewport />
|
|
43
|
+
* </NavigationMenu>
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
10
46
|
function NavigationMenu(props) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
47
|
+
const [local, others] = splitProps(props, [
|
|
48
|
+
"value",
|
|
49
|
+
"defaultValue",
|
|
50
|
+
"onValueChange",
|
|
51
|
+
"orientation",
|
|
52
|
+
"delayDuration",
|
|
53
|
+
"closeDelay",
|
|
54
|
+
"children",
|
|
55
|
+
"fx",
|
|
56
|
+
"style"
|
|
57
|
+
]);
|
|
58
|
+
const [value, setValue] = createControllableSignal({
|
|
59
|
+
value: () => local.value,
|
|
60
|
+
defaultValue: () => local.defaultValue,
|
|
61
|
+
onChange: (next) => local.onValueChange?.(next)
|
|
62
|
+
});
|
|
63
|
+
const orientation = local.orientation ?? "horizontal";
|
|
64
|
+
const baseId = createId("navigation-menu");
|
|
65
|
+
const triggers = createCollection();
|
|
66
|
+
const [listRef, setListRef] = createSignal(void 0);
|
|
67
|
+
const [viewportNode, setViewportNode] = createSignal(void 0);
|
|
68
|
+
const [viewportSize, setViewportSize] = createSignal(void 0);
|
|
69
|
+
let openTimer;
|
|
70
|
+
let closeTimer;
|
|
71
|
+
const clearOpen = () => openTimer && clearTimeout(openTimer);
|
|
72
|
+
const clearClose = () => closeTimer && clearTimeout(closeTimer);
|
|
73
|
+
onCleanup(() => {
|
|
74
|
+
batch(() => {
|
|
75
|
+
clearOpen();
|
|
76
|
+
clearClose();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
const open = (next) => {
|
|
80
|
+
batch(() => {
|
|
81
|
+
clearOpen();
|
|
82
|
+
clearClose();
|
|
83
|
+
setToggle(true);
|
|
84
|
+
setValue(next);
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
const close = () => {
|
|
88
|
+
batch(() => {
|
|
89
|
+
clearOpen();
|
|
90
|
+
clearClose();
|
|
91
|
+
setToggle(false);
|
|
92
|
+
setValue(void 0);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const adjacent = (from, dir) => {
|
|
96
|
+
const items = triggers.items();
|
|
97
|
+
const index = items.findIndex((item) => item.value === from);
|
|
98
|
+
if (index === -1) return void 0;
|
|
99
|
+
return items[(index + (dir === "next" ? 1 : -1) + items.length) % items.length];
|
|
100
|
+
};
|
|
101
|
+
const [onToggle, setToggle] = createSignal(false);
|
|
102
|
+
const context = {
|
|
103
|
+
value,
|
|
104
|
+
orientation,
|
|
105
|
+
baseId,
|
|
106
|
+
triggers,
|
|
107
|
+
onToggle,
|
|
108
|
+
activeTrigger: () => {
|
|
109
|
+
const active = value();
|
|
110
|
+
if (active === void 0) return void 0;
|
|
111
|
+
return triggers.items().find((item) => item.value === active)?.ref;
|
|
112
|
+
},
|
|
113
|
+
listRef,
|
|
114
|
+
setListRef,
|
|
115
|
+
viewportNode,
|
|
116
|
+
setViewportNode,
|
|
117
|
+
viewportSize,
|
|
118
|
+
setViewportSize,
|
|
119
|
+
moveTrigger: (from, dir) => {
|
|
120
|
+
const next = adjacent(from, dir);
|
|
121
|
+
if (!next) return;
|
|
122
|
+
focusWithoutScrolling(next.ref);
|
|
123
|
+
if (value() !== void 0 && next.value !== void 0) open(next.value);
|
|
124
|
+
},
|
|
125
|
+
requestOpen: (next) => {
|
|
126
|
+
clearClose();
|
|
127
|
+
if (value() !== void 0) {
|
|
128
|
+
open(next);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
clearOpen();
|
|
132
|
+
openTimer = setTimeout(() => open(next), local.delayDuration ?? 200);
|
|
133
|
+
},
|
|
134
|
+
open,
|
|
135
|
+
requestClose: () => {
|
|
136
|
+
clearOpen();
|
|
137
|
+
clearClose();
|
|
138
|
+
setToggle(false);
|
|
139
|
+
closeTimer = setTimeout(() => setValue(void 0), local.closeDelay ?? 300);
|
|
140
|
+
},
|
|
141
|
+
cancelClose: () => clearClose(),
|
|
142
|
+
close,
|
|
143
|
+
toggle: (next) => value() === next ? close() : open(next)
|
|
144
|
+
};
|
|
145
|
+
return createMemo(() => createComponent(NavigationMenuProvider, {
|
|
146
|
+
get value() {
|
|
147
|
+
return context;
|
|
148
|
+
},
|
|
149
|
+
get children() {
|
|
150
|
+
return (() => {
|
|
151
|
+
const _el$ = createNativeElement("nav");
|
|
152
|
+
spread({
|
|
153
|
+
element: _el$,
|
|
154
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
155
|
+
"data-orientation": orientation,
|
|
156
|
+
onPointerLeave: (event) => {
|
|
157
|
+
if (event.pointerType !== "touch") context.requestClose();
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
});
|
|
161
|
+
insert(_el$, local.children, null);
|
|
162
|
+
return _el$;
|
|
163
|
+
})();
|
|
164
|
+
}
|
|
165
|
+
}));
|
|
125
166
|
}
|
|
167
|
+
/** The list wrapping the menu's items. */
|
|
126
168
|
function NavigationMenuList(props) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
const ctx = useNavigationMenuContext();
|
|
170
|
+
const [local, others] = splitProps(props, [
|
|
171
|
+
"ref",
|
|
172
|
+
"children",
|
|
173
|
+
"fx",
|
|
174
|
+
"style"
|
|
175
|
+
]);
|
|
176
|
+
const setRef = (node) => {
|
|
177
|
+
ctx.setListRef(node);
|
|
178
|
+
handleRef(local.ref, node);
|
|
179
|
+
};
|
|
180
|
+
return (() => {
|
|
181
|
+
const _el$ = createNativeElement("ul");
|
|
182
|
+
spread({
|
|
183
|
+
element: _el$,
|
|
184
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
185
|
+
ref: setRef,
|
|
186
|
+
"data-navigation-menu-list": "",
|
|
187
|
+
"data-orientation": () => ctx.orientation
|
|
188
|
+
})
|
|
189
|
+
});
|
|
190
|
+
insert(_el$, local.children, null);
|
|
191
|
+
return _el$;
|
|
192
|
+
})();
|
|
142
193
|
}
|
|
143
|
-
|
|
194
|
+
var [NavigationMenuItemProvider, useNavigationMenuItemContext] = createSafeContext("NavigationMenuItem");
|
|
195
|
+
/** One item — a trigger + its content, or a bare {@link NavigationMenuLink}. */
|
|
144
196
|
function NavigationMenuItem(props) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
const menu = useNavigationMenuContext();
|
|
198
|
+
const fallback = createId("navigation-menu-item");
|
|
199
|
+
const value = () => props.value ?? fallback;
|
|
200
|
+
const [triggerRef, setTriggerRef] = createSignal(void 0);
|
|
201
|
+
const [contentRef, setContentRef] = createSignal(void 0);
|
|
202
|
+
const context = {
|
|
203
|
+
value: value(),
|
|
204
|
+
isOpen: () => menu.value() !== void 0 && menu.value() === value(),
|
|
205
|
+
ids: {
|
|
206
|
+
trigger: composeId(menu.baseId, `trigger-${value()}`),
|
|
207
|
+
content: composeId(menu.baseId, `content-${value()}`)
|
|
208
|
+
},
|
|
209
|
+
triggerRef,
|
|
210
|
+
setTriggerRef,
|
|
211
|
+
contentRef,
|
|
212
|
+
setContentRef,
|
|
213
|
+
focusContent: () => {
|
|
214
|
+
const content = contentRef();
|
|
215
|
+
if (!content) return;
|
|
216
|
+
const focusable = content.querySelector("a[href],button:not([disabled]),[tabindex]:not([tabindex=\"-1\"])");
|
|
217
|
+
focusWithoutScrolling(focusable ?? content);
|
|
218
|
+
},
|
|
219
|
+
focusTrigger: () => focusWithoutScrolling(triggerRef())
|
|
220
|
+
};
|
|
221
|
+
return (() => {
|
|
222
|
+
const _el$ = templateNode(_fxTmpl$0, "li", true);
|
|
223
|
+
const _n$0 = _el$.firstChild;
|
|
224
|
+
const _n$1 = holeEnd(_n$0);
|
|
225
|
+
insert(_el$, holeScope(_n$0, () => createMemo(() => createComponent(NavigationMenuItemProvider, {
|
|
226
|
+
get value() {
|
|
227
|
+
return context;
|
|
228
|
+
},
|
|
229
|
+
get children() {
|
|
230
|
+
return props.children;
|
|
231
|
+
}
|
|
232
|
+
}))), _n$1, holeContent(_n$0, _n$1));
|
|
233
|
+
return _el$;
|
|
234
|
+
})();
|
|
183
235
|
}
|
|
236
|
+
/** The button that reveals its item's {@link NavigationMenuContent}. */
|
|
184
237
|
function NavigationMenuTrigger(props) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
const menu = useNavigationMenuContext();
|
|
239
|
+
const item = useNavigationMenuItemContext();
|
|
240
|
+
const [local, others] = splitProps(props, [
|
|
241
|
+
"onClick",
|
|
242
|
+
"onKeyDown",
|
|
243
|
+
"ref",
|
|
244
|
+
"children",
|
|
245
|
+
"fx",
|
|
246
|
+
"style"
|
|
247
|
+
]);
|
|
248
|
+
const setRef = (node) => {
|
|
249
|
+
item.setTriggerRef(node);
|
|
250
|
+
const remove = menu.triggers.register({
|
|
251
|
+
ref: node,
|
|
252
|
+
value: item.value
|
|
253
|
+
});
|
|
254
|
+
onCleanup(remove);
|
|
255
|
+
const onEnter = (event) => {
|
|
256
|
+
if (event.pointerType !== "touch") menu.requestOpen(item.value);
|
|
257
|
+
};
|
|
258
|
+
node.addEventListener("pointerenter", onEnter);
|
|
259
|
+
onCleanup(() => node.removeEventListener("pointerenter", onEnter));
|
|
260
|
+
handleRef(local.ref, node);
|
|
261
|
+
};
|
|
262
|
+
const forward = menu.orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
|
|
263
|
+
const backward = menu.orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
|
|
264
|
+
const openKey = menu.orientation === "horizontal" ? "ArrowDown" : "ArrowRight";
|
|
265
|
+
return (() => {
|
|
266
|
+
const _el$ = createNativeElement("button");
|
|
267
|
+
spread({
|
|
268
|
+
element: _el$,
|
|
269
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
270
|
+
ref: setRef,
|
|
271
|
+
type: "button",
|
|
272
|
+
id: () => item.ids.trigger,
|
|
273
|
+
"data-navigation-menu-trigger": "",
|
|
274
|
+
"aria-expanded": () => item.isOpen() ? "true" : "false",
|
|
275
|
+
"aria-controls": () => item.isOpen() ? item.ids.content : void 0,
|
|
276
|
+
"data-state": () => item.isOpen() ? "open" : "closed",
|
|
277
|
+
onClick: composeEventHandlers([local.onClick, () => menu.toggle(item.value)]),
|
|
278
|
+
onKeyDown: composeEventHandlers([local.onKeyDown, (event) => {
|
|
279
|
+
switch (event.key) {
|
|
280
|
+
case forward:
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
menu.moveTrigger(item.value, "next");
|
|
283
|
+
break;
|
|
284
|
+
case backward:
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
menu.moveTrigger(item.value, "previous");
|
|
287
|
+
break;
|
|
288
|
+
case openKey:
|
|
289
|
+
case "Enter":
|
|
290
|
+
case " ":
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
menu.open(item.value);
|
|
293
|
+
queueMicrotask(() => item.focusContent());
|
|
294
|
+
}
|
|
295
|
+
}])
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
insert(_el$, local.children, null);
|
|
299
|
+
return _el$;
|
|
300
|
+
})();
|
|
235
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* The panel of links revealed by a {@link NavigationMenuTrigger}. Renders into a
|
|
304
|
+
* {@link NavigationMenuViewport} when one exists, else floats under its trigger.
|
|
305
|
+
*/
|
|
236
306
|
function NavigationMenuContent(props) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
307
|
+
const menu = useNavigationMenuContext();
|
|
308
|
+
const item = useNavigationMenuItemContext();
|
|
309
|
+
return createMemo(() => createComponent(Show, {
|
|
310
|
+
get when() {
|
|
311
|
+
return menu.viewportNode();
|
|
312
|
+
},
|
|
313
|
+
get fallback() {
|
|
314
|
+
return createMemo(() => createComponent(Portal, { get children() {
|
|
315
|
+
return createMemo(() => createComponent(Show, {
|
|
316
|
+
get when() {
|
|
317
|
+
return item.isOpen();
|
|
318
|
+
},
|
|
319
|
+
get children() {
|
|
320
|
+
return createMemo(() => createComponent(NavigationMenuContentImpl, mergeProps(props, { get floating() {
|
|
321
|
+
return true;
|
|
322
|
+
} })));
|
|
323
|
+
}
|
|
324
|
+
}));
|
|
325
|
+
} }));
|
|
326
|
+
},
|
|
327
|
+
get children() {
|
|
328
|
+
return createMemo(() => createComponent(Portal, {
|
|
329
|
+
get mount() {
|
|
330
|
+
return menu.viewportNode();
|
|
331
|
+
},
|
|
332
|
+
get children() {
|
|
333
|
+
return createMemo(() => createComponent(Show, {
|
|
334
|
+
get when() {
|
|
335
|
+
return item.isOpen();
|
|
336
|
+
},
|
|
337
|
+
get children() {
|
|
338
|
+
return createMemo(() => createComponent(NavigationMenuContentImpl, mergeProps(props, {})));
|
|
339
|
+
}
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
}));
|
|
262
345
|
}
|
|
263
346
|
function NavigationMenuContentImpl(props) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
347
|
+
const menu = useNavigationMenuContext();
|
|
348
|
+
const item = useNavigationMenuItemContext();
|
|
349
|
+
const [local, others] = splitProps(props, [
|
|
350
|
+
"ref",
|
|
351
|
+
"children",
|
|
352
|
+
"style",
|
|
353
|
+
"floating",
|
|
354
|
+
"fx",
|
|
355
|
+
"style"
|
|
356
|
+
]);
|
|
357
|
+
const [element, setElement] = createSignal(void 0);
|
|
358
|
+
if (local.floating) {
|
|
359
|
+
const position = createFloatingPosition(item.triggerRef, element, {
|
|
360
|
+
placement: menu.orientation === "horizontal" ? "bottom-start" : "right-start",
|
|
361
|
+
offset: 8
|
|
362
|
+
});
|
|
363
|
+
createDismissableLayer(element, {
|
|
364
|
+
shouldExcludeTarget: (target) => {
|
|
365
|
+
const trigger = item.triggerRef();
|
|
366
|
+
return !!trigger && (trigger === target || trigger.contains(target));
|
|
367
|
+
},
|
|
368
|
+
onDismiss: () => menu.close()
|
|
369
|
+
});
|
|
370
|
+
createRenderEffect(() => {
|
|
371
|
+
const el = element();
|
|
372
|
+
if (!el) return;
|
|
373
|
+
el.style.position = "absolute";
|
|
374
|
+
el.style.left = `${Math.round(position.x())}px`;
|
|
375
|
+
el.style.top = `${Math.round(position.y())}px`;
|
|
376
|
+
el.style.visibility = position.isPositioned() ? "visible" : "hidden";
|
|
377
|
+
});
|
|
378
|
+
} else createResizeObserver(element, (_entry, el) => {
|
|
379
|
+
const box = el;
|
|
380
|
+
menu.setViewportSize({
|
|
381
|
+
width: box.offsetWidth,
|
|
382
|
+
height: box.offsetHeight
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
const setRef = (node) => {
|
|
386
|
+
setElement(node);
|
|
387
|
+
item.setContentRef(node);
|
|
388
|
+
handleRef(local.ref, node);
|
|
389
|
+
};
|
|
390
|
+
return (() => {
|
|
391
|
+
const _el$ = createNativeElement("div");
|
|
392
|
+
spread({
|
|
393
|
+
element: _el$,
|
|
394
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
395
|
+
ref: setRef,
|
|
396
|
+
id: () => item.ids.content,
|
|
397
|
+
"data-navigation-menu-content": "",
|
|
398
|
+
"data-floating": () => dataAttr(Boolean(local.floating)),
|
|
399
|
+
"aria-labelledby": () => item.ids.trigger,
|
|
400
|
+
"data-state": "open",
|
|
401
|
+
onPointerEnter: () => menu.cancelClose(),
|
|
402
|
+
onKeyDown: (event) => {
|
|
403
|
+
if (event.key === "Escape") {
|
|
404
|
+
event.preventDefault();
|
|
405
|
+
menu.close();
|
|
406
|
+
item.focusTrigger();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
});
|
|
411
|
+
insert(_el$, local.children, null);
|
|
412
|
+
return _el$;
|
|
413
|
+
})();
|
|
314
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* The single shared surface that every {@link NavigationMenuContent} renders
|
|
417
|
+
* into. It anchors below the active trigger and animates its size between panels.
|
|
418
|
+
* Optional — without it, each panel floats under its own trigger.
|
|
419
|
+
*/
|
|
315
420
|
function NavigationMenuViewport(props) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
421
|
+
const menu = useNavigationMenuContext();
|
|
422
|
+
const [local, others] = splitProps(props, [
|
|
423
|
+
"ref",
|
|
424
|
+
"style",
|
|
425
|
+
"fx",
|
|
426
|
+
"style"
|
|
427
|
+
]);
|
|
428
|
+
const [element, setElement] = createSignal(void 0);
|
|
429
|
+
const position = createFloatingPosition(menu.activeTrigger, element, {
|
|
430
|
+
placement: menu.orientation === "horizontal" ? "bottom-start" : "right-start",
|
|
431
|
+
offset: 8
|
|
432
|
+
});
|
|
433
|
+
createDismissableLayer(element, {
|
|
434
|
+
shouldExcludeTarget: (target) => target instanceof Element && !!target.closest("[data-navigation-menu-trigger]"),
|
|
435
|
+
onDismiss: () => menu.close()
|
|
436
|
+
});
|
|
437
|
+
createRenderEffect(() => {
|
|
438
|
+
const el = element();
|
|
439
|
+
if (!el) return;
|
|
440
|
+
el.style.position = "absolute";
|
|
441
|
+
el.style.left = `${Math.round(position.x())}px`;
|
|
442
|
+
el.style.top = `${Math.round(position.y())}px`;
|
|
443
|
+
const size = menu.viewportSize();
|
|
444
|
+
if (size) {
|
|
445
|
+
el.style.setProperty("--flx-ui-navigation-menu-viewport-width", `${size.width}px`);
|
|
446
|
+
el.style.setProperty("--flx-ui-navigation-menu-viewport-height", `${size.height}px`);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
const setRef = (node) => {
|
|
450
|
+
setElement(node);
|
|
451
|
+
menu.setViewportNode(node);
|
|
452
|
+
handleRef(local.ref, node);
|
|
453
|
+
};
|
|
454
|
+
const portalRef = useRef(null);
|
|
455
|
+
onCleanup(() => {
|
|
456
|
+
menu.setViewportNode(void 0);
|
|
457
|
+
});
|
|
458
|
+
createEffect(() => {
|
|
459
|
+
if (!menu.onToggle() && portalRef.current) portalRef.current?.remove();
|
|
460
|
+
});
|
|
461
|
+
return createMemo(() => createComponent(Show, {
|
|
462
|
+
get when() {
|
|
463
|
+
return menu.onToggle();
|
|
464
|
+
},
|
|
465
|
+
get children() {
|
|
466
|
+
return createMemo(() => createComponent(Portal, {
|
|
467
|
+
get ref() {
|
|
468
|
+
return portalRef;
|
|
469
|
+
},
|
|
470
|
+
get children() {
|
|
471
|
+
return (() => {
|
|
472
|
+
const _el$ = createNativeElement("div");
|
|
473
|
+
spread({
|
|
474
|
+
element: _el$,
|
|
475
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
476
|
+
ref: setRef,
|
|
477
|
+
"data-navigation-menu-viewport": "",
|
|
478
|
+
"data-state": () => menu.value() !== void 0 ? "open" : "closed",
|
|
479
|
+
onPointerEnter: () => menu.cancelClose()
|
|
480
|
+
})
|
|
481
|
+
});
|
|
482
|
+
return _el$;
|
|
483
|
+
})();
|
|
484
|
+
}
|
|
485
|
+
}));
|
|
486
|
+
}
|
|
487
|
+
}));
|
|
370
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* A marker (typically an arrow) that tracks the active trigger — place it inside
|
|
491
|
+
* a {@link NavigationMenuList}, positioned relative to the list.
|
|
492
|
+
*/
|
|
371
493
|
function NavigationMenuIndicator(props) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
494
|
+
const menu = useNavigationMenuContext();
|
|
495
|
+
const [local, others] = splitProps(props, [
|
|
496
|
+
"ref",
|
|
497
|
+
"children",
|
|
498
|
+
"fx",
|
|
499
|
+
"style"
|
|
500
|
+
]);
|
|
501
|
+
const [element, setElement] = createSignal(void 0);
|
|
502
|
+
createRenderEffect(() => {
|
|
503
|
+
const el = element();
|
|
504
|
+
const trigger = menu.activeTrigger();
|
|
505
|
+
const list = menu.listRef();
|
|
506
|
+
if (!el || !trigger || !list) return;
|
|
507
|
+
const t = trigger.getBoundingClientRect();
|
|
508
|
+
const l = list.getBoundingClientRect();
|
|
509
|
+
el.style.setProperty("--flx-ui-navigation-menu-indicator-x", `${Math.round(t.left - l.left)}px`);
|
|
510
|
+
el.style.setProperty("--flx-ui-navigation-menu-indicator-size", `${Math.round(t.width)}px`);
|
|
511
|
+
});
|
|
512
|
+
const setRef = (node) => {
|
|
513
|
+
setElement(node);
|
|
514
|
+
handleRef(local.ref, node);
|
|
515
|
+
};
|
|
516
|
+
return createMemo(() => createComponent(Show, {
|
|
517
|
+
get when() {
|
|
518
|
+
return () => menu.value() !== void 0;
|
|
519
|
+
},
|
|
520
|
+
get children() {
|
|
521
|
+
return (() => {
|
|
522
|
+
const _el$ = createNativeElement("li");
|
|
523
|
+
spread({
|
|
524
|
+
element: _el$,
|
|
525
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
526
|
+
ref: setRef,
|
|
527
|
+
"data-navigation-menu-indicator": "",
|
|
528
|
+
"aria-hidden": "true",
|
|
529
|
+
"data-orientation": () => menu.orientation
|
|
530
|
+
})
|
|
531
|
+
});
|
|
532
|
+
insert(_el$, local.children, null);
|
|
533
|
+
return _el$;
|
|
534
|
+
})();
|
|
535
|
+
}
|
|
536
|
+
}));
|
|
402
537
|
}
|
|
538
|
+
/** A navigation link. Selecting it closes any open panel. */
|
|
403
539
|
function NavigationMenuLink(props) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
540
|
+
const menu = useNavigationMenuContext();
|
|
541
|
+
const [local, others] = splitProps(props, [
|
|
542
|
+
"active",
|
|
543
|
+
"onClick",
|
|
544
|
+
"children",
|
|
545
|
+
"fx",
|
|
546
|
+
"style"
|
|
547
|
+
]);
|
|
548
|
+
return (() => {
|
|
549
|
+
const _el$ = createNativeElement("a");
|
|
550
|
+
spread({
|
|
551
|
+
element: _el$,
|
|
552
|
+
props: mergeProps(fxProps(() => local.fx, () => local.style), others, {
|
|
553
|
+
"data-navigation-menu-link": "",
|
|
554
|
+
"data-active": () => dataAttr(Boolean(local.active)),
|
|
555
|
+
"aria-current": () => local.active ? "page" : void 0,
|
|
556
|
+
onClick: composeEventHandlers([local.onClick, () => menu.close()])
|
|
557
|
+
})
|
|
558
|
+
});
|
|
559
|
+
insert(_el$, local.children, null);
|
|
560
|
+
return _el$;
|
|
561
|
+
})();
|
|
418
562
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
NavigationMenuContent,
|
|
422
|
-
NavigationMenuIndicator,
|
|
423
|
-
NavigationMenuItem,
|
|
424
|
-
NavigationMenuLink,
|
|
425
|
-
NavigationMenuList,
|
|
426
|
-
NavigationMenuTrigger,
|
|
427
|
-
NavigationMenuViewport
|
|
428
|
-
};
|
|
563
|
+
//#endregion
|
|
564
|
+
export { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport };
|