@base-ui-components/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.
159 lines (156 loc) • 5.85 kB
JavaScript
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MenuTrigger = void 0;
var React = _interopRequireWildcard(require("react"));
var _dom = require("@floating-ui/utils/dom");
var _useMergedRefs = require("@base-ui-components/utils/useMergedRefs");
var _useTimeout = require("@base-ui-components/utils/useTimeout");
var _owner = require("@base-ui-components/utils/owner");
var _useEventCallback = require("@base-ui-components/utils/useEventCallback");
var _utils = require("../../floating-ui-react/utils");
var _index = require("../../floating-ui-react/index");
var _MenuRootContext = require("../root/MenuRootContext");
var _popupStateMapping = require("../../utils/popupStateMapping");
var _useRenderElement = require("../../utils/useRenderElement");
var _mergeProps = require("../../merge-props");
var _useButton = require("../../use-button/useButton");
var _getPseudoElementBounds = require("../../utils/getPseudoElementBounds");
var _CompositeItem = require("../../composite/item/CompositeItem");
var _jsxRuntime = require("react/jsx-runtime");
const BOUNDARY_OFFSET = 2;
/**
* A button that opens the menu.
* Renders a `<button>` element.
*
* Documentation: [Base UI Menu](https://base-ui.com/react/components/menu)
*/
const MenuTrigger = exports.MenuTrigger = /*#__PURE__*/React.forwardRef(function MenuTrigger(componentProps, forwardedRef) {
const {
render,
className,
disabled: disabledProp = false,
nativeButton = true,
...elementProps
} = componentProps;
const {
triggerProps: rootTriggerProps,
disabled: menuDisabled,
setTriggerElement,
open,
allowMouseUpTriggerRef,
positionerRef,
parent,
lastOpenChangeReason,
rootId
} = (0, _MenuRootContext.useMenuRootContext)();
const disabled = disabledProp || menuDisabled;
const triggerRef = React.useRef(null);
const allowMouseUpTriggerTimeout = (0, _useTimeout.useTimeout)();
const {
getButtonProps,
buttonRef
} = (0, _useButton.useButton)({
disabled,
native: nativeButton
});
const handleRef = (0, _useMergedRefs.useMergedRefs)(buttonRef, setTriggerElement);
const {
events: menuEvents
} = (0, _index.useFloatingTree)();
React.useEffect(() => {
if (!open && parent.type === undefined) {
allowMouseUpTriggerRef.current = false;
}
}, [allowMouseUpTriggerRef, open, parent.type]);
const handleDocumentMouseUp = (0, _useEventCallback.useEventCallback)(mouseEvent => {
if (!triggerRef.current) {
return;
}
allowMouseUpTriggerTimeout.clear();
allowMouseUpTriggerRef.current = false;
const mouseUpTarget = mouseEvent.target;
if ((0, _utils.contains)(triggerRef.current, mouseUpTarget) || (0, _utils.contains)(positionerRef.current, mouseUpTarget) || mouseUpTarget === triggerRef.current) {
return;
}
if (mouseUpTarget != null && findRootOwnerId(mouseUpTarget) === rootId) {
return;
}
const bounds = (0, _getPseudoElementBounds.getPseudoElementBounds)(triggerRef.current);
if (mouseEvent.clientX >= bounds.left - BOUNDARY_OFFSET && mouseEvent.clientX <= bounds.right + BOUNDARY_OFFSET && mouseEvent.clientY >= bounds.top - BOUNDARY_OFFSET && mouseEvent.clientY <= bounds.bottom + BOUNDARY_OFFSET) {
return;
}
menuEvents.emit('close', {
domEvent: mouseEvent,
reason: 'cancel-open'
});
});
React.useEffect(() => {
if (open && lastOpenChangeReason === 'trigger-hover') {
const doc = (0, _owner.ownerDocument)(triggerRef.current);
doc.addEventListener('mouseup', handleDocumentMouseUp, {
once: true
});
}
}, [open, handleDocumentMouseUp, lastOpenChangeReason]);
const isMenubar = parent.type === 'menubar';
const getTriggerProps = React.useCallback(externalProps => {
return (0, _mergeProps.mergeProps)(isMenubar ? {
role: 'menuitem'
} : {}, {
'aria-haspopup': 'menu',
ref: handleRef,
onMouseDown: event => {
if (open) {
return;
}
// mousedown -> mouseup on menu item should not trigger it within 200ms.
allowMouseUpTriggerTimeout.start(200, () => {
allowMouseUpTriggerRef.current = true;
});
const doc = (0, _owner.ownerDocument)(event.currentTarget);
doc.addEventListener('mouseup', handleDocumentMouseUp, {
once: true
});
}
}, externalProps, getButtonProps);
}, [getButtonProps, handleRef, open, allowMouseUpTriggerRef, allowMouseUpTriggerTimeout, handleDocumentMouseUp, isMenubar]);
const state = React.useMemo(() => ({
disabled,
open
}), [disabled, open]);
const ref = [triggerRef, forwardedRef, buttonRef];
const props = [rootTriggerProps, elementProps, getTriggerProps];
const element = (0, _useRenderElement.useRenderElement)('button', componentProps, {
enabled: !isMenubar,
customStyleHookMapping: _popupStateMapping.pressableTriggerOpenStateMapping,
state,
ref,
props
});
if (isMenubar) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_CompositeItem.CompositeItem, {
tag: "button",
render: render,
className: className,
state: state,
refs: ref,
props: props,
customStyleHookMapping: _popupStateMapping.pressableTriggerOpenStateMapping
});
}
return element;
});
if (process.env.NODE_ENV !== "production") MenuTrigger.displayName = "MenuTrigger";
function findRootOwnerId(node) {
if ((0, _dom.isHTMLElement)(node) && node.hasAttribute('data-rootownerid')) {
return node.getAttribute('data-rootownerid') ?? undefined;
}
if ((0, _dom.isLastTraversableNode)(node)) {
return undefined;
}
return findRootOwnerId((0, _dom.getParentNode)(node));
}