import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ; import useSafeState from '@restart/hooks/useSafeState' ; import * as Popper from '@popperjs/core' ; import { createPopper } from './popper' ; const initialPopperStyles = ( position ) : Partial < CSSStyleDeclaration > => ( { position , top : '0' , left : '0' , opacity : '0' , pointerEvents : 'none' , } ) ; const disabledApplyStylesModifier = { name : 'applyStyles' , enabled : false } ; export type Modifier < Name , Options > = Popper . Modifier < Name , Options > ; export type Options = Popper . Options ; export type Instance = Popper . Instance ; export type Placement = Popper . Placement ; export type VirtualElement = Popper . VirtualElement ; export type State = Popper . State ; export type OffsetValue = [ number | null | undefined , number | null | undefined ] ; export type OffsetFunction = ( details : { popper : Popper . Rect ; reference : Popper . Rect ; placement : Placement ; } ) => OffsetValue ; export type Offset = OffsetFunction | OffsetValue ; export type ModifierMap = Record < string , Partial < Modifier < any , any > > > ; export type Modifiers = Popper . Options [ 'modifiers' ] | Record < string , Partial < Modifier < any , any > > > ; export type UsePopperOptions = Omit < Options , 'modifiers' | 'placement' | 'strategy' > & { enabled ? : boolean ; placement ? : Options [ 'placement' ] ; strategy ? : Options [ 'strategy' ] ; modifiers ? : Options [ 'modifiers' ] ; } ; export interface UsePopperState { placement : Placement ; update : ( ) => void ; forceUpdate : ( ) => void ; attributes : Record < string , Record < string , any > > ; styles : Record < string , Partial < CSSStyleDeclaration > > ; state ? : State ; } const ariaDescribedByModifier = { name : 'ariaDescribedBy' , enabled : true , phase : 'afterWrite' , effect : ( { state } ) => { return ( ) => { const { reference , popper } = state . elements ; if ( 'removeAttribute' in reference ) { const ids = ( reference . getAttribute ( 'aria-describedby' ) || '' ) . split ( ',' ) . filter ( ( id ) => id . trim ( ) !== popper . id ) ; if ( ! ids . length ) reference . removeAttribute ( 'aria-describedby' ) ; else reference . setAttribute ( 'aria-describedby' , ids . join ( ',' ) ) ; } } ; } , fn : ( { state } ) => { const { popper , reference } = state . elements ; const role = popper . getAttribute ( 'role' ) ?. toLowerCase ( ) ; if ( popper . id && role === 'tooltip' && 'setAttribute' in reference ) { const ids = reference . getAttribute ( 'aria-describedby' ) ; if ( ids && ids . split ( ',' ) . indexOf ( popper . id ) !== - 1 ) { return ; } reference . setAttribute ( 'aria-describedby' , ids ? `${ ids },${ popper . id }` : popper . id ) ; } } , } ; const EMPTY_MODIFIERS = [ ] as any ; param referenceElement param popperElement param options param { object = } options . modifiers param { boolean = } options . enabled param { string = } options . placement param { string = } options . strategy param { boolean = } options . eventsEnabled param { function = } options . onCreate param { function = } options . onUpdate returns { UsePopperState } function usePopper ( referenceElement , popperElement , { enabled = true , placement = 'bottom' , strategy = 'absolute' , modifiers = EMPTY_MODIFIERS , ... config } = { } ) { const popperInstanceRef = useRef < Instance > ( ) ; const update = useCallback ( ( ) => { popperInstanceRef . current ?. update ( ) ; } , [ ] ) ; const forceUpdate = useCallback ( ( ) => { popperInstanceRef . current ?. forceUpdate ( ) ; } , [ ] ) ; const [ popperState , setState ] = useSafeState ( useState < UsePopperState > ( { placement , update , forceUpdate , attributes : { } , styles : { popper : initialPopperStyles ( strategy ) , arrow : { } , } , } ) ) ; const updateModifier = useMemo < Modifier < 'updateStateModifier' , any > > ( ( ) => ( { name : 'updateStateModifier' , enabled : true , phase : 'write' , requires : [ 'computeStyles' ] , fn : ( { state } ) => { const styles = { } ; const attributes = { } ; Object . keys ( state . elements ) . forEach ( ( element ) => { styles [ element ] = state . styles [ element ] ; attributes [ element ] = state . attributes [ element ] ; } ) ; setState ( { state , styles , attributes , update , forceUpdate , placement : state . placement , } ) ; } , } ) , [ update , forceUpdate , setState ] ) ; useEffect ( ( ) => { if ( ! popperInstanceRef . current || ! enabled ) return ; popperInstanceRef . current . setOptions ( { placement , strategy , modifiers : [ ... modifiers , updateModifier , disabledApplyStylesModifier ] , } ) ; } , [ strategy , placement , updateModifier , enabled ] ) ; useEffect ( ( ) => { if ( ! enabled || referenceElement == null || popperElement == null ) { return undefined ; } popperInstanceRef . current = createPopper ( referenceElement , popperElement , { ... config , placement , strategy , modifiers : [ ... modifiers , ariaDescribedByModifier , updateModifier ] , } ) ; return ( ) => { if ( popperInstanceRef . current != null ) { popperInstanceRef . current . destroy ( ) ; popperInstanceRef . current = undefined ; setState ( ( s ) => ( { ... s , attributes : { } , styles : { popper : initialPopperStyles ( strategy ) } , } ) ) ; } } ; } , [ enabled , referenceElement , popperElement ] ) ; return popperState ; } export default usePopper ;