@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
TypeScript
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 {};