You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							158 lines
						
					
					
						
							4.0 KiB
						
					
					
				
			
		
		
	
	
							158 lines
						
					
					
						
							4.0 KiB
						
					
					
				// @flow strict
 | 
						|
import * as React from 'react';
 | 
						|
import * as ReactDOM from 'react-dom';
 | 
						|
import {
 | 
						|
  createPopper as defaultCreatePopper,
 | 
						|
  type Options as PopperOptions,
 | 
						|
  type VirtualElement,
 | 
						|
  type State as PopperState,
 | 
						|
  type Instance as PopperInstance,
 | 
						|
} from '@popperjs/core';
 | 
						|
import isEqual from 'react-fast-compare';
 | 
						|
import { fromEntries, useIsomorphicLayoutEffect } from './utils';
 | 
						|
 | 
						|
type Options = $Shape<{
 | 
						|
  ...PopperOptions,
 | 
						|
  createPopper: typeof defaultCreatePopper,
 | 
						|
}>;
 | 
						|
 | 
						|
type Styles = {
 | 
						|
  [key: string]: $Shape<CSSStyleDeclaration>,
 | 
						|
};
 | 
						|
 | 
						|
type Attributes = {
 | 
						|
  [key: string]: { [key: string]: string },
 | 
						|
};
 | 
						|
 | 
						|
type State = {
 | 
						|
  styles: Styles,
 | 
						|
  attributes: Attributes,
 | 
						|
};
 | 
						|
 | 
						|
const EMPTY_MODIFIERS = [];
 | 
						|
 | 
						|
type UsePopperResult = $ReadOnly<{
 | 
						|
  state: ?PopperState,
 | 
						|
  styles: Styles,
 | 
						|
  attributes: Attributes,
 | 
						|
  update: ?$PropertyType<PopperInstance, 'update'>,
 | 
						|
  forceUpdate: ?$PropertyType<PopperInstance, 'forceUpdate'>,
 | 
						|
}>;
 | 
						|
 | 
						|
export const usePopper = (
 | 
						|
  referenceElement: ?(Element | VirtualElement),
 | 
						|
  popperElement: ?HTMLElement,
 | 
						|
  options: Options = {}
 | 
						|
): UsePopperResult => {
 | 
						|
  const prevOptions = React.useRef<?PopperOptions>(null);
 | 
						|
 | 
						|
  const optionsWithDefaults = {
 | 
						|
    onFirstUpdate: options.onFirstUpdate,
 | 
						|
    placement: options.placement || 'bottom',
 | 
						|
    strategy: options.strategy || 'absolute',
 | 
						|
    modifiers: options.modifiers || EMPTY_MODIFIERS,
 | 
						|
  };
 | 
						|
 | 
						|
  const [state, setState] = React.useState<State>({
 | 
						|
    styles: {
 | 
						|
      popper: {
 | 
						|
        position: optionsWithDefaults.strategy,
 | 
						|
        left: '0',
 | 
						|
        top: '0',
 | 
						|
      },
 | 
						|
      arrow: {
 | 
						|
        position: 'absolute',
 | 
						|
      },
 | 
						|
    },
 | 
						|
    attributes: {},
 | 
						|
  });
 | 
						|
 | 
						|
  const updateStateModifier = React.useMemo(
 | 
						|
    () => ({
 | 
						|
      name: 'updateState',
 | 
						|
      enabled: true,
 | 
						|
      phase: 'write',
 | 
						|
      fn: ({ state }) => {
 | 
						|
        const elements = Object.keys(state.elements);
 | 
						|
 | 
						|
        ReactDOM.flushSync(() => {
 | 
						|
          setState({
 | 
						|
            styles: fromEntries(
 | 
						|
              elements.map((element) => [element, state.styles[element] || {}])
 | 
						|
            ),
 | 
						|
            attributes: fromEntries(
 | 
						|
              elements.map((element) => [element, state.attributes[element]])
 | 
						|
            ),
 | 
						|
          });
 | 
						|
        });
 | 
						|
      },
 | 
						|
      requires: ['computeStyles'],
 | 
						|
    }),
 | 
						|
    []
 | 
						|
  );
 | 
						|
 | 
						|
  const popperOptions = React.useMemo(() => {
 | 
						|
    const newOptions = {
 | 
						|
      onFirstUpdate: optionsWithDefaults.onFirstUpdate,
 | 
						|
      placement: optionsWithDefaults.placement,
 | 
						|
      strategy: optionsWithDefaults.strategy,
 | 
						|
      modifiers: [
 | 
						|
        ...optionsWithDefaults.modifiers,
 | 
						|
        updateStateModifier,
 | 
						|
        { name: 'applyStyles', enabled: false },
 | 
						|
      ],
 | 
						|
    };
 | 
						|
 | 
						|
    if (isEqual(prevOptions.current, newOptions)) {
 | 
						|
      return prevOptions.current || newOptions;
 | 
						|
    } else {
 | 
						|
      prevOptions.current = newOptions;
 | 
						|
      return newOptions;
 | 
						|
    }
 | 
						|
  }, [
 | 
						|
    optionsWithDefaults.onFirstUpdate,
 | 
						|
    optionsWithDefaults.placement,
 | 
						|
    optionsWithDefaults.strategy,
 | 
						|
    optionsWithDefaults.modifiers,
 | 
						|
    updateStateModifier,
 | 
						|
  ]);
 | 
						|
 | 
						|
  const popperInstanceRef = React.useRef();
 | 
						|
 | 
						|
  useIsomorphicLayoutEffect(() => {
 | 
						|
    if (popperInstanceRef.current) {
 | 
						|
      popperInstanceRef.current.setOptions(popperOptions);
 | 
						|
    }
 | 
						|
  }, [popperOptions]);
 | 
						|
 | 
						|
  useIsomorphicLayoutEffect(() => {
 | 
						|
    if (referenceElement == null || popperElement == null) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const createPopper = options.createPopper || defaultCreatePopper;
 | 
						|
    const popperInstance = createPopper(
 | 
						|
      referenceElement,
 | 
						|
      popperElement,
 | 
						|
      popperOptions
 | 
						|
    );
 | 
						|
 | 
						|
    popperInstanceRef.current = popperInstance;
 | 
						|
 | 
						|
    return () => {
 | 
						|
      popperInstance.destroy();
 | 
						|
      popperInstanceRef.current = null;
 | 
						|
    };
 | 
						|
  }, [referenceElement, popperElement, options.createPopper]);
 | 
						|
 | 
						|
  return {
 | 
						|
    state: popperInstanceRef.current ? popperInstanceRef.current.state : null,
 | 
						|
    styles: state.styles,
 | 
						|
    attributes: state.attributes,
 | 
						|
    update: popperInstanceRef.current ? popperInstanceRef.current.update : null,
 | 
						|
    forceUpdate: popperInstanceRef.current
 | 
						|
      ? popperInstanceRef.current.forceUpdate
 | 
						|
      : null,
 | 
						|
  };
 | 
						|
};
 |