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

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

@ -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) {

@ -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<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) {
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 {

@ -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}) {

@ -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 { 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);`
};

@ -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;
})
: [];

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

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

@ -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<T=any>(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);

@ -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,<script>onresize=function(){parent.postMessage(0,'*')}</script>`;
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();

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

@ -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 './dom';
export * from './environment';
export * from './globals';
export * from './keyed_each';
export * from './lifecycle';
export * from './loop';

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

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

@ -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;
type TaskEntry = { c: TaskCallback; f: () => void };
let running_frame : Array<TaskCallback> = [],
next_frame : Array<TaskCallback> = [];
const tasks = new Set<TaskEntry>();
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<TimeoutTask> = [],
timed_tasks : Array<TimeoutTask> = [];
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));

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

@ -1,74 +1,62 @@
import { element } from './dom';
import { raf } from './environment';
interface ExtendedDoc extends Document {
__svelte_stylesheet: CSSStyleSheet;
__svelte_rules: Record<string, true>;
}
const active_docs = new Set<ExtendedDoc>();
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;
}
}
};
});

@ -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<void>|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;
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);
let running = true;
dispatch(node, true, 'end');
let cancel_css,
cancel_raf;
cleanup();
return running = false;
}
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) animation_name = create_rule(node, 1, 0, duration, delay, easing, css);
if (css) cancel_css = animate_css(this, runner(css), duration, delay);
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<HTMLElement, StopResetReverseFn> = 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;

@ -1,4 +1,4 @@
export function noop() {}
import { noop } from "./environment";
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) {
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 { 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<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 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}`
};
return { delay, duration, easing, css: (t) => `opacity: ${t * o};` };
}
interface FlyParams {
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 {
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}`
};
}
interface CrossfadeParams {
delay: number;
duration: number | ((len: number) => number);
easing: EasingFunction;
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};` };
}
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();
const b: ElementMap = new 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();
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)];
}

Loading…
Cancel
Save