/
Portal.js
126 lines (107 loc) · 3.39 KB
/
Portal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* Copyright 2019, SumUp Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import exactProp from 'prop-types-exact';
import ownerDocument from '../../util/ownerDocument';
function getContainer(container, defaultContainer) {
// eslint-disable-next-line no-param-reassign
container = typeof container === 'function' ? container() : container;
// eslint-disable-next-line react/no-find-dom-node
return ReactDOM.findDOMNode(container) || defaultContainer;
}
function getOwnerDocument(element) {
// eslint-disable-next-line react/no-find-dom-node
return ownerDocument(ReactDOM.findDOMNode(element));
}
/**
* Portals provide a first-class way to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*/
class Portal extends React.Component {
componentDidMount() {
this.setMountNode(this.props.container);
// Only rerender if needed
if (!this.props.disablePortal) {
this.forceUpdate(this.props.onRendered);
}
}
componentDidUpdate(prevProps) {
if (
prevProps.container !== this.props.container ||
prevProps.disablePortal !== this.props.disablePortal
) {
this.setMountNode(this.props.container);
// Only rerender if needed
if (!this.props.disablePortal) {
this.forceUpdate(this.props.onRendered);
}
}
}
componentWillUnmount() {
this.mountNode = null;
}
setMountNode(container) {
if (this.props.disablePortal) {
// eslint-disable-next-line react/no-find-dom-node
this.mountNode = ReactDOM.findDOMNode(this).parentElement;
return;
}
this.mountNode = getContainer(container, getOwnerDocument(this).body);
}
/**
* @public
*/
getMountNode = () => this.mountNode;
render() {
const { children, disablePortal } = this.props;
if (disablePortal) {
return children;
}
return this.mountNode
? ReactDOM.createPortal(children, this.mountNode)
: null;
}
}
Portal.propTypes = {
/**
* The children to render into the `container`.
*/
children: PropTypes.node.isRequired,
/**
* A node, component instance, or function that returns either.
* The `container` will have the portal children appended to it.
* By default, it uses the body of the top-level document object,
* so it's simply `document.body` most of the time.
*/
container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
/**
* Disable the portal behavior.
* The children stay within it's parent DOM hierarchy.
*/
disablePortal: PropTypes.bool,
/**
* Callback fired once the children has been mounted into the `container`.
*/
onRendered: PropTypes.func
};
Portal.defaultProps = {
disablePortal: false,
container: null,
onRendered: () => {}
};
Portal.propTypes = exactProp(Portal.propTypes);
export default Portal;