UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

101 lines (75 loc) 3.14 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.Portal = exports.default = void 0; var _react = require("react"); var _reactDom = require("react-dom"); var _lodash = _interopRequireDefault(require("lodash.isnil")); var _Provider = require("../../components/HSDS/Provider"); var _usePortal = require("./usePortal.utils"); /* istanbul ignore file */ /** * Automatically handles creating and tearing-down the root elements (no SRR * makes this trivial), so there is no need to ensure the parent target already * exists. * @param {String} selector A valid selector for the target container, e.g '#modal' or '.spotlight' * @returns {HTMLElement} The DOM node to use as the Portal target. */ function usePortal(selector) { var rootElemRef = (0, _react.useRef)(null); var _ref = (0, _react.useContext)(_Provider.GlobalContext) || {}, getCurrentScope = _ref.getCurrentScope; var scope = getCurrentScope ? getCurrentScope() : null; (0, _react.useEffect)(function setupElement() { var parentElem; if (!selector) { parentElem = document.body; parentElem.classList.add(scope); } else { // Look for existing target dom element to append to var existingParent = document.querySelector(selector); // Parent is either a new root or the existing dom element parentElem = existingParent || (0, _usePortal.createRootElement)(selector); if (scope && (0, _lodash.default)(parentElem.closest("." + scope))) { parentElem.classList.add(scope); } // If there is no existing DOM element, add a new one. if (!existingParent) { (0, _usePortal.addRootElement)(parentElem); } } // Add the detached element to the parent parentElem.appendChild(rootElemRef.current); return function removeElement() { rootElemRef.current.remove(); if (!parentElem.childElementCount) { parentElem.remove(); } }; }, [selector, scope]); /** * It's important we evaluate this lazily: * - We import { createPortal } from 'react-dom' need first render to contain the DOM element, so it shouldn't happen * in useEffect. We would normally put this in the constructor(). * - We can't do 'const rootElemRef = useRef(document.createElement('div))', * since this will run every single render (that's a lot). * - We want the ref to consistently point to the same DOM element and only * ever run once. * @link https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily */ function getRootElem() { if (!rootElemRef.current) { rootElemRef.current = document.createElement('div'); } return rootElemRef.current; } return getRootElem(); } var _default = usePortal; /** A convenience <Portal> component */ exports.default = _default; var Portal = function Portal(_ref2) { var selector = _ref2.selector, children = _ref2.children; var target = usePortal(selector); return /*#__PURE__*/(0, _reactDom.createPortal)(children, target); }; exports.Portal = Portal;