summaryrefslogtreecommitdiff
path: root/preact/compat/src/portals.js
diff options
context:
space:
mode:
Diffstat (limited to 'preact/compat/src/portals.js')
-rw-r--r--preact/compat/src/portals.js80
1 files changed, 80 insertions, 0 deletions
diff --git a/preact/compat/src/portals.js b/preact/compat/src/portals.js
new file mode 100644
index 0000000..cf9f8f7
--- /dev/null
+++ b/preact/compat/src/portals.js
@@ -0,0 +1,80 @@
+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;
+ let 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, before) {
+ this.childNodes.push(child);
+ _this._container.appendChild(child);
+ },
+ removeChild(child) {
+ this.childNodes.splice(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) {
+ return createElement(Portal, { _vnode: vnode, _container: container });
+}