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