Implement local transitions — fixes #1480

pull/2009/head
Rich Harris 6 years ago committed by GitHub
parent ebd0b5ed0f
commit eccc8b264d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,7 @@ export default class Transition extends Node {
name: string;
directive: string;
expression: Expression;
is_local: boolean;
constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info);
@ -15,6 +16,7 @@ export default class Transition extends Node {
this.name = info.name;
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
this.is_local = info.modifiers.includes('local');
if ((info.intro && parent.intro) || (info.outro && parent.outro)) {
const parentTransition = (parent.intro || parent.outro);

@ -174,13 +174,15 @@ export default class Block {
}
}
addIntro() {
addIntro(local?: boolean) {
this.hasIntros = this.hasIntroMethod = this.renderer.hasIntroTransitions = true;
if (!local && this.parent) this.parent.addIntro();
}
addOutro() {
addOutro(local?: boolean) {
this.hasOutros = this.hasOutroMethod = this.renderer.hasOutroTransitions = true;
this.outros += 1;
if (!local && this.parent) this.parent.addOutro();
}
addAnimation() {
@ -327,7 +329,7 @@ export default class Block {
properties.addLine(`i: @noop,`);
} else {
properties.addBlock(deindent`
${dev ? 'i: function intro' : 'i'}() {
${dev ? 'i: function intro' : 'i'}(#local) {
${this.hasOutros && `if (#current) return;`}
${this.builders.intro}
},
@ -338,7 +340,7 @@ export default class Block {
properties.addLine(`o: @noop,`);
} else {
properties.addBlock(deindent`
${dev ? 'o: function outro' : 'o'}() {
${dev ? 'o: function outro' : 'o'}(#local) {
${this.builders.outro}
},
`);

@ -403,7 +403,7 @@ export default class EachBlockWrapper extends Wrapper {
const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock')
if (outroBlock) {
block.builders.init.addBlock(deindent`
function ${outroBlock}(i, detach) {
function ${outroBlock}(i, detach, local) {
if (${iterations}[i]) {
if (detach) {
@on_outro(() => {
@ -412,7 +412,7 @@ export default class EachBlockWrapper extends Wrapper {
});
}
${iterations}[i].o();
${iterations}[i].o(local);
}
}
`);
@ -434,27 +434,27 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i].c();
${iterations}[#i].m(${updateMountNode}, ${anchor});
}
${has_transitions && `${iterations}[#i].i();`}
${has_transitions && `${iterations}[#i].i(1);`}
`
: deindent`
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
${iterations}[#i].m(${updateMountNode}, ${anchor});
${has_transitions && `${iterations}[#i].i();`}
${has_transitions && `${iterations}[#i].i(1);`}
`;
const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`;
let destroy;
let remove_old_blocks;
if (this.block.hasOutros) {
destroy = deindent`
remove_old_blocks = deindent`
@group_outros();
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1);
for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1, 1);
@check_outros();
`;
} else {
destroy = deindent`
remove_old_blocks = deindent`
for (${this.block.hasUpdateMethod ? `` : `#i = ${this.vars.each_block_value}.${length}`}; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].d(1);
}
@ -471,7 +471,7 @@ export default class EachBlockWrapper extends Wrapper {
${forLoopBody}
}
${destroy}
${remove_old_blocks}
`;
block.builders.update.addBlock(deindent`

@ -162,8 +162,8 @@ export default class ElementWrapper extends Wrapper {
this.bindings = this.node.bindings.map(binding => new Binding(block, binding, this));
if (node.intro || node.outro) {
if (node.intro) block.addIntro();
if (node.outro) block.addOutro();
if (node.intro) block.addIntro(node.intro.is_local);
if (node.outro) block.addOutro(node.outro.is_local);
}
if (node.animation) {
@ -622,17 +622,34 @@ export default class ElementWrapper extends Wrapper {
const fn = component.qualify(intro.name);
block.builders.intro.addBlock(deindent`
const intro_block = deindent`
@add_render_callback(() => {
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true);
${name}.run(1);
});
`);
`;
block.builders.outro.addBlock(deindent`
const outro_block = deindent`
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false);
${name}.run(0);
`);
`;
if (intro.is_local) {
block.builders.intro.addBlock(deindent`
if (#local) {
${intro_block}
}
`);
block.builders.outro.addBlock(deindent`
if (#local) {
${outro_block}
}
`);
} else {
block.builders.intro.addBlock(intro_block);
block.builders.outro.addBlock(outro_block);
}
block.builders.destroy.addConditional('detach', `if (${name}) ${name}.end();`);
}
@ -649,25 +666,37 @@ export default class ElementWrapper extends Wrapper {
const fn = component.qualify(intro.name);
let intro_block;
if (outro) {
block.builders.intro.addBlock(deindent`
intro_block = deindent`
@add_render_callback(() => {
if (!${introName}) ${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start();
});
`);
`;
block.builders.outro.addLine(`if (${introName}) ${introName}.invalidate()`);
} else {
block.builders.intro.addBlock(deindent`
intro_block = deindent`
if (!${introName}) {
@add_render_callback(() => {
${introName} = @create_in_transition(${this.var}, ${fn}, ${snippet});
${introName}.start();
});
}
`);
`;
}
if (intro.is_local) {
intro_block = deindent`
if (#local) {
${intro_block}
}
`;
}
block.builders.intro.addBlock(intro_block);
}
if (outro) {
@ -684,9 +713,19 @@ export default class ElementWrapper extends Wrapper {
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
block.builders.outro.addBlock(deindent`
let outro_block = deindent`
${outroName} = @create_out_transition(${this.var}, ${fn}, ${snippet});
`);
`;
if (outro_block) {
outro_block = deindent`
if (#local) {
${outro_block}
}
`;
}
block.builders.outro.addBlock(outro_block);
block.builders.destroy.addConditional('detach', `if (${outroName}) ${outroName}.end();`);
}

@ -125,9 +125,6 @@ export default class IfBlockWrapper extends Wrapper {
createBranches(this.node);
if (hasIntros) block.addIntro();
if (hasOutros) block.addOutro();
blocks.forEach(block => {
block.hasUpdateMethod = isDynamic;
block.hasIntroMethod = hasIntros;
@ -239,7 +236,7 @@ export default class IfBlockWrapper extends Wrapper {
if (${name}) {
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
${has_transitions && `${name}.i();`}
${has_transitions && `${name}.i(1);`}
}
`;
@ -327,7 +324,7 @@ export default class IfBlockWrapper extends Wrapper {
${if_blocks}[${previous_block_index}].d(1);
${if_blocks}[${previous_block_index}] = null;
});
${name}.o();
${name}.o(1);
@check_outros();
`;
@ -338,7 +335,7 @@ export default class IfBlockWrapper extends Wrapper {
${name}.c();
}
${name}.m(${updateMountNode}, ${anchor});
${has_transitions && `${name}.i();`}
${has_transitions && `${name}.i(1);`}
`;
const changeBlock = hasElse
@ -415,7 +412,7 @@ export default class IfBlockWrapper extends Wrapper {
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
${has_transitions && `${name}.i();`}
${has_transitions && `${name}.i(1);`}
`
: deindent`
if (!${name}) {
@ -423,7 +420,7 @@ export default class IfBlockWrapper extends Wrapper {
${name}.c();
${name}.m(${updateMountNode}, ${anchor});
}
${has_transitions && `${name}.i();`}
${has_transitions && `${name}.i(1);`}
`;
// no `p()` here — we don't want to update outroing nodes,
@ -436,7 +433,7 @@ export default class IfBlockWrapper extends Wrapper {
${name} = null;
});
${name}.o();
${name}.o(1);
@check_outros();
`
: deindent`

@ -397,7 +397,7 @@ export default class InlineComponentWrapper extends Wrapper {
@on_outro(() => {
old_component.$destroy();
});
old_component.$$.fragment.o();
old_component.$$.fragment.o(1);
@check_outros();
}
@ -409,7 +409,7 @@ export default class InlineComponentWrapper extends Wrapper {
${name}.$$.fragment.c();
@mount_component(${name}, ${updateMountNode}, ${anchor});
${name}.$$.fragment.i();
${name}.$$.fragment.i(1);
} else {
${name} = null;
}
@ -417,7 +417,7 @@ export default class InlineComponentWrapper extends Wrapper {
`);
block.builders.intro.addBlock(deindent`
if (${name}) ${name}.$$.fragment.i();
if (${name}) ${name}.$$.fragment.i(#local);
`);
if (updates.length) {
@ -430,6 +430,10 @@ export default class InlineComponentWrapper extends Wrapper {
`);
}
block.builders.outro.addLine(
`if (${name}) ${name}.$$.fragment.o(#local);`
);
block.builders.destroy.addLine(`if (${name}) ${name}.$destroy(${parentNode ? '' : 'detach'});`);
} else {
const expression = this.node.name === 'svelte:self'
@ -459,7 +463,7 @@ export default class InlineComponentWrapper extends Wrapper {
);
block.builders.intro.addBlock(deindent`
${name}.$$.fragment.i();
${name}.$$.fragment.i(#local);
`);
if (updates.length) {
@ -473,11 +477,11 @@ export default class InlineComponentWrapper extends Wrapper {
block.builders.destroy.addBlock(deindent`
${name}.$destroy(${parentNode ? '' : 'detach'});
`);
}
block.builders.outro.addLine(
`if (${name}) ${name}.$$.fragment.o();`
);
block.builders.outro.addLine(
`${name}.$$.fragment.o(#local);`
);
}
}
}

@ -22,7 +22,7 @@ export function handlePromise(promise, info) {
block.d(1);
info.blocks[i] = null;
});
block.o();
block.o(1);
check_outros();
}
});
@ -32,7 +32,7 @@ export function handlePromise(promise, info) {
block.c();
block.m(info.mount(), info.anchor);
if (block.i) block.i();
if (block.i) block.i(1);
flush();
}

@ -10,7 +10,7 @@ export function outroAndDestroyBlock(block, lookup) {
destroyBlock(block, lookup);
});
block.o();
block.o(1);
}
export function fixAndOutroAndDestroyBlock(block, lookup) {
@ -53,7 +53,7 @@ export function updateKeyedEach(old_blocks, changed, get_key, dynamic, ctx, list
function insert(block) {
block.m(node, next);
if (block.i) block.i();
if (block.i) block.i(1);
lookup[block.key] = block;
next = block.first;
n--;

@ -17,15 +17,15 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
nested.$$.fragment.i();
nested.$$.fragment.i(local);
current = true;
},
o() {
if (nested) nested.$$.fragment.o();
o(local) {
nested.$$.fragment.o(local);
current = false;
},

@ -17,15 +17,15 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
nested.$$.fragment.i();
nested.$$.fragment.i(local);
current = true;
},
o() {
if (nested) nested.$$.fragment.o();
o(local) {
nested.$$.fragment.o(local);
current = false;
},

@ -17,15 +17,15 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
nested.$$.fragment.i();
nested.$$.fragment.i(local);
current = true;
},
o() {
if (nested) nested.$$.fragment.o();
o(local) {
nested.$$.fragment.o(local);
current = false;
},

@ -17,15 +17,15 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
nested.$$.fragment.i();
nested.$$.fragment.i(local);
current = true;
},
o() {
if (nested) nested.$$.fragment.o();
o(local) {
nested.$$.fragment.o(local);
current = false;
},

@ -18,15 +18,15 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
lazyload.$$.fragment.i();
lazyload.$$.fragment.i(local);
current = true;
},
o() {
if (lazyload) lazyload.$$.fragment.o();
o(local) {
lazyload.$$.fragment.o(local);
current = false;
},

@ -24,18 +24,18 @@ function create_fragment(ctx) {
p: noop,
i() {
i(local) {
if (current) return;
imported.$$.fragment.i();
imported.$$.fragment.i(local);
nonimported.$$.fragment.i();
nonimported.$$.fragment.i(local);
current = true;
},
o() {
if (imported) imported.$$.fragment.o();
if (nonimported) nonimported.$$.fragment.o();
o(local) {
imported.$$.fragment.o(local);
nonimported.$$.fragment.o(local);
current = false;
},

@ -0,0 +1,162 @@
/* generated by Svelte vX.Y.Z */
import { SvelteComponent as SvelteComponent_1, add_render_callback, createComment, createElement, create_in_transition, detachNode, flush, init, insert, noop, safe_not_equal } from "svelte/internal";
// (8:0) {#if x}
function create_if_block(ctx) {
var if_block_anchor;
var if_block = (ctx.y) && create_if_block_1(ctx);
return {
c() {
if (if_block) if_block.c();
if_block_anchor = createComment();
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, if_block_anchor, anchor);
},
p(changed, ctx) {
if (ctx.y) {
if (!if_block) {
if_block = create_if_block_1(ctx);
if_block.c();
if_block.m(if_block_anchor.parentNode, if_block_anchor);
}
if_block.i(1);
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
d(detach) {
if (if_block) if_block.d(detach);
if (detach) {
detachNode(if_block_anchor);
}
}
};
}
// (9:1) {#if y}
function create_if_block_1(ctx) {
var div, div_intro;
return {
c() {
div = createElement("div");
div.textContent = "...";
},
m(target, anchor) {
insert(target, div, anchor);
},
i(local) {
if (local) {
if (!div_intro) {
add_render_callback(() => {
div_intro = create_in_transition(div, foo, {});
div_intro.start();
});
}
}
},
o: noop,
d(detach) {
if (detach) {
detachNode(div);
}
}
};
}
function create_fragment(ctx) {
var if_block_anchor;
var if_block = (ctx.x) && create_if_block(ctx);
return {
c() {
if (if_block) if_block.c();
if_block_anchor = createComment();
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, if_block_anchor, anchor);
},
p(changed, ctx) {
if (ctx.x) {
if (if_block) {
if_block.p(changed, ctx);
} else {
if_block = create_if_block(ctx);
if_block.c();
if_block.m(if_block_anchor.parentNode, if_block_anchor);
}
} else if (if_block) {
if_block.d(1);
if_block = null;
}
},
i: noop,
o: noop,
d(detach) {
if (if_block) if_block.d(detach);
if (detach) {
detachNode(if_block_anchor);
}
}
};
}
function foo() {}
function instance($$self, $$props, $$invalidate) {
let { x, y } = $$props;
$$self.$set = $$props => {
if ('x' in $$props) $$invalidate('x', x = $$props.x);
if ('y' in $$props) $$invalidate('y', y = $$props.y);
};
return { x, y };
}
class SvelteComponent extends SvelteComponent_1 {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal);
}
get x() {
return this.$$.ctx.x;
}
set x(x) {
this.$set({ x });
flush();
}
get y() {
return this.$$.ctx.y;
}
set y(y) {
this.$set({ y });
flush();
}
}
export default SvelteComponent;

@ -0,0 +1,12 @@
<script>
export let x;
export let y;
function foo() {}
</script>
{#if x}
{#if y}
<div in:foo|local>...</div>
{/if}
{/if}

@ -0,0 +1,67 @@
export default {
props: {
x: false,
y: true
},
test({ assert, component, target, raf }) {
// first, toggle x — first element should snap in
// and out while second one transitions
component.x = true;
let divs = target.querySelectorAll('div');
assert.equal(divs[0].foo, undefined);
assert.equal(divs[1].foo, 0);
raf.tick(50);
assert.equal(divs[0].foo, undefined);
assert.equal(divs[1].foo, 0.5);
raf.tick(100);
component.x = false;
assert.htmlEqual(target.innerHTML, `
<div>snaps if x changes</div>
<div>transitions if x changes</div>
`);
raf.tick(150);
assert.equal(divs[0].foo, undefined);
assert.equal(divs[1].foo, 0.5);
raf.tick(200);
assert.htmlEqual(target.innerHTML, '');
// then toggle y
component.y = false;
component.x = true;
component.y = true;
assert.htmlEqual(target.innerHTML, `
<div>snaps if x changes</div>
<div>transitions if x changes</div>
`);
divs = target.querySelectorAll('div');
raf.tick(250);
assert.equal(divs[0].foo, 0.5);
assert.equal(divs[1].foo, 0.5);
raf.tick(300);
assert.equal(divs[0].foo, 1);
assert.equal(divs[1].foo, 1);
component.y = false;
assert.htmlEqual(target.innerHTML, `
<div>snaps if x changes</div>
<div>transitions if x changes</div>
`);
raf.tick(320);
assert.equal(divs[0].foo, 0.8);
assert.equal(divs[1].foo, 0.8);
raf.tick(400);
assert.htmlEqual(target.innerHTML, '');
},
};

@ -0,0 +1,20 @@
<script>
export let x;
export let y;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#if y}
<div transition:foo|local>snaps if x changes</div>
<div transition:foo>transitions if x changes</div>
{/if}
{/if}

@ -0,0 +1,31 @@
let fulfil;
const promise = new Promise(f => {
fulfil = f;
});
export default {
props: {
x: false,
promise
},
test({ assert, component, target, raf }) {
component.x = true;
fulfil();
return promise.then(() => {
const div = target.querySelector('div');
assert.equal(div.foo, 0);
raf.tick(100);
assert.equal(div.foo, 1);
component.x = false;
assert.htmlEqual(target.innerHTML, '');
raf.tick(150);
assert.equal(div.foo, 1);
});
}
};

@ -0,0 +1,19 @@
<script>
export let x;
export let promise;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#await promise then value}
<div transition:foo|local></div>
{/await}
{/if}

@ -0,0 +1,12 @@
<script>
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
<div transition:foo|local></div>

@ -0,0 +1,24 @@
export default {
props: {
x: false
},
test({ assert, component, target, raf }) {
component.x = true;
const div = target.querySelector('div');
assert.equal(div.foo, 0);
raf.tick(100);
assert.equal(div.foo, 1);
component.x = false;
assert.htmlEqual(target.innerHTML, '<div></div>');
raf.tick(150);
assert.equal(div.foo, 0.5);
raf.tick(200);
assert.htmlEqual(target.innerHTML, '');
}
};

@ -0,0 +1,9 @@
<script>
export let x;
import Widget from './Widget.html';
</script>
{#if x}
<Widget/>
{/if}

@ -0,0 +1,30 @@
export default {
props: {
x: false,
things: ['a']
},
test({ assert, component, target, raf }) {
component.x = true;
const div1 = target.querySelector('div');
assert.equal(div1.foo, undefined);
raf.tick(100);
assert.equal(div1.foo, undefined);
component.things = ['a', 'b'];
assert.htmlEqual(target.innerHTML, '<div></div><div></div>');
const div2 = target.querySelector('div:last-child');
assert.equal(div1.foo, undefined);
assert.equal(div2.foo, 0);
raf.tick(200);
assert.equal(div1.foo, undefined);
assert.equal(div2.foo, 1);
component.x = false;
assert.htmlEqual(target.innerHTML, '');
},
};

@ -0,0 +1,19 @@
<script>
export let x;
export let things;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#each things as thing (thing)}
<div transition:foo|local></div>
{/each}
{/if}

@ -0,0 +1,30 @@
export default {
props: {
x: false,
things: ['a']
},
test({ assert, component, target, raf }) {
component.x = true;
const div1 = target.querySelector('div');
assert.equal(div1.foo, undefined);
raf.tick(100);
assert.equal(div1.foo, undefined);
component.things = ['a', 'b'];
assert.htmlEqual(target.innerHTML, '<div></div><div></div>');
const div2 = target.querySelector('div:last-child');
assert.equal(div1.foo, undefined);
assert.equal(div2.foo, 0);
raf.tick(200);
assert.equal(div1.foo, undefined);
assert.equal(div2.foo, 1);
component.x = false;
assert.htmlEqual(target.innerHTML, '');
},
};

@ -0,0 +1,19 @@
<script>
export let x;
export let things;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#each things as thing}
<div transition:foo|local></div>
{/each}
{/if}

@ -0,0 +1,40 @@
export default {
props: {
x: false,
y: true
},
test({ assert, component, target, raf }) {
component.x = true;
let div = target.querySelector('div');
assert.equal(div.foo, undefined);
component.y = false;
assert.htmlEqual(target.innerHTML, '<div></div>');
div = target.querySelector('div');
raf.tick(50);
assert.equal(div.foo, 0.5);
raf.tick(100);
assert.htmlEqual(target.innerHTML, '');
component.x = false;
component.y = true;
assert.htmlEqual(target.innerHTML, '');
component.x = true;
assert.htmlEqual(target.innerHTML, '<div></div>');
div = target.querySelector('div');
component.y = false;
assert.htmlEqual(target.innerHTML, '<div></div>');
raf.tick(150);
assert.equal(div.foo, 0.5);
raf.tick(200);
assert.htmlEqual(target.innerHTML, '');
},
};

@ -0,0 +1,19 @@
<script>
export let x;
export let y;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#if y}
<div transition:foo|local></div>
{/if}
{/if}

@ -0,0 +1,40 @@
export default {
props: {
x: false,
y: true
},
test({ assert, component, target, window, raf }) {
component.x = true;
let div = target.querySelector('div');
assert.equal(div.foo, undefined);
component.y = false;
assert.htmlEqual(target.innerHTML, '<div></div>');
div = target.querySelector('div');
raf.tick(50);
assert.equal(div.foo, 0.5);
raf.tick(100);
assert.htmlEqual(target.innerHTML, '');
component.x = false;
component.y = true;
assert.htmlEqual(target.innerHTML, '');
component.x = true;
assert.htmlEqual(target.innerHTML, '<div></div>');
div = target.querySelector('div');
component.y = false;
assert.htmlEqual(target.innerHTML, '<div></div>');
raf.tick(120);
assert.equal(div.foo, 0.8);
raf.tick(200);
assert.htmlEqual(target.innerHTML, '');
},
};

@ -0,0 +1,19 @@
<script>
export let x;
export let y;
function foo(node, params) {
return {
duration: 100,
tick: t => {
node.foo = t;
}
};
}
</script>
{#if x}
{#if y}
<div transition:foo|local></div>
{/if}
{/if}
Loading…
Cancel
Save