@zag-js/menubar 2.0.0-next.1
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/LICENSE +21 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +39 -0
- package/dist/index.mjs +10 -0
- package/dist/menubar.anatomy.d.mts +6 -0
- package/dist/menubar.anatomy.d.ts +6 -0
- package/dist/menubar.anatomy.js +34 -0
- package/dist/menubar.anatomy.mjs +8 -0
- package/dist/menubar.connect.d.mts +8 -0
- package/dist/menubar.connect.d.ts +8 -0
- package/dist/menubar.connect.js +107 -0
- package/dist/menubar.connect.mjs +72 -0
- package/dist/menubar.dom.d.mts +18 -0
- package/dist/menubar.dom.d.ts +18 -0
- package/dist/menubar.dom.js +78 -0
- package/dist/menubar.dom.mjs +44 -0
- package/dist/menubar.machine.d.mts +8 -0
- package/dist/menubar.machine.d.ts +8 -0
- package/dist/menubar.machine.js +253 -0
- package/dist/menubar.machine.mjs +218 -0
- package/dist/menubar.props.d.mts +9 -0
- package/dist/menubar.props.d.ts +9 -0
- package/dist/menubar.props.js +43 -0
- package/dist/menubar.props.mjs +17 -0
- package/dist/menubar.types.d.mts +106 -0
- package/dist/menubar.types.d.ts +106 -0
- package/dist/menubar.types.js +18 -0
- package/dist/menubar.types.mjs +0 -0
- package/package.json +73 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/menubar.machine.ts
|
|
31
|
+
var menubar_machine_exports = {};
|
|
32
|
+
__export(menubar_machine_exports, {
|
|
33
|
+
machine: () => machine
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(menubar_machine_exports);
|
|
36
|
+
var import_core = require("@zag-js/core");
|
|
37
|
+
var import_dom_query = require("@zag-js/dom-query");
|
|
38
|
+
var dom = __toESM(require("./menubar.dom.js"));
|
|
39
|
+
var machine = (0, import_core.createMachine)({
|
|
40
|
+
props({ props }) {
|
|
41
|
+
return {
|
|
42
|
+
orientation: "horizontal",
|
|
43
|
+
loopFocus: true,
|
|
44
|
+
...props
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
initialState() {
|
|
48
|
+
return "idle";
|
|
49
|
+
},
|
|
50
|
+
context({ bindable }) {
|
|
51
|
+
return {
|
|
52
|
+
activeId: bindable(() => ({ defaultValue: null })),
|
|
53
|
+
hasOpenMenu: bindable(() => ({ defaultValue: false }))
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
refs() {
|
|
57
|
+
return {
|
|
58
|
+
typeaheadState: { ...import_dom_query.getByTypeahead.defaultOptions }
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
// Always-on: keep the active trigger valid as triggers mount/change, and listen for the
|
|
62
|
+
// menu coordination events dispatched on the root element.
|
|
63
|
+
effects: ["trackTriggers", "trackMenuEvents"],
|
|
64
|
+
on: {
|
|
65
|
+
"TRIGGER.FOCUS": {
|
|
66
|
+
actions: ["setActiveId"]
|
|
67
|
+
},
|
|
68
|
+
TYPEAHEAD: {
|
|
69
|
+
actions: ["focusMatchingTrigger"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
states: {
|
|
73
|
+
idle: {
|
|
74
|
+
on: {
|
|
75
|
+
"TRIGGER.FOCUS_IN": {
|
|
76
|
+
target: "focused",
|
|
77
|
+
actions: ["setActiveId"]
|
|
78
|
+
},
|
|
79
|
+
"MENU.OPEN": {
|
|
80
|
+
target: "open",
|
|
81
|
+
actions: ["setHasOpenMenu"]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
focused: {
|
|
86
|
+
on: {
|
|
87
|
+
"ROOT.BLUR": {
|
|
88
|
+
target: "idle"
|
|
89
|
+
},
|
|
90
|
+
"TRIGGER.FOCUS_NEXT": {
|
|
91
|
+
actions: ["focusNextTrigger"]
|
|
92
|
+
},
|
|
93
|
+
"TRIGGER.FOCUS_PREV": {
|
|
94
|
+
actions: ["focusPrevTrigger"]
|
|
95
|
+
},
|
|
96
|
+
"TRIGGER.FOCUS_FIRST": {
|
|
97
|
+
actions: ["focusFirstTrigger"]
|
|
98
|
+
},
|
|
99
|
+
"TRIGGER.FOCUS_LAST": {
|
|
100
|
+
actions: ["focusLastTrigger"]
|
|
101
|
+
},
|
|
102
|
+
"MENU.OPEN": {
|
|
103
|
+
target: "open",
|
|
104
|
+
actions: ["setHasOpenMenu"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
open: {
|
|
109
|
+
on: {
|
|
110
|
+
// Arrow-switch to the sibling menu (move + request open).
|
|
111
|
+
"TRIGGER.FOCUS_NEXT": {
|
|
112
|
+
actions: ["switchToNextMenu"]
|
|
113
|
+
},
|
|
114
|
+
"TRIGGER.FOCUS_PREV": {
|
|
115
|
+
actions: ["switchToPrevMenu"]
|
|
116
|
+
},
|
|
117
|
+
// Another menu opened (sibling switch) — stay open.
|
|
118
|
+
"MENU.OPEN": {
|
|
119
|
+
actions: ["setHasOpenMenu"]
|
|
120
|
+
},
|
|
121
|
+
"MENU.CLOSE": [
|
|
122
|
+
{
|
|
123
|
+
// Transient close: another menu is about to open. Keep `hasOpenMenu` active
|
|
124
|
+
// so it doesn't flicker while switching siblings.
|
|
125
|
+
guard: "isTransientClose"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
target: "focused",
|
|
129
|
+
actions: ["clearHasOpenMenu"]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
// No ROOT.BLUR: focus lives in the open menu's portalled content. The menu drives
|
|
133
|
+
// close transitions via `menu:close`.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
implementations: {
|
|
138
|
+
guards: {
|
|
139
|
+
isTransientClose: ({ event }) => event.reason === "sibling-open" || event.reason === "list-navigation"
|
|
140
|
+
},
|
|
141
|
+
effects: {
|
|
142
|
+
// Reconcile activeId as triggers mount/unmount. Triggers render `role=menuitem`
|
|
143
|
+
// synchronously (the menu is told it's in a menubar via props), so childList is enough.
|
|
144
|
+
trackTriggers({ scope, context }) {
|
|
145
|
+
const rootEl = dom.getRootEl(scope);
|
|
146
|
+
if (!rootEl) return;
|
|
147
|
+
const reconcile = () => reconcileActiveId(context, scope);
|
|
148
|
+
(0, import_dom_query.raf)(reconcile);
|
|
149
|
+
return (0, import_dom_query.observeChildren)(rootEl, { callback: reconcile });
|
|
150
|
+
},
|
|
151
|
+
trackMenuEvents({ scope, send }) {
|
|
152
|
+
const rootEl = dom.getRootEl(scope);
|
|
153
|
+
if (!rootEl) return;
|
|
154
|
+
const onFocusIn = (event) => {
|
|
155
|
+
const target = event.target;
|
|
156
|
+
if (!dom.isTriggerEl(target)) return;
|
|
157
|
+
send({ type: "TRIGGER.FOCUS_IN", id: target.id });
|
|
158
|
+
send({ type: "TRIGGER.FOCUS", id: target.id });
|
|
159
|
+
};
|
|
160
|
+
const onFocusOut = (event) => {
|
|
161
|
+
const related = event.relatedTarget;
|
|
162
|
+
if (related && rootEl.contains(related)) return;
|
|
163
|
+
send({ type: "ROOT.BLUR" });
|
|
164
|
+
};
|
|
165
|
+
const cleanups = [
|
|
166
|
+
(0, import_dom_query.addDomEvent)(rootEl, "menu:open", (event) => {
|
|
167
|
+
send({ type: "MENU.OPEN", id: event.detail?.menuId, triggerId: event.detail?.triggerId });
|
|
168
|
+
}),
|
|
169
|
+
(0, import_dom_query.addDomEvent)(rootEl, "menu:close", (event) => {
|
|
170
|
+
send({ type: "MENU.CLOSE", id: event.detail?.menuId, reason: event.detail?.reason });
|
|
171
|
+
}),
|
|
172
|
+
(0, import_dom_query.addDomEvent)(rootEl, "menu:focus-next", () => send({ type: "TRIGGER.FOCUS_NEXT" })),
|
|
173
|
+
(0, import_dom_query.addDomEvent)(rootEl, "menu:focus-prev", () => send({ type: "TRIGGER.FOCUS_PREV" })),
|
|
174
|
+
(0, import_dom_query.addDomEvent)(rootEl, "focusin", onFocusIn),
|
|
175
|
+
(0, import_dom_query.addDomEvent)(rootEl, "focusout", onFocusOut)
|
|
176
|
+
];
|
|
177
|
+
return () => {
|
|
178
|
+
cleanups.forEach((fn) => fn());
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
actions: {
|
|
183
|
+
setActiveId({ context, event }) {
|
|
184
|
+
if (event.id == null) return;
|
|
185
|
+
context.set("activeId", event.id);
|
|
186
|
+
},
|
|
187
|
+
setHasOpenMenu({ context, event }) {
|
|
188
|
+
context.set("hasOpenMenu", true);
|
|
189
|
+
if (event.triggerId) {
|
|
190
|
+
context.set("activeId", event.triggerId);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
clearHasOpenMenu({ context }) {
|
|
194
|
+
context.set("hasOpenMenu", false);
|
|
195
|
+
},
|
|
196
|
+
focusNextTrigger({ context, scope, prop }) {
|
|
197
|
+
const activeId = context.get("activeId");
|
|
198
|
+
const nextEl = activeId ? dom.getNextTriggerEl(scope, activeId, prop("loopFocus")) : dom.getFirstTriggerEl(scope);
|
|
199
|
+
focusTrigger(context, scope, nextEl);
|
|
200
|
+
},
|
|
201
|
+
focusPrevTrigger({ context, scope, prop }) {
|
|
202
|
+
const activeId = context.get("activeId");
|
|
203
|
+
const prevEl = activeId ? dom.getPrevTriggerEl(scope, activeId, prop("loopFocus")) : dom.getLastTriggerEl(scope);
|
|
204
|
+
focusTrigger(context, scope, prevEl);
|
|
205
|
+
},
|
|
206
|
+
focusMatchingTrigger({ context, scope, event, refs }) {
|
|
207
|
+
const matched = (0, import_dom_query.getByTypeahead)(dom.getTriggerEls(scope), {
|
|
208
|
+
state: refs.get("typeaheadState"),
|
|
209
|
+
key: event.key,
|
|
210
|
+
activeId: context.get("activeId")
|
|
211
|
+
});
|
|
212
|
+
focusTrigger(context, scope, matched);
|
|
213
|
+
},
|
|
214
|
+
focusFirstTrigger({ context, scope }) {
|
|
215
|
+
focusTrigger(context, scope, dom.getFirstTriggerEl(scope));
|
|
216
|
+
},
|
|
217
|
+
focusLastTrigger({ context, scope }) {
|
|
218
|
+
focusTrigger(context, scope, dom.getLastTriggerEl(scope));
|
|
219
|
+
},
|
|
220
|
+
switchToNextMenu({ context, scope, prop }) {
|
|
221
|
+
const activeId = context.get("activeId");
|
|
222
|
+
const nextEl = activeId ? dom.getNextTriggerEl(scope, activeId, prop("loopFocus")) : dom.getFirstTriggerEl(scope);
|
|
223
|
+
if (!nextEl) return;
|
|
224
|
+
setActiveTrigger(context, nextEl.id);
|
|
225
|
+
dom.requestOpenMenu(scope, nextEl.id);
|
|
226
|
+
},
|
|
227
|
+
switchToPrevMenu({ context, scope, prop }) {
|
|
228
|
+
const activeId = context.get("activeId");
|
|
229
|
+
const prevEl = activeId ? dom.getPrevTriggerEl(scope, activeId, prop("loopFocus")) : dom.getLastTriggerEl(scope);
|
|
230
|
+
if (!prevEl) return;
|
|
231
|
+
setActiveTrigger(context, prevEl.id);
|
|
232
|
+
dom.requestOpenMenu(scope, prevEl.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
function reconcileActiveId(context, scope) {
|
|
238
|
+
const activeId = context.get("activeId");
|
|
239
|
+
const nextActiveId = dom.getValidActiveId(scope, activeId);
|
|
240
|
+
if (nextActiveId !== activeId) context.set("activeId", nextActiveId);
|
|
241
|
+
}
|
|
242
|
+
function setActiveTrigger(context, id) {
|
|
243
|
+
context.set("activeId", id);
|
|
244
|
+
}
|
|
245
|
+
function focusTrigger(context, scope, el) {
|
|
246
|
+
if (!el) return;
|
|
247
|
+
setActiveTrigger(context, el.id);
|
|
248
|
+
(0, import_dom_query.raf)(() => el.focus());
|
|
249
|
+
}
|
|
250
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
251
|
+
0 && (module.exports = {
|
|
252
|
+
machine
|
|
253
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/menubar.machine.ts
|
|
2
|
+
import { createMachine } from "@zag-js/core";
|
|
3
|
+
import { addDomEvent, getByTypeahead, observeChildren, raf } from "@zag-js/dom-query";
|
|
4
|
+
import * as dom from "./menubar.dom.mjs";
|
|
5
|
+
var machine = createMachine({
|
|
6
|
+
props({ props }) {
|
|
7
|
+
return {
|
|
8
|
+
orientation: "horizontal",
|
|
9
|
+
loopFocus: true,
|
|
10
|
+
...props
|
|
11
|
+
};
|
|
12
|
+
},
|
|
13
|
+
initialState() {
|
|
14
|
+
return "idle";
|
|
15
|
+
},
|
|
16
|
+
context({ bindable }) {
|
|
17
|
+
return {
|
|
18
|
+
activeId: bindable(() => ({ defaultValue: null })),
|
|
19
|
+
hasOpenMenu: bindable(() => ({ defaultValue: false }))
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
refs() {
|
|
23
|
+
return {
|
|
24
|
+
typeaheadState: { ...getByTypeahead.defaultOptions }
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
// Always-on: keep the active trigger valid as triggers mount/change, and listen for the
|
|
28
|
+
// menu coordination events dispatched on the root element.
|
|
29
|
+
effects: ["trackTriggers", "trackMenuEvents"],
|
|
30
|
+
on: {
|
|
31
|
+
"TRIGGER.FOCUS": {
|
|
32
|
+
actions: ["setActiveId"]
|
|
33
|
+
},
|
|
34
|
+
TYPEAHEAD: {
|
|
35
|
+
actions: ["focusMatchingTrigger"]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
states: {
|
|
39
|
+
idle: {
|
|
40
|
+
on: {
|
|
41
|
+
"TRIGGER.FOCUS_IN": {
|
|
42
|
+
target: "focused",
|
|
43
|
+
actions: ["setActiveId"]
|
|
44
|
+
},
|
|
45
|
+
"MENU.OPEN": {
|
|
46
|
+
target: "open",
|
|
47
|
+
actions: ["setHasOpenMenu"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
focused: {
|
|
52
|
+
on: {
|
|
53
|
+
"ROOT.BLUR": {
|
|
54
|
+
target: "idle"
|
|
55
|
+
},
|
|
56
|
+
"TRIGGER.FOCUS_NEXT": {
|
|
57
|
+
actions: ["focusNextTrigger"]
|
|
58
|
+
},
|
|
59
|
+
"TRIGGER.FOCUS_PREV": {
|
|
60
|
+
actions: ["focusPrevTrigger"]
|
|
61
|
+
},
|
|
62
|
+
"TRIGGER.FOCUS_FIRST": {
|
|
63
|
+
actions: ["focusFirstTrigger"]
|
|
64
|
+
},
|
|
65
|
+
"TRIGGER.FOCUS_LAST": {
|
|
66
|
+
actions: ["focusLastTrigger"]
|
|
67
|
+
},
|
|
68
|
+
"MENU.OPEN": {
|
|
69
|
+
target: "open",
|
|
70
|
+
actions: ["setHasOpenMenu"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
open: {
|
|
75
|
+
on: {
|
|
76
|
+
// Arrow-switch to the sibling menu (move + request open).
|
|
77
|
+
"TRIGGER.FOCUS_NEXT": {
|
|
78
|
+
actions: ["switchToNextMenu"]
|
|
79
|
+
},
|
|
80
|
+
"TRIGGER.FOCUS_PREV": {
|
|
81
|
+
actions: ["switchToPrevMenu"]
|
|
82
|
+
},
|
|
83
|
+
// Another menu opened (sibling switch) — stay open.
|
|
84
|
+
"MENU.OPEN": {
|
|
85
|
+
actions: ["setHasOpenMenu"]
|
|
86
|
+
},
|
|
87
|
+
"MENU.CLOSE": [
|
|
88
|
+
{
|
|
89
|
+
// Transient close: another menu is about to open. Keep `hasOpenMenu` active
|
|
90
|
+
// so it doesn't flicker while switching siblings.
|
|
91
|
+
guard: "isTransientClose"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
target: "focused",
|
|
95
|
+
actions: ["clearHasOpenMenu"]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
// No ROOT.BLUR: focus lives in the open menu's portalled content. The menu drives
|
|
99
|
+
// close transitions via `menu:close`.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
implementations: {
|
|
104
|
+
guards: {
|
|
105
|
+
isTransientClose: ({ event }) => event.reason === "sibling-open" || event.reason === "list-navigation"
|
|
106
|
+
},
|
|
107
|
+
effects: {
|
|
108
|
+
// Reconcile activeId as triggers mount/unmount. Triggers render `role=menuitem`
|
|
109
|
+
// synchronously (the menu is told it's in a menubar via props), so childList is enough.
|
|
110
|
+
trackTriggers({ scope, context }) {
|
|
111
|
+
const rootEl = dom.getRootEl(scope);
|
|
112
|
+
if (!rootEl) return;
|
|
113
|
+
const reconcile = () => reconcileActiveId(context, scope);
|
|
114
|
+
raf(reconcile);
|
|
115
|
+
return observeChildren(rootEl, { callback: reconcile });
|
|
116
|
+
},
|
|
117
|
+
trackMenuEvents({ scope, send }) {
|
|
118
|
+
const rootEl = dom.getRootEl(scope);
|
|
119
|
+
if (!rootEl) return;
|
|
120
|
+
const onFocusIn = (event) => {
|
|
121
|
+
const target = event.target;
|
|
122
|
+
if (!dom.isTriggerEl(target)) return;
|
|
123
|
+
send({ type: "TRIGGER.FOCUS_IN", id: target.id });
|
|
124
|
+
send({ type: "TRIGGER.FOCUS", id: target.id });
|
|
125
|
+
};
|
|
126
|
+
const onFocusOut = (event) => {
|
|
127
|
+
const related = event.relatedTarget;
|
|
128
|
+
if (related && rootEl.contains(related)) return;
|
|
129
|
+
send({ type: "ROOT.BLUR" });
|
|
130
|
+
};
|
|
131
|
+
const cleanups = [
|
|
132
|
+
addDomEvent(rootEl, "menu:open", (event) => {
|
|
133
|
+
send({ type: "MENU.OPEN", id: event.detail?.menuId, triggerId: event.detail?.triggerId });
|
|
134
|
+
}),
|
|
135
|
+
addDomEvent(rootEl, "menu:close", (event) => {
|
|
136
|
+
send({ type: "MENU.CLOSE", id: event.detail?.menuId, reason: event.detail?.reason });
|
|
137
|
+
}),
|
|
138
|
+
addDomEvent(rootEl, "menu:focus-next", () => send({ type: "TRIGGER.FOCUS_NEXT" })),
|
|
139
|
+
addDomEvent(rootEl, "menu:focus-prev", () => send({ type: "TRIGGER.FOCUS_PREV" })),
|
|
140
|
+
addDomEvent(rootEl, "focusin", onFocusIn),
|
|
141
|
+
addDomEvent(rootEl, "focusout", onFocusOut)
|
|
142
|
+
];
|
|
143
|
+
return () => {
|
|
144
|
+
cleanups.forEach((fn) => fn());
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
actions: {
|
|
149
|
+
setActiveId({ context, event }) {
|
|
150
|
+
if (event.id == null) return;
|
|
151
|
+
context.set("activeId", event.id);
|
|
152
|
+
},
|
|
153
|
+
setHasOpenMenu({ context, event }) {
|
|
154
|
+
context.set("hasOpenMenu", true);
|
|
155
|
+
if (event.triggerId) {
|
|
156
|
+
context.set("activeId", event.triggerId);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
clearHasOpenMenu({ context }) {
|
|
160
|
+
context.set("hasOpenMenu", false);
|
|
161
|
+
},
|
|
162
|
+
focusNextTrigger({ context, scope, prop }) {
|
|
163
|
+
const activeId = context.get("activeId");
|
|
164
|
+
const nextEl = activeId ? dom.getNextTriggerEl(scope, activeId, prop("loopFocus")) : dom.getFirstTriggerEl(scope);
|
|
165
|
+
focusTrigger(context, scope, nextEl);
|
|
166
|
+
},
|
|
167
|
+
focusPrevTrigger({ context, scope, prop }) {
|
|
168
|
+
const activeId = context.get("activeId");
|
|
169
|
+
const prevEl = activeId ? dom.getPrevTriggerEl(scope, activeId, prop("loopFocus")) : dom.getLastTriggerEl(scope);
|
|
170
|
+
focusTrigger(context, scope, prevEl);
|
|
171
|
+
},
|
|
172
|
+
focusMatchingTrigger({ context, scope, event, refs }) {
|
|
173
|
+
const matched = getByTypeahead(dom.getTriggerEls(scope), {
|
|
174
|
+
state: refs.get("typeaheadState"),
|
|
175
|
+
key: event.key,
|
|
176
|
+
activeId: context.get("activeId")
|
|
177
|
+
});
|
|
178
|
+
focusTrigger(context, scope, matched);
|
|
179
|
+
},
|
|
180
|
+
focusFirstTrigger({ context, scope }) {
|
|
181
|
+
focusTrigger(context, scope, dom.getFirstTriggerEl(scope));
|
|
182
|
+
},
|
|
183
|
+
focusLastTrigger({ context, scope }) {
|
|
184
|
+
focusTrigger(context, scope, dom.getLastTriggerEl(scope));
|
|
185
|
+
},
|
|
186
|
+
switchToNextMenu({ context, scope, prop }) {
|
|
187
|
+
const activeId = context.get("activeId");
|
|
188
|
+
const nextEl = activeId ? dom.getNextTriggerEl(scope, activeId, prop("loopFocus")) : dom.getFirstTriggerEl(scope);
|
|
189
|
+
if (!nextEl) return;
|
|
190
|
+
setActiveTrigger(context, nextEl.id);
|
|
191
|
+
dom.requestOpenMenu(scope, nextEl.id);
|
|
192
|
+
},
|
|
193
|
+
switchToPrevMenu({ context, scope, prop }) {
|
|
194
|
+
const activeId = context.get("activeId");
|
|
195
|
+
const prevEl = activeId ? dom.getPrevTriggerEl(scope, activeId, prop("loopFocus")) : dom.getLastTriggerEl(scope);
|
|
196
|
+
if (!prevEl) return;
|
|
197
|
+
setActiveTrigger(context, prevEl.id);
|
|
198
|
+
dom.requestOpenMenu(scope, prevEl.id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
function reconcileActiveId(context, scope) {
|
|
204
|
+
const activeId = context.get("activeId");
|
|
205
|
+
const nextActiveId = dom.getValidActiveId(scope, activeId);
|
|
206
|
+
if (nextActiveId !== activeId) context.set("activeId", nextActiveId);
|
|
207
|
+
}
|
|
208
|
+
function setActiveTrigger(context, id) {
|
|
209
|
+
context.set("activeId", id);
|
|
210
|
+
}
|
|
211
|
+
function focusTrigger(context, scope, el) {
|
|
212
|
+
if (!el) return;
|
|
213
|
+
setActiveTrigger(context, el.id);
|
|
214
|
+
raf(() => el.focus());
|
|
215
|
+
}
|
|
216
|
+
export {
|
|
217
|
+
machine
|
|
218
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MenubarProps } from './menubar.types.mjs';
|
|
2
|
+
import '@zag-js/core';
|
|
3
|
+
import '@zag-js/dom-query';
|
|
4
|
+
import '@zag-js/types';
|
|
5
|
+
|
|
6
|
+
declare const props: (keyof MenubarProps)[];
|
|
7
|
+
declare const splitProps: <Props extends Partial<MenubarProps>>(props: Props) => [Partial<MenubarProps>, Omit<Props, keyof MenubarProps>];
|
|
8
|
+
|
|
9
|
+
export { props, splitProps };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MenubarProps } from './menubar.types.js';
|
|
2
|
+
import '@zag-js/core';
|
|
3
|
+
import '@zag-js/dom-query';
|
|
4
|
+
import '@zag-js/types';
|
|
5
|
+
|
|
6
|
+
declare const props: (keyof MenubarProps)[];
|
|
7
|
+
declare const splitProps: <Props extends Partial<MenubarProps>>(props: Props) => [Partial<MenubarProps>, Omit<Props, keyof MenubarProps>];
|
|
8
|
+
|
|
9
|
+
export { props, splitProps };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/menubar.props.ts
|
|
21
|
+
var menubar_props_exports = {};
|
|
22
|
+
__export(menubar_props_exports, {
|
|
23
|
+
props: () => props,
|
|
24
|
+
splitProps: () => splitProps
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(menubar_props_exports);
|
|
27
|
+
var import_types = require("@zag-js/types");
|
|
28
|
+
var import_utils = require("@zag-js/utils");
|
|
29
|
+
var props = (0, import_types.createProps)()([
|
|
30
|
+
"dir",
|
|
31
|
+
"disabled",
|
|
32
|
+
"getRootNode",
|
|
33
|
+
"id",
|
|
34
|
+
"ids",
|
|
35
|
+
"loopFocus",
|
|
36
|
+
"orientation"
|
|
37
|
+
]);
|
|
38
|
+
var splitProps = (0, import_utils.createSplitProps)(props);
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
props,
|
|
42
|
+
splitProps
|
|
43
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/menubar.props.ts
|
|
2
|
+
import { createProps } from "@zag-js/types";
|
|
3
|
+
import { createSplitProps } from "@zag-js/utils";
|
|
4
|
+
var props = createProps()([
|
|
5
|
+
"dir",
|
|
6
|
+
"disabled",
|
|
7
|
+
"getRootNode",
|
|
8
|
+
"id",
|
|
9
|
+
"ids",
|
|
10
|
+
"loopFocus",
|
|
11
|
+
"orientation"
|
|
12
|
+
]);
|
|
13
|
+
var splitProps = createSplitProps(props);
|
|
14
|
+
export {
|
|
15
|
+
props,
|
|
16
|
+
splitProps
|
|
17
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Machine, EventObject, Service } from '@zag-js/core';
|
|
2
|
+
import { TypeaheadState } from '@zag-js/dom-query';
|
|
3
|
+
import { PropTypes, Orientation, RequiredBy, DirectionProperty, CommonProperties } from '@zag-js/types';
|
|
4
|
+
export { Orientation } from '@zag-js/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The reason a menu within the menubar closed. Used to decide whether the menubar
|
|
8
|
+
* should keep `hasOpenMenu` active (another menu is about to open) or reset it.
|
|
9
|
+
*/
|
|
10
|
+
type MenuCloseReason = "escape" | "click-outside" | "sibling-open" | "item-select" | "list-navigation";
|
|
11
|
+
/**
|
|
12
|
+
* The custom DOM events menus dispatch on the menubar element to coordinate.
|
|
13
|
+
* Coordination is intentionally DOM-based — no imports between the menu and menubar packages.
|
|
14
|
+
*/
|
|
15
|
+
interface MenubarEventMap {
|
|
16
|
+
"menu:open": CustomEvent<{
|
|
17
|
+
menuId: string;
|
|
18
|
+
}>;
|
|
19
|
+
"menu:close": CustomEvent<{
|
|
20
|
+
menuId: string;
|
|
21
|
+
reason: MenuCloseReason;
|
|
22
|
+
}>;
|
|
23
|
+
"menu:focus-next": CustomEvent<void>;
|
|
24
|
+
"menu:focus-prev": CustomEvent<void>;
|
|
25
|
+
}
|
|
26
|
+
type ElementIds = Partial<{
|
|
27
|
+
root: string;
|
|
28
|
+
}>;
|
|
29
|
+
interface MenubarProps extends DirectionProperty, CommonProperties {
|
|
30
|
+
/**
|
|
31
|
+
* The ids of the elements in the menubar. Useful for composition.
|
|
32
|
+
*/
|
|
33
|
+
ids?: ElementIds | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* The orientation of the menubar.
|
|
36
|
+
* @default "horizontal"
|
|
37
|
+
*/
|
|
38
|
+
orientation?: Orientation | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to loop the keyboard navigation across the triggers.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
loopFocus?: boolean | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the menubar (and all its menu triggers) is disabled.
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
disabled?: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
type PropsWithDefault = "orientation" | "loopFocus";
|
|
51
|
+
interface MenubarContext {
|
|
52
|
+
/**
|
|
53
|
+
* The id of the currently focused/tabbable trigger (roving tabindex).
|
|
54
|
+
*/
|
|
55
|
+
activeId: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* Whether any menu within the menubar is currently open.
|
|
58
|
+
*/
|
|
59
|
+
hasOpenMenu: boolean;
|
|
60
|
+
}
|
|
61
|
+
interface MenubarSchema {
|
|
62
|
+
props: RequiredBy<MenubarProps, PropsWithDefault>;
|
|
63
|
+
context: MenubarContext;
|
|
64
|
+
refs: {
|
|
65
|
+
typeaheadState: TypeaheadState;
|
|
66
|
+
};
|
|
67
|
+
state: "idle" | "focused" | "open";
|
|
68
|
+
event: EventObject;
|
|
69
|
+
action: string;
|
|
70
|
+
effect: string;
|
|
71
|
+
guard: string;
|
|
72
|
+
}
|
|
73
|
+
type MenubarService = Service<MenubarSchema>;
|
|
74
|
+
type MenubarMachine = Machine<MenubarSchema>;
|
|
75
|
+
/**
|
|
76
|
+
* Config to pass to a child menu's `menubar` prop so it behaves as a menubar menu.
|
|
77
|
+
* Structurally matches the `MenubarContext` type from `@zag-js/menu`.
|
|
78
|
+
*/
|
|
79
|
+
interface MenubarMenuContext {
|
|
80
|
+
rootId: string;
|
|
81
|
+
disabled: boolean;
|
|
82
|
+
orientation: Orientation;
|
|
83
|
+
activeId: string | null;
|
|
84
|
+
}
|
|
85
|
+
interface MenubarApi<T extends PropTypes = PropTypes> {
|
|
86
|
+
/**
|
|
87
|
+
* Whether any menu within the menubar is open.
|
|
88
|
+
*/
|
|
89
|
+
hasOpenMenu: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The orientation of the menubar.
|
|
92
|
+
*/
|
|
93
|
+
orientation: Orientation;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the menubar is disabled.
|
|
96
|
+
*/
|
|
97
|
+
disabled: boolean;
|
|
98
|
+
getRootProps: () => T["element"];
|
|
99
|
+
/**
|
|
100
|
+
* Returns the config to pass to each top-level menu's `menubar` prop. Adapters
|
|
101
|
+
* usually expose this via a context so nested `<Menu>`s can read it.
|
|
102
|
+
*/
|
|
103
|
+
getMenuContext: () => MenubarMenuContext;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type { ElementIds, MenuCloseReason, MenubarApi, MenubarEventMap, MenubarMachine, MenubarMenuContext, MenubarProps, MenubarSchema, MenubarService };
|