support very basic outro transitions

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

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

@ -169,7 +169,7 @@ export default function dom ( parsed, source, options ) {
if ( templateProperties.oncreate ) {
builders.init.addBlock( deindent`
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 {
${generator.alias( 'template' )}.oncreate.call( this );
}

@ -200,6 +200,14 @@ const preprocessors = {
const dependencies = block.findDependencies( attribute.value );
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 ) {

@ -6,7 +6,7 @@ import visitAttribute from './Attribute.js';
import visitEventHandler from './EventHandler.js';
import visitBinding from './Binding.js';
import visitRef from './Ref.js';
import visitTransition from './Transition.js';
import addTransitions from './addTransitions.js';
const meta = {
':Window': visitWindow
@ -16,16 +16,14 @@ const order = {
Attribute: 1,
Binding: 2,
EventHandler: 3,
Ref: 4,
Transition: 5
Ref: 4
};
const visitors = {
Attribute: visitAttribute,
EventHandler: visitEventHandler,
Binding: visitBinding,
Ref: visitRef,
Transition: visitTransition
Ref: visitRef
};
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.mount( name, state.parentNode );
if ( !state.parentNode ) {
block.builders.detach.addLine( `${generator.helper( 'detachNode' )}( ${name} );` );
}
// add CSS encapsulation attribute
if ( generator.cssId && state.isTopLevel ) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
}
function visitAttributes () {
let intro;
let outro;
node.attributes
.sort( ( a, b ) => order[ a.type ] - order[ b.type ] )
.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 );
});
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' ) {

@ -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 = [{
condition: block.contextualise( node.expression ).snippet,
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 );
@ -22,7 +23,8 @@ function getBranches ( generator, block, state, node ) {
branches.push({
condition: 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 ) {
@ -81,6 +83,17 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
const parentNode = state.parentNode || `${anchor}.parentNode`;
const remove = branch.hasOutroTransitions ?
deindent`
${name}.outro( function () {
${name} = null;
});
` :
deindent`
${name}.destroy( true );
${name} = null;
`;
if ( dynamic ) {
block.builders.update.addBlock( deindent`
if ( ${branch.condition} ) {
@ -91,8 +104,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
${name}.mount( ${parentNode}, ${anchor} );
}
} else if ( ${name} ) {
${name}.destroy( true );
${name} = null;
${remove}
}
` );
} else {
@ -103,8 +115,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
${name}.mount( ${parentNode}, ${anchor} );
}
} else if ( ${name} ) {
${name}.destroy( true );
${name} = null;
${remove}
}
` );
}
@ -135,6 +146,10 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
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 ) {
block.builders.update.addBlock( deindent`
if ( ${current_block} === ( ${current_block} = ${getBlock}( ${params} ) ) && ${name} ) {

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

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

Loading…
Cancel
Save