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.

136 lines (132 loc) 5.11 kB
'use client'; import _formatErrorMessage from "@base-ui/utils/formatErrorMessage"; import * as React from 'react'; import { usePopoverRootContext } from "../root/PopoverRootContext.js"; import { useButton } from "../../internals/use-button/useButton.js"; import { triggerOpenStateMapping, pressableTriggerOpenStateMapping } from "../../utils/popupStateMapping.js"; import { useRenderElement } from "../../internals/useRenderElement.js"; import { CLICK_TRIGGER_IDENTIFIER } from "../../internals/constants.js"; import { safePolygon, useClick, useHoverReferenceInteraction, useInteractions } from "../../floating-ui-react/index.js"; import { OPEN_DELAY } from "../utils/constants.js"; import { useBaseUiId } from "../../internals/useBaseUiId.js"; import { FocusGuard } from "../../utils/FocusGuard.js"; import { REASONS } from "../../internals/reasons.js"; import { useTriggerDataForwarding } from "../../utils/popups/index.js"; import { useTriggerFocusGuards } from "../../utils/popups/useTriggerFocusGuards.js"; /** * A button that opens the popover. * Renders a `<button>` element. * * Documentation: [Base UI Popover](https://base-ui.com/react/components/popover) */ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export const PopoverTrigger = /*#__PURE__*/React.forwardRef(function PopoverTrigger(componentProps, forwardedRef) { const { render, className, disabled = false, nativeButton = true, handle, payload, openOnHover = false, delay = OPEN_DELAY, closeDelay = 0, id: idProp, style, ...elementProps } = componentProps; const rootContext = usePopoverRootContext(true); const store = handle?.store ?? rootContext?.store; if (!store) { throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Popover.Trigger> must be either used within a <Popover.Root> component or provided with a handle.' : _formatErrorMessage(74)); } const thisTriggerId = useBaseUiId(idProp); const isTriggerActive = store.useState('isTriggerActive', thisTriggerId); const floatingContext = store.useState('floatingRootContext'); const isOpenedByThisTrigger = store.useState('isOpenedByTrigger', thisTriggerId); const triggerElementRef = React.useRef(null); const { registerTrigger, isMountedByThisTrigger } = useTriggerDataForwarding(thisTriggerId, triggerElementRef, store, { payload, disabled, openOnHover, closeDelay }); const openReason = store.useState('openChangeReason'); const stickIfOpen = store.useState('stickIfOpen'); const openMethod = store.useState('openMethod'); const focusManagerModal = store.useState('focusManagerModal'); const hoverProps = useHoverReferenceInteraction(floatingContext, { enabled: floatingContext != null && openOnHover && (openMethod !== 'touch' || openReason !== REASONS.triggerPress), mouseOnly: true, move: false, handleClose: safePolygon(), restMs: delay, delay: { close: closeDelay }, triggerElementRef, isActiveTrigger: isTriggerActive, isClosing: () => store.select('transitionStatus') === 'ending' }); const click = useClick(floatingContext, { enabled: floatingContext != null, stickIfOpen }); const localProps = useInteractions([click]); const rootTriggerProps = store.useState('triggerProps', isMountedByThisTrigger); const state = { disabled, open: isOpenedByThisTrigger }; const { getButtonProps, buttonRef } = useButton({ disabled, native: nativeButton }); const stateAttributesMapping = React.useMemo(() => ({ open(value) { if (value && openReason === REASONS.triggerPress) { return pressableTriggerOpenStateMapping.open(value); } return triggerOpenStateMapping.open(value); } }), [openReason]); const element = useRenderElement('button', componentProps, { state, ref: [buttonRef, forwardedRef, registerTrigger, triggerElementRef], props: [localProps.getReferenceProps(), hoverProps, rootTriggerProps, { [CLICK_TRIGGER_IDENTIFIER]: '', id: thisTriggerId }, elementProps, getButtonProps], stateAttributesMapping }); const { preFocusGuardRef, handlePreFocusGuardFocus, handleFocusTargetFocus } = useTriggerFocusGuards(store, triggerElementRef); // A fragment with key is required to ensure that the `element` is mounted to the same DOM node // regardless of whether the focus guards are rendered or not. if (isTriggerActive && !focusManagerModal) { return /*#__PURE__*/_jsxs(React.Fragment, { children: [/*#__PURE__*/_jsx(FocusGuard, { ref: preFocusGuardRef, onFocus: handlePreFocusGuardFocus }), /*#__PURE__*/_jsx(React.Fragment, { children: element }, thisTriggerId), /*#__PURE__*/_jsx(FocusGuard, { ref: store.context.triggerFocusTargetRef, onFocus: handleFocusTargetFocus })] }); } return /*#__PURE__*/_jsx(React.Fragment, { children: element }, thisTriggerId); }); if (process.env.NODE_ENV !== "production") PopoverTrigger.displayName = "PopoverTrigger";