const _excluded = ["enabled", "placement", "strategy", "modifiers"]; function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { dequal } from 'dequal'; import useSafeState from '@restart/hooks/useSafeState'; import { createPopper } from './popper'; const disabledApplyStylesModifier = { name: 'applyStyles', enabled: false, phase: 'afterWrite', fn: () => undefined }; // until docjs supports type exports... const ariaDescribedByModifier = { name: 'ariaDescribedBy', enabled: true, phase: 'afterWrite', effect: ({ state }) => () => { 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 }) => { var _popper$getAttribute; const { popper, reference } = state.elements; const role = (_popper$getAttribute = popper.getAttribute('role')) == null ? void 0 : _popper$getAttribute.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 = []; /** * Position an element relative some reference element using Popper.js * * @param referenceElement * @param popperElement * @param {object} options * @param {object=} options.modifiers Popper.js modifiers * @param {boolean=} options.enabled toggle the popper functionality on/off * @param {string=} options.placement The popper element placement relative to the reference element * @param {string=} options.strategy the positioning strategy * @param {function=} options.onCreate called when the popper is created * @param {function=} options.onUpdate called when the popper is updated * * @returns {UsePopperState} The popper state */ function usePopper(referenceElement, popperElement, _ref = {}) { let { enabled = true, placement = 'bottom', strategy = 'absolute', modifiers = EMPTY_MODIFIERS } = _ref, config = _objectWithoutPropertiesLoose(_ref, _excluded); const prevModifiers = useRef(modifiers); const popperInstanceRef = useRef(); const update = useCallback(() => { var _popperInstanceRef$cu; (_popperInstanceRef$cu = popperInstanceRef.current) == null ? void 0 : _popperInstanceRef$cu.update(); }, []); const forceUpdate = useCallback(() => { var _popperInstanceRef$cu2; (_popperInstanceRef$cu2 = popperInstanceRef.current) == null ? void 0 : _popperInstanceRef$cu2.forceUpdate(); }, []); const [popperState, setState] = useSafeState(useState({ placement, update, forceUpdate, attributes: {}, styles: { popper: {}, arrow: {} } })); const updateModifier = useMemo(() => ({ 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]); const nextModifiers = useMemo(() => { if (!dequal(prevModifiers.current, modifiers)) { prevModifiers.current = modifiers; } return prevModifiers.current; }, [modifiers]); useEffect(() => { if (!popperInstanceRef.current || !enabled) return; popperInstanceRef.current.setOptions({ placement, strategy, modifiers: [...nextModifiers, updateModifier, disabledApplyStylesModifier] }); }, [strategy, placement, updateModifier, enabled, nextModifiers]); useEffect(() => { if (!enabled || referenceElement == null || popperElement == null) { return undefined; } popperInstanceRef.current = createPopper(referenceElement, popperElement, Object.assign({}, config, { placement, strategy, modifiers: [...nextModifiers, ariaDescribedByModifier, updateModifier] })); return () => { if (popperInstanceRef.current != null) { popperInstanceRef.current.destroy(); popperInstanceRef.current = undefined; setState(s => Object.assign({}, s, { attributes: {}, styles: { popper: {} } })); } }; // This is only run once to _create_ the popper // eslint-disable-next-line react-hooks/exhaustive-deps }, [enabled, referenceElement, popperElement]); return popperState; } export default usePopper;