portalkey or dom element

portals
Simon Holthausen 9 months ago
parent ae62c7c84b
commit bce9c3220d

@ -17,13 +17,16 @@ export function SveltePortal(node, context) {
context.state.template.push('<!>'); context.state.template.push('<!>');
if (target) { if (target) {
// TODO handle reactive targets? Doesn't really make sense IMHO const { value, has_state } = build_attribute_value(
const value = build_attribute_value(/** @type {AST.Attribute} */ (target).value, context); /** @type {AST.Attribute} */ (target).value,
context
);
const body = /** @type {BlockStatement} */ ( const body = /** @type {BlockStatement} */ (
context.visit(node.fragment, { ...context.state, transform: { ...context.state.transform } }) context.visit(node.fragment, { ...context.state, transform: { ...context.state.transform } })
); );
const portal = b.call('$.portal', value, b.arrow([b.id('$$anchor')], body));
context.state.init.push( context.state.init.push(
b.stmt(b.call('$.portal', value.value, b.arrow([b.id('$$anchor')], body))) b.stmt(has_state ? b.call('$.render_effect', b.thunk(portal)) : portal)
); );
} else { } else {
// TODO reactive sources? Doesn't really make sense IMHO // TODO reactive sources? Doesn't really make sense IMHO

@ -191,3 +191,5 @@ export {
} from './internal/client/runtime.js'; } from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
export { createPortalKey } from './internal/shared/svelte-portal.js';

@ -38,3 +38,5 @@ export async function tick() {}
export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js'; export { getAllContexts, getContext, hasContext, setContext } from './internal/server/context.js';
export { createRawSnippet } from './internal/server/blocks/snippet.js'; export { createRawSnippet } from './internal/server/blocks/snippet.js';
export { createPortalKey } from './internal/shared/svelte-portal.js';

@ -1,5 +1,6 @@
/** @import { TemplateNode } from '#client' */ /** @import { TemplateNode } from '#client' */
import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../../constants.js'; import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../../constants.js';
import { PortalKey } from '../../../shared/svelte-portal.js';
import { block, remove_nodes, render_effect } from '../../reactivity/effects.js'; import { block, remove_nodes, render_effect } from '../../reactivity/effects.js';
import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hydration.js'; import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hydration.js';
import { get_next_sibling } from '../operations.js'; import { get_next_sibling } from '../operations.js';
@ -44,23 +45,30 @@ export function portal_outlet(node, id) {
* @returns {void} * @returns {void}
*/ */
export function portal(target, content) { export function portal(target, content) {
const is_css_selector = typeof target === 'string'; if (target == null) return;
const is_dom_node = target instanceof Element;
if (!is_dom_node && !(target instanceof PortalKey)) {
throw new Error(
'TODO error code: target can only be a key instantiated with createPortalKey, or a DOM node'
);
}
const portal = portals.get(target); const portal = portals.get(target);
if (!is_dom_node && !portal)
throw new Error(
'TODO error code: No portal found for given target. Make sure portal target exists before referencing it'
);
let previous_hydrating = false; let previous_hydrating = false;
let previous_hydrate_node = null; let previous_hydrate_node = null;
// TODO right now the portal is rendered before the given anchor. Is that confusing for people and they'd rather have it as // TODO right now the portal is rendered before the given anchor. Is that confusing for people and they'd rather have it as
// the first child _inside_ the anchor if the anchor is an element? // the first child _inside_ the anchor if the anchor is an element?
var anchor = is_css_selector ? document.querySelector(target) : portal?.anchor; var anchor = is_dom_node ? target : portal.anchor;
if (!anchor)
throw new Error(
'TODO error code: No portal found for given target. Make sure portal target exists before referencing it'
);
if (hydrating) { if (hydrating) {
previous_hydrating = true; previous_hydrating = true;
if (is_css_selector) { if (is_dom_node) {
// These are not SSR'd, so temporarily disable hydration to properly insert them // These are not SSR'd, so temporarily disable hydration to properly insert them
set_hydrating(false); set_hydrating(false);
} else { } else {
@ -81,7 +89,7 @@ export function portal(target, content) {
}); });
if (previous_hydrating) { if (previous_hydrating) {
if (is_css_selector) { if (is_dom_node) {
set_hydrating(true); set_hydrating(true);
} else { } else {
portal.anchor = hydrate_node; // so that next head block starts from the correct node portal.anchor = hydrate_node; // so that next head block starts from the correct node

@ -18,6 +18,7 @@ import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js'; import { validate_store } from '../shared/validate.js';
import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js';
import { reset_elements } from './dev.js'; import { reset_elements } from './dev.js';
import { PortalKey } from '../shared/svelte-portal.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter // https://infra.spec.whatwg.org/#noncharacter
@ -182,7 +183,13 @@ export function portal_outlet(payload, id) {
export function portal(payload, target, content) { export function portal(payload, target, content) {
payload.out += EMPTY_COMMENT; payload.out += EMPTY_COMMENT;
if (typeof target === 'string') return; // targets a CSS selector - not possible in SSR, ignore if (!target) return; // probably targets a DOM node - not possible in SSR, ignore
if (!(target instanceof PortalKey)) {
throw new Error(
'TODO error code: target can only be a key instantiated with createPortalKey, or a DOM node'
);
}
const portal = payload.portals.get(target); const portal = payload.portals.get(target);
if (!portal) if (!portal)

@ -0,0 +1,15 @@
export class PortalKey {
/** @param {string} [name] */
constructor(name) {
this.v = Symbol(name);
}
}
/**
* Creates a key for use with a `<svelte:portal>`. It connects the portal source and target.
* Example: TODO write out once exact API clear.
* @param {string} [name]
*/
export function createPortalKey(name) {
return new PortalKey(name);
}
Loading…
Cancel
Save