8000 Transitions by Rich-Harris · Pull Request #525 · sveltejs/svelte · GitHub
[go: up one dir, main page]

Skip to content

Transitions #525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
May 3, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2784ae0
parse transition directives
Rich-Harris Apr 25, 2017
4fa7765
failing test for intro transition
Rich-Harris Apr 25, 2017
9df2243
Merge branch 'master' into gh-7
Rich-Harris Apr 25, 2017
d0c0fbc
add transitions property to default export, track intros/outros in Block
Rich-Harris Apr 26, 2017
8ccad1f
first working intro transition, woooo
Rich-Harris Apr 26, 2017
6ed2a6c
update tests
Rich-Harris Apr 26, 2017
53c5c32
allow parameter-less transitions
Rich-Harris Apr 26, 2017
8000
2a5b0ee
support very basic outro transitions
Rich-Harris Apr 26, 2017
7f76ab2
Merge branch 'master' into gh-7
Rich-Harris Apr 30, 2017
aa67f8b
abort transitions
Rich-Harris Apr 30, 2017
45a9ce0
handle bidirectional transitions differently
Rich-Harris Apr 30, 2017
806b098
CSS transitions
Rich-Harris Apr 30, 2017
d63f80f
never abort transitions, they are either bidi or non-abortable
Rich-Harris Apr 30, 2017
5638a76
restart animations on secondary intro, various bits of cleanup
Rich-Harris Apr 30, 2017
5bee31f
get basic intro transition test passing
Rich-Harris May 1, 2017
26ed672
some more transition tests, albeit somewhat ugly
Rich-Harris May 1, 2017
dfe00d8
support dynamic simple if-blocks
Rich-Harris May 1, 2017
ec0e4a6
support transitions in compound if-blocks
Rich-Harris May 1, 2017
07f6ec5
only apply easing function once!
Rich-Harris May 1, 2017
f5bc3e3
remove method is unused
Rich-Harris May 1, 2017
f76fac2
tighten up transition tests
Rich-Harris May 1, 2017
65064cb
improve deindent slightly — allow inline false expressions (which get…
Rich-Harris May 1, 2017
a2cd983
intro transitions in each-blocks
Rich-Harris May 1, 2017
2d533f9
remove redundant ternary
Rich-Harris May 1, 2017
42af2bb
fix mount order of keyed each-block with intros
Rich-Harris May 1, 2017
f06eced
unkeyed each-blocks with outros
Rich-Harris May 1, 2017
22ac50a
outros on keyed each-blocks
Rich-Harris May 1, 2017
b8affd4
simplify/unify transitions
Rich-Harris May 1, 2017
8da7019
rename styles method to css - less ambiguity over what it returns, no…
Rich-Harris May 3, 2017
dee8694
merge master -> gh-7
Rich-Harris May 3, 2017
e796fef
stringify helpers before bundling
Rich-Harris May 3, 2017
bdfa01b
fix script path, duh
Rich-Harris May 3, 2017
d7b3f2e
lint after build
Rich-Harris May 3, 2017
90adb3b
gah node 4
Rich-Harris May 3, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
first working intro transition, woooo
  • Loading branch information
Rich-Harris committed Apr 26, 2017
commit 8ccad1f10793470162e5070d081acd3717cb41bf
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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": &quo 10000 t;^0.5.6",
"source-map-support": "^0.4.8"
},
Expand Down
3 changes: 2 additions & 1 deletion src/generators/Generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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 );
Expand Down
6 changes: 0 additions & 6 deletions src/generators/dom/Block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,` );
Expand Down
29 changes: 21 additions & 8 deletions src/generators/dom/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 = [];` );
}

Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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 );

Expand All @@ -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 } );
Expand Down
2 changes: 1 addition & 1 deletion src/generators/dom/visitors/Component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions src/generators/dom/visitors/Element/Transition.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
16 changes: 4 additions & 12 deletions src/shared/index.js
Original file line number Diff line number Diff line change
@@ -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 10000 ) {
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' ) );
Expand Down Expand Up @@ -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 () {
Expand Down
118 changes: 118 additions & 0 deletions src/shared/transitions.js
Original file line number Diff line number Diff line change
@@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to support older versions of IE? Not sure if there's been a ton of discussion on it, but in this case performance.now() is only supported in IE10+ https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. I don't have a strong view. I reckon it's probably ok, since it's easily polyfilled, but perhaps we need to think about having a section of the docs that says which polyfills you'll likely need for which version of IE.

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 );
}
}
};
10 changes: 10 additions & 0 deletions src/shared/utils.js
Original file line number Diff line number Diff line change
@@ -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];
}

8000 return target;
}
44 changes: 44 additions & 0 deletions src/utils/toSource.js
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion src/validate/html/validateElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}
}

Expand Down
Loading
0