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.

246 lines (241 loc) 9.2 kB
"use strict"; 'use client'; Object.defineProperty(exports, "__esModule", { value: true }); exports.useAnchorPositioning = useAnchorPositioning; var React = _interopRequireWildcard(require("react")); var _react2 = require("@floating-ui/react"); var _utils = require("@floating-ui/utils"); var _useEnhancedEffect = require("./useEnhancedEffect"); var _DirectionContext = require("../direction-provider/DirectionContext"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * Provides standardized anchor positioning behavior for floating elements. Wraps Floating UI's * `useFloating` hook. * @ignore - internal hook. */ function useAnchorPositioning(params) { const { anchor, floatingRootContext, positionMethod = 'absolute', side: sideParam = 'top', sideOffset = 0, align = 'center', alignOffset = 0, collisionBoundary, collisionPadding = 5, fallbackAxisSideDirection = 'none', sticky = false, keepMounted = false, arrowPadding = 5, mounted, trackAnchor = true, allowAxisFlip = true, nodeId } = params; const direction = (0, _DirectionContext.useDirection)(); const isRtl = direction === 'rtl'; const side = { top: 'top', right: 'right', bottom: 'bottom', left: 'left', 'inline-end': isRtl ? 'left' : 'right', 'inline-start': isRtl ? 'right' : 'left' }[sideParam]; const placement = align === 'center' ? side : `${side}-${align}`; const commonCollisionProps = { boundary: collisionBoundary === 'clipping-ancestors' ? 'clippingAncestors' : collisionBoundary, padding: collisionPadding }; // Using a ref assumes that the arrow element is always present in the DOM for the lifetime of the // tooltip. If this assumption ends up being false, we can switch to state to manage the arrow's // presence. const arrowRef = React.useRef(null); const middleware = [(0, _react2.offset)({ mainAxis: sideOffset, crossAxis: alignOffset, alignmentAxis: alignOffset })]; const flipMiddleware = (0, _react2.flip)({ ...commonCollisionProps, fallbackAxisSideDirection: allowAxisFlip ? fallbackAxisSideDirection : 'none' }); const shiftMiddleware = (0, _react2.shift)({ ...commonCollisionProps, crossAxis: sticky, limiter: sticky ? undefined : (0, _react2.limitShift)(() => { if (!arrowRef.current) { return {}; } const { height } = arrowRef.current.getBoundingClientRect(); return { offset: height / 2 + (typeof collisionPadding === 'number' ? collisionPadding : 0) }; }) }); // https://floating-ui.com/docs/flip#combining-with-shift if (align !== 'center') { middleware.push(flipMiddleware, shiftMiddleware); } else { middleware.push(shiftMiddleware, flipMiddleware); } middleware.push((0, _react2.size)({ ...commonCollisionProps, apply({ elements: { floating }, rects: { reference }, availableWidth, availableHeight }) { Object.entries({ '--available-width': `${availableWidth}px`, '--available-height': `${availableHeight}px`, '--anchor-width': `${reference.width}px`, '--anchor-height': `${reference.height}px` }).forEach(([key, value]) => { floating.style.setProperty(key, value); }); } }), (0, _react2.arrow)(() => ({ // `transform-origin` calculations rely on an element existing. If the arrow hasn't been set, // we'll create a fake element. element: arrowRef.current || document.createElement('div'), padding: arrowPadding }), [arrowPadding]), (0, _react2.hide)(), { name: 'transformOrigin', fn({ elements, middlewareData, placement: renderedPlacement }) { const currentRenderedSide = (0, _utils.getSide)(renderedPlacement); const arrowEl = arrowRef.current; const arrowX = middlewareData.arrow?.x ?? 0; const arrowY = middlewareData.arrow?.y ?? 0; const arrowWidth = arrowEl?.clientWidth ?? 0; const arrowHeight = arrowEl?.clientHeight ?? 0; const transformX = arrowX + arrowWidth / 2; const transformY = arrowY + arrowHeight / 2; const transformOrigin = { top: `${transformX}px calc(100% + ${sideOffset}px)`, bottom: `${transformX}px ${-sideOffset}px`, left: `calc(100% + ${sideOffset}px) ${transformY}px`, right: `${-sideOffset}px ${transformY}px` }[currentRenderedSide]; elements.floating.style.setProperty('--transform-origin', transformOrigin); return {}; } }); // Ensure positioning doesn't run initially for `keepMounted` elements that // aren't initially open. let rootContext = floatingRootContext; if (!mounted && floatingRootContext) { rootContext = { ...floatingRootContext, elements: { reference: null, floating: null, domReference: null } }; } const autoUpdateOptions = React.useMemo(() => ({ // Keep `ancestorResize` for window resizing. TODO: determine the best configuration, or // if we need to allow options. ancestorScroll: trackAnchor, elementResize: trackAnchor && typeof ResizeObserver !== 'undefined', layoutShift: trackAnchor && typeof IntersectionObserver !== 'undefined' }), [trackAnchor]); const { refs, elements, floatingStyles, middlewareData, update, placement: renderedPlacement, context: positionerContext, isPositioned } = (0, _react2.useFloating)({ rootContext, placement, middleware, strategy: positionMethod, whileElementsMounted: keepMounted ? undefined : (...args) => (0, _react2.autoUpdate)(...args, autoUpdateOptions), nodeId }); const registeredPositionReferenceRef = React.useRef(null); (0, _useEnhancedEffect.useEnhancedEffect)(() => { if (!mounted) { return; } const resolvedAnchor = typeof anchor === 'function' ? anchor() : anchor; if (resolvedAnchor) { const unwrappedElement = isRef(resolvedAnchor) ? resolvedAnchor.current : resolvedAnchor; refs.setPositionReference(unwrappedElement); registeredPositionReferenceRef.current = unwrappedElement; } }, [mounted, refs, anchor]); React.useEffect(() => { if (!mounted) { return; } // Refs from parent components are set after useLayoutEffect runs and are available in useEffect. // Therefore, if the anchor is a ref, we need to update the position reference in useEffect. if (typeof anchor === 'function') { return; } if (isRef(anchor) && anchor.current !== registeredPositionReferenceRef.current) { refs.setPositionReference(anchor.current); registeredPositionReferenceRef.current = anchor.current; } }, [mounted, refs, anchor]); React.useEffect(() => { if (keepMounted && mounted && elements.domReference && elements.floating) { return (0, _react2.autoUpdate)(elements.domReference, elements.floating, update, autoUpdateOptions); } return undefined; }, [keepMounted, mounted, elements, update, autoUpdateOptions]); const renderedSide = (0, _utils.getSide)(renderedPlacement); const isLogicalSideParam = sideParam === 'inline-start' || sideParam === 'inline-end'; const logicalRight = isRtl ? 'inline-start' : 'inline-end'; const logicalLeft = isRtl ? 'inline-end' : 'inline-start'; const logicalRenderedSide = { top: 'top', right: isLogicalSideParam ? logicalRight : 'right', bottom: 'bottom', left: isLogicalSideParam ? logicalLeft : 'left' }[renderedSide]; const renderedAlign = (0, _utils.getAlignment)(renderedPlacement) || 'center'; const anchorHidden = Boolean(middlewareData.hide?.referenceHidden); const arrowStyles = React.useMemo(() => ({ position: 'absolute', top: middlewareData.arrow?.y, left: middlewareData.arrow?.x }), [middlewareData.arrow]); const arrowUncentered = middlewareData.arrow?.centerOffset !== 0; return React.useMemo(() => ({ positionerStyles: floatingStyles, arrowStyles, arrowRef, arrowUncentered, renderedSide: logicalRenderedSide, renderedAlign, anchorHidden, refs, positionerContext, isPositioned }), [floatingStyles, arrowStyles, arrowRef, arrowUncentered, logicalRenderedSide, renderedAlign, anchorHidden, refs, positionerContext, isPositioned]); } function isRef(param) { return param != null && 'current' in param; }