mirror of https://github.com/sveltejs/svelte
506 lines
15 KiB
506 lines
15 KiB
import Renderer from '../Renderer';
|
|
import Block from '../Block';
|
|
import Node from '../../nodes/shared/Node';
|
|
import Wrapper from './shared/Wrapper';
|
|
import createDebuggingComment from '../../../utils/createDebuggingComment';
|
|
import EachBlock from '../../nodes/EachBlock';
|
|
import FragmentWrapper from './Fragment';
|
|
import deindent from '../../../utils/deindent';
|
|
import ElseBlock from '../../nodes/ElseBlock';
|
|
|
|
class ElseBlockWrapper extends Wrapper {
|
|
node: ElseBlock;
|
|
block: Block;
|
|
fragment: FragmentWrapper;
|
|
isDynamic: boolean;
|
|
|
|
var = null;
|
|
|
|
constructor(
|
|
renderer: Renderer,
|
|
block: Block,
|
|
parent: Wrapper,
|
|
node: ElseBlock,
|
|
stripWhitespace: boolean,
|
|
nextSibling: Wrapper
|
|
) {
|
|
super(renderer, block, parent, node);
|
|
|
|
this.block = block.child({
|
|
comment: createDebuggingComment(node, this.renderer.component),
|
|
name: this.renderer.component.getUniqueName(`create_else_block`)
|
|
});
|
|
|
|
this.fragment = new FragmentWrapper(
|
|
renderer,
|
|
this.block,
|
|
this.node.children,
|
|
parent,
|
|
stripWhitespace,
|
|
nextSibling
|
|
);
|
|
|
|
this.isDynamic = this.block.dependencies.size > 0;
|
|
if (this.isDynamic) {
|
|
// TODO this can't be right
|
|
this.block.hasUpdateMethod = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default class EachBlockWrapper extends Wrapper {
|
|
block: Block;
|
|
node: EachBlock;
|
|
fragment: FragmentWrapper;
|
|
else?: ElseBlockWrapper;
|
|
vars: {
|
|
anchor: string;
|
|
create_each_block: string;
|
|
each_block_value: string;
|
|
get_each_context: string;
|
|
iterations: string;
|
|
length: string;
|
|
mountOrIntro: string;
|
|
}
|
|
|
|
contextProps: string[];
|
|
indexName: string;
|
|
|
|
var = 'each';
|
|
hasBinding = false;
|
|
|
|
constructor(
|
|
renderer: Renderer,
|
|
block: Block,
|
|
parent: Wrapper,
|
|
node: EachBlock,
|
|
stripWhitespace: boolean,
|
|
nextSibling: Wrapper
|
|
) {
|
|
super(renderer, block, parent, node);
|
|
this.cannotUseInnerHTML();
|
|
|
|
const { dependencies } = node.expression;
|
|
block.addDependencies(dependencies);
|
|
|
|
this.block = block.child({
|
|
comment: createDebuggingComment(this.node, this.renderer.component),
|
|
name: renderer.component.getUniqueName('create_each_block'),
|
|
key: <string>node.key, // TODO...
|
|
|
|
bindings: new Map(block.bindings),
|
|
contextOwners: new Map(block.contextOwners)
|
|
});
|
|
|
|
// TODO this seems messy
|
|
this.block.hasAnimation = this.node.hasAnimation;
|
|
|
|
this.indexName = this.node.index || renderer.component.getUniqueName(`${this.node.context}_index`);
|
|
|
|
node.contexts.forEach(prop => {
|
|
this.block.contextOwners.set(prop.key.name, this);
|
|
|
|
// TODO this doesn't feel great
|
|
this.block.bindings.set(prop.key.name, () => `ctx.${this.vars.each_block_value}[ctx.${this.indexName}]${prop.tail}`);
|
|
});
|
|
|
|
if (this.node.index) {
|
|
this.block.getUniqueName(this.node.index); // this prevents name collisions (#1254)
|
|
}
|
|
|
|
renderer.blocks.push(this.block);
|
|
|
|
this.fragment = new FragmentWrapper(renderer, this.block, node.children, this, stripWhitespace, nextSibling);
|
|
|
|
if (this.node.else) {
|
|
this.else = new ElseBlockWrapper(
|
|
renderer,
|
|
block,
|
|
this,
|
|
this.node.else,
|
|
stripWhitespace,
|
|
nextSibling
|
|
);
|
|
|
|
renderer.blocks.push(this.else.block);
|
|
|
|
if (this.else.isDynamic) {
|
|
this.block.addDependencies(this.else.block.dependencies);
|
|
}
|
|
}
|
|
|
|
block.addDependencies(this.block.dependencies);
|
|
this.block.hasUpdateMethod = this.block.dependencies.size > 0; // TODO should this logic be in Block?
|
|
|
|
if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) {
|
|
block.addOutro();
|
|
}
|
|
}
|
|
|
|
render(block: Block, parentNode: string, parentNodes: string) {
|
|
if (this.fragment.nodes.length === 0) return;
|
|
|
|
const { renderer } = this;
|
|
const { component } = renderer;
|
|
|
|
// hack the sourcemap, so that if data is missing the bug
|
|
// is easy to find
|
|
let c = this.node.start + 2;
|
|
while (component.source[c] !== 'e') c += 1;
|
|
component.code.overwrite(c, c + 4, 'length');
|
|
const length = `[✂${c}-${c+4}✂]`;
|
|
|
|
const needsAnchor = this.next
|
|
? !this.next.isDomNode() :
|
|
!parentNode || !this.parent.isDomNode();
|
|
|
|
this.vars = {
|
|
anchor: needsAnchor
|
|
? block.getUniqueName(`${this.var}_anchor`)
|
|
: (this.next && this.next.var) || 'null',
|
|
create_each_block: this.block.name,
|
|
each_block_value: renderer.component.getUniqueName(`${this.var}_value`),
|
|
get_each_context: renderer.component.getUniqueName(`get_${this.var}_context`),
|
|
iterations: block.getUniqueName(`${this.var}_blocks`),
|
|
length: `[✂${c}-${c+4}✂]`,
|
|
mountOrIntro: (this.block.hasIntroMethod || this.block.hasOutroMethod)
|
|
? 'i'
|
|
: 'm'
|
|
};
|
|
|
|
this.contextProps = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`);
|
|
|
|
if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`);
|
|
if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`);
|
|
|
|
const { snippet } = this.node.expression;
|
|
|
|
block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`);
|
|
|
|
renderer.blocks.push(deindent`
|
|
function ${this.vars.get_each_context}(ctx, list, i) {
|
|
const child_ctx = Object.create(ctx);
|
|
${this.contextProps}
|
|
return child_ctx;
|
|
}
|
|
`);
|
|
|
|
if (this.node.key) {
|
|
this.renderKeyed(block, parentNode, parentNodes, snippet);
|
|
} else {
|
|
this.renderUnkeyed(block, parentNode, parentNodes, snippet);
|
|
}
|
|
|
|
if (needsAnchor) {
|
|
block.addElement(
|
|
this.vars.anchor,
|
|
`@createComment()`,
|
|
parentNodes && `@createComment()`,
|
|
parentNode
|
|
);
|
|
}
|
|
|
|
if (this.else) {
|
|
const each_block_else = component.getUniqueName(`${this.var}_else`);
|
|
const mountOrIntro = (this.else.block.hasIntroMethod || this.else.block.hasOutroMethod) ? 'i' : 'm';
|
|
|
|
block.builders.init.addLine(`var ${each_block_else} = null;`);
|
|
|
|
// TODO neaten this up... will end up with an empty line in the block
|
|
block.builders.init.addBlock(deindent`
|
|
if (!${this.vars.each_block_value}.${length}) {
|
|
${each_block_else} = ${this.else.block.name}(#component, ctx);
|
|
${each_block_else}.c();
|
|
}
|
|
`);
|
|
|
|
block.builders.mount.addBlock(deindent`
|
|
if (${each_block_else}) {
|
|
${each_block_else}.${mountOrIntro}(${parentNode || '#target'}, null);
|
|
}
|
|
`);
|
|
|
|
const initialMountNode = parentNode || `${this.vars.anchor}.parentNode`;
|
|
|
|
if (this.else.block.hasUpdateMethod) {
|
|
block.builders.update.addBlock(deindent`
|
|
if (!${this.vars.each_block_value}.${length} && ${each_block_else}) {
|
|
${each_block_else}.p(changed, ctx);
|
|
} else if (!${this.vars.each_block_value}.${length}) {
|
|
${each_block_else} = ${this.else.block.name}(#component, ctx);
|
|
${each_block_else}.c();
|
|
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor});
|
|
} else if (${each_block_else}) {
|
|
${each_block_else}.d(1);
|
|
${each_block_else} = null;
|
|
}
|
|
`);
|
|
} else {
|
|
block.builders.update.addBlock(deindent`
|
|
if (${this.vars.each_block_value}.${length}) {
|
|
if (${each_block_else}) {
|
|
${each_block_else}.d(1);
|
|
${each_block_else} = null;
|
|
}
|
|
} else if (!${each_block_else}) {
|
|
${each_block_else} = ${this.else.block.name}(#component, ctx);
|
|
${each_block_else}.c();
|
|
${each_block_else}.${mountOrIntro}(${initialMountNode}, ${this.vars.anchor});
|
|
}
|
|
`);
|
|
}
|
|
|
|
block.builders.destroy.addBlock(deindent`
|
|
if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'});
|
|
`);
|
|
}
|
|
|
|
this.fragment.render(this.block, null, 'nodes');
|
|
|
|
if (this.else) {
|
|
this.else.fragment.render(this.else.block, null, 'nodes');
|
|
}
|
|
}
|
|
|
|
renderKeyed(
|
|
block: Block,
|
|
parentNode: string,
|
|
parentNodes: string,
|
|
snippet: string
|
|
) {
|
|
const {
|
|
create_each_block,
|
|
length,
|
|
anchor,
|
|
mountOrIntro,
|
|
} = this.vars;
|
|
|
|
const get_key = block.getUniqueName('get_key');
|
|
const blocks = block.getUniqueName(`${this.var}_blocks`);
|
|
const lookup = block.getUniqueName(`${this.var}_lookup`);
|
|
|
|
block.addVariable(blocks, '[]');
|
|
block.addVariable(lookup, `@blankObject()`);
|
|
|
|
if (this.fragment.nodes[0].isDomNode()) {
|
|
this.block.first = this.fragment.nodes[0].var;
|
|
} else {
|
|
this.block.first = this.block.getUniqueName('first');
|
|
this.block.addElement(
|
|
this.block.first,
|
|
`@createComment()`,
|
|
parentNodes && `@createComment()`,
|
|
null
|
|
);
|
|
}
|
|
|
|
block.builders.init.addBlock(deindent`
|
|
const ${get_key} = ctx => ${this.node.key.snippet};
|
|
|
|
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
|
|
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
|
|
let key = ${get_key}(child_ctx);
|
|
${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx);
|
|
}
|
|
`);
|
|
|
|
const initialMountNode = parentNode || '#target';
|
|
const updateMountNode = this.getUpdateMountNode(anchor);
|
|
const anchorNode = parentNode ? 'null' : 'anchor';
|
|
|
|
block.builders.create.addBlock(deindent`
|
|
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c();
|
|
`);
|
|
|
|
if (parentNodes) {
|
|
block.builders.claim.addBlock(deindent`
|
|
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes});
|
|
`);
|
|
}
|
|
|
|
block.builders.mount.addBlock(deindent`
|
|
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
|
|
`);
|
|
|
|
const dynamic = this.block.hasUpdateMethod;
|
|
|
|
const rects = block.getUniqueName('rects');
|
|
const destroy = this.node.hasAnimation
|
|
? `@fixAndOutroAndDestroyBlock`
|
|
: this.block.hasOutros
|
|
? `@outroAndDestroyBlock`
|
|
: `@destroyBlock`;
|
|
|
|
block.builders.update.addBlock(deindent`
|
|
const ${this.vars.each_block_value} = ${snippet};
|
|
|
|
${this.block.hasOutros && `@groupOutros();`}
|
|
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`}
|
|
${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.vars.get_each_context});
|
|
${this.node.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`}
|
|
`);
|
|
|
|
if (this.block.hasOutros && this.renderer.component.options.nestedTransitions) {
|
|
const countdown = block.getUniqueName('countdown');
|
|
block.builders.outro.addBlock(deindent`
|
|
const ${countdown} = @callAfter(#outrocallback, ${blocks}.length);
|
|
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].o(${countdown});
|
|
`);
|
|
}
|
|
|
|
block.builders.destroy.addBlock(deindent`
|
|
for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'});
|
|
`);
|
|
}
|
|
|
|
renderUnkeyed(
|
|
block: Block,
|
|
parentNode: string,
|
|
parentNodes: string,
|
|
snippet: string
|
|
) {
|
|
const {
|
|
create_each_block,
|
|
length,
|
|
iterations,
|
|
anchor,
|
|
mountOrIntro,
|
|
} = this.vars;
|
|
|
|
block.builders.init.addBlock(deindent`
|
|
var ${iterations} = [];
|
|
|
|
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
|
|
${iterations}[#i] = ${create_each_block}(#component, ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i));
|
|
}
|
|
`);
|
|
|
|
const initialMountNode = parentNode || '#target';
|
|
const updateMountNode = this.getUpdateMountNode(anchor);
|
|
const anchorNode = parentNode ? 'null' : 'anchor';
|
|
|
|
block.builders.create.addBlock(deindent`
|
|
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
|
|
${iterations}[#i].c();
|
|
}
|
|
`);
|
|
|
|
if (parentNodes) {
|
|
block.builders.claim.addBlock(deindent`
|
|
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
|
|
${iterations}[#i].l(${parentNodes});
|
|
}
|
|
`);
|
|
}
|
|
|
|
block.builders.mount.addBlock(deindent`
|
|
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
|
|
${iterations}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
|
|
}
|
|
`);
|
|
|
|
const allDependencies = new Set(this.block.dependencies);
|
|
const { dependencies } = this.node.expression;
|
|
dependencies.forEach((dependency: string) => {
|
|
allDependencies.add(dependency);
|
|
});
|
|
|
|
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock')
|
|
if (outroBlock) {
|
|
block.builders.init.addBlock(deindent`
|
|
function ${outroBlock}(i, detach, fn) {
|
|
if (${iterations}[i]) {
|
|
${iterations}[i].o(() => {
|
|
if (detach) {
|
|
${iterations}[i].d(detach);
|
|
${iterations}[i] = null;
|
|
}
|
|
if (fn) fn();
|
|
});
|
|
}
|
|
}
|
|
`);
|
|
}
|
|
|
|
// TODO do this for keyed blocks as well
|
|
const condition = Array.from(allDependencies)
|
|
.map(dependency => `changed.${dependency}`)
|
|
.join(' || ');
|
|
|
|
if (condition !== '') {
|
|
const forLoopBody = this.block.hasUpdateMethod
|
|
? (this.block.hasIntros || this.block.hasOutros)
|
|
? deindent`
|
|
if (${iterations}[#i]) {
|
|
${iterations}[#i].p(changed, child_ctx);
|
|
} else {
|
|
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
|
|
${iterations}[#i].c();
|
|
}
|
|
${iterations}[#i].i(${updateMountNode}, ${anchor});
|
|
`
|
|
: deindent`
|
|
if (${iterations}[#i]) {
|
|
${iterations}[#i].p(changed, child_ctx);
|
|
} else {
|
|
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
|
|
${iterations}[#i].c();
|
|
${iterations}[#i].m(${updateMountNode}, ${anchor});
|
|
}
|
|
`
|
|
: deindent`
|
|
${iterations}[#i] = ${create_each_block}(#component, child_ctx);
|
|
${iterations}[#i].c();
|
|
${iterations}[#i].${mountOrIntro}(${updateMountNode}, ${anchor});
|
|
`;
|
|
|
|
const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`;
|
|
|
|
let destroy;
|
|
|
|
if (this.block.hasOutros) {
|
|
destroy = deindent`
|
|
@groupOutros();
|
|
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
|
|
`;
|
|
} else {
|
|
destroy = deindent`
|
|
for (${this.block.hasUpdateMethod ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${iterations}.length; #i += 1) {
|
|
${iterations}[#i].d(1);
|
|
}
|
|
${iterations}.length = ${this.vars.each_block_value}.${length};
|
|
`;
|
|
}
|
|
|
|
block.builders.update.addBlock(deindent`
|
|
if (${condition}) {
|
|
${this.vars.each_block_value} = ${snippet};
|
|
|
|
for (var #i = ${start}; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
|
|
const child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);
|
|
|
|
${forLoopBody}
|
|
}
|
|
|
|
${destroy}
|
|
}
|
|
`);
|
|
}
|
|
|
|
if (outroBlock && this.renderer.component.options.nestedTransitions) {
|
|
const countdown = block.getUniqueName('countdown');
|
|
block.builders.outro.addBlock(deindent`
|
|
${iterations} = ${iterations}.filter(Boolean);
|
|
const ${countdown} = @callAfter(#outrocallback, ${iterations}.length);
|
|
for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 0, ${countdown});`
|
|
);
|
|
}
|
|
|
|
block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`);
|
|
}
|
|
|
|
remount(name: string) {
|
|
// TODO consider keyed blocks
|
|
return `for (var #i = 0; #i < ${this.vars.iterations}.length; #i += 1) ${this.vars.iterations}[#i].m(${name}._slotted.default, null);`;
|
|
}
|
|
} |