UNPKG

5.97 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2022 Palantir Technologies, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.Portal2 = void 0;
19var tslib_1 = require("tslib");
20/**
21 * @fileoverview This is the next version of <Portal>, reimplemented as a function component.
22 *
23 * It supports both the newer React context API and the legacy context API. Support for the legacy context API
24 * will be removed in Blueprint v6.0.
25 *
26 * Portal2 is not currently used anywhere in Blueprint. We had to revert the change which updated the standard
27 * <Portal> to use this implementation because of subtle breaks caused by interactions with the (long-deprecated)
28 * react-hot-loader library. To be safe, we've left Portal as a class component for now, and will promote this Portal2
29 * implementation to be the standard Portal in Blueprint v5.0.
30 *
31 * @see https://github.com/palantir/blueprint/issues/5511
32 */
33var React = tslib_1.__importStar(require("react"));
34var ReactDOM = tslib_1.__importStar(require("react-dom"));
35var Classes = tslib_1.__importStar(require("../../common/classes"));
36var Errors = tslib_1.__importStar(require("../../common/errors"));
37var props_1 = require("../../common/props");
38var portalProvider_1 = require("../../context/portal/portalProvider");
39var usePrevious_1 = require("../../hooks/usePrevious");
40var REACT_CONTEXT_TYPES = {
41 blueprintPortalClassName: function (obj, key) {
42 if (obj[key] != null && typeof obj[key] !== "string") {
43 return new Error(Errors.PORTAL_CONTEXT_CLASS_NAME_STRING);
44 }
45 return undefined;
46 },
47};
48/**
49 * Portal (v2) component.
50 *
51 * This component detaches its contents and re-attaches them to document.body.
52 * Use it when you need to circumvent DOM z-stacking (for dialogs, popovers, etc.).
53 * Any class names passed to this element will be propagated to the new container element on document.body.
54 */
55function Portal2(props, legacyContext) {
56 if (legacyContext === void 0) { legacyContext = {}; }
57 var context = React.useContext(portalProvider_1.PortalContext);
58 var _a = React.useState(false), hasMounted = _a[0], setHasMounted = _a[1];
59 var _b = React.useState(), portalElement = _b[0], setPortalElement = _b[1];
60 var createContainerElement = React.useCallback(function () {
61 var container = document.createElement("div");
62 container.classList.add(Classes.PORTAL);
63 maybeAddClass(container.classList, props.className); // directly added to this portal element
64 maybeAddClass(container.classList, context.portalClassName); // added via PortalProvider context
65 var blueprintPortalClassName = legacyContext.blueprintPortalClassName;
66 if (blueprintPortalClassName != null && blueprintPortalClassName !== "") {
67 console.error(Errors.PORTAL_LEGACY_CONTEXT_API);
68 maybeAddClass(container.classList, blueprintPortalClassName); // added via legacy context
69 }
70 return container;
71 }, [props.className, context.portalClassName]);
72 // create the container element & attach it to the DOM
73 React.useEffect(function () {
74 if (props.container == null) {
75 return;
76 }
77 var newPortalElement = createContainerElement();
78 props.container.appendChild(newPortalElement);
79 setPortalElement(newPortalElement);
80 setHasMounted(true);
81 return function () {
82 newPortalElement.remove();
83 setHasMounted(false);
84 setPortalElement(undefined);
85 };
86 }, [props.container, createContainerElement]);
87 // wait until next successful render to invoke onChildrenMount callback
88 React.useEffect(function () {
89 var _a;
90 if (hasMounted) {
91 (_a = props.onChildrenMount) === null || _a === void 0 ? void 0 : _a.call(props);
92 }
93 }, [hasMounted, props.onChildrenMount]);
94 // update className prop on portal DOM element when props change
95 var prevClassName = (0, usePrevious_1.usePrevious)(props.className);
96 React.useEffect(function () {
97 if (portalElement != null) {
98 maybeRemoveClass(portalElement.classList, prevClassName);
99 maybeAddClass(portalElement.classList, props.className);
100 }
101 }, [props.className]);
102 // Only render `children` once this component has mounted in a browser environment, so they are
103 // immediately attached to the DOM tree and can do DOM things like measuring or `autoFocus`.
104 // See long comment on componentDidMount in https://reactjs.org/docs/portals.html#event-bubbling-through-portals
105 if (typeof document === "undefined" || !hasMounted || portalElement == null) {
106 return null;
107 }
108 else {
109 return ReactDOM.createPortal(props.children, portalElement);
110 }
111}
112exports.Portal2 = Portal2;
113Portal2.defaultProps = {
114 container: typeof document !== "undefined" ? document.body : undefined,
115};
116Portal2.displayName = "".concat(props_1.DISPLAYNAME_PREFIX, ".Portal2");
117Portal2.contextTypes = REACT_CONTEXT_TYPES;
118function maybeRemoveClass(classList, className) {
119 if (className != null && className !== "") {
120 classList.remove.apply(classList, className.split(" "));
121 }
122}
123function maybeAddClass(classList, className) {
124 if (className != null && className !== "") {
125 classList.add.apply(classList, className.split(" "));
126 }
127}
128//# sourceMappingURL=portal2.js.map
\No newline at end of file