pull/4999/head
pushkine 5 years ago
parent a4dadf82be
commit dbe7d797d1

@ -205,6 +205,10 @@ export default class Block {
this.has_animation = true; 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) { add_variable(id: Identifier, init?: Node) {
if (this.variables.has(id.name)) { if (this.variables.has(id.name)) {
throw new Error( throw new Error(

@ -7,6 +7,7 @@ import FragmentWrapper from './Fragment';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import ElseBlock from '../../nodes/ElseBlock'; import ElseBlock from '../../nodes/ElseBlock';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';
import bit_state from '../../utils/bit_state'
export class ElseBlockWrapper extends Wrapper { export class ElseBlockWrapper extends Wrapper {
node: ElseBlock; node: ElseBlock;
@ -423,25 +424,18 @@ export default class EachBlockWrapper extends Wrapper {
const dynamic = this.block.has_update_method; const dynamic = this.block.has_update_method;
const destroy = this.node.has_animation const transition_state = bit_state([dynamic, this.node.has_animation, this.block.has_outros]);
? (this.block.has_outros const update_keyed_each = (transition_out) =>
? `@fix_and_outro_and_destroy_block` 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});`;
: `@fix_and_destroy_block`)
: this.block.has_outros
? `@outro_and_destroy_block`
: `@destroy_block`;
if (this.dependencies.size) { if (this.dependencies.size) {
this.updates.push(b` this.updates.push(b`
const ${this.vars.each_block_value} = ${snippet}; const ${this.vars.each_block_value} = ${snippet};
${this.renderer.options.dev && b`@validate_each_argument(${this.vars.each_block_value});`} ${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.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.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.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; let remove_old_blocks;
if (this.block.has_outros) { if (this.block.has_outros) {
const out = block.get_unique_name('out'); remove_old_blocks = this.block.group_transition_out((transition_out) =>
b`for (#i = ${data_length}; #i < ${view_length}; #i += 1) {
block.chunks.init.push(b` ${transition_out}(#i);
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();
`;
} else { } else {
remove_old_blocks = b` 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) { for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) {

@ -26,6 +26,7 @@ import { Identifier } from 'estree';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import { extract_names } from 'periscopic'; import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action'; import Action from '../../../nodes/Action';
import Transition from '../../../nodes/Transition';
const events = [ const events = [
{ {
@ -379,8 +380,18 @@ export default class ElementWrapper extends Wrapper {
this.add_attributes(block); this.add_attributes(block);
this.add_directives_in_order(block); this.add_directives_in_order(block);
this.add_transitions(block); const { intro, outro } = this.node;
this.add_animation(block); 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_classes(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
@ -728,165 +739,101 @@ export default class ElementWrapper extends Wrapper {
} }
} }
add_transitions( add_bidi_transition(block: Block, intro: Transition) {
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 name = block.get_unique_name(`${this.var.name}_transition`);
const snippet = intro.expression const snippet = intro.expression ? intro.expression.manipulate(block) : null;
? intro.expression.manipulate(block)
: x`{}`;
block.add_variable(name); block.add_variable(name, x`@noop`);
const fn = this.renderer.reference(intro.name); const fn = this.renderer.reference(intro.name);
const intro_block = b` let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 1, ${snippet});`;
@add_render_callback(() => { let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 2, ${snippet});`;
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, true);
${name}.run(1);
});
`;
const outro_block = b`
if (!${name}) ${name} = @create_bidirectional_transition(${this.var}, ${fn}, ${snippet}, false);
${name}.run(0);
`;
if (intro.is_local) { if (intro.is_local) {
block.chunks.intro.push(b` intro_block = b`if (#local) {${intro_block}}`;
if (#local) { outro_block = b`if (#local) {${outro_block}}`;
${intro_block}
} }
`);
block.chunks.outro.push(b`
if (#local) {
${outro_block}
}
`);
} else {
block.chunks.intro.push(intro_block); block.chunks.intro.push(intro_block);
block.chunks.outro.push(outro_block); block.chunks.outro.push(outro_block);
}
block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`); block.chunks.destroy.push(b`if (detaching) ${name}();`);
} }
add_intro(block: Block, intro: Transition, outro: Transition) {
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) { if (outro) {
intro_block = b` const outro_var = block.alias(`${this.var.name}_outro`);
@add_render_callback(() => { block.chunks.intro.push(b`${outro_var}(1);`);
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}
}
`;
} }
if (this.node.animation) {
block.chunks.intro.push(intro_block); const [unfreeze_var, rect_var, stop_animation_var, animationFn, params] = run_animation(this, 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` block.chunks.intro.push(b`
if (${outro_name}) ${outro_name}.end(1); if (${unfreeze_var}) {
${unfreeze_var}();
${unfreeze_var} = void 0;
${stop_animation_var} = @run_animation(${this.var}, ${rect_var}, ${animationFn}, ${params});
}
`); `);
} }
if (!intro) return;
// TODO hide elements that have outro'd (unless they belong to a still-outroing const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`);
// group) prior to their removal from the DOM block.add_variable(intro_var, x`@noop`);
let outro_block = b`
${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet});
`;
if (outro.is_local) { let start_intro;
outro_block = b` if (intro.is_local)
if (#local) { start_intro = b`if (#local) ${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`;
${outro_block} 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;
block.chunks.outro.push(outro_block); const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`);
block.add_variable(outro_var, x`@noop`);
block.chunks.destroy.push(b`if (detaching && ${outro_name}) ${outro_name}.end();`); 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);
add_animation(block: Block) { block.chunks.destroy.push(b`if (detaching) ${outro_var}();`);
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 [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block);
const stop_animation = block.get_unique_name('stop_animation');
block.add_variable(rect); block.add_variable(unfreeze_var);
block.add_variable(stop_animation, x`@noop`); block.add_variable(rect_var);
block.add_variable(stop_animation_var, x`@noop`);
block.chunks.measure.push(b` block.chunks.measure.push(b`
${rect} = ${this.var}.getBoundingClientRect(); ${rect_var} = ${this.var}.getBoundingClientRect();
${intro && b`${intro_var}();`}
`); `);
block.chunks.fix.push(b` block.chunks.fix.push(b`
@fix_position(${this.var}); ${stop_animation_var}();
${stop_animation}(); ${unfreeze_var} = @fix_position(${this.var}, ${rect_var});
${outro && b`@add_transform(${this.var}, ${rect});`}
`); `);
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` block.chunks.animate.push(b`
${stop_animation}(); if (${unfreeze_var}) return
${stop_animation} = @create_animation(${this.var}, ${rect}, ${name}, ${params}); 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) { add_classes(block: Block) {
@ -995,3 +942,20 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | TagWrapper>, 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,
];
}

@ -434,13 +434,10 @@ export default class IfBlockWrapper extends Wrapper {
if (this.needs_update) { if (this.needs_update) {
const update_mount_node = this.get_update_mount_node(anchor); const update_mount_node = this.get_update_mount_node(anchor);
const destroy_old_block = b` const destroy_old_block = block.group_transition_out(
@group_outros(); (transition_out) =>
@transition_out(${if_blocks}[${previous_block_index}], 1, 1, () => { b`${transition_out}(${if_blocks}[${previous_block_index}], () => {${if_blocks}[${previous_block_index}] = null;})`
${if_blocks}[${previous_block_index}] = null; );
});
@check_outros();
`;
const create_new_block = b` const create_new_block = b`
${name} = ${if_blocks}[${current_block_type_index}]; ${name} = ${if_blocks}[${current_block_type_index}];
@ -556,11 +553,7 @@ export default class IfBlockWrapper extends Wrapper {
if (${branch.condition}) { if (${branch.condition}) {
${enter} ${enter}
} else if (${name}) { } else if (${name}) {
@group_outros(); ${block.group_transition_out((transition_out) => b`${transition_out}(${name},() => {${name} = null;})`)}
@transition_out(${name}, 1, 1, () => {
${name} = null;
});
@check_outros();
} }
`); `);
} else { } else {

@ -451,12 +451,11 @@ export default class InlineComponentWrapper extends Wrapper {
block.chunks.update.push(b` block.chunks.update.push(b`
if (${switch_value} !== (${switch_value} = ${snippet})) { if (${switch_value} !== (${switch_value} = ${snippet})) {
if (${name}) { if (${name}) {
@group_outros();
const old_component = ${name}; const old_component = ${name};
@transition_out(old_component.$$.fragment, 1, 0, () => { ${block.group_transition_out(
@destroy_component(old_component, 1); (transition_out) =>
}); b`${transition_out}(old_component.$$.fragment, () => { @destroy_component(old_component, 1); }, 0);`
@check_outros(); )}
} }
if (${switch_value}) { if (${switch_value}) {

@ -0,0 +1 @@
export default (arr) => arr.reduce((state, bool, index) => (bool ? (state |= 1 << index) : state), 0);

@ -1,41 +1,22 @@
import { cubicOut } from 'svelte/easing'; import { cubicOut } from "svelte/easing";
import { is_function } from 'svelte/internal'; import { run_duration } from "svelte/internal";
import { CssTransitionConfig, TimeableConfig } from "svelte/transition";
// todo: same as Transition, should it be shared? export function flip(
export interface AnimationConfig { node: Element,
delay?: number; animation: { from: DOMRect; to: DOMRect },
duration?: number; { delay = 0, duration = (d: number) => Math.sqrt(d) * 30, easing = cubicOut }: TimeableConfig
easing?: (t: number) => number; ): CssTransitionConfig {
css?: (t: number, u: number) => string; const style = getComputedStyle(node).transform;
tick?: (t: number, u: number) => void; const transform = style === "none" ? "" : style;
}
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;
const scaleX = animation.from.width / node.clientWidth; const scaleX = animation.from.width / node.clientWidth;
const scaleY = animation.from.height / node.clientHeight; const scaleY = animation.from.height / node.clientHeight;
const dx = (animation.from.left - animation.to.left) / scaleX; const dx = (animation.from.left - animation.to.left) / scaleX;
const dy = (animation.from.top - animation.to.top) / scaleY; 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 { return {
delay, delay,
duration: is_function(duration) ? duration(d) : duration, duration: run_duration(duration, Math.sqrt(dx * dx + dy * dy)),
easing, easing,
css: (_t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);` css: (_t, u) => `transform: ${transform} translate(${u * dx}px, ${u * dy}px);`
}; };

@ -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 { 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 { children, detach } from './dom';
import { transition_in } from './transitions'; import { transition_in } from './transitions';
import { noop } from './environment';
interface Fragment { export interface Fragment {
key: string|null; key: string|null;
first: null; first: null;
/* create */ c: () => void; /* create */ c: () => void;
@ -20,7 +21,7 @@ interface Fragment {
/* destroy */ d: (detaching: 0|1) => void; /* destroy */ d: (detaching: 0|1) => void;
} }
// eslint-disable-next-line @typescript-eslint/class-name-casing // eslint-disable-next-line @typescript-eslint/class-name-casing
interface T$$ { export interface T$$ {
dirty: number[]; dirty: number[];
ctx: null|any; ctx: null|any;
bound: 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]) { export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {
const parent_component = current_component; const parent_component = current_component;
set_current_component(component); set_current_component(component);
@ -127,13 +119,18 @@ export function init(component, options, instance, create_fragment, not_equal, p
let ready = false; let ready = false;
$$.ctx = instance $$.ctx = instance
? instance(component, prop_values, (i, ret, ...rest) => { ? instance(component, prop_values, (i, res, ...rest) => {
const value = rest.length ? rest[0] : ret; if ($$.ctx && not_equal($$.ctx[i], ($$.ctx[i] = rest.length ? rest[0] : res))) {
if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) { if (i in $$.bound) $$.bound[i]($$.ctx[i]);
if ($$.bound[i]) $$.bound[i](value); if (ready) {
if (ready) make_dirty(component, i); if (-1 === $$.dirty[0]) {
schedule_update(component);
$$.dirty.fill(0);
}
$$.dirty[(i / 31) | 0] |= 1 << i % 31;
}
} }
return ret; return res;
}) })
: []; : [];

@ -1,103 +1,42 @@
import { identity as linear, noop } from './utils'; import { run_transition } from './transitions';
import { now } from './environment'; import { noop } from './environment';
import { loop } from './loop'; import { methodify } from './utils';
import { create_rule, delete_rule } from './style_manager'; import { CssTransitionConfig } from 'svelte/transition';
import { AnimationConfig } from '../animate';
type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => CssTransitionConfig;
//todo: documentation says it is DOMRect, but in IE it would be ClientRect export const run_animation = /*#__PURE__*/ methodify(
type PositionRect = DOMRect|ClientRect; function run_animation(this: HTMLElement, from: DOMRect, fn: AnimationFn, params = {}) {
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; if (!from) return noop;
return run_transition(
const to = node.getBoundingClientRect(); this,
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return noop; (_, params) => {
const to = this.getBoundingClientRect();
if (from.left !== to.left || from.right !== to.right || from.top !== to.top || from.bottom !== to.bottom) {
const { return fn(this, { from, to }, params);
delay = 0, } else return null;
duration = 300, },
easing = linear, 9,
// @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation? params
start: start_time = now() + delay, );
// @ts-ignore todo: }
end = start_time + duration, );
tick = noop,
css export const fix_position = /*#__PURE__*/ methodify(
} = fn(node, { from, to }, params); function fix_position(this: HTMLElement, { left, top }: DOMRect) {
const { position, width, height, transform } = getComputedStyle(this);
let running = true; if (position === 'absolute' || position === 'fixed') return noop;
let started = false; const { position: og_position, width: og_width, height: og_height } = this.style;
let name; this.style.position = 'absolute';
this.style.width = width;
function start() { this.style.height = height;
if (css) { const b = this.getBoundingClientRect();
name = create_rule(node, 0, 1, duration, delay, easing, css); this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`;
} return () => {
this.style.position = og_position;
if (!delay) { this.style.width = og_width;
started = true; this.style.height = og_height;
} this.style.transform = ''; // unsafe
} };
}
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);
}
}
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)`;
}
}

@ -1,5 +1,5 @@
import { is_promise } from './utils'; 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 { flush } from './scheduler';
import { get_current_component, set_current_component } from './lifecycle'; import { get_current_component, set_current_component } from './lifecycle';
@ -26,11 +26,11 @@ export function handle_promise(promise, info) {
if (info.blocks) { if (info.blocks) {
info.blocks.forEach((block, i) => { info.blocks.forEach((block, i) => {
if (i !== index && block) { if (i !== index && block) {
group_outros(); group_transition_out((transition_out) => {
transition_out(block, 1, 1, () => { transition_out(block, () => {
info.blocks[i] = null; info.blocks[i] = null;
}); });
check_outros(); });
} }
}); });
} else { } else {

@ -1,5 +1,6 @@
import { custom_event, append, insert, detach, listen, attr } from './dom'; import { custom_event, append, insert, detach, listen, attr } from './dom';
import { SvelteComponent } from './Component'; import { SvelteComponent } from './Component';
import { has_Symbol } from './environment';
export function dispatch_dev<T=any>(type: string, detail?: T) { export function dispatch_dev<T=any>(type: string, detail?: T) {
document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); 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) { export function validate_each_argument(arg) {
if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) {
let msg = '{#each} only iterates over array-like objects.'; 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.'; msg += ' You can use a spread to convert this iterable into an array.';
} }
throw new Error(msg); throw new Error(msg);

@ -1,4 +1,5 @@
import { has_prop } from "./utils"; import { has_prop } from "./utils";
import { is_cors } from "./environment";
export function append(target: Node, node: Node) { export function append(target: Node, node: Node) {
target.appendChild(node); target.appendChild(node);
@ -234,26 +235,6 @@ export function select_multiple_value(select) {
return [].map.call(select.querySelectorAll(':checked'), option => option.__value); 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) { export function add_resize_listener(node: HTMLElement, fn: () => void) {
const computed_style = getComputedStyle(node); const computed_style = getComputedStyle(node);
const z_index = (parseInt(computed_style.zIndex) || 0) - 1; 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.setAttribute('aria-hidden', 'true');
iframe.tabIndex = -1; iframe.tabIndex = -1;
const crossorigin = is_crossorigin();
let unsubscribe: () => void; let unsubscribe: () => void;
if (crossorigin) { if (is_cors) {
iframe.src = `data:text/html,<script>onresize=function(){parent.postMessage(0,'*')}</script>`; iframe.src = `data:text/html,<script>onresize=function(){parent.postMessage(0,'*')}</script>`;
unsubscribe = listen(window, 'message', (event: MessageEvent) => { unsubscribe = listen(window, 'message', (event: MessageEvent) => {
if (event.source === iframe.contentWindow) fn(); if (event.source === iframe.contentWindow) fn();
@ -289,7 +268,7 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) {
append(node, iframe); append(node, iframe);
return () => { return () => {
if (crossorigin) { if (is_cors) {
unsubscribe(); unsubscribe();
} else if (unsubscribe && iframe.contentWindow) { } else if (unsubscribe && iframe.contentWindow) {
unsubscribe(); unsubscribe();

@ -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'; /* tests only */
export const set_now = (v) => void (now = v);
export let now: () => number = is_client export const set_raf = (fn) => void (raf = fn);
? () => window.performance.now() export const set_framerate = (v) => void (framerate = v);
: () => 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;
}

@ -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;

@ -2,7 +2,6 @@ export * from './animations';
export * from './await_block'; export * from './await_block';
export * from './dom'; export * from './dom';
export * from './environment'; export * from './environment';
export * from './globals';
export * from './keyed_each'; export * from './keyed_each';
export * from './lifecycle'; export * from './lifecycle';
export * from './loop'; export * from './loop';

@ -1,27 +1,18 @@
import { transition_in, transition_out } from './transitions'; import { transition_in } from './transitions';
export const update_keyed_each = (
export function destroy_block(block, lookup) { old_blocks,
block.d(1); dirty,
lookup.delete(block.key); ctx,
} state,
get_key,
export function outro_and_destroy_block(block, lookup) { list,
transition_out(block, 1, 1, () => { lookup,
lookup.delete(block.key); node,
}); create_each_block,
} next,
get_context,
export function fix_and_destroy_block(block, lookup) { transition_out?
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) {
let o = old_blocks.length; let o = old_blocks.length;
let n = list.length; let n = list.length;
@ -42,11 +33,11 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
if (!block) { if (!block) {
block = create_each_block(key, child_ctx); block = create_each_block(key, child_ctx);
block.c(); block.c();
} else if (dynamic) { } else if (state & 1) {
block.p(child_ctx, dirty); 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])); 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 will_move = new Set();
const did_move = new Set(); const did_move = new Set();
function insert(block) { const insert = (block) => {
transition_in(block, 1); transition_in(block, 1);
block.m(node, next); block.m(node, next, lookup.has(block.key));
lookup.set(block.key, block); lookup.set(block.key, block);
next = block.first; next = block.first;
n--; 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) { while (o && n) {
const new_block = new_blocks[n - 1]; 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; next = new_block.first;
o--; o--;
n--; n--;
} } else if (!new_lookup.has(old_key)) {
else if (!new_lookup.has(old_key)) {
// remove old block // remove old block
destroy(old_block, lookup); destroy(old_block);
o--; 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); insert(new_block);
} } else if (did_move.has(old_key)) {
else if (did_move.has(old_key)) {
o--; o--;
} else if (deltas.get(new_key) > deltas.get(old_key)) { } else if (deltas.get(new_key) > deltas.get(old_key)) {
did_move.add(new_key); did_move.add(new_key);
insert(new_block); insert(new_block);
} else { } else {
will_move.add(old_key); will_move.add(old_key);
o--; o--;
@ -100,21 +88,10 @@ export function update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list
while (o--) { while (o--) {
const old_block = old_blocks[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]); while (n) insert(new_blocks[n - 1]);
return new_blocks; 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);
}
}

@ -2,9 +2,7 @@ import { custom_event } from './dom';
export let current_component; export let current_component;
export function set_current_component(component) { export const set_current_component = (component) => (current_component = component);
current_component = component;
}
export function get_current_component() { export function get_current_component() {
if (!current_component) throw new Error(`Function called outside component initialization`); if (!current_component) throw new Error(`Function called outside component initialization`);

@ -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<void> } let i = 0,
j = 0,
n = 0,
v : TaskCallback;
type TaskCallback = (now: number) => boolean | void; let running_frame : Array<TaskCallback> = [],
type TaskEntry = { c: TaskCallback; f: () => void }; next_frame : Array<TaskCallback> = [];
const tasks = new Set<TaskEntry>(); 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<TimeoutTask> = [],
timed_tasks : Array<TimeoutTask> = [];
function run_tasks(now: number) { let pending_inserts = false,
tasks.forEach(task => { running_timed = false;
if (!task.c(now)) {
tasks.delete(task); const run_timed = (now: number) => {
task.f(); 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;
};
if (tasks.size !== 0) raf(run_tasks); export const loop = (fn) => {
} let running = true;
if (0 === n) raf(run);
next_frame[n++] = (t) => !running || fn(t);
return () => void (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);
} 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() { export const setTweenTimeout = (
tasks.clear(); 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 * Calls function every frame with time elapsed in seconds
* until it returns a falsy value or is aborted
*/ */
export function loop(callback: TaskCallback): Task { export const onEachFrame = (
let task: TaskEntry; callback: (seconds_elapsed: number) => boolean,
on_stop?,
if (tasks.size === 0) raf(run_tasks); 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 { /** tests only */
promise: new Promise(fulfill => { export const clear_loops = () =>
tasks.add(task = { c: callback, f: fulfill }); void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false));
}),
abort() {
tasks.delete(task);
}
};
}

@ -1,89 +1,106 @@
import { run_all } from './utils';
import { set_current_component } from './lifecycle'; import { set_current_component } from './lifecycle';
import { resolved_promise, now } from './environment';
import { T$$ } from './Component';
export const dirty_components = []; let update_scheduled = false;
export const intros = { enabled: false }; let is_flushing = false;
const dirty_components = [];
// todo : remove binding_callbacks export
export const binding_callbacks = []; export const binding_callbacks = [];
const render_callbacks = []; const render_callbacks = [];
const measure_callbacks = [];
const flush_callbacks = []; const flush_callbacks = [];
const resolved_promise = Promise.resolve(); // todo : remove add_flush_callback
let update_scheduled = false; 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) { if (!update_scheduled) {
update_scheduled = true; update_scheduled = true;
resolved_promise.then(flush); resolved_promise.then(flush);
} }
}
export function tick() {
schedule_update();
return resolved_promise; return resolved_promise;
} };
export const flush = () => {
export function add_render_callback(fn) { if (is_flushing) return;
render_callbacks.push(fn); else is_flushing = true;
}
let i = 0,
export function add_flush_callback(fn) { j = 0,
flush_callbacks.push(fn); t = 0,
} $$: T$$,
dirty,
let flushing = false; before_update,
const seen_callbacks = new Set(); after_update;
export function flush() {
if (flushing) return;
flushing = true;
do { do {
// first, call beforeUpdate functions while (i < dirty_components.length) {
// and update components ({ $$ } = set_current_component(dirty_components[i]));
for (let i = 0; i < dirty_components.length; i += 1) {
const component = dirty_components[i];
set_current_component(component);
update(component.$$);
}
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 /* run beforeUpdate */
// afterUpdate functions. This may cause for (j = 0, { before_update } = $$; j < before_update.length; j++) {
// subsequent updates... before_update[j]();
for (let i = 0; i < render_callbacks.length; i += 1) { }
const callback = render_callbacks[i];
if (!seen_callbacks.has(callback)) { /* update blocks */
// ...so guard against infinite loops ({ dirty } = $$).dirty = [-1];
seen_callbacks.add(callback); 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); } while (dirty_components.length);
seen_render_callbacks.clear();
update_scheduled = false;
while (flush_callbacks.length) { // measurement callbacks for animations
flush_callbacks.pop()(); 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; // apply styles
flushing = false; // todo : remove every non style callback from flush_callbacks
seen_callbacks.clear(); for (t = now(); i < j; i++) flush_callbacks[i](t);
} flush_callbacks.length = i = j = 0;
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); is_flushing = false;
} };
}

@ -1,74 +1,62 @@
import { element } from './dom'; import { framerate } from './environment';
import { raf } from './environment';
let documents_uid = 0;
interface ExtendedDoc extends Document { let running_animations = 0;
__svelte_stylesheet: CSSStyleSheet;
__svelte_rules: Record<string, true>; const document_uid = new Map();
} const document_stylesheets = new Map();
const active_docs = new Set<ExtendedDoc>(); const current_rules = new Set();
let active = 0; export const animate_css = /*#__PURE__*/ Function.prototype.call.bind(function animate_css(
this: HTMLElement,
// https://github.com/darkskyapp/string-hash/blob/master/index.js css: (t: number) => string,
function hash(str: string) { duration: number,
let hash = 5381; delay = 0
let i = str.length; ) {
if (!document_uid.has(this.ownerDocument)) {
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); document_uid.set(this.ownerDocument, documents_uid++);
return hash >>> 0; document_stylesheets.set(
} this.ownerDocument,
this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet
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`;
} }
let rule = '{\n';
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`;
const name = `__svelte_${hash(rule)}_${uid}`; rule += `100% {${css(1)}}\n}`;
const doc = node.ownerDocument as ExtendedDoc;
active_docs.add(doc); // darkskyapp/string-hash
const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet); let i = rule.length, hash = 5381;
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {}); while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i);
const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`;
if (!current_rules[name]) {
current_rules[name] = true; if (!current_rules.has(name)) {
current_rules.add(name);
const stylesheet = document_stylesheets.get(this.ownerDocument);
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length); stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
} }
const animation = node.style.animation || ''; const previous = this.style.animation;
node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`; this.style.animation = `${
previous ? `${previous}, ` : ''
active += 1; }${duration}ms linear ${delay}ms 1 normal both running ${name}`;
return name;
}
export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) { running_animations++;
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() { return () => {
raf(() => { const prev = (this.style.animation || '').split(', ');
if (active) return; const next = prev.filter((anim) => !anim.includes(name));
active_docs.forEach(doc => { if (prev.length !== next.length) this.style.animation = next.join(', ');
const stylesheet = doc.__svelte_stylesheet; if (--running_animations === 0) {
document_stylesheets.forEach((stylesheet) => {
let i = stylesheet.cssRules.length; let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i); while (i--) stylesheet.deleteRule(i);
doc.__svelte_rules = {};
}); });
active_docs.clear(); current_rules.clear();
}); if (1 !== documents_uid) {
} document_stylesheets.clear();
document_uid.clear();
documents_uid = 0;
}
}
};
});

@ -1,353 +1,203 @@
import { identity as linear, is_function, noop, run_all } from './utils'; import { CssTransitionConfig } from '../transition';
import { now } from "./environment"; import { Fragment } from './Component';
import { loop } from './loop';
import { create_rule, delete_rule } from './style_manager';
import { custom_event } from './dom'; import { custom_event } from './dom';
import { add_render_callback } from './scheduler'; import { now, noop } from './environment';
import { TransitionConfig } from '../transition'; import { setFrameTimeout, setTweenTimeout } from './loop';
import { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager';
import { linear } from 'svelte/easing';
let promise: Promise<void>|null; type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig;
export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void;
function wait() { export const transition_in = (block: Fragment, local?) => {
if (!promise) { if (!block || !block.i) return;
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}`));
}
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); outroing.delete(block);
block.i(local); block.i(local);
} };
}
export function transition_out(block, local: 0 | 1, detach: 0 | 1, callback) { export const transition_out = (block: Fragment, local?) => {
if (block && block.o) { if (!block || !block.o || outroing.has(block)) return;
if (outroing.has(block)) return;
outroing.add(block); outroing.add(block);
block.o(local);
outros.c.push(() => { };
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();
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);
c.push((cancelled = false) => {
if (cancelled) {
// block was destroyed before outro ended
outroing.delete(block);
} else if (outroing.has(block)) {
outroing.delete(block); outroing.delete(block);
if (callback) {
if (detach) block.d(1); if (detach) block.d(1);
callback(); callback();
} }
}); });
block.o(1);
block.o(local);
}
}
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;
}
if (now >= start_time) {
const t = easing((now - start_time) / duration);
tick(t, 1 - t);
}
}
return running;
}); });
} if (!current_group.r) for (let i = 0; i < c.length; i++) c[i]();
transition_group = transition_group.p;
let started = false; };
return { const swap = (fn, rx) =>
start() { fn.length === 1
if (started) return; ? rx & tx.intro
? fn
delete_rule(node); : (t) => fn(1 - t)
: rx & tx.intro
if (is_function(config)) { ? (t) => fn(t, 1 - t)
config = config(); : (t) => fn(1 - t, t);
wait().then(go);
} else { const mirrored = (fn, rx, easing) => {
go(); const run = swap(fn, rx);
} return easing
}, ? rx & tx.intro
? (t) => run(easing(t))
invalidate() { : (t) => run(1 - easing(1 - t))
started = false; : run;
}, };
const reversed = (fn, rx, easing, start = 0, end = 1) => {
end() { const run = swap(fn, rx);
if (running) { const difference = end - start;
cleanup(); return easing
running = false; ? (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;
export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
let config = fn(node, params);
let running = true; let running = true;
let animation_name;
const group = outros;
group.r += 1; let cancel_css,
cancel_raf;
function go() { let start_time = 0,
const { end_time = 0;
delay = 0,
duration = 300,
easing = linear,
tick = noop,
css
} = config || null_transition;
if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css); const current_group = transition_group;
if (rx & tx.outro) current_group.r++;
const start_time = now() + delay; add_measure_callback(() => {
const end_time = start_time + duration; if (null === (config = fn(this, params))) return noop;
return (current_frame_time) => {
if (false === running) return;
add_render_callback(() => dispatch(node, false, 'start')); let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: CssTransitionConfig =
'function' === typeof config ? (config = config()) : config;
loop(now => { const solver = 'reverse' === strategy ? reversed : mirrored;
if (running) { const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1);
if (now >= end_time) {
tick(0, 1);
dispatch(node, false, 'end'); if (rx & tx.bidirectional) {
if (-1 !== delay_left) delay = delay_left;
if (!--group.r) { if (solver === reversed) duration -= elapsed_duration;
// this will result in `end()` being called, else if (solver === mirrored) delay -= elapsed_duration;
// so we don't need to clean up here
run_all(group.c);
} }
return false; end_time = (start_time = current_frame_time + delay) + duration;
}
if (now >= start_time) { if (0 === (rx & tx.animation)) {
const t = easing((now - start_time) / duration); this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`));
tick(1 - t, t);
}
} }
return running; if (css) cancel_css = animate_css(this, runner(css), duration, delay);
});
}
if (is_function(config)) { if (rx & tx.outro) {
wait().then(() => { if (current_group.s.push(stop) === current_group.r) {
// @ts-ignore setFrameTimeout((t) => {
config = config(); for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t);
go(); }, Math.max(end_time, current_group.t));
});
} else { } else {
go(); current_group.t = Math.max(end_time, current_group.t);
}
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) { const stop: StopResetReverseFn = (t?: number | 1 | -1) => {
let config = fn(node, params); // resetting `out:` in intros
if (t === 1 && rx & tx.outro && 0 === (rx & tx.bidirectional) && 'tick' in config) config.tick(1, 0);
let t = intro ? 0 : 1; if (false === running) return;
else running = false;
let running_program = null; if (cancel_css) cancel_css();
let pending_program = null; if (cancel_raf) cancel_raf(rx & tx.outro && t >= end_time);
let animation_name = null;
function clear_animation() { if (rx & tx.animation) return;
if (animation_name) delete_rule(node, animation_name);
}
function init(program, duration) { if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
const d = program.b - t;
duration *= Math.abs(d);
return { if (rx & tx.outro && !--current_group.r)
a: t, for (let i = 0; i < current_group.c.length; i++) current_group.c[i](t === void 0);
b: program.b,
d,
duration,
start: program.start,
end: program.start + duration,
group: program.group
};
}
function go(b) { if (0 === (rx & tx.bidirectional)) return;
const {
delay = 0,
duration = 300,
easing = linear,
tick = noop,
css
} = config || null_transition;
const program = { if (-1 === t)
start: now() + delay, return (
b (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);
}; };
if (!b) { return stop;
// @ts-ignore todo: improve typings });
program.group = outros;
outros.r += 1; const running_bidi: Map<HTMLElement, StopResetReverseFn> = new Map();
} export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional(
this: HTMLElement,
if (running_program) { fn: TransitionFn,
pending_program = program; rx: tx.intro | tx.outro,
} else { params: any
// if this is an intro, and there's a delay, we need to do ) {
// an initial tick and/or apply CSS animation immediately let cancel;
if (css) { running_bidi.set(
clear_animation(); this,
animation_name = create_rule(node, t, b, duration, delay, easing, css); (cancel =
} (running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params))
);
if (b) tick(0, 1); return cancel;
});
running_program = init(program, duration); export const run_duration = (duration, value1, value2?): number =>
add_render_callback(() => dispatch(node, b, 'start')); typeof duration === 'function' ? duration(value1, value2) : duration;
loop(now => {
if (pending_program && now > pending_program.start) {
running_program = init(pending_program, duration);
pending_program = null;
dispatch(node, running_program.b, 'start');
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);
}
}
return !!(running_program || pending_program);
});
}
}
return {
run(b) {
if (is_function(config)) {
wait().then(() => {
// @ts-ignore
config = config();
go(b);
});
} else {
go(b);
}
},
end() {
clear_animation();
running_program = pending_program = null;
}
};
}

@ -1,4 +1,4 @@
export function noop() {} import { noop } from "./environment";
export const identity = x => x; export const identity = x => x;
@ -147,3 +147,8 @@ export const has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj,
export function action_destroyer(action_result) { export function action_destroyer(action_result) {
return action_result && is_function(action_result.destroy) ? action_result.destroy : noop; return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;
} }
export const methodify = /*#__PURE__*/ (function() {
const call = Function.prototype.call;
return call.bind.bind(call);
})();

@ -1,110 +1,60 @@
import { cubicOut, cubicInOut, linear } from 'svelte/easing'; import { cubicOut, cubicInOut } from 'svelte/easing';
import { assign, is_function } from 'svelte/internal'; import { run_duration } from 'svelte/internal';
type EasingFunction = (t: number) => number; interface CssAnimationConfig {
export interface TransitionConfig {
delay?: number; delay?: number;
duration?: number; duration?: number;
easing?: EasingFunction; easing?: (t: number) => number;
css?: (t: number, u: number) => string; strategy?: 'reverse' | 'mirror';
tick?: (t: number, u: number) => void;
} }
interface BlurParams { export interface CssTransitionConfig extends CssAnimationConfig {
delay: number; css?: (t: number, u?: number) => string;
duration: number; tick?: (t: number, u?: number) => void;
easing?: EasingFunction;
amount: number;
opacity: number;
} }
export function blur(node: Element, { type FlyParams = FadingConfig & { x: number; y: number; };
delay = 0, type BlurParams = FadingConfig & { amount: number; };
duration = 400, type ScaleParams = FadingConfig & { start: number; };
easing = cubicInOut, type DrawParams = CssAnimationConfig & { speed : number };
amount = 5, type FadingConfig = CssAnimationConfig & { opacity: number; };
opacity = 0 type MarkedCrossFadeConfig = TimeableConfig & { key: any; };
}: BlurParams): TransitionConfig { export type TimeableConfig = Omit<CssAnimationConfig, 'duration'> & { duration?: number | ((len: number) => number) };
type CrossFadeConfig = TimeableConfig & { fallback(node: Element, params: TimeableConfig, intro: boolean): CssTransitionConfig; };
type ElementMap = Map<any, Element>;
export function blur(node: Element, { delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 }: BlurParams): CssTransitionConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const target_opacity = +style.opacity; const target_opacity = +style.opacity;
const f = style.filter === 'none' ? '' : style.filter; const f = style.filter === 'none' ? '' : style.filter;
const od = target_opacity * (1 - opacity); const od = target_opacity * (1 - opacity);
return { return {
delay, delay,
duration, duration,
easing, 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 { export function fade(node: Element, { delay = 0, duration = 400, easing }: CssAnimationConfig): CssTransitionConfig {
delay: number;
duration: number;
easing: EasingFunction;
}
export function fade(node: Element, {
delay = 0,
duration = 400,
easing = linear
}: FadeParams): TransitionConfig {
const o = +getComputedStyle(node).opacity; const o = +getComputedStyle(node).opacity;
return { delay, duration, easing, css: (t) => `opacity: ${t * o};` };
return {
delay,
duration,
easing,
css: t => `opacity: ${t * o}`
};
} }
interface FlyParams { export function fly(node: Element, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 }: FlyParams ): CssTransitionConfig {
delay: number;
duration: number;
easing: EasingFunction;
x: number;
y: number;
opacity: number;
}
export function fly(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut,
x = 0,
y = 0,
opacity = 0
}: FlyParams): TransitionConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const target_opacity = +style.opacity; 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); const od = target_opacity * (1 - opacity);
return { return {
delay, delay,
duration, duration,
easing, easing,
css: (t, u) => ` css: (_t, u) => `transform: ${prev} translate(${u * x}px, ${u * y}px); opacity: ${target_opacity - od * u};`,
transform: ${transform} translate(${(1 - t) * x}px, ${(1 - t) * y}px);
opacity: ${target_opacity - (od * u)}`
}; };
} }
interface SlideParams { export function slide(node: Element, { delay = 0, duration = 400, easing = cubicOut }: CssAnimationConfig): CssTransitionConfig {
delay: number;
duration: number;
easing: EasingFunction;
}
export function slide(node: Element, {
delay = 0,
duration = 400,
easing = cubicOut
}: SlideParams): TransitionConfig {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const opacity = +style.opacity; const opacity = +style.opacity;
const height = parseFloat(style.height); const height = parseFloat(style.height);
@ -114,159 +64,91 @@ export function slide(node: Element, {
const margin_bottom = parseFloat(style.marginBottom); const margin_bottom = parseFloat(style.marginBottom);
const border_top_width = parseFloat(style.borderTopWidth); const border_top_width = parseFloat(style.borderTopWidth);
const border_bottom_width = parseFloat(style.borderBottomWidth); const border_bottom_width = parseFloat(style.borderBottomWidth);
return { return {
delay, delay,
duration, duration,
easing, easing,
css: t => css: (t) => `
`overflow: hidden;` + overflow: hidden;
`opacity: ${Math.min(t * 20, 1) * opacity};` + opacity: ${Math.min(t * 20, 1) * opacity};
`height: ${t * height}px;` + height: ${t * height}px;
`padding-top: ${t * padding_top}px;` + padding-top: ${t * padding_top}px;
`padding-bottom: ${t * padding_bottom}px;` + padding-bottom: ${t * padding_bottom}px;
`margin-top: ${t * margin_top}px;` + margin-top: ${t * margin_top}px;
`margin-bottom: ${t * margin_bottom}px;` + margin-bottom: ${t * margin_bottom}px;
`border-top-width: ${t * border_top_width}px;` + border-top-width: ${t * border_top_width}px;
`border-bottom-width: ${t * border_bottom_width}px;` border-bottom-width: ${t * border_bottom_width}px;`,
}; };
} }
interface ScaleParams { export function scale(node: Element, { delay = 0, duration = 400, easing = cubicOut, start = 0, opacity = 0 }: ScaleParams): CssTransitionConfig {
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 {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const target_opacity = +style.opacity; const target_opacity = +style.opacity;
const transform = style.transform === 'none' ? '' : style.transform; const transform = style.transform === 'none' ? '' : style.transform;
const sd = 1 - start; const sd = 1 - start;
const od = target_opacity * (1 - opacity); const od = target_opacity * (1 - opacity);
return { return {
delay, delay,
duration, duration,
easing, easing,
css: (_t, u) => ` css: (_t, u) => `transform: ${transform} scale(${1 - sd * u}); opacity: ${target_opacity - od * 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 }, { export function draw(node: SVGPathElement | SVGGeometryElement, { delay = 0, speed, duration, easing = cubicInOut }: DrawParams): CssTransitionConfig {
delay = 0,
speed,
duration,
easing = cubicInOut
}: DrawParams): TransitionConfig {
const len = node.getTotalLength(); const len = node.getTotalLength();
if (duration === undefined) duration = speed ? len / speed : 800;
if (duration === undefined) { else duration = run_duration(duration, len);
if (speed === undefined) { return { delay, duration, easing, css: (t, u) => `stroke-dasharray: ${t * len} ${u * len};` };
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}`
};
}
interface CrossfadeParams {
delay: number;
duration: number | ((len: number) => number);
easing: EasingFunction;
} }
type ClientRectMap = Map<any, { rect: ClientRect }>; 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();
export function crossfade({ fallback, ...defaults }: CrossfadeParams & { const b: ElementMap = new Map();
fallback: (node: Element, params: CrossfadeParams, intro: boolean) => TransitionConfig;
}) {
const to_receive: ClientRectMap = new Map();
const to_send: ClientRectMap = 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 dx = from.left - to.left;
const dy = from.top - to.top; const dy = from.top - to.top;
const dw = from.width / to.width; const dw = from.width / to.width;
const dh = from.height / to.height; const dh = from.height / to.height;
const d = Math.sqrt(dx * dx + dy * dy); const { transform, opacity } = getComputedStyle(to_node);
const op = +opacity;
const style = getComputedStyle(node); const prev = transform === 'none' ? '' : transform;
const transform = style.transform === 'none' ? '' : style.transform;
const opacity = +style.opacity;
return { return {
delay, delay,
duration: is_function(duration) ? duration(d) : duration,
easing, easing,
duration: run_duration(duration, Math.sqrt(dx * dx + dy * dy)),
css: (t, u) => ` css: (t, u) => `
opacity: ${t * opacity}; opacity: ${t * op};
transform-origin: top left; transform-origin: top left;
transform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1-t) * dw}, ${t + (1-t) * dh}); transform: ${prev} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh});
` `,
} as CssTransitionConfig;
}; };
}
function transition(items: ClientRectMap, counterparts: ClientRectMap, intro: boolean) {
return (node: Element, params: CrossfadeParams & { key: any }) => {
items.set(params.key, {
rect: node.getBoundingClientRect()
});
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 () => { return () => {
if (counterparts.has(params.key)) { if (b.has(key)) {
const { rect } = counterparts.get(params.key); const from_node = b.get(key);
counterparts.delete(params.key); b.delete(key);
return crossfade(from_node, node, params);
return crossfade(rect, 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 [ return [transition(b, a, false), transition(a, b, true)];
transition(to_send, to_receive, false),
transition(to_receive, to_send, true)
];
} }

Loading…
Cancel
Save