UNPKG

@breakaway/react-core

Version:

This library provides a set of common React components for use with the PatternFly reference implementation.

221 lines 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Popper = exports.getOpacityTransition = void 0; const tslib_1 = require("tslib"); const React = tslib_1.__importStar(require("react")); const ReactDOM = tslib_1.__importStar(require("react-dom")); const usePopper_1 = require("./thirdparty/react-popper/usePopper"); const react_styles_1 = require("@breakaway/react-styles"); const FindRefWrapper_1 = require("./FindRefWrapper"); require("@breakaway/react-styles/css/components/Popper/Popper.module.css"); 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, popperMatchesTriggerWidth = true, direction = 'down', position = 'left', placement, 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', reference, removeFindDomNode = false, popperRef }) => { const [triggerElement, setTriggerElement] = React.useState(null); const [refElement, setRefElement] = React.useState(null); const [popperElement, setPopperElement] = React.useState(null); const [ready, setReady] = React.useState(false); const refOrTrigger = refElement || triggerElement; const onDocumentClickCallback = React.useCallback((event) => onDocumentClick(event, refOrTrigger, popperElement), [isVisible, triggerElement, refElement, popperElement, onDocumentClick]); React.useEffect(() => { setReady(true); }, []); React.useEffect(() => { if (reference) { if (reference.current) { setRefElement(reference.current); } else if (typeof reference === 'function') { setRefElement(reference()); } } }, [reference]); React.useEffect(() => { // When the popperRef is defined or the popper visiblity changes, ensure the popper element is up to date if (popperRef) { if (popperRef.current) { setPopperElement(popperRef.current); } else if (typeof popperRef === 'function') { setPopperElement(popperRef()); } } }, [isVisible, popperRef]); 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 }); } }; React.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); // Trigger a Popper update when content changes. const observer = new MutationObserver(() => { update && update(); }); popperElement && observer.observe(popperElement, { attributes: true, childList: true, subtree: 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); observer.disconnect(); }; }, [ 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 (position !== 'center') { convertedPlacement = `${convertedPlacement}-${position === 'right' ? 'end' : 'start'}`; } return convertedPlacement; }; const getPlacementMemo = React.useMemo(getPlacement, [direction, position, placement]); const getOppositePlacementMemo = React.useMemo(() => getOppositePlacement(getPlacement()), [ direction, position, placement ]); const sameWidthMod = React.useMemo(() => ({ name: 'sameWidth', enabled: popperMatchesTriggerWidth, phase: 'beforeWrite', requires: ['computeStyles'], fn: ({ state }) => { state.styles.popper.width = `${state.rects.reference.width}px`; }, effect: ({ state }) => { state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`; return () => { }; } }), [popperMatchesTriggerWidth]); const { styles: popperStyles, attributes, update } = usePopper_1.usePopper(refOrTrigger, popperElement, { placement: getPlacementMemo, modifiers: [ { name: 'offset', options: { offset: [0, distance] } }, { name: 'preventOverflow', enabled: false }, { // 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 } }, sameWidthMod ] }); // 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: 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 }) }, attributes.popper); const menuWithPopper = React.cloneElement(popper, options); const getTarget = () => { if (typeof appendTo === 'function') { return appendTo(); } return appendTo; }; /** * To enable strict mode, the popper must either have its reference defined via the popperRef property, * or the removeFindDomNode flag must be present. Even if the reference is passed in, unlike the trigger, * the popper property must still be passed. * The trigger must similarly have either its reference defined via the reference property, or the * removeFindDomNode flag must be present. The trigger property is not required when the reference is passed. * * Strict mode may be enabled by passing both reference properties to Popper, or by passing the * removeFindDomNode flag with either reference property, or by solely passing the removeFindDomNode * flag. */ let popperPortal; if (removeFindDomNode) { // If removeFindDomNode is passed, use the removeFindDomNode method of wrapping divs popperPortal = React.createElement("div", { ref: node => setPopperElement(node === null || node === void 0 ? void 0 : node.firstElementChild) }, menuWithPopper); } else if (popperRef) { // If removeFindDomNode is not passed and popperRef is passed, use the popperRef method popperPortal = menuWithPopper; } else { // If neither removeFindDomNode and popperRef exist, use the old method of FindRefWrapper popperPortal = (React.createElement(FindRefWrapper_1.FindRefWrapper, { onFoundRef: (foundRef) => setPopperElement(foundRef) }, menuWithPopper)); } return (React.createElement(React.Fragment, null, !reference && trigger && React.isValidElement(trigger) && !removeFindDomNode && (React.createElement(FindRefWrapper_1.FindRefWrapper, { onFoundRef: (foundRef) => setTriggerElement(foundRef) }, trigger)), !reference && trigger && React.isValidElement(trigger) && removeFindDomNode && (React.createElement("div", { ref: node => setTriggerElement(node === null || node === void 0 ? void 0 : node.firstElementChild) }, trigger)), ready && isVisible && ReactDOM.createPortal(popperPortal, getTarget()))); }; exports.Popper = Popper; exports.Popper.displayName = 'Popper'; //# sourceMappingURL=Popper.js.map