From dbe7d797d16fecc429f92082b7880f0d20d9be47 Mon Sep 17 00:00:00 2001 From: pushkine Date: Sun, 31 May 2020 22:28:33 +0200 Subject: [PATCH 01/12] init --- src/compiler/compile/render_dom/Block.ts | 4 + .../compile/render_dom/wrappers/EachBlock.ts | 35 +- .../render_dom/wrappers/Element/index.ts | 236 ++++----- .../compile/render_dom/wrappers/IfBlock.ts | 17 +- .../wrappers/InlineComponent/index.ts | 9 +- src/compiler/compile/utils/bit_state.ts | 1 + src/runtime/animate/index.ts | 41 +- src/runtime/internal/Component.ts | 37 +- src/runtime/internal/animations.ts | 141 ++--- src/runtime/internal/await_block.ts | 10 +- src/runtime/internal/dev.ts | 3 +- src/runtime/internal/dom.ts | 27 +- src/runtime/internal/environment.ts | 49 +- src/runtime/internal/globals.ts | 7 - src/runtime/internal/index.ts | 1 - src/runtime/internal/keyed_each.ts | 85 ++- src/runtime/internal/lifecycle.ts | 4 +- src/runtime/internal/loop.ts | 145 ++++-- src/runtime/internal/scheduler.ts | 141 ++--- src/runtime/internal/style_manager.ts | 128 +++-- src/runtime/internal/transitions.ts | 490 ++++++------------ src/runtime/internal/utils.ts | 9 +- src/runtime/transition/index.ts | 270 +++------- 23 files changed, 767 insertions(+), 1123 deletions(-) create mode 100644 src/compiler/compile/utils/bit_state.ts delete mode 100644 src/runtime/internal/globals.ts diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index b77cf6111253..c1a3fdd098d9 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -205,6 +205,10 @@ export default class Block { this.has_animation = true; } + group_transition_out(fn) { + return this.has_outros ? b`@group_transition_out((#transition_out) => { ${fn(x`#transition_out`)} })` : fn(null); + } + add_variable(id: Identifier, init?: Node) { if (this.variables.has(id.name)) { throw new Error( diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 1efadfb90cf2..e32549a79e5c 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -7,6 +7,7 @@ import FragmentWrapper from './Fragment'; import { b, x } from 'code-red'; import ElseBlock from '../../nodes/ElseBlock'; import { Identifier, Node } from 'estree'; +import bit_state from '../../utils/bit_state' export class ElseBlockWrapper extends Wrapper { node: ElseBlock; @@ -423,25 +424,18 @@ export default class EachBlockWrapper extends Wrapper { const dynamic = this.block.has_update_method; - const destroy = this.node.has_animation - ? (this.block.has_outros - ? `@fix_and_outro_and_destroy_block` - : `@fix_and_destroy_block`) - : this.block.has_outros - ? `@outro_and_destroy_block` - : `@destroy_block`; + const transition_state = bit_state([dynamic, this.node.has_animation, this.block.has_outros]); + const update_keyed_each = (transition_out) => + b`${iterations} = @update_keyed_each(${iterations}, #dirty, #ctx, ${transition_state}, ${get_key}, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}, ${transition_out});`; if (this.dependencies.size) { this.updates.push(b` const ${this.vars.each_block_value} = ${snippet}; ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} - - ${this.block.has_outros && b`@group_outros();`} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} - ${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); + ${this.block.group_transition_out(update_keyed_each)} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} - ${this.block.has_outros && b`@check_outros();`} `); } @@ -552,20 +546,11 @@ export default class EachBlockWrapper extends Wrapper { let remove_old_blocks; if (this.block.has_outros) { - const out = block.get_unique_name('out'); - - block.chunks.init.push(b` - const ${out} = i => @transition_out(${iterations}[i], 1, 1, () => { - ${iterations}[i] = null; - }); - `); - remove_old_blocks = b` - @group_outros(); - for (#i = ${data_length}; #i < ${view_length}; #i += 1) { - ${out}(#i); - } - @check_outros(); - `; + remove_old_blocks = this.block.group_transition_out((transition_out) => + b`for (#i = ${data_length}; #i < ${view_length}; #i += 1) { + ${transition_out}(#i); + }` + ) } else { remove_old_blocks = b` for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 83bc8be94e66..f8aae39a04e9 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -26,6 +26,7 @@ import { Identifier } from 'estree'; import EventHandler from './EventHandler'; import { extract_names } from 'periscopic'; import Action from '../../../nodes/Action'; +import Transition from '../../../nodes/Transition'; const events = [ { @@ -379,8 +380,18 @@ export default class ElementWrapper extends Wrapper { this.add_attributes(block); this.add_directives_in_order(block); - this.add_transitions(block); - this.add_animation(block); + const { intro, outro } = this.node; + if (intro || outro) { + if (intro === outro) { + this.add_bidi_transition(block, intro); + } else { + this.add_intro(block, intro, outro); + this.add_outro(block, intro, outro); + } + } + if (this.node.animation) { + this.add_animation(block, intro); + } this.add_classes(block); this.add_manual_style_scoping(block); @@ -728,165 +739,101 @@ export default class ElementWrapper extends Wrapper { } } - add_transitions( - block: Block - ) { - const { intro, outro } = this.node; - if (!intro && !outro) return; - - if (intro === outro) { - // bidirectional transition - const name = block.get_unique_name(`${this.var.name}_transition`); - const snippet = intro.expression - ? intro.expression.manipulate(block) - : x`{}`; - - block.add_variable(name); + add_bidi_transition(block: Block, intro: Transition) { + const name = block.get_unique_name(`${this.var.name}_transition`); + const snippet = intro.expression ? intro.expression.manipulate(block) : null; - const fn = this.renderer.reference(intro.name); + block.add_variable(name, x`@noop`); - const intro_block = b` - @add_render_callback(() => { - if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true); - ${name}.run(1); - }); - `; + const fn = this.renderer.reference(intro.name); - const outro_block = b` - if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false); - ${name}.run(0); - `; + let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 1, ${snippet});`; + let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 2, ${snippet});`; - if (intro.is_local) { - block.chunks.intro.push(b` - if (#local) { - ${intro_block} - } - `); - - block.chunks.outro.push(b` - if (#local) { - ${outro_block} - } - `); - } else { - block.chunks.intro.push(intro_block); - block.chunks.outro.push(outro_block); - } - - block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`); + if (intro.is_local) { + intro_block = b`if (#local) {${intro_block}}`; + outro_block = b`if (#local) {${outro_block}}`; } + block.chunks.intro.push(intro_block); + block.chunks.outro.push(outro_block); - else { - const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`); - const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`); - - if (intro) { - block.add_variable(intro_name); - const snippet = intro.expression - ? intro.expression.manipulate(block) - : x`{}`; - - const fn = this.renderer.reference(intro.name); - - let intro_block; - - if (outro) { - intro_block = b` - @add_render_callback(() => { - if (${outro_name}) ${outro_name}.end(1); - if (!${intro_name}) ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${intro_name}.start(); - }); - `; - - block.chunks.outro.push(b`if (${intro_name}) ${intro_name}.invalidate();`); - } else { - intro_block = b` - if (!${intro_name}) { - @add_render_callback(() => { - ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${intro_name}.start(); - }); - } - `; - } - - if (intro.is_local) { - intro_block = b` - if (#local) { - ${intro_block} - } - `; + block.chunks.destroy.push(b`if (detaching) ${name}();`); + } + add_intro(block: Block, intro: Transition, outro: Transition) { + if (outro) { + const outro_var = block.alias(`${this.var.name}_outro`); + block.chunks.intro.push(b`${outro_var}(1);`); + } + if (this.node.animation) { + const [unfreeze_var, rect_var, stop_animation_var, animationFn, params] = run_animation(this, block); + block.chunks.intro.push(b` + if (${unfreeze_var}) { + ${unfreeze_var}(); + ${unfreeze_var} = void 0; + ${stop_animation_var} = @run_animation(${this.var}, ${rect_var}, ${animationFn}, ${params}); } + `); + } + if (!intro) return; - block.chunks.intro.push(intro_block); - } - - if (outro) { - block.add_variable(outro_name); - const snippet = outro.expression - ? outro.expression.manipulate(block) - : x`{}`; - - const fn = this.renderer.reference(outro.name); - - if (!intro) { - block.chunks.intro.push(b` - if (${outro_name}) ${outro_name}.end(1); - `); - } + const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`); + block.add_variable(intro_var, x`@noop`); - // TODO hide elements that have outro'd (unless they belong to a still-outroing - // group) prior to their removal from the DOM - let outro_block = b` - ${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet}); - `; + let start_intro; + if (intro.is_local) + start_intro = b`if (#local) ${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; + else start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; + block.chunks.intro.push(start_intro); + } + // TODO + // hide elements that have outro'd prior to their removal from the DOM + // ( ...unless they belong to a still-outroing group ) + add_outro(block: Block, intro: Transition, outro: Transition) { + if (intro) { + const intro_var = block.alias(`${this.var.name}_intro`); + block.chunks.outro.push(b`${intro_var}();`); + } + if (!outro) return; - if (outro.is_local) { - outro_block = b` - if (#local) { - ${outro_block} - } - `; - } + const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`); + block.add_variable(outro_var, x`@noop`); - block.chunks.outro.push(outro_block); + let start_outro; + if (outro.is_local) start_outro = b`if (#local) @run_transition(${node}, ${transitionFn}, 2, ${params});`; + else start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`; + block.chunks.outro.push(start_outro); - block.chunks.destroy.push(b`if (detaching && ${outro_name}) ${outro_name}.end();`); - } - } + block.chunks.destroy.push(b`if (detaching) ${outro_var}();`); } - add_animation(block: Block) { - if (!this.node.animation) return; - - const { outro } = this.node; + add_animation(block: Block, intro: Transition) { + const intro_var = intro && block.alias(`${this.var.name}_intro`); - const rect = block.get_unique_name('rect'); - const stop_animation = block.get_unique_name('stop_animation'); + const [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block); - block.add_variable(rect); - block.add_variable(stop_animation, x`@noop`); + block.add_variable(unfreeze_var); + block.add_variable(rect_var); + block.add_variable(stop_animation_var, x`@noop`); block.chunks.measure.push(b` - ${rect} = ${this.var}.getBoundingClientRect(); + ${rect_var} = ${this.var}.getBoundingClientRect(); + ${intro && b`${intro_var}();`} `); block.chunks.fix.push(b` - @fix_position(${this.var}); - ${stop_animation}(); - ${outro && b`@add_transform(${this.var}, ${rect});`} + ${stop_animation_var}(); + ${unfreeze_var} = @fix_position(${this.var}, ${rect_var}); `); - const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`; - - const name = this.renderer.reference(this.node.animation.name); - block.chunks.animate.push(b` - ${stop_animation}(); - ${stop_animation} = @create_animation(${this.var}, ${rect}, ${name}, ${params}); + if (${unfreeze_var}) return + else { + ${stop_animation_var}(); + ${stop_animation_var} = @run_animation(${this.var}, ${rect_var}, ${name_var}, ${params_var}); + } `); + + block.chunks.destroy.push(b`${unfreeze_var} = void 0;`); } add_classes(block: Block) { @@ -995,3 +942,20 @@ function to_html(wrappers: Array, blo } }); } +function run_animation(element: ElementWrapper, block: Block) { + return [ + block.alias('unfreeze'), + block.alias('rect'), + block.alias('stop_animation'), + element.renderer.reference(element.node.animation.name), + element.node.animation.expression ? element.node.animation.expression.manipulate(block) : null, + ]; +} +function run_transition(element: ElementWrapper, block: Block, transition: Transition, type: string) { + return [ + /* node_intro */ block.alias(`${element.var.name}_${type}`), + /* node */ element.var, + /* transitionFn */ element.renderer.reference(transition.name), + /* params */ transition.expression ? transition.expression.manipulate(block) : null, + ]; +} diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 220b52990201..412b5bb37dec 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -434,13 +434,10 @@ export default class IfBlockWrapper extends Wrapper { if (this.needs_update) { const update_mount_node = this.get_update_mount_node(anchor); - const destroy_old_block = b` - @group_outros(); - @transition_out(${if_blocks}[${previous_block_index}], 1, 1, () => { - ${if_blocks}[${previous_block_index}] = null; - }); - @check_outros(); - `; + const destroy_old_block = block.group_transition_out( + (transition_out) => + b`${transition_out}(${if_blocks}[${previous_block_index}], () => {${if_blocks}[${previous_block_index}] = null;})` + ); const create_new_block = b` ${name} = ${if_blocks}[${current_block_type_index}]; @@ -556,11 +553,7 @@ export default class IfBlockWrapper extends Wrapper { if (${branch.condition}) { ${enter} } else if (${name}) { - @group_outros(); - @transition_out(${name}, 1, 1, () => { - ${name} = null; - }); - @check_outros(); + ${block.group_transition_out((transition_out) => b`${transition_out}(${name},() => {${name} = null;})`)} } `); } else { diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 00f803bbbd48..cf6a15c9fa61 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -451,12 +451,11 @@ export default class InlineComponentWrapper extends Wrapper { block.chunks.update.push(b` if (${switch_value} !== (${switch_value} = ${snippet})) { if (${name}) { - @group_outros(); const old_component = ${name}; - @transition_out(old_component.$$.fragment, 1, 0, () => { - @destroy_component(old_component, 1); - }); - @check_outros(); + ${block.group_transition_out( + (transition_out) => + b`${transition_out}(old_component.$$.fragment, () => { @destroy_component(old_component, 1); }, 0);` + )} } if (${switch_value}) { diff --git a/src/compiler/compile/utils/bit_state.ts b/src/compiler/compile/utils/bit_state.ts new file mode 100644 index 000000000000..7523606c50cf --- /dev/null +++ b/src/compiler/compile/utils/bit_state.ts @@ -0,0 +1 @@ +export default (arr) => arr.reduce((state, bool, index) => (bool ? (state |= 1 << index) : state), 0); \ No newline at end of file diff --git a/src/runtime/animate/index.ts b/src/runtime/animate/index.ts index 087c0f7141c1..58aeceff7a4d 100644 --- a/src/runtime/animate/index.ts +++ b/src/runtime/animate/index.ts @@ -1,41 +1,22 @@ -import { cubicOut } from 'svelte/easing'; -import { is_function } from 'svelte/internal'; - -// todo: same as Transition, should it be shared? -export interface AnimationConfig { - delay?: number; - duration?: number; - easing?: (t: number) => number; - css?: (t: number, u: number) => string; - tick?: (t: number, u: number) => void; -} - -interface FlipParams { - delay: number; - duration: number | ((len: number) => number); - easing: (t: number) => number; -} - -export function flip(node: Element, animation: { from: DOMRect; to: DOMRect }, params: FlipParams): AnimationConfig { - const style = getComputedStyle(node); - const transform = style.transform === 'none' ? '' : style.transform; +import { cubicOut } from "svelte/easing"; +import { run_duration } from "svelte/internal"; +import { CssTransitionConfig, TimeableConfig } from "svelte/transition"; +export function flip( + node: Element, + animation: { from: DOMRect; to: DOMRect }, + { delay = 0, duration = (d: number) => Math.sqrt(d) * 30, easing = cubicOut }: TimeableConfig +): CssTransitionConfig { + const style = getComputedStyle(node).transform; + const transform = style === "none" ? "" : style; const scaleX = animation.from.width / node.clientWidth; const scaleY = animation.from.height / node.clientHeight; const dx = (animation.from.left - animation.to.left) / scaleX; const dy = (animation.from.top - animation.to.top) / scaleY; - const d = Math.sqrt(dx * dx + dy * dy); - - const { - delay = 0, - duration = (d: number) => Math.sqrt(d) * 120, - easing = cubicOut - } = params; - return { delay, - duration: is_function(duration) ? duration(d) : duration, + duration: run_duration(duration, Math.sqrt(dx * dx + dy * dy)), easing, css: (_t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);` }; diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 7d2a92fa1bce..780e7e0eec05 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,10 +1,11 @@ -import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler'; +import { add_render_callback, flush, schedule_update } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; -import { blank_object, is_function, run, run_all, noop } from './utils'; +import { blank_object, is_function, run, run_all } from './utils'; import { children, detach } from './dom'; import { transition_in } from './transitions'; +import { noop } from './environment'; -interface Fragment { +export interface Fragment { key: string|null; first: null; /* create */ c: () => void; @@ -20,7 +21,7 @@ interface Fragment { /* destroy */ d: (detaching: 0|1) => void; } // eslint-disable-next-line @typescript-eslint/class-name-casing -interface T$$ { +export interface T$$ { dirty: number[]; ctx: null|any; bound: any; @@ -87,15 +88,6 @@ export function destroy_component(component, detaching) { } } -function make_dirty(component, i) { - if (component.$$.dirty[0] === -1) { - dirty_components.push(component); - schedule_update(); - component.$$.dirty.fill(0); - } - component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); -} - export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) { const parent_component = current_component; set_current_component(component); @@ -127,13 +119,18 @@ export function init(component, options, instance, create_fragment, not_equal, p let ready = false; $$.ctx = instance - ? instance(component, prop_values, (i, ret, ...rest) => { - const value = rest.length ? rest[0] : ret; - if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { - if ($$.bound[i]) $$.bound[i](value); - if (ready) make_dirty(component, i); - } - return ret; + ? instance(component, prop_values, (i, res, ...rest) => { + if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = rest.length ? rest[0] : res))) { + if (i in $$.bound) $$.bound[i]($$.ctx[i]); + if (ready) { + if (-1 === $$.dirty[0]) { + schedule_update(component); + $$.dirty.fill(0); + } + $$.dirty[(i / 31) | 0] |= 1 << i % 31; + } + } + return res; }) : []; diff --git a/src/runtime/internal/animations.ts b/src/runtime/internal/animations.ts index 6dc6a446f6b3..d266488a8cc0 100644 --- a/src/runtime/internal/animations.ts +++ b/src/runtime/internal/animations.ts @@ -1,103 +1,42 @@ -import { identity as linear, noop } from './utils'; -import { now } from './environment'; -import { loop } from './loop'; -import { create_rule, delete_rule } from './style_manager'; -import { AnimationConfig } from '../animate'; - - -//todo: documentation says it is DOMRect, but in IE it would be ClientRect -type PositionRect = DOMRect|ClientRect; - -type AnimationFn = (node: Element, { from, to }: { from: PositionRect; to: PositionRect }, params: any) => AnimationConfig; - -export function create_animation(node: Element & ElementCSSInlineStyle, from: PositionRect, fn: AnimationFn, params) { - if (!from) return noop; - - const to = node.getBoundingClientRect(); - if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return noop; - - - const { - delay = 0, - duration = 300, - easing = linear, - // @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation? - start: start_time = now() + delay, - // @ts-ignore todo: - end = start_time + duration, - tick = noop, - css - } = fn(node, { from, to }, params); - - let running = true; - let started = false; - let name; - - function start() { - if (css) { - name = create_rule(node, 0, 1, duration, delay, easing, css); - } - - if (!delay) { - started = true; - } - } - - function stop() { - if (css) delete_rule(node, name); - running = false; - } - - loop(now => { - if (!started && now >= start_time) { - started = true; - } - - if (started && now >= end) { - tick(1, 0); - stop(); - } - - if (!running) { - return false; - } - - if (started) { - const p = now - start_time; - const t = 0 + 1 * easing(p / duration); - tick(t, 1 - t); - } - - return true; - }); - - start(); - - tick(0, 1); - - return stop; -} - -export function fix_position(node: Element & ElementCSSInlineStyle) { - 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; - add_transform(node, a); +import { run_transition } from './transitions'; +import { noop } from './environment'; +import { methodify } from './utils'; +import { CssTransitionConfig } from 'svelte/transition'; + +type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => CssTransitionConfig; + +export const run_animation = /*#__PURE__*/ methodify( + function run_animation(this: HTMLElement, from: DOMRect, fn: AnimationFn, params = {}) { + if (!from) return noop; + return run_transition( + this, + (_, params) => { + const to = this.getBoundingClientRect(); + if (from.left !== to.left || from.right !== to.right || from.top !== to.top || from.bottom !== to.bottom) { + return fn(this, { from, to }, params); + } else return null; + }, + 9, + params + ); } -} - -export function add_transform(node: Element & ElementCSSInlineStyle, a: PositionRect) { - 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)`; +); + +export const fix_position = /*#__PURE__*/ methodify( + function fix_position(this: HTMLElement, { left, top }: DOMRect) { + const { position, width, height, transform } = getComputedStyle(this); + if (position === 'absolute' || position === 'fixed') return noop; + const { position: og_position, width: og_width, height: og_height } = this.style; + this.style.position = 'absolute'; + this.style.width = width; + this.style.height = height; + const b = this.getBoundingClientRect(); + this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`; + return () => { + this.style.position = og_position; + this.style.width = og_width; + this.style.height = og_height; + this.style.transform = ''; // unsafe + }; } -} +); diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts index f70cbd6c2c7d..e88dc159fc24 100644 --- a/src/runtime/internal/await_block.ts +++ b/src/runtime/internal/await_block.ts @@ -1,5 +1,5 @@ import { is_promise } from './utils'; -import { check_outros, group_outros, transition_in, transition_out } from './transitions'; +import { transition_in, group_transition_out } from './transitions'; import { flush } from './scheduler'; import { get_current_component, set_current_component } from './lifecycle'; @@ -26,11 +26,11 @@ export function handle_promise(promise, info) { if (info.blocks) { info.blocks.forEach((block, i) => { if (i !== index && block) { - group_outros(); - transition_out(block, 1, 1, () => { - info.blocks[i] = null; + group_transition_out((transition_out) => { + transition_out(block, () => { + info.blocks[i] = null; + }); }); - check_outros(); } }); } else { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 751f1f802bc0..d38cb8d0a0d2 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -1,5 +1,6 @@ import { custom_event, append, insert, detach, listen, attr } from './dom'; import { SvelteComponent } from './Component'; +import { has_Symbol } from './environment'; export function dispatch_dev(type: string, detail?: T) { document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); @@ -82,7 +83,7 @@ export function set_data_dev(text, data) { export function validate_each_argument(arg) { if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { let msg = '{#each} only iterates over array-like objects.'; - if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { + if (has_Symbol && arg && Symbol.iterator in arg) { msg += ' You can use a spread to convert this iterable into an array.'; } throw new Error(msg); diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index f67fd13b2d7c..7fdc26808bc2 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,4 +1,5 @@ import { has_prop } from "./utils"; +import { is_cors } from "./environment"; export function append(target: Node, node: Node) { target.appendChild(node); @@ -234,26 +235,6 @@ export function select_multiple_value(select) { return [].map.call(select.querySelectorAll(':checked'), option => option.__value); } -// unfortunately this can't be a constant as that wouldn't be tree-shakeable -// so we cache the result instead -let crossorigin: boolean; - -export function is_crossorigin() { - if (crossorigin === undefined) { - crossorigin = false; - - try { - if (typeof window !== 'undefined' && window.parent) { - void window.parent.document; - } - } catch (error) { - crossorigin = true; - } - } - - return crossorigin; -} - export function add_resize_listener(node: HTMLElement, fn: () => void) { const computed_style = getComputedStyle(node); const z_index = (parseInt(computed_style.zIndex) || 0) - 1; @@ -270,11 +251,9 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { iframe.setAttribute('aria-hidden', 'true'); iframe.tabIndex = -1; - const crossorigin = is_crossorigin(); - let unsubscribe: () => void; - if (crossorigin) { + if (is_cors) { iframe.src = `data:text/html,`; unsubscribe = listen(window, 'message', (event: MessageEvent) => { if (event.source === iframe.contentWindow) fn(); @@ -289,7 +268,7 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { append(node, iframe); return () => { - if (crossorigin) { + if (is_cors) { unsubscribe(); } else if (unsubscribe && iframe.contentWindow) { unsubscribe(); diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index 7123399180d2..bda01b4d3f2b 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -1,18 +1,33 @@ -import { noop } from './utils'; +export function noop() {} +export const is_browser = typeof window !== 'undefined'; +export const is_iframe = is_browser && window.self !== window.top; +export const is_cors = + is_iframe && + /*#__PURE__*/ (() => { + try { + if (window.parent) void window.parent.document; + return false; + } catch (error) { + return true; + } + })(); +export const has_Symbol = typeof Symbol === 'function'; +/* eslint-disable no-var */ +declare var global: any; +export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global; +export const resolved_promise = Promise.resolve(); +export let now = is_browser ? window.performance.now.bind(window.performance) : Date.now.bind(Date); +export let raf = is_browser ? requestAnimationFrame : noop; +export let framerate = 1000 / 60; +/*#__PURE__*/ raf((t1) => { + raf((d) => { + const f24 = 1000 / 24, + f144 = 1000 / 144; + framerate = (d = d - t1) > f144 ? f144 : d < f24 ? f24 : d; + }); +}); -export const is_client = typeof window !== 'undefined'; - -export let now: () => number = is_client - ? () => window.performance.now() - : () => Date.now(); - -export let raf = is_client ? cb => requestAnimationFrame(cb) : noop; - -// used internally for testing -export function set_now(fn) { - now = fn; -} - -export function set_raf(fn) { - raf = fn; -} +/* tests only */ +export const set_now = (v) => void (now = v); +export const set_raf = (fn) => void (raf = fn); +export const set_framerate = (v) => void (framerate = v); diff --git a/src/runtime/internal/globals.ts b/src/runtime/internal/globals.ts deleted file mode 100644 index b97f81ab9f47..000000000000 --- a/src/runtime/internal/globals.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare const global: any; - -export const globals = (typeof window !== 'undefined' - ? window - : typeof globalThis !== 'undefined' - ? globalThis - : global) as unknown as typeof globalThis; diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index e1dd2a1fcf36..daeb9b1f0a6c 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -2,7 +2,6 @@ export * from './animations'; export * from './await_block'; export * from './dom'; export * from './environment'; -export * from './globals'; export * from './keyed_each'; export * from './lifecycle'; export * from './loop'; diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index b397335c8766..3c89c01e4b36 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -1,27 +1,18 @@ -import { transition_in, transition_out } from './transitions'; - -export function destroy_block(block, lookup) { - block.d(1); - lookup.delete(block.key); -} - -export function outro_and_destroy_block(block, lookup) { - transition_out(block, 1, 1, () => { - lookup.delete(block.key); - }); -} - -export function fix_and_destroy_block(block, lookup) { - block.f(); - destroy_block(block, lookup); -} - -export function fix_and_outro_and_destroy_block(block, lookup) { - block.f(); - outro_and_destroy_block(block, lookup); -} - -export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) { +import { transition_in } from './transitions'; +export const update_keyed_each = ( + old_blocks, + dirty, + ctx, + state, + get_key, + list, + lookup, + node, + create_each_block, + next, + get_context, + transition_out? +) => { let o = old_blocks.length; let n = list.length; @@ -42,11 +33,11 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list if (!block) { block = create_each_block(key, child_ctx); block.c(); - } else if (dynamic) { + } else if (state & 1) { block.p(child_ctx, dirty); } - new_lookup.set(key, new_blocks[i] = block); + new_lookup.set(key, (new_blocks[i] = block)); if (key in old_indexes) deltas.set(key, Math.abs(i - old_indexes[key])); } @@ -54,13 +45,18 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list const will_move = new Set(); const did_move = new Set(); - function insert(block) { + const insert = (block) => { transition_in(block, 1); - block.m(node, next); + block.m(node, next, lookup.has(block.key)); lookup.set(block.key, block); next = block.first; n--; - } + }; + const destroy = (block) => { + if (state & 2) block.f(); + if (state & 4) transition_out(block, lookup.delete.bind(lookup, block.key)); + else block.d(1), lookup.delete(block.key); + }; while (o && n) { const new_block = new_blocks[n - 1]; @@ -73,25 +69,17 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list next = new_block.first; o--; n--; - } - - else if (!new_lookup.has(old_key)) { + } else if (!new_lookup.has(old_key)) { // remove old block - destroy(old_block, lookup); + destroy(old_block); o--; - } - - else if (!lookup.has(new_key) || will_move.has(new_key)) { + } else if (!lookup.has(new_key) || will_move.has(new_key)) { insert(new_block); - } - - else if (did_move.has(old_key)) { + } else if (did_move.has(old_key)) { o--; - } else if (deltas.get(new_key) > deltas.get(old_key)) { did_move.add(new_key); insert(new_block); - } else { will_move.add(old_key); o--; @@ -100,21 +88,10 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list while (o--) { const old_block = old_blocks[o]; - if (!new_lookup.has(old_block.key)) destroy(old_block, lookup); + if (!new_lookup.has(old_block.key)) destroy(old_block); } while (n) insert(new_blocks[n - 1]); return new_blocks; -} - -export function validate_each_keys(ctx, list, get_context, get_key) { - const keys = new Set(); - for (let i = 0; i < list.length; i++) { - const key = get_key(get_context(ctx, list, i)); - if (keys.has(key)) { - throw new Error(`Cannot have duplicate keys in a keyed each`); - } - keys.add(key); - } -} +}; diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index a8e37e9632a3..c9172b378384 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -2,9 +2,7 @@ import { custom_event } from './dom'; export let current_component; -export function set_current_component(component) { - current_component = component; -} +export const set_current_component = (component) => (current_component = component); export function get_current_component() { if (!current_component) throw new Error(`Function called outside component initialization`); diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index 33e519732fba..5cdfc434ad0b 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -1,45 +1,122 @@ -import { raf } from './environment'; +import { now, raf, framerate, noop } from './environment'; +type TaskCallback = (t: number) => boolean; +type TaskCanceller = () => void; -export interface Task { abort(): void; promise: Promise } +let i = 0, + j = 0, + n = 0, + v : TaskCallback; -type TaskCallback = (now: number) => boolean | void; -type TaskEntry = { c: TaskCallback; f: () => void }; +let running_frame : Array = [], + next_frame : Array = []; -const tasks = new Set(); - -function run_tasks(now: number) { - tasks.forEach(task => { - if (!task.c(now)) { - tasks.delete(task); - task.f(); +const run = (t: number) => { + [running_frame, next_frame] = [next_frame, running_frame]; + for (t = now(), i = n = 0, j = running_frame.length; i < j; i++) { + if ((v = running_frame[i])(t)) { + next_frame[n++] = v; } - }); + } + if ((running_frame.length = 0) < n) { + raf(run); + } +}; + +type TimeoutTask = { timestamp: number; callback: (now: number) => void }; + +const pending_insert_timed : Array = [], + timed_tasks : Array = []; + +let pending_inserts = false, + running_timed = false; + +const run_timed = (now: number) => { + let last_index = timed_tasks.length - 1; + while (~last_index && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now); + if (pending_inserts) { + for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++) + if (now >= (this_task = pending_insert_timed[i]).timestamp) this_task.callback(now); + else { + for (j = last_index; ~j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) + timed_tasks[j + 1] = that_task; + timed_tasks[j + 1] = this_task; + last_index++; + } + pending_insert_timed.length = 0; + pending_inserts = false; + } + return (running_timed = !!(timed_tasks.length = last_index + 1)); +}; + +const unsafe_loop = (fn) => { + if (0 === n) raf(run); + next_frame[n++] = fn; +}; + +export const loop = (fn) => { + let running = true; + if (0 === n) raf(run); + next_frame[n++] = (t) => !running || fn(t); + return () => void (running = false); +}; - if (tasks.size !== 0) raf(run_tasks); -} +export const setFrameTimeout = (callback: (t: number) => void, timestamp: number): TaskCanceller => { + const task: TimeoutTask = { callback, timestamp }; + if (running_timed) { + pending_inserts = !!pending_insert_timed.push(task); + } else { + unsafe_loop(run_timed); + running_timed = true; + timed_tasks.push(task); + } + return () => void (task.callback = noop); +}; /** - * For testing purposes only! + * Calls function every frame with linear tween from 0 to 1 */ -export function clear_loops() { - tasks.clear(); -} - +export const setTweenTimeout = ( + stop: (now: number) => void, + end_time: number, + run: (now: number) => void, + duration = end_time - now() +): TaskCanceller => { + let running = true; + let t = 0.0; + unsafe_loop((now) => { + if (!running) return false; + t = 1.0 - (end_time - now) / duration; + if (t >= 1.0) return run(1), stop(now), false; + if (t >= 0.0) run(t); + return running; + }); + return (run_last = false) => { + // since outros are cancelled in group by a setFrameTimeout + // tick(0, 1) has to be called in here + if (run_last) run(1); + running = false; + }; +}; /** - * Creates a new task that runs on each raf frame - * until it returns a falsy value or is aborted + * Calls function every frame with time elapsed in seconds */ -export function loop(callback: TaskCallback): Task { - let task: TaskEntry; - - if (tasks.size === 0) raf(run_tasks); +export const onEachFrame = ( + callback: (seconds_elapsed: number) => boolean, + on_stop?, + max_skipped_frames = 4 +): TaskCanceller => { + max_skipped_frames *= framerate; + let lastTime = now(); + let running = true; + const cancel = (t) => (on_stop && on_stop(t), false); + unsafe_loop((t: number) => { + if (!running) return cancel(t); + if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames; + return callback((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t); + }); + return () => void (running = false); +}; - return { - promise: new Promise(fulfill => { - tasks.add(task = { c: callback, f: fulfill }); - }), - abort() { - tasks.delete(task); - } - }; -} +/** tests only */ +export const clear_loops = () => + void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false)); diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index b0db71035a98..709999ee9709 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -1,89 +1,106 @@ -import { run_all } from './utils'; import { set_current_component } from './lifecycle'; +import { resolved_promise, now } from './environment'; +import { T$$ } from './Component'; -export const dirty_components = []; -export const intros = { enabled: false }; +let update_scheduled = false; +let is_flushing = false; + +const dirty_components = []; +// todo : remove binding_callbacks export export const binding_callbacks = []; const render_callbacks = []; +const measure_callbacks = []; const flush_callbacks = []; -const resolved_promise = Promise.resolve(); -let update_scheduled = false; +// todo : remove add_flush_callback +export const add_flush_callback = /*#__PURE__*/ Array.prototype.push.bind(flush_callbacks); +export const add_measure_callback = /*#__PURE__*/ Array.prototype.push.bind(measure_callbacks); -export function schedule_update() { +const seen_render_callbacks = new Set(); +export const add_render_callback = (fn) => { + if (!seen_render_callbacks.has(fn)) { + seen_render_callbacks.add(fn); + render_callbacks.push(fn); + } +}; +export const schedule_update = (component) => { + dirty_components.push(component); + if (!update_scheduled) { + update_scheduled = true; + resolved_promise.then(flush); + } +}; +export const tick = () => { if (!update_scheduled) { update_scheduled = true; resolved_promise.then(flush); } -} - -export function tick() { - schedule_update(); return resolved_promise; -} - -export function add_render_callback(fn) { - render_callbacks.push(fn); -} - -export function add_flush_callback(fn) { - flush_callbacks.push(fn); -} - -let flushing = false; -const seen_callbacks = new Set(); -export function flush() { - if (flushing) return; - flushing = true; +}; +export const flush = () => { + if (is_flushing) return; + else is_flushing = true; + + let i = 0, + j = 0, + t = 0, + $$: T$$, + dirty, + before_update, + after_update; do { - // first, call beforeUpdate functions - // and update components - for (let i = 0; i < dirty_components.length; i += 1) { - const component = dirty_components[i]; - set_current_component(component); - update(component.$$); - } + while (i < dirty_components.length) { + ({ $$ } = set_current_component(dirty_components[i])); - dirty_components.length = 0; + // todo : is this check still necessary ? + if (null === $$.fragment) continue; - while (binding_callbacks.length) binding_callbacks.pop()(); + /* run reactive statements */ + $$.update(); - // then, once components are updated, call - // afterUpdate functions. This may cause - // subsequent updates... - for (let i = 0; i < render_callbacks.length; i += 1) { - const callback = render_callbacks[i]; + /* run beforeUpdate */ + for (j = 0, { before_update } = $$; j < before_update.length; j++) { + before_update[j](); + } - if (!seen_callbacks.has(callback)) { - // ...so guard against infinite loops - seen_callbacks.add(callback); + /* update blocks */ + ({ dirty } = $$).dirty = [-1]; + if (false !== $$.fragment) $$.fragment.p($$.ctx, dirty); - callback(); + /* schedule afterUpdate */ + for (j = 0, { after_update } = $$; j < after_update.length; j++) { + add_render_callback(after_update[j]); } + + i = i + 1; } + dirty_components.length = 0; - render_callbacks.length = 0; + // update bindings [ ...in reverse order (#3145) ] + i = binding_callbacks.length; + while (i--) binding_callbacks[i](); + binding_callbacks.length = i = 0; + + // run afterUpdates + // todo : remove every non afterUpdate callback from render_callbacks + for (; i < render_callbacks.length; i++) render_callbacks[i](); + render_callbacks.length = i = 0; } while (dirty_components.length); + seen_render_callbacks.clear(); + update_scheduled = false; - while (flush_callbacks.length) { - flush_callbacks.pop()(); + // measurement callbacks for animations + for (i = 0, j = flush_callbacks.length; i < measure_callbacks.length; i++) { + flush_callbacks[j++] = measure_callbacks[i](); } + measure_callbacks.length = i = 0; - update_scheduled = false; - flushing = false; - seen_callbacks.clear(); -} - -function update($$) { - if ($$.fragment !== null) { - $$.update(); - run_all($$.before_update); - const dirty = $$.dirty; - $$.dirty = [-1]; - $$.fragment && $$.fragment.p($$.ctx, dirty); - - $$.after_update.forEach(add_render_callback); - } -} + // apply styles + // todo : remove every non style callback from flush_callbacks + for (t = now(); i < j; i++) flush_callbacks[i](t); + flush_callbacks.length = i = j = 0; + + is_flushing = false; +}; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 31d7573a769e..5d5b30885d5c 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,74 +1,62 @@ -import { element } from './dom'; -import { raf } from './environment'; - -interface ExtendedDoc extends Document { - __svelte_stylesheet: CSSStyleSheet; - __svelte_rules: Record; -} - -const active_docs = new Set(); -let active = 0; - -// https://github.com/darkskyapp/string-hash/blob/master/index.js -function hash(str: string) { - let hash = 5381; - let i = str.length; - - while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); - return hash >>> 0; -} - -export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) { - const step = 16.666 / duration; - let keyframes = '{\n'; - - for (let p = 0; p <= 1; p += step) { - const t = a + (b - a) * ease(p); - keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`; +import { framerate } from './environment'; + +let documents_uid = 0; +let running_animations = 0; + +const document_uid = new Map(); +const document_stylesheets = new Map(); + +const current_rules = new Set(); +export const animate_css = /*#__PURE__*/ Function.prototype.call.bind(function animate_css( + this: HTMLElement, + css: (t: number) => string, + duration: number, + delay = 0 +) { + if (!document_uid.has(this.ownerDocument)) { + document_uid.set(this.ownerDocument, documents_uid++); + document_stylesheets.set( + this.ownerDocument, + this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet + ); } - - const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; - const name = `__svelte_${hash(rule)}_${uid}`; - const doc = node.ownerDocument as ExtendedDoc; - active_docs.add(doc); - const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet); - const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {}); - - if (!current_rules[name]) { - current_rules[name] = true; + let rule = '{\n'; + for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + rule += `100% {${css(1)}}\n}`; + + // darkskyapp/string-hash + let i = rule.length, hash = 5381; + while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i); + const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`; + + if (!current_rules.has(name)) { + current_rules.add(name); + const stylesheet = document_stylesheets.get(this.ownerDocument); stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); } - const animation = node.style.animation || ''; - node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`; - - active += 1; - return name; -} - -export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) { - const previous = (node.style.animation || '').split(', '); - const next = previous.filter(name - ? anim => anim.indexOf(name) < 0 // remove specific animation - : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations - ); - const deleted = previous.length - next.length; - if (deleted) { - node.style.animation = next.join(', '); - active -= deleted; - if (!active) clear_rules(); - } -} - -export function clear_rules() { - raf(() => { - if (active) return; - active_docs.forEach(doc => { - const stylesheet = doc.__svelte_stylesheet; - let i = stylesheet.cssRules.length; - while (i--) stylesheet.deleteRule(i); - doc.__svelte_rules = {}; - }); - active_docs.clear(); - }); -} + const previous = this.style.animation; + this.style.animation = `${ + previous ? `${previous}, ` : '' + }${duration}ms linear ${delay}ms 1 normal both running ${name}`; + + running_animations++; + + return () => { + const prev = (this.style.animation || '').split(', '); + const next = prev.filter((anim) => !anim.includes(name)); + if (prev.length !== next.length) this.style.animation = next.join(', '); + if (--running_animations === 0) { + document_stylesheets.forEach((stylesheet) => { + let i = stylesheet.cssRules.length; + while (i--) stylesheet.deleteRule(i); + }); + current_rules.clear(); + if (1 !== documents_uid) { + document_stylesheets.clear(); + document_uid.clear(); + documents_uid = 0; + } + } + }; +}); \ No newline at end of file diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index ed23d3c1dd1e..48f2df750f84 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -1,353 +1,203 @@ -import { identity as linear, is_function, noop, run_all } from './utils'; -import { now } from "./environment"; -import { loop } from './loop'; -import { create_rule, delete_rule } from './style_manager'; +import { CssTransitionConfig } from '../transition'; +import { Fragment } from './Component'; import { custom_event } from './dom'; -import { add_render_callback } from './scheduler'; -import { TransitionConfig } from '../transition'; - -let promise: Promise|null; - -function wait() { - if (!promise) { - promise = Promise.resolve(); - promise.then(() => { - promise = null; - }); - } - - return promise; -} - -function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') { - node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`)); -} - +import { now, noop } from './environment'; +import { setFrameTimeout, setTweenTimeout } from './loop'; +import { add_measure_callback } from './scheduler'; +import { animate_css } from './style_manager'; +import { linear } from 'svelte/easing'; + +type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig; +export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void; + +export const transition_in = (block: Fragment, local?) => { + if (!block || !block.i) return; + outroing.delete(block); + block.i(local); +}; + +export const transition_out = (block: Fragment, local?) => { + if (!block || !block.o || outroing.has(block)) return; + outroing.add(block); + block.o(local); +}; +type TransitionGroup = { + /* parent group */ p: TransitionGroup; + /* callbacks */ c: ((cancelled: boolean) => void)[]; + /* running outros */ r: number; + /* stop callbacks */ s: ((t: number) => void)[]; + /* outro timeout */ t: number; +}; +let transition_group: TransitionGroup; const outroing = new Set(); -let outros; - -export function group_outros() { - outros = { - r: 0, // remaining outros - c: [], // callbacks - p: outros // parent group - }; -} - -export function check_outros() { - if (!outros.r) { - run_all(outros.c); - } - outros = outros.p; -} - -export function transition_in(block, local?: 0 | 1) { - if (block && block.i) { - outroing.delete(block); - block.i(local); - } -} - -export function transition_out(block, local: 0 | 1, detach: 0 | 1, callback) { - if (block && block.o) { - if (outroing.has(block)) return; +export const group_transition_out = (fn) => { + const c = []; + const current_group = (transition_group = { p: transition_group, c, r: 0, s: [], t: 0 }); + fn((block, callback, detach = true) => { + if (!block || !block.o || outroing.has(block)) return; outroing.add(block); - - outros.c.push(() => { - outroing.delete(block); - if (callback) { + c.push((cancelled = false) => { + if (cancelled) { + // block was destroyed before outro ended + outroing.delete(block); + } else if (outroing.has(block)) { + outroing.delete(block); if (detach) block.d(1); callback(); } }); - - block.o(local); - } + block.o(1); + }); + if (!current_group.r) for (let i = 0; i < c.length; i++) c[i](); + transition_group = transition_group.p; +}; + +const swap = (fn, rx) => + fn.length === 1 + ? rx & tx.intro + ? fn + : (t) => fn(1 - t) + : rx & tx.intro + ? (t) => fn(t, 1 - t) + : (t) => fn(1 - t, t); + +const mirrored = (fn, rx, easing) => { + const run = swap(fn, rx); + return easing + ? rx & tx.intro + ? (t) => run(easing(t)) + : (t) => run(1 - easing(1 - t)) + : run; +}; +const reversed = (fn, rx, easing, start = 0, end = 1) => { + const run = swap(fn, rx); + const difference = end - start; + return easing + ? (t) => run(start + difference * easing(t)) + : (t) => run(start + difference * t); +}; +export const enum tx { + intro = 1, + outro = 2, + reverse = 3, + bidirectional = 4, + animation = 8, } +export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(function transition( + this: HTMLElement, + fn: TransitionFn, + rx: tx, + params = {}, + /* internal to this file */ + elapsed_duration = 0, + delay_left = -1, + elapsed_ratio = 0 +) { + let config; + + let running = true; -const null_transition: TransitionConfig = { duration: 0 }; - -type TransitionFn = (node: Element, params: any) => TransitionConfig; - -export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) { - let config = fn(node, params); - let running = false; - let animation_name; - let task; - let uid = 0; - - function cleanup() { - if (animation_name) delete_rule(node, animation_name); - } - - function go() { - const { - delay = 0, - duration = 300, - easing = linear, - tick = noop, - css - } = config || null_transition; - - if (css) animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++); - tick(0, 1); - - const start_time = now() + delay; - const end_time = start_time + duration; - - if (task) task.abort(); - running = true; - - add_render_callback(() => dispatch(node, true, 'start')); - - task = loop(now => { - if (running) { - if (now >= end_time) { - tick(1, 0); - - dispatch(node, true, 'end'); - - cleanup(); - return running = false; - } + let cancel_css, + cancel_raf; - if (now >= start_time) { - const t = easing((now - start_time) / duration); - tick(t, 1 - t); - } - } + let start_time = 0, + end_time = 0; - return running; - }); - } + const current_group = transition_group; + if (rx & tx.outro) current_group.r++; - let started = false; + add_measure_callback(() => { + if (null === (config = fn(this, params))) return noop; + return (current_frame_time) => { + if (false === running) return; - return { - start() { - if (started) return; + let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: CssTransitionConfig = + 'function' === typeof config ? (config = config()) : config; - delete_rule(node); + const solver = 'reverse' === strategy ? reversed : mirrored; + const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1); - if (is_function(config)) { - config = config(); - wait().then(go); - } else { - go(); + if (rx & tx.bidirectional) { + if (-1 !== delay_left) delay = delay_left; + if (solver === reversed) duration -= elapsed_duration; + else if (solver === mirrored) delay -= elapsed_duration; } - }, - invalidate() { - started = false; - }, + end_time = (start_time = current_frame_time + delay) + duration; - end() { - if (running) { - cleanup(); - running = false; + if (0 === (rx & tx.animation)) { + this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`)); } - } - }; -} - -export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) { - let config = fn(node, params); - let running = true; - let animation_name; - - const group = outros; - - group.r += 1; - function go() { - const { - delay = 0, - duration = 300, - easing = linear, - tick = noop, - css - } = config || null_transition; + if (css) cancel_css = animate_css(this, runner(css), duration, delay); - if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css); - - const start_time = now() + delay; - const end_time = start_time + duration; - - add_render_callback(() => dispatch(node, false, 'start')); - - loop(now => { - if (running) { - if (now >= end_time) { - tick(0, 1); - - dispatch(node, false, 'end'); - - if (!--group.r) { - // this will result in `end()` being called, - // so we don't need to clean up here - run_all(group.c); - } - - return false; + if (rx & tx.outro) { + if (current_group.s.push(stop) === current_group.r) { + setFrameTimeout((t) => { + for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t); + }, Math.max(end_time, current_group.t)); + } else { + current_group.t = Math.max(end_time, current_group.t); } - - if (now >= start_time) { - const t = easing((now - start_time) / duration); - tick(1 - t, t); - } - } - - return running; - }); - } - - if (is_function(config)) { - wait().then(() => { - // @ts-ignore - config = config(); - go(); - }); - } else { - go(); - } - - return { - end(reset) { - if (reset && config.tick) { - config.tick(1, 0); - } - - if (running) { - if (animation_name) delete_rule(node, animation_name); - running = false; + if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration); + } else { + cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); } - } - }; -} - -export function create_bidirectional_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any, intro: boolean) { - let config = fn(node, params); - - let t = intro ? 0 : 1; - - let running_program = null; - let pending_program = null; - let animation_name = null; - - function clear_animation() { - if (animation_name) delete_rule(node, animation_name); - } - - function init(program, duration) { - const d = program.b - t; - duration *= Math.abs(d); - - return { - a: t, - b: program.b, - d, - duration, - start: program.start, - end: program.start + duration, - group: program.group - }; - } - - function go(b) { - const { - delay = 0, - duration = 300, - easing = linear, - tick = noop, - css - } = config || null_transition; - - const program = { - start: now() + delay, - b }; + }); - if (!b) { - // @ts-ignore todo: improve typings - program.group = outros; - outros.r += 1; - } - - if (running_program) { - pending_program = program; - } else { - // if this is an intro, and there's a delay, we need to do - // an initial tick and/or apply CSS animation immediately - if (css) { - clear_animation(); - animation_name = create_rule(node, t, b, duration, delay, easing, css); - } - - if (b) tick(0, 1); + const stop: StopResetReverseFn = (t?: number | 1 | -1) => { + // resetting `out:` in intros + if (t === 1 && rx & tx.outro && 0 === (rx & tx.bidirectional) && 'tick' in config) config.tick(1, 0); - running_program = init(program, duration); - add_render_callback(() => dispatch(node, b, 'start')); + if (false === running) return; + else running = false; - loop(now => { - if (pending_program && now > pending_program.start) { - running_program = init(pending_program, duration); - pending_program = null; + if (cancel_css) cancel_css(); + if (cancel_raf) cancel_raf(rx & tx.outro && t >= end_time); - dispatch(node, running_program.b, 'start'); + if (rx & tx.animation) return; - if (css) { - clear_animation(); - animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css); - } - } - - if (running_program) { - if (now >= running_program.end) { - tick(t = running_program.b, 1 - t); - dispatch(node, running_program.b, 'end'); - - if (!pending_program) { - // we're done - if (running_program.b) { - // intro — we can tidy up immediately - clear_animation(); - } else { - // outro — needs to be coordinated - if (!--running_program.group.r) run_all(running_program.group.c); - } - } - - running_program = null; - } - - else if (now >= running_program.start) { - const p = now - running_program.start; - t = running_program.a + running_program.d * easing(p / running_program.duration); - tick(t, 1 - t); - } - } + if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`)); - return !!(running_program || pending_program); - }); - } - } + if (rx & tx.outro && !--current_group.r) + for (let i = 0; i < current_group.c.length; i++) current_group.c[i](t === void 0); - return { - run(b) { - if (is_function(config)) { - wait().then(() => { - // @ts-ignore - config = config(); - go(b); - }); - } else { - go(b); - } - }, + if (0 === (rx & tx.bidirectional)) return; - end() { - clear_animation(); - running_program = pending_program = null; - } + if (-1 === t) + return ( + (t = now()) < end_time && + run_transition( + this, + () => config, + rx ^ tx.reverse, + params, + end_time - t, + start_time > t ? start_time - t : 0, + (1 - elapsed_ratio) * (1 - (config.easing || linear)(1 - (end_time - t) / (end_time - start_time))) + ) + ); + else running_bidi.delete(this); }; -} + + return stop; +}); + +const running_bidi: Map = new Map(); +export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional( + this: HTMLElement, + fn: TransitionFn, + rx: tx.intro | tx.outro, + params: any +) { + let cancel; + running_bidi.set( + this, + (cancel = + (running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params)) + ); + return cancel; +}); +export const run_duration = (duration, value1, value2?): number => + typeof duration === 'function' ? duration(value1, value2) : duration; diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index d752c9de9d83..5e06aa16bf25 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,4 +1,4 @@ -export function noop() {} +import { noop } from "./environment"; export const identity = x => x; @@ -146,4 +146,9 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, export function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; -} \ No newline at end of file +} + +export const methodify = /*#__PURE__*/ (function() { + const call = Function.prototype.call; + return call.bind.bind(call); +})(); \ No newline at end of file diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 0a20c81b1f83..d7ffb3b42de0 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -1,110 +1,60 @@ -import { cubicOut, cubicInOut, linear } from 'svelte/easing'; -import { assign, is_function } from 'svelte/internal'; +import { cubicOut, cubicInOut } from 'svelte/easing'; +import { run_duration } from 'svelte/internal'; -type EasingFunction = (t: number) => number; - -export interface TransitionConfig { +interface CssAnimationConfig { delay?: number; duration?: number; - easing?: EasingFunction; - css?: (t: number, u: number) => string; - tick?: (t: number, u: number) => void; + easing?: (t: number) => number; + strategy?: 'reverse' | 'mirror'; } -interface BlurParams { - delay: number; - duration: number; - easing?: EasingFunction; - amount: number; - opacity: number; +export interface CssTransitionConfig extends CssAnimationConfig { + css?: (t: number, u?: number) => string; + tick?: (t: number, u?: number) => void; } -export function blur(node: Element, { - delay = 0, - duration = 400, - easing = cubicInOut, - amount = 5, - opacity = 0 -}: BlurParams): TransitionConfig { +type FlyParams = FadingConfig & { x: number; y: number; }; +type BlurParams = FadingConfig & { amount: number; }; +type ScaleParams = FadingConfig & { start: number; }; +type DrawParams = CssAnimationConfig & { speed : number }; +type FadingConfig = CssAnimationConfig & { opacity: number; }; +type MarkedCrossFadeConfig = TimeableConfig & { key: any; }; +export type TimeableConfig = Omit & { duration?: number | ((len: number) => number) }; +type CrossFadeConfig = TimeableConfig & { fallback(node: Element, params: TimeableConfig, intro: boolean): CssTransitionConfig; }; +type ElementMap = Map; + +export function blur(node: Element, { delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 }: BlurParams): CssTransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const f = style.filter === 'none' ? '' : style.filter; - const od = target_opacity * (1 - opacity); - return { delay, duration, easing, - css: (_t, u) => `opacity: ${target_opacity - (od * u)}; filter: ${f} blur(${u * amount}px);` + css: (_t, u) => `opacity: ${target_opacity - od * u}; filter:${f} blur(${u * amount}px);`, }; } -interface FadeParams { - delay: number; - duration: number; - easing: EasingFunction; -} - -export function fade(node: Element, { - delay = 0, - duration = 400, - easing = linear -}: FadeParams): TransitionConfig { +export function fade(node: Element, { delay = 0, duration = 400, easing }: CssAnimationConfig): CssTransitionConfig { const o = +getComputedStyle(node).opacity; - - return { - delay, - duration, - easing, - css: t => `opacity: ${t * o}` - }; -} - -interface FlyParams { - delay: number; - duration: number; - easing: EasingFunction; - x: number; - y: number; - opacity: number; + return { delay, duration, easing, css: (t) => `opacity: ${t * o};` }; } -export function fly(node: Element, { - delay = 0, - duration = 400, - easing = cubicOut, - x = 0, - y = 0, - opacity = 0 -}: FlyParams): TransitionConfig { +export function fly(node: Element, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 }: FlyParams ): CssTransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; - const transform = style.transform === 'none' ? '' : style.transform; - + const prev = style.transform === 'none' ? '' : style.transform; const od = target_opacity * (1 - opacity); - return { delay, duration, easing, - css: (t, u) => ` - transform: ${transform} translate(${(1 - t) * x}px, ${(1 - t) * y}px); - opacity: ${target_opacity - (od * u)}` + css: (_t, u) => `transform: ${prev} translate(${u * x}px, ${u * y}px); opacity: ${target_opacity - od * u};`, }; } -interface SlideParams { - delay: number; - duration: number; - easing: EasingFunction; -} - -export function slide(node: Element, { - delay = 0, - duration = 400, - easing = cubicOut -}: SlideParams): TransitionConfig { +export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: CssAnimationConfig): CssTransitionConfig { const style = getComputedStyle(node); const opacity = +style.opacity; const height = parseFloat(style.height); @@ -114,159 +64,91 @@ export function slide(node: Element, { const margin_bottom = parseFloat(style.marginBottom); const border_top_width = parseFloat(style.borderTopWidth); const border_bottom_width = parseFloat(style.borderBottomWidth); - return { delay, duration, easing, - css: t => - `overflow: hidden;` + - `opacity: ${Math.min(t * 20, 1) * opacity};` + - `height: ${t * height}px;` + - `padding-top: ${t * padding_top}px;` + - `padding-bottom: ${t * padding_bottom}px;` + - `margin-top: ${t * margin_top}px;` + - `margin-bottom: ${t * margin_bottom}px;` + - `border-top-width: ${t * border_top_width}px;` + - `border-bottom-width: ${t * border_bottom_width}px;` + css: (t) => ` + overflow: hidden; + opacity: ${Math.min(t * 20, 1) * opacity}; + height: ${t * height}px; + padding-top: ${t * padding_top}px; + padding-bottom: ${t * padding_bottom}px; + margin-top: ${t * margin_top}px; + margin-bottom: ${t * margin_bottom}px; + border-top-width: ${t * border_top_width}px; + border-bottom-width: ${t * border_bottom_width}px;`, }; } -interface ScaleParams { - delay: number; - duration: number; - easing: EasingFunction; - start: number; - opacity: number; -} - -export function scale(node: Element, { - delay = 0, - duration = 400, - easing = cubicOut, - start = 0, - opacity = 0 -}: ScaleParams): TransitionConfig { +export function scale(node: Element, { delay = 0, duration = 400, easing = cubicOut, start = 0, opacity = 0 }: ScaleParams): CssTransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const transform = style.transform === 'none' ? '' : style.transform; - const sd = 1 - start; const od = target_opacity * (1 - opacity); - return { delay, duration, easing, - css: (_t, u) => ` - transform: ${transform} scale(${1 - (sd * u)}); - opacity: ${target_opacity - (od * u)} - ` + css: (_t, u) => `transform: ${transform} scale(${1 - sd * u}); opacity: ${target_opacity - od * u};`, }; } -interface DrawParams { - delay: number; - speed: number; - duration: number | ((len: number) => number); - easing: EasingFunction; -} -export function draw(node: SVGElement & { getTotalLength(): number }, { - delay = 0, - speed, - duration, - easing = cubicInOut -}: DrawParams): TransitionConfig { +export function draw(node: SVGPathElement | SVGGeometryElement, { delay = 0, speed, duration, easing = cubicInOut }: DrawParams): CssTransitionConfig { const len = node.getTotalLength(); - - if (duration === undefined) { - if (speed === undefined) { - duration = 800; - } else { - duration = len / speed; - } - } else if (typeof duration === 'function') { - duration = duration(len); - } - - return { - delay, - duration, - easing, - css: (t, u) => `stroke-dasharray: ${t * len} ${u * len}` - }; + if (duration === undefined) duration = speed ? len / speed : 800; + else duration = run_duration(duration, len); + return { delay, duration, easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};` }; } -interface CrossfadeParams { - delay: number; - duration: number | ((len: number) => number); - easing: EasingFunction; -} - -type ClientRectMap = Map; - -export function crossfade({ fallback, ...defaults }: CrossfadeParams & { - fallback: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig; -}) { - const to_receive: ClientRectMap = new Map(); - const to_send: ClientRectMap = new Map(); +export function crossfade({ delay: default_delay = 0, duration: default_duration = (d) => Math.sqrt(d) * 30, easing: default_easing = cubicOut, fallback }: CrossFadeConfig) { + const a: ElementMap = new Map(); + const b: ElementMap = new Map(); - function crossfade(from: ClientRect, node: Element, params: CrossfadeParams): TransitionConfig { - const { - delay = 0, - duration = d => Math.sqrt(d) * 30, - easing = cubicOut - } = assign(assign({}, defaults), params); - - const to = node.getBoundingClientRect(); + const crossfade = (from_node: Element, to_node: Element, { delay = default_delay, easing = default_easing, duration = default_duration }: TimeableConfig ) => { + const from = from_node.getBoundingClientRect(); + const to = to_node.getBoundingClientRect(); const dx = from.left - to.left; const dy = from.top - to.top; const dw = from.width / to.width; const dh = from.height / to.height; - const d = Math.sqrt(dx * dx + dy * dy); - - const style = getComputedStyle(node); - const transform = style.transform === 'none' ? '' : style.transform; - const opacity = +style.opacity; - + const { transform, opacity } = getComputedStyle(to_node); + const op = +opacity; + const prev = transform === 'none' ? '' : transform; return { delay, - duration: is_function(duration) ? duration(d) : duration, easing, + duration: run_duration(duration, Math.sqrt(dx * dx + dy * dy)), css: (t, u) => ` - opacity: ${t * opacity}; + opacity: ${t * op}; transform-origin: top left; - transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1-t) * dw}, ${t + (1-t) * dh}); - ` - }; - } - - function transition(items: ClientRectMap, counterparts: ClientRectMap, intro: boolean) { - return (node: Element, params: CrossfadeParams & { key: any }) => { - items.set(params.key, { - rect: node.getBoundingClientRect() - }); - + transform: ${prev} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh}); + `, + } as CssTransitionConfig; + }; + + const transition = (a: ElementMap, b: ElementMap, is_intro: boolean) => ( node: Element, params: MarkedCrossFadeConfig ) => { + const { key } = params; + a.set(key, node); + if (b.has(key)) { + const from_node = b.get(key); + b.delete(key); + return crossfade(from_node, node, params); + } else { return () => { - if (counterparts.has(params.key)) { - const { rect } = counterparts.get(params.key); - counterparts.delete(params.key); - - return crossfade(rect, node, params); + if (b.has(key)) { + const from_node = b.get(key); + b.delete(key); + return crossfade(from_node, node, params); + } else { + a.delete(key); + return fallback && fallback(node, params, is_intro); } - - // if the node is disappearing altogether - // (i.e. wasn't claimed by the other list) - // then we need to supply an outro - items.delete(params.key); - return fallback && fallback(node, params, intro); }; - }; - } + } + }; - return [ - transition(to_send, to_receive, false), - transition(to_receive, to_send, true) - ]; + return [transition(b, a, false), transition(a, b, true)]; } From 8700cad8a479c72c5076eeb39d1618c8d4716a6e Mon Sep 17 00:00:00 2001 From: pushkine Date: Sun, 31 May 2020 23:13:34 +0200 Subject: [PATCH 02/12] fix --- src/runtime/internal/scheduler.ts | 4 +--- src/runtime/internal/transitions.ts | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 709999ee9709..796ca624be7a 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -51,7 +51,7 @@ export const flush = () => { after_update; do { - while (i < dirty_components.length) { + for (;i < dirty_components.length;i++) { ({ $$ } = set_current_component(dirty_components[i])); // todo : is this check still necessary ? @@ -73,8 +73,6 @@ export const flush = () => { for (j = 0, { after_update } = $$; j < after_update.length; j++) { add_render_callback(after_update[j]); } - - i = i + 1; } dirty_components.length = 0; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 48f2df750f84..762fa38bf927 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -5,7 +5,6 @@ import { now, noop } from './environment'; import { setFrameTimeout, setTweenTimeout } from './loop'; import { add_measure_callback } from './scheduler'; import { animate_css } from './style_manager'; -import { linear } from 'svelte/easing'; type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig; export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void; @@ -175,7 +174,7 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio params, end_time - t, start_time > t ? start_time - t : 0, - (1 - elapsed_ratio) * (1 - (config.easing || linear)(1 - (end_time - t) / (end_time - start_time))) + (1 - elapsed_ratio) * (1 - (config.easing || ((v) => v))(1 - (end_time - t) / (end_time - start_time))) ) ); else running_bidi.delete(this); From 927c8929557a12aed58be87b3d6405760c531eb1 Mon Sep 17 00:00:00 2001 From: pushkine Date: Mon, 1 Jun 2020 19:54:13 +0200 Subject: [PATCH 03/12] fix --- src/compiler/compile/render_dom/Block.ts | 2 +- .../compile/render_dom/wrappers/EachBlock.ts | 11 +- .../render_dom/wrappers/Element/index.ts | 18 ++-- src/runtime/internal/environment.ts | 5 +- src/runtime/internal/keyed_each.ts | 10 ++ src/runtime/internal/loop.ts | 30 +++--- src/runtime/internal/scheduler.ts | 26 +++-- src/runtime/internal/style_manager.ts | 101 +++++++++--------- src/runtime/internal/transitions.ts | 45 ++++---- src/runtime/transition/index.ts | 14 +-- .../each-block-keyed-animated/expected.js | 15 +-- test/js/samples/each-block-keyed/expected.js | 3 +- test/js/samples/transition-local/expected.js | 12 +-- .../transition-repeated-outro/expected.js | 24 ++--- .../transition-css-in-out-in/_config.js | 10 +- .../transition-js-await-block/_config.js | 4 +- .../samples/transition-js-deferred/_config.js | 2 +- .../transition-js-deferred/main.svelte | 2 +- .../samples/transition-js-delay/_config.js | 8 +- .../_config.js | 12 +-- .../transition-js-events-in-out/_config.js | 12 +-- .../_config.js | 4 +- 22 files changed, 188 insertions(+), 182 deletions(-) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index c1a3fdd098d9..91e326d98cde 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -206,7 +206,7 @@ export default class Block { } group_transition_out(fn) { - return this.has_outros ? b`@group_transition_out((#transition_out) => { ${fn(x`#transition_out`)} })` : fn(null); + return b`@group_transition_out((#transition_out) => { ${fn(x`#transition_out`)} })`; } add_variable(id: Identifier, init?: Node) { diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index e32549a79e5c..3a6ad03240a5 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -7,7 +7,7 @@ import FragmentWrapper from './Fragment'; import { b, x } from 'code-red'; import ElseBlock from '../../nodes/ElseBlock'; import { Identifier, Node } from 'estree'; -import bit_state from '../../utils/bit_state' +import bit_state from '../../utils/bit_state'; export class ElseBlockWrapper extends Wrapper { node: ElseBlock; @@ -434,7 +434,7 @@ export default class EachBlockWrapper extends Wrapper { ${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} ${this.renderer.options.dev && b`@validate_each_keys(#ctx, ${this.vars.each_block_value}, ${this.vars.get_each_context}, ${get_key});`} - ${this.block.group_transition_out(update_keyed_each)} + ${(this.block.has_outros ? this.block.group_transition_out : v => v(null))(update_keyed_each)} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} `); } @@ -548,9 +548,12 @@ export default class EachBlockWrapper extends Wrapper { if (this.block.has_outros) { remove_old_blocks = this.block.group_transition_out((transition_out) => b`for (#i = ${data_length}; #i < ${view_length}; #i += 1) { - ${transition_out}(#i); + const #index = #i; + ${transition_out}(${iterations}[#index], () => { + ${iterations}[#index] = null; + }); }` - ) + ); } else { remove_old_blocks = b` for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index f8aae39a04e9..28ad3791c28d 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -751,8 +751,8 @@ export default class ElementWrapper extends Wrapper { let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 2, ${snippet});`; if (intro.is_local) { - intro_block = b`if (#local) {${intro_block}}`; - outro_block = b`if (#local) {${outro_block}}`; + intro_block = b`if (#local) { ${intro_block} }`; + outro_block = b`if (#local) { ${outro_block} }`; } block.chunks.intro.push(intro_block); block.chunks.outro.push(outro_block); @@ -777,12 +777,11 @@ export default class ElementWrapper extends Wrapper { if (!intro) return; const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`); - block.add_variable(intro_var, x`@noop`); + block.add_variable(intro_var, outro ? x`@noop`: null); - let start_intro; - if (intro.is_local) - start_intro = b`if (#local) ${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; - else start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; + let start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; + if (!outro) start_intro = b`if (!${intro_var}) { ${start_intro} }`; + if (intro.is_local) start_intro = b`if (#local) { ${start_intro} }`; block.chunks.intro.push(start_intro); } // TODO @@ -798,9 +797,8 @@ export default class ElementWrapper extends Wrapper { const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`); block.add_variable(outro_var, x`@noop`); - let start_outro; - if (outro.is_local) start_outro = b`if (#local) @run_transition(${node}, ${transitionFn}, 2, ${params});`; - else start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`; + let start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`; + if (outro.is_local) start_outro = b`if (#local) { ${start_outro} }`; block.chunks.outro.push(start_outro); block.chunks.destroy.push(b`if (detaching) ${outro_var}();`); diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index bda01b4d3f2b..2dfe05eba5d4 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -21,8 +21,8 @@ export let raf = is_browser ? requestAnimationFrame : noop; export let framerate = 1000 / 60; /*#__PURE__*/ raf((t1) => { raf((d) => { - const f24 = 1000 / 24, - f144 = 1000 / 144; + const f24 = 1000 / 24; + const f144 = 1000 / 144; framerate = (d = d - t1) > f144 ? f144 : d < f24 ? f24 : d; }); }); @@ -30,4 +30,3 @@ export let framerate = 1000 / 60; /* tests only */ export const set_now = (v) => void (now = v); export const set_raf = (fn) => void (raf = fn); -export const set_framerate = (v) => void (framerate = v); diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index 3c89c01e4b36..b7991d2f9fa6 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -95,3 +95,13 @@ export const update_keyed_each = ( return new_blocks; }; +export function validate_each_keys(ctx, list, get_context, get_key) { + const keys = new Set(); + for (let i = 0; i < list.length; i++) { + const key = get_key(get_context(ctx, list, i)); + if (keys.has(key)) { + throw new Error(`Cannot have duplicate keys in a keyed each`); + } + keys.add(key); + } +} \ No newline at end of file diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index 5cdfc434ad0b..95abd518fa42 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -2,13 +2,13 @@ import { now, raf, framerate, noop } from './environment'; type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; -let i = 0, - j = 0, - n = 0, - v : TaskCallback; +let i = 0; +let j = 0; +let n = 0; +let v: TaskCallback; -let running_frame : Array = [], - next_frame : Array = []; +let running_frame: TaskCallback[] = []; +let next_frame: TaskCallback[] = []; const run = (t: number) => { [running_frame, next_frame] = [next_frame, running_frame]; @@ -24,11 +24,11 @@ const run = (t: number) => { type TimeoutTask = { timestamp: number; callback: (now: number) => void }; -const pending_insert_timed : Array = [], - timed_tasks : Array = []; +const pending_insert_timed: TimeoutTask[] = []; +const timed_tasks: TimeoutTask[] = []; -let pending_inserts = false, - running_timed = false; +let pending_inserts = false; +let running_timed = false; const run_timed = (now: number) => { let last_index = timed_tasks.length - 1; @@ -79,20 +79,20 @@ export const setTweenTimeout = ( stop: (now: number) => void, end_time: number, run: (now: number) => void, - duration = end_time - now() + duration = end_time - now(), + is_outro = false ): TaskCanceller => { let running = true; - let t = 0.0; + let t = 1 - (end_time - now) / duration || 0; + if (!is_outro && t <= 1.0) run(t >= 0.0 ? t : 0); unsafe_loop((now) => { if (!running) return false; - t = 1.0 - (end_time - now) / duration; + t = 1 - (end_time - now) / duration; if (t >= 1.0) return run(1), stop(now), false; if (t >= 0.0) run(t); return running; }); return (run_last = false) => { - // since outros are cancelled in group by a setFrameTimeout - // tick(0, 1) has to be called in here if (run_last) run(1); running = false; }; diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 796ca624be7a..47c6edd08d45 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -28,13 +28,17 @@ export const schedule_update = (component) => { dirty_components.push(component); if (!update_scheduled) { update_scheduled = true; - resolved_promise.then(flush); + if (!is_flushing) { + resolved_promise.then(flush); + } } }; export const tick = () => { if (!update_scheduled) { update_scheduled = true; - resolved_promise.then(flush); + if (!is_flushing) { + resolved_promise.then(flush); + } } return resolved_promise; }; @@ -42,13 +46,13 @@ export const flush = () => { if (is_flushing) return; else is_flushing = true; - let i = 0, - j = 0, - t = 0, - $$: T$$, - dirty, - before_update, - after_update; + let i = 0; + let j = 0; + let t = 0; + let $$: T$$; + let dirty; + let before_update; + let after_update; do { for (;i < dirty_components.length;i++) { @@ -101,4 +105,8 @@ export const flush = () => { flush_callbacks.length = i = j = 0; is_flushing = false; + if (update_scheduled) { + // reflush if applying animations triggered an update + flush(); + } }; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 5d5b30885d5c..2efbae0ec904 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,4 +1,5 @@ import { framerate } from './environment'; +import { methodify } from './utils'; let documents_uid = 0; let running_animations = 0; @@ -7,56 +8,54 @@ const document_uid = new Map(); const document_stylesheets = new Map(); const current_rules = new Set(); -export const animate_css = /*#__PURE__*/ Function.prototype.call.bind(function animate_css( - this: HTMLElement, - css: (t: number) => string, - duration: number, - delay = 0 -) { - if (!document_uid.has(this.ownerDocument)) { - document_uid.set(this.ownerDocument, documents_uid++); - document_stylesheets.set( - this.ownerDocument, - this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet - ); - } - let rule = '{\n'; - for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; - rule += `100% {${css(1)}}\n}`; - - // darkskyapp/string-hash - let i = rule.length, hash = 5381; - while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i); - const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`; - - if (!current_rules.has(name)) { - current_rules.add(name); - const stylesheet = document_stylesheets.get(this.ownerDocument); - stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); - } +export const animate_css = /*#__PURE__*/ methodify( + function animate_css(this: HTMLElement, css: (t: number) => string, duration: number, delay = 0) { + if (!document_uid.has(this.ownerDocument)) { + document_uid.set(this.ownerDocument, documents_uid++); + document_stylesheets.set( + this.ownerDocument, + this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet + ); + } + let rule = '{\n'; + for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + rule += `100% {${css(1)}}\n}`; + + // darkskyapp/string-hash + let i = rule.length; + let hash = 5381; + while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i); + const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`; + + if (!current_rules.has(name)) { + current_rules.add(name); + const stylesheet = document_stylesheets.get(this.ownerDocument); + stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); + } - const previous = this.style.animation; - this.style.animation = `${ - previous ? `${previous}, ` : '' - }${duration}ms linear ${delay}ms 1 normal both running ${name}`; - - running_animations++; - - return () => { - const prev = (this.style.animation || '').split(', '); - const next = prev.filter((anim) => !anim.includes(name)); - if (prev.length !== next.length) this.style.animation = next.join(', '); - if (--running_animations === 0) { - document_stylesheets.forEach((stylesheet) => { - let i = stylesheet.cssRules.length; - while (i--) stylesheet.deleteRule(i); - }); - current_rules.clear(); - if (1 !== documents_uid) { - document_stylesheets.clear(); - document_uid.clear(); - documents_uid = 0; + const previous = this.style.animation; + this.style.animation = `${ + previous ? `${previous}, ` : '' + }${duration}ms linear ${delay}ms 1 normal both running ${name}`; + + running_animations++; + + return () => { + const prev = (this.style.animation || '').split(', '); + const next = prev.filter((anim) => !anim.includes(name)); + if (prev.length !== next.length) this.style.animation = next.join(', '); + if (--running_animations === 0) { + document_stylesheets.forEach((stylesheet) => { + let i = stylesheet.cssRules.length; + while (i--) stylesheet.deleteRule(i); + }); + current_rules.clear(); + if (1 !== documents_uid) { + document_stylesheets.clear(); + document_uid.clear(); + documents_uid = 0; + } } - } - }; -}); \ No newline at end of file + }; + } +); \ No newline at end of file diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 762fa38bf927..4adcfd691558 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -5,6 +5,7 @@ import { now, noop } from './environment'; import { setFrameTimeout, setTweenTimeout } from './loop'; import { add_measure_callback } from './scheduler'; import { animate_css } from './style_manager'; +import { methodify } from './utils'; type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig; export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void; @@ -22,9 +23,9 @@ export const transition_out = (block: Fragment, local?) => { }; type TransitionGroup = { /* parent group */ p: TransitionGroup; - /* callbacks */ c: ((cancelled: boolean) => void)[]; + /* callbacks */ c: Array<((cancelled: boolean) => void)>; /* running outros */ r: number; - /* stop callbacks */ s: ((t: number) => void)[]; + /* stop callbacks */ s: Array<((t: number) => void)>; /* outro timeout */ t: number; }; let transition_group: TransitionGroup; @@ -82,7 +83,7 @@ export const enum tx { bidirectional = 4, animation = 8, } -export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(function transition( +export const run_transition = /*#__PURE__*/ methodify(function transition( this: HTMLElement, fn: TransitionFn, rx: tx, @@ -96,11 +97,11 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio let running = true; - let cancel_css, - cancel_raf; + let cancel_css; + let cancel_raf; - let start_time = 0, - end_time = 0; + let start_time = 0; + let end_time = 0; const current_group = transition_group; if (rx & tx.outro) current_group.r++; @@ -129,7 +130,7 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio } if (css) cancel_css = animate_css(this, runner(css), duration, delay); - + if (rx & tx.outro) { if (current_group.s.push(stop) === current_group.r) { setFrameTimeout((t) => { @@ -138,7 +139,7 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio } else { current_group.t = Math.max(end_time, current_group.t); } - if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration); + if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration, true); } else { cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); } @@ -146,7 +147,6 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio }); const stop: StopResetReverseFn = (t?: number | 1 | -1) => { - // resetting `out:` in intros if (t === 1 && rx & tx.outro && 0 === (rx & tx.bidirectional) && 'tick' in config) config.tick(1, 0); if (false === running) return; @@ -184,19 +184,16 @@ export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(functio }); const running_bidi: Map = new Map(); -export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional( - this: HTMLElement, - fn: TransitionFn, - rx: tx.intro | tx.outro, - params: any -) { - let cancel; - running_bidi.set( - this, - (cancel = - (running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params)) - ); - return cancel; -}); +export const run_bidirectional_transition = /*#__PURE__*/ methodify( + function bidirectional(this: HTMLElement, fn: TransitionFn, rx: tx.intro | tx.outro, params: any ) { + let cancel; + running_bidi.set( + this, + (cancel = + (running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params)) + ); + return cancel; + } +); export const run_duration = (duration, value1, value2?): number => typeof duration === 'function' ? duration(value1, value2) : duration; diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index d7ffb3b42de0..7c262b194bc5 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -13,14 +13,14 @@ export interface CssTransitionConfig extends CssAnimationConfig { tick?: (t: number, u?: number) => void; } -type FlyParams = FadingConfig & { x: number; y: number; }; -type BlurParams = FadingConfig & { amount: number; }; -type ScaleParams = FadingConfig & { start: number; }; -type DrawParams = CssAnimationConfig & { speed : number }; -type FadingConfig = CssAnimationConfig & { opacity: number; }; -type MarkedCrossFadeConfig = TimeableConfig & { key: any; }; +type FlyParams = FadingConfig & { x: number; y: number }; +type BlurParams = FadingConfig & { amount: number }; +type ScaleParams = FadingConfig & { start: number }; +type DrawParams = CssAnimationConfig & { speed: number }; +type FadingConfig = CssAnimationConfig & { opacity: number }; +type MarkedCrossFadeConfig = TimeableConfig & { key: any }; export type TimeableConfig = Omit & { duration?: number | ((len: number) => number) }; -type CrossFadeConfig = TimeableConfig & { fallback(node: Element, params: TimeableConfig, intro: boolean): CssTransitionConfig; }; +type CrossFadeConfig = TimeableConfig & { fallback(node: Element, params: TimeableConfig, intro: boolean): CssTransitionConfig }; type ElementMap = Map; export function blur(node: Element, { delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 }: BlurParams): CssTransitionConfig { diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 7fb81c27a226..24dfd5465a34 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -2,15 +2,14 @@ import { SvelteComponent, append, - create_animation, detach, element, empty, - fix_and_destroy_block, fix_position, init, insert, noop, + run_animation, safe_not_equal, set_data, text, @@ -28,6 +27,7 @@ function create_each_block(key_1, ctx) { let div; let t_value = /*thing*/ ctx[1].name + ""; let t; + let unfreeze; let rect; let stop_animation = noop; @@ -50,15 +50,18 @@ function create_each_block(key_1, ctx) { rect = div.getBoundingClientRect(); }, f() { - fix_position(div); stop_animation(); + unfreeze = fix_position(div, rect); }, a() { - stop_animation(); - stop_animation = create_animation(div, rect, foo, {}); + if (unfreeze) return; else { + stop_animation(); + stop_animation = run_animation(div, rect, foo); + } }, d(detaching) { if (detaching) detach(div); + unfreeze = void 0; } }; } @@ -95,7 +98,7 @@ function create_fragment(ctx) { if (dirty & /*things*/ 1) { const each_value = /*things*/ ctx[0]; for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r(); - each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context); + each_blocks = update_keyed_each(each_blocks, dirty, ctx, 3, get_key, each_value, each_1_lookup, each_1_anchor.parentNode, create_each_block, each_1_anchor, get_each_context); for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a(); } }, diff --git a/test/js/samples/each-block-keyed/expected.js b/test/js/samples/each-block-keyed/expected.js index ad8c074e99f8..0707de49e502 100644 --- a/test/js/samples/each-block-keyed/expected.js +++ b/test/js/samples/each-block-keyed/expected.js @@ -2,7 +2,6 @@ import { SvelteComponent, append, - destroy_block, detach, element, empty, @@ -79,7 +78,7 @@ function create_fragment(ctx) { p(ctx, [dirty]) { if (dirty & /*things*/ 1) { const each_value = /*things*/ ctx[0]; - each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context); + each_blocks = update_keyed_each(each_blocks, dirty, ctx, 1, get_key, each_value, each_1_lookup, each_1_anchor.parentNode, create_each_block, each_1_anchor, get_each_context); } }, i: noop, diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js index 25a03f026f75..30d31805ef73 100644 --- a/test/js/samples/transition-local/expected.js +++ b/test/js/samples/transition-local/expected.js @@ -1,14 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, - add_render_callback, - create_in_transition, detach, element, empty, init, insert, noop, + run_transition, safe_not_equal, transition_in } from "svelte/internal"; @@ -66,14 +65,13 @@ function create_if_block_1(ctx) { i(local) { if (local) { if (!div_intro) { - add_render_callback(() => { - div_intro = create_in_transition(div, foo, {}); - div_intro.start(); - }); + div_intro = run_transition(div, foo, 1); } } }, - o: noop, + o(local) { + div_intro(); + }, d(detaching) { if (detaching) detach(div); } diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js index 1f76a93666be..02febde2af59 100644 --- a/test/js/samples/transition-repeated-outro/expected.js +++ b/test/js/samples/transition-repeated-outro/expected.js @@ -1,14 +1,14 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, - check_outros, - create_out_transition, detach, element, empty, - group_outros, + group_transition_out, init, insert, + noop, + run_transition, safe_not_equal, transition_in, transition_out @@ -18,7 +18,7 @@ import { fade } from "svelte/transition"; function create_if_block(ctx) { let div; - let div_outro; + let div_outro = noop; let current; return { @@ -32,16 +32,16 @@ function create_if_block(ctx) { }, i(local) { if (current) return; - if (div_outro) div_outro.end(1); + div_outro(1); current = true; }, o(local) { - div_outro = create_out_transition(div, fade, {}); + div_outro = run_transition(div, fade, 2); current = false; }, d(detaching) { if (detaching) detach(div); - if (detaching && div_outro) div_outro.end(); + if (detaching) div_outro(); } }; } @@ -74,13 +74,11 @@ function create_fragment(ctx) { if_block.m(if_block_anchor.parentNode, if_block_anchor); } } else if (if_block) { - group_outros(); - - transition_out(if_block, 1, 1, () => { - if_block = null; + group_transition_out(transition_out => { + transition_out(if_block, () => { + if_block = null; + }); }); - - check_outros(); } }, i(local) { diff --git a/test/runtime/samples/transition-css-in-out-in/_config.js b/test/runtime/samples/transition-css-in-out-in/_config.js index 10719280e92d..314271687736 100644 --- a/test/runtime/samples/transition-css-in-out-in/_config.js +++ b/test/runtime/samples/transition-css-in-out-in/_config.js @@ -2,19 +2,19 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); + const startsWith = (str) => + assert.equal(div.style.animation.slice(0, div.style.animation.length-1), str); - assert.equal(div.style.animation, `__svelte_3809512021_0 100ms linear 0ms 1 both`); + startsWith(`100ms linear 0ms 1 normal both running __svelte_3261048502`); raf.tick(50); component.visible = false; - // both in and out styles - assert.equal(div.style.animation, `__svelte_3809512021_0 100ms linear 0ms 1 both, __svelte_3750847757_0 100ms linear 0ms 1 both`); + startsWith(`100ms linear 0ms 1 normal both running __svelte_890840093`); raf.tick(75); component.visible = true; - // reset original styles - assert.equal(div.style.animation, `__svelte_3809512021_1 100ms linear 0ms 1 both`); + startsWith(`100ms linear 0ms 1 normal both running __svelte_3261048502`); }, }; diff --git a/test/runtime/samples/transition-js-await-block/_config.js b/test/runtime/samples/transition-js-await-block/_config.js index 80546ae6b8f4..d019c062d6a0 100644 --- a/test/runtime/samples/transition-js-await-block/_config.js +++ b/test/runtime/samples/transition-js-await-block/_config.js @@ -29,8 +29,8 @@ export default { const ps = document.querySelectorAll('p'); assert.equal(ps[1].className, 'pending'); assert.equal(ps[0].className, 'then'); - assert.equal(ps[1].foo, 0.2); - assert.equal(ps[0].foo, 0.3); + assert.equal(Math.round(ps[1].foo * 10) / 10, 0.2); + assert.equal(Math.round(ps[0].foo * 10) / 10, 0.3); }); } }; diff --git a/test/runtime/samples/transition-js-deferred/_config.js b/test/runtime/samples/transition-js-deferred/_config.js index 9d6833d2f491..05929964cd4e 100644 --- a/test/runtime/samples/transition-js-deferred/_config.js +++ b/test/runtime/samples/transition-js-deferred/_config.js @@ -3,7 +3,7 @@ export default { component.visible = true; return Promise.resolve().then(() => { - const div = target.querySelector('div'); + const div = target.querySelector('.foo'); assert.equal(div.foo, 0); raf.tick(50); diff --git a/test/runtime/samples/transition-js-deferred/main.svelte b/test/runtime/samples/transition-js-deferred/main.svelte index 063773df396d..eb75f3a7e3af 100644 --- a/test/runtime/samples/transition-js-deferred/main.svelte +++ b/test/runtime/samples/transition-js-deferred/main.svelte @@ -40,7 +40,7 @@ {#if visible} -
a
+
a
{:else}
b
{/if} \ No newline at end of file diff --git a/test/runtime/samples/transition-js-delay/_config.js b/test/runtime/samples/transition-js-delay/_config.js index a0613737b25d..43d7cf5b2a5f 100644 --- a/test/runtime/samples/transition-js-delay/_config.js +++ b/test/runtime/samples/transition-js-delay/_config.js @@ -13,15 +13,9 @@ export default { component.visible = false; raf.tick(125); - assert.equal(div.foo, 0.75); + assert.equal(div.foo, 0.25); raf.tick(150); - assert.equal(div.foo, 1); - - raf.tick(175); - assert.equal(div.foo, 0.75); - - raf.tick(250); assert.equal(div.foo, 0); } }; \ No newline at end of file diff --git a/test/runtime/samples/transition-js-each-block-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-intro-outro/_config.js index 3a764c561241..b3cb9b097713 100644 --- a/test/runtime/samples/transition-js-each-block-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-intro-outro/_config.js @@ -19,9 +19,9 @@ export default { component.visible = false; raf.tick(70); - assert.equal(divs[0].foo, 0.7); - assert.equal(divs[1].foo, 0.7); - assert.equal(divs[2].foo, 0.7); + assert.equal(divs[0].foo, 0.5); + assert.equal(divs[1].foo, 0.5); + assert.equal(divs[2].foo, 0.5); assert.equal(divs[0].bar, 0.8); assert.equal(divs[1].bar, 0.8); @@ -30,9 +30,9 @@ export default { component.visible = true; raf.tick(100); - assert.equal(divs[0].foo, 0.3); - assert.equal(divs[1].foo, 0.3); - assert.equal(divs[2].foo, 0.3); + assert.equal(Math.round(divs[0].foo * 10) / 10, 0.3); + assert.equal(Math.round(divs[1].foo * 10) / 10, 0.3); + assert.equal(Math.round(divs[2].foo * 10) / 10, 0.3); assert.equal(divs[0].bar, 1); assert.equal(divs[1].bar, 1); diff --git a/test/runtime/samples/transition-js-events-in-out/_config.js b/test/runtime/samples/transition-js-events-in-out/_config.js index 854e7e2f4b24..d6e1019f9128 100644 --- a/test/runtime/samples/transition-js-events-in-out/_config.js +++ b/test/runtime/samples/transition-js-events-in-out/_config.js @@ -10,7 +10,7 @@ export default {

waiting...

`, - async test({ assert, component, target, raf }) { + test({ assert, component, target, raf }) { component.visible = true; assert.htmlEqual(target.innerHTML, ` @@ -21,12 +21,12 @@ export default {

d

`); - await raf.tick(50); + raf.tick(50); assert.deepEqual(component.intros.sort(), ['a', 'b', 'c', 'd']); assert.equal(component.intro_count, 4); - await raf.tick(100); + raf.tick(100); assert.equal(component.intro_count, 0); assert.htmlEqual(target.innerHTML, ` @@ -47,16 +47,16 @@ export default {

d

`); - await raf.tick(150); + raf.tick(150); assert.deepEqual(component.outros.sort(), ['a', 'b', 'c', 'd']); assert.equal(component.outro_count, 4); - await raf.tick(200); + raf.tick(200); assert.equal(component.outro_count, 0); component.visible = true; - await raf.tick(250); + raf.tick(250); assert.deepEqual(component.intros.sort(), ['a', 'a', 'b', 'b', 'c', 'c', 'd', 'd']); assert.equal(component.intro_count, 4); diff --git a/test/runtime/samples/transition-js-if-block-intro-outro/_config.js b/test/runtime/samples/transition-js-if-block-intro-outro/_config.js index e6512c93ed23..edf7438d27ef 100644 --- a/test/runtime/samples/transition-js-if-block-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-if-block-intro-outro/_config.js @@ -32,11 +32,11 @@ export default { component.visible = false; raf.tick(1300); - assert.equal(div.foo, 0.75); + assert.equal(div.foo, 0.5); assert.equal(div.bar, 0.75); raf.tick(1400); - assert.equal(div.foo, 1); + assert.equal(div.foo, 0.5); assert.equal(div.bar, 0.5); raf.tick(2000); From ab09b33d199180c9a9f484ba3f61ee3f4db0b3d8 Mon Sep 17 00:00:00 2001 From: pushkine Date: Mon, 1 Jun 2020 20:07:37 +0200 Subject: [PATCH 04/12] fix --- src/runtime/internal/keyed_each.ts | 2 +- src/runtime/internal/loop.ts | 39 ++++++++++++++++++++++++++++-- src/runtime/motion/spring.ts | 2 +- src/runtime/motion/tweened.ts | 2 +- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/runtime/internal/keyed_each.ts b/src/runtime/internal/keyed_each.ts index b7991d2f9fa6..88db365ebe8a 100644 --- a/src/runtime/internal/keyed_each.ts +++ b/src/runtime/internal/keyed_each.ts @@ -47,7 +47,7 @@ export const update_keyed_each = ( const insert = (block) => { transition_in(block, 1); - block.m(node, next, lookup.has(block.key)); + block.m(node, next); lookup.set(block.key, block); next = block.first; n--; diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index 95abd518fa42..b4c1c895af89 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -118,5 +118,40 @@ export const onEachFrame = ( }; /** tests only */ -export const clear_loops = () => - void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false)); +export const clear_loops = () => { + next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false); + tasks.clear(); +}; + +/** legacy loop for svelte/motion */ + +export interface MotionTask { abort(): void; promise: Promise } +type MotionTaskCallback = (now: number) => boolean | void; +type MotionTaskEntry = { c: MotionTaskCallback; f: () => void }; + +const tasks = new Set(); + +function run_tasks(now: number) { + tasks.forEach(task => { + if (!task.c(now)) { + tasks.delete(task); + task.f(); + } + }); + + if (tasks.size !== 0) raf(run_tasks); +} +export function motion_loop(callback: MotionTaskCallback): MotionTask { + let task: MotionTaskEntry; + + if (tasks.size === 0) raf(run_tasks); + + return { + promise: new Promise(fulfill => { + tasks.add(task = { c: callback, f: fulfill }); + }), + abort() { + tasks.delete(task); + } + }; +} \ No newline at end of file diff --git a/src/runtime/motion/spring.ts b/src/runtime/motion/spring.ts index 5845a13a577f..d78926aadb1a 100644 --- a/src/runtime/motion/spring.ts +++ b/src/runtime/motion/spring.ts @@ -1,5 +1,5 @@ import { Readable, writable } from 'svelte/store'; -import { loop, now, Task } from 'svelte/internal'; +import { motion_loop as loop, now, MotionTask as Task } from 'svelte/internal'; import { is_date } from './utils'; interface TickContext { diff --git a/src/runtime/motion/tweened.ts b/src/runtime/motion/tweened.ts index c802604c0e27..0c4cf55d8178 100644 --- a/src/runtime/motion/tweened.ts +++ b/src/runtime/motion/tweened.ts @@ -1,5 +1,5 @@ import { Readable, writable } from 'svelte/store'; -import { assign, loop, now, Task } from 'svelte/internal'; +import { assign, motion_loop as loop, now, MotionTask as Task } from 'svelte/internal'; import { linear } from 'svelte/easing'; import { is_date } from './utils'; From 5d1608a24535e4b4092710e1ad53bb65a2f40d71 Mon Sep 17 00:00:00 2001 From: pushkine Date: Mon, 1 Jun 2020 20:47:58 +0200 Subject: [PATCH 05/12] fix --- src/runtime/internal/Component.ts | 3 +-- src/runtime/internal/animations.ts | 3 +-- src/runtime/internal/dev.ts | 5 ++-- src/runtime/internal/dom.ts | 29 ++++++++++++++++++--- src/runtime/internal/environment.ts | 40 ++++++++++++----------------- src/runtime/internal/globals.ts | 7 +++++ src/runtime/internal/loop.ts | 3 ++- src/runtime/internal/scheduler.ts | 4 ++- src/runtime/internal/transitions.ts | 4 +-- src/runtime/internal/utils.ts | 3 +-- 10 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/runtime/internal/globals.ts diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 780e7e0eec05..f3af2f33e423 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -1,9 +1,8 @@ import { add_render_callback, flush, schedule_update } from './scheduler'; import { current_component, set_current_component } from './lifecycle'; -import { blank_object, is_function, run, run_all } from './utils'; +import { blank_object, is_function, run, run_all, noop } from './utils'; import { children, detach } from './dom'; import { transition_in } from './transitions'; -import { noop } from './environment'; export interface Fragment { key: string|null; diff --git a/src/runtime/internal/animations.ts b/src/runtime/internal/animations.ts index d266488a8cc0..4d65480c7c3e 100644 --- a/src/runtime/internal/animations.ts +++ b/src/runtime/internal/animations.ts @@ -1,6 +1,5 @@ import { run_transition } from './transitions'; -import { noop } from './environment'; -import { methodify } from './utils'; +import { methodify, noop } from './utils'; import { CssTransitionConfig } from 'svelte/transition'; type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => CssTransitionConfig; diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index d38cb8d0a0d2..f1bdfc9ca28d 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -1,6 +1,5 @@ import { custom_event, append, insert, detach, listen, attr } from './dom'; import { SvelteComponent } from './Component'; -import { has_Symbol } from './environment'; export function dispatch_dev(type: string, detail?: T) { document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); @@ -83,7 +82,7 @@ export function set_data_dev(text, data) { export function validate_each_argument(arg) { if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { let msg = '{#each} only iterates over array-like objects.'; - if (has_Symbol && arg && Symbol.iterator in arg) { + if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { msg += ' You can use a spread to convert this iterable into an array.'; } throw new Error(msg); @@ -141,4 +140,4 @@ export function loop_guard(timeout) { throw new Error(`Infinite loop detected`); } }; -} +} \ No newline at end of file diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 7fdc26808bc2..07d0896a271a 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,5 +1,4 @@ import { has_prop } from "./utils"; -import { is_cors } from "./environment"; export function append(target: Node, node: Node) { target.appendChild(node); @@ -235,6 +234,26 @@ export function select_multiple_value(select) { return [].map.call(select.querySelectorAll(':checked'), option => option.__value); } +// unfortunately this can't be a constant as that wouldn't be tree-shakeable +// so we cache the result instead +let crossorigin: boolean; + +export function is_crossorigin() { + if (crossorigin === undefined) { + crossorigin = false; + + try { + if (typeof window !== 'undefined' && window.parent) { + void window.parent.document; + } + } catch (error) { + crossorigin = true; + } + } + + return crossorigin; +} + export function add_resize_listener(node: HTMLElement, fn: () => void) { const computed_style = getComputedStyle(node); const z_index = (parseInt(computed_style.zIndex) || 0) - 1; @@ -251,9 +270,11 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { iframe.setAttribute('aria-hidden', 'true'); iframe.tabIndex = -1; + const crossorigin = is_crossorigin(); + let unsubscribe: () => void; - if (is_cors) { + if (crossorigin) { iframe.src = `data:text/html,`; unsubscribe = listen(window, 'message', (event: MessageEvent) => { if (event.source === iframe.contentWindow) fn(); @@ -268,7 +289,7 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { append(node, iframe); return () => { - if (is_cors) { + if (crossorigin) { unsubscribe(); } else if (unsubscribe && iframe.contentWindow) { unsubscribe(); @@ -333,4 +354,4 @@ export class HtmlTag { d() { this.n.forEach(detach); } -} +} \ No newline at end of file diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index 2dfe05eba5d4..a4de8f491278 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -1,23 +1,12 @@ -export function noop() {} -export const is_browser = typeof window !== 'undefined'; -export const is_iframe = is_browser && window.self !== window.top; -export const is_cors = - is_iframe && - /*#__PURE__*/ (() => { - try { - if (window.parent) void window.parent.document; - return false; - } catch (error) { - return true; - } - })(); -export const has_Symbol = typeof Symbol === 'function'; -/* eslint-disable no-var */ -declare var global: any; -export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global; -export const resolved_promise = Promise.resolve(); -export let now = is_browser ? window.performance.now.bind(window.performance) : Date.now.bind(Date); -export let raf = is_browser ? requestAnimationFrame : noop; +import { noop } from './utils'; + +export const is_client = typeof window !== 'undefined'; + +export let now: () => number = is_client + ? () => window.performance.now() + : () => Date.now(); + +export let raf = is_client ? cb => requestAnimationFrame(cb) : noop; export let framerate = 1000 / 60; /*#__PURE__*/ raf((t1) => { raf((d) => { @@ -27,6 +16,11 @@ export let framerate = 1000 / 60; }); }); -/* tests only */ -export const set_now = (v) => void (now = v); -export const set_raf = (fn) => void (raf = fn); +// used internally for testing +export function set_now(fn) { + now = fn; +} + +export function set_raf(fn) { + raf = fn; +} \ No newline at end of file diff --git a/src/runtime/internal/globals.ts b/src/runtime/internal/globals.ts new file mode 100644 index 000000000000..ab75e38db6ad --- /dev/null +++ b/src/runtime/internal/globals.ts @@ -0,0 +1,7 @@ +declare const global: any; + +export const globals = (typeof window !== 'undefined' + ? window + : typeof globalThis !== 'undefined' + ? globalThis + : global) as unknown as typeof globalThis; \ No newline at end of file diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index b4c1c895af89..c63bdd4000b8 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -1,4 +1,5 @@ -import { now, raf, framerate, noop } from './environment'; +import { now, raf, framerate } from './environment'; +import { noop } from './utils'; type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 47c6edd08d45..99b6dd940fcf 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -1,7 +1,9 @@ import { set_current_component } from './lifecycle'; -import { resolved_promise, now } from './environment'; +import { now } from './environment'; import { T$$ } from './Component'; +const resolved_promise = Promise.resolve(); + let update_scheduled = false; let is_flushing = false; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 4adcfd691558..088659858ea1 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -1,11 +1,11 @@ import { CssTransitionConfig } from '../transition'; import { Fragment } from './Component'; import { custom_event } from './dom'; -import { now, noop } from './environment'; +import { now } from './environment'; import { setFrameTimeout, setTweenTimeout } from './loop'; import { add_measure_callback } from './scheduler'; import { animate_css } from './style_manager'; -import { methodify } from './utils'; +import { methodify, noop } from './utils'; type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig; export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void; diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 5e06aa16bf25..9f8addb65e5f 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -1,5 +1,4 @@ -import { noop } from "./environment"; - +export function noop() {} export const identity = x => x; export function assign(tar: T, src: S): T & S { From 398aa59eb5e79b4cd3db207c3c357db2d7fb578f Mon Sep 17 00:00:00 2001 From: pushkine Date: Mon, 1 Jun 2020 21:08:14 +0200 Subject: [PATCH 06/12] fix --- src/runtime/internal/environment.ts | 2 +- src/runtime/internal/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index a4de8f491278..b424c769abc9 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -23,4 +23,4 @@ export function set_now(fn) { export function set_raf(fn) { raf = fn; -} \ No newline at end of file +} diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index daeb9b1f0a6c..e1dd2a1fcf36 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -2,6 +2,7 @@ export * from './animations'; export * from './await_block'; export * from './dom'; export * from './environment'; +export * from './globals'; export * from './keyed_each'; export * from './lifecycle'; export * from './loop'; From 5d0430e79577ba559d52af370cd45d14afe29d32 Mon Sep 17 00:00:00 2001 From: pushkine Date: Mon, 1 Jun 2020 22:56:36 +0200 Subject: [PATCH 07/12] fix --- src/runtime/internal/animations.ts | 7 ++++--- src/runtime/internal/loop.ts | 2 +- src/runtime/internal/transitions.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/runtime/internal/animations.ts b/src/runtime/internal/animations.ts index 4d65480c7c3e..5f18b073a5fb 100644 --- a/src/runtime/internal/animations.ts +++ b/src/runtime/internal/animations.ts @@ -2,10 +2,11 @@ import { run_transition } from './transitions'; import { methodify, noop } from './utils'; import { CssTransitionConfig } from 'svelte/transition'; -type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => CssTransitionConfig; +type Rect = DOMRect | ClientRect; +type AnimationFn = (node: Element, { from, to }: { from: Rect; to: Rect }, params: any) => CssTransitionConfig; export const run_animation = /*#__PURE__*/ methodify( - function run_animation(this: HTMLElement, from: DOMRect, fn: AnimationFn, params = {}) { + function run_animation(this: HTMLElement, from: Rect, fn: AnimationFn, params = {}) { if (!from) return noop; return run_transition( this, @@ -22,7 +23,7 @@ export const run_animation = /*#__PURE__*/ methodify( ); export const fix_position = /*#__PURE__*/ methodify( - function fix_position(this: HTMLElement, { left, top }: DOMRect) { + function fix_position(this: HTMLElement, { left, top }: Rect) { const { position, width, height, transform } = getComputedStyle(this); if (position === 'absolute' || position === 'fixed') return noop; const { position: og_position, width: og_width, height: og_height } = this.style; diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index c63bdd4000b8..993f565f6398 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -84,7 +84,7 @@ export const setTweenTimeout = ( is_outro = false ): TaskCanceller => { let running = true; - let t = 1 - (end_time - now) / duration || 0; + let t = 1 - (end_time - now()) / duration || 0; if (!is_outro && t <= 1.0) run(t >= 0.0 ? t : 0); unsafe_loop((now) => { if (!running) return false; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 088659858ea1..0776a4e5d65c 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -61,7 +61,7 @@ const swap = (fn, rx) => ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t); -const mirrored = (fn, rx, easing) => { +const mirrored = (fn, rx, easing, _start, _end) => { const run = swap(fn, rx); return easing ? rx & tx.intro @@ -76,7 +76,7 @@ const reversed = (fn, rx, easing, start = 0, end = 1) => { ? (t) => run(start + difference * easing(t)) : (t) => run(start + difference * t); }; -export const enum tx { +const enum tx { intro = 1, outro = 2, reverse = 3, From 0353d52d919041b20d2adec23803cd187ef52174 Mon Sep 17 00:00:00 2001 From: pushkine Date: Sun, 7 Jun 2020 11:58:45 +0200 Subject: [PATCH 08/12] delayed --- .../render_dom/wrappers/Element/index.ts | 19 +- src/runtime/animate/index.ts | 3 +- src/runtime/internal/animations.ts | 42 -- src/runtime/internal/environment.ts | 14 +- src/runtime/internal/index.ts | 2 +- src/runtime/internal/loop.ts | 92 ++--- src/runtime/internal/scheduler.ts | 11 +- src/runtime/internal/style_manager.ts | 28 +- src/runtime/internal/transitions.ts | 377 ++++++++++++------ src/runtime/internal/utils.ts | 4 +- src/runtime/transition/index.ts | 32 +- 11 files changed, 359 insertions(+), 265 deletions(-) delete mode 100644 src/runtime/internal/animations.ts diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 28ad3791c28d..131d17cf33a6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -740,15 +740,16 @@ export default class ElementWrapper extends Wrapper { } add_bidi_transition(block: Block, intro: Transition) { - const name = block.get_unique_name(`${this.var.name}_transition`); + const transition = block.get_unique_name(`${this.var.name}_transition`); const snippet = intro.expression ? intro.expression.manipulate(block) : null; + const fn = this.renderer.reference(intro.name); - block.add_variable(name, x`@noop`); + block.add_variable(transition); + block.chunks.create.push(b`${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet})`) - const fn = this.renderer.reference(intro.name); - let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 1, ${snippet});`; - let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 2, ${snippet});`; + let intro_block = b`${transition}.i();`; + let outro_block = b`${transition}.o();`; if (intro.is_local) { intro_block = b`if (#local) { ${intro_block} }`; @@ -757,7 +758,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.intro.push(intro_block); block.chunks.outro.push(outro_block); - block.chunks.destroy.push(b`if (detaching) ${name}();`); + block.chunks.destroy.push(b`if (detaching) ${transition}.d();`); } add_intro(block: Block, intro: Transition, outro: Transition) { if (outro) { @@ -779,7 +780,7 @@ export default class ElementWrapper extends Wrapper { const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`); block.add_variable(intro_var, outro ? x`@noop`: null); - let start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`; + let start_intro = b`${intro_var} = @run_in(${node}, ${transitionFn}, ${params}, this);`; if (!outro) start_intro = b`if (!${intro_var}) { ${start_intro} }`; if (intro.is_local) start_intro = b`if (#local) { ${start_intro} }`; block.chunks.intro.push(start_intro); @@ -797,7 +798,7 @@ export default class ElementWrapper extends Wrapper { const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`); block.add_variable(outro_var, x`@noop`); - let start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`; + let start_outro = b`${outro_var} = @run_out(${node}, ${transitionFn}, ${params}, this);`; if (outro.is_local) start_outro = b`if (#local) { ${start_outro} }`; block.chunks.outro.push(start_outro); @@ -824,7 +825,7 @@ export default class ElementWrapper extends Wrapper { `); block.chunks.animate.push(b` - if (${unfreeze_var}) return + if (${unfreeze_var} || !${rect_var}) return else { ${stop_animation_var}(); ${stop_animation_var} = @run_animation(${this.var}, ${rect_var}, ${name_var}, ${params_var}); diff --git a/src/runtime/animate/index.ts b/src/runtime/animate/index.ts index 58aeceff7a4d..b1d4e509a6a4 100644 --- a/src/runtime/animate/index.ts +++ b/src/runtime/animate/index.ts @@ -1,6 +1,5 @@ import { cubicOut } from "svelte/easing"; -import { run_duration } from "svelte/internal"; -import { CssTransitionConfig, TimeableConfig } from "svelte/transition"; +import { run_duration, TimeableConfig, CssTransitionConfig } from "svelte/internal"; export function flip( node: Element, animation: { from: DOMRect; to: DOMRect }, diff --git a/src/runtime/internal/animations.ts b/src/runtime/internal/animations.ts deleted file mode 100644 index 5f18b073a5fb..000000000000 --- a/src/runtime/internal/animations.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { run_transition } from './transitions'; -import { methodify, noop } from './utils'; -import { CssTransitionConfig } from 'svelte/transition'; - -type Rect = DOMRect | ClientRect; -type AnimationFn = (node: Element, { from, to }: { from: Rect; to: Rect }, params: any) => CssTransitionConfig; - -export const run_animation = /*#__PURE__*/ methodify( - function run_animation(this: HTMLElement, from: Rect, fn: AnimationFn, params = {}) { - if (!from) return noop; - return run_transition( - this, - (_, params) => { - const to = this.getBoundingClientRect(); - if (from.left !== to.left || from.right !== to.right || from.top !== to.top || from.bottom !== to.bottom) { - return fn(this, { from, to }, params); - } else return null; - }, - 9, - params - ); - } -); - -export const fix_position = /*#__PURE__*/ methodify( - function fix_position(this: HTMLElement, { left, top }: Rect) { - const { position, width, height, transform } = getComputedStyle(this); - if (position === 'absolute' || position === 'fixed') return noop; - const { position: og_position, width: og_width, height: og_height } = this.style; - this.style.position = 'absolute'; - this.style.width = width; - this.style.height = height; - const b = this.getBoundingClientRect(); - this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`; - return () => { - this.style.position = og_position; - this.style.width = og_width; - this.style.height = og_height; - this.style.transform = ''; // unsafe - }; - } -); diff --git a/src/runtime/internal/environment.ts b/src/runtime/internal/environment.ts index b424c769abc9..178f167a9c5f 100644 --- a/src/runtime/internal/environment.ts +++ b/src/runtime/internal/environment.ts @@ -1,20 +1,12 @@ -import { noop } from './utils'; +import { noop } from "./utils"; -export const is_client = typeof window !== 'undefined'; +export const is_client = typeof window !== "undefined"; export let now: () => number = is_client ? () => window.performance.now() : () => Date.now(); -export let raf = is_client ? cb => requestAnimationFrame(cb) : noop; -export let framerate = 1000 / 60; -/*#__PURE__*/ raf((t1) => { - raf((d) => { - const f24 = 1000 / 24; - const f144 = 1000 / 144; - framerate = (d = d - t1) > f144 ? f144 : d < f24 ? f24 : d; - }); -}); +export let raf = /*#__PURE__*/ is_client ? (cb: FrameRequestCallback) => requestAnimationFrame(cb) : noop; // used internally for testing export function set_now(fn) { diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index e1dd2a1fcf36..253b8fccc8dc 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -1,4 +1,3 @@ -export * from './animations'; export * from './await_block'; export * from './dom'; export * from './environment'; @@ -12,4 +11,5 @@ export * from './ssr'; export * from './transitions'; export * from './utils'; export * from './Component'; +export * from './style_manager' export * from './dev'; diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index 993f565f6398..bdecd318cc53 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -1,5 +1,31 @@ -import { now, raf, framerate } from './environment'; +import { now, raf } from './environment'; import { noop } from './utils'; + +export const frame = { + rate: 1000 / 60, + time: 0.0, + sync() { + return n ? this.time : (this.time = now()); + }, +}; + +function calc_framerate() { + raf((t1) => { + raf((t2) => { + const delta = t2 - t1; + raf((t3) => { + if (Math.abs(t3 - t2 - delta) > 1) { + calc_framerate(); + } else { + const f24 = 1000 / 24; + const f144 = 1000 / 144; + frame.rate = delta > f144 ? f144 : delta < f24 ? f24 : delta; + } + }); + }); + }); +} +calc_framerate(); type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; @@ -13,7 +39,7 @@ let next_frame: TaskCallback[] = []; const run = (t: number) => { [running_frame, next_frame] = [next_frame, running_frame]; - for (t = now(), i = n = 0, j = running_frame.length; i < j; i++) { + for (t = (frame.time = now()), i = n = 0, j = running_frame.length; i < j; i++) { if ((v = running_frame[i])(t)) { next_frame[n++] = v; } @@ -33,22 +59,26 @@ let running_timed = false; const run_timed = (now: number) => { let last_index = timed_tasks.length - 1; - while (~last_index && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now); + while (-1 !== last_index && now >= timed_tasks[last_index].timestamp) { + timed_tasks[last_index--].callback(now); + } if (pending_inserts) { - for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++) - if (now >= (this_task = pending_insert_timed[i]).timestamp) this_task.callback(now); - else { - for (j = last_index; ~j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) + for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++) { + if (now >= (this_task = pending_insert_timed[i]).timestamp) { + this_task.callback(now); + } else { + for (j = last_index; -1 !== j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) { timed_tasks[j + 1] = that_task; + } timed_tasks[j + 1] = this_task; last_index++; } + } pending_insert_timed.length = 0; pending_inserts = false; } return (running_timed = !!(timed_tasks.length = last_index + 1)); }; - const unsafe_loop = (fn) => { if (0 === n) raf(run); next_frame[n++] = fn; @@ -56,8 +86,7 @@ const unsafe_loop = (fn) => { export const loop = (fn) => { let running = true; - if (0 === n) raf(run); - next_frame[n++] = (t) => !running || fn(t); + unsafe_loop((t) => running && fn(t)); return () => void (running = false); }; @@ -70,52 +99,23 @@ export const setFrameTimeout = (callback: (t: number) => void, timestamp: number running_timed = true; timed_tasks.push(task); } - return () => void (task.callback = noop); + return () => { + task.callback = noop; + }; }; - -/** - * Calls function every frame with linear tween from 0 to 1 - */ export const setTweenTimeout = ( stop: (now: number) => void, end_time: number, run: (now: number) => void, - duration = end_time - now(), - is_outro = false + duration = end_time - frame.sync() ): TaskCanceller => { - let running = true; - let t = 1 - (end_time - now()) / duration || 0; - if (!is_outro && t <= 1.0) run(t >= 0.0 ? t : 0); - unsafe_loop((now) => { - if (!running) return false; + let t = 0.0; + return loop((now) => { t = 1 - (end_time - now) / duration; if (t >= 1.0) return run(1), stop(now), false; if (t >= 0.0) run(t); - return running; + return true; }); - return (run_last = false) => { - if (run_last) run(1); - running = false; - }; -}; -/** - * Calls function every frame with time elapsed in seconds - */ -export const onEachFrame = ( - callback: (seconds_elapsed: number) => boolean, - on_stop?, - max_skipped_frames = 4 -): TaskCanceller => { - max_skipped_frames *= framerate; - let lastTime = now(); - let running = true; - const cancel = (t) => (on_stop && on_stop(t), false); - unsafe_loop((t: number) => { - if (!running) return cancel(t); - if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames; - return callback((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t); - }); - return () => void (running = false); }; /** tests only */ diff --git a/src/runtime/internal/scheduler.ts b/src/runtime/internal/scheduler.ts index 99b6dd940fcf..b7433cb31199 100644 --- a/src/runtime/internal/scheduler.ts +++ b/src/runtime/internal/scheduler.ts @@ -1,6 +1,6 @@ import { set_current_component } from './lifecycle'; -import { now } from './environment'; import { T$$ } from './Component'; +import { frame } from './loop'; const resolved_promise = Promise.resolve(); @@ -17,7 +17,9 @@ const flush_callbacks = []; // todo : remove add_flush_callback export const add_flush_callback = /*#__PURE__*/ Array.prototype.push.bind(flush_callbacks); -export const add_measure_callback = /*#__PURE__*/ Array.prototype.push.bind(measure_callbacks); +type MeasureCallback = () => FlushCallback +type FlushCallback = (current_frame_time: number) => void +export const add_measure_callback: (...args: MeasureCallback[]) => number = /*#__PURE__*/ Array.prototype.push.bind(measure_callbacks); const seen_render_callbacks = new Set(); export const add_render_callback = (fn) => { @@ -48,9 +50,10 @@ export const flush = () => { if (is_flushing) return; else is_flushing = true; + frame.sync(); + let i = 0; let j = 0; - let t = 0; let $$: T$$; let dirty; let before_update; @@ -103,7 +106,7 @@ export const flush = () => { // apply styles // todo : remove every non style callback from flush_callbacks - for (t = now(); i < j; i++) flush_callbacks[i](t); + for (const t = frame.time; i < j; i++) flush_callbacks[i](t); flush_callbacks.length = i = j = 0; is_flushing = false; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 2efbae0ec904..8090417c0231 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,5 +1,5 @@ -import { framerate } from './environment'; -import { methodify } from './utils'; +import { frame } from './loop'; +import { methodify, noop } from './utils'; let documents_uid = 0; let running_animations = 0; @@ -9,7 +9,7 @@ const document_stylesheets = new Map(); const current_rules = new Set(); export const animate_css = /*#__PURE__*/ methodify( - function animate_css(this: HTMLElement, css: (t: number) => string, duration: number, delay = 0) { + function (this: HTMLElement, css: (t: number) => string, duration: number, delay = 0) { if (!document_uid.has(this.ownerDocument)) { document_uid.set(this.ownerDocument, documents_uid++); document_stylesheets.set( @@ -18,7 +18,7 @@ export const animate_css = /*#__PURE__*/ methodify( ); } let rule = '{\n'; - for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + for (let t = 0, step = frame.rate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; rule += `100% {${css(1)}}\n}`; // darkskyapp/string-hash @@ -34,6 +34,7 @@ export const animate_css = /*#__PURE__*/ methodify( } const previous = this.style.animation; + if (previous) {console.error("stacked animations"); return noop} this.style.animation = `${ previous ? `${previous}, ` : '' }${duration}ms linear ${delay}ms 1 normal both running ${name}`; @@ -58,4 +59,23 @@ export const animate_css = /*#__PURE__*/ methodify( } }; } +); +export const fix_position = /*#__PURE__*/ methodify( + function (this: HTMLElement, { left, top }: DOMRect | ClientRect) { + const { position, width, height, transform } = getComputedStyle(this); + if (position === 'absolute' || position === 'fixed') return noop; + const { position: og_position, width: og_width, height: og_height, transform: og_transform } = this.style; + this.style.position = 'absolute'; + this.style.width = width; + this.style.height = height; + const b = this.getBoundingClientRect(); + this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`; + return () => { + // unsafe + this.style.position = og_position; + this.style.width = og_width; + this.style.height = og_height; + this.style.transform = og_transform; + }; + } ); \ No newline at end of file diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 0776a4e5d65c..3eb64bc6ee4b 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -1,44 +1,70 @@ -import { CssTransitionConfig } from '../transition'; -import { Fragment } from './Component'; -import { custom_event } from './dom'; -import { now } from './environment'; -import { setFrameTimeout, setTweenTimeout } from './loop'; -import { add_measure_callback } from './scheduler'; -import { animate_css } from './style_manager'; -import { methodify, noop } from './utils'; - -type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig; -export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void; +import { Fragment } from "./Component"; +import { custom_event } from "./dom"; +import { setFrameTimeout, setTweenTimeout, frame } from "./loop"; +import { add_measure_callback, tick } from "./scheduler"; +import { animate_css } from "./style_manager"; +import { methodify, noop } from "./utils"; + +export interface CssAnimationConfig { + delay?: number; + duration?: number; + easing?: (t: number) => number; +} +export interface CssTransitionConfig extends CssAnimationConfig { + css?: (t: number, u?: number) => string; + tick?: (t: number, u?: number) => void; + strategy?: EasingStrategy; +} +export type TimeableConfig = Omit & { duration?: number | ((len: number) => number) }; +const enum EasingStrategy { + reversed = "reversed", + balanced = "balanced", + mirrored = "mirrored", +} +const enum TransitionEvent { + introstart = "introstart", + introend = "introend", + outrostart = "outrostart", + outroend = "outroend" +} export const transition_in = (block: Fragment, local?) => { + // todo : is `!block` necessary ? if (!block || !block.i) return; outroing.delete(block); block.i(local); }; export const transition_out = (block: Fragment, local?) => { + // todo : are `!block` and `outroing.has` checks necessary ? if (!block || !block.o || outroing.has(block)) return; outroing.add(block); block.o(local); }; type TransitionGroup = { /* parent group */ p: TransitionGroup; - /* callbacks */ c: Array<((cancelled: boolean) => void)>; + /* callbacks */ c: Array<(cancelled: boolean) => void>; /* running outros */ r: number; - /* stop callbacks */ s: Array<((t: number) => void)>; + /* stop callbacks */ s: Array<(t: number) => void>; /* outro timeout */ t: number; }; let transition_group: TransitionGroup; const outroing = new Set(); export const group_transition_out = (fn) => { const c = []; - const current_group = (transition_group = { p: transition_group, c, r: 0, s: [], t: 0 }); + const current_group = (transition_group = { + p: transition_group, + c, + r: 0, + s: [], + t: 0, + }); fn((block, callback, detach = true) => { if (!block || !block.o || outroing.has(block)) return; outroing.add(block); c.push((cancelled = false) => { if (cancelled) { - // block was destroyed before outro ended + // block destroyed before outro ended outroing.delete(block); } else if (outroing.has(block)) { outroing.delete(block); @@ -51,149 +77,256 @@ export const group_transition_out = (fn) => { if (!current_group.r) for (let i = 0; i < c.length; i++) c[i](); transition_group = transition_group.p; }; +type Rect = DOMRect | ClientRect; +type MeasureCallback = () => CssTransitionConfig +type CustomTransitionFunction = (node: HTMLElement, params: any) => MeasureCallback | CssTransitionConfig; +type AnimationFn = (node: Element, { from, to }: { from: Rect; to: Rect }, params: any) => CssTransitionConfig; +type StopResetReverseFn = (t?: number | 1 | -1) => StopResetReverseFn | void; -const swap = (fn, rx) => +const swap = (fn, is_intro) => fn.length === 1 - ? rx & tx.intro + ? is_intro ? fn : (t) => fn(1 - t) - : rx & tx.intro + : is_intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t); -const mirrored = (fn, rx, easing, _start, _end) => { - const run = swap(fn, rx); +const mirrored = (fn, is_intro, easing, _start?, _end?) => { + const run = swap(fn, is_intro); return easing - ? rx & tx.intro + ? is_intro ? (t) => run(easing(t)) : (t) => run(1 - easing(1 - t)) : run; }; -const reversed = (fn, rx, easing, start = 0, end = 1) => { - const run = swap(fn, rx); + +const reversed = (fn, is_intro, easing, start = 0, end = 1) => { + const run = swap(fn, is_intro); const difference = end - start; return easing ? (t) => run(start + difference * easing(t)) : (t) => run(start + difference * t); }; -const enum tx { - intro = 1, - outro = 2, - reverse = 3, - bidirectional = 4, - animation = 8, -} -export const run_transition = /*#__PURE__*/ methodify(function transition( - this: HTMLElement, - fn: TransitionFn, - rx: tx, - params = {}, - /* internal to this file */ - elapsed_duration = 0, - delay_left = -1, - elapsed_ratio = 0 -) { + +export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement, from : Rect, fn: AnimationFn, params: CssTransitionConfig = {}) { + let running = true; + let cancel_css; + let cancel_raf; + add_measure_callback(() => { + const to = this.getBoundingClientRect(); + if (from.top === to.top && from.left === to.left && from.right === to.right && from.bottom === to.bottom) return noop; + const config = fn(this, { from, to }, params); + return (current_frame_time) => { + if (false === running) return; + const { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = config; + const end_time = current_frame_time + delay + duration; + const runner = (fn) => reversed(fn, true, easing); + if (css) cancel_css = animate_css(this, runner(css), duration, delay); + cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); + }; + }); + const stop = () => { + if (false === running) return; + else running = false; + if (cancel_css) cancel_css(); + if (cancel_raf) cancel_raf(); + } + return stop +}); +export const run_in = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: CustomTransitionFunction, params: CssTransitionConfig = {}) { let config; - let running = true; - let cancel_css; - let cancel_raf; - - let start_time = 0; - let end_time = 0; - + let cancel_raf; + let end_time; + add_measure_callback(() => { + config = fn(this, params); + return (current_frame_time) => { + let { delay = 0, duration = 300, easing, tick, css, strategy = EasingStrategy.balanced }: CssTransitionConfig = + "function" === typeof config ? (config = config()) : config; + const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; + const runner = (fn) => solver(fn, true, easing); + end_time = current_frame_time + delay + duration; + this.dispatchEvent(custom_event(TransitionEvent.introstart)); + if (css) cancel_css = animate_css(this, runner(css), duration, delay); + cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration ) : setFrameTimeout(stop, end_time); + }; + }); + const stop = (t?: number) => { + if (false === running) return; + else running = false; + if (cancel_css) cancel_css(); + if (cancel_raf) cancel_raf(); + if (t && t >= end_time) this.dispatchEvent(custom_event(TransitionEvent.introend)); + } + return stop +}); +export const run_out = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: CustomTransitionFunction, params: CssTransitionConfig = {}) { + let config; + let running = true; + let cancel_css; + let cancel_raf; + let end_time; const current_group = transition_group; - if (rx & tx.outro) current_group.r++; - + current_group.r++; add_measure_callback(() => { - if (null === (config = fn(this, params))) return noop; + config = fn(this, params); return (current_frame_time) => { - if (false === running) return; + let { delay = 0, duration = 300, easing, tick, css, strategy = EasingStrategy.balanced }: CssTransitionConfig = + "function" === typeof config ? (config = config()) : config; + const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; + const runner = (fn) => solver(fn, false, easing); + end_time = current_frame_time + delay + duration; + current_group.t = Math.max(end_time, current_group.t); + if (current_group.s.push(stop) === current_group.r) { + setFrameTimeout((t) => { + for (let i = 0; i < current_group.s.length; i++) { + current_group.s[i](t); + } + }, current_group.t); + } + this.dispatchEvent(custom_event(TransitionEvent.outrostart)); + if (css) cancel_css = animate_css(this, runner(css), duration, delay); + if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration); + }; + }); + const stop = (t?: number) => { + if (1 === t && "tick" in config) config.tick(1, 0); + if (false === running) return; + else running = false; + if (cancel_css) cancel_css(); + if (cancel_raf) cancel_raf(); + if (t && t >= end_time) { + if ("tick" in config) config.tick(0, 1); + this.dispatchEvent(custom_event(TransitionEvent.outroend)); + } + if(!--current_group.r) for (let i = 0, { c } = current_group, r = t === void 0;i < c.length;i++) c[i](r); + } + return stop +}); +export const create_bidirectional_transition = /*#__PURE__*/ methodify(function(this: HTMLElement, fn: CustomTransitionFunction, params?: CssTransitionConfig) { + let transition_delay; + let pending = 0; + let prev; - let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: CssTransitionConfig = - 'function' === typeof config ? (config = config()) : config; + const u = (new_fn = fn, new_params = params) => { + let test_config; + if (typeof (test_config = (fn = new_fn)(this,(params = new_params))) === "function") test_config = test_config(); + transition_delay = test_config.delay || 0.0; + } + u(); - const solver = 'reverse' === strategy ? reversed : mirrored; - const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1); + const run_transition = (is_intro:boolean, cancel_previous?) => { + const delayed_start = transition_delay && cancel_previous && pending; - if (rx & tx.bidirectional) { - if (-1 !== delay_left) delay = delay_left; - if (solver === reversed) duration -= elapsed_duration; - else if (solver === mirrored) delay -= elapsed_duration; - } + let config; + + let running = true; + let cancelled = false; + + let cancel_css; + let cancel_raf; - end_time = (start_time = current_frame_time + delay) + duration; + let start_time = 0.0; + let end_time = 0.0; + let ratio_left = 0.0; - if (0 === (rx & tx.animation)) { - this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`)); + const current_group = transition_group; + if (!is_intro) current_group.r++; + + const run = (flush_frame_time) => { + pending++; + const [prev_duration_left, prev_ratio_left] = ((cancel_previous && cancel_previous(flush_frame_time)) || [0.0, 0.0] ); + ratio_left = prev_ratio_left; + return () => { + config = fn(this, params); + return (current_frame_time) => { + let { tick, css, duration = 300.0, delay = 0.0, easing, strategy = EasingStrategy.balanced }: CssTransitionConfig = + "function" === typeof config ? (config = config()) : config; + const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; + const runner = (fn) => solver(fn, is_intro, easing, ratio_left, 1); + if (delayed_start) delay = 0; + if (solver === reversed) duration -= prev_duration_left; + else if (solver === mirrored) delay -= prev_duration_left; + start_time = current_frame_time + delay; + end_time = start_time + duration; + if (cancelled) return; + if (!is_intro) { + current_group.t = Math.max(end_time, current_group.t); + if (current_group.s.push(stop) === current_group.r) { + setFrameTimeout((t) => { + for (let i = 0; i < current_group.s.length; i++) { + current_group.s[i](t); + } + }, current_group.t); + } + } + this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introstart : TransitionEvent.outrostart)); + if (css) cancel_css = animate_css(this, runner(css), duration, delay); + if (tick) cancel_raf = setTweenTimeout(is_intro ? stop : noop, end_time, runner(tick), duration); + else if (is_intro) cancel_raf = setFrameTimeout(stop, end_time); + }; + }; + } + + const cancel = (t) => { + if (!cancelled) { + pending--; + cancelled = true; + if (cancel_css) cancel_css(); + if (cancel_raf) cancel_raf(); + if (1 === t && cancel_previous) cancel_previous(); } + if (!config) return; + const duration_left = end_time - t; + const next_ratio_left = 1 - duration_left / (end_time - start_time); + return duration_left > 0 && next_ratio_left > 0 && [duration_left, (1 - ratio_left) * (1 - (config.easing || ((v) => v))(next_ratio_left))]; + }; - if (css) cancel_css = animate_css(this, runner(css), duration, delay); - - if (rx & tx.outro) { - if (current_group.s.push(stop) === current_group.r) { - setFrameTimeout((t) => { - for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t); - }, Math.max(end_time, current_group.t)); - } else { - current_group.t = Math.max(end_time, current_group.t); + const stop: StopResetReverseFn = (t?: number | -1 | 1) => { + if (running) { + running = false; + if (config) { + if (t >= end_time) { + if (!is_intro && "tick" in config) config.tick(0, 1); + if (pending === 1) cancel(t); + this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introend : TransitionEvent.outroend)); + } + if (!is_intro) { + if (!--current_group.r) { + for (let i = 0, { c } = current_group, r = t === 1; i < c.length; i++) c[i](r); + } + } } - if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration, true); - } else { - cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); } + if (t === -1) return run_transition(!is_intro, cancel); }; - }); - - const stop: StopResetReverseFn = (t?: number | 1 | -1) => { - if (t === 1 && rx & tx.outro && 0 === (rx & tx.bidirectional) && 'tick' in config) config.tick(1, 0); - if (false === running) return; - else running = false; + if (delayed_start) { + setFrameTimeout((t) => { + add_measure_callback(run(t)); + tick(); + }, frame.time + transition_delay); + } else { + add_measure_callback(run(frame.time)); + } - if (cancel_css) cancel_css(); - if (cancel_raf) cancel_raf(rx & tx.outro && t >= end_time); - - if (rx & tx.animation) return; - - if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`)); - - if (rx & tx.outro && !--current_group.r) - for (let i = 0; i < current_group.c.length; i++) current_group.c[i](t === void 0); - - if (0 === (rx & tx.bidirectional)) return; - - if (-1 === t) - return ( - (t = now()) < end_time && - run_transition( - this, - () => config, - rx ^ tx.reverse, - params, - end_time - t, - start_time > t ? start_time - t : 0, - (1 - elapsed_ratio) * (1 - (config.easing || ((v) => v))(1 - (end_time - t) / (end_time - start_time))) - ) - ); - else running_bidi.delete(this); + return stop }; - return stop; -}); - -const running_bidi: Map = new Map(); -export const run_bidirectional_transition = /*#__PURE__*/ methodify( - function bidirectional(this: HTMLElement, fn: TransitionFn, rx: tx.intro | tx.outro, params: any ) { - let cancel; - running_bidi.set( - this, - (cancel = - (running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params)) - ); - return cancel; + return { + u, + o() { + prev = prev ? prev(-1) : run_transition(false); + }, + i() { + prev = prev ? prev(-1) : run_transition(true); + }, + d() { + prev = prev(1); + } } -); +}); export const run_duration = (duration, value1, value2?): number => - typeof duration === 'function' ? duration(value1, value2) : duration; + typeof duration === "function" ? duration(value1, value2) : duration; diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 9f8addb65e5f..65d6036b61c1 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -146,8 +146,8 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, export function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } - -export const methodify = /*#__PURE__*/ (function() { +type Methodify any> = (thisType : ThisParameterType, ...parameters :Parameters) => T extends (...args: Parameters) => infer R ? R : any +export const methodify: any>(fn: T) => Methodify = /*#__PURE__*/ (function () { const call = Function.prototype.call; return call.bind.bind(call); })(); \ No newline at end of file diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 7c262b194bc5..88daba3c881b 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -1,25 +1,12 @@ import { cubicOut, cubicInOut } from 'svelte/easing'; -import { run_duration } from 'svelte/internal'; +import { run_duration, CssAnimationConfig, CssTransitionConfig, TimeableConfig } from 'svelte/internal'; -interface CssAnimationConfig { - delay?: number; - duration?: number; - easing?: (t: number) => number; - strategy?: 'reverse' | 'mirror'; -} - -export interface CssTransitionConfig extends CssAnimationConfig { - css?: (t: number, u?: number) => string; - tick?: (t: number, u?: number) => void; -} - -type FlyParams = FadingConfig & { x: number; y: number }; +type FlyParams = FadingConfig & { x: number; y: number; rotate: number }; type BlurParams = FadingConfig & { amount: number }; type ScaleParams = FadingConfig & { start: number }; type DrawParams = CssAnimationConfig & { speed: number }; type FadingConfig = CssAnimationConfig & { opacity: number }; type MarkedCrossFadeConfig = TimeableConfig & { key: any }; -export type TimeableConfig = Omit & { duration?: number | ((len: number) => number) }; type CrossFadeConfig = TimeableConfig & { fallback(node: Element, params: TimeableConfig, intro: boolean): CssTransitionConfig }; type ElementMap = Map; @@ -41,7 +28,7 @@ export function fade(node: Element, { delay = 0, duration = 400, easing }: CssAn return { delay, duration, easing, css: (t) => `opacity: ${t * o};` }; } -export function fly(node: Element, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 }: FlyParams ): CssTransitionConfig { +export function fly(node: Element, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0, rotate = 0 }: FlyParams ): CssTransitionConfig { const style = getComputedStyle(node); const target_opacity = +style.opacity; const prev = style.transform === 'none' ? '' : style.transform; @@ -50,7 +37,7 @@ export function fly(node: Element, { delay = 0, duration = 400, easing = cubicOu delay, duration, easing, - css: (_t, u) => `transform: ${prev} translate(${u * x}px, ${u * y}px); opacity: ${target_opacity - od * u};`, + css: (_t, u) => `transform: ${prev} translate(${u * x}px, ${u * y}px) rotate(${u * rotate}deg); opacity: ${target_opacity - od * u};`, }; } @@ -129,22 +116,23 @@ export function crossfade({ delay: default_delay = 0, duration: default_duration } as CssTransitionConfig; }; - const transition = (a: ElementMap, b: ElementMap, is_intro: boolean) => ( node: Element, params: MarkedCrossFadeConfig ) => { + const transition = (a: ElementMap, b: ElementMap, is_intro: boolean) => ( to_node: Element, params: MarkedCrossFadeConfig ) => { const { key } = params; - a.set(key, node); + a.set(key, to_node); if (b.has(key)) { const from_node = b.get(key); b.delete(key); - return crossfade(from_node, node, params); + return crossfade(from_node, to_node, params); } else { return () => { if (b.has(key)) { const from_node = b.get(key); b.delete(key); - return crossfade(from_node, node, params); + return crossfade(from_node, to_node, params); } else { + debugger a.delete(key); - return fallback && fallback(node, params, is_intro); + return fallback && fallback(to_node, params, is_intro); } }; } From b59759cd4a7b2d85c90bf8e1a174e7f4ceb9e185 Mon Sep 17 00:00:00 2001 From: pushkine Date: Tue, 9 Jun 2020 21:41:09 +0200 Subject: [PATCH 09/12] fix --- src/compiler/compile/render_dom/Block.ts | 15 ++++---- .../render_dom/wrappers/Element/index.ts | 35 +++++++++---------- src/runtime/internal/loop.ts | 34 +++++++++--------- src/runtime/internal/style_manager.ts | 1 - src/runtime/internal/transitions.ts | 3 +- .../each-block-keyed-animated/expected.js | 2 +- test/js/samples/transition-local/expected.js | 17 +++++---- .../transition-repeated-outro/expected.js | 4 +-- test/runtime/index.js | 2 +- .../transition-css-in-out-in/_config.js | 11 ++++-- .../samples/transition-js-args/_config.js | 3 -- .../transition-js-await-block/_config.js | 3 +- .../samples/transition-js-context/_config.js | 3 +- .../transition-js-deferred-b/_config.js | 3 +- .../samples/transition-js-deferred/_config.js | 1 - .../transition-js-deferred/main.svelte | 8 ++--- .../transition-js-delay-in-out/_config.js | 1 - .../samples/transition-js-delay/_config.js | 9 +++-- .../_config.js | 3 +- .../_config.js | 9 ++--- .../transition-js-each-block-intro/_config.js | 4 --- .../_config.js | 3 -- .../_config.js | 4 --- .../transition-js-if-block-bidi/_config.js | 5 ++- .../_config.js | 1 - .../_config.js | 5 ++- .../_config.js | 2 -- .../samples/transition-js-initial/_config.js | 1 - .../_config.js | 1 - .../transition-js-local-and-global/_config.js | 1 - .../_config.js | 1 - .../_config.js | 1 - .../_config.js | 1 - .../_config.js | 1 - .../transition-js-nested-await/_config.js | 1 - .../transition-js-nested-component/_config.js | 1 - .../_config.js | 1 - .../transition-js-nested-each/_config.js | 1 - .../transition-js-nested-if/_config.js | 1 - .../transition-js-nested-intro/_config.js | 1 - .../_config.js | 1 - .../transition-js-parameterised/_config.js | 1 - .../samples/transition-js-slot/_config.js | 1 - 43 files changed, 87 insertions(+), 120 deletions(-) diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index 91e326d98cde..f864dc22465c 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -234,17 +234,14 @@ export default class Block { get_contents(key?: any) { const { dev } = this.renderer.options; - if (this.has_outros) { + if (this.has_outros || this.has_intros) { this.add_variable({ type: 'Identifier', name: '#current' }); - if (this.chunks.intro.length > 0) { - this.chunks.intro.push(b`#current = true;`); - this.chunks.mount.push(b`#current = true;`); - } + this.chunks.intro.push(b`#current = true;`); + this.chunks.mount.push(b`#current = true;`); + - if (this.chunks.outro.length > 0) { - this.chunks.outro.push(b`#current = false;`); - } + this.chunks.outro.push(b`#current = false;`); } if (this.autofocus) { @@ -345,7 +342,7 @@ export default class Block { properties.intro = noop; } else { properties.intro = x`function #intro(#local) { - ${this.has_outros && b`if (#current) return;`} + ${b`if (#current) return;`} ${this.chunks.intro} }`; } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 131d17cf33a6..77555299ec45 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -744,10 +744,14 @@ export default class ElementWrapper extends Wrapper { const snippet = intro.expression ? intro.expression.manipulate(block) : null; const fn = this.renderer.reference(intro.name); + block.add_variable(transition); - block.chunks.create.push(b`${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet})`) - - + block.chunks.mount.push(b`if (!${transition}) ${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet});`); + block.chunks.hydrate.push(b`if (!${transition}) ${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet});`); + if (intro.expression) { + const dirty = block.renderer.dirty([intro.name,...Array.from(intro.expression.dependencies)]); + block.chunks.update.push(b`if (${dirty}) ${transition}.u(${fn}, ${snippet});`); + } let intro_block = b`${transition}.i();`; let outro_block = b`${transition}.o();`; @@ -758,7 +762,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.intro.push(intro_block); block.chunks.outro.push(outro_block); - block.chunks.destroy.push(b`if (detaching) ${transition}.d();`); + block.chunks.destroy.push(b`if (detaching && ${transition}) ${transition}.d();`); } add_intro(block: Block, intro: Transition, outro: Transition) { if (outro) { @@ -766,47 +770,43 @@ export default class ElementWrapper extends Wrapper { block.chunks.intro.push(b`${outro_var}(1);`); } if (this.node.animation) { - const [unfreeze_var, rect_var, stop_animation_var, animationFn, params] = run_animation(this, block); + const [unfreeze_var] = run_animation(this, block); block.chunks.intro.push(b` if (${unfreeze_var}) { ${unfreeze_var}(); ${unfreeze_var} = void 0; - ${stop_animation_var} = @run_animation(${this.var}, ${rect_var}, ${animationFn}, ${params}); } `); } if (!intro) return; const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`); - block.add_variable(intro_var, outro ? x`@noop`: null); + block.add_variable(intro_var, x`@noop`); - let start_intro = b`${intro_var} = @run_in(${node}, ${transitionFn}, ${params}, this);`; - if (!outro) start_intro = b`if (!${intro_var}) { ${start_intro} }`; + let start_intro = b` + ${intro_var}(); + ${intro_var} = @run_in(${node}, ${transitionFn}, ${params}); + `; if (intro.is_local) start_intro = b`if (#local) { ${start_intro} }`; block.chunks.intro.push(start_intro); } // TODO // hide elements that have outro'd prior to their removal from the DOM // ( ...unless they belong to a still-outroing group ) - add_outro(block: Block, intro: Transition, outro: Transition) { - if (intro) { - const intro_var = block.alias(`${this.var.name}_intro`); - block.chunks.outro.push(b`${intro_var}();`); - } + add_outro(block: Block, _intro: Transition, outro: Transition) { if (!outro) return; const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`); block.add_variable(outro_var, x`@noop`); - let start_outro = b`${outro_var} = @run_out(${node}, ${transitionFn}, ${params}, this);`; + let start_outro = b`${outro_var} = @run_out(${node}, ${transitionFn}, ${params});`; if (outro.is_local) start_outro = b`if (#local) { ${start_outro} }`; block.chunks.outro.push(start_outro); block.chunks.destroy.push(b`if (detaching) ${outro_var}();`); } - add_animation(block: Block, intro: Transition) { - const intro_var = intro && block.alias(`${this.var.name}_intro`); + add_animation(block: Block) { const [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block); @@ -816,7 +816,6 @@ export default class ElementWrapper extends Wrapper { block.chunks.measure.push(b` ${rect_var} = ${this.var}.getBoundingClientRect(); - ${intro && b`${intro_var}();`} `); block.chunks.fix.push(b` diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index bdecd318cc53..f745b55ee39b 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -9,23 +9,23 @@ export const frame = { }, }; -function calc_framerate() { - raf((t1) => { - raf((t2) => { - const delta = t2 - t1; - raf((t3) => { - if (Math.abs(t3 - t2 - delta) > 1) { - calc_framerate(); - } else { - const f24 = 1000 / 24; - const f144 = 1000 / 144; - frame.rate = delta > f144 ? f144 : delta < f24 ? f24 : delta; - } - }); - }); - }); -} -calc_framerate(); +// function calc_framerate() { +// raf((t1) => { +// raf((t2) => { +// const delta = t2 - t1; +// raf((t3) => { +// if (Math.abs(t3 - t2 - delta) > 1) { +// calc_framerate(); +// } else { +// const f24 = 1000 / 24; +// const f144 = 1000 / 144; +// frame.rate = delta > f144 ? f144 : delta < f24 ? f24 : delta; +// } +// }); +// }); +// }); +// } +// calc_framerate(); type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 8090417c0231..a98ad9221c6a 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -34,7 +34,6 @@ export const animate_css = /*#__PURE__*/ methodify( } const previous = this.style.animation; - if (previous) {console.error("stacked animations"); return noop} this.style.animation = `${ previous ? `${previous}, ` : '' }${duration}ms linear ${delay}ms 1 normal both running ${name}`; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 3eb64bc6ee4b..554704457ad9 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -123,6 +123,7 @@ export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement const end_time = current_frame_time + delay + duration; const runner = (fn) => reversed(fn, true, easing); if (css) cancel_css = animate_css(this, runner(css), duration, delay); + if (tick) tick(0, 1); cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); }; }); @@ -324,7 +325,7 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( prev = prev ? prev(-1) : run_transition(true); }, d() { - prev = prev(1); + if (prev) prev(1); } } }); diff --git a/test/js/samples/each-block-keyed-animated/expected.js b/test/js/samples/each-block-keyed-animated/expected.js index 24dfd5465a34..f25b4cd57efc 100644 --- a/test/js/samples/each-block-keyed-animated/expected.js +++ b/test/js/samples/each-block-keyed-animated/expected.js @@ -54,7 +54,7 @@ function create_each_block(key_1, ctx) { unfreeze = fix_position(div, rect); }, a() { - if (unfreeze) return; else { + if (unfreeze || !rect) return; else { stop_animation(); stop_animation = run_animation(div, rect, foo); } diff --git a/test/js/samples/transition-local/expected.js b/test/js/samples/transition-local/expected.js index 30d31805ef73..5d4bde5ae252 100644 --- a/test/js/samples/transition-local/expected.js +++ b/test/js/samples/transition-local/expected.js @@ -7,7 +7,7 @@ import { init, insert, noop, - run_transition, + run_in, safe_not_equal, transition_in } from "svelte/internal"; @@ -52,7 +52,8 @@ function create_if_block(ctx) { // (9:1) {#if y} function create_if_block_1(ctx) { let div; - let div_intro; + let div_intro = noop; + let current; return { c() { @@ -61,16 +62,20 @@ function create_if_block_1(ctx) { }, m(target, anchor) { insert(target, div, anchor); + current = true; }, i(local) { + if (current) return; + if (local) { - if (!div_intro) { - div_intro = run_transition(div, foo, 1); - } + div_intro(); + div_intro = run_in(div, foo); } + + current = true; }, o(local) { - div_intro(); + current = false; }, d(detaching) { if (detaching) detach(div); diff --git a/test/js/samples/transition-repeated-outro/expected.js b/test/js/samples/transition-repeated-outro/expected.js index 02febde2af59..93b5dab54d67 100644 --- a/test/js/samples/transition-repeated-outro/expected.js +++ b/test/js/samples/transition-repeated-outro/expected.js @@ -8,7 +8,7 @@ import { init, insert, noop, - run_transition, + run_out, safe_not_equal, transition_in, transition_out @@ -36,7 +36,7 @@ function create_if_block(ctx) { current = true; }, o(local) { - div_outro = run_transition(div, fade, 2); + div_outro = run_out(div, fade); current = false; }, d(detaching) { diff --git a/test/runtime/index.js b/test/runtime/index.js index 65157196c9d7..3aeab126f904 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -243,7 +243,7 @@ describe("runtime", () => { }); } - fs.readdirSync(`${__dirname}/samples`).forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).reverse().forEach(dir => { runTest(dir, false); runTest(dir, true); }); diff --git a/test/runtime/samples/transition-css-in-out-in/_config.js b/test/runtime/samples/transition-css-in-out-in/_config.js index 314271687736..138bef9763b1 100644 --- a/test/runtime/samples/transition-css-in-out-in/_config.js +++ b/test/runtime/samples/transition-css-in-out-in/_config.js @@ -2,15 +2,20 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - const startsWith = (str) => - assert.equal(div.style.animation.slice(0, div.style.animation.length-1), str); + const startsWith = (v, t = div.style.animation) => { + if (Array.isArray(v)) { + t.split(", ").forEach((r,i)=>startsWith(v[i],r)) + } else { + assert.equal(t.slice(0, -1), v); + } + } startsWith(`100ms linear 0ms 1 normal both running __svelte_3261048502`); raf.tick(50); component.visible = false; - startsWith(`100ms linear 0ms 1 normal both running __svelte_890840093`); + startsWith([`100ms linear 0ms 1 normal both running __svelte_3261048502`, `100ms linear 0ms 1 normal both running __svelte_890840093`]); raf.tick(75); component.visible = true; diff --git a/test/runtime/samples/transition-js-args/_config.js b/test/runtime/samples/transition-js-args/_config.js index 00682894cd06..1999f0d54f92 100644 --- a/test/runtime/samples/transition-js-args/_config.js +++ b/test/runtime/samples/transition-js-args/_config.js @@ -4,9 +4,6 @@ export default { const div = target.querySelector('div'); - assert.equal(div.foo, 0); - assert.equal(div.oof, 1); - raf.tick(50); assert.equal(div.foo, 0.5); assert.equal(div.oof, 0.5); diff --git a/test/runtime/samples/transition-js-await-block/_config.js b/test/runtime/samples/transition-js-await-block/_config.js index d019c062d6a0..5da109748e34 100644 --- a/test/runtime/samples/transition-js-await-block/_config.js +++ b/test/runtime/samples/transition-js-await-block/_config.js @@ -17,8 +17,7 @@ export default { const p = target.querySelector('p'); assert.equal(p.className, 'pending'); - assert.equal(p.foo, 0); - + raf.tick(50); assert.equal(p.foo, 0.5); diff --git a/test/runtime/samples/transition-js-context/_config.js b/test/runtime/samples/transition-js-context/_config.js index e2e7135180be..823762ef379f 100644 --- a/test/runtime/samples/transition-js-context/_config.js +++ b/test/runtime/samples/transition-js-context/_config.js @@ -3,8 +3,7 @@ export default { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 42); - + raf.tick(50); assert.equal(div.foo, 42); } diff --git a/test/runtime/samples/transition-js-deferred-b/_config.js b/test/runtime/samples/transition-js-deferred-b/_config.js index 05929964cd4e..3b435d91f9fa 100644 --- a/test/runtime/samples/transition-js-deferred-b/_config.js +++ b/test/runtime/samples/transition-js-deferred-b/_config.js @@ -4,8 +4,7 @@ export default { return Promise.resolve().then(() => { const div = target.querySelector('.foo'); - assert.equal(div.foo, 0); - + raf.tick(50); assert.equal(div.foo, 0.5); }); diff --git a/test/runtime/samples/transition-js-deferred/_config.js b/test/runtime/samples/transition-js-deferred/_config.js index 05929964cd4e..b534aa4cbbca 100644 --- a/test/runtime/samples/transition-js-deferred/_config.js +++ b/test/runtime/samples/transition-js-deferred/_config.js @@ -4,7 +4,6 @@ export default { return Promise.resolve().then(() => { const div = target.querySelector('.foo'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0.5); diff --git a/test/runtime/samples/transition-js-deferred/main.svelte b/test/runtime/samples/transition-js-deferred/main.svelte index eb75f3a7e3af..0297ced7beb2 100644 --- a/test/runtime/samples/transition-js-deferred/main.svelte +++ b/test/runtime/samples/transition-js-deferred/main.svelte @@ -7,9 +7,9 @@ function foo(node, params) { foo_text = node.textContent; - return () => { + return (is_intro) => { if (bar_text !== `b`) { - throw new Error(`foo ran prematurely`); + if (is_intro != null) throw new Error(`foo ran prematurely`); } return { @@ -24,9 +24,9 @@ function bar(node, params) { bar_text = node.textContent; - return () => { + return (is_intro) => { if (foo_text !== `a`) { - throw new Error(`bar ran prematurely`); + if (is_intro != null) throw new Error(`bar ran prematurely`); } return { diff --git a/test/runtime/samples/transition-js-delay-in-out/_config.js b/test/runtime/samples/transition-js-delay-in-out/_config.js index fb77fca01554..ba3df07de215 100644 --- a/test/runtime/samples/transition-js-delay-in-out/_config.js +++ b/test/runtime/samples/transition-js-delay-in-out/_config.js @@ -2,7 +2,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0); diff --git a/test/runtime/samples/transition-js-delay/_config.js b/test/runtime/samples/transition-js-delay/_config.js index 43d7cf5b2a5f..710a616dc64f 100644 --- a/test/runtime/samples/transition-js-delay/_config.js +++ b/test/runtime/samples/transition-js-delay/_config.js @@ -2,7 +2,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0); @@ -13,9 +12,15 @@ export default { component.visible = false; raf.tick(125); - assert.equal(div.foo, 0.25); + assert.equal(div.foo, 0.75); raf.tick(150); + assert.equal(div.foo, 1); + + raf.tick(175); + assert.equal(div.foo, 0.75); + + raf.tick(250); assert.equal(div.foo, 0); } }; \ No newline at end of file diff --git a/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js b/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js index aa0bd9e1aaef..7cffe51d6d76 100644 --- a/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js +++ b/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js @@ -7,9 +7,8 @@ export default { global.count = 0; component.visible = true; - assert.equal(global.count, 1); + assert.equal(global.count, 2); const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(75); component.name = 'everybody'; diff --git a/test/runtime/samples/transition-js-each-block-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-intro-outro/_config.js index b3cb9b097713..2fd6b3075c24 100644 --- a/test/runtime/samples/transition-js-each-block-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-intro-outro/_config.js @@ -7,9 +7,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const divs = target.querySelectorAll('div'); - assert.equal(divs[0].foo, 0); - assert.equal(divs[1].foo, 0); - assert.equal(divs[2].foo, 0); raf.tick(50); assert.equal(divs[0].foo, 0.5); @@ -19,9 +16,9 @@ export default { component.visible = false; raf.tick(70); - assert.equal(divs[0].foo, 0.5); - assert.equal(divs[1].foo, 0.5); - assert.equal(divs[2].foo, 0.5); + assert.equal(divs[0].foo, 0.7); + assert.equal(divs[1].foo, 0.7); + assert.equal(divs[2].foo, 0.7); assert.equal(divs[0].bar, 0.8); assert.equal(divs[1].bar, 0.8); diff --git a/test/runtime/samples/transition-js-each-block-intro/_config.js b/test/runtime/samples/transition-js-each-block-intro/_config.js index 3d143ea5149a..d7b5abf8cf37 100644 --- a/test/runtime/samples/transition-js-each-block-intro/_config.js +++ b/test/runtime/samples/transition-js-each-block-intro/_config.js @@ -7,9 +7,6 @@ export default { test({ assert, component, target, window, raf }) { let divs = target.querySelectorAll('div'); - assert.equal(divs[0].foo, 0); - assert.equal(divs[1].foo, 0); - assert.equal(divs[2].foo, 0); raf.tick(50); assert.equal(divs[0].foo, 0.5); @@ -21,7 +18,6 @@ export default { assert.equal(divs[0].foo, 0.5); assert.equal(divs[1].foo, 0.5); assert.equal(divs[2].foo, 0.5); - assert.equal(divs[3].foo, 0); raf.tick(75); assert.equal(divs[0].foo, 0.75); diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js index 083752cbd9ab..95c3b59bd295 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js @@ -15,9 +15,6 @@ export default { divs[1].i = 1; divs[2].i = 2; - assert.equal(divs[0].foo, 0); - assert.equal(divs[1].foo, 0); - assert.equal(divs[2].foo, 0); raf.tick(100); assert.equal(divs[0].foo, 1); diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js index 5886cc5c6fff..d65b2343c419 100644 --- a/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js +++ b/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js @@ -11,9 +11,6 @@ export default { test({ assert, component, target, window, raf }) { let divs = target.querySelectorAll('div'); - assert.equal(divs[0].foo, 0); - assert.equal(divs[1].foo, 0); - assert.equal(divs[2].foo, 0); raf.tick(50); assert.equal(divs[0].foo, 0.5); @@ -34,7 +31,6 @@ export default { `); divs = target.querySelectorAll('div'); assert.equal(divs[0].foo, 0.5); - assert.equal(divs[1].foo, 0); assert.equal(divs[2].foo, 0.5); assert.equal(divs[3].foo, 0.5); diff --git a/test/runtime/samples/transition-js-if-block-bidi/_config.js b/test/runtime/samples/transition-js-if-block-bidi/_config.js index a070f978e2c8..3201c933b9e5 100644 --- a/test/runtime/samples/transition-js-if-block-bidi/_config.js +++ b/test/runtime/samples/transition-js-if-block-bidi/_config.js @@ -3,15 +3,14 @@ export default { global.count = 0; component.visible = true; - assert.equal(global.count, 1); + assert.equal(global.count, 2); const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(300); assert.equal(div.foo, 0.75); component.visible = false; - assert.equal(global.count, 1); + assert.equal(global.count, 3); raf.tick(500); assert.equal(div.foo, 0.25); diff --git a/test/runtime/samples/transition-js-if-block-in-each-block-bidi/_config.js b/test/runtime/samples/transition-js-if-block-in-each-block-bidi/_config.js index 4c75e3ef06b2..f5469b045b54 100644 --- a/test/runtime/samples/transition-js-if-block-in-each-block-bidi/_config.js +++ b/test/runtime/samples/transition-js-if-block-in-each-block-bidi/_config.js @@ -16,7 +16,6 @@ export default { test({ assert, component, target, window, raf }) { const divs = target.querySelectorAll('div'); - assert.equal(divs[0].foo, 0); raf.tick(100); assert.equal(divs[0].foo, 1); diff --git a/test/runtime/samples/transition-js-if-block-intro-outro/_config.js b/test/runtime/samples/transition-js-if-block-intro-outro/_config.js index edf7438d27ef..e59cb015362a 100644 --- a/test/runtime/samples/transition-js-if-block-intro-outro/_config.js +++ b/test/runtime/samples/transition-js-if-block-intro-outro/_config.js @@ -2,7 +2,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; let div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(200); assert.equal(div.foo, 0.5); @@ -32,11 +31,11 @@ export default { component.visible = false; raf.tick(1300); - assert.equal(div.foo, 0.5); + assert.equal(div.foo, 0.75); assert.equal(div.bar, 0.75); raf.tick(1400); - assert.equal(div.foo, 0.5); + assert.equal(div.foo, 1); assert.equal(div.bar, 0.5); raf.tick(2000); diff --git a/test/runtime/samples/transition-js-if-else-block-intro/_config.js b/test/runtime/samples/transition-js-if-else-block-intro/_config.js index c5eccf50e562..45d4b5934f5f 100644 --- a/test/runtime/samples/transition-js-if-else-block-intro/_config.js +++ b/test/runtime/samples/transition-js-if-else-block-intro/_config.js @@ -3,7 +3,6 @@ export default { test({ assert, component, target, raf }) { assert.equal(target.querySelector('div'), component.no); - assert.equal(component.no.foo, 0); raf.tick(200); assert.equal(component.no.foo, 0.5); @@ -11,7 +10,6 @@ export default { raf.tick(500); component.x = true; assert.equal(component.no, undefined); - assert.equal(component.yes.foo, 0); raf.tick(700); assert.equal(component.yes.foo, 0.5); diff --git a/test/runtime/samples/transition-js-initial/_config.js b/test/runtime/samples/transition-js-initial/_config.js index 3760a1cbfb53..9ed39aead3f0 100644 --- a/test/runtime/samples/transition-js-initial/_config.js +++ b/test/runtime/samples/transition-js-initial/_config.js @@ -3,7 +3,6 @@ export default { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0.5); diff --git a/test/runtime/samples/transition-js-intro-enabled-by-option/_config.js b/test/runtime/samples/transition-js-intro-enabled-by-option/_config.js index 4b5f1cf98755..820a26800be4 100644 --- a/test/runtime/samples/transition-js-intro-enabled-by-option/_config.js +++ b/test/runtime/samples/transition-js-intro-enabled-by-option/_config.js @@ -5,7 +5,6 @@ export default { test({ assert, component, target, window, raf }) { const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0.5); diff --git a/test/runtime/samples/transition-js-local-and-global/_config.js b/test/runtime/samples/transition-js-local-and-global/_config.js index b7f8baa7ee95..c10dc89d027f 100644 --- a/test/runtime/samples/transition-js-local-and-global/_config.js +++ b/test/runtime/samples/transition-js-local-and-global/_config.js @@ -11,7 +11,6 @@ export default { let divs = target.querySelectorAll('div'); assert.equal(divs[0].foo, undefined); - assert.equal(divs[1].foo, 0); raf.tick(50); assert.equal(divs[0].foo, undefined); diff --git a/test/runtime/samples/transition-js-local-nested-await/_config.js b/test/runtime/samples/transition-js-local-nested-await/_config.js index b07d88741fa1..8add2a208055 100644 --- a/test/runtime/samples/transition-js-local-nested-await/_config.js +++ b/test/runtime/samples/transition-js-local-nested-await/_config.js @@ -16,7 +16,6 @@ export default { return promise.then(() => { const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-local-nested-component/_config.js b/test/runtime/samples/transition-js-local-nested-component/_config.js index 87d7aaa233f3..606b9f35ef8e 100644 --- a/test/runtime/samples/transition-js-local-nested-component/_config.js +++ b/test/runtime/samples/transition-js-local-nested-component/_config.js @@ -7,7 +7,6 @@ export default { component.x = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-local-nested-each-keyed/_config.js b/test/runtime/samples/transition-js-local-nested-each-keyed/_config.js index 0595e6701309..85b94422610c 100644 --- a/test/runtime/samples/transition-js-local-nested-each-keyed/_config.js +++ b/test/runtime/samples/transition-js-local-nested-each-keyed/_config.js @@ -18,7 +18,6 @@ export default { const div2 = target.querySelector('div:last-child'); assert.equal(div1.foo, undefined); - assert.equal(div2.foo, 0); raf.tick(200); assert.equal(div1.foo, undefined); diff --git a/test/runtime/samples/transition-js-local-nested-each/_config.js b/test/runtime/samples/transition-js-local-nested-each/_config.js index 0595e6701309..85b94422610c 100644 --- a/test/runtime/samples/transition-js-local-nested-each/_config.js +++ b/test/runtime/samples/transition-js-local-nested-each/_config.js @@ -18,7 +18,6 @@ export default { const div2 = target.querySelector('div:last-child'); assert.equal(div1.foo, undefined); - assert.equal(div2.foo, 0); raf.tick(200); assert.equal(div1.foo, undefined); diff --git a/test/runtime/samples/transition-js-nested-await/_config.js b/test/runtime/samples/transition-js-nested-await/_config.js index 78dbe5ae41d4..637f37ca2b9d 100644 --- a/test/runtime/samples/transition-js-nested-await/_config.js +++ b/test/runtime/samples/transition-js-nested-await/_config.js @@ -16,7 +16,6 @@ export default { return promise.then(() => { const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-nested-component/_config.js b/test/runtime/samples/transition-js-nested-component/_config.js index f1ca81c52e87..7bdc49e2dc1b 100644 --- a/test/runtime/samples/transition-js-nested-component/_config.js +++ b/test/runtime/samples/transition-js-nested-component/_config.js @@ -7,7 +7,6 @@ export default { component.x = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-nested-each-keyed/_config.js b/test/runtime/samples/transition-js-nested-each-keyed/_config.js index 3d2fe3c32e57..9226e47eee69 100644 --- a/test/runtime/samples/transition-js-nested-each-keyed/_config.js +++ b/test/runtime/samples/transition-js-nested-each-keyed/_config.js @@ -8,7 +8,6 @@ export default { component.x = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-nested-each/_config.js b/test/runtime/samples/transition-js-nested-each/_config.js index c21024dd6995..0d3a393e9b40 100644 --- a/test/runtime/samples/transition-js-nested-each/_config.js +++ b/test/runtime/samples/transition-js-nested-each/_config.js @@ -8,7 +8,6 @@ export default { component.x = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-nested-if/_config.js b/test/runtime/samples/transition-js-nested-if/_config.js index 2fdd17da936e..93b624dfa85b 100644 --- a/test/runtime/samples/transition-js-nested-if/_config.js +++ b/test/runtime/samples/transition-js-nested-if/_config.js @@ -8,7 +8,6 @@ export default { component.x = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(100); assert.equal(div.foo, 1); diff --git a/test/runtime/samples/transition-js-nested-intro/_config.js b/test/runtime/samples/transition-js-nested-intro/_config.js index 9072e885fcbe..95d2775d50d0 100644 --- a/test/runtime/samples/transition-js-nested-intro/_config.js +++ b/test/runtime/samples/transition-js-nested-intro/_config.js @@ -2,7 +2,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 0); diff --git a/test/runtime/samples/transition-js-parameterised-with-state/_config.js b/test/runtime/samples/transition-js-parameterised-with-state/_config.js index 64e40dc63dbd..c7988fce9f8a 100644 --- a/test/runtime/samples/transition-js-parameterised-with-state/_config.js +++ b/test/runtime/samples/transition-js-parameterised-with-state/_config.js @@ -6,7 +6,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 100); diff --git a/test/runtime/samples/transition-js-parameterised/_config.js b/test/runtime/samples/transition-js-parameterised/_config.js index 1370905547a2..851c714a7192 100644 --- a/test/runtime/samples/transition-js-parameterised/_config.js +++ b/test/runtime/samples/transition-js-parameterised/_config.js @@ -2,7 +2,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const div = target.querySelector('div'); - assert.equal(div.foo, 0); raf.tick(50); assert.equal(div.foo, 100); diff --git a/test/runtime/samples/transition-js-slot/_config.js b/test/runtime/samples/transition-js-slot/_config.js index a32e4bda7202..1af9e25d33af 100644 --- a/test/runtime/samples/transition-js-slot/_config.js +++ b/test/runtime/samples/transition-js-slot/_config.js @@ -10,7 +10,6 @@ export default { test({ assert, component, target, window, raf }) { component.visible = true; const p = target.querySelector('p'); - assert.equal(p.foo, 0); raf.tick(50); assert.equal(p.foo, 0.5); From de2526739a53c784f3eaef3abee47a9b7494ce53 Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 10 Jun 2020 04:00:12 +0200 Subject: [PATCH 10/12] fix --- .../render_dom/wrappers/Element/index.ts | 3 +- src/runtime/internal/loop.ts | 20 +------- src/runtime/internal/transitions.ts | 51 ++++++++++--------- .../samples/transition-js-events/_config.js | 6 +-- 4 files changed, 32 insertions(+), 48 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 77555299ec45..dd3044145edb 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -746,8 +746,7 @@ export default class ElementWrapper extends Wrapper { block.add_variable(transition); - block.chunks.mount.push(b`if (!${transition}) ${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet});`); - block.chunks.hydrate.push(b`if (!${transition}) ${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet});`); + block.chunks.hydrate.push(b`${transition} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet});`); if (intro.expression) { const dirty = block.renderer.dirty([intro.name,...Array.from(intro.expression.dependencies)]); block.chunks.update.push(b`if (${dirty}) ${transition}.u(${fn}, ${snippet});`); diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index f745b55ee39b..8a07a2fca9e4 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -8,24 +8,6 @@ export const frame = { return n ? this.time : (this.time = now()); }, }; - -// function calc_framerate() { -// raf((t1) => { -// raf((t2) => { -// const delta = t2 - t1; -// raf((t3) => { -// if (Math.abs(t3 - t2 - delta) > 1) { -// calc_framerate(); -// } else { -// const f24 = 1000 / 24; -// const f144 = 1000 / 144; -// frame.rate = delta > f144 ? f144 : delta < f24 ? f24 : delta; -// } -// }); -// }); -// }); -// } -// calc_framerate(); type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; @@ -111,7 +93,7 @@ export const setTweenTimeout = ( ): TaskCanceller => { let t = 0.0; return loop((now) => { - t = 1 - (end_time - now) / duration; + t = 1.0 - (end_time - now) / duration; if (t >= 1.0) return run(1), stop(now), false; if (t >= 0.0) run(t); return true; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 554704457ad9..d5a7d616a01c 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -18,8 +18,8 @@ export interface CssTransitionConfig extends CssAnimationConfig { } export type TimeableConfig = Omit & { duration?: number | ((len: number) => number) }; const enum EasingStrategy { - reversed = "reversed", balanced = "balanced", + reversed = "reversed", mirrored = "mirrored", } const enum TransitionEvent { @@ -109,6 +109,14 @@ const reversed = (fn, is_intro, easing, start = 0, end = 1) => { : (t) => run(start + difference * t); }; +const balanced = (fn, is_intro, easing, start = 0, end = 1) => { + const run = swap(fn, is_intro); + const difference = end - start; + return easing + ? (t) => run(start + difference * easing(t)) + : (t) => run(start + difference * t); +}; + export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement, from : Rect, fn: AnimationFn, params: CssTransitionConfig = {}) { let running = true; let cancel_css; @@ -144,10 +152,9 @@ export const run_in = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: C add_measure_callback(() => { config = fn(this, params); return (current_frame_time) => { - let { delay = 0, duration = 300, easing, tick, css, strategy = EasingStrategy.balanced }: CssTransitionConfig = + let { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = "function" === typeof config ? (config = config()) : config; - const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; - const runner = (fn) => solver(fn, true, easing); + const runner = (fn) => balanced(fn, true, easing); end_time = current_frame_time + delay + duration; this.dispatchEvent(custom_event(TransitionEvent.introstart)); if (css) cancel_css = animate_css(this, runner(css), duration, delay); @@ -174,10 +181,9 @@ export const run_out = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: add_measure_callback(() => { config = fn(this, params); return (current_frame_time) => { - let { delay = 0, duration = 300, easing, tick, css, strategy = EasingStrategy.balanced }: CssTransitionConfig = + let { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = "function" === typeof config ? (config = config()) : config; - const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; - const runner = (fn) => solver(fn, false, easing); + const runner = (fn) => balanced(fn, false, easing); end_time = current_frame_time + delay + duration; current_group.t = Math.max(end_time, current_group.t); if (current_group.s.push(stop) === current_group.r) { @@ -245,14 +251,19 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( return (current_frame_time) => { let { tick, css, duration = 300.0, delay = 0.0, easing, strategy = EasingStrategy.balanced }: CssTransitionConfig = "function" === typeof config ? (config = config()) : config; - const solver = EasingStrategy.balanced === strategy ? reversed : mirrored; + const solver = EasingStrategy.balanced === strategy ? balanced : EasingStrategy.reversed === strategy ? reversed : mirrored; const runner = (fn) => solver(fn, is_intro, easing, ratio_left, 1); if (delayed_start) delay = 0; if (solver === reversed) duration -= prev_duration_left; + else if(solver === balanced) duration *= (1-ratio_left) else if (solver === mirrored) delay -= prev_duration_left; start_time = current_frame_time + delay; end_time = start_time + duration; - if (cancelled) return; + if (cancelled || duration < 1) return; + this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introstart : TransitionEvent.outrostart)); + if (css) cancel_css = animate_css(this, runner(css), duration, delay); + if (tick) cancel_raf = setTweenTimeout(stop, end_time, runner(tick), duration); + else cancel_raf = setFrameTimeout(stop, end_time); if (!is_intro) { current_group.t = Math.max(end_time, current_group.t); if (current_group.s.push(stop) === current_group.r) { @@ -263,10 +274,6 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( }, current_group.t); } } - this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introstart : TransitionEvent.outrostart)); - if (css) cancel_css = animate_css(this, runner(css), duration, delay); - if (tick) cancel_raf = setTweenTimeout(is_intro ? stop : noop, end_time, runner(tick), duration); - else if (is_intro) cancel_raf = setFrameTimeout(stop, end_time); }; }; } @@ -277,7 +284,6 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( cancelled = true; if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); - if (1 === t && cancel_previous) cancel_previous(); } if (!config) return; const duration_left = end_time - t; @@ -286,18 +292,15 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( }; const stop: StopResetReverseFn = (t?: number | -1 | 1) => { + if (t >= end_time) { + if (pending === 1 && (is_intro || t >= current_group.t)) cancel(t); + this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introend : TransitionEvent.outroend)); + } if (running) { running = false; - if (config) { - if (t >= end_time) { - if (!is_intro && "tick" in config) config.tick(0, 1); - if (pending === 1) cancel(t); - this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introend : TransitionEvent.outroend)); - } - if (!is_intro) { - if (!--current_group.r) { - for (let i = 0, { c } = current_group, r = t === 1; i < c.length; i++) c[i](r); - } + if (config && !is_intro && !--current_group.r) { + for (let i = 0, { c } = current_group, r = Math.abs(t) === 1; i < c.length; i++) { + c[i](r); } } } diff --git a/test/runtime/samples/transition-js-events/_config.js b/test/runtime/samples/transition-js-events/_config.js index 00d83cb2755f..891f8f03821a 100644 --- a/test/runtime/samples/transition-js-events/_config.js +++ b/test/runtime/samples/transition-js-events/_config.js @@ -10,7 +10,7 @@ export default {

waiting...

`, - async test({ assert, component, target, raf }) { + test({ assert, component, target, raf }) { component.visible = true; assert.htmlEqual(target.innerHTML, ` @@ -25,7 +25,7 @@ export default { assert.deepEqual(component.intros.sort(), ['a', 'b', 'c', 'd']); assert.equal(component.intro_count, 4); - await raf.tick(100); + raf.tick(100); assert.equal(component.intro_count, 0); assert.htmlEqual(target.innerHTML, ` @@ -55,7 +55,7 @@ export default { component.visible = true; - await raf.tick(250); + raf.tick(250); assert.deepEqual(component.intros.sort(), ['a', 'a', 'b', 'b', 'c', 'c', 'd', 'd']); assert.equal(component.intro_count, 4); From 0ec763a2159466a539b8727fff5c37bd3aa2739e Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 10 Jun 2020 06:06:47 +0200 Subject: [PATCH 11/12] fix --- .../render_dom/wrappers/Element/index.ts | 2 +- src/runtime/internal/index.ts | 2 +- src/runtime/internal/style_manager.ts | 2 +- src/runtime/internal/transitions.ts | 67 ++++++++++--------- src/runtime/internal/utils.ts | 2 +- src/runtime/transition/index.ts | 1 - test/runtime/index.js | 2 +- .../transition-css-in-out-in/_config.js | 4 +- 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index dd3044145edb..8e2eadf401f8 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -805,7 +805,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (detaching) ${outro_var}();`); } - add_animation(block: Block) { + add_animation(block: Block, _intro: Transition) { const [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block); diff --git a/src/runtime/internal/index.ts b/src/runtime/internal/index.ts index 253b8fccc8dc..6643d1b7ef5e 100644 --- a/src/runtime/internal/index.ts +++ b/src/runtime/internal/index.ts @@ -11,5 +11,5 @@ export * from './ssr'; export * from './transitions'; export * from './utils'; export * from './Component'; -export * from './style_manager' +export * from './style_manager'; export * from './dev'; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index a98ad9221c6a..8de5a5e0e2e9 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -18,7 +18,7 @@ export const animate_css = /*#__PURE__*/ methodify( ); } let rule = '{\n'; - for (let t = 0, step = frame.rate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + for (let t = 0, step = frame.rate / Math.max(frame.rate,duration); t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; rule += `100% {${css(1)}}\n}`; // darkskyapp/string-hash diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index d5a7d616a01c..794184a8086e 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -78,7 +78,7 @@ export const group_transition_out = (fn) => { transition_group = transition_group.p; }; type Rect = DOMRect | ClientRect; -type MeasureCallback = () => CssTransitionConfig +type MeasureCallback = (is_intro?: boolean) => CssTransitionConfig; type CustomTransitionFunction = (node: HTMLElement, params: any) => MeasureCallback | CssTransitionConfig; type AnimationFn = (node: Element, { from, to }: { from: Rect; to: Rect }, params: any) => CssTransitionConfig; type StopResetReverseFn = (t?: number | 1 | -1) => StopResetReverseFn | void; @@ -117,7 +117,7 @@ const balanced = (fn, is_intro, easing, start = 0, end = 1) => { : (t) => run(start + difference * t); }; -export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement, from : Rect, fn: AnimationFn, params: CssTransitionConfig = {}) { +export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement, from: Rect, fn: AnimationFn, params: CssTransitionConfig = {}) { let running = true; let cancel_css; let cancel_raf; @@ -140,8 +140,8 @@ export const run_animation = /*#__PURE__*/ methodify(function (this: HTMLElement else running = false; if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); - } - return stop + }; + return stop; }); export const run_in = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: CustomTransitionFunction, params: CssTransitionConfig = {}) { let config; @@ -152,7 +152,7 @@ export const run_in = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: C add_measure_callback(() => { config = fn(this, params); return (current_frame_time) => { - let { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = + const { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = "function" === typeof config ? (config = config()) : config; const runner = (fn) => balanced(fn, true, easing); end_time = current_frame_time + delay + duration; @@ -167,8 +167,8 @@ export const run_in = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: C if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); if (t && t >= end_time) this.dispatchEvent(custom_event(TransitionEvent.introend)); - } - return stop + }; + return stop; }); export const run_out = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: CustomTransitionFunction, params: CssTransitionConfig = {}) { let config; @@ -181,7 +181,7 @@ export const run_out = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: add_measure_callback(() => { config = fn(this, params); return (current_frame_time) => { - let { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = + const { delay = 0, duration = 300, easing, tick, css }: CssTransitionConfig = "function" === typeof config ? (config = config()) : config; const runner = (fn) => balanced(fn, false, easing); end_time = current_frame_time + delay + duration; @@ -208,12 +208,12 @@ export const run_out = /*#__PURE__*/ methodify(function (this: HTMLElement, fn: if ("tick" in config) config.tick(0, 1); this.dispatchEvent(custom_event(TransitionEvent.outroend)); } - if(!--current_group.r) for (let i = 0, { c } = current_group, r = t === void 0;i < c.length;i++) c[i](r); - } - return stop + if (!--current_group.r) for (let i = 0, { c } = current_group, r = t === void 0;i < c.length;i++) c[i](r); + }; + return stop; }); export const create_bidirectional_transition = /*#__PURE__*/ methodify(function(this: HTMLElement, fn: CustomTransitionFunction, params?: CssTransitionConfig) { - let transition_delay; + let transition_delay = 0.0; let pending = 0; let prev; @@ -221,10 +221,10 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( let test_config; if (typeof (test_config = (fn = new_fn)(this,(params = new_params))) === "function") test_config = test_config(); transition_delay = test_config.delay || 0.0; - } + }; u(); - const run_transition = (is_intro:boolean, cancel_previous?) => { + const run_transition = (is_intro: boolean, cancel_previous?) => { const delayed_start = transition_delay && cancel_previous && pending; let config; @@ -250,20 +250,20 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( config = fn(this, params); return (current_frame_time) => { let { tick, css, duration = 300.0, delay = 0.0, easing, strategy = EasingStrategy.balanced }: CssTransitionConfig = - "function" === typeof config ? (config = config()) : config; + "function" === typeof config ? (config = config(is_intro)) : config; const solver = EasingStrategy.balanced === strategy ? balanced : EasingStrategy.reversed === strategy ? reversed : mirrored; const runner = (fn) => solver(fn, is_intro, easing, ratio_left, 1); if (delayed_start) delay = 0; if (solver === reversed) duration -= prev_duration_left; - else if(solver === balanced) duration *= (1-ratio_left) + else if (solver === balanced) duration *= 1 - ratio_left; else if (solver === mirrored) delay -= prev_duration_left; start_time = current_frame_time + delay; end_time = start_time + duration; - if (cancelled || duration < 1) return; + if (cancelled) return; this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introstart : TransitionEvent.outrostart)); if (css) cancel_css = animate_css(this, runner(css), duration, delay); - if (tick) cancel_raf = setTweenTimeout(stop, end_time, runner(tick), duration); - else cancel_raf = setFrameTimeout(stop, end_time); + if (tick) cancel_raf = setTweenTimeout(is_intro ? stop : noop, end_time, runner(tick), duration); + else if (is_intro) cancel_raf = setFrameTimeout(stop, end_time); if (!is_intro) { current_group.t = Math.max(end_time, current_group.t); if (current_group.s.push(stop) === current_group.r) { @@ -274,9 +274,15 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( }, current_group.t); } } + setFrameTimeout(() => { + if (!cancelled || !pending) { + if (!is_intro && tick) tick(0, 1); + this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introend : TransitionEvent.outroend)); + } + }, end_time); }; }; - } + }; const cancel = (t) => { if (!cancelled) { @@ -285,22 +291,23 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( if (cancel_css) cancel_css(); if (cancel_raf) cancel_raf(); } - if (!config) return; + if (!config || 1 === t) return; const duration_left = end_time - t; const next_ratio_left = 1 - duration_left / (end_time - start_time); return duration_left > 0 && next_ratio_left > 0 && [duration_left, (1 - ratio_left) * (1 - (config.easing || ((v) => v))(next_ratio_left))]; }; const stop: StopResetReverseFn = (t?: number | -1 | 1) => { - if (t >= end_time) { - if (pending === 1 && (is_intro || t >= current_group.t)) cancel(t); - this.dispatchEvent(custom_event(is_intro ? TransitionEvent.introend : TransitionEvent.outroend)); - } if (running) { running = false; - if (config && !is_intro && !--current_group.r) { - for (let i = 0, { c } = current_group, r = Math.abs(t) === 1; i < c.length; i++) { - c[i](r); + if (config) { + if (t && t >= end_time) { + if (pending === 1) cancel(1); + } + if (!is_intro) { + if (!--current_group.r) { + for (let i = 0, { c } = current_group, r = Math.abs(t) === 1; i < c.length; i++) c[i](r); + } } } } @@ -316,7 +323,7 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( add_measure_callback(run(frame.time)); } - return stop + return stop; }; return { @@ -330,7 +337,7 @@ export const create_bidirectional_transition = /*#__PURE__*/ methodify(function( d() { if (prev) prev(1); } - } + }; }); export const run_duration = (duration, value1, value2?): number => typeof duration === "function" ? duration(value1, value2) : duration; diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 65d6036b61c1..0cffedfbf571 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -146,7 +146,7 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, export function action_destroyer(action_result) { return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; } -type Methodify any> = (thisType : ThisParameterType, ...parameters :Parameters) => T extends (...args: Parameters) => infer R ? R : any +type Methodify any> = (thisType: ThisParameterType, ...parameters: Parameters) => T extends (...args: Parameters) => infer R ? R : any export const methodify: any>(fn: T) => Methodify = /*#__PURE__*/ (function () { const call = Function.prototype.call; return call.bind.bind(call); diff --git a/src/runtime/transition/index.ts b/src/runtime/transition/index.ts index 88daba3c881b..39a1a2af86a1 100644 --- a/src/runtime/transition/index.ts +++ b/src/runtime/transition/index.ts @@ -130,7 +130,6 @@ export function crossfade({ delay: default_delay = 0, duration: default_duration b.delete(key); return crossfade(from_node, to_node, params); } else { - debugger a.delete(key); return fallback && fallback(to_node, params, is_intro); } diff --git a/test/runtime/index.js b/test/runtime/index.js index 3aeab126f904..65157196c9d7 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -243,7 +243,7 @@ describe("runtime", () => { }); } - fs.readdirSync(`${__dirname}/samples`).reverse().forEach(dir => { + fs.readdirSync(`${__dirname}/samples`).forEach(dir => { runTest(dir, false); runTest(dir, true); }); diff --git a/test/runtime/samples/transition-css-in-out-in/_config.js b/test/runtime/samples/transition-css-in-out-in/_config.js index 138bef9763b1..eaf761b6a71d 100644 --- a/test/runtime/samples/transition-css-in-out-in/_config.js +++ b/test/runtime/samples/transition-css-in-out-in/_config.js @@ -4,11 +4,11 @@ export default { const div = target.querySelector('div'); const startsWith = (v, t = div.style.animation) => { if (Array.isArray(v)) { - t.split(", ").forEach((r,i)=>startsWith(v[i],r)) + t.split(", ").forEach((r,i) => startsWith(v[i],r)); } else { assert.equal(t.slice(0, -1), v); } - } + }; startsWith(`100ms linear 0ms 1 normal both running __svelte_3261048502`); From d94d3bfc33b95049a059d48d8b58268d810e66e9 Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 10 Jun 2020 08:41:57 +0200 Subject: [PATCH 12/12] fix --- src/runtime/internal/loop.ts | 114 ++++++++++++++------------ src/runtime/internal/style_manager.ts | 2 +- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/src/runtime/internal/loop.ts b/src/runtime/internal/loop.ts index 8a07a2fca9e4..405f3d578a9f 100644 --- a/src/runtime/internal/loop.ts +++ b/src/runtime/internal/loop.ts @@ -10,79 +10,86 @@ export const frame = { }; type TaskCallback = (t: number) => boolean; type TaskCanceller = () => void; +type TimeoutTask = { t: number; c: (now: number) => void }; + +const pending_sort: TimeoutTask[] = []; +const timed_tasks: TimeoutTask[] = []; let i = 0; let j = 0; -let n = 0; -let v: TaskCallback; +let t = 0; +let c: TaskCallback; let running_frame: TaskCallback[] = []; let next_frame: TaskCallback[] = []; -const run = (t: number) => { - [running_frame, next_frame] = [next_frame, running_frame]; - for (t = (frame.time = now()), i = n = 0, j = running_frame.length; i < j; i++) { - if ((v = running_frame[i])(t)) { - next_frame[n++] = v; - } - } - if ((running_frame.length = 0) < n) { - raf(run); - } -}; - -type TimeoutTask = { timestamp: number; callback: (now: number) => void }; - -const pending_insert_timed: TimeoutTask[] = []; -const timed_tasks: TimeoutTask[] = []; - -let pending_inserts = false; -let running_timed = false; +let this_task: TimeoutTask; +let that_task: TimeoutTask; -const run_timed = (now: number) => { - let last_index = timed_tasks.length - 1; - while (-1 !== last_index && now >= timed_tasks[last_index].timestamp) { - timed_tasks[last_index--].callback(now); +let l = -1; +let n = 0; +let p = 0; + +const run = (time: number) => { + time = (frame.time = now()); + if (0 !== n) { + [running_frame, next_frame] = [next_frame, running_frame]; + j = n; + for (i = n = 0; i < j; i++) { + c = running_frame[i]; + if (c(time)) { + next_frame[n++] = c; + } + } + running_frame.length = 0; } - if (pending_inserts) { - for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++) { - if (now >= (this_task = pending_insert_timed[i]).timestamp) { - this_task.callback(now); - } else { - for (j = last_index; -1 !== j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--) { + if (-1 !== l) { + while (-1 !== l && time >= timed_tasks[l].t) { + timed_tasks[l--].c(time); + } + if (0 !== p) { + for (i = j = 0; i < p; i++) { + this_task = pending_sort[i]; + t = this_task.t; + if (time >= t) { + this_task.c(time); + continue; + } + for (j = l++; -1 !== j; j--) { + that_task = timed_tasks[j]; + if (t <= that_task.t) break; timed_tasks[j + 1] = that_task; } timed_tasks[j + 1] = this_task; - last_index++; } + pending_sort.length = p = 0; } - pending_insert_timed.length = 0; - pending_inserts = false; + timed_tasks.length = l + 1; + } + if (0 !== n || -1 !== l) { + raf(run); } - return (running_timed = !!(timed_tasks.length = last_index + 1)); -}; -const unsafe_loop = (fn) => { - if (0 === n) raf(run); - next_frame[n++] = fn; }; -export const loop = (fn) => { +const loop = (fn) => { let running = true; - unsafe_loop((t) => running && fn(t)); - return () => void (running = false); + if (0 === n) raf(run); + next_frame[n++] = (t) => running && fn(t); + return () => { + running = false; + }; }; -export const setFrameTimeout = (callback: (t: number) => void, timestamp: number): TaskCanceller => { - const task: TimeoutTask = { callback, timestamp }; - if (running_timed) { - pending_inserts = !!pending_insert_timed.push(task); +export const setFrameTimeout = (c: (t: number) => void, t: number): TaskCanceller => { + const task: TimeoutTask = { c, t }; + if (-1 !== l) { + pending_sort[p++] = task; } else { - unsafe_loop(run_timed); - running_timed = true; - timed_tasks.push(task); + if (0 === n) raf(run); + timed_tasks[(l = 0)] = task; } return () => { - task.callback = noop; + task.c = noop; }; }; export const setTweenTimeout = ( @@ -102,7 +109,12 @@ export const setTweenTimeout = ( /** tests only */ export const clear_loops = () => { - next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false); + running_frame.length = 0; + pending_sort.length = 0; + timed_tasks.length = 0; + next_frame.length = 0; + i = j = t = n = p = 0; + l = -1; tasks.clear(); }; diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 8de5a5e0e2e9..571bf4c587c6 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -18,7 +18,7 @@ export const animate_css = /*#__PURE__*/ methodify( ); } let rule = '{\n'; - for (let t = 0, step = frame.rate / Math.max(frame.rate,duration); t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; + for (let t = 0, step = frame.rate / Math.max(frame.rate, duration); t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; rule += `100% {${css(1)}}\n}`; // darkskyapp/string-hash