UNPKG

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

125 lines (123 loc) 4.81 kB
'use client'; import * as React from 'react'; import { useStore } from '@base-ui-components/utils/store'; import { useIsoLayoutEffect } from '@base-ui-components/utils/useIsoLayoutEffect'; import { useStableCallback } from '@base-ui-components/utils/useStableCallback'; import { inertValue } from '@base-ui-components/utils/inertValue'; import { useScrollLock } from '@base-ui-components/utils/useScrollLock'; import { useComboboxFloatingContext, useComboboxRootContext, useComboboxDerivedItemsContext } from "../root/ComboboxRootContext.js"; import { ComboboxPositionerContext } from "./ComboboxPositionerContext.js"; import { useAnchorPositioning } from "../../utils/useAnchorPositioning.js"; import { popupStateMapping } from "../../utils/popupStateMapping.js"; import { useComboboxPortalContext } from "../portal/ComboboxPortalContext.js"; import { DROPDOWN_COLLISION_AVOIDANCE } from "../../utils/constants.js"; import { useRenderElement } from "../../utils/useRenderElement.js"; import { selectors } from "../store.js"; import { InternalBackdrop } from "../../utils/InternalBackdrop.js"; /** * Positions the popup against the trigger. * Renders a `<div>` element. */ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositioner(componentProps, forwardedRef) { const { render, className, anchor, positionMethod = 'absolute', side = 'bottom', align = 'center', sideOffset = 0, alignOffset = 0, collisionBoundary = 'clipping-ancestors', collisionPadding = 5, arrowPadding = 5, sticky = false, disableAnchorTracking = false, collisionAvoidance = DROPDOWN_COLLISION_AVOIDANCE, ...elementProps } = componentProps; const store = useComboboxRootContext(); const { filteredItems } = useComboboxDerivedItemsContext(); const floatingRootContext = useComboboxFloatingContext(); const keepMounted = useComboboxPortalContext(); const modal = useStore(store, selectors.modal); const open = useStore(store, selectors.open); const mounted = useStore(store, selectors.mounted); const openMethod = useStore(store, selectors.openMethod); const triggerElement = useStore(store, selectors.triggerElement); const inputElement = useStore(store, selectors.inputElement); const inputInsidePopup = useStore(store, selectors.inputInsidePopup); const empty = filteredItems.length === 0; const resolvedAnchor = anchor ?? (inputInsidePopup ? triggerElement : inputElement); const positioning = useAnchorPositioning({ anchor: resolvedAnchor, floatingRootContext, positionMethod, mounted, side, sideOffset, align, alignOffset, arrowPadding, collisionBoundary, collisionPadding, sticky, disableAnchorTracking, keepMounted, collisionAvoidance, lazyFlip: true }); useScrollLock(open && modal && openMethod !== 'touch', triggerElement); const defaultProps = React.useMemo(() => { const style = { ...positioning.positionerStyles }; if (!open) { style.pointerEvents = 'none'; } return { role: 'presentation', hidden: !mounted, style }; }, [open, mounted, positioning.positionerStyles]); const state = React.useMemo(() => ({ open, side: positioning.side, align: positioning.align, anchorHidden: positioning.anchorHidden, empty }), [open, positioning.side, positioning.align, positioning.anchorHidden, empty]); useIsoLayoutEffect(() => { store.set('popupSide', positioning.side); }, [store, positioning.side]); const contextValue = React.useMemo(() => ({ side: positioning.side, align: positioning.align, arrowRef: positioning.arrowRef, arrowUncentered: positioning.arrowUncentered, arrowStyles: positioning.arrowStyles, anchorHidden: positioning.anchorHidden, isPositioned: positioning.isPositioned }), [positioning.side, positioning.align, positioning.arrowRef, positioning.arrowUncentered, positioning.arrowStyles, positioning.anchorHidden, positioning.isPositioned]); const setPositionerElement = useStableCallback(element => { store.set('positionerElement', element); }); const element = useRenderElement('div', componentProps, { state, ref: [forwardedRef, setPositionerElement], props: [defaultProps, elementProps], stateAttributesMapping: popupStateMapping }); return /*#__PURE__*/_jsxs(ComboboxPositionerContext.Provider, { value: contextValue, children: [mounted && modal && /*#__PURE__*/_jsx(InternalBackdrop, { inert: inertValue(!open), cutout: inputElement ?? triggerElement }), element] }); }); if (process.env.NODE_ENV !== "production") ComboboxPositioner.displayName = "ComboboxPositioner";