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.
169 lines
5.3 KiB
169 lines
5.3 KiB
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; |