mirror of https://github.com/sveltejs/svelte
529 lines
15 KiB
529 lines
15 KiB
import Wrapper from './shared/Wrapper';
|
|
import Renderer from '../Renderer';
|
|
import Block from '../Block';
|
|
import EachBlock from '../../nodes/EachBlock';
|
|
import IfBlock from '../../nodes/IfBlock';
|
|
import create_debugging_comment from './shared/create_debugging_comment';
|
|
import ElseBlock from '../../nodes/ElseBlock';
|
|
import FragmentWrapper from './Fragment';
|
|
import deindent from '../../utils/deindent';
|
|
import { walk } from 'estree-walker';
|
|
|
|
function is_else_if(node: ElseBlock) {
|
|
return (
|
|
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
|
|
);
|
|
}
|
|
|
|
class IfBlockBranch extends Wrapper {
|
|
block: Block;
|
|
fragment: FragmentWrapper;
|
|
dependencies?: string[];
|
|
condition?: string;
|
|
snippet?: string;
|
|
is_dynamic: boolean;
|
|
|
|
var = null;
|
|
|
|
constructor(
|
|
renderer: Renderer,
|
|
block: Block,
|
|
parent: IfBlockWrapper,
|
|
node: IfBlock | ElseBlock,
|
|
strip_whitespace: boolean,
|
|
next_sibling: Wrapper
|
|
) {
|
|
super(renderer, block, parent, node);
|
|
|
|
const { expression } = (node as IfBlock);
|
|
const is_else = !expression;
|
|
|
|
if (expression) {
|
|
this.dependencies = expression.dynamic_dependencies();
|
|
|
|
// TODO is this the right rule? or should any non-reference count?
|
|
// const should_cache = !is_reference(expression.node, null) && dependencies.length > 0;
|
|
let should_cache = false;
|
|
walk(expression.node, {
|
|
enter(node) {
|
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
|
should_cache = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (should_cache) {
|
|
this.condition = block.get_unique_name(`show_if`);
|
|
this.snippet = expression.render(block);
|
|
} else {
|
|
this.condition = expression.render(block);
|
|
}
|
|
}
|
|
|
|
this.block = block.child({
|
|
comment: create_debugging_comment(node, parent.renderer.component),
|
|
name: parent.renderer.component.get_unique_name(is_else ? `create_else_block` : `create_if_block`)
|
|
});
|
|
|
|
this.fragment = new FragmentWrapper(renderer, this.block, node.children, parent, strip_whitespace, next_sibling);
|
|
|
|
this.is_dynamic = this.block.dependencies.size > 0;
|
|
}
|
|
}
|
|
|
|
export default class IfBlockWrapper extends Wrapper {
|
|
node: IfBlock;
|
|
branches: IfBlockBranch[];
|
|
needs_update = false;
|
|
|
|
var = 'if_block';
|
|
|
|
constructor(
|
|
renderer: Renderer,
|
|
block: Block,
|
|
parent: Wrapper,
|
|
node: EachBlock,
|
|
strip_whitespace: boolean,
|
|
next_sibling: Wrapper
|
|
) {
|
|
super(renderer, block, parent, node);
|
|
|
|
this.cannot_use_innerhtml();
|
|
|
|
this.branches = [];
|
|
|
|
const blocks: Block[] = [];
|
|
let is_dynamic = false;
|
|
let has_intros = false;
|
|
let has_outros = false;
|
|
|
|
const create_branches = (node: IfBlock) => {
|
|
const branch = new IfBlockBranch(
|
|
renderer,
|
|
block,
|
|
this,
|
|
node,
|
|
strip_whitespace,
|
|
next_sibling
|
|
);
|
|
|
|
this.branches.push(branch);
|
|
|
|
blocks.push(branch.block);
|
|
block.add_dependencies(node.expression.dependencies);
|
|
|
|
if (branch.block.dependencies.size > 0) {
|
|
// the condition, or its contents, is dynamic
|
|
is_dynamic = true;
|
|
block.add_dependencies(branch.block.dependencies);
|
|
}
|
|
|
|
if (branch.dependencies && branch.dependencies.length > 0) {
|
|
// the condition itself is dynamic
|
|
this.needs_update = true;
|
|
}
|
|
|
|
if (branch.block.has_intros) has_intros = true;
|
|
if (branch.block.has_outros) has_outros = true;
|
|
|
|
if (is_else_if(node.else)) {
|
|
create_branches(node.else.children[0] as IfBlock);
|
|
} else if (node.else) {
|
|
const branch = new IfBlockBranch(
|
|
renderer,
|
|
block,
|
|
this,
|
|
node.else,
|
|
strip_whitespace,
|
|
next_sibling
|
|
);
|
|
|
|
this.branches.push(branch);
|
|
|
|
blocks.push(branch.block);
|
|
|
|
if (branch.block.dependencies.size > 0) {
|
|
is_dynamic = true;
|
|
block.add_dependencies(branch.block.dependencies);
|
|
}
|
|
|
|
if (branch.block.has_intros) has_intros = true;
|
|
if (branch.block.has_outros) has_outros = true;
|
|
}
|
|
};
|
|
|
|
create_branches(this.node);
|
|
|
|
blocks.forEach(block => {
|
|
block.has_update_method = is_dynamic;
|
|
block.has_intro_method = has_intros;
|
|
block.has_outro_method = has_outros;
|
|
});
|
|
|
|
renderer.blocks.push(...blocks);
|
|
}
|
|
|
|
render(
|
|
block: Block,
|
|
parent_node: string,
|
|
parent_nodes: string
|
|
) {
|
|
const name = this.var;
|
|
|
|
const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node();
|
|
const anchor = needs_anchor
|
|
? block.get_unique_name(`${name}_anchor`)
|
|
: (this.next && this.next.var) || 'null';
|
|
|
|
const has_else = !(this.branches[this.branches.length - 1].condition);
|
|
const if_name = has_else ? '' : `if (${name}) `;
|
|
|
|
const dynamic = this.branches[0].block.has_update_method; // can use [0] as proxy for all, since they necessarily have the same value
|
|
const has_intros = this.branches[0].block.has_intro_method;
|
|
const has_outros = this.branches[0].block.has_outro_method;
|
|
const has_transitions = has_intros || has_outros;
|
|
|
|
const vars = { name, anchor, if_name, has_else, has_transitions };
|
|
|
|
const detaching = (parent_node && parent_node !== '@_document.head') ? '' : 'detaching';
|
|
|
|
if (this.node.else) {
|
|
this.branches.forEach(branch => {
|
|
if (branch.snippet) block.add_variable(branch.condition);
|
|
});
|
|
|
|
if (has_outros) {
|
|
this.render_compound_with_outros(block, parent_node, parent_nodes, dynamic, vars, detaching);
|
|
|
|
block.builders.outro.add_line(`@transition_out(${name});`);
|
|
} else {
|
|
this.render_compound(block, parent_node, parent_nodes, dynamic, vars, detaching);
|
|
}
|
|
} else {
|
|
this.render_simple(block, parent_node, parent_nodes, dynamic, vars, detaching);
|
|
|
|
if (has_outros) {
|
|
block.builders.outro.add_line(`@transition_out(${name});`);
|
|
}
|
|
}
|
|
|
|
block.builders.create.add_line(`${if_name}${name}.c();`);
|
|
|
|
if (parent_nodes && this.renderer.options.hydratable) {
|
|
block.builders.claim.add_line(
|
|
`${if_name}${name}.l(${parent_nodes});`
|
|
);
|
|
}
|
|
|
|
if (has_intros || has_outros) {
|
|
block.builders.intro.add_line(`@transition_in(${name});`);
|
|
}
|
|
|
|
if (needs_anchor) {
|
|
block.add_element(
|
|
anchor,
|
|
`@empty()`,
|
|
parent_nodes && `@empty()`,
|
|
parent_node
|
|
);
|
|
}
|
|
|
|
this.branches.forEach(branch => {
|
|
branch.fragment.render(branch.block, null, 'nodes');
|
|
});
|
|
}
|
|
|
|
render_compound(
|
|
block: Block,
|
|
parent_node: string,
|
|
_parent_nodes: string,
|
|
dynamic,
|
|
{ name, anchor, has_else, if_name, has_transitions },
|
|
detaching
|
|
) {
|
|
const select_block_type = this.renderer.component.get_unique_name(`select_block_type`);
|
|
const current_block_type = block.get_unique_name(`current_block_type`);
|
|
const current_block_type_and = has_else ? '' : `${current_block_type} && `;
|
|
|
|
/* eslint-disable @typescript-eslint/indent,indent */
|
|
if (this.needs_update) {
|
|
block.builders.init.add_block(deindent`
|
|
function ${select_block_type}(changed, ctx) {
|
|
${this.branches.map(({ dependencies, condition, snippet, block }) => condition
|
|
? deindent`
|
|
${snippet && (
|
|
dependencies.length > 0
|
|
? `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`
|
|
: `if (${condition} == null) ${condition} = !!(${snippet})`
|
|
)}
|
|
if (${condition}) return ${block.name};`
|
|
: `return ${block.name};`)}
|
|
}
|
|
`);
|
|
} else {
|
|
block.builders.init.add_block(deindent`
|
|
function ${select_block_type}(changed, ctx) {
|
|
${this.branches.map(({ condition, snippet, block }) => condition
|
|
? `if (${snippet}) return ${block.name};`
|
|
: `return ${block.name};`)}
|
|
}
|
|
`);
|
|
}
|
|
/* eslint-enable @typescript-eslint/indent,indent */
|
|
|
|
block.builders.init.add_block(deindent`
|
|
var ${current_block_type} = ${select_block_type}(null, ctx);
|
|
var ${name} = ${current_block_type_and}${current_block_type}(ctx);
|
|
`);
|
|
|
|
const initial_mount_node = parent_node || '#target';
|
|
const anchor_node = parent_node ? 'null' : 'anchor';
|
|
block.builders.mount.add_line(
|
|
`${if_name}${name}.m(${initial_mount_node}, ${anchor_node});`
|
|
);
|
|
|
|
if (this.needs_update) {
|
|
const update_mount_node = this.get_update_mount_node(anchor);
|
|
|
|
const change_block = deindent`
|
|
${if_name}${name}.d(1);
|
|
${name} = ${current_block_type_and}${current_block_type}(ctx);
|
|
if (${name}) {
|
|
${name}.c();
|
|
${has_transitions && `@transition_in(${name}, 1);`}
|
|
${name}.m(${update_mount_node}, ${anchor});
|
|
}
|
|
`;
|
|
|
|
if (dynamic) {
|
|
block.builders.update.add_block(deindent`
|
|
if (${current_block_type} === (${current_block_type} = ${select_block_type}(changed, ctx)) && ${name}) {
|
|
${name}.p(changed, ctx);
|
|
} else {
|
|
${change_block}
|
|
}
|
|
`);
|
|
} else {
|
|
block.builders.update.add_block(deindent`
|
|
if (${current_block_type} !== (${current_block_type} = ${select_block_type}(changed, ctx))) {
|
|
${change_block}
|
|
}
|
|
`);
|
|
}
|
|
} else if (dynamic) {
|
|
block.builders.update.add_line(`${name}.p(changed, ctx);`);
|
|
}
|
|
|
|
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
|
|
}
|
|
|
|
// if any of the siblings have outros, we need to keep references to the blocks
|
|
// (TODO does this only apply to bidi transitions?)
|
|
render_compound_with_outros(
|
|
block: Block,
|
|
parent_node: string,
|
|
_parent_nodes: string,
|
|
dynamic,
|
|
{ name, anchor, has_else, has_transitions },
|
|
detaching
|
|
) {
|
|
const select_block_type = this.renderer.component.get_unique_name(`select_block_type`);
|
|
const current_block_type_index = block.get_unique_name(`current_block_type_index`);
|
|
const previous_block_index = block.get_unique_name(`previous_block_index`);
|
|
const if_block_creators = block.get_unique_name(`if_block_creators`);
|
|
const if_blocks = block.get_unique_name(`if_blocks`);
|
|
|
|
const if_current_block_type_index = has_else
|
|
? ''
|
|
: `if (~${current_block_type_index}) `;
|
|
|
|
block.add_variable(current_block_type_index);
|
|
block.add_variable(name);
|
|
|
|
/* eslint-disable @typescript-eslint/indent,indent */
|
|
block.builders.init.add_block(deindent`
|
|
var ${if_block_creators} = [
|
|
${this.branches.map(branch => branch.block.name).join(',\n')}
|
|
];
|
|
|
|
var ${if_blocks} = [];
|
|
|
|
function ${select_block_type}(changed, ctx) {
|
|
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
|
|
? deindent`
|
|
${snippet && `if ((${condition} == null) || ${dependencies.map(n => `changed.${n}`).join(' || ')}) ${condition} = !!(${snippet})`}
|
|
if (${condition}) return ${String(i)};`
|
|
: `return ${i};`)}
|
|
${!has_else && `return -1;`}
|
|
}
|
|
`);
|
|
/* eslint-enable @typescript-eslint/indent,indent */
|
|
|
|
if (has_else) {
|
|
block.builders.init.add_block(deindent`
|
|
${current_block_type_index} = ${select_block_type}(null, ctx);
|
|
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
|
|
`);
|
|
} else {
|
|
block.builders.init.add_block(deindent`
|
|
if (~(${current_block_type_index} = ${select_block_type}(null, ctx))) {
|
|
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
|
|
}
|
|
`);
|
|
}
|
|
|
|
const initial_mount_node = parent_node || '#target';
|
|
const anchor_node = parent_node ? 'null' : 'anchor';
|
|
|
|
block.builders.mount.add_line(
|
|
`${if_current_block_type_index}${if_blocks}[${current_block_type_index}].m(${initial_mount_node}, ${anchor_node});`
|
|
);
|
|
|
|
const update_mount_node = this.get_update_mount_node(anchor);
|
|
|
|
const destroy_old_block = deindent`
|
|
@group_outros();
|
|
@transition_out(${if_blocks}[${previous_block_index}], 1, 1, () => {
|
|
${if_blocks}[${previous_block_index}] = null;
|
|
});
|
|
@check_outros();
|
|
`;
|
|
|
|
const create_new_block = deindent`
|
|
${name} = ${if_blocks}[${current_block_type_index}];
|
|
if (!${name}) {
|
|
${name} = ${if_blocks}[${current_block_type_index}] = ${if_block_creators}[${current_block_type_index}](ctx);
|
|
${name}.c();
|
|
}
|
|
${has_transitions && `@transition_in(${name}, 1);`}
|
|
${name}.m(${update_mount_node}, ${anchor});
|
|
`;
|
|
|
|
const change_block = has_else
|
|
? deindent`
|
|
${destroy_old_block}
|
|
|
|
${create_new_block}
|
|
`
|
|
: deindent`
|
|
if (${name}) {
|
|
${destroy_old_block}
|
|
}
|
|
|
|
if (~${current_block_type_index}) {
|
|
${create_new_block}
|
|
} else {
|
|
${name} = null;
|
|
}
|
|
`;
|
|
|
|
if (dynamic) {
|
|
block.builders.update.add_block(deindent`
|
|
var ${previous_block_index} = ${current_block_type_index};
|
|
${current_block_type_index} = ${select_block_type}(changed, ctx);
|
|
if (${current_block_type_index} === ${previous_block_index}) {
|
|
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].p(changed, ctx);
|
|
} else {
|
|
${change_block}
|
|
}
|
|
`);
|
|
} else {
|
|
block.builders.update.add_block(deindent`
|
|
var ${previous_block_index} = ${current_block_type_index};
|
|
${current_block_type_index} = ${select_block_type}(changed, ctx);
|
|
if (${current_block_type_index} !== ${previous_block_index}) {
|
|
${change_block}
|
|
}
|
|
`);
|
|
}
|
|
|
|
block.builders.destroy.add_line(deindent`
|
|
${if_current_block_type_index}${if_blocks}[${current_block_type_index}].d(${detaching});
|
|
`);
|
|
}
|
|
|
|
render_simple(
|
|
block: Block,
|
|
parent_node: string,
|
|
_parent_nodes: string,
|
|
dynamic,
|
|
{ name, anchor, if_name, has_transitions },
|
|
detaching
|
|
) {
|
|
const branch = this.branches[0];
|
|
|
|
if (branch.snippet) block.add_variable(branch.condition, branch.snippet);
|
|
|
|
block.builders.init.add_block(deindent`
|
|
var ${name} = (${branch.condition}) && ${branch.block.name}(ctx);
|
|
`);
|
|
|
|
const initial_mount_node = parent_node || '#target';
|
|
const anchor_node = parent_node ? 'null' : 'anchor';
|
|
|
|
block.builders.mount.add_line(
|
|
`if (${name}) ${name}.m(${initial_mount_node}, ${anchor_node});`
|
|
);
|
|
|
|
if (branch.dependencies.length > 0) {
|
|
const update_mount_node = this.get_update_mount_node(anchor);
|
|
|
|
const enter = dynamic
|
|
? deindent`
|
|
if (${name}) {
|
|
${name}.p(changed, ctx);
|
|
${has_transitions && `@transition_in(${name}, 1);`}
|
|
} else {
|
|
${name} = ${branch.block.name}(ctx);
|
|
${name}.c();
|
|
${has_transitions && `@transition_in(${name}, 1);`}
|
|
${name}.m(${update_mount_node}, ${anchor});
|
|
}
|
|
`
|
|
: deindent`
|
|
if (!${name}) {
|
|
${name} = ${branch.block.name}(ctx);
|
|
${name}.c();
|
|
${has_transitions && `@transition_in(${name}, 1);`}
|
|
${name}.m(${update_mount_node}, ${anchor});
|
|
} ${has_transitions && `else @transition_in(${name}, 1);`}
|
|
`;
|
|
|
|
if (branch.snippet) {
|
|
block.builders.update.add_block(`if (${branch.dependencies.map(n => `changed.${n}`).join(' || ')}) ${branch.condition} = ${branch.snippet}`);
|
|
}
|
|
|
|
// no `p()` here — we don't want to update outroing nodes,
|
|
// as that will typically result in glitching
|
|
if (branch.block.has_outro_method) {
|
|
block.builders.update.add_block(deindent`
|
|
if (${branch.condition}) {
|
|
${enter}
|
|
} else if (${name}) {
|
|
@group_outros();
|
|
@transition_out(${name}, 1, 1, () => {
|
|
${name} = null;
|
|
});
|
|
@check_outros();
|
|
}
|
|
`);
|
|
} else {
|
|
block.builders.update.add_block(deindent`
|
|
if (${branch.condition}) {
|
|
${enter}
|
|
} else if (${name}) {
|
|
${name}.d(1);
|
|
${name} = null;
|
|
}
|
|
`);
|
|
}
|
|
} else if (dynamic) {
|
|
block.builders.update.add_block(
|
|
`if (${branch.condition}) ${name}.p(changed, ctx);`
|
|
);
|
|
}
|
|
|
|
block.builders.destroy.add_line(`${if_name}${name}.d(${detaching});`);
|
|
}
|
|
}
|