UNPKG

3.24 kBJavaScriptView Raw
1import { createElement, hydrate, render, __u as _unmount } from 'preact';
2
3function ContextProvider(props) {
4 this.getChildContext = () => props.context;
5 return props.children;
6}
7
8/**
9 * Portal component
10 * @param {object | null | undefined} props
11 *
12 * TODO: this could use the "fake root node" trick from the partial hydration demo
13 */
14function Portal(props) {
15 const _this = this;
16 let container = props._container;
17 let wrap = createElement(
18 ContextProvider,
19 { context: _this.context },
20 props._vnode
21 );
22
23 _this.componentWillUnmount = function() {
24 let parent = _this._temp.parentNode;
25 if (parent) parent.removeChild(_this._temp);
26 _unmount(_this._wrap);
27 };
28
29 // When we change container we should clear our old container and
30 // indicate a new mount.
31 if (_this._container && _this._container !== container) {
32 _this.componentWillUnmount();
33 // if (_this._temp.parentNode) _this._container.removeChild(_this._temp);
34 // _unmount(_this._wrap);
35 _this._hasMounted = false;
36 }
37
38 // When props.vnode is undefined/false/null we are dealing with some kind of
39 // conditional vnode. This should not trigger a render.
40 if (props._vnode) {
41 if (!_this._hasMounted) {
42 // Create a placeholder that we can use to insert into.
43 _this._temp = document.createTextNode('');
44 // temporarily store the current children of the container to restore them after render
45 _this._children = container._children;
46 // Hydrate existing nodes to keep the dom intact, when rendering
47 // wrap into the container.
48 hydrate('', container);
49 // Append to the container (this matches React's behavior)
50 container.appendChild(_this._temp);
51 // At this point we have mounted and should set our container.
52 _this._hasMounted = true;
53 _this._container = container;
54 // Render our wrapping element into temp.
55 render(wrap, container, _this._temp);
56 // restore the previous children of the container
57 container._children = _this._children;
58 // store the children of the new vnode to be used in subsequent re-renders
59 _this._children = _this._temp._children;
60 } else {
61 // When we have mounted and the vnode is present it means the
62 // props have changed or a parent is triggering a rerender.
63 // This implies we only need to call render. But we need to keep
64 // the old tree around, otherwise will treat the vnodes as new and
65 // will wrongly call `componentDidMount` on them
66 container._children = _this._children;
67 render(wrap, container);
68 _this._children = container._children;
69 }
70 }
71 // When we come from a conditional render, on a mounted
72 // portal we should clear the DOM.
73 else if (_this._hasMounted) {
74 _this.componentWillUnmount();
75 // if (_this._temp.parentNode) _this._container.removeChild(_this._temp);
76 // _unmount(_this._wrap);
77 }
78 // Set the wrapping element for future unmounting.
79 _this._wrap = wrap;
80}
81
82/**
83 * Create a `Portal` to continue rendering the vnode tree at a different DOM node
84 * @param {import('./internal').VNode} vnode The vnode to render
85 * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to.
86 */
87export function createPortal(vnode, container) {
88 return createElement(Portal, { _vnode: vnode, _container: container });
89}