1 | import { createElement, hydrate, render, __u as _unmount } from 'preact';
|
2 |
|
3 | function 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 | */
|
14 | function 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 | */
|
87 | export function createPortal(vnode, container) {
|
88 | return createElement(Portal, { _vnode: vnode, _container: container });
|
89 | }
|