support very basic outro transitions

pull/525/head
Rich-Harris 9 years ago
parent 53c5c32da3
commit 2a5b0ee1a4

@ -24,11 +24,16 @@ export default class Block {
create: new CodeBuilder(), create: new CodeBuilder(),
mount: new CodeBuilder(), mount: new CodeBuilder(),
update: new CodeBuilder(), update: new CodeBuilder(),
outro: new CodeBuilder(),
detach: new CodeBuilder(), detach: new CodeBuilder(),
detachRaw: new CodeBuilder(), detachRaw: new CodeBuilder(),
destroy: new CodeBuilder() destroy: new CodeBuilder()
}; };
this.hasIntroTransitions = false;
this.hasOutroTransitions = false;
this.outros = 0;
this.aliases = new Map(); this.aliases = new Map();
this.variables = new Map(); this.variables = new Map();
this.getUniqueName = this.generator.getUniqueNameMaker( options.params ); this.getUniqueName = this.generator.getUniqueNameMaker( options.params );
@ -100,6 +105,12 @@ export default class Block {
} }
render () { render () {
let outroing;
if ( this.hasOutroTransitions ) {
outroing = this.getUniqueName( 'outroing' );
this.addVariable( outroing );
}
if ( this.variables.size ) { if ( this.variables.size ) {
const variables = Array.from( this.variables.keys() ) const variables = Array.from( this.variables.keys() )
.map( key => { .map( key => {
@ -135,11 +146,6 @@ export default class Block {
properties.addBlock( `key: ${localKey},` ); properties.addBlock( `key: ${localKey},` );
} }
if ( this.outros.length ) {
// TODO
properties.addLine( `outro: null,` );
}
if ( this.builders.mount.isEmpty() ) { if ( this.builders.mount.isEmpty() ) {
properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` ); properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` );
} else { } else {
@ -162,6 +168,23 @@ export default class Block {
} }
} }
if ( this.hasOutroTransitions ) {
if ( this.builders.outro.isEmpty() ) {
properties.addBlock( `outro: ${this.generator.helper( 'noop' )},` );
} else {
properties.addBlock( deindent`
outro: function ( ${this.alias( 'outrocallback' )} ) {
if ( ${outroing} ) return;
${outroing} = true;
var ${this.alias( 'outros' )} = ${this.outros};
${this.builders.outro}
},
` );
}
}
if ( this.builders.destroy.isEmpty() ) { if ( this.builders.destroy.isEmpty() ) {
properties.addBlock( `destroy: ${this.generator.helper( 'noop' )}` ); properties.addBlock( `destroy: ${this.generator.helper( 'noop' )}` );
} else { } else {

@ -169,7 +169,7 @@ export default function dom ( parsed, source, options ) {
if ( templateProperties.oncreate ) { if ( templateProperties.oncreate ) {
builders.init.addBlock( deindent` builders.init.addBlock( deindent`
if ( options._root ) { if ( options._root ) {
options._root._renderHooks.push({ fn: ${generator.alias( 'template' )}.oncreate, context: this }); options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
} else { } else {
${generator.alias( 'template' )}.oncreate.call( this ); ${generator.alias( 'template' )}.oncreate.call( this );
} }

@ -200,6 +200,14 @@ const preprocessors = {
const dependencies = block.findDependencies( attribute.value ); const dependencies = block.findDependencies( attribute.value );
block.addDependencies( dependencies ); block.addDependencies( dependencies );
} }
else if ( attribute.type === 'Transition' ) {
if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroTransitions = true;
if ( attribute.outro ) {
generator.hasOutroTransitions = block.hasOutroTransitions = true;
block.outros += 1;
}
}
}); });
if ( node.children.length ) { if ( node.children.length ) {

@ -6,7 +6,7 @@ import visitAttribute from './Attribute.js';
import visitEventHandler from './EventHandler.js'; import visitEventHandler from './EventHandler.js';
import visitBinding from './Binding.js'; import visitBinding from './Binding.js';
import visitRef from './Ref.js'; import visitRef from './Ref.js';
import visitTransition from './Transition.js'; import addTransitions from './addTransitions.js';
const meta = { const meta = {
':Window': visitWindow ':Window': visitWindow
@ -16,16 +16,14 @@ const order = {
Attribute: 1, Attribute: 1,
Binding: 2, Binding: 2,
EventHandler: 3, EventHandler: 3,
Ref: 4, Ref: 4
Transition: 5
}; };
const visitors = { const visitors = {
Attribute: visitAttribute, Attribute: visitAttribute,
EventHandler: visitEventHandler, EventHandler: visitEventHandler,
Binding: visitBinding, Binding: visitBinding,
Ref: visitRef, Ref: visitRef
Transition: visitTransition
}; };
export default function visitElement ( generator, block, state, node ) { export default function visitElement ( generator, block, state, node ) {
@ -43,21 +41,35 @@ export default function visitElement ( generator, block, state, node ) {
block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` ); block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` );
block.mount( name, state.parentNode ); block.mount( name, state.parentNode );
if ( !state.parentNode ) {
block.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
}
// add CSS encapsulation attribute // add CSS encapsulation attribute
if ( generator.cssId && state.isTopLevel ) { if ( generator.cssId && state.isTopLevel ) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` ); block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
} }
function visitAttributes () { function visitAttributes () {
let intro;
let outro;
node.attributes node.attributes
.sort( ( a, b ) => order[ a.type ] - order[ b.type ] ) .sort( ( a, b ) => order[ a.type ] - order[ b.type ] )
.forEach( attribute => { .forEach( attribute => {
if ( attribute.type === 'Transition' ) {
if ( attribute.intro ) intro = attribute;
if ( attribute.outro ) outro = attribute;
return;
}
visitors[ attribute.type ]( generator, block, childState, node, attribute ); visitors[ attribute.type ]( generator, block, childState, node, attribute );
}); });
addTransitions( generator, block, childState, node, intro, outro );
if ( !outro && !state.parentNode ) {
// TODO this probably doesn't belong here. We eventually need to consider
// what happens to elements that belong to the same outgroup as an
// outroing element...
block.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
}
} }
if ( node.name !== 'select' ) { if ( node.name !== 'select' ) {

@ -1,30 +0,0 @@
import deindent from '../../../../utils/deindent.js';
export default function visitTransition ( generator, block, state, node, attribute ) {
const name = block.getUniqueName( `${state.name}_${attribute.intro ? 'intro' : 'outro'}` );
block.addVariable( name );
const snippet = attribute.expression ? block.contextualise( attribute.expression ).snippet : '{}';
const fn = `${generator.alias( 'template' )}.transitions.${attribute.name}`; // TODO add built-in transitions?
if ( attribute.intro ) {
generator.hasIntroTransitions = true;
block.builders.create.addBlock( deindent`
${block.component}._renderHooks.push({
fn: function () {
${name} = ${generator.helper( 'wrapTransition' )}( ${state.name}, ${fn}, ${snippet}, true );
${generator.helper( 'transitionManager' )}.add( ${name} );
},
context: ${block.component}
});
` );
}
if ( attribute.outro ) {
generator.hasOutroTransitions = true;
throw new Error( 'TODO' );
}
}

@ -0,0 +1,48 @@
import deindent from '../../../../utils/deindent.js';
export default function addTransitions ( generator, block, state, node, intro, outro ) {
const introName = intro && block.getUniqueName( `${state.name}_intro` );
const outroName = outro && block.getUniqueName( `${state.name}_outro` );
const introSnippet = intro && intro.expression ? block.contextualise( intro.expression ).snippet : '{}';
const outroSnippet = outro === intro ?
introSnippet :
outro && outro.expression ? block.contextualise( outro.expression ).snippet : '{}';
const wrapTransition = generator.helper( 'wrapTransition' );
if ( intro ) {
block.addVariable( introName );
const fn = `${generator.alias( 'template' )}.transitions.${intro.name}`; // TODO add built-in transitions?
block.builders.create.addBlock( deindent`
${block.component}._renderHooks.push( function () {
${introName} = ${wrapTransition}( ${state.name}, ${fn}, ${introSnippet}, true, null, function () {
${block.component}.fire( 'intro.end', { node: ${state.name} });
});
${generator.helper( 'transitionManager' )}.add( ${introName} );
});
` );
}
if ( outro ) {
block.addVariable( outroName );
const fn = `${generator.alias( 'template' )}.transitions.${outro.name}`;
if ( intro ) {
block.builders.outro.addBlock( `${introName}.abort();` );
}
block.builders.outro.addBlock( deindent`
${outroName} = ${wrapTransition}( ${state.name}, ${fn}, ${outroSnippet}, false, null, function () {
detachNode( div );
${block.component}.fire( 'outro.end', { node: ${state.name} });
if ( --${block.alias( 'outros' )} === 0 ) ${block.alias( 'outrocallback' )}();
});
transitionManager.add( ${outroName} );
` );
}
}

@ -9,7 +9,8 @@ function getBranches ( generator, block, state, node ) {
const branches = [{ const branches = [{
condition: block.contextualise( node.expression ).snippet, condition: block.contextualise( node.expression ).snippet,
block: node._block.name, block: node._block.name,
dynamic: node._block.dependencies.size > 0 dynamic: node._block.dependencies.size > 0,
hasOutroTransitions: node._block.hasOutroTransitions
}]; }];
visitChildren( generator, block, state, node ); visitChildren( generator, block, state, node );
@ -22,7 +23,8 @@ function getBranches ( generator, block, state, node ) {
branches.push({ branches.push({
condition: null, condition: null,
block: node.else ? node.else._block.name : null, block: node.else ? node.else._block.name : null,
dynamic: node.else ? node.else._block.dependencies.size > 0 : false dynamic: node.else ? node.else._block.dependencies.size > 0 : false,
hasOutroTransitions: node.else ? node.else._block.hasOutroTransitions : false
}); });
if ( node.else ) { if ( node.else ) {
@ -81,6 +83,17 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
const remove = branch.hasOutroTransitions ?
deindent`
${name}.outro( function () {
${name} = null;
});
` :
deindent`
${name}.destroy( true );
${name} = null;
`;
if ( dynamic ) { if ( dynamic ) {
block.builders.update.addBlock( deindent` block.builders.update.addBlock( deindent`
if ( ${branch.condition} ) { if ( ${branch.condition} ) {
@ -91,8 +104,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
${name}.mount( ${parentNode}, ${anchor} ); ${name}.mount( ${parentNode}, ${anchor} );
} }
} else if ( ${name} ) { } else if ( ${name} ) {
${name}.destroy( true ); ${remove}
${name} = null;
} }
` ); ` );
} else { } else {
@ -103,8 +115,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
${name}.mount( ${parentNode}, ${anchor} ); ${name}.mount( ${parentNode}, ${anchor} );
} }
} else if ( ${name} ) { } else if ( ${name} ) {
${name}.destroy( true ); ${remove}
${name} = null;
} }
` ); ` );
} }
@ -135,6 +146,10 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
const parentNode = state.parentNode || `${anchor}.parentNode`; const parentNode = state.parentNode || `${anchor}.parentNode`;
if ( block.hasOutroTransitions ) {
throw new Error( 'TODO compound if-blocks with outro transitions are not yet supported' );
}
if ( dynamic ) { if ( dynamic ) {
block.builders.update.addBlock( deindent` block.builders.update.addBlock( deindent`
if ( ${current_block} === ( ${current_block} = ${getBlock}( ${params} ) ) && ${name} ) { if ( ${current_block} === ( ${current_block} = ${getBlock}( ${params} ) ) && ${name} ) {

@ -106,8 +106,7 @@ export function _flush () {
if ( !this._renderHooks ) return; if ( !this._renderHooks ) return;
while ( this._renderHooks.length ) { while ( this._renderHooks.length ) {
var hook = this._renderHooks.pop(); this._renderHooks.pop()();
hook.fn.call( hook.context );
} }
} }

@ -4,8 +4,8 @@ export function linear ( t ) {
return t; return t;
} }
export function wrapTransition ( node, fn, params, isIntro ) { export function wrapTransition ( node, fn, params, intro, outgroup, callback ) {
var obj = fn( node, params, isIntro ); var obj = fn( node, params, intro );
var start = window.performance.now() + ( obj.delay || 0 ); var start = window.performance.now() + ( obj.delay || 0 );
var duration = obj.duration || 300; var duration = obj.duration || 300;
@ -14,16 +14,18 @@ export function wrapTransition ( node, fn, params, isIntro ) {
if ( obj.tick ) { if ( obj.tick ) {
// JS transition // JS transition
if ( isIntro ) obj.tick( 0 ); if ( intro ) obj.tick( 0 );
return { return {
start: start, start: start,
end: end, end: end,
update: function ( now ) { update: function ( now ) {
obj.tick( ease( ( now - start ) / duration ) ); const p = intro ? now - start : end - now;
obj.tick( ease( p / duration ) );
}, },
done: function () { done: function () {
obj.tick( isIntro ? 1 : 0 ); obj.tick( intro ? 1 : 0 );
callback();
}, },
abort: noop abort: noop
}; };
@ -39,7 +41,7 @@ export function wrapTransition ( node, fn, params, isIntro ) {
init: function () { init: function () {
for ( var key in obj.styles ) { for ( var key in obj.styles ) {
inlineStyles[ key ] = node.style[ key ]; inlineStyles[ key ] = node.style[ key ];
node.style[ key ] = isIntro ? obj.styles[ key ] : computedStyles[ key ]; node.style[ key ] = intro ? obj.styles[ key ] : computedStyles[ key ];
} }
}, },
update: function ( now ) { update: function ( now ) {
@ -52,7 +54,7 @@ export function wrapTransition ( node, fn, params, isIntro ) {
// TODO use a keyframe animation for custom easing functions // TODO use a keyframe animation for custom easing functions
for ( var key in obj.styles ) { for ( var key in obj.styles ) {
node.style[ key ] = isIntro ? computedStyles[ key ] : obj.styles[ key ]; node.style[ key ] = intro ? computedStyles[ key ] : obj.styles[ key ];
} }
started = true; started = true;
@ -60,11 +62,12 @@ export function wrapTransition ( node, fn, params, isIntro ) {
}, },
done: function () { done: function () {
// TODO what if one of these styles was dynamic? // TODO what if one of these styles was dynamic?
if ( isIntro ) { if ( intro ) {
for ( var key in obj.styles ) { for ( var key in obj.styles ) {
node.style[ key ] = inlineStyles[ key ]; node.style[ key ] = inlineStyles[ key ];
} }
} }
callback();
}, },
abort: function () { abort: function () {
node.style.cssText = getComputedStyle( node ).cssText; node.style.cssText = getComputedStyle( node ).cssText;

Loading…
Cancel
Save