UNPKG

@appbuckets/react-ui-core

Version:

Core utilities built for AppBuckets React UI Framework

458 lines (452 loc) 13.2 kB
'use strict'; var tslib = require('tslib'); var React = require('react'); var index = require('../useAutoControlledValue/index.js'); var index$1 = require('../useDOMElementEvent/index.js'); var Ref = require('../Ref/Ref.js'); var refUtils = require('../utils/refUtils.js'); var doesNodeContainClick = require('../utils/doesNodeContainClick.js'); var PortalInner = require('./PortalInner.js'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; }, } ); } }); } n['default'] = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/ _interopNamespace(React); var Portal = function (props) { var _a, _b, _c, _d, _e; var children = props.children, closeOnDocumentClick = props.closeOnDocumentClick, closeOnEscape = props.closeOnEscape, closeOnPortalMouseLeave = props.closeOnPortalMouseLeave, closeOnTriggerBlur = props.closeOnTriggerBlur, closeOnTriggerClick = props.closeOnTriggerClick, closeOnTriggerMouseLeave = props.closeOnTriggerMouseLeave, userDefinedDefaultOpen = props.defaultOpen, mountNode = props.mountNode, mouseEnterDelay = props.mouseEnterDelay, mouseLeaveDelay = props.mouseLeaveDelay, userDefinedOpen = props.open, openOnTriggerClick = props.openOnTriggerClick, openOnTriggerFocus = props.openOnTriggerFocus, openOnTriggerMouseEnter = props.openOnTriggerMouseEnter, trigger = props.trigger, userDefinedTriggerRef = props.triggerRef, userDefinedOnCloseHandler = props.onClose, userDefinedOnMountHandler = props.onMount, userDefinedOnOpenHandler = props.onOpen, userDefinedOnUnmountHandler = props.onUnmount; // ---- // AutoControlled Props // ---- var _f = tslib.__read( index(false, { defaultProp: userDefinedDefaultOpen, prop: userDefinedOpen, }), 2 ), open = _f[0], trySetOpen = _f[1]; // ---- // DOM Ref // ---- var contentRef = React__namespace.useRef(); var triggerRef = React__namespace.useRef(); // ---- // Internal Variables // ---- var latestDocumentMouseEvent = React__namespace.useRef(null); var mouseEnterTimer = React__namespace.useRef(null); var mouseLeaveTimer = React__namespace.useRef(null); // ---- // Lifecycle event to cancel timer // ---- var clearMouseEnterTimer = React__namespace.useCallback(function () { if (mouseEnterTimer.current) { clearTimeout(mouseEnterTimer.current); mouseEnterTimer.current = null; } }, []); var clearMouseLeaveTimer = React__namespace.useCallback(function () { if (mouseEnterTimer.current) { clearTimeout(mouseEnterTimer.current); mouseEnterTimer.current = null; } }, []); React__namespace.useEffect( function () { return function () { clearMouseEnterTimer(); clearMouseLeaveTimer(); }; }, [clearMouseEnterTimer, clearMouseLeaveTimer] ); // ---- // Portal Control // ---- var openPortal = React__namespace.useCallback( function (e) { /** Avoid event propagation */ e.stopPropagation(); if (typeof userDefinedOnOpenHandler === 'function') { userDefinedOnOpenHandler(e); } trySetOpen(true); }, [userDefinedOnOpenHandler, trySetOpen] ); var openPortalWithTimeout = React__namespace.useCallback( function (e) { return setTimeout(function () { return openPortal(e); }, mouseEnterDelay); }, [openPortal, mouseEnterDelay] ); var closePortal = React__namespace.useCallback( function (e) { /** Avoid event propagation */ e.stopPropagation(); if (typeof userDefinedOnCloseHandler === 'function') { userDefinedOnCloseHandler(e); } trySetOpen(false); }, [userDefinedOnCloseHandler, trySetOpen] ); var closePortalWithTimeout = React__namespace.useCallback( function (e) { return setTimeout( function () { return closePortal(e); }, mouseLeaveDelay !== null && mouseLeaveDelay !== void 0 ? mouseLeaveDelay : 0 ); }, [closePortal, mouseLeaveDelay] ); // ---- // Trigger Handler // ---- var handleTriggerRef = function (component) { triggerRef.current = component; refUtils.handleRef(userDefinedTriggerRef, component); }; var triggerOnBlurHandler = (_a = trigger === null || trigger === void 0 ? void 0 : trigger.props) === null || _a === void 0 ? void 0 : _a.onBlur; var handleTriggerBlur = React__namespace.useCallback( function (e) { var _a; var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } /** Invoke original trigger event handler */ if (typeof triggerOnBlurHandler === 'function') { triggerOnBlurHandler.apply( void 0, tslib.__spreadArray([e], tslib.__read(rest), false) ); } /** Do not close if focus is given to the portal */ var target = e.relatedTarget || document.activeElement; var didFocusPortal = (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.contains(target); if (!closeOnTriggerBlur || didFocusPortal) { return; } closePortal(e); }, [triggerOnBlurHandler, closeOnTriggerBlur, closePortal] ); var triggerOnClickHandler = (_b = trigger === null || trigger === void 0 ? void 0 : trigger.props) === null || _b === void 0 ? void 0 : _b.onClick; var handleTriggerClick = React__namespace.useCallback( function (e) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } /** Invoke original trigger event handler */ if (typeof triggerOnClickHandler === 'function') { triggerOnClickHandler.apply( void 0, tslib.__spreadArray([e], tslib.__read(rest), false) ); } /** Toggle the Portal */ if (open && closeOnTriggerClick) { closePortal(e); } else if (!open && openOnTriggerClick) { openPortal(e); } }, [ closeOnTriggerClick, closePortal, open, openOnTriggerClick, openPortal, triggerOnClickHandler, ] ); var triggerOnFocusHandler = (_c = trigger === null || trigger === void 0 ? void 0 : trigger.props) === null || _c === void 0 ? void 0 : _c.onFocus; var handleTriggerFocus = React__namespace.useCallback( function (e) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } /** Invoke original trigger event handler */ if (typeof triggerOnFocusHandler === 'function') { triggerOnFocusHandler.apply( void 0, tslib.__spreadArray([e], tslib.__read(rest), false) ); } if (!openOnTriggerFocus) { return; } openPortal(e); }, [openOnTriggerFocus, openPortal, triggerOnFocusHandler] ); var triggerOnMouseEnterHandler = (_d = trigger === null || trigger === void 0 ? void 0 : trigger.props) === null || _d === void 0 ? void 0 : _d.onMouseEnter; var handleTriggerMouseEnter = React__namespace.useCallback( function (e) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } /** Remove mouse leave timer */ clearMouseLeaveTimer(); /** Invoke original trigger event handler */ if (typeof triggerOnMouseEnterHandler === 'function') { triggerOnMouseEnterHandler.apply( void 0, tslib.__spreadArray([e], tslib.__read(rest), false) ); } if (!openOnTriggerMouseEnter) { return; } mouseEnterTimer.current = openPortalWithTimeout(e); }, [ clearMouseLeaveTimer, openOnTriggerMouseEnter, openPortalWithTimeout, triggerOnMouseEnterHandler, ] ); var triggerOnMouseLeaveHandler = (_e = trigger === null || trigger === void 0 ? void 0 : trigger.props) === null || _e === void 0 ? void 0 : _e.onMouseLeave; var handleTriggerMouseLeave = React__namespace.useCallback( function (e) { var rest = []; for (var _i = 1; _i < arguments.length; _i++) { rest[_i - 1] = arguments[_i]; } /** Remove mouse enter timer */ clearMouseEnterTimer(); /** Invoke original trigger event handler */ if (typeof triggerOnMouseLeaveHandler === 'function') { triggerOnMouseLeaveHandler.apply( void 0, tslib.__spreadArray([e], tslib.__read(rest), false) ); } if (!closeOnTriggerMouseLeave) { return; } mouseLeaveTimer.current = closePortalWithTimeout(e); }, [ clearMouseEnterTimer, closeOnTriggerMouseLeave, closePortalWithTimeout, triggerOnMouseLeaveHandler, ] ); // ---- // Document and DOM Event // ---- var handleDocumentMouseDown = React__namespace.useCallback(function (e) { latestDocumentMouseEvent.current = e; }, []); index$1({ disabled: !closeOnDocumentClick, event: 'mousedown', callback: handleDocumentMouseDown, }); var handleDocumentClick = React__namespace.useCallback( function (e) { var currentMouseDownEvent = latestDocumentMouseEvent.current; latestDocumentMouseEvent.current = null; var triggerElement = triggerRef.current; var contentElement = contentRef.current; /** * Ignore the click if there's no Portal * or the event happened in trigger, or the event * is originated in Portal but ended outside, or the event * happened in the portal */ if ( !contentElement || doesNodeContainClick.doesNodeContainClick(triggerElement, e) || (currentMouseDownEvent && doesNodeContainClick.doesNodeContainClick( contentElement, currentMouseDownEvent )) || doesNodeContainClick.doesNodeContainClick(contentElement, e) ) { return; } if (closeOnDocumentClick) { closePortal(e); } }, [closeOnDocumentClick, closePortal] ); index$1({ disabled: !closeOnDocumentClick, event: 'click', callback: handleDocumentClick, }); var handleEscapeKey = React__namespace.useCallback( function (e) { if (!closeOnEscape) { return; } if (e.key === 'Escape') { closePortal(e); } }, [closeOnEscape, closePortal] ); index$1({ disabled: !closeOnEscape, event: 'keydown', callback: handleEscapeKey, }); // ---- // Portal Mouse Event Handler // ---- var handlePortalMouseEnter = React__namespace.useCallback( function () { if (!closeOnPortalMouseLeave) { return; } clearMouseLeaveTimer(); }, [closeOnPortalMouseLeave, clearMouseLeaveTimer] ); index$1({ disabled: !closeOnPortalMouseLeave, target: contentRef.current, event: 'mouseenter', callback: handlePortalMouseEnter, }); var handlePortalMouseLeave = React__namespace.useCallback( function (e) { if (!closeOnPortalMouseLeave) { return; } if (e.target !== contentRef.current) { return; } mouseLeaveTimer.current = closePortalWithTimeout(e); }, [closeOnPortalMouseLeave, closePortalWithTimeout] ); index$1({ disabled: !closeOnPortalMouseLeave, target: contentRef.current, event: 'mouseleave', callback: handlePortalMouseLeave, }); // ---- // Component Render // ---- return React__namespace.createElement( React__namespace.Fragment, null, open && React__namespace.createElement( PortalInner, { innerRef: contentRef, mountNode: mountNode, onMount: userDefinedOnMountHandler, onUnmount: userDefinedOnUnmountHandler, }, children ), trigger && React__namespace.createElement( Ref, { innerRef: handleTriggerRef }, React__namespace.cloneElement(trigger, { onBlur: handleTriggerBlur, onClick: openOnTriggerClick || closeOnTriggerClick || triggerOnClickHandler ? handleTriggerClick : undefined, onFocus: handleTriggerFocus, onMouseEnter: handleTriggerMouseEnter, onMouseLeave: handleTriggerMouseLeave, }) ) ); }; Portal.displayName = 'Portal'; Portal.defaultProps = { closeOnDocumentClick: true, closeOnEscape: true, openOnTriggerClick: true, }; module.exports = Portal;