mirror of https://github.com/sveltejs/svelte
parent
bbf38291fc
commit
1d40d3f237
@ -0,0 +1,15 @@
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { visit_component } from './shared/component.js';
|
||||
import * as e from '../../../errors.js';
|
||||
import * as w from '../../../warnings.js';
|
||||
import { filename } from '../../../state.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SveltePortal} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function SveltePortal(node, context) {
|
||||
// TODO validation for attributes etc
|
||||
context.next();
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/** @import { BlockStatement } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { build_attribute_value } from './shared/element.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SveltePortal} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function SveltePortal(node, context) {
|
||||
const _for = node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'for');
|
||||
const target = node.attributes.find(
|
||||
(attr) => attr.type === 'Attribute' && attr.name === 'target'
|
||||
);
|
||||
|
||||
context.state.template.push('<!>');
|
||||
|
||||
if (target) {
|
||||
// TODO handle reactive targets? Doesn't really make sense IMHO
|
||||
const value = build_attribute_value(/** @type {AST.Attribute} */ (target).value, context);
|
||||
const body = /** @type {BlockStatement} */ (
|
||||
context.visit(node.fragment, { ...context.state, transform: { ...context.state.transform } })
|
||||
);
|
||||
context.state.init.push(
|
||||
b.stmt(b.call('$.portal', value.value, b.arrow([b.id('$$anchor')], body)))
|
||||
);
|
||||
} else {
|
||||
// TODO reactive sources? Doesn't really make sense IMHO
|
||||
const value = build_attribute_value(/** @type {AST.Attribute} */ (_for).value, context);
|
||||
context.state.init.push(b.stmt(b.call('$.portal_outlet', context.state.node, value.value)));
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/** @import { BlockStatement } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types.js' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { build_attribute_value } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.SveltePortal} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function SveltePortal(node, context) {
|
||||
const _for = node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'for');
|
||||
const target = node.attributes.find(
|
||||
(attr) => attr.type === 'Attribute' && attr.name === 'target'
|
||||
);
|
||||
|
||||
if (target) {
|
||||
const value = build_attribute_value(/** @type {AST.Attribute} */ (target).value, context);
|
||||
const body = /** @type {BlockStatement} */ (context.visit(node.fragment, context.state));
|
||||
context.state.template.push(
|
||||
b.stmt(b.call('$.portal', b.id('$$payload'), value, b.arrow([b.id('$$payload')], body)))
|
||||
);
|
||||
} else {
|
||||
const value = build_attribute_value(/** @type {AST.Attribute} */ (_for).value, context);
|
||||
context.state.template.push(b.stmt(b.call('$.portal_outlet', b.id('$$payload'), value)));
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/** @import { TemplateNode } from '#client' */
|
||||
import { HYDRATION_END, HYDRATION_START, HYDRATION_START_ELSE } from '../../../../constants.js';
|
||||
import { block, remove_nodes, render_effect } from '../../reactivity/effects.js';
|
||||
import { hydrate_node, hydrating, set_hydrate_node } from '../hydration.js';
|
||||
import { get_next_sibling } from '../operations.js';
|
||||
|
||||
const portals = new Map();
|
||||
|
||||
/**
|
||||
* @param {TemplateNode} node
|
||||
* @param {any} id
|
||||
* @returns {void}
|
||||
*/
|
||||
export function portal_outlet(node, id) {
|
||||
var anchor = node;
|
||||
|
||||
render_effect(() => {
|
||||
portals.set(id, { anchor });
|
||||
|
||||
return () => {
|
||||
portals.delete(id);
|
||||
// TODO what happens to rendered content, if there's still some? Remove? Error?
|
||||
};
|
||||
});
|
||||
|
||||
if (hydrating) {
|
||||
let depth = 1;
|
||||
while (anchor !== null && depth > 0) {
|
||||
// TODO we have similar logic in other places, consolidate?
|
||||
anchor = /** @type {TemplateNode} */ (get_next_sibling(anchor));
|
||||
if (anchor?.nodeType === 8) {
|
||||
var comment = /** @type {Comment} */ (anchor).data;
|
||||
if (comment === HYDRATION_START || comment === HYDRATION_START_ELSE) depth += 1;
|
||||
else if (comment[0] === HYDRATION_END) depth -= 1;
|
||||
}
|
||||
}
|
||||
set_hydrate_node(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} target
|
||||
* @param {(anchor: TemplateNode) => void} content
|
||||
* @returns {void}
|
||||
*/
|
||||
export function portal(target, content) {
|
||||
const portal = portals.get(target);
|
||||
if (!portal)
|
||||
throw new Error(
|
||||
'TODO error code: No portal found for given target. Make sure portal target exists before referencing it'
|
||||
);
|
||||
|
||||
let previous_hydrate_node = null;
|
||||
var anchor = portal.anchor;
|
||||
|
||||
if (hydrating) {
|
||||
previous_hydrate_node = hydrate_node;
|
||||
set_hydrate_node((anchor = /** @type {TemplateNode} */ (get_next_sibling(portal.anchor))));
|
||||
}
|
||||
|
||||
const effect = block(() => {
|
||||
content(anchor);
|
||||
return () => {
|
||||
// The parent block will traverse all nodes in the current context, and then state that
|
||||
// child effects (like this one) don't need to traverse the nodes anymore because they
|
||||
// were already removed by the parent. That's not true in this case because the nodes
|
||||
// are somewhere else, so remove them "manually" here.
|
||||
remove_nodes(effect);
|
||||
};
|
||||
});
|
||||
|
||||
if (hydrating) {
|
||||
portal.anchor = hydrate_node; // so that next head block starts from the correct node
|
||||
set_hydrate_node(/** @type {TemplateNode} */ (previous_hydrate_node));
|
||||
}
|
||||
}
|
Loading…
Reference in new issue