import * as React from 'react'; import { type Ref, useMemo } from 'react'; import { type OnPlacementChange, useFloatingMiddlewaresBootstrap, type UseFloatingMiddlewaresBootstrapOptions, useFloatingWithInteractions, type UseFloatingWithInteractionsProps, type UseFloatingWithInteractionsReturn, usePlacementChangeCallback, } from '../lib/floating'; import { type ReferenceProps } from '../lib/floating/useFloatingWithInteractions/types'; import { useReferenceHiddenChangeCallback } from '../lib/floating/useReferenceHiddenChangeCallback'; import { useExternRef } from './useExternRef'; import { useGlobalEscKeyDown } from './useGlobalEscKeyDown'; export type FloatingComponentProps = Pick< UseFloatingWithInteractionsReturn, | 'shown' | 'willBeHide' | 'floatingProps' | 'middlewareData' | 'onClose' | 'onRestoreFocus' | 'placement' > & { floatingRef: React.Ref; setArrowRef: React.Dispatch>; }; export type RenderFloatingComponentFn = ( props: FloatingComponentProps, ) => React.ReactNode | null; export type RemapReferencePropsFn = ( props: ReferenceProps & { shown: boolean }, ) => ReferenceProps; export type UseFloatingElementProps< FloatingElement extends HTMLElement = HTMLElement, ReferenceElement extends HTMLElement = HTMLElement, > = Omit & Omit & { onPlacementChange?: OnPlacementChange; onReferenceHiddenChange?: (hidden: boolean) => void; renderFloatingComponent: RenderFloatingComponentFn; remapReferenceProps?: RemapReferencePropsFn; externalFloatingElementRef?: React.Ref; }; export type UseFloatingResult = { anchorRef: Ref; anchorProps: ReferenceProps; component: React.ReactNode | null; }; export const useFloatingElement = < ReferenceElement extends HTMLElement = HTMLElement, FloatingElement extends HTMLElement = HTMLElement, >({ // useFloatingMiddlewaresBootstrap placement = 'bottom-start', arrow, arrowHeight, arrowPadding, sameWidth, offsetByMainAxis = 0, offsetByCrossAxis = 0, customMiddlewares, hideWhenReferenceHidden, disableFlipMiddleware = false, disableShiftMiddleware = false, overflowPadding, // useFloatingWithInteractions trigger, hoverDelay, closeAfterClick, disabled, disableInteractive, disableCloseOnClickOutside, disableCloseOnEscKey, defaultShown, shown: shownProp, onShownChange, onShownChanged, strategy, onReferenceHiddenChange, onPlacementChange, renderFloatingComponent, externalFloatingElementRef, remapReferenceProps, }: UseFloatingElementProps< FloatingElement, ReferenceElement >): UseFloatingResult => { const [arrowRef, setArrowRef] = React.useState(null); const { middlewares, strictPlacement } = useFloatingMiddlewaresBootstrap({ placement, offsetByMainAxis, offsetByCrossAxis, customMiddlewares, hideWhenReferenceHidden, sameWidth, arrow, arrowRef, arrowPadding, arrowHeight, disableFlipMiddleware, disableShiftMiddleware, overflowPadding, }); const { placement: resolvedPlacement, shown, willBeHide, refs, referenceProps, floatingProps, middlewareData, onClose, onRestoreFocus, onEscapeKeyDown, } = useFloatingWithInteractions({ middlewares, strategy, placement: strictPlacement, trigger, hoverDelay, closeAfterClick, disabled, disableInteractive, disableCloseOnClickOutside, disableCloseOnEscKey, defaultShown, shown: shownProp, onShownChange, onShownChanged, }); const resultRef = useExternRef(externalFloatingElementRef, refs.setFloating); usePlacementChangeCallback(placement, resolvedPlacement, onPlacementChange); useReferenceHiddenChangeCallback(middlewareData.hide, onReferenceHiddenChange); const component = renderFloatingComponent({ shown, willBeHide, floatingProps, floatingRef: resultRef, middlewareData, placement: resolvedPlacement, onClose, onRestoreFocus, setArrowRef, }); useGlobalEscKeyDown(shown, onEscapeKeyDown); const remappedReferenceProps = useMemo( () => remapReferenceProps ? remapReferenceProps({ ...referenceProps, shown }) : referenceProps, [remapReferenceProps, shown, referenceProps], ); return { anchorRef: refs.setReference, anchorProps: remappedReferenceProps, component, }; };