S
SolidJS4w ago
Lȩge

How does Portal work?

Source code:
export function Portal<T extends boolean = false, S extends boolean = false>(props: {
mount?: Node;
useShadow?: T;
isSVG?: S;
ref?:
| (S extends true ? SVGGElement : HTMLDivElement)
| ((
el: (T extends true ? { readonly shadowRoot: ShadowRoot } : {}) &
(S extends true ? SVGGElement : HTMLDivElement)
) => void);
children: JSX.Element;
}) {
const { useShadow } = props,
marker = document.createTextNode(""),
mount = () => props.mount || document.body,
owner = getOwner();
let content: undefined | (() => JSX.Element);
let hydrating = !!sharedConfig.context;

createEffect(
() => {
// basically we backdoor into a sort of renderEffect here
if (hydrating) (getOwner() as any).user = hydrating = false;
content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
const el = mount();
if (el instanceof HTMLHeadElement) {
const [clean, setClean] = createSignal(false);
const cleanup = () => setClean(true);
createRoot(dispose => insert(el, () => (!clean() ? content!() : dispose()), null));
onCleanup(cleanup);
} else {
const container = createElement(props.isSVG ? "g" : "div", props.isSVG),
renderRoot =
useShadow && container.attachShadow
? container.attachShadow({ mode: "open" })
: container;

Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: true
});
insert(renderRoot, content);
el.appendChild(container);
props.ref && (props as any).ref(container);
onCleanup(() => el.removeChild(container));
}
},
undefined,
{ render: !hydrating }
);
return marker;
}
export function Portal<T extends boolean = false, S extends boolean = false>(props: {
mount?: Node;
useShadow?: T;
isSVG?: S;
ref?:
| (S extends true ? SVGGElement : HTMLDivElement)
| ((
el: (T extends true ? { readonly shadowRoot: ShadowRoot } : {}) &
(S extends true ? SVGGElement : HTMLDivElement)
) => void);
children: JSX.Element;
}) {
const { useShadow } = props,
marker = document.createTextNode(""),
mount = () => props.mount || document.body,
owner = getOwner();
let content: undefined | (() => JSX.Element);
let hydrating = !!sharedConfig.context;

createEffect(
() => {
// basically we backdoor into a sort of renderEffect here
if (hydrating) (getOwner() as any).user = hydrating = false;
content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
const el = mount();
if (el instanceof HTMLHeadElement) {
const [clean, setClean] = createSignal(false);
const cleanup = () => setClean(true);
createRoot(dispose => insert(el, () => (!clean() ? content!() : dispose()), null));
onCleanup(cleanup);
} else {
const container = createElement(props.isSVG ? "g" : "div", props.isSVG),
renderRoot =
useShadow && container.attachShadow
? container.attachShadow({ mode: "open" })
: container;

Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: true
});
insert(renderRoot, content);
el.appendChild(container);
props.ref && (props as any).ref(container);
onCleanup(() => el.removeChild(container));
}
},
undefined,
{ render: !hydrating }
);
return marker;
}
1 Reply
Lȩge
LȩgeOP4w ago
And a version I've tried to simplify to remove SVG and HeadElement handling.
export function Portal<T extends boolean = false>(props: {
mount?: Node;
useShadow?: T;
ref?:
| HTMLDivElement
| ((
el: (T extends true ? { readonly shadowRoot: ShadowRoot } : {}) & HTMLDivElement
) => void);
children: JSX.Element;
}) {
const { useShadow } = props,
marker = document.createTextNode(""),
mount = () => props.mount || document.body,
owner = getOwner();
let content: undefined | (() => JSX.Element);
let hydrating = !!sharedConfig.context;

createEffect(
() => {
// basically we backdoor into a sort of renderEffect here
if (hydrating) (getOwner() as any).user = hydrating = false;
content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
const el = mount();
const container = document.createElement("div"),
renderRoot =
useShadow && container.attachShadow
? container.attachShadow({ mode: "open" })
: container;

Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: true
});
insert(renderRoot, content);
el.appendChild(container);
props.ref && (props as any).ref(container);
onCleanup(() => el.removeChild(container));
},
undefined,
{ render: !hydrating }
);
return marker;
}
export function Portal<T extends boolean = false>(props: {
mount?: Node;
useShadow?: T;
ref?:
| HTMLDivElement
| ((
el: (T extends true ? { readonly shadowRoot: ShadowRoot } : {}) & HTMLDivElement
) => void);
children: JSX.Element;
}) {
const { useShadow } = props,
marker = document.createTextNode(""),
mount = () => props.mount || document.body,
owner = getOwner();
let content: undefined | (() => JSX.Element);
let hydrating = !!sharedConfig.context;

createEffect(
() => {
// basically we backdoor into a sort of renderEffect here
if (hydrating) (getOwner() as any).user = hydrating = false;
content || (content = runWithOwner(owner, () => createMemo(() => props.children)));
const el = mount();
const container = document.createElement("div"),
renderRoot =
useShadow && container.attachShadow
? container.attachShadow({ mode: "open" })
: container;

Object.defineProperty(container, "_$host", {
get() {
return marker.parentNode;
},
configurable: true
});
insert(renderRoot, content);
el.appendChild(container);
props.ref && (props as any).ref(container);
onCleanup(() => el.removeChild(container));
},
undefined,
{ render: !hydrating }
);
return marker;
}
what's the thing about $host and why does it need to set owner? wouldn't the default owner already be correcd that makes sense, but surely tere'd have to be an event handler on the container to do the custom bubbling I assume that the $host thing is not just an obscure web api? thank you this is very helpful

Did you find this page helpful?