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.

239 lines 9.46 kB
import * as React from 'react'; import { type Rect } from '@floating-ui/utils'; import { useFloating, type FloatingRootContext, type VirtualElement, type Padding, type FloatingContext, type Side as PhysicalSide, type Middleware, type FloatingTreeStore } from "../floating-ui-react/index.js"; export type Side = 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'; export type Align = 'start' | 'center' | 'end'; export type Boundary = 'clipping-ancestors' | Element | Element[] | Rect; export type OffsetFunction = (data: { side: Side; align: Align; anchor: { width: number; height: number; }; positioner: { width: number; height: number; }; }) => number; interface SideFlipMode { /** * How to avoid collisions on the side axis. * - `'flip'`: If there is not enough space, place the popup on the opposite side. * - `'none'`: Keep the preferred side even if it overflows. */ side?: 'flip' | 'none' | undefined; /** * How to avoid collisions on the align axis. * - `'flip'`: If there is not enough space, swap `'start'` and `'end'` alignment. * - `'shift'`: Keep the alignment and shift the popup to fit within the boundary. * - `'none'`: Keep the preferred alignment even if it overflows. */ align?: 'flip' | 'shift' | 'none' | undefined; /** * If both sides on the preferred axis do not fit, determines whether to fallback * to a side on the perpendicular axis and which logical side to prefer. * - `'start'`: Prefer the logical start side on the perpendicular axis. * - `'end'`: Prefer the logical end side on the perpendicular axis. * - `'none'`: Do not fallback to the perpendicular axis. */ fallbackAxisSide?: 'start' | 'end' | 'none' | undefined; } interface SideShiftMode { /** * How to avoid collisions on the side axis. * - `'shift'`: Keep the preferred side and shift the popup to fit within the boundary. * - `'none'`: Keep the preferred side even if it overflows. */ side?: 'shift' | 'none' | undefined; /** * How to avoid collisions on the align axis. * - `'shift'`: Keep the alignment and shift the popup to fit within the boundary. * - `'none'`: Keep the preferred alignment even if it overflows. */ align?: 'shift' | 'none' | undefined; /** * If both sides on the preferred axis do not fit, determines whether to fallback * to a side on the perpendicular axis and which logical side to prefer. * - `'start'`: Prefer the logical start side on the perpendicular axis. * - `'end'`: Prefer the logical end side on the perpendicular axis. * - `'none'`: Do not fallback to the perpendicular axis. */ fallbackAxisSide?: 'start' | 'end' | 'none' | undefined; } export type CollisionAvoidance = SideFlipMode | SideShiftMode; /** * Provides standardized anchor positioning behavior for floating elements. Wraps Floating UI's * `useFloating` hook. */ export declare function useAnchorPositioning(params: UseAnchorPositioningParameters): UseAnchorPositioningReturnValue; export interface UseAnchorPositioningSharedParameters { /** * An element to position the popup against. * By default, the popup will be positioned against the trigger. */ anchor?: Element | null | VirtualElement | React.RefObject<Element | null> | (() => Element | VirtualElement | null) | undefined; /** * Determines which CSS `position` property to use. * @default 'absolute' */ positionMethod?: 'absolute' | 'fixed' | undefined; /** * Which side of the anchor element to align the popup against. * May automatically change to avoid collisions. * @default 'bottom' */ side?: Side | undefined; /** * Distance between the anchor and the popup in pixels. * Also accepts a function that returns the distance to read the dimensions of the anchor * and positioner elements, along with its side and alignment. * * The function takes a `data` object parameter with the following properties: * - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. * - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. * - `data.side`: which side of the anchor element the positioner is aligned against. * - `data.align`: how the positioner is aligned relative to the specified side. * * @example * ```jsx * <Positioner * sideOffset={({ side, align, anchor, positioner }) => { * return side === 'top' || side === 'bottom' * ? anchor.height * : anchor.width; * }} * /> * ``` * * @default 0 */ sideOffset?: number | OffsetFunction | undefined; /** * How to align the popup relative to the specified side. * @default 'center' */ align?: Align | undefined; /** * Additional offset along the alignment axis in pixels. * Also accepts a function that returns the offset to read the dimensions of the anchor * and positioner elements, along with its side and alignment. * * The function takes a `data` object parameter with the following properties: * - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. * - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. * - `data.side`: which side of the anchor element the positioner is aligned against. * - `data.align`: how the positioner is aligned relative to the specified side. * * @example * ```jsx * <Positioner * alignOffset={({ side, align, anchor, positioner }) => { * return side === 'top' || side === 'bottom' * ? anchor.width * : anchor.height; * }} * /> * ``` * * @default 0 */ alignOffset?: number | OffsetFunction | undefined; /** * An element or a rectangle that delimits the area that the popup is confined to. * @default 'clipping-ancestors' */ collisionBoundary?: Boundary | undefined; /** * Additional space to maintain from the edge of the collision boundary. * @default 5 */ collisionPadding?: Padding | undefined; /** * Whether to maintain the popup in the viewport after * the anchor element was scrolled out of view. * @default false */ sticky?: boolean | undefined; /** * Minimum distance to maintain between the arrow and the edges of the popup. * * Use it to prevent the arrow element from hanging out of the rounded corners of a popup. * @default 5 */ arrowPadding?: number | undefined; /** * Whether to disable the popup from tracking any layout shift of its positioning anchor. * @default false */ disableAnchorTracking?: boolean | undefined; /** * Determines how to handle collisions when positioning the popup. * * `side` controls overflow on the preferred placement axis (`top`/`bottom` or `left`/`right`): * - `'flip'`: keep the requested side when it fits; otherwise try the opposite side * (`top` and `bottom`, or `left` and `right`). * - `'shift'`: never change side; keep the requested side and move the popup within * the clipping boundary so it stays visible. * - `'none'`: do not correct side-axis overflow. * * `align` controls overflow on the alignment axis (`start`/`center`/`end`): * - `'flip'`: keep side, but swap `start` and `end` when the requested alignment overflows. * - `'shift'`: keep side and requested alignment, then nudge the popup along the * alignment axis to fit. * - `'none'`: do not correct alignment-axis overflow. * * `fallbackAxisSide` controls fallback behavior on the perpendicular axis when the * preferred axis cannot fit: * - `'start'`: allow perpendicular fallback and try the logical start side first * (`top` before `bottom`, or `left` before `right` in LTR). * - `'end'`: allow perpendicular fallback and try the logical end side first * (`bottom` before `top`, or `right` before `left` in LTR). * - `'none'`: do not fallback to the perpendicular axis. * * When `side` is `'shift'`, explicitly setting `align` only supports `'shift'` or `'none'`. * If `align` is omitted, it defaults to `'flip'`. * * @example * ```jsx * <Positioner * collisionAvoidance={{ * side: 'shift', * align: 'shift', * fallbackAxisSide: 'none', * }} * /> * ``` * */ collisionAvoidance?: CollisionAvoidance | undefined; } export interface UseAnchorPositioningParameters extends UseAnchorPositioningSharedParameters { keepMounted?: boolean | undefined; trackCursorAxis?: 'none' | 'x' | 'y' | 'both' | undefined; floatingRootContext?: FloatingRootContext | undefined; mounted: boolean; disableAnchorTracking: boolean; nodeId?: string | undefined; adaptiveOrigin?: Middleware | undefined; collisionAvoidance: CollisionAvoidance; shiftCrossAxis?: boolean | undefined; lazyFlip?: boolean | undefined; externalTree?: FloatingTreeStore | undefined; } export interface UseAnchorPositioningReturnValue { positionerStyles: React.CSSProperties; arrowStyles: React.CSSProperties; arrowRef: React.RefObject<Element | null>; arrowUncentered: boolean; side: Side; align: Align; physicalSide: PhysicalSide; anchorHidden: boolean; refs: ReturnType<typeof useFloating>['refs']; context: FloatingContext; isPositioned: boolean; update: () => void; } export interface UseAnchorPositioningState {} export {};