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

2
.gitignore vendored

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

@ -68,7 +68,7 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
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 warnings = [];

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

@ -210,7 +210,7 @@ export default class AttributeWrapper {
if (scoped_css && rendered.length === 2) {
// 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}`);

@ -360,7 +360,7 @@ export default class ElementWrapper extends Wrapper {
}
}
if (this.node.animation) {
this.add_animation(block, intro, outro);
this.add_animation(block, intro);
}
this.add_classes(block);
this.add_manual_style_scoping(block);
@ -677,8 +677,8 @@ export default class ElementWrapper extends Wrapper {
const fn = this.renderer.reference(intro.name);
let intro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, true, ${snippet});`;
let outro_block = b`${name} = @run_bidirectional_transition(${this.var}, ${fn}, false, ${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}, 2, ${snippet});`;
if (intro.is_local) {
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.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) {
if (outro) {
const outro_var = block.alias(`${this.var.name}_outro`);
block.chunks.intro.push(b`
if (${outro_var}){
${outro_var}(1);
}
`);
block.chunks.intro.push(b`${outro_var}();`);
}
if (this.node.animation) {
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;
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};`;
block.chunks.intro.push(start_intro);
}
@ -723,21 +719,21 @@ export default class ElementWrapper extends Wrapper {
add_outro(block: Block, intro: Transition, outro: Transition) {
if (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;
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};`;
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 [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.chunks.measure.push(b`
if(!${unfreeze_var}) ${rect_var} = ${this.var}.getBoundingClientRect();
${intro && b`if(${intro_var}) ${intro_var}();`}
${rect_var} = ${this.var}.getBoundingClientRect();
${intro && b`${intro_var}();`}
`);
block.chunks.fix.push(b`

@ -36,21 +36,14 @@ export default class SlotWrapper extends Wrapper {
this.fallback = block.child({
comment: create_debugging_comment(this.node.children[0], this.renderer.component),
name: this.renderer.component.get_unique_name(`fallback_block`),
type: 'fallback'
type: 'fallback',
});
renderer.blocks.push(this.fallback);
}
this.fragment = new FragmentWrapper(
renderer,
this.fallback,
node.children,
this,
strip_whitespace,
next_sibling
);
this.fragment = new FragmentWrapper(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);
});
@ -61,11 +54,7 @@ export default class SlotWrapper extends Wrapper {
block.add_outro();
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this;
const { slot_name } = this.node;
@ -81,20 +70,20 @@ export default class SlotWrapper extends Wrapper {
const dependencies = new Set();
this.node.values.forEach(attribute => {
attribute.chunks.forEach(chunk => {
this.node.values.forEach((attribute) => {
attribute.chunks.forEach((chunk) => {
if ((chunk as Expression).dependencies) {
add_to_set(dependencies, (chunk as Expression).contextual_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);
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;
const variable = renderer.component.var_lookup.get(name);
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}
`);
block.chunks.create.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`
);
block.chunks.create.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.c();`);
if (renderer.options.hydratable) {
block.chunks.claim.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`
);
block.chunks.claim.push(b`if (${slot_or_fallback}) ${slot_or_fallback}.l(${parent_nodes});`);
}
block.chunks.mount.push(b`
@ -149,15 +134,11 @@ export default class SlotWrapper extends Wrapper {
}
`);
block.chunks.intro.push(
b`@transition_in(${slot_or_fallback}, #local);`
);
block.chunks.intro.push(b`@transition_in(${slot_or_fallback}, #local);`);
block.chunks.outro.push(
b`@transition_out(${slot_or_fallback}, #local);`
);
block.chunks.outro.push(b`@transition_out(${slot_or_fallback}, #local);`);
const is_dependency_dynamic = name => {
const is_dependency_dynamic = (name) => {
if (name === '$$scope') return true;
if (this.node.scope.is_let(name)) return true;
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)}) {
${slot_or_fallback}.p(#ctx, #dirty);
}
@ -200,8 +184,6 @@ export default class SlotWrapper extends Wrapper {
`);
}
block.chunks.destroy.push(
b`if (${slot_or_fallback}) ${slot_or_fallback}.d(detaching);`
);
block.chunks.destroy.push(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
if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
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);

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

@ -10,7 +10,7 @@ interface FlipParams {
export function flip(
node: Element,
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 {
const style = getComputedStyle(node).transform;
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,
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,
d = 0.0,
x = 0.0;
return (t: number) => {
r = t;
for (let i = 0; 32 > i; i++)
for (r = t, 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);
else if (1e-5 > Math.abs((d = r * (r * ax * 3.0 + x2 * 2.0) + x1))) break;
else r = r - x / d;

@ -12,11 +12,13 @@ export const is_cors =
}
})();
export const has_Symbol = typeof Symbol === 'function';
declare var global: any;
export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
export const resolved_promise = Promise.resolve();
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;
raf((t1) => {
raf((d) => {

@ -11,38 +11,42 @@ export interface 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;
return run_transition(
node,
(node, params) => fn(node, { from, to: node.getBoundingClientRect() }, params),
true,
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 fix_position(node: HTMLElement, { left, top }: DOMRect) {
const { position, width, height, transform } = getComputedStyle(node);
export const fix_position = Function.prototype.call.bind(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 } = node.style;
node.style.position = 'absolute';
node.style.width = width;
node.style.height = height;
const b = node.getBoundingClientRect();
node.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`;
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 () => {
node.style.position = og_position;
node.style.width = og_width;
node.style.height = og_height;
node.style.transform = ''; // unsafe
this.style.position = og_position;
this.style.width = og_width;
this.style.height = og_height;
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$tracing(type, value: any) {}
export function dev$assert(truthy: any, else_throw: string) {
if (__DEV__ && !truthy) {
throw new Error(else_throw);

@ -3,19 +3,23 @@ import { noop } from './utils';
type TaskCallback = (t: number) => boolean;
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 running_frame: Array<TaskCallback> = [];
let next_frame_length = 0;
const run = (t: number) => {
t = now();
[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)) {
next_frame[next_frame_length++] = v;
next_frame[n++] = v;
}
}
running_frame.length = 0;
if (next_frame_length) raf(run);
if ((running_frame.length = 0) < n) {
raf(run);
}
};
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));
};
const unsafe_loop = (fn) => {
if (!next_frame_length) raf(run);
next_frame[next_frame_length++] = fn;
if (0 === n) raf(run);
next_frame[n++] = fn;
};
export const loop = (fn) => {
let running = true;
if (!next_frame_length) raf(run);
next_frame[next_frame_length++] = (t) => !running || fn(t);
if (0 === n) raf(run);
next_frame[n++] = (t) => !running || fn(t);
return () => void (running = false);
};
export const setFrameTimeout = (callback: () => void, timestamp: number): TaskCanceller => {
@ -103,4 +107,4 @@ export const onEachFrame = (
/** tests only */
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 { resolved_promise } from 'svelte/environment';
import { resolved_promise, now } from 'svelte/environment';
import { T$$ } from './Component';
let update_scheduled = false;
@ -43,6 +43,7 @@ export const flush = () => {
let i = 0,
j = 0,
t = 0,
$$: T$$,
dirty,
before_update,
@ -74,7 +75,7 @@ export const flush = () => {
}
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;
while (i--) binding_callbacks[i]();
binding_callbacks.length = i = 0;
@ -95,7 +96,7 @@ export const flush = () => {
// apply styles
// 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;
is_flushing = false;

@ -8,7 +8,7 @@ type Unsubscriber = () => void;
interface Observable<T> {
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;
/**
@ -26,7 +26,8 @@ export class Store<T> {
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++)
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;
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);
}
}
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;
target;
deriver;
@ -129,9 +130,9 @@ export class Derived<S extends Observable_s, D extends Deriver<T>, T> extends St
this.set =
// deriver defines < 2 arguments ?
deriver.length < 2
? // return value is store value
? // deriver returned value is store value
(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) =>
void (this.cleanup(),
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 SpringTick<T> = (current_value: T, elapsed: number, dt: 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,
schema: initCreateMotionTick<T> | initCreateTweenTick<T>
): initCreateMotionTick<T> | initCreateTweenTick<T> {
): initCreateMotionTick<T> | initCreateTweenTick<T> => {
const isArray = Array.isArray(obj);
if (typeof obj === 'object' && obj !== null && (isArray || Object.prototype === Object.getPrototypeOf(obj))) {
const keys = Object.keys(obj);
@ -160,10 +162,10 @@ function parseStructure<T>(
pending = 0;
const target = { ...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 (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);
set(isArray ? [...(obj as T[])] : { ...(obj as any) });
return !!pending;
@ -171,16 +173,16 @@ function parseStructure<T>(
};
}
return schema;
}
};
abstract class MotionStore<T> extends Store<T> {
running = false;
cancel = noop;
initCreateTicker;
createTicker;
init;
create;
tick;
constructor(value: T, startSetTick: initCreateMotionTick<T> | initCreateTweenTick<T>) {
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) {
const this_id = ++this.uidRunning;
@ -188,7 +190,7 @@ abstract class MotionStore<T> extends Store<T> {
if (!this.value && (this.value as unknown) !== 0) {
this.setImmediate(next_value);
} else {
this.tick = this.createTicker(this.value, next_value);
this.tick = this.create(this.value, next_value);
this.loop(() => this.clearStateSubscribers(true));
this.running = true;
}
@ -202,7 +204,7 @@ abstract class MotionStore<T> extends Store<T> {
}
abstract loop(stop): void;
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));
if (this.running) this.cancel();
this.running = false;
@ -233,8 +235,8 @@ abstract class MotionStore<T> extends Store<T> {
}
}
export class SpringMotion<T> extends MotionStore<T> {
initCreateTicker: initCreateMotionTick<T>;
createTicker: createMotionTick<T>;
init: initCreateMotionTick<T>;
create: createMotionTick<T>;
tick: SpringTick<T>;
elapsed = 0.0;
loop(stop) {
@ -243,8 +245,8 @@ export class SpringMotion<T> extends MotionStore<T> {
}
}
export class TweenMotion<T> extends MotionStore<T> {
initCreateTicker: initCreateTweenTick<T>;
createTicker: createTweenTick<T>;
init: initCreateTweenTick<T>;
create: createTweenTick<T>;
tick: TweenTick;
loop(stop) {
if (this.running) this.cancel();

@ -1,67 +1,63 @@
import { element } from './dom';
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;
function add_rule(node): [CSSStyleSheet, Set<string>] {
const { ownerDocument } = node;
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]];
}
const document_uid = new Map();
const document_stylesheets = new Map();
function hash(str: string) {
// darkskyapp/string-hash
let hash = 5381;
let i = str.length;
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return hash >>> 0;
const current_rules = new Set();
export const animate_css = 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 gen = (step, css) => {
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}`;
const name = SVELTE.RULE + hash(rule);
return [name, `@keyframes ${name} ${rule}`];
};
function animate(this: HTMLElement, css: (t: number) => string, duration: number, delay = 0) {
const [name, rule] = gen(Math.max(1 / 1000, framerate / duration), css);
const [stylesheet, rules] = add_rule(this);
if (!rules.has(name)) {
rules.add(name);
stylesheet.insertRule(rule, stylesheet.cssRules.length);
// darkskyapp/string-hash
let i = rule.length,
hash = 5381;
while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i);
const name = '_' + (hash >>> 0) + document_uid.get(this.ownerDocument);
if (!current_rules.has(name)) {
current_rules.add(name);
const stylesheet = document_stylesheets.get(this.ownerDocument);
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
}
const previous = this.style.animation;
this.style.animation = `${
previous ? `${previous}, ` : ''
} ${duration}ms linear ${delay}ms 1 normal both running ${name}`;
running_animations++;
return () => {
const prev = (this.style.animation || '').split(', ');
const next = prev.filter((anim) => !anim.includes(name));
if (prev.length !== next.length) this.style.animation = next.join(', ');
if (--running_animations) return;
active_documents.forEach(({ [SVELTE.STYLESHEET]: stylesheet, [SVELTE.RULESET]: ruleset }) => {
if (--running_animations === 0) {
document_stylesheets.forEach((stylesheet) => {
let i = stylesheet.cssRules.length;
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 { add_measure_callback } from './scheduler';
import { animate_css } from './style_manager';
import { noop } from './utils';
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?) => {
if (!block || !block.i) return;
@ -46,72 +47,78 @@ export const group_transition_out = (fn) => {
check_transition_group(current_group, false);
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 */
const swap = (fn, is_intro) =>
fn.length === 1 ? (is_intro ? fn : (t) => fn(1 - t)) : is_intro ? (t) => fn(t, 1 - t) : (t) => fn(1 - t, t);
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, is_intro, easing) => {
const run = swap(fn, is_intro);
return easing ? (!is_intro ? (t) => run(1 - easing(1 - t)) : (t) => run(easing(t))) : run;
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, is_intro, easing, start = 0, end = 1) => {
const run = swap(fn, is_intro);
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 run_transition = (
node: HTMLElement,
export enum tx {
intro = 1,
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,
is_intro = true,
rx: tx,
params = {},
/* internal to this file */
is_bidirectional = false,
elapsed_duration = 0,
delay_left = -1,
elapsed_ratio = 0
) => {
) {
let config;
let running = true;
let cancel_css;
let cancel_raf;
let dispatch_end;
let start_time = 0;
let end_time = 0;
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) => {
if (!running) return;
add_measure_callback(() => {
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 runner = (fn) => solver(fn, is_intro, easing, elapsed_ratio, 1);
const solver = 'reverse' === strategy ? reversed : mirrored;
const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1);
if (solver === reversed) {
duration -= elapsed_duration;
} else if (solver === mirrored) {
delay -= elapsed_duration;
if (rx & tx.bidirectional) {
if (-1 !== delay_left) delay = delay_left;
if (solver === reversed) duration -= 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);
};
});
const stop: StopResetReverse = (t?: number | 1 | -1) => {
if (!running) return;
@ -119,49 +126,53 @@ export const run_transition = (
if (cancel_css) cancel_css();
if (cancel_raf) cancel_raf();
if (t > end_time) {
if (dispatch_end) {
dispatch_end();
if (0 === (rx & tx.animation)) {
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 (is_bidirectional) {
if (rx & tx.bidirectional) {
if (-1 === t) {
return (
(t = now()) < end_time &&
run_transition(
node,
this,
() => config,
!is_intro,
rx ^ 1,
params,
true,
end_time - t,
start_time > t ? start_time - t : 0,
(1 - elapsed_ratio) * (1 - config.easing(1 - (end_time - t) / (end_time - start_time)))
)
);
} 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;
};
});
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;
if (running_bidi.has(node) && (cancel = running_bidi.get(node)(-1))) running_bidi.set(node, cancel);
else running_bidi.set(node, (cancel = run_transition(node, fn, is_intro, params, true)));
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, ...args): number =>
typeof duration === 'function' ? duration.apply(null, args) : duration;
});
export const run_duration = (duration, value1, value2?): number =>
typeof duration === 'function' ? duration(value1, value2) : duration;

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

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

Loading…
Cancel
Save