UNPKG

semantic-ui-react

Version:
450 lines (373 loc) 17.9 kB
import _objectSpread from "@babel/runtime/helpers/objectSpread"; import _classCallCheck from "@babel/runtime/helpers/classCallCheck"; import _createClass from "@babel/runtime/helpers/createClass"; import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn"; import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf"; import _inherits from "@babel/runtime/helpers/inherits"; import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _invoke from "lodash/invoke"; import keyboardKey from 'keyboard-key'; import PropTypes from 'prop-types'; import React, { Children, cloneElement } from 'react'; import ReactDOM from 'react-dom'; import { AutoControlledComponent as Component, doesNodeContainClick, eventStack, isBrowser, META } from '../../lib'; import Ref from '../Ref'; /** * A component that allows you to render children outside their parent. * @see Modal * @see Popup * @see Dimmer * @see Confirm */ var Portal = /*#__PURE__*/ function (_Component) { _inherits(Portal, _Component); function Portal() { var _getPrototypeOf2; var _temp, _this; _classCallCheck(this, Portal); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _possibleConstructorReturn(_this, (_temp = _this = _possibleConstructorReturn(this, (_getPrototypeOf2 = _getPrototypeOf(Portal)).call.apply(_getPrototypeOf2, [this].concat(args))), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleDocumentClick", function (e) { var _this$props = _this.props, closeOnDocumentClick = _this$props.closeOnDocumentClick, closeOnRootNodeClick = _this$props.closeOnRootNodeClick; if (!_this.rootNode || // not mounted !_this.portalNode || // no portal doesNodeContainClick(_this.triggerNode, e) || // event happened in trigger (delegate to trigger handlers) doesNodeContainClick(_this.portalNode, e) // event happened in the portal ) { return; } // ignore the click var didClickInRootNode = doesNodeContainClick(_this.rootNode, e); if (closeOnDocumentClick && !didClickInRootNode || closeOnRootNodeClick && didClickInRootNode) { _this.close(e); } }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleEscape", function (e) { if (!_this.props.closeOnEscape) return; if (keyboardKey.getCode(e) !== keyboardKey.Escape) return; _this.close(e); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handlePortalMouseLeave", function (e) { var _this$props2 = _this.props, closeOnPortalMouseLeave = _this$props2.closeOnPortalMouseLeave, mouseLeaveDelay = _this$props2.mouseLeaveDelay; if (!closeOnPortalMouseLeave) return; _this.mouseLeaveTimer = _this.closeWithTimeout(e, mouseLeaveDelay); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handlePortalMouseEnter", function () { // In order to enable mousing from the trigger to the portal, we need to // clear the mouseleave timer that was set when leaving the trigger. var closeOnPortalMouseLeave = _this.props.closeOnPortalMouseLeave; if (!closeOnPortalMouseLeave) return; clearTimeout(_this.mouseLeaveTimer); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTriggerBlur", function (e) { var _this$props3 = _this.props, trigger = _this$props3.trigger, closeOnTriggerBlur = _this$props3.closeOnTriggerBlur; // Call original event handler for (var _len2 = arguments.length, rest = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { rest[_key2 - 1] = arguments[_key2]; } _invoke.apply(void 0, [trigger, 'props.onBlur', e].concat(rest)); // do not close if focus is given to the portal var didFocusPortal = _invoke(_assertThisInitialized(_assertThisInitialized(_this)), 'rootNode.contains', e.relatedTarget); if (!closeOnTriggerBlur || didFocusPortal) return; _this.close(e); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTriggerClick", function (e) { var _this$props4 = _this.props, trigger = _this$props4.trigger, closeOnTriggerClick = _this$props4.closeOnTriggerClick, openOnTriggerClick = _this$props4.openOnTriggerClick; var open = _this.state.open; // Call original event handler for (var _len3 = arguments.length, rest = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { rest[_key3 - 1] = arguments[_key3]; } _invoke.apply(void 0, [trigger, 'props.onClick', e].concat(rest)); if (open && closeOnTriggerClick) { _this.close(e); } else if (!open && openOnTriggerClick) { _this.open(e); } }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTriggerFocus", function (e) { var _this$props5 = _this.props, trigger = _this$props5.trigger, openOnTriggerFocus = _this$props5.openOnTriggerFocus; // Call original event handler for (var _len4 = arguments.length, rest = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { rest[_key4 - 1] = arguments[_key4]; } _invoke.apply(void 0, [trigger, 'props.onFocus', e].concat(rest)); if (!openOnTriggerFocus) return; _this.open(e); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTriggerMouseLeave", function (e) { clearTimeout(_this.mouseEnterTimer); var _this$props6 = _this.props, trigger = _this$props6.trigger, closeOnTriggerMouseLeave = _this$props6.closeOnTriggerMouseLeave, mouseLeaveDelay = _this$props6.mouseLeaveDelay; // Call original event handler for (var _len5 = arguments.length, rest = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) { rest[_key5 - 1] = arguments[_key5]; } _invoke.apply(void 0, [trigger, 'props.onMouseLeave', e].concat(rest)); if (!closeOnTriggerMouseLeave) return; _this.mouseLeaveTimer = _this.closeWithTimeout(e, mouseLeaveDelay); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleTriggerMouseEnter", function (e) { clearTimeout(_this.mouseLeaveTimer); var _this$props7 = _this.props, trigger = _this$props7.trigger, mouseEnterDelay = _this$props7.mouseEnterDelay, openOnTriggerMouseEnter = _this$props7.openOnTriggerMouseEnter; // Call original event handler for (var _len6 = arguments.length, rest = new Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) { rest[_key6 - 1] = arguments[_key6]; } _invoke.apply(void 0, [trigger, 'props.onMouseEnter', e].concat(rest)); if (!openOnTriggerMouseEnter) return; _this.mouseEnterTimer = _this.openWithTimeout(e, mouseEnterDelay); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "open", function (e) { var onOpen = _this.props.onOpen; if (onOpen) onOpen(e, _this.props); _this.trySetState({ open: true }); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "openWithTimeout", function (e, delay) { // React wipes the entire event object and suggests using e.persist() if // you need the event for async access. However, even with e.persist // certain required props (e.g. currentTarget) are null so we're forced to clone. var eventClone = _objectSpread({}, e); return setTimeout(function () { return _this.open(eventClone); }, delay || 0); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "close", function (e) { var onClose = _this.props.onClose; if (onClose) onClose(e, _this.props); _this.trySetState({ open: false }); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "closeWithTimeout", function (e, delay) { // React wipes the entire event object and suggests using e.persist() if // you need the event for async access. However, even with e.persist // certain required props (e.g. currentTarget) are null so we're forced to clone. var eventClone = _objectSpread({}, e); return setTimeout(function () { return _this.close(eventClone); }, delay || 0); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "attachRenderSubTreeSubscribers", function (eventPool) { // Prevent race condition bug // https://github.com/Semantic-Org/Semantic-UI-React/issues/2401 if (!_this.rootNode) return null; _this.portalNode = _this.rootNode.firstElementChild; eventStack.sub('mouseleave', _this.handlePortalMouseLeave, { pool: eventPool, target: _this.portalNode }); eventStack.sub('mouseenter', _this.handlePortalMouseEnter, { pool: eventPool, target: _this.portalNode }); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "mountPortal", function () { if (!isBrowser() || _this.rootNode) return; var _this$props8 = _this.props, eventPool = _this$props8.eventPool, _this$props8$mountNod = _this$props8.mountNode, mountNode = _this$props8$mountNod === void 0 ? isBrowser() ? document.body : null : _this$props8$mountNod, prepend = _this$props8.prepend; _this.rootNode = document.createElement('div'); if (prepend) { mountNode.insertBefore(_this.rootNode, mountNode.firstElementChild); } else { mountNode.appendChild(_this.rootNode); } eventStack.sub('click', _this.handleDocumentClick, { pool: eventPool }); eventStack.sub('keydown', _this.handleEscape, { pool: eventPool }); _invoke(_this.props, 'onMount', null, _this.props); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "unmountPortal", function () { if (!isBrowser() || !_this.rootNode) return; var eventPool = _this.props.eventPool; ReactDOM.unmountComponentAtNode(_this.rootNode); _this.rootNode.parentNode.removeChild(_this.rootNode); eventStack.unsub('mouseleave', _this.handlePortalMouseLeave, { pool: eventPool, target: _this.portalNode }); eventStack.unsub('mouseenter', _this.handlePortalMouseEnter, { pool: eventPool, target: _this.portalNode }); _this.rootNode = null; _this.portalNode = null; eventStack.unsub('click', _this.handleDocumentClick, { pool: eventPool }); eventStack.unsub('keydown', _this.handleEscape, { pool: eventPool }); _invoke(_this.props, 'onUnmount', null, _this.props); }), _defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "handleRef", function (c) { return _this.triggerNode = c; }), _temp)); } _createClass(Portal, [{ key: "componentDidMount", value: function componentDidMount() { this.renderPortal(); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { // NOTE: Ideally the portal rendering would happen in the render() function // but React gives a warning about not being pure and suggests doing it // within this method. // If the portal is open, render (or re-render) the portal and child. this.renderPortal(); if (prevState.open && !this.state.open) { this.unmountPortal(); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.unmountPortal(); // Clean up timers clearTimeout(this.mouseEnterTimer); clearTimeout(this.mouseLeaveTimer); } // ---------------------------------------- // Document Event Handlers // ---------------------------------------- }, { key: "renderPortal", value: function renderPortal() { var _this2 = this; if (!this.state.open) return; var _this$props9 = this.props, children = _this$props9.children, className = _this$props9.className, eventPool = _this$props9.eventPool; this.mountPortal(); // Server side rendering if (!isBrowser()) return null; this.rootNode.className = className || ''; // when re-rendering, first remove listeners before re-adding them to the new node if (this.portalNode) { eventStack.unsub('mouseleave', this.handlePortalMouseLeave, { pool: eventPool, target: this.portalNode }); eventStack.unsub('mouseenter', this.handlePortalMouseEnter, { pool: eventPool, target: this.portalNode }); } ReactDOM.unstable_renderSubtreeIntoContainer(this, Children.only(children), this.rootNode, function () { return _this2.attachRenderSubTreeSubscribers(eventPool); }); } }, { key: "render", value: function render() { var trigger = this.props.trigger; if (!trigger) return null; return React.createElement(Ref, { innerRef: this.handleRef }, cloneElement(trigger, { onBlur: this.handleTriggerBlur, onClick: this.handleTriggerClick, onFocus: this.handleTriggerFocus, onMouseLeave: this.handleTriggerMouseLeave, onMouseEnter: this.handleTriggerMouseEnter })); } }]); return Portal; }(Component); _defineProperty(Portal, "defaultProps", { closeOnDocumentClick: true, closeOnEscape: true, eventPool: 'default', openOnTriggerClick: true }); _defineProperty(Portal, "autoControlledProps", ['open']); _defineProperty(Portal, "_meta", { name: 'Portal', type: META.TYPES.ADDON }); _defineProperty(Portal, "handledProps", ["children", "className", "closeOnDocumentClick", "closeOnEscape", "closeOnPortalMouseLeave", "closeOnRootNodeClick", "closeOnTriggerBlur", "closeOnTriggerClick", "closeOnTriggerMouseLeave", "defaultOpen", "eventPool", "mountNode", "mouseEnterDelay", "mouseLeaveDelay", "onClose", "onMount", "onOpen", "onUnmount", "open", "openOnTriggerClick", "openOnTriggerFocus", "openOnTriggerMouseEnter", "prepend", "trigger"]); Portal.propTypes = process.env.NODE_ENV !== "production" ? { /** Primary content. */ children: PropTypes.node.isRequired, /** Additional classes. */ className: PropTypes.string, /** Controls whether or not the portal should close when the document is clicked. */ closeOnDocumentClick: PropTypes.bool, /** Controls whether or not the portal should close when escape is pressed is displayed. */ closeOnEscape: PropTypes.bool, /** * Controls whether or not the portal should close when mousing out of the portal. * NOTE: This will prevent `closeOnTriggerMouseLeave` when mousing over the * gap from the trigger to the portal. */ closeOnPortalMouseLeave: PropTypes.bool, /** * Controls whether or not the portal should close on a click on the portal background. * NOTE: This differs from closeOnDocumentClick: * - DocumentClick - any click not within the portal * - RootNodeClick - a click not within the portal but within the portal's wrapper */ closeOnRootNodeClick: PropTypes.bool, /** Controls whether or not the portal should close on blur of the trigger. */ closeOnTriggerBlur: PropTypes.bool, /** Controls whether or not the portal should close on click of the trigger. */ closeOnTriggerClick: PropTypes.bool, /** Controls whether or not the portal should close when mousing out of the trigger. */ closeOnTriggerMouseLeave: PropTypes.bool, /** Initial value of open. */ defaultOpen: PropTypes.bool, /** Event pool namespace that is used to handle component events */ eventPool: PropTypes.string, /** The node where the portal should mount. */ mountNode: PropTypes.any, /** Milliseconds to wait before opening on mouse over */ mouseEnterDelay: PropTypes.number, /** Milliseconds to wait before closing on mouse leave */ mouseLeaveDelay: PropTypes.number, /** * Called when a close event happens * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onClose: PropTypes.func, /** * Called when the portal is mounted on the DOM * * @param {null} * @param {object} data - All props. */ onMount: PropTypes.func, /** * Called when an open event happens * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onOpen: PropTypes.func, /** * Called when the portal is unmounted from the DOM * * @param {null} * @param {object} data - All props. */ onUnmount: PropTypes.func, /** Controls whether or not the portal is displayed. */ open: PropTypes.bool, /** Controls whether or not the portal should open when the trigger is clicked. */ openOnTriggerClick: PropTypes.bool, /** Controls whether or not the portal should open on focus of the trigger. */ openOnTriggerFocus: PropTypes.bool, /** Controls whether or not the portal should open when mousing over the trigger. */ openOnTriggerMouseEnter: PropTypes.bool, /** Controls whether the portal should be prepended to the mountNode instead of appended. */ prepend: PropTypes.bool, /** Element to be rendered in-place where the portal is defined. */ trigger: PropTypes.node } : {}; export default Portal;