101 lines
2.5 KiB
JavaScript
101 lines
2.5 KiB
JavaScript
|
// @ts-nocheck
|
||
|
|
||
|
/**
|
||
|
* External dependencies
|
||
|
*/
|
||
|
import { createElement, render } from 'preact';
|
||
|
|
||
|
/**
|
||
|
* @param {import('../../src/index').RenderableProps<{ context: any }>} props
|
||
|
*/
|
||
|
function ContextProvider( props ) {
|
||
|
this.getChildContext = () => props.context;
|
||
|
return props.children;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Portal component
|
||
|
*
|
||
|
* @this {import('./internal').Component}
|
||
|
* @param {object | null | undefined} props
|
||
|
*
|
||
|
* TODO: use createRoot() instead of fake root
|
||
|
*/
|
||
|
function Portal( props ) {
|
||
|
const _this = this;
|
||
|
const container = props._container;
|
||
|
|
||
|
_this.componentWillUnmount = function () {
|
||
|
render( null, _this._temp );
|
||
|
_this._temp = null;
|
||
|
_this._container = null;
|
||
|
};
|
||
|
|
||
|
// When we change container we should clear our old container and
|
||
|
// indicate a new mount.
|
||
|
if ( _this._container && _this._container !== container ) {
|
||
|
_this.componentWillUnmount();
|
||
|
}
|
||
|
|
||
|
// When props.vnode is undefined/false/null we are dealing with some kind of
|
||
|
// conditional vnode. This should not trigger a render.
|
||
|
if ( props._vnode ) {
|
||
|
if ( ! _this._temp ) {
|
||
|
_this._container = container;
|
||
|
|
||
|
// Create a fake DOM parent node that manages a subset of `container`'s children:
|
||
|
_this._temp = {
|
||
|
nodeType: 1,
|
||
|
parentNode: container,
|
||
|
childNodes: [],
|
||
|
appendChild( child ) {
|
||
|
this.childNodes.push( child );
|
||
|
_this._container.appendChild( child );
|
||
|
},
|
||
|
insertBefore( child ) {
|
||
|
this.childNodes.push( child );
|
||
|
_this._container.appendChild( child );
|
||
|
},
|
||
|
removeChild( child ) {
|
||
|
this.childNodes.splice(
|
||
|
// eslint-disable-next-line no-bitwise
|
||
|
this.childNodes.indexOf( child ) >>> 1,
|
||
|
1
|
||
|
);
|
||
|
_this._container.removeChild( child );
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// Render our wrapping element into temp.
|
||
|
render(
|
||
|
createElement(
|
||
|
ContextProvider,
|
||
|
{ context: _this.context },
|
||
|
props._vnode
|
||
|
),
|
||
|
_this._temp
|
||
|
);
|
||
|
}
|
||
|
// When we come from a conditional render, on a mounted
|
||
|
// portal we should clear the DOM.
|
||
|
else if ( _this._temp ) {
|
||
|
_this.componentWillUnmount();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create a `Portal` to continue rendering the vnode tree at a different DOM node
|
||
|
*
|
||
|
* @param {import('./internal').VNode} vnode The vnode to render
|
||
|
* @param {import('./internal').PreactElement} container The DOM node to continue rendering in to.
|
||
|
*/
|
||
|
export function createPortal( vnode, container ) {
|
||
|
const el = createElement( Portal, {
|
||
|
_vnode: vnode,
|
||
|
_container: container,
|
||
|
} );
|
||
|
el.containerInfo = container;
|
||
|
return el;
|
||
|
}
|