pull/4742/head
pushkine 5 years ago
parent a4b86fb02d
commit 82d17c5a6f

2
.gitignore vendored

@ -10,7 +10,7 @@ node_modules
/animate /animate
/dev /dev
/easing /easing
/environment /environment/
/internal /internal
/interpolate /interpolate
/motion /motion

@ -68,7 +68,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
} }
export default function compile(source: string, options: CompileOptions = {}) { export default function compile(source: string, options: CompileOptions = {}) {
options = { generate: 'dom', dev: null, ...options }; options = { generate: 'dom', dev: null, version: 3, ...options };
const stats = new Stats(); const stats = new Stats();
const warnings = []; const warnings = [];

@ -436,27 +436,29 @@ export default class EachBlockWrapper extends Wrapper {
// We declare `i` as block scoped here, as the `remove_old_blocks` code // We declare `i` as block scoped here, as the `remove_old_blocks` code
// may rely on continuing where this iteration stopped. // may rely on continuing where this iteration stopped.
this.updates.push(b` this.updates.push(b`
${!has_update_method && b`const #old_length = ${each_block_value}.length;`} ${!has_update_method && b`const #old_length = ${each_block}.length;`}
${each_block_value} = ${snippet}; ${each_block_value} = ${snippet};
${__DEV__ && b`@validate_each_argument(${each_block_value});`} ${__DEV__ && b`@validate_each_argument(${each_block_value});`}
let #i; let #i = ${start}, #block;
${for_each_block( ${for_loop(
(block, index) => b` each_block_value,
(_, index) => b`
#block = ${each_block}[${index}]
const #child_ctx = ${each_context_getter}(#ctx, ${each_block_value}, ${index}); const #child_ctx = ${each_context_getter}(#ctx, ${each_block_value}, ${index});
${$if({ ${$if({
if: (has_update_method || has_transitions) && block, if: (has_update_method || has_transitions) && x`#block`,
true: b` true: b`
${has_update_method && b`${block}.p(#child_ctx, #dirty);`} ${has_update_method && b`#block.p(#child_ctx, #dirty);`}
${has_transitions && b`@transition_in(${block}, 1);`} ${has_transitions && b`@transition_in(#block, 1);`}
`, `,
false: b` false: b`
${block} = ${create_each_block}(#child_ctx); #block = ${each_block}[${index}] = ${create_each_block}(#child_ctx);
${block}.c(); #block.c();
${has_transitions && b`@transition_in(${block}, 1);`} ${has_transitions && b`@transition_in(#block, 1);`}
${block}.m(${update_mount_node}, ${update_anchor_node}); #block.m(${update_mount_node}, ${update_anchor_node});
`, `,
})}`, })}`,
{ i: start } { i: null }
)} )}
${this.block.group_transition_out((transition_out) => ${this.block.group_transition_out((transition_out) =>
for_each_block( for_each_block(
@ -488,7 +490,7 @@ const for_loop = <T>(
callback: (item: Node, index: Node, array: T) => Node[], callback: (item: Node, index: Node, array: T) => Node[],
{ length = x`${arr}.length`, i = undefined } = {} { length = x`${arr}.length`, i = undefined } = {}
) => ) =>
i || i === null i !== undefined
? b`for (${i}; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }` ? b`for (${i}; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }`
: b`for (let #i = 0; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }`; : b`for (let #i = 0; #i < ${length}; #i++) { ${callback(x`${arr}[#i]`, x`#i`, arr)} }`;
@ -504,7 +506,7 @@ const $if = ({ if: condition, true: success, false: failure = null }) => {
return b`if(!${condition}){ ${success} }`; return b`if(!${condition}){ ${success} }`;
} }
} else { } else {
if (!failure) { if (failure) {
return failure; return failure;
} }
} }

@ -210,7 +210,7 @@ export default class AttributeWrapper {
if (scoped_css && rendered.length === 2) { if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined} // we have a situation like class={possiblyUndefined}
rendered[0] = x`${rendered[0]} ?? ""`; rendered[0] = x`null != ${rendered[0]} ? ${rendered[0]} : ""`;
} }
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`); return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -360,7 +360,7 @@ export default class ElementWrapper extends Wrapper {
} }
} }
if (this.node.animation) { if (this.node.animation) {
this.add_animation(block, intro, outro); 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);
@ -677,8 +677,8 @@ export default class ElementWrapper extends Wrapper {
const fn = this.renderer.reference(intro.name); const fn = this.renderer.reference(intro.name);
let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, true, ${snippet});`; let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 1, ${snippet});`;
let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, false, ${snippet});`; let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, 2, ${snippet});`;
if (intro.is_local) { if (intro.is_local) {
intro_block = b`if (#local) {${intro_block}}`; intro_block = b`if (#local) {${intro_block}}`;
@ -687,16 +687,12 @@ export default class ElementWrapper extends Wrapper {
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}();`); block.chunks.destroy.push(b`if (detaching) ${name}();`);
} }
add_intro(block: Block, intro: Transition, outro: Transition) { add_intro(block: Block, intro: Transition, outro: Transition) {
if (outro) { if (outro) {
const outro_var = block.alias(`${this.var.name}_outro`); const outro_var = block.alias(`${this.var.name}_outro`);
block.chunks.intro.push(b` block.chunks.intro.push(b`${outro_var}();`);
if (${outro_var}){
${outro_var}(1);
}
`);
} }
if (this.node.animation) { if (this.node.animation) {
const [unfreeze_var, rect_var, stop_animation_var, animationFn, params] = run_animation(this, block); const [unfreeze_var, rect_var, stop_animation_var, animationFn, params] = run_animation(this, block);
@ -711,9 +707,9 @@ export default class ElementWrapper extends Wrapper {
if (!intro) return; if (!intro) return;
const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`); const [intro_var, node, transitionFn, params] = run_transition(this, block, intro, `intro`);
block.add_variable(intro_var); block.add_variable(intro_var, x`@noop`);
let start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, true, ${params});`; let start_intro = b`${intro_var} = @run_transition(${node}, ${transitionFn}, 1, ${params});`;
if (intro.is_local) start_intro = b`if (#local) ${start_intro};`; if (intro.is_local) start_intro = b`if (#local) ${start_intro};`;
block.chunks.intro.push(start_intro); block.chunks.intro.push(start_intro);
} }
@ -723,21 +719,21 @@ export default class ElementWrapper extends Wrapper {
add_outro(block: Block, intro: Transition, outro: Transition) { add_outro(block: Block, intro: Transition, outro: Transition) {
if (intro) { if (intro) {
const intro_var = block.alias(`${this.var.name}_intro`); const intro_var = block.alias(`${this.var.name}_intro`);
block.chunks.outro.push(b`if (${intro_var}) ${intro_var}();`); block.chunks.outro.push(b`${intro_var}();`);
} }
if (!outro) return; if (!outro) return;
const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`); const [outro_var, node, transitionFn, params] = run_transition(this, block, outro, `outro`);
block.add_variable(outro_var); block.add_variable(outro_var, x`@noop`);
let start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, false, ${params});`; let start_outro = b`${outro_var} = @run_transition(${node}, ${transitionFn}, 2, ${params});`;
if (intro.is_local) start_outro = b`if (#local) ${start_outro};`; if (intro.is_local) start_outro = b`if (#local) ${start_outro};`;
block.chunks.outro.push(start_outro); block.chunks.outro.push(start_outro);
block.chunks.destroy.push(b`if (detaching && ${outro_var}) ${outro_var}();`); block.chunks.destroy.push(b`if (detaching) ${outro_var}();`);
} }
add_animation(block: Block, intro: Transition, outro: Transition) { add_animation(block: Block, intro: Transition) {
const intro_var = intro && block.alias(`${this.var.name}_intro`); const intro_var = intro && block.alias(`${this.var.name}_intro`);
const [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block); const [unfreeze_var, rect_var, stop_animation_var, name_var, params_var] = run_animation(this, block);
@ -747,8 +743,8 @@ export default class ElementWrapper extends Wrapper {
block.add_variable(stop_animation_var, x`@noop`); block.add_variable(stop_animation_var, x`@noop`);
block.chunks.measure.push(b` block.chunks.measure.push(b`
if(!${unfreeze_var}) ${rect_var} = ${this.var}.getBoundingClientRect(); ${rect_var} = ${this.var}.getBoundingClientRect();
${intro && b`if(${intro_var}) ${intro_var}();`} ${intro && b`${intro_var}();`}
`); `);
block.chunks.fix.push(b` block.chunks.fix.push(b`

@ -36,21 +36,14 @@ export default class SlotWrapper extends Wrapper {
this.fallback = block.child({ this.fallback = block.child({
comment: create_debugging_comment(this.node.children[0], this.renderer.component), comment: create_debugging_comment(this.node.children[0], this.renderer.component),
name: this.renderer.component.get_unique_name(`fallback_block`), name: this.renderer.component.get_unique_name(`fallback_block`),
type: 'fallback' type: 'fallback',
}); });
renderer.blocks.push(this.fallback); renderer.blocks.push(this.fallback);
} }
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(renderer, this.fallback, node.children, this, strip_whitespace, next_sibling);
renderer,
this.fallback,
node.children,
this,
strip_whitespace,
next_sibling
);
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
add_to_set(this.dependencies, attribute.dependencies); add_to_set(this.dependencies, attribute.dependencies);
}); });
@ -61,11 +54,7 @@ export default class SlotWrapper extends Wrapper {
block.add_outro(); block.add_outro();
} }
render( render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
const { renderer } = this; const { renderer } = this;
const { slot_name } = this.node; const { slot_name } = this.node;
@ -81,20 +70,20 @@ export default class SlotWrapper extends Wrapper {
const dependencies = new Set(); const dependencies = new Set();
this.node.values.forEach(attribute => { this.node.values.forEach((attribute) => {
attribute.chunks.forEach(chunk => { attribute.chunks.forEach((chunk) => {
if ((chunk as Expression).dependencies) { if ((chunk as Expression).dependencies) {
add_to_set(dependencies, (chunk as Expression).contextual_dependencies); add_to_set(dependencies, (chunk as Expression).contextual_dependencies);
// add_to_set(dependencies, (chunk as Expression).dependencies); // add_to_set(dependencies, (chunk as Expression).dependencies);
(chunk as Expression).dependencies.forEach(name => { (chunk as Expression).dependencies.forEach((name) => {
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
if (variable && !variable.hoistable) dependencies.add(name); if (variable && !variable.hoistable) dependencies.add(name);
}); });
} }
}); });
const dynamic_dependencies = Array.from(attribute.dependencies).filter(name => { const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => {
if (this.node.scope.is_let(name)) return true; if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
return is_dynamic(variable); return is_dynamic(variable);
@ -133,14 +122,10 @@ export default class SlotWrapper extends Wrapper {
${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null}
`); `);
block.chunks.create.push( block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
if (renderer.options.hydratable) { if (renderer.options.hydratable) {
block.chunks.claim.push( block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
} }
block.chunks.mount.push(b` block.chunks.mount.push(b`
@ -149,15 +134,11 @@ export default class SlotWrapper extends Wrapper {
} }
`); `);
block.chunks.intro.push( block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`);
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.outro.push( block.chunks.outro.push(b`@transition_out(${slot_or_fallback}, #local);`);
b`@transition_out(${slot_or_fallback}, #local);`
);
const is_dependency_dynamic = name => { const is_dependency_dynamic = (name) => {
if (name === '$$scope') return true; if (name === '$$scope') return true;
if (this.node.scope.is_let(name)) return true; if (this.node.scope.is_let(name)) return true;
const variable = renderer.component.var_lookup.get(name); const variable = renderer.component.var_lookup.get(name);
@ -178,7 +159,10 @@ export default class SlotWrapper extends Wrapper {
); );
} }
`; `;
const fallback_update = has_fallback && fallback_dynamic_dependencies.length > 0 && b` const fallback_update =
has_fallback &&
fallback_dynamic_dependencies.length > 0 &&
b`
if (${slot_or_fallback} && ${slot_or_fallback}.p && ${renderer.dirty(fallback_dynamic_dependencies)}) { if (${slot_or_fallback} && ${slot_or_fallback}.p && ${renderer.dirty(fallback_dynamic_dependencies)}) {
${slot_or_fallback}.p(#ctx, #dirty); ${slot_or_fallback}.p(#ctx, #dirty);
} }
@ -200,8 +184,6 @@ export default class SlotWrapper extends Wrapper {
`); `);
} }
block.chunks.destroy.push( block.chunks.destroy.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`);
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
} }
} }

@ -9,7 +9,7 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
// handle special case — `class={possiblyUndefined}` with scoped CSS // handle special case — `class={possiblyUndefined}` with scoped CSS
if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) { if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
const value = (attribute.chunks[0] as Expression).node; const value = (attribute.chunks[0] as Expression).node;
return x`@escape(${value} ?? "") + "${(attribute.chunks[1] as Text).data}"`; return x`(${x`${value} != null && @escape(${value}) || ""`}) + "${(attribute.chunks[1] as Text).data}"`;
} }
return get_attribute_value(attribute); return get_attribute_value(attribute);

@ -5,7 +5,7 @@ import Block from '../render_dom/Block';
export default function get_slot_data(values: Map<string, Attribute>, block: Block = null) { export default function get_slot_data(values: Map<string, Attribute>, block: Block = null) {
return { return {
type: 'Expression', type: 'ObjectExpression',
properties: Array.from(values.values()) properties: Array.from(values.values())
.filter((attribute) => attribute.name !== 'name') .filter((attribute) => attribute.name !== 'name')
.map((attribute) => { .map((attribute) => {

@ -10,7 +10,7 @@ interface FlipParams {
export function flip( export function flip(
node: Element, node: Element,
animation: { from: DOMRect; to: DOMRect }, animation: { from: DOMRect; to: DOMRect },
{ delay = 0, duration = (d: number) => Math.sqrt(d) * 120, easing = cubicOut }: FlipParams { delay = 0, duration = (d: number) => Math.sqrt(d) * 30, easing = cubicOut }: FlipParams
): AnimationConfig { ): AnimationConfig {
const style = getComputedStyle(node).transform; const style = getComputedStyle(node).transform;
const transform = style === 'none' ? '' : style; const transform = style === 'none' ? '' : style;

@ -55,13 +55,13 @@ export const cubicBezier = (x1: number, y1: number, x2: number, y2: number) => {
); );
const ax = 1.0 - (x2 = 3.0 * (x2 - x1) - (x1 = 3.0 * x1)) - x1, const ax = 1.0 - (x2 = 3.0 * (x2 - x1) - (x1 = 3.0 * x1)) - x1,
ay = 1.0 - (y2 = 3.0 * (y2 - y1) - (y1 = 3.0 * y1)) - y1; ay = 1.0 - (y2 = 3.0 * (y2 - y1) - (y1 = 3.0 * y1)) - y1;
let r = 0.0, let i = 0,
r = 0.0,
s = 0.0, s = 0.0,
d = 0.0, d = 0.0,
x = 0.0; x = 0.0;
return (t: number) => { return (t: number) => {
r = t; for (r = t, i = 0; 32 > i; i++)
for (let i = 0; 32 > i; i++)
if (1e-5 > Math.abs((x = r * r * (r * ax + x1 + x2) - t))) return r * (r * (r * ay + y2) + y1); if (1e-5 > Math.abs((x = r * r * (r * ax + x1 + x2) - t))) return r * (r * (r * ay + y2) + y1);
else if (1e-5 > Math.abs((d = r * (r * ax * 3.0 + x2 * 2.0) + x1))) break; else if (1e-5 > Math.abs((d = r * (r * ax * 3.0 + x2 * 2.0) + x1))) break;
else r = r - x / d; else r = r - x / d;

@ -12,11 +12,13 @@ export const is_cors =
} }
})(); })();
export const has_Symbol = typeof Symbol === 'function'; export const has_Symbol = typeof Symbol === 'function';
declare var global: any;
export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global; export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
export const resolved_promise = Promise.resolve(); export const resolved_promise = Promise.resolve();
export let now = /*#__PURE__*/ is_browser ? performance.now.bind(performance) : Date.now.bind(Date); export let now = /*#__PURE__*/ is_browser ? performance.now.bind(performance) : Date.now.bind(Date);
export let raf = /*#__PURE__*/ is_browser ? requestAnimationFrame : noop; export let raf = /*#__PURE__*/ __TEST__ ? () => {} : is_browser ? requestAnimationFrame : noop;
export let framerate = 1000 / 60; export let framerate = 1000 / 60;
raf((t1) => { raf((t1) => {
raf((d) => { raf((d) => {

@ -11,38 +11,42 @@ export interface AnimationConfig {
type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => AnimationConfig; type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => AnimationConfig;
export function run_animation(node: HTMLElement, from: DOMRect, fn: AnimationFn, params) { export const run_animation = Function.prototype.call.bind(function run_animation(
this: HTMLElement,
from: DOMRect,
fn: AnimationFn,
params = {}
) {
if (!from) return noop; if (!from) return noop;
return run_transition( return run_transition(
node, this,
(node, params) => fn(node, { from, to: node.getBoundingClientRect() }, params), (_, params) => {
true, 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 params
); );
} });
export function fix_position(node: HTMLElement, { left, top }: DOMRect) { export const fix_position = Function.prototype.call.bind(function fix_position(
const { position, width, height, transform } = getComputedStyle(node); this: HTMLElement,
{ left, top }: DOMRect
) {
const { position, width, height, transform } = getComputedStyle(this);
if (position === 'absolute' || position === 'fixed') return noop; if (position === 'absolute' || position === 'fixed') return noop;
const { position: og_position, width: og_width, height: og_height } = node.style; const { position: og_position, width: og_width, height: og_height } = this.style;
node.style.position = 'absolute'; this.style.position = 'absolute';
node.style.width = width; this.style.width = width;
node.style.height = height; this.style.height = height;
const b = node.getBoundingClientRect(); const b = this.getBoundingClientRect();
node.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`; this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`;
return () => { return () => {
node.style.position = og_position; this.style.position = og_position;
node.style.width = og_width; this.style.width = og_width;
node.style.height = og_height; this.style.height = og_height;
node.style.transform = ''; // unsafe this.style.transform = ''; // unsafe
}; };
} });
export function add_transform(node: HTMLElement, a: DOMRect) {
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)`;
}
}

@ -124,7 +124,6 @@ export function dev$element(element: Element | Node | EventTarget, event: keyof
} }
} }
export function dev$block(event: keyof BlockEventsMap, payload) {} export function dev$block(event: keyof BlockEventsMap, payload) {}
export function dev$tracing(type, value: any) {}
export function dev$assert(truthy: any, else_throw: string) { export function dev$assert(truthy: any, else_throw: string) {
if (__DEV__ && !truthy) { if (__DEV__ && !truthy) {
throw new Error(else_throw); throw new Error(else_throw);

@ -3,19 +3,23 @@ import { noop } from './utils';
type TaskCallback = (t: number) => boolean; type TaskCallback = (t: number) => boolean;
type TaskCanceller = () => void; type TaskCanceller = () => void;
/** manual upkeeping of next_frame.length */
let n = 0;
let i = 0,
j = 0;
let v;
let next_frame: Array<TaskCallback> = []; let next_frame: Array<TaskCallback> = [];
let running_frame: Array<TaskCallback> = []; let running_frame: Array<TaskCallback> = [];
let next_frame_length = 0;
const run = (t: number) => { const run = (t: number) => {
t = now();
[running_frame, next_frame] = [next_frame, running_frame]; [running_frame, next_frame] = [next_frame, running_frame];
for (let i = (next_frame_length = 0), j = running_frame.length, v; i < j; i++) { for (t = now(), i = n = 0, j = running_frame.length; i < j; i++) {
if ((v = running_frame[i])(t)) { if ((v = running_frame[i])(t)) {
next_frame[next_frame_length++] = v; next_frame[n++] = v;
} }
} }
running_frame.length = 0; if ((running_frame.length = 0) < n) {
if (next_frame_length) raf(run); raf(run);
}
}; };
type TimeoutTask = { timestamp: number; callback: (now: number) => void }; type TimeoutTask = { timestamp: number; callback: (now: number) => void };
@ -42,13 +46,13 @@ const run_timed = (now: number) => {
return (running_timed = !!(timed_tasks.length = last_index + 1)); return (running_timed = !!(timed_tasks.length = last_index + 1));
}; };
const unsafe_loop = (fn) => { const unsafe_loop = (fn) => {
if (!next_frame_length) raf(run); if (0 === n) raf(run);
next_frame[next_frame_length++] = fn; next_frame[n++] = fn;
}; };
export const loop = (fn) => { export const loop = (fn) => {
let running = true; let running = true;
if (!next_frame_length) raf(run); if (0 === n) raf(run);
next_frame[next_frame_length++] = (t) => !running || fn(t); next_frame[n++] = (t) => !running || fn(t);
return () => void (running = false); return () => void (running = false);
}; };
export const setFrameTimeout = (callback: () => void, timestamp: number): TaskCanceller => { export const setFrameTimeout = (callback: () => void, timestamp: number): TaskCanceller => {
@ -103,4 +107,4 @@ export const onEachFrame = (
/** tests only */ /** tests only */
export const clear_loops = () => export const clear_loops = () =>
void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = next_frame_length = +(running_timed = pending_inserts = false)); void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = +(running_timed = pending_inserts = false));

@ -1,5 +1,5 @@
import { set_current_component } from './lifecycle'; import { set_current_component } from './lifecycle';
import { resolved_promise } from 'svelte/environment'; import { resolved_promise, now } from 'svelte/environment';
import { T$$ } from './Component'; import { T$$ } from './Component';
let update_scheduled = false; let update_scheduled = false;
@ -43,6 +43,7 @@ export const flush = () => {
let i = 0, let i = 0,
j = 0, j = 0,
t = 0,
$$: T$$, $$: T$$,
dirty, dirty,
before_update, before_update,
@ -74,7 +75,7 @@ export const flush = () => {
} }
dirty_components.length = 0; dirty_components.length = 0;
// update bindings [ in reverse order (#3145); is there a better way ? ] // update bindings [ ...in reverse order (#3145) ]
i = binding_callbacks.length; i = binding_callbacks.length;
while (i--) binding_callbacks[i](); while (i--) binding_callbacks[i]();
binding_callbacks.length = i = 0; binding_callbacks.length = i = 0;
@ -95,7 +96,7 @@ export const flush = () => {
// apply styles // apply styles
// todo : remove every non style callback from flush_callbacks // todo : remove every non style callback from flush_callbacks
for (; i < j; i++) flush_callbacks[i](); for (t = now(); i < j; i++) flush_callbacks[i](t);
flush_callbacks.length = i = j = 0; flush_callbacks.length = i = j = 0;
is_flushing = false; is_flushing = false;

@ -8,7 +8,7 @@ type Unsubscriber = () => void;
interface Observable<T> { interface Observable<T> {
subscribe(callback: Subscriber<T>): Unsubscriber; subscribe(callback: Subscriber<T>): Unsubscriber;
} }
type Observable_s = Observable<unknown>[] | Observable<unknown>; type Obs = Observable<unknown>[] | Observable<unknown>;
type Deriver<T> = (values: any, setter?: Setter<T>) => void | (() => void) | T; type Deriver<T> = (values: any, setter?: Setter<T>) => void | (() => void) | T;
/** /**
@ -26,7 +26,8 @@ export class Store<T> {
for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++) for (let i = 0, j = 0, subscribers, value; i < this.update_queue.length; i++)
for (j = 0, subscribers = this.update_queue[i], value = this.value_queue[i]; j < subscribers.length; j++) for (j = 0, subscribers = this.update_queue[i], value = this.value_queue[i]; j < subscribers.length; j++)
subscribers[j].run(value); subscribers[j].run(value);
this.update_queue.length = this.value_queue.length = +(this.is_flushing = false); this.update_queue.length = this.value_queue.length = 0;
this.is_flushing = false;
} }
value: T; value: T;
has_subscribers = false; has_subscribers = false;
@ -97,7 +98,7 @@ export class Writable<T> extends StartStopWritable<T> {
if (safe_not_equal(this.value, next_value)) super.set(next_value); if (safe_not_equal(this.value, next_value)) super.set(next_value);
} }
} }
export class Derived<S extends Observable_s, D extends Deriver<T>, T> extends StartStopWritable<T> { export class Derived<S extends Obs, D extends Deriver<T>, T> extends StartStopWritable<T> {
cleanup = noop; cleanup = noop;
target; target;
deriver; deriver;
@ -129,9 +130,9 @@ export class Derived<S extends Observable_s, D extends Deriver<T>, T> extends St
this.set = this.set =
// deriver defines < 2 arguments ? // deriver defines < 2 arguments ?
deriver.length < 2 deriver.length < 2
? // return value is store value ? // deriver returned value is store value
(v) => void super.set(deriver(v) as T) (v) => void super.set(deriver(v) as T)
: // return value is cleanup | void, store value is set manually : // deriver returned value is cleanup | void, store value is set manually within deriver
(v) => (v) =>
void (this.cleanup(), void (this.cleanup(),
typeof (this.cleanup = deriver(v, super.set.bind(this)) as () => void) !== 'function' && typeof (this.cleanup = deriver(v, super.set.bind(this)) as () => void) !== 'function' &&
@ -144,11 +145,12 @@ export type createMotionTick<T> = (prev_value: T, next_value: T) => SpringTick<T
export type createTweenTick<T> = (prev_value: T, next_value: T) => TweenTick; export type createTweenTick<T> = (prev_value: T, next_value: T) => TweenTick;
export type SpringTick<T> = (current_value: T, elapsed: number, dt: number) => boolean; export type SpringTick<T> = (current_value: T, elapsed: number, dt: number) => boolean;
export type TweenTick = (t: number) => boolean; export type TweenTick = (t: number) => boolean;
/** applies motion fn to every leaf of any object */
function parseStructure<T>( /** applies motion function initializer to every leaf of any shape of array-like or object literal like value */
const parseStructure = <T>(
obj: unknown, obj: unknown,
schema: initCreateMotionTick<T> | initCreateTweenTick<T> schema: initCreateMotionTick<T> | initCreateTweenTick<T>
): initCreateMotionTick<T> | initCreateTweenTick<T> { ): initCreateMotionTick<T> | initCreateTweenTick<T> => {
const isArray = Array.isArray(obj); const isArray = Array.isArray(obj);
if (typeof obj === 'object' && obj !== null && (isArray || Object.prototype === Object.getPrototypeOf(obj))) { if (typeof obj === 'object' && obj !== null && (isArray || Object.prototype === Object.getPrototypeOf(obj))) {
const keys = Object.keys(obj); const keys = Object.keys(obj);
@ -160,10 +162,10 @@ function parseStructure<T>(
pending = 0; pending = 0;
const target = { ...obj }; const target = { ...obj };
obj = isArray ? [...(obj as T[])] : { ...obj }; obj = isArray ? [...(obj as T[])] : { ...obj };
return (set) => (_from_value, to_value) => { return (set) => (_, to_value) => {
for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k]; for (k in to_value) if (to_value[k] !== obj[k]) target[k] = to_value[k];
for (i = 0; i < l; i++) (pending |= 1 << i), (tickers[i] = createTickers[i](obj[keys[i]], target[keys[i]])); for (i = 0; i < l; i++) (pending |= 1 << i), (tickers[i] = createTickers[i](obj[keys[i]], target[keys[i]]));
return (_current, elapsed, dt) => { return (_, elapsed, dt) => {
for (i = 0; i < l; i++) if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i); for (i = 0; i < l; i++) if (pending & (1 << i) && !tickers[i](obj[keys[i]], elapsed, dt)) pending &= ~(1 << i);
set(isArray ? [...(obj as T[])] : { ...(obj as any) }); set(isArray ? [...(obj as T[])] : { ...(obj as any) });
return !!pending; return !!pending;
@ -171,16 +173,16 @@ function parseStructure<T>(
}; };
} }
return schema; return schema;
} };
abstract class MotionStore<T> extends Store<T> { abstract class MotionStore<T> extends Store<T> {
running = false; running = false;
cancel = noop; cancel = noop;
initCreateTicker; init;
createTicker; create;
tick; tick;
constructor(value: T, startSetTick: initCreateMotionTick<T> | initCreateTweenTick<T>) { constructor(value: T, startSetTick: initCreateMotionTick<T> | initCreateTweenTick<T>) {
super(value); super(value);
this.createTicker = parseStructure(value, (this.initCreateTicker = startSetTick))(super.set.bind(this)); this.create = parseStructure(value, (this.init = startSetTick))(super.set.bind(this));
} }
set(next_value: T) { set(next_value: T) {
const this_id = ++this.uidRunning; const this_id = ++this.uidRunning;
@ -188,7 +190,7 @@ abstract class MotionStore<T> extends Store<T> {
if (!this.value && (this.value as unknown) !== 0) { if (!this.value && (this.value as unknown) !== 0) {
this.setImmediate(next_value); this.setImmediate(next_value);
} else { } else {
this.tick = this.createTicker(this.value, next_value); this.tick = this.create(this.value, next_value);
this.loop(() => this.clearStateSubscribers(true)); this.loop(() => this.clearStateSubscribers(true));
this.running = true; this.running = true;
} }
@ -202,7 +204,7 @@ abstract class MotionStore<T> extends Store<T> {
} }
abstract loop(stop): void; abstract loop(stop): void;
setImmediate(value) { setImmediate(value) {
this.createTicker = parseStructure(value, this.initCreateTicker)(super.set.bind(this)); this.create = parseStructure(value, this.init)(super.set.bind(this));
super.set((this.value = value)); super.set((this.value = value));
if (this.running) this.cancel(); if (this.running) this.cancel();
this.running = false; this.running = false;
@ -233,8 +235,8 @@ abstract class MotionStore<T> extends Store<T> {
} }
} }
export class SpringMotion<T> extends MotionStore<T> { export class SpringMotion<T> extends MotionStore<T> {
initCreateTicker: initCreateMotionTick<T>; init: initCreateMotionTick<T>;
createTicker: createMotionTick<T>; create: createMotionTick<T>;
tick: SpringTick<T>; tick: SpringTick<T>;
elapsed = 0.0; elapsed = 0.0;
loop(stop) { loop(stop) {
@ -243,8 +245,8 @@ export class SpringMotion<T> extends MotionStore<T> {
} }
} }
export class TweenMotion<T> extends MotionStore<T> { export class TweenMotion<T> extends MotionStore<T> {
initCreateTicker: initCreateTweenTick<T>; init: initCreateTweenTick<T>;
createTicker: createTweenTick<T>; create: createTweenTick<T>;
tick: TweenTick; tick: TweenTick;
loop(stop) { loop(stop) {
if (this.running) this.cancel(); if (this.running) this.cancel();

@ -1,67 +1,63 @@
import { element } from './dom';
import { framerate } from 'svelte/environment'; import { framerate } from 'svelte/environment';
enum SVELTE {
RULE = `__svelte_`,
STYLESHEET = `__svelte_stylesheet`,
RULESET = `__svelte_rules`,
}
interface ExtendedDoc extends Document {
[SVELTE.STYLESHEET]: CSSStyleSheet;
[SVELTE.RULESET]: Set<string>;
}
const active_documents = new Set<ExtendedDoc>(); let documents_uid = 0;
let running_animations = 0; let running_animations = 0;
function add_rule(node): [CSSStyleSheet, Set<string>] { const document_uid = new Map();
const { ownerDocument } = node; const document_stylesheets = new Map();
if (!active_documents.has(ownerDocument)) {
active_documents.add(ownerDocument);
if (!(SVELTE.STYLESHEET in ownerDocument))
ownerDocument[SVELTE.STYLESHEET] = ownerDocument.head.appendChild(element('style')).sheet;
if (!(SVELTE.RULESET in ownerDocument)) ownerDocument[SVELTE.RULESET] = new Set();
}
return [ownerDocument[SVELTE.STYLESHEET], ownerDocument[SVELTE.RULESET]];
}
function hash(str: string) { const current_rules = new Set();
// darkskyapp/string-hash export const animate_css = Function.prototype.call.bind(function animate_css(
let hash = 5381; this: HTMLElement,
let i = str.length; css: (t: number) => string,
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); duration: number,
return hash >>> 0; delay = 0
} ) {
const gen = (step, css) => { if (!document_uid.has(this.ownerDocument)) {
document_uid.set(this.ownerDocument, documents_uid++);
document_stylesheets.set(
this.ownerDocument,
this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet
);
}
let rule = '{\n'; let rule = '{\n';
for (let t = 0; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`; for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`;
rule += `100% {${css(1)}}\n}`; rule += `100% {${css(1)}}\n}`;
const name = SVELTE.RULE + hash(rule);
return [name, `@keyframes ${name} ${rule}`]; // darkskyapp/string-hash
}; let i = rule.length,
function animate(this: HTMLElement, css: (t: number) => string, duration: number, delay = 0) { hash = 5381;
const [name, rule] = gen(Math.max(1 / 1000, framerate / duration), css); while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i);
const [stylesheet, rules] = add_rule(this); const name = '_' + (hash >>> 0) + document_uid.get(this.ownerDocument);
if (!rules.has(name)) {
rules.add(name); if (!current_rules.has(name)) {
stylesheet.insertRule(rule, stylesheet.cssRules.length); current_rules.add(name);
const stylesheet = document_stylesheets.get(this.ownerDocument);
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
} }
const previous = this.style.animation; const previous = this.style.animation;
this.style.animation = `${ this.style.animation = `${
previous ? `${previous}, ` : '' previous ? `${previous}, ` : ''
} ${duration}ms linear ${delay}ms 1 normal both running ${name}`; } ${duration}ms linear ${delay}ms 1 normal both running ${name}`;
running_animations++; running_animations++;
return () => { return () => {
const prev = (this.style.animation || '').split(', '); const prev = (this.style.animation || '').split(', ');
const next = prev.filter((anim) => !anim.includes(name)); const next = prev.filter((anim) => !anim.includes(name));
if (prev.length !== next.length) this.style.animation = next.join(', '); if (prev.length !== next.length) this.style.animation = next.join(', ');
if (--running_animations) return; if (--running_animations === 0) {
active_documents.forEach(({ [SVELTE.STYLESHEET]: stylesheet, [SVELTE.RULESET]: ruleset }) => { document_stylesheets.forEach((stylesheet) => {
let i = stylesheet.cssRules.length; let i = stylesheet.cssRules.length;
while (i--) stylesheet.deleteRule(i); while (i--) stylesheet.deleteRule(i);
ruleset.clear();
}); });
active_documents.clear(); current_rules.clear();
if (1 !== documents_uid) {
document_stylesheets.clear();
document_uid.clear();
documents_uid = 0;
}
}
}; };
} });
export const animate_css = Function.prototype.call.bind(animate);

@ -5,9 +5,10 @@ import { now } from 'svelte/environment';
import { setFrameTimeout, setTweenTimeout } from './loop'; import { setFrameTimeout, setTweenTimeout } from './loop';
import { add_measure_callback } from './scheduler'; import { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager'; import { animate_css } from './style_manager';
import { noop } from './utils';
type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig; type TransitionFn = (node: HTMLElement, params: any) => TransitionConfig;
export type StopResetReverse = (reset_reverse?: 1 | -1) => StopResetReverse; export type StopResetReverse = (t?: number | -1) => StopResetReverse | void;
export const transition_in = (block: Fragment, local?) => { export const transition_in = (block: Fragment, local?) => {
if (!block || !block.i) return; if (!block || !block.i) return;
@ -46,72 +47,78 @@ export const group_transition_out = (fn) => {
check_transition_group(current_group, false); check_transition_group(current_group, false);
transition_group = transition_group.p; transition_group = transition_group.p;
}; };
// todo : deprecate
function startStopDispatcher(node: Element, is_intro: boolean) {
node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}start`));
return () => node.dispatchEvent(custom_event(`${is_intro ? 'intro' : 'outro'}end`));
}
/* todo: deprecate */ /* todo: deprecate */
const swap = (fn, is_intro) => const swap = (fn, rx) =>
fn.length === 1 ? (is_intro ? fn : (t) => fn(1 - t)) : is_intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t); 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, is_intro, easing) => { const mirrored = (fn, rx, easing) => {
const run = swap(fn, is_intro); const run = swap(fn, rx);
return easing ? (!is_intro ? (t) => run(1 - easing(1 - t)) : (t) => run(easing(t))) : run; 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 reversed = (fn, is_intro, easing, start = 0, end = 1) => { const run = swap(fn, rx);
const run = swap(fn, is_intro);
const difference = end - start; const difference = end - start;
return easing ? (t) => run(start + difference * easing(t)) : (t) => run(start + difference * t); return easing ? (t) => run(start + difference * easing(t)) : (t) => run(start + difference * t);
}; };
export enum tx {
export const run_transition = ( intro = 1,
node: HTMLElement, outro = 2,
bidirectional = 4,
bidirectional_intro = 5,
bidirectional_outro = 6,
animation = 8,
}
export const run_transition = Function.prototype.call.bind(function transition(
this: HTMLElement,
fn: TransitionFn, fn: TransitionFn,
is_intro = true, rx: tx,
params = {}, params = {},
/* internal to this file */ /* internal to this file */
is_bidirectional = false,
elapsed_duration = 0, elapsed_duration = 0,
delay_left = -1, delay_left = -1,
elapsed_ratio = 0 elapsed_ratio = 0
) => { ) {
let config; let config;
let running = true; let running = true;
let cancel_css; let cancel_css;
let cancel_raf; let cancel_raf;
let dispatch_end;
let start_time = 0; let start_time = 0;
let end_time = 0; let end_time = 0;
const current_group = transition_group; const current_group = transition_group;
if (!is_intro) transition_group.r++; if (rx & tx.outro) current_group.r++;
const start = ({ delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: TransitionConfig) => { add_measure_callback(() => {
if (!running) return; if (null === (config = fn(this, params))) return noop;
return (t) => {
if (false === running) return void (running = false);
if ('then' in config) return void config.then(stop);
if (~delay_left) delay = delay_left; let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: TransitionConfig =
'function' === typeof config ? (config = config()) : config;
const solver = strategy === 'reverse' ? reversed : mirrored; const solver = 'reverse' === strategy ? reversed : mirrored;
const runner = (fn) => solver(fn, is_intro, easing, elapsed_ratio, 1); const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1);
if (solver === reversed) { if (rx & tx.bidirectional) {
duration -= elapsed_duration; if (-1 !== delay_left) delay = delay_left;
} else if (solver === mirrored) { if (solver === reversed) duration -= elapsed_duration;
delay -= elapsed_duration; else if (solver === mirrored) delay -= elapsed_duration;
} }
end_time = (start_time = now() + delay) + duration; end_time = (start_time = t + delay) + duration;
dispatch_end = startStopDispatcher(node, is_intro); if (0 === (rx & tx.animation)) {
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`));
}
if (css) cancel_css = animate_css(node, runner(css), duration, delay); if (css) cancel_css = animate_css(this, runner(css), duration, delay);
cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time); cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time);
}; };
});
const stop: StopResetReverse = (t?: number | 1 | -1) => { const stop: StopResetReverse = (t?: number | 1 | -1) => {
if (!running) return; if (!running) return;
@ -119,49 +126,53 @@ export const run_transition = (
if (cancel_css) cancel_css(); if (cancel_css) cancel_css();
if (cancel_raf) cancel_raf(); if (cancel_raf) cancel_raf();
if (t > end_time) {
if (dispatch_end) { if (0 === (rx & tx.animation)) {
dispatch_end(); if (t >= end_time) {
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
}
if (rx & tx.outro) {
check_transition_group(current_group);
} }
} }
if (!is_intro) check_transition_group(current_group); if (rx & tx.bidirectional) {
if (is_bidirectional) {
if (-1 === t) { if (-1 === t) {
return ( return (
(t = now()) < end_time && (t = now()) < end_time &&
run_transition( run_transition(
node, this,
() => config, () => config,
!is_intro, rx ^ 1,
params, params,
true,
end_time - t, end_time - t,
start_time > t ? start_time - t : 0, start_time > t ? start_time - t : 0,
(1 - elapsed_ratio) * (1 - config.easing(1 - (end_time - t) / (end_time - start_time))) (1 - elapsed_ratio) * (1 - config.easing(1 - (end_time - t) / (end_time - start_time)))
) )
); );
} else { } else {
running_bidi.delete(node); running_bidi.delete(this);
} }
} }
}; };
add_measure_callback(() => {
config = fn(node, params);
return () => start(typeof config === 'function' ? (config = config()) : config);
});
return stop; return stop;
}; });
const running_bidi: Map<HTMLElement, StopResetReverse> = new Map(); const running_bidi: Map<HTMLElement, StopResetReverse> = new Map();
export const run_bidirectional_transition = (node: HTMLElement, fn: TransitionFn, is_intro: boolean, params: any) => { export const run_bidirectional_transition = Function.prototype.call.bind(function bidirectional(
this: HTMLElement,
fn: TransitionFn,
rx: tx.intro | tx.outro,
params: any
) {
let cancel; let cancel;
if (running_bidi.has(node) && (cancel = running_bidi.get(node)(-1))) running_bidi.set(node, cancel); running_bidi.set(
else running_bidi.set(node, (cancel = run_transition(node, fn, is_intro, params, true))); this,
(cancel =
(running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params))
);
return cancel; return cancel;
}; });
export const run_duration = (duration, ...args): number => export const run_duration = (duration, value1, value2?): number =>
typeof duration === 'function' ? duration.apply(null, args) : duration; typeof duration === 'function' ? duration(value1, value2) : duration;

@ -4,7 +4,7 @@ interface TweenParams<T> {
delay?: number; delay?: number;
duration?: number | ((from: T, to: T) => number); duration?: number | ((from: T, to: T) => number);
easing?: (t: number) => number; easing?: (t: number) => number;
interpolate?: (a: T, b: T) => (t: number) => T; interpolate?: (from: T, to: T) => (t: number) => T;
} }
interface SpringParams { interface SpringParams {
stiffness?: number /* { 10 < 100 < 200 } */; stiffness?: number /* { 10 < 100 < 200 } */;
@ -110,7 +110,7 @@ export function tween<T>(
}; };
return { return {
set, set,
/* todo: test update() */ /* todo: test update() with objects */
update: (fn, params) => set(fn(store.value, value), params), update: (fn, params) => set(fn(store.value, value), params),
setImmediate: store.setImmediate.bind(store), setImmediate: store.setImmediate.bind(store),
subscribe: store.subscribe.bind(store), subscribe: store.subscribe.bind(store),

@ -141,11 +141,11 @@ export function crossfade({
const to_receive: ElementMap = new Map(); const to_receive: ElementMap = new Map();
const to_send: ElementMap = new Map(); const to_send: ElementMap = new Map();
function crossfade( const crossfade = (
from_node: Element, from_node: Element,
to_node: Element, to_node: Element,
{ delay = default_delay, easing = default_easing, duration = default_duration }: TimeableConfig { delay = default_delay, easing = default_easing, duration = default_duration }: TimeableConfig
) { ) => {
const from = from_node.getBoundingClientRect(); const from = from_node.getBoundingClientRect();
const to = to_node.getBoundingClientRect(); const to = to_node.getBoundingClientRect();
const dx = from.left - to.left; const dx = from.left - to.left;
@ -165,7 +165,7 @@ export function crossfade({
transform: ${prev} 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 TransitionConfig; } as TransitionConfig;
} };
const transition = (a: ElementMap, b: ElementMap, is_intro: boolean) => ( const transition = (a: ElementMap, b: ElementMap, is_intro: boolean) => (
node: Element, node: Element,
params: MarkedCrossFadeConfig params: MarkedCrossFadeConfig

@ -0,0 +1,4 @@
import * as assert from 'assert';
type TestSetup = {
test: ({ assert: module.assert, component, mod, target, window, raf, compileOptions }) => void | Promise<void>;
};

@ -1,20 +1,12 @@
import * as assert from "assert"; import * as assert from 'assert';
import * as path from "path"; import * as path from 'path';
import * as fs from "fs"; import * as fs from 'fs';
import { rollup } from 'rollup'; import { rollup } from 'rollup';
import * as virtual from '@rollup/plugin-virtual'; import * as virtual from '@rollup/plugin-virtual';
import * as glob from 'tiny-glob/sync.js'; import * as glob from 'tiny-glob/sync.js';
import { clear_loops, flush, set_now, set_raf } from "../../internal"; import { clear_loops, flush, set_now, set_raf } from '../../internal';
import { import { showOutput, loadConfig, loadSvelte, cleanRequireCache, env, setupHtmlEqual, mkdirp } from '../helpers.js';
showOutput,
loadConfig,
loadSvelte,
cleanRequireCache,
env,
setupHtmlEqual,
mkdirp
} from "../helpers.js";
let svelte$; let svelte$;
let svelte; let svelte;
@ -25,21 +17,26 @@ let compile = null;
const sveltePath = process.cwd().split('\\').join('/'); const sveltePath = process.cwd().split('\\').join('/');
let unhandled_rejection = false; let unhandled_rejection = false;
process.on('unhandledRejection', err => { process.on('unhandledRejection', (err) => {
unhandled_rejection = err; unhandled_rejection = err;
}); });
describe("runtime", () => { describe('runtime', () => {
before(() => { before(() => {
svelte = loadSvelte(false); svelte = loadSvelte(false);
svelte$ = loadSvelte(true); svelte$ = loadSvelte(true);
require.extensions[".svelte"] = function(module, filename) { require.extensions['.svelte'] = function (module, filename) {
const options = Object.assign({ const options = Object.assign(
filename {
}, compileOptions); filename,
},
compileOptions
);
const { js: { code } } = compile(fs.readFileSync(filename, "utf-8"), options); const {
js: { code },
} = compile(fs.readFileSync(filename, 'utf-8'), options);
return module._compile(code, filename); return module._compile(code, filename);
}; };
@ -50,7 +47,7 @@ describe("runtime", () => {
const failed = new Set(); const failed = new Set();
function runTest(dir, hydrate) { function runTest(dir, hydrate) {
if (dir[0] === ".") return; if (dir[0] === '.') return;
const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`); const config = loadConfig(`${__dirname}/samples/${dir}/_config.js`);
const solo = config.solo || /\.solo/.test(dir); const solo = config.solo || /\.solo/.test(dir);
@ -58,7 +55,7 @@ describe("runtime", () => {
if (hydrate && config.skip_if_hydrate) return; if (hydrate && config.skip_if_hydrate) return;
if (solo && process.env.CI) { if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test"); throw new Error('Forgot to remove `solo: true` from test');
} }
(config.skip ? it.skip : solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => { (config.skip ? it.skip : solo ? it.only : it)(`${dir} ${hydrate ? '(with hydration)' : ''}`, () => {
@ -89,7 +86,7 @@ describe("runtime", () => {
const window = env(); const window = env();
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach((file) => {
if (file[0] === '_') return; if (file[0] === '_') return;
const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`; const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
@ -102,13 +99,10 @@ describe("runtime", () => {
mkdirp(dir); mkdirp(dir);
try { try {
const { js } = compile( const { js } = compile(fs.readFileSync(`${cwd}/${file}`, 'utf-8'), {
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
{
...compileOptions, ...compileOptions,
filename: file filename: file,
} });
);
fs.writeFileSync(out, js.code); fs.writeFileSync(out, js.code);
} catch (err) { } catch (err) {
@ -124,13 +118,13 @@ describe("runtime", () => {
const raf = { const raf = {
time: 0, time: 0,
callback: null, callback: null,
tick: now => { tick: (now) => {
raf.time = now; raf.time = now;
if (raf.callback) raf.callback(); if (raf.callback) raf.callback();
} },
}; };
set_now(() => raf.time); set_now(() => raf.time);
set_raf(cb => { set_raf((cb) => {
raf.callback = () => { raf.callback = () => {
raf.callback = null; raf.callback = null;
cb(raf.time); cb(raf.time);
@ -151,20 +145,24 @@ describe("runtime", () => {
// Put things we need on window for testing // Put things we need on window for testing
window.SvelteComponent = SvelteComponent; window.SvelteComponent = SvelteComponent;
const target = window.document.querySelector("main"); const target = window.document.querySelector('main');
const warnings = []; const warnings = [];
const warn = console.warn; const warn = console.warn;
console.warn = warning => { console.warn = (warning) => {
warnings.push(warning); warnings.push(warning);
}; };
const options = Object.assign({}, { const options = Object.assign(
{},
{
target, target,
hydrate, hydrate,
props: config.props, props: config.props,
intro: config.intro intro: config.intro,
}, config.options || {}); },
config.options || {}
);
const component = new SvelteComponent(options); const component = new SvelteComponent(options);
@ -172,14 +170,14 @@ describe("runtime", () => {
if (config.error) { if (config.error) {
unintendedError = true; unintendedError = true;
throw new Error("Expected a runtime error"); throw new Error('Expected a runtime error');
} }
if (config.warnings) { if (config.warnings) {
assert.deepEqual(warnings, config.warnings); assert.deepEqual(warnings, config.warnings);
} else if (warnings.length) { } else if (warnings.length) {
unintendedError = true; unintendedError = true;
throw new Error("Received unexpected warnings"); throw new Error('Received unexpected warnings');
} }
if (config.html) { if (config.html) {
@ -187,15 +185,17 @@ describe("runtime", () => {
} }
if (config.test) { if (config.test) {
return Promise.resolve(config.test({ return Promise.resolve(
config.test({
assert, assert,
component, component,
mod, mod,
target, target,
window, window,
raf, raf,
compileOptions compileOptions,
})).then(() => { })
).then(() => {
component.$destroy(); component.$destroy();
if (unhandled_rejection) { if (unhandled_rejection) {
@ -204,14 +204,14 @@ describe("runtime", () => {
}); });
} else { } else {
component.$destroy(); component.$destroy();
assert.htmlEqual(target.innerHTML, ""); assert.htmlEqual(target.innerHTML, '');
if (unhandled_rejection) { if (unhandled_rejection) {
throw unhandled_rejection; throw unhandled_rejection;
} }
} }
}) })
.catch(err => { .catch((err) => {
if (config.error && !unintendedError) { if (config.error && !unintendedError) {
if (typeof config.error === 'function') { if (typeof config.error === 'function') {
config.error(assert, err); config.error(assert, err);
@ -221,12 +221,13 @@ describe("runtime", () => {
} else { } else {
throw err; throw err;
} }
}).catch(err => { })
.catch((err) => {
failed.add(dir); failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err; throw err;
}) })
.catch(err => { .catch((err) => {
// print a clickable link to open the directory // print a clickable link to open the directory
err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`;
throw err; throw err;
@ -243,23 +244,23 @@ describe("runtime", () => {
}); });
} }
fs.readdirSync(`${__dirname}/samples`).forEach(dir => { fs.readdirSync(`${__dirname}/samples`).forEach((dir) => {
runTest(dir, false); runTest(dir, false);
runTest(dir, true); runTest(dir, true);
}); });
async function create_component(src = '<div></div>') { async function create_component(src = '<div></div>') {
const { js } = svelte$.compile(src, { const { js } = svelte$.compile(src, {
format: "esm", format: 'esm',
name: "SvelteComponent", name: 'SvelteComponent',
dev: true dev: true,
}); });
const bundle = await rollup({ const bundle = await rollup({
input: 'main.js', input: 'main.js',
plugins: [ plugins: [
virtual({ virtual({
'main.js': js.code 'main.js': js.code,
}), }),
{ {
name: 'svelte-packages', name: 'svelte-packages',
@ -267,22 +268,20 @@ describe("runtime", () => {
if (importee.startsWith('svelte/')) { if (importee.startsWith('svelte/')) {
return importee.replace('svelte', process.cwd()) + '/index.mjs'; return importee.replace('svelte', process.cwd()) + '/index.mjs';
} }
} },
} },
] ],
}); });
const result = await bundle.generate({ const result = await bundle.generate({
format: 'iife', format: 'iife',
name: 'App' name: 'App',
}); });
return eval( return eval(`(function () { ${result.output[0].code}; return App; }())`);
`(function () { ${result.output[0].code}; return App; }())`
);
} }
it("fails if options.target is missing in dev mode", async () => { it('fails if options.target is missing in dev mode', async () => {
const App = await create_component(); const App = await create_component();
assert.throws(() => { assert.throws(() => {
@ -290,13 +289,13 @@ describe("runtime", () => {
}, /'target' is a required option/); }, /'target' is a required option/);
}); });
it("fails if options.hydrate is true but the component is non-hydratable", async () => { it('fails if options.hydrate is true but the component is non-hydratable', async () => {
const App = await create_component(); const App = await create_component();
assert.throws(() => { assert.throws(() => {
new App({ new App({
target: { childNodes: [] }, target: { childNodes: [] },
hydrate: true hydrate: true,
}); });
}, /options.hydrate only works if the component was compiled with the `hydratable: true` option/); }, /options.hydrate only works if the component was compiled with the `hydratable: true` option/);
}); });

Loading…
Cancel
Save