UNPKG

2.29 kBJavaScriptView Raw
1import { createElement, render } from 'preact';
2
3/**
4 * @param {import('../../src/index').RenderableProps<{ context: any }>} props
5 */
6function ContextProvider(props) {
7 this.getChildContext = () => props.context;
8 return props.children;
9}
10
11/**
12 * Portal component
13 * @this {import('./internal').Component}
14 * @param {object | null | undefined} props
15 *
16 * TODO: use createRoot() instead of fake root
17 */
18function Portal(props) {
19 const _this = this;
20 let container = props._container;
21
22 _this.componentWillUnmount = function() {
23 render(null, _this._temp);
24 _this._temp = null;
25 _this._container = null;
26 };
27
28 // When we change container we should clear our old container and
29 // indicate a new mount.
30 if (_this._container && _this._container !== container) {
31 _this.componentWillUnmount();
32 }
33
34 // When props.vnode is undefined/false/null we are dealing with some kind of
35 // conditional vnode. This should not trigger a render.
36 if (props._vnode) {
37 if (!_this._temp) {
38 _this._container = container;
39
40 // Create a fake DOM parent node that manages a subset of `container`'s children:
41 _this._temp = {
42 nodeType: 1,
43 parentNode: container,
44 childNodes: [],
45 appendChild(child) {
46 this.childNodes.push(child);
47 _this._container.appendChild(child);
48 },
49 insertBefore(child, before) {
50 this.childNodes.push(child);
51 _this._container.appendChild(child);
52 },
53 removeChild(child) {
54 this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1);
55 _this._container.removeChild(child);
56 }
57 };
58 }
59
60 // Render our wrapping element into temp.
61 render(
62 createElement(ContextProvider, { context: _this.context }, props._vnode),
63 _this._temp
64 );
65 }
66 // When we come from a conditional render, on a mounted
67 // portal we should clear the DOM.
68 else if (_this._temp) {
69 _this.componentWillUnmount();
70 }
71}
72
73/**
74 * Create a `Portal` to continue rendering the vnode tree at a different DOM node
75 * @param {import('./internal').VNode} vnode The vnode to render
76 * @param {import('./internal').PreactElement} container The DOM node to continue rendering in to.
77 */
78export function createPortal(vnode, container) {
79 const el = createElement(Portal, { _vnode: vnode, _container: container });
80 el.containerInfo = container;
81 return el;
82}