UNPKG

@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
"use strict"; 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