UNPKG

@base-ui/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

168 lines (163 loc) 6.32 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.MenuStore = void 0; exports.createNullMenuStore = createNullMenuStore; var React = _interopRequireWildcard(require("react")); var _store = require("@base-ui/utils/store"); var _empty = require("@base-ui/utils/empty"); var _FloatingTreeStore = require("../../floating-ui-react/components/FloatingTreeStore"); var _NullStore = require("../../utils/NullStore"); var _popups = require("../../utils/popups"); const selectors = { ..._popups.popupStoreSelectors, disabled: state => state.parent.type === 'menubar' ? state.parent.context.disabled || state.disabled : state.disabled, modal: state => (state.parent.type === undefined || state.parent.type === 'context-menu') && (state.modal ?? true), openMethod: state => state.openMethod, allowMouseEnter: state => state.allowMouseEnter, highlightItemOnHover: state => state.highlightItemOnHover, parent: state => state.parent, rootId: state => { if (state.parent.type === 'menu') { return state.parent.store.select('rootId'); } return state.parent.type !== undefined ? state.parent.context.rootId : state.rootId; }, activeIndex: state => state.activeIndex, isActive: (state, itemIndex) => state.activeIndex === itemIndex, hoverEnabled: state => state.hoverEnabled, instantType: state => state.instantType, lastOpenChangeReason: state => state.openChangeReason, floatingTreeRoot: state => { if (state.parent.type === 'menu') { return state.parent.store.select('floatingTreeRoot'); } return state.floatingTreeRoot; }, floatingNodeId: state => state.floatingNodeId, floatingParentNodeId: state => state.floatingParentNodeId, itemProps: state => state.itemProps, closeDelay: state => state.closeDelay, adaptiveOrigin: state => state.adaptiveOrigin, keyboardEventRelay: state => { if (state.keyboardEventRelay) { return state.keyboardEventRelay; } if (state.parent.type === 'menu') { return state.parent.store.select('keyboardEventRelay'); } return undefined; } }; /** * The store view that detached handle-backed triggers read from. Both the real `MenuStore` and the * inert fallback store satisfy it, so a trigger can read from whichever store the handle currently * exposes. Narrowed to the members a trigger actually uses — the trigger-data members plus `setOpen` * (called by the focus guards) — so the exposed surface can't bypass the open-change pipeline; on * the detached fallback store every one of these mutations is a no-op. */ class MenuStore extends _store.ReactStore { constructor(initialState) { super({ ...createInitialState(), ...initialState }, createInitialContext(), selectors); // Set up propagation of state from parent menu if applicable. this.unsubscribeParentListener = this.observe('parent', parent => { this.unsubscribeParentListener?.(); if (parent.type === 'menu') { let rootId = parent.store.select('rootId'); let floatingTreeRoot = parent.store.select('floatingTreeRoot'); let keyboardEventRelay = parent.store.select('keyboardEventRelay'); this.unsubscribeParentListener = parent.store.subscribe(() => { const nextRootId = parent.store.select('rootId'); const nextFloatingTreeRoot = parent.store.select('floatingTreeRoot'); const nextKeyboardEventRelay = parent.store.select('keyboardEventRelay'); if (rootId === nextRootId && floatingTreeRoot === nextFloatingTreeRoot && keyboardEventRelay === nextKeyboardEventRelay) { return; } rootId = nextRootId; floatingTreeRoot = nextFloatingTreeRoot; keyboardEventRelay = nextKeyboardEventRelay; this.notifyAll(); }); this.context.allowMouseUpTriggerRef = parent.store.context.allowMouseUpTriggerRef; return; } if (parent.type !== undefined) { this.context.allowMouseUpTriggerRef = parent.context.allowMouseUpTriggerRef; } this.unsubscribeParentListener = null; }); } setOpen(open, eventDetails) { this.state.floatingRootContext.context.events.emit('setOpen', { open, eventDetails }); } unsubscribeParentListener = null; } /** * Creates the inert fallback store used by detached handle-backed triggers while no `Menu.Root` is * attached. It preserves a menu-specific trigger registry in context so detached triggers can * register before migrating to the live root store. `setOpen` is a no-op (matching the inert * reads/writes of `NullStore`), so a trigger can hand the store to focus-guard helpers that expect * `setOpen` without it ever taking effect while detached. */ exports.MenuStore = MenuStore; function createNullMenuStore() { const store = new _NullStore.NullStore(Object.freeze(createInitialState()), Object.freeze(createInitialContext()), selectors); return Object.assign(store, { setOpen: _empty.NOOP }); } function createInitialContext() { return { positionerRef: /*#__PURE__*/React.createRef(), popupRef: /*#__PURE__*/React.createRef(), typingRef: { current: false }, itemDomElements: { current: [] }, itemLabels: { current: [] }, allowMouseUpTriggerRef: { current: false }, triggerFocusTargetRef: /*#__PURE__*/React.createRef(), beforeContentFocusGuardRef: /*#__PURE__*/React.createRef(), onOpenChangeComplete: undefined, triggerElements: new _popups.PopupTriggerMap() }; } function createInitialState() { return { ...(0, _popups.createInitialPopupStoreState)(), disabled: false, modal: true, openMethod: null, allowMouseEnter: false, highlightItemOnHover: true, parent: { type: undefined }, rootId: undefined, activeIndex: null, hoverEnabled: true, instantType: undefined, openChangeReason: null, floatingTreeRoot: new _FloatingTreeStore.FloatingTreeStore(), floatingNodeId: undefined, floatingParentNodeId: null, itemProps: _empty.EMPTY_OBJECT, keyboardEventRelay: undefined, closeDelay: 0, adaptiveOrigin: undefined }; }