diff --git a/src/compile/dom/Block.ts b/src/compile/dom/Block.ts index cde0eb0f3f..a6cf1e75d2 100644 --- a/src/compile/dom/Block.ts +++ b/src/compile/dom/Block.ts @@ -33,6 +33,9 @@ export default class Block { claim: CodeBuilder; hydrate: CodeBuilder; mount: CodeBuilder; + measure: CodeBuilder; + fix: CodeBuilder; + animate: CodeBuilder; intro: CodeBuilder; update: CodeBuilder; outro: CodeBuilder; @@ -40,7 +43,7 @@ export default class Block { }; maintainContext: boolean; - animation?: string; + hasAnimation: boolean; hasIntroMethod: boolean; hasOutroMethod: boolean; outros: number; @@ -72,13 +75,16 @@ export default class Block { claim: new CodeBuilder(), hydrate: new CodeBuilder(), mount: new CodeBuilder(), + measure: new CodeBuilder(), + fix: new CodeBuilder(), + animate: new CodeBuilder(), intro: new CodeBuilder(), update: new CodeBuilder(), outro: new CodeBuilder(), destroy: new CodeBuilder(), }; - this.animation = null; + this.hasAnimation = false; this.hasIntroMethod = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros this.hasOutroMethod = false; this.outros = 0; @@ -129,8 +135,8 @@ export default class Block { this.outros += 1; } - addAnimation(name) { - this.animation = name; + addAnimation() { + this.hasAnimation = true; } addVariable(name: string, init?: string) { @@ -189,11 +195,6 @@ export default class Block { this.builders.hydrate.addLine(`this.first = ${this.first};`); } - if (this.animation) { - properties.addBlock(`node: null,`); - this.builders.hydrate.addLine(`this.node = ${this.animation};`); - } - if (this.builders.create.isEmpty() && this.builders.hydrate.isEmpty()) { properties.addBlock(`c: @noop,`); } else { @@ -255,6 +256,22 @@ export default class Block { } } + if (this.hasAnimation) { + properties.addBlock(deindent` + ${dev ? `r: function measure` : `r`}() { + ${this.builders.measure} + }, + + ${dev ? `f: function fix` : `f`}() { + ${this.builders.fix} + }, + + ${dev ? `a: function animate` : `a`}() { + ${this.builders.animate} + }, + `); + } + if (this.hasIntroMethod || this.hasOutroMethod) { if (hasIntros) { properties.addBlock(deindent` diff --git a/src/compile/nodes/Animation.ts b/src/compile/nodes/Animation.ts index 8041f26485..cf9ec35143 100644 --- a/src/compile/nodes/Animation.ts +++ b/src/compile/nodes/Animation.ts @@ -1,3 +1,4 @@ +import Block from '../dom/Block'; import Node from './shared/Node'; import Expression from './shared/Expression'; @@ -15,4 +16,12 @@ export default class Animation extends Node { ? new Expression(compiler, this, scope, info.expression) : null; } + + build( + block: Block, + parentNode: string, + parentNodes: string + ) { + + } } \ No newline at end of file diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 35c314bfda..70ce3ab218 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -315,7 +315,7 @@ export default class EachBlock extends Node { const dynamic = this.block.hasUpdateMethod; const rects = block.getUniqueName('rects'); - const destroy = this.block.animation + const destroy = this.block.hasAnimation ? `@fixAndOutroAndDestroyBlock` : this.block.hasOutroMethod ? `@outroAndDestroyBlock` @@ -325,9 +325,9 @@ export default class EachBlock extends Node { const ${this.each_block_value} = ${snippet}; ${this.block.hasOutroMethod && `@transitionManager.groupOutros();`} - ${this.block.animation && `const ${rects} = @measure(${blocks});`} + ${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`} ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); - ${this.block.animation && `@animate(${blocks}, ${rects}, %animations-${this.children[0].animation.name}, {});`} + ${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`} `); if (this.compiler.options.nestedTransitions) { diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 58eeafafeb..c897ca3b2c 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -195,7 +195,7 @@ export default class Element extends Node { if (this.intro) block.addIntro(); if (this.outro) block.addOutro(); - if (this.animation) block.addAnimation(this.var); + if (this.animation) block.addAnimation(); const valueAttribute = this.attributes.find((attribute: Attribute) => attribute.name === 'value'); @@ -368,6 +368,7 @@ export default class Element extends Node { if (this.ref) this.addRef(block); this.addAttributes(block); this.addTransitions(block); + this.addAnimation(block); this.addActions(block); if (this.initialUpdate) { @@ -764,6 +765,31 @@ export default class Element extends Node { } } + addAnimation(block: Block) { + if (!this.animation) return; + + const rect = block.getUniqueName('rect'); + const animation = block.getUniqueName('animation'); + + block.addVariable(rect); + block.addVariable(animation); + + block.builders.measure.addBlock(deindent` + ${rect} = ${this.var}.getBoundingClientRect(); + `); + + block.builders.fix.addBlock(deindent` + @fixPosition(${this.var}); + if (${animation}) ${animation}.stop(); + `); + + const params = this.animation.expression ? this.animation.expression.snippet : '{}'; + block.builders.animate.addBlock(deindent` + if (${animation}) ${animation}.stop(); + ${animation} = @wrapAnimation(${this.var}, ${rect}, %animations-${this.animation.name}, ${params}); + `); + } + addActions(block: Block) { this.actions.forEach(action => { const { expression } = action; diff --git a/src/shared/animations.js b/src/shared/animations.js new file mode 100644 index 0000000000..355c0254e7 --- /dev/null +++ b/src/shared/animations.js @@ -0,0 +1,102 @@ +import { transitionManager, linear, generateRule, hash } from './transitions.js'; + +export function wrapAnimation(node, from, fn, params) { + if (!from) return; + + const to = node.getBoundingClientRect(); + if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return; + + const info = fn(node, { from, to }, params); + + const duration = 'duration' in info ? info.duration : 300; + const delay = 'delay' in info ? info.delay : 0; + const ease = info.easing || linear; + const start = window.performance.now() + delay; + const end = start + duration; + + const program = { + a: 0, + t: 0, + b: 1, + delta: 1, + duration, + start, + end + }; + + const cssText = node.style.cssText; + + const animation = { + pending: delay ? program : null, + program: delay ? null : program, + running: true, + + start() { + if (info.css) { + if (delay) node.style.cssText = cssText; + + const rule = generateRule(program, ease, info.css); + program.name = `__svelte_${hash(rule)}`; + + transitionManager.addRule(rule, program.name); + + node.style.animation = (node.style.animation || '') + .split(', ') + .filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim))) + .concat(`${program.name} ${program.duration}ms linear 1 forwards`) + .join(', '); + } + + animation.program = program; + animation.pending = null; + }, + + update: now => { + const p = now - program.start; + const t = program.a + program.delta * ease(p / program.duration); + if (info.tick) info.tick(t, 1 - t); + }, + + done() { + if (info.tick) info.tick(1, 0); + animation.stop(); + }, + + stop() { + if (info.css) transitionManager.deleteRule(node, program.name); + animation.running = false; + } + }; + + transitionManager.add(animation); + + if (info.tick) info.tick(0, 1); + + if (delay) { + if (info.css) node.style.cssText += info.css(0, 1); + } else { + animation.start(); + } + + return animation; +} + +export function fixPosition(node) { + const style = getComputedStyle(node); + + if (style.position !== 'absolute' && style.position !== 'fixed') { + const { width, height } = style; + const a = node.getBoundingClientRect(); + node.style.position = 'absolute'; + node.style.width = width; + node.style.height = height; + const b = node.getBoundingClientRect(); + + if (a.left !== b.left || a.top !== b.top) { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`; + } + } +} \ No newline at end of file diff --git a/src/shared/index.js b/src/shared/index.js index cf82282ad8..c9d901af64 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -1,5 +1,6 @@ import { assign } from './utils.js'; import { noop } from './utils.js'; +export * from './animations.js'; export * from './await-block.js'; export * from './dom.js'; export * from './keyed-each.js'; diff --git a/src/shared/keyed-each.js b/src/shared/keyed-each.js index 5e7d7281c6..9ec3d1c622 100644 --- a/src/shared/keyed-each.js +++ b/src/shared/keyed-each.js @@ -12,25 +12,7 @@ export function outroAndDestroyBlock(block, lookup) { } export function fixAndOutroAndDestroyBlock(block, lookup) { - const { node } = block; - const style = getComputedStyle(node); - - if (style.position !== 'absolute' && style.position !== 'fixed') { - const { width, height } = style; - const a = node.getBoundingClientRect(); - node.style.position = 'absolute'; - node.style.width = width; - node.style.height = height; - const b = node.getBoundingClientRect(); - - if (a.left !== b.left || a.top !== b.top) { - const style = getComputedStyle(node); - const transform = style.transform === 'none' ? '' : style.transform; - - node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`; - } - } - + block.f(); outroAndDestroyBlock(block, lookup); } @@ -121,10 +103,10 @@ export function updateKeyedEach(old_blocks, component, changed, get_key, dynamic } export function measure(blocks) { - const measurements = {}; + const rects = {}; let i = blocks.length; - while (i--) measurements[blocks[i].key] = blocks[i].node.getBoundingClientRect(); - return measurements; + while (i--) rects[blocks[i].key] = blocks[i].node.getBoundingClientRect(); + return rects; } export function animate(blocks, rects, fn, params) { @@ -138,67 +120,6 @@ export function animate(blocks, rects, fn, params) { if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) continue; - const info = fn(block.node, { from, to }, params); - - const duration = 'duration' in info ? info.duration : 300; - const delay = 'delay' in info ? info.delay : 0; - const ease = info.easing || linear; - const start = window.performance.now() + delay; - const end = start + duration; - - const program = { - a: 0, - t: 0, - b: 1, - delta: 1, - duration, - start, - end - }; - - const animation = { - pending: delay ? program : null, - program: delay ? null : program, - running: !delay, - - start() { - if (info.css) { - const rule = generateRule(program, ease, info.css); - program.name = `__svelte_${hash(rule)}`; - - transitionManager.addRule(rule, program.name); - - block.node.style.animation = (block.node.style.animation || '') - .split(', ') - .filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim))) - .concat(`${program.name} ${program.duration}ms linear 1 forwards`) - .join(', '); - } - }, - - update: now => { - const p = now - program.start; - const t = program.a + program.delta * ease(p / program.duration); - if (info.tick) info.tick(t, 1 - t); - }, - - done() { - if (info.css) { - transitionManager.deleteRule(block.node, program.name); - } - - if (info.tick) { - info.tick(1, 0); - } - - animation.running = false; - } - }; - - transitionManager.add(animation); - - if (info.tick) info.tick(0, 1); - - if (!delay) animation.start(); + } } \ No newline at end of file diff --git a/test/js/samples/each-block-keyed-animated/expected-bundle.js b/test/js/samples/each-block-keyed-animated/expected-bundle.js new file mode 100644 index 0000000000..54876c6152 --- /dev/null +++ b/test/js/samples/each-block-keyed-animated/expected-bundle.js @@ -0,0 +1,589 @@ +function noop() {} + +function assign(tar, src) { + for (var k in src) tar[k] = src[k]; + return tar; +} + +function appendNode(node, target) { + target.appendChild(node); +} + +function insertNode(node, target, anchor) { + target.insertBefore(node, anchor); +} + +function detachNode(node) { + node.parentNode.removeChild(node); +} + +function createElement(name) { + return document.createElement(name); +} + +function createText(data) { + return document.createTextNode(data); +} + +function createComment() { + return document.createComment(''); +} + +function linear(t) { + return t; +} + +function generateRule({ a, b, delta, duration }, ease, fn) { + const step = 16.666 / duration; + let keyframes = '{\n'; + + for (let p = 0; p <= 1; p += step) { + const t = a + delta * ease(p); + keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`; + } + + return keyframes + `100% {${fn(b, 1 - b)}}\n}`; +} + +// https://github.com/darkskyapp/string-hash/blob/master/index.js +function hash(str) { + let hash = 5381; + let i = str.length; + + while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); + return hash >>> 0; +} + +var transitionManager = { + running: false, + transitions: [], + bound: null, + stylesheet: null, + activeRules: {}, + promise: null, + + add(transition) { + this.transitions.push(transition); + + if (!this.running) { + this.running = true; + requestAnimationFrame(this.bound || (this.bound = this.next.bind(this))); + } + }, + + addRule(rule, name) { + if (!this.stylesheet) { + const style = createElement('style'); + document.head.appendChild(style); + transitionManager.stylesheet = style.sheet; + } + + if (!this.activeRules[name]) { + this.activeRules[name] = true; + this.stylesheet.insertRule(`@keyframes ${name} ${rule}`, this.stylesheet.cssRules.length); + } + }, + + next() { + this.running = false; + + const now = window.performance.now(); + let i = this.transitions.length; + + while (i--) { + const transition = this.transitions[i]; + + if (transition.program && now >= transition.program.end) { + transition.done(); + } + + if (transition.pending && now >= transition.pending.start) { + transition.start(transition.pending); + } + + if (transition.running) { + transition.update(now); + this.running = true; + } else if (!transition.pending) { + this.transitions.splice(i, 1); + } + } + + if (this.running) { + requestAnimationFrame(this.bound); + } else if (this.stylesheet) { + let i = this.stylesheet.cssRules.length; + while (i--) this.stylesheet.deleteRule(i); + this.activeRules = {}; + } + }, + + deleteRule(node, name) { + node.style.animation = node.style.animation + .split(', ') + .filter(anim => anim && anim.indexOf(name) === -1) + .join(', '); + }, + + groupOutros() { + this.outros = { + remaining: 0, + callbacks: [] + }; + }, + + wait() { + if (!transitionManager.promise) { + transitionManager.promise = Promise.resolve(); + transitionManager.promise.then(() => { + transitionManager.promise = null; + }); + } + + return transitionManager.promise; + } +}; + +function wrapAnimation(node, from, fn, params) { + if (!from) return; + + const to = node.getBoundingClientRect(); + if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return; + + const info = fn(node, { from, to }, params); + + const duration = 'duration' in info ? info.duration : 300; + const delay = 'delay' in info ? info.delay : 0; + const ease = info.easing || linear; + const start = window.performance.now() + delay; + const end = start + duration; + + const program = { + a: 0, + t: 0, + b: 1, + delta: 1, + duration, + start, + end + }; + + const cssText = node.style.cssText; + + const animation = { + pending: delay ? program : null, + program: delay ? null : program, + running: true, + + start() { + if (info.css) { + if (delay) node.style.cssText = cssText; + + const rule = generateRule(program, ease, info.css); + program.name = `__svelte_${hash(rule)}`; + + transitionManager.addRule(rule, program.name); + + node.style.animation = (node.style.animation || '') + .split(', ') + .filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim))) + .concat(`${program.name} ${program.duration}ms linear 1 forwards`) + .join(', '); + } + + animation.program = program; + animation.pending = null; + }, + + update: now => { + const p = now - program.start; + const t = program.a + program.delta * ease(p / program.duration); + if (info.tick) info.tick(t, 1 - t); + }, + + done() { + if (info.tick) info.tick(1, 0); + animation.stop(); + }, + + stop() { + if (info.css) transitionManager.deleteRule(node, program.name); + animation.running = false; + } + }; + + transitionManager.add(animation); + + if (info.tick) info.tick(0, 1); + + if (delay) { + if (info.css) node.style.cssText += info.css(0, 1); + } else { + animation.start(); + } + + return animation; +} + +function fixPosition(node) { + const style = getComputedStyle(node); + + if (style.position !== 'absolute' && style.position !== 'fixed') { + const { width, height } = style; + const a = node.getBoundingClientRect(); + node.style.position = 'absolute'; + node.style.width = width; + node.style.height = height; + const b = node.getBoundingClientRect(); + + if (a.left !== b.left || a.top !== b.top) { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`; + } + } +} + +function destroyBlock(block, lookup) { + block.d(1); + lookup[block.key] = null; +} + +function outroAndDestroyBlock(block, lookup) { + block.o(function() { + destroyBlock(block, lookup); + }); +} + +function fixAndOutroAndDestroyBlock(block, lookup) { + block.f(); + outroAndDestroyBlock(block, lookup); +} + +function updateKeyedEach(old_blocks, component, changed, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, intro_method, next, get_context) { + var o = old_blocks.length; + var n = list.length; + + var i = o; + var old_indexes = {}; + while (i--) old_indexes[old_blocks[i].key] = i; + + var new_blocks = []; + var new_lookup = {}; + var deltas = {}; + + var i = n; + while (i--) { + var child_ctx = get_context(ctx, list, i); + var key = get_key(child_ctx); + var block = lookup[key]; + + if (!block) { + block = create_each_block(component, key, child_ctx); + block.c(); + } else if (dynamic) { + block.p(changed, child_ctx); + } + + new_blocks[i] = new_lookup[key] = block; + + if (key in old_indexes) deltas[key] = Math.abs(i - old_indexes[key]); + } + + var will_move = {}; + var did_move = {}; + + function insert(block) { + block[intro_method](node, next); + lookup[block.key] = block; + next = block.first; + n--; + } + + while (o && n) { + var new_block = new_blocks[n - 1]; + var old_block = old_blocks[o - 1]; + var new_key = new_block.key; + var old_key = old_block.key; + + if (new_block === old_block) { + // do nothing + next = new_block.first; + o--; + n--; + } + + else if (!new_lookup[old_key]) { + // remove old block + destroy(old_block, lookup); + o--; + } + + else if (!lookup[new_key] || will_move[new_key]) { + insert(new_block); + } + + else if (did_move[old_key]) { + o--; + + } else if (deltas[new_key] > deltas[old_key]) { + did_move[new_key] = true; + insert(new_block); + + } else { + will_move[old_key] = true; + o--; + } + } + + while (o--) { + var old_block = old_blocks[o]; + if (!new_lookup[old_block.key]) destroy(old_block, lookup); + } + + while (n) insert(new_blocks[n - 1]); + + return new_blocks; +} + +function blankObject() { + return Object.create(null); +} + +function destroy(detach) { + this.destroy = noop; + this.fire('destroy'); + this.set = noop; + + this._fragment.d(detach !== false); + this._fragment = null; + this._state = {}; +} + +function _differs(a, b) { + return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); +} + +function fire(eventName, data) { + var handlers = + eventName in this._handlers && this._handlers[eventName].slice(); + if (!handlers) return; + + for (var i = 0; i < handlers.length; i += 1) { + var handler = handlers[i]; + + if (!handler.__calling) { + handler.__calling = true; + handler.call(this, data); + handler.__calling = false; + } + } +} + +function get() { + return this._state; +} + +function init(component, options) { + component._handlers = blankObject(); + component._bind = options._bind; + + component.options = options; + component.root = options.root || component; + component.store = component.root.store || options.store; +} + +function on(eventName, handler) { + var handlers = this._handlers[eventName] || (this._handlers[eventName] = []); + handlers.push(handler); + + return { + cancel: function() { + var index = handlers.indexOf(handler); + if (~index) handlers.splice(index, 1); + } + }; +} + +function set(newState) { + this._set(assign({}, newState)); + if (this.root._lock) return; + this.root._lock = true; + callAll(this.root._beforecreate); + callAll(this.root._oncreate); + callAll(this.root._aftercreate); + this.root._lock = false; +} + +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign(assign({}, oldState), newState); + this._recompute(changed, this._state); + if (this._bind) this._bind(changed, this._state); + + if (this._fragment) { + this.fire("state", { changed: changed, current: this._state, previous: oldState }); + this._fragment.p(changed, this._state); + this.fire("update", { changed: changed, current: this._state, previous: oldState }); + } +} + +function callAll(fns) { + while (fns && fns.length) fns.shift()(); +} + +function _mount(target, anchor) { + this._fragment[this._fragment.i ? 'i' : 'm'](target, anchor || null); +} + +var proto = { + destroy, + get, + fire, + on, + set, + _recompute: noop, + _set, + _mount, + _differs +}; + +/* generated by Svelte vX.Y.Z */ + +function foo(node, animation, params) { + const dx = animation.from.left - animation.to.left; + const dy = animation.from.top - animation.to.top; + + return { + delay: params.delay, + duration: 100, + tick: (t, u) => { + node.dx = u * dx; + node.dy = u * dy; + } + }; +} +function create_main_fragment(component, ctx) { + var each_blocks_1 = [], each_lookup = blankObject(), each_anchor; + + var each_value = ctx.things; + + const get_key = ctx => ctx.thing.id; + + for (var i = 0; i < each_value.length; i += 1) { + let child_ctx = get_each_context(ctx, each_value, i); + let key = get_key(child_ctx); + each_blocks_1[i] = each_lookup[key] = create_each_block(component, key, child_ctx); + } + + return { + c() { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].c(); + + each_anchor = createComment(); + }, + + m(target, anchor) { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].m(target, anchor); + + insertNode(each_anchor, target, anchor); + }, + + p(changed, ctx) { + const each_value = ctx.things; + for (let i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].r(); + each_blocks_1 = updateKeyedEach(each_blocks_1, component, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, fixAndOutroAndDestroyBlock, create_each_block, "m", each_anchor, get_each_context); + for (let i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].a(); + }, + + d(detach) { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].d(detach); + + if (detach) { + detachNode(each_anchor); + } + } + }; +} + +// (1:0) {#each things as thing (thing.id)} +function create_each_block(component, key_1, ctx) { + var div, text_value = ctx.thing.name, text, rect, animation; + + return { + key: key_1, + + first: null, + + c() { + div = createElement("div"); + text = createText(text_value); + this.first = div; + }, + + m(target, anchor) { + insertNode(div, target, anchor); + appendNode(text, div); + }, + + p(changed, ctx) { + if ((changed.things) && text_value !== (text_value = ctx.thing.name)) { + text.data = text_value; + } + }, + + r() { + rect = div.getBoundingClientRect(); + }, + + f() { + fixPosition(div); + if (animation) animation.stop(); + }, + + a() { + if (animation) animation.stop(); + animation = wrapAnimation(div, rect, foo, {}); + }, + + d(detach) { + if (detach) { + detachNode(div); + } + } + }; +} + +function get_each_context(ctx, list, i) { + const child_ctx = Object.create(ctx); + child_ctx.thing = list[i]; + child_ctx.each_value = list; + child_ctx.thing_index = i; + return child_ctx; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); + +export default SvelteComponent; diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js new file mode 100644 index 0000000000..99ff9b03fe --- /dev/null +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -0,0 +1,131 @@ +/* generated by Svelte vX.Y.Z */ +import { appendNode, assign, blankObject, createComment, createElement, createText, detachNode, fixAndOutroAndDestroyBlock, fixPosition, init, insertNode, proto, updateKeyedEach, wrapAnimation } from "svelte/shared.js"; + +function foo(node, animation, params) { + const dx = animation.from.left - animation.to.left; + const dy = animation.from.top - animation.to.top; + + return { + delay: params.delay, + duration: 100, + tick: (t, u) => { + node.dx = u * dx; + node.dy = u * dy; + } + }; +}; + +function create_main_fragment(component, ctx) { + var each_blocks_1 = [], each_lookup = blankObject(), each_anchor; + + var each_value = ctx.things; + + const get_key = ctx => ctx.thing.id; + + for (var i = 0; i < each_value.length; i += 1) { + let child_ctx = get_each_context(ctx, each_value, i); + let key = get_key(child_ctx); + each_blocks_1[i] = each_lookup[key] = create_each_block(component, key, child_ctx); + } + + return { + c() { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].c(); + + each_anchor = createComment(); + }, + + m(target, anchor) { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].m(target, anchor); + + insertNode(each_anchor, target, anchor); + }, + + p(changed, ctx) { + const each_value = ctx.things; + for (let i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].r(); + each_blocks_1 = updateKeyedEach(each_blocks_1, component, changed, get_key, 1, ctx, each_value, each_lookup, each_anchor.parentNode, fixAndOutroAndDestroyBlock, create_each_block, "m", each_anchor, get_each_context); + for (let i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].a(); + }, + + d(detach) { + for (i = 0; i < each_blocks_1.length; i += 1) each_blocks_1[i].d(detach); + + if (detach) { + detachNode(each_anchor); + } + } + }; +} + +// (1:0) {#each things as thing (thing.id)} +function create_each_block(component, key_1, ctx) { + var div, text_value = ctx.thing.name, text, rect, animation; + + return { + key: key_1, + + first: null, + + c() { + div = createElement("div"); + text = createText(text_value); + this.first = div; + }, + + m(target, anchor) { + insertNode(div, target, anchor); + appendNode(text, div); + }, + + p(changed, ctx) { + if ((changed.things) && text_value !== (text_value = ctx.thing.name)) { + text.data = text_value; + } + }, + + r() { + rect = div.getBoundingClientRect(); + }, + + f() { + fixPosition(div); + if (animation) animation.stop(); + }, + + a() { + if (animation) animation.stop(); + animation = wrapAnimation(div, rect, foo, {}); + }, + + d(detach) { + if (detach) { + detachNode(div); + } + } + }; +} + +function get_each_context(ctx, list, i) { + const child_ctx = Object.create(ctx); + child_ctx.thing = list[i]; + child_ctx.each_value = list; + child_ctx.thing_index = i; + return child_ctx; +} + +function SvelteComponent(options) { + init(this, options); + this._state = assign({}, options.data); + this._intro = true; + + this._fragment = create_main_fragment(this, this._state); + + if (options.target) { + this._fragment.c(); + this._mount(options.target, options.anchor); + } +} + +assign(SvelteComponent.prototype, proto); +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/each-block-keyed-animated/input.html b/test/js/samples/each-block-keyed-animated/input.html new file mode 100644 index 0000000000..8c2e2f872b --- /dev/null +++ b/test/js/samples/each-block-keyed-animated/input.html @@ -0,0 +1,23 @@ +{#each things as thing (thing.id)} +
{thing.name}
+{/each} + + \ No newline at end of file diff --git a/test/runtime/samples/animation-js-delay/_config.js b/test/runtime/samples/animation-js-delay/_config.js new file mode 100644 index 0000000000..eb0c7863e3 --- /dev/null +++ b/test/runtime/samples/animation-js-delay/_config.js @@ -0,0 +1,62 @@ +export default { + data: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ] + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test(assert, component, target, window, raf) { + let divs = document.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + } + }; + }) + + component.set({ + things: [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ] + }); + + divs = document.querySelectorAll('div'); + assert.equal(divs[0].dy, 120); + assert.equal(divs[4].dy, -120); + + raf.tick(50); + assert.equal(divs[0].dy, 108); + assert.equal(divs[4].dy, -60); + + raf.tick(100); + assert.equal(divs[0].dy, 48); + assert.equal(divs[4].dy, 0); + + raf.tick(150); + assert.equal(divs[0].dy, 0); + assert.equal(divs[4].dy, 0); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/animation-js-delay/main.html b/test/runtime/samples/animation-js-delay/main.html new file mode 100644 index 0000000000..5656f39700 --- /dev/null +++ b/test/runtime/samples/animation-js-delay/main.html @@ -0,0 +1,23 @@ +{#each things as thing, i (thing.id)} +
{thing.name}
+{/each} + + \ No newline at end of file