@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
101 lines (75 loc) • 3.14 kB
JavaScript
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;
;