@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.
@@ -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 };