@patternfly/react-core
Version:
This library provides a set of common React components for use with the PatternFly reference implementation.
300 lines • 15.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.Popper = exports.getOpacityTransition = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const ReactDOM = tslib_1.__importStar(require("react-dom"));
const usePopper_1 = require("./thirdparty/react-popper/usePopper");
const util_1 = require("../util");
const react_styles_1 = require("@patternfly/react-styles");
require("@patternfly/react-styles/css/components/Popper/Popper.css");
const util_2 = require("../util");
const hash = {
left: 'right',
right: 'left',
bottom: 'top',
top: 'bottom',
'top-start': 'bottom-end',
'top-end': 'bottom-start',
'bottom-start': 'top-end',
'bottom-end': 'top-start',
'left-start': 'right-end',
'left-end': 'right-start',
'right-start': 'left-end',
'right-end': 'left-start'
};
const getOppositePlacement = (placement) => placement.replace(/left|right|bottom|top|top-start|top-end|bottom-start|bottom-end|right-start|right-end|left-start|left-end/g, (matched) => hash[matched]);
const getOpacityTransition = (animationDuration) => `opacity ${animationDuration}ms cubic-bezier(.54, 1.5, .38, 1.11)`;
exports.getOpacityTransition = getOpacityTransition;
const Popper = ({ trigger, popper, direction = 'down', position = 'start', placement, width, minWidth = 'trigger', maxWidth, appendTo = () => document.body, zIndex = 9999, isVisible = true, positionModifiers, distance = 0, onMouseEnter, onMouseLeave, onFocus, onBlur, onDocumentClick, onTriggerClick, onTriggerEnter, onPopperClick, onPopperMouseEnter, onPopperMouseLeave, onDocumentKeyDown, enableFlip = true, flipBehavior = 'flip', triggerRef, popperRef, animationDuration = 0, entryDelay = 0, exitDelay = 0, onHidden = () => { }, onHide = () => { }, onMount = () => { }, onShow = () => { }, onShown = () => { }, preventOverflow = false }) => {
var _a;
const [triggerElement, setTriggerElement] = (0, react_1.useState)(null);
const [refElement, setRefElement] = (0, react_1.useState)(null);
const [popperElement, setPopperElement] = (0, react_1.useState)(null);
const [popperContent, setPopperContent] = (0, react_1.useState)(null);
const [ready, setReady] = (0, react_1.useState)(false);
const [opacity, setOpacity] = (0, react_1.useState)(0);
const [internalIsVisible, setInternalIsVisible] = (0, react_1.useState)(isVisible);
const transitionTimerRef = (0, react_1.useRef)(null);
const showTimerRef = (0, react_1.useRef)(null);
const hideTimerRef = (0, react_1.useRef)(null);
const prevExitDelayRef = (0, react_1.useRef)(undefined);
const refOrTrigger = refElement || triggerElement;
const showPopper = isVisible || internalIsVisible;
const triggerParent = (_a = ((triggerRef === null || triggerRef === void 0 ? void 0 : triggerRef.current) || triggerElement)) === null || _a === void 0 ? void 0 : _a.parentElement;
const languageDirection = (0, util_2.getLanguageDirection)(triggerParent);
const internalPosition = (0, react_1.useMemo)(() => {
const fixedPositions = { left: 'left', right: 'right', center: 'center' };
const positionMap = {
ltr: Object.assign({ start: 'left', end: 'right' }, fixedPositions),
rtl: Object.assign({ start: 'right', end: 'left' }, fixedPositions)
};
return positionMap[languageDirection][position];
}, [position, languageDirection]);
const onDocumentClickCallback = (0, react_1.useCallback)((event) => onDocumentClick(event, refOrTrigger, popperElement), [showPopper, triggerElement, refElement, popperElement, onDocumentClick]);
(0, react_1.useEffect)(() => {
setReady(true);
onMount();
}, []);
// Cancel all timers on unmount
(0, react_1.useEffect)(() => () => {
(0, util_1.clearTimeouts)([transitionTimerRef, hideTimerRef, showTimerRef]);
}, []);
(0, react_1.useEffect)(() => {
if (triggerRef) {
if (triggerRef.current) {
setRefElement(triggerRef.current);
}
else if (typeof triggerRef === 'function') {
setRefElement(triggerRef());
}
}
}, [triggerRef, trigger]);
(0, react_1.useEffect)(() => {
// When the popperRef is defined or the popper visibility changes, ensure the popper element is up to date
if (popperRef) {
if (popperRef.current) {
setPopperElement(popperRef.current);
}
else if (typeof popperRef === 'function') {
setPopperElement(popperRef());
}
}
}, [showPopper, popperRef]);
(0, react_1.useEffect)(() => {
// Trigger a Popper update when content changes.
const observer = new MutationObserver(() => {
update && update();
});
popperElement && observer.observe(popperElement, { attributes: true, childList: true, subtree: true });
return () => {
observer.disconnect();
};
}, [popperElement]);
const addEventListener = (listener, element, event, capture = false) => {
if (listener && element) {
element.addEventListener(event, listener, { capture });
}
};
const removeEventListener = (listener, element, event, capture = false) => {
if (listener && element) {
element.removeEventListener(event, listener, { capture });
}
};
(0, react_1.useEffect)(() => {
addEventListener(onMouseEnter, refOrTrigger, 'mouseenter');
addEventListener(onMouseLeave, refOrTrigger, 'mouseleave');
addEventListener(onFocus, refOrTrigger, 'focus');
addEventListener(onBlur, refOrTrigger, 'blur');
addEventListener(onTriggerClick, refOrTrigger, 'click');
addEventListener(onTriggerEnter, refOrTrigger, 'keydown');
addEventListener(onPopperClick, popperElement, 'click');
addEventListener(onPopperMouseEnter, popperElement, 'mouseenter');
addEventListener(onPopperMouseLeave, popperElement, 'mouseleave');
onDocumentClick && addEventListener(onDocumentClickCallback, document, 'click', true);
addEventListener(onDocumentKeyDown, document, 'keydown', true);
return () => {
removeEventListener(onMouseEnter, refOrTrigger, 'mouseenter');
removeEventListener(onMouseLeave, refOrTrigger, 'mouseleave');
removeEventListener(onFocus, refOrTrigger, 'focus');
removeEventListener(onBlur, refOrTrigger, 'blur');
removeEventListener(onTriggerClick, refOrTrigger, 'click');
removeEventListener(onTriggerEnter, refOrTrigger, 'keydown');
removeEventListener(onPopperClick, popperElement, 'click');
removeEventListener(onPopperMouseEnter, popperElement, 'mouseenter');
removeEventListener(onPopperMouseLeave, popperElement, 'mouseleave');
onDocumentClick && removeEventListener(onDocumentClickCallback, document, 'click', true);
removeEventListener(onDocumentKeyDown, document, 'keydown', true);
};
}, [
triggerElement,
popperElement,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
onTriggerClick,
onTriggerEnter,
onPopperClick,
onPopperMouseEnter,
onPopperMouseLeave,
onDocumentClick,
onDocumentKeyDown,
refElement
]);
const getPlacement = () => {
if (placement) {
return placement;
}
let convertedPlacement = direction === 'up' ? 'top' : 'bottom';
if (internalPosition !== 'center') {
convertedPlacement = `${convertedPlacement}-${internalPosition === 'right' ? 'end' : 'start'}`;
}
return convertedPlacement;
};
const getPlacementMemo = (0, react_1.useMemo)(getPlacement, [direction, internalPosition, placement]);
const getOppositePlacementMemo = (0, react_1.useMemo)(() => getOppositePlacement(getPlacement()), [direction, internalPosition, placement]);
const widthMods = (0, react_1.useMemo)(() => ({
name: 'widthMods',
enabled: width !== undefined || minWidth !== undefined || maxWidth !== undefined,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
const triggerWidth = state.rects.reference.width;
if (width) {
state.styles.popper.width = width === 'trigger' ? `${triggerWidth}px` : width;
}
if (minWidth) {
state.styles.popper.minWidth = minWidth === 'trigger' ? `${triggerWidth}px` : minWidth;
}
if (maxWidth) {
state.styles.popper.maxWidth = maxWidth === 'trigger' ? `${triggerWidth}px` : maxWidth;
}
},
effect: ({ state }) => {
const triggerWidth = state.elements.reference.offsetWidth;
if (width) {
state.elements.popper.style.width = width === 'trigger' ? `${triggerWidth}px` : width;
}
if (minWidth) {
state.elements.popper.style.minWidth = minWidth === 'trigger' ? `${triggerWidth}px` : minWidth;
}
if (maxWidth) {
state.elements.popper.style.maxWidth = maxWidth === 'trigger' ? `${triggerWidth}px` : maxWidth;
}
return () => { };
}
}), [width, minWidth, maxWidth]);
const { styles: popperStyles, attributes, update, forceUpdate } = (0, usePopper_1.usePopper)(refOrTrigger, popperElement, {
placement: getPlacementMemo,
modifiers: [
{
name: 'offset',
options: {
offset: [0, distance]
}
},
{
name: 'preventOverflow',
enabled: preventOverflow
},
{
// adds attribute [data-popper-reference-hidden] to the popper element which can be used to hide it using CSS
name: 'hide',
enabled: true
},
{
name: 'flip',
enabled: getPlacementMemo.startsWith('auto') || enableFlip,
options: {
fallbackPlacements: flipBehavior === 'flip' ? [getOppositePlacementMemo] : flipBehavior
}
},
widthMods
]
});
/** We want to forceUpdate only when a tooltip's content is dynamically updated.
* TODO: Investigate into 3rd party libraries for a less limited/specific solution
*/
(0, react_1.useEffect)(() => {
var _a, _b, _c, _d, _e, _f, _g;
// currentPopperContent = {tooltip children} || {dropdown children}
const currentPopperContent = ((_d = (_c = (_b = (_a = popper === null || popper === void 0 ? void 0 : popper.props) === null || _a === void 0 ? void 0 : _a.children) === null || _b === void 0 ? void 0 : _b[1]) === null || _c === void 0 ? void 0 : _c.props) === null || _d === void 0 ? void 0 : _d.children) || ((_g = (_f = (_e = popper === null || popper === void 0 ? void 0 : popper.props) === null || _e === void 0 ? void 0 : _e.children) === null || _f === void 0 ? void 0 : _f.props) === null || _g === void 0 ? void 0 : _g.children);
setPopperContent(currentPopperContent);
if (currentPopperContent && popperContent && currentPopperContent !== popperContent) {
forceUpdate && forceUpdate();
}
}, [popper]);
(0, react_1.useEffect)(() => {
if (prevExitDelayRef.current < exitDelay) {
(0, util_1.clearTimeouts)([transitionTimerRef, hideTimerRef]);
hideTimerRef.current = setTimeout(() => {
transitionTimerRef.current = setTimeout(() => {
setInternalIsVisible(false);
}, animationDuration);
}, exitDelay);
}
prevExitDelayRef.current = exitDelay;
}, [exitDelay]);
const show = () => {
onShow();
(0, util_1.clearTimeouts)([transitionTimerRef, hideTimerRef]);
showTimerRef.current = setTimeout(() => {
setInternalIsVisible(true);
setOpacity(1);
onShown();
}, entryDelay);
};
const hide = () => {
onHide();
(0, util_1.clearTimeouts)([showTimerRef]);
hideTimerRef.current = setTimeout(() => {
setOpacity(0);
transitionTimerRef.current = setTimeout(() => {
setInternalIsVisible(false);
onHidden();
}, animationDuration);
}, exitDelay);
};
(0, react_1.useEffect)(() => {
if (isVisible) {
show();
}
else {
hide();
}
}, [isVisible]);
// Returns the CSS modifier class in order to place the Popper's arrow properly
// Depends on the position of the Popper relative to the reference element
const modifierFromPopperPosition = () => {
if (attributes && attributes.popper && attributes.popper['data-popper-placement']) {
const popperPlacement = attributes.popper['data-popper-placement'];
return positionModifiers[popperPlacement];
}
return positionModifiers.top;
};
const options = Object.assign({ className: (0, react_styles_1.css)(popper.props && popper.props.className, positionModifiers && modifierFromPopperPosition()), style: Object.assign(Object.assign(Object.assign({}, ((popper.props && popper.props.style) || {})), popperStyles.popper), { zIndex,
opacity, transition: (0, exports.getOpacityTransition)(animationDuration) }) }, attributes.popper);
const getMenuWithPopper = () => {
const localPopper = (0, react_1.cloneElement)(popper, options);
return popperRef ? (localPopper) : ((0, jsx_runtime_1.jsx)("div", { style: { display: 'contents' }, ref: (node) => {
setPopperElement(node === null || node === void 0 ? void 0 : node.firstElementChild);
}, children: localPopper }));
};
const getPopper = () => {
if (appendTo === 'inline') {
return getMenuWithPopper();
}
else {
const target = typeof appendTo === 'function' ? appendTo() : appendTo;
return ReactDOM.createPortal(getMenuWithPopper(), target);
}
};
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!triggerRef && trigger && (0, react_1.isValidElement)(trigger) && ((0, jsx_runtime_1.jsx)("div", { style: { display: 'contents' }, ref: (node) => {
setTriggerElement(node === null || node === void 0 ? void 0 : node.firstElementChild);
}, children: trigger })), triggerRef && trigger && (0, react_1.isValidElement)(trigger) && trigger, ready && showPopper && getPopper()] }));
};
exports.Popper = Popper;
exports.Popper.displayName = 'Popper';
//# sourceMappingURL=Popper.js.map
;