first working intro transition, woooo

pull/525/head
Rich-Harris 7 years ago
parent d0c0fbcef4
commit 8ccad1f107

@ -20,6 +20,7 @@
"build:main": "rollup -c rollup/rollup.config.main.js",
"build:shared": "rollup -c rollup/rollup.config.shared.js",
"build:ssr": "rollup -c rollup/rollup.config.ssr.js",
"dev": "rollup -c rollup/rollup.config.main.js -w",
"pretest": "npm run build",
"prepublish": "npm run lint && npm run build"
},
@ -72,6 +73,7 @@
"rollup-plugin-commonjs": "^7.0.0",
"rollup-plugin-json": "^2.1.0",
"rollup-plugin-node-resolve": "^2.0.0",
"rollup-watch": "^3.2.2",
"source-map": "^0.5.6",
"source-map-support": "^0.4.8"
},

@ -24,6 +24,7 @@ export default class Generator {
this.helpers = new Set();
this.components = new Set();
this.events = new Set();
this.transitions = new Set();
this.importedComponents = new Map();
this.bindingGroups = [];
@ -328,7 +329,7 @@ export default class Generator {
});
}
[ 'helpers', 'events', 'components' ].forEach( key => {
[ 'helpers', 'events', 'components', 'transitions' ].forEach( key => {
if ( templateProperties[ key ] ) {
templateProperties[ key ].value.properties.forEach( prop => {
this[ key ].add( prop.key.name );

@ -138,12 +138,6 @@ export default class Block {
properties.addBlock( `key: ${localKey},` );
}
if ( this.intros.length ) {
properties.addBlock( `intro: ${this.generator.helper( 'transition' )}([
// TODO
]),` );
}
if ( this.outros.length ) {
// TODO
properties.addLine( `outro: null,` );

@ -1,10 +1,11 @@
import MagicString from 'magic-string';
import { parse } from 'acorn';
import { parseExpressionAt } from 'acorn';
import annotateWithScopes from '../../utils/annotateWithScopes.js';
import isReference from '../../utils/isReference.js';
import { walk } from 'estree-walker';
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import toSource from '../../utils/toSource.js';
import visit from './visit.js';
import Generator from '../Generator.js';
import preprocess from './preprocess.js';
@ -138,7 +139,7 @@ export default function dom ( parsed, source, options ) {
builders.init.addLine( `if ( !${generator.alias( 'added_css' )} ) ${generator.alias( 'add_css' )}();` );
}
if ( generator.hasComponents ) {
if ( generator.hasComponents || generator.hasIntroTransitions ) {
builders.init.addLine( `this._renderHooks = [];` );
}
@ -218,7 +219,7 @@ export default function dom ( parsed, source, options ) {
this._handlers = Object.create( null );
this._root = options._root;
this._root = options._root || this;
this._yield = options._yield;
${builders.init}
@ -275,9 +276,10 @@ export default function dom ( parsed, source, options ) {
);
} else {
generator.uses.forEach( key => {
const str = shared[ key ].toString(); // eslint-disable-line import/namespace
const value = shared[ key ]; // eslint-disable-line import/namespace
const str = toSource( value );
const code = new MagicString( str );
const fn = parse( str ).body[0];
const fn = parseExpressionAt( str, 0 );
let scope = annotateWithScopes( fn );
@ -301,11 +303,22 @@ export default function dom ( parsed, source, options ) {
}
});
const alias = generator.alias( fn.id.name );
if ( alias !== fn.id.name ) code.overwrite( fn.id.start, fn.id.end, alias );
if ( typeof value === 'function' ) { // exclude transitionManager — special case
const alias = generator.alias( fn.id.name );
if ( alias !== fn.id.name ) code.overwrite( fn.id.start, fn.id.end, alias );
builders.main.addBlock( code.toString() );
builders.main.addBlock( code.toString() );
}
});
if ( generator.hasIntroTransitions || generator.hasOutroTransitions ) {
const global = `_svelteTransitionManager`;
const transitionManager = toSource( shared.transitionManager );
builders.main.addBlock(
`var ${generator.alias( 'transitionManager' )} = window.${global} || ( window.${global} = ${transitionManager});`
);
}
}
return generator.generate( builders.main.toString(), options, { name, format } );

@ -92,7 +92,7 @@ export default function visitComponent ( generator, block, state, node ) {
const componentInitProperties = [
`target: ${!isToplevel ? state.parentNode: 'null'}`,
`_root: ${block.component}._root || ${block.component}`
`_root: ${block.component}._root`
];
// Component has children, put them in a separate {{yield}} block

@ -1,4 +1,27 @@
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 );
if ( attribute.intro ) {
generator.hasIntroTransitions = true;
const { snippet } = block.contextualise( attribute.expression );
const fn = `${generator.alias( 'template' )}.transitions.${attribute.name}`; // TODO add built-in transitions?
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}
});
` );
}
( attribute.intro ? block.intros : block.outros ).push({
node: state.name,
transition: attribute.name,

@ -1,15 +1,7 @@
import { assign } from './utils.js';
export * from './dom.js';
export function noop () {}
export function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) {
var source = arguments[i];
for ( var k in source ) target[k] = source[k];
}
return target;
}
export * from './transitions.js';
export * from './utils.js';
export function differs ( a, b ) {
return ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) );
@ -107,7 +99,7 @@ export function onDev ( eventName, handler ) {
export function set ( newState ) {
this._set( assign( {}, newState ) );
( this._root || this )._flush();
this._root._flush();
}
export function _flush () {

@ -0,0 +1,118 @@
import { noop } from './utils.js';
export function linear ( t ) {
return t;
}
export function wrapTransition ( node, fn, params, isIntro ) {
var obj = fn( node, params, isIntro );
var start = window.performance.now() + ( obj.delay || 0 );
var duration = obj.duration || 300;
var end = start + duration;
var ease = obj.easing || linear;
if ( obj.tick ) {
// JS transition
if ( isIntro ) obj.tick( 0 );
return {
start: start,
end: end,
update: function ( now ) {
obj.tick( ease( ( now - start ) / duration ) );
},
done: function () {
obj.tick( isIntro ? 1 : 0 );
},
abort: noop
};
} else {
// CSS transition
var started = false;
var inlineStyles = {};
var computedStyles = getComputedStyle( node );
return {
start: start,
end: end,
init: function () {
for ( var key in obj.styles ) {
inlineStyles[ key ] = node.style[ key ];
node.style[ key ] = isIntro ? obj.styles[ key ] : computedStyles[ key ];
}
},
update: function ( now ) {
if ( !started ) {
var keys = Object.keys( obj.styles );
div.style.transition = keys.map( function ( key ) {
return key + ' ' + d;
}).join( ', ' );
// TODO use a keyframe animation for custom easing functions
for ( var key in obj.styles ) {
node.style[ key ] = isIntro ? computedStyles[ key ] : obj.styles[ key ];
}
started = true;
}
},
done: function () {
// TODO what if one of these styles was dynamic?
if ( isIntro ) {
for ( var key in obj.styles ) {
node.style[ key ] = inlineStyles[ key ];
}
}
},
abort: function () {
node.style.cssText = getComputedStyle( node ).cssText;
}
};
}
}
export var transitionManager = {
running: false,
transitions: [],
add: function ( transition ) {
transitionManager.transitions.push( transition );
if ( !this.running ) {
this.running = true;
this.next();
}
},
remove: function ( transitions ) {
var i = transitions.length;
while ( i-- ) {
var index = this.transitions.indexOf( transitions[i] );
if ( ~index ) this.transitions.splice( index, 1 );
}
},
next: function () {
transitionManager.running = false;
var now = window.performance.now();
var i = transitionManager.transitions.length;
while ( i-- ) {
var transition = transitionManager.transitions[i];
if ( now >= transition.end ) {
transition.done();
transitionManager.transitions.splice( i, 1 );
} else {
if ( now > transition.start ) transition.update( now );
transitionManager.running = true;
}
}
if ( transitionManager.running ) {
requestAnimationFrame( transitionManager.next );
}
}
};

@ -0,0 +1,10 @@
export function noop () {}
export function assign ( target ) {
for ( var i = 1; i < arguments.length; i += 1 ) {
var source = arguments[i];
for ( var k in source ) target[k] = source[k];
}
return target;
}

@ -0,0 +1,44 @@
import deindent from './deindent.js';
export default function toSource ( thing ) {
if ( typeof thing === 'function' ) {
return normaliseIndentation( thing.toString() );
}
if ( Array.isArray( thing ) ) {
if ( thing.length === 0 ) return '[]';
throw new Error( 'TODO' ); // not currently needed
}
if ( thing && typeof thing === 'object' ) {
const keys = Object.keys( thing );
if ( keys.length === 0 ) return '{}';
const props = keys.map( key => `${key}: ${toSource( thing[ key ] )}` ).join( ',\n' );
return deindent`
{
${props}
}
`;
}
return JSON.stringify( thing );
}
function normaliseIndentation ( str ) {
const lines = str.split( '\n' ).slice( 1, -1 );
let minIndentation = Infinity;
lines.forEach( line => {
if ( !/\S/.test( line ) ) return;
const indentation = /^\t*/.exec( line )[0].length;
if ( indentation < minIndentation ) minIndentation = indentation;
});
if ( minIndentation !== Infinity && minIndentation !== 1 ) {
const pattern = new RegExp( `^\\t{${minIndentation - 1}}`, 'gm' );
return str.replace( pattern, '' );
}
return str;
}

@ -25,7 +25,7 @@ export default function validateElement ( validator, node ) {
}
if ( getType( validator, node ) !== 'checkbox' ) {
validator.error( `'checked' binding can only be used with <input type="checkbox">` );
validator.error( `'checked' binding can only be used with <input type="checkbox">`, attribute.start );
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save