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
			| 
								 
											3 years ago
										 
									 | 
							
								// @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,
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								};
							 |