mirror of https://github.com/sveltejs/svelte
parent
a4dadf82be
commit
dbe7d797d1
@ -0,0 +1 @@
|
|||||||
|
export default (arr) => arr.reduce((state, bool, index) => (bool ? (state |= 1 << index) : state), 0);
|
@ -1,103 +1,42 @@
|
|||||||
import { identity as linear, noop } from './utils';
|
import { run_transition } from './transitions';
|
||||||
import { now } from './environment';
|
import { noop } from './environment';
|
||||||
import { loop } from './loop';
|
import { methodify } from './utils';
|
||||||
import { create_rule, delete_rule } from './style_manager';
|
import { CssTransitionConfig } from 'svelte/transition';
|
||||||
import { AnimationConfig } from '../animate';
|
|
||||||
|
|
||||||
|
type AnimationFn = (node: Element, { from, to }: { from: DOMRect; to: DOMRect }, params: any) => CssTransitionConfig;
|
||||||
|
|
||||||
//todo: documentation says it is DOMRect, but in IE it would be ClientRect
|
export const run_animation = /*#__PURE__*/ methodify(
|
||||||
type PositionRect = DOMRect|ClientRect;
|
function run_animation(this: HTMLElement, from: DOMRect, fn: AnimationFn, params = {}) {
|
||||||
|
|
||||||
type AnimationFn = (node: Element, { from, to }: { from: PositionRect; to: PositionRect }, params: any) => AnimationConfig;
|
|
||||||
|
|
||||||
export function create_animation(node: Element & ElementCSSInlineStyle, from: PositionRect, fn: AnimationFn, params) {
|
|
||||||
if (!from) return noop;
|
if (!from) return noop;
|
||||||
|
return run_transition(
|
||||||
const to = node.getBoundingClientRect();
|
this,
|
||||||
if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom) return noop;
|
(_, params) => {
|
||||||
|
const to = this.getBoundingClientRect();
|
||||||
|
if (from.left !== to.left || from.right !== to.right || from.top !== to.top || from.bottom !== to.bottom) {
|
||||||
const {
|
return fn(this, { from, to }, params);
|
||||||
delay = 0,
|
} else return null;
|
||||||
duration = 300,
|
},
|
||||||
easing = linear,
|
9,
|
||||||
// @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation?
|
params
|
||||||
start: start_time = now() + delay,
|
);
|
||||||
// @ts-ignore todo:
|
}
|
||||||
end = start_time + duration,
|
);
|
||||||
tick = noop,
|
|
||||||
css
|
export const fix_position = /*#__PURE__*/ methodify(
|
||||||
} = fn(node, { from, to }, params);
|
function fix_position(this: HTMLElement, { left, top }: DOMRect) {
|
||||||
|
const { position, width, height, transform } = getComputedStyle(this);
|
||||||
let running = true;
|
if (position === 'absolute' || position === 'fixed') return noop;
|
||||||
let started = false;
|
const { position: og_position, width: og_width, height: og_height } = this.style;
|
||||||
let name;
|
this.style.position = 'absolute';
|
||||||
|
this.style.width = width;
|
||||||
function start() {
|
this.style.height = height;
|
||||||
if (css) {
|
const b = this.getBoundingClientRect();
|
||||||
name = create_rule(node, 0, 1, duration, delay, easing, css);
|
this.style.transform = `${transform === 'none' ? '' : transform} translate(${left - b.left}px, ${top - b.top}px)`;
|
||||||
}
|
return () => {
|
||||||
|
this.style.position = og_position;
|
||||||
if (!delay) {
|
this.style.width = og_width;
|
||||||
started = true;
|
this.style.height = og_height;
|
||||||
}
|
this.style.transform = ''; // unsafe
|
||||||
}
|
};
|
||||||
|
}
|
||||||
function stop() {
|
);
|
||||||
if (css) delete_rule(node, name);
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop(now => {
|
|
||||||
if (!started && now >= start_time) {
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (started && now >= end) {
|
|
||||||
tick(1, 0);
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!running) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (started) {
|
|
||||||
const p = now - start_time;
|
|
||||||
const t = 0 + 1 * easing(p / duration);
|
|
||||||
tick(t, 1 - t);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
tick(0, 1);
|
|
||||||
|
|
||||||
return stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fix_position(node: Element & ElementCSSInlineStyle) {
|
|
||||||
const style = getComputedStyle(node);
|
|
||||||
|
|
||||||
if (style.position !== 'absolute' && style.position !== 'fixed') {
|
|
||||||
const { width, height } = style;
|
|
||||||
const a = node.getBoundingClientRect();
|
|
||||||
node.style.position = 'absolute';
|
|
||||||
node.style.width = width;
|
|
||||||
node.style.height = height;
|
|
||||||
add_transform(node, a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function add_transform(node: Element & ElementCSSInlineStyle, a: PositionRect) {
|
|
||||||
const b = node.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (a.left !== b.left || a.top !== b.top) {
|
|
||||||
const style = getComputedStyle(node);
|
|
||||||
const transform = style.transform === 'none' ? '' : style.transform;
|
|
||||||
|
|
||||||
node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,18 +1,33 @@
|
|||||||
import { noop } from './utils';
|
export function noop() {}
|
||||||
|
export const is_browser = typeof window !== 'undefined';
|
||||||
|
export const is_iframe = is_browser && window.self !== window.top;
|
||||||
|
export const is_cors =
|
||||||
|
is_iframe &&
|
||||||
|
/*#__PURE__*/ (() => {
|
||||||
|
try {
|
||||||
|
if (window.parent) void window.parent.document;
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
export const has_Symbol = typeof Symbol === 'function';
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
declare var global: any;
|
||||||
|
export const globals = is_browser ? window : typeof globalThis !== 'undefined' ? globalThis : global;
|
||||||
|
export const resolved_promise = Promise.resolve();
|
||||||
|
export let now = is_browser ? window.performance.now.bind(window.performance) : Date.now.bind(Date);
|
||||||
|
export let raf = is_browser ? requestAnimationFrame : noop;
|
||||||
|
export let framerate = 1000 / 60;
|
||||||
|
/*#__PURE__*/ raf((t1) => {
|
||||||
|
raf((d) => {
|
||||||
|
const f24 = 1000 / 24,
|
||||||
|
f144 = 1000 / 144;
|
||||||
|
framerate = (d = d - t1) > f144 ? f144 : d < f24 ? f24 : d;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
export const is_client = typeof window !== 'undefined';
|
/* tests only */
|
||||||
|
export const set_now = (v) => void (now = v);
|
||||||
export let now: () => number = is_client
|
export const set_raf = (fn) => void (raf = fn);
|
||||||
? () => window.performance.now()
|
export const set_framerate = (v) => void (framerate = v);
|
||||||
: () => Date.now();
|
|
||||||
|
|
||||||
export let raf = is_client ? cb => requestAnimationFrame(cb) : noop;
|
|
||||||
|
|
||||||
// used internally for testing
|
|
||||||
export function set_now(fn) {
|
|
||||||
now = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function set_raf(fn) {
|
|
||||||
raf = fn;
|
|
||||||
}
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
declare const global: any;
|
|
||||||
|
|
||||||
export const globals = (typeof window !== 'undefined'
|
|
||||||
? window
|
|
||||||
: typeof globalThis !== 'undefined'
|
|
||||||
? globalThis
|
|
||||||
: global) as unknown as typeof globalThis;
|
|
@ -1,45 +1,122 @@
|
|||||||
import { raf } from './environment';
|
import { now, raf, framerate, noop } from './environment';
|
||||||
|
type TaskCallback = (t: number) => boolean;
|
||||||
|
type TaskCanceller = () => void;
|
||||||
|
|
||||||
export interface Task { abort(): void; promise: Promise<void> }
|
let i = 0,
|
||||||
|
j = 0,
|
||||||
|
n = 0,
|
||||||
|
v : TaskCallback;
|
||||||
|
|
||||||
type TaskCallback = (now: number) => boolean | void;
|
let running_frame : Array<TaskCallback> = [],
|
||||||
type TaskEntry = { c: TaskCallback; f: () => void };
|
next_frame : Array<TaskCallback> = [];
|
||||||
|
|
||||||
const tasks = new Set<TaskEntry>();
|
const run = (t: number) => {
|
||||||
|
[running_frame, next_frame] = [next_frame, running_frame];
|
||||||
|
for (t = now(), i = n = 0, j = running_frame.length; i < j; i++) {
|
||||||
|
if ((v = running_frame[i])(t)) {
|
||||||
|
next_frame[n++] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((running_frame.length = 0) < n) {
|
||||||
|
raf(run);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type TimeoutTask = { timestamp: number; callback: (now: number) => void };
|
||||||
|
|
||||||
|
const pending_insert_timed : Array<TimeoutTask> = [],
|
||||||
|
timed_tasks : Array<TimeoutTask> = [];
|
||||||
|
|
||||||
function run_tasks(now: number) {
|
let pending_inserts = false,
|
||||||
tasks.forEach(task => {
|
running_timed = false;
|
||||||
if (!task.c(now)) {
|
|
||||||
tasks.delete(task);
|
const run_timed = (now: number) => {
|
||||||
task.f();
|
let last_index = timed_tasks.length - 1;
|
||||||
|
while (~last_index && now >= timed_tasks[last_index].timestamp) timed_tasks[last_index--].callback(now);
|
||||||
|
if (pending_inserts) {
|
||||||
|
for (let i = 0, j = 0, this_task: TimeoutTask, that_task: TimeoutTask; i < pending_insert_timed.length; i++)
|
||||||
|
if (now >= (this_task = pending_insert_timed[i]).timestamp) this_task.callback(now);
|
||||||
|
else {
|
||||||
|
for (j = last_index; ~j && this_task.timestamp > (that_task = timed_tasks[j]).timestamp; j--)
|
||||||
|
timed_tasks[j + 1] = that_task;
|
||||||
|
timed_tasks[j + 1] = this_task;
|
||||||
|
last_index++;
|
||||||
}
|
}
|
||||||
});
|
pending_insert_timed.length = 0;
|
||||||
|
pending_inserts = false;
|
||||||
|
}
|
||||||
|
return (running_timed = !!(timed_tasks.length = last_index + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsafe_loop = (fn) => {
|
||||||
|
if (0 === n) raf(run);
|
||||||
|
next_frame[n++] = fn;
|
||||||
|
};
|
||||||
|
|
||||||
if (tasks.size !== 0) raf(run_tasks);
|
export const loop = (fn) => {
|
||||||
}
|
let running = true;
|
||||||
|
if (0 === n) raf(run);
|
||||||
|
next_frame[n++] = (t) => !running || fn(t);
|
||||||
|
return () => void (running = false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setFrameTimeout = (callback: (t: number) => void, timestamp: number): TaskCanceller => {
|
||||||
|
const task: TimeoutTask = { callback, timestamp };
|
||||||
|
if (running_timed) {
|
||||||
|
pending_inserts = !!pending_insert_timed.push(task);
|
||||||
|
} else {
|
||||||
|
unsafe_loop(run_timed);
|
||||||
|
running_timed = true;
|
||||||
|
timed_tasks.push(task);
|
||||||
|
}
|
||||||
|
return () => void (task.callback = noop);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For testing purposes only!
|
* Calls function every frame with linear tween from 0 to 1
|
||||||
*/
|
*/
|
||||||
export function clear_loops() {
|
export const setTweenTimeout = (
|
||||||
tasks.clear();
|
stop: (now: number) => void,
|
||||||
}
|
end_time: number,
|
||||||
|
run: (now: number) => void,
|
||||||
|
duration = end_time - now()
|
||||||
|
): TaskCanceller => {
|
||||||
|
let running = true;
|
||||||
|
let t = 0.0;
|
||||||
|
unsafe_loop((now) => {
|
||||||
|
if (!running) return false;
|
||||||
|
t = 1.0 - (end_time - now) / duration;
|
||||||
|
if (t >= 1.0) return run(1), stop(now), false;
|
||||||
|
if (t >= 0.0) run(t);
|
||||||
|
return running;
|
||||||
|
});
|
||||||
|
return (run_last = false) => {
|
||||||
|
// since outros are cancelled in group by a setFrameTimeout
|
||||||
|
// tick(0, 1) has to be called in here
|
||||||
|
if (run_last) run(1);
|
||||||
|
running = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Creates a new task that runs on each raf frame
|
* Calls function every frame with time elapsed in seconds
|
||||||
* until it returns a falsy value or is aborted
|
|
||||||
*/
|
*/
|
||||||
export function loop(callback: TaskCallback): Task {
|
export const onEachFrame = (
|
||||||
let task: TaskEntry;
|
callback: (seconds_elapsed: number) => boolean,
|
||||||
|
on_stop?,
|
||||||
if (tasks.size === 0) raf(run_tasks);
|
max_skipped_frames = 4
|
||||||
|
): TaskCanceller => {
|
||||||
|
max_skipped_frames *= framerate;
|
||||||
|
let lastTime = now();
|
||||||
|
let running = true;
|
||||||
|
const cancel = (t) => (on_stop && on_stop(t), false);
|
||||||
|
unsafe_loop((t: number) => {
|
||||||
|
if (!running) return cancel(t);
|
||||||
|
if (t > lastTime + max_skipped_frames) t = lastTime + max_skipped_frames;
|
||||||
|
return callback((-lastTime + (lastTime = t)) / 1000) ? true : cancel(t);
|
||||||
|
});
|
||||||
|
return () => void (running = false);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
/** tests only */
|
||||||
promise: new Promise(fulfill => {
|
export const clear_loops = () =>
|
||||||
tasks.add(task = { c: callback, f: fulfill });
|
void (next_frame.length = running_frame.length = timed_tasks.length = pending_insert_timed.length = n = i = j = +(running_timed = pending_inserts = false));
|
||||||
}),
|
|
||||||
abort() {
|
|
||||||
tasks.delete(task);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,89 +1,106 @@
|
|||||||
import { run_all } from './utils';
|
|
||||||
import { set_current_component } from './lifecycle';
|
import { set_current_component } from './lifecycle';
|
||||||
|
import { resolved_promise, now } from './environment';
|
||||||
|
import { T$$ } from './Component';
|
||||||
|
|
||||||
export const dirty_components = [];
|
let update_scheduled = false;
|
||||||
export const intros = { enabled: false };
|
let is_flushing = false;
|
||||||
|
|
||||||
|
const dirty_components = [];
|
||||||
|
|
||||||
|
// todo : remove binding_callbacks export
|
||||||
export const binding_callbacks = [];
|
export const binding_callbacks = [];
|
||||||
const render_callbacks = [];
|
const render_callbacks = [];
|
||||||
|
const measure_callbacks = [];
|
||||||
const flush_callbacks = [];
|
const flush_callbacks = [];
|
||||||
|
|
||||||
const resolved_promise = Promise.resolve();
|
// todo : remove add_flush_callback
|
||||||
let update_scheduled = false;
|
export const add_flush_callback = /*#__PURE__*/ Array.prototype.push.bind(flush_callbacks);
|
||||||
|
export const add_measure_callback = /*#__PURE__*/ Array.prototype.push.bind(measure_callbacks);
|
||||||
|
|
||||||
export function schedule_update() {
|
const seen_render_callbacks = new Set();
|
||||||
|
export const add_render_callback = (fn) => {
|
||||||
|
if (!seen_render_callbacks.has(fn)) {
|
||||||
|
seen_render_callbacks.add(fn);
|
||||||
|
render_callbacks.push(fn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const schedule_update = (component) => {
|
||||||
|
dirty_components.push(component);
|
||||||
|
if (!update_scheduled) {
|
||||||
|
update_scheduled = true;
|
||||||
|
resolved_promise.then(flush);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const tick = () => {
|
||||||
if (!update_scheduled) {
|
if (!update_scheduled) {
|
||||||
update_scheduled = true;
|
update_scheduled = true;
|
||||||
resolved_promise.then(flush);
|
resolved_promise.then(flush);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function tick() {
|
|
||||||
schedule_update();
|
|
||||||
return resolved_promise;
|
return resolved_promise;
|
||||||
}
|
};
|
||||||
|
export const flush = () => {
|
||||||
export function add_render_callback(fn) {
|
if (is_flushing) return;
|
||||||
render_callbacks.push(fn);
|
else is_flushing = true;
|
||||||
}
|
|
||||||
|
let i = 0,
|
||||||
export function add_flush_callback(fn) {
|
j = 0,
|
||||||
flush_callbacks.push(fn);
|
t = 0,
|
||||||
}
|
$$: T$$,
|
||||||
|
dirty,
|
||||||
let flushing = false;
|
before_update,
|
||||||
const seen_callbacks = new Set();
|
after_update;
|
||||||
export function flush() {
|
|
||||||
if (flushing) return;
|
|
||||||
flushing = true;
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// first, call beforeUpdate functions
|
while (i < dirty_components.length) {
|
||||||
// and update components
|
({ $$ } = set_current_component(dirty_components[i]));
|
||||||
for (let i = 0; i < dirty_components.length; i += 1) {
|
|
||||||
const component = dirty_components[i];
|
|
||||||
set_current_component(component);
|
|
||||||
update(component.$$);
|
|
||||||
}
|
|
||||||
|
|
||||||
dirty_components.length = 0;
|
// todo : is this check still necessary ?
|
||||||
|
if (null === $$.fragment) continue;
|
||||||
|
|
||||||
while (binding_callbacks.length) binding_callbacks.pop()();
|
/* run reactive statements */
|
||||||
|
$$.update();
|
||||||
|
|
||||||
// then, once components are updated, call
|
/* run beforeUpdate */
|
||||||
// afterUpdate functions. This may cause
|
for (j = 0, { before_update } = $$; j < before_update.length; j++) {
|
||||||
// subsequent updates...
|
before_update[j]();
|
||||||
for (let i = 0; i < render_callbacks.length; i += 1) {
|
}
|
||||||
const callback = render_callbacks[i];
|
|
||||||
|
|
||||||
if (!seen_callbacks.has(callback)) {
|
/* update blocks */
|
||||||
// ...so guard against infinite loops
|
({ dirty } = $$).dirty = [-1];
|
||||||
seen_callbacks.add(callback);
|
if (false !== $$.fragment) $$.fragment.p($$.ctx, dirty);
|
||||||
|
|
||||||
callback();
|
/* schedule afterUpdate */
|
||||||
|
for (j = 0, { after_update } = $$; j < after_update.length; j++) {
|
||||||
|
add_render_callback(after_update[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
}
|
}
|
||||||
|
dirty_components.length = 0;
|
||||||
|
|
||||||
render_callbacks.length = 0;
|
// update bindings [ ...in reverse order (#3145) ]
|
||||||
|
i = binding_callbacks.length;
|
||||||
|
while (i--) binding_callbacks[i]();
|
||||||
|
binding_callbacks.length = i = 0;
|
||||||
|
|
||||||
|
// run afterUpdates
|
||||||
|
// todo : remove every non afterUpdate callback from render_callbacks
|
||||||
|
for (; i < render_callbacks.length; i++) render_callbacks[i]();
|
||||||
|
render_callbacks.length = i = 0;
|
||||||
} while (dirty_components.length);
|
} while (dirty_components.length);
|
||||||
|
seen_render_callbacks.clear();
|
||||||
|
update_scheduled = false;
|
||||||
|
|
||||||
while (flush_callbacks.length) {
|
// measurement callbacks for animations
|
||||||
flush_callbacks.pop()();
|
for (i = 0, j = flush_callbacks.length; i < measure_callbacks.length; i++) {
|
||||||
|
flush_callbacks[j++] = measure_callbacks[i]();
|
||||||
}
|
}
|
||||||
|
measure_callbacks.length = i = 0;
|
||||||
|
|
||||||
update_scheduled = false;
|
// apply styles
|
||||||
flushing = false;
|
// todo : remove every non style callback from flush_callbacks
|
||||||
seen_callbacks.clear();
|
for (t = now(); i < j; i++) flush_callbacks[i](t);
|
||||||
}
|
flush_callbacks.length = i = j = 0;
|
||||||
|
|
||||||
function update($$) {
|
|
||||||
if ($$.fragment !== null) {
|
|
||||||
$$.update();
|
|
||||||
run_all($$.before_update);
|
|
||||||
const dirty = $$.dirty;
|
|
||||||
$$.dirty = [-1];
|
|
||||||
$$.fragment && $$.fragment.p($$.ctx, dirty);
|
|
||||||
|
|
||||||
$$.after_update.forEach(add_render_callback);
|
is_flushing = false;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
@ -1,74 +1,62 @@
|
|||||||
import { element } from './dom';
|
import { framerate } from './environment';
|
||||||
import { raf } from './environment';
|
|
||||||
|
let documents_uid = 0;
|
||||||
interface ExtendedDoc extends Document {
|
let running_animations = 0;
|
||||||
__svelte_stylesheet: CSSStyleSheet;
|
|
||||||
__svelte_rules: Record<string, true>;
|
const document_uid = new Map();
|
||||||
}
|
const document_stylesheets = new Map();
|
||||||
|
|
||||||
const active_docs = new Set<ExtendedDoc>();
|
const current_rules = new Set();
|
||||||
let active = 0;
|
export const animate_css = /*#__PURE__*/ Function.prototype.call.bind(function animate_css(
|
||||||
|
this: HTMLElement,
|
||||||
// https://github.com/darkskyapp/string-hash/blob/master/index.js
|
css: (t: number) => string,
|
||||||
function hash(str: string) {
|
duration: number,
|
||||||
let hash = 5381;
|
delay = 0
|
||||||
let i = str.length;
|
) {
|
||||||
|
if (!document_uid.has(this.ownerDocument)) {
|
||||||
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
document_uid.set(this.ownerDocument, documents_uid++);
|
||||||
return hash >>> 0;
|
document_stylesheets.set(
|
||||||
}
|
this.ownerDocument,
|
||||||
|
this.ownerDocument.head.appendChild(this.ownerDocument.createElement('style')).sheet
|
||||||
export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: number, duration: number, delay: number, ease: (t: number) => number, fn: (t: number, u: number) => string, uid: number = 0) {
|
);
|
||||||
const step = 16.666 / duration;
|
|
||||||
let keyframes = '{\n';
|
|
||||||
|
|
||||||
for (let p = 0; p <= 1; p += step) {
|
|
||||||
const t = a + (b - a) * ease(p);
|
|
||||||
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
|
|
||||||
}
|
}
|
||||||
|
let rule = '{\n';
|
||||||
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
|
for (let t = 0, step = framerate / duration; t < 1; t += step) rule += `${100 * t}%{${css(t)}}\n`;
|
||||||
const name = `__svelte_${hash(rule)}_${uid}`;
|
rule += `100% {${css(1)}}\n}`;
|
||||||
const doc = node.ownerDocument as ExtendedDoc;
|
|
||||||
active_docs.add(doc);
|
// darkskyapp/string-hash
|
||||||
const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet);
|
let i = rule.length, hash = 5381;
|
||||||
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});
|
while (i--) hash = ((hash << 5) - hash) ^ rule.charCodeAt(i);
|
||||||
|
const name = `__svelte_${hash >>> 0}${document_uid.get(this.ownerDocument)}`;
|
||||||
if (!current_rules[name]) {
|
|
||||||
current_rules[name] = true;
|
if (!current_rules.has(name)) {
|
||||||
|
current_rules.add(name);
|
||||||
|
const stylesheet = document_stylesheets.get(this.ownerDocument);
|
||||||
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
|
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
const animation = node.style.animation || '';
|
const previous = this.style.animation;
|
||||||
node.style.animation = `${animation ? `${animation}, ` : ``}${name} ${duration}ms linear ${delay}ms 1 both`;
|
this.style.animation = `${
|
||||||
|
previous ? `${previous}, ` : ''
|
||||||
active += 1;
|
}${duration}ms linear ${delay}ms 1 normal both running ${name}`;
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function delete_rule(node: Element & ElementCSSInlineStyle, name?: string) {
|
running_animations++;
|
||||||
const previous = (node.style.animation || '').split(', ');
|
|
||||||
const next = previous.filter(name
|
|
||||||
? anim => anim.indexOf(name) < 0 // remove specific animation
|
|
||||||
: anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations
|
|
||||||
);
|
|
||||||
const deleted = previous.length - next.length;
|
|
||||||
if (deleted) {
|
|
||||||
node.style.animation = next.join(', ');
|
|
||||||
active -= deleted;
|
|
||||||
if (!active) clear_rules();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clear_rules() {
|
return () => {
|
||||||
raf(() => {
|
const prev = (this.style.animation || '').split(', ');
|
||||||
if (active) return;
|
const next = prev.filter((anim) => !anim.includes(name));
|
||||||
active_docs.forEach(doc => {
|
if (prev.length !== next.length) this.style.animation = next.join(', ');
|
||||||
const stylesheet = doc.__svelte_stylesheet;
|
if (--running_animations === 0) {
|
||||||
|
document_stylesheets.forEach((stylesheet) => {
|
||||||
let i = stylesheet.cssRules.length;
|
let i = stylesheet.cssRules.length;
|
||||||
while (i--) stylesheet.deleteRule(i);
|
while (i--) stylesheet.deleteRule(i);
|
||||||
doc.__svelte_rules = {};
|
|
||||||
});
|
});
|
||||||
active_docs.clear();
|
current_rules.clear();
|
||||||
});
|
if (1 !== documents_uid) {
|
||||||
}
|
document_stylesheets.clear();
|
||||||
|
document_uid.clear();
|
||||||
|
documents_uid = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
@ -1,353 +1,203 @@
|
|||||||
import { identity as linear, is_function, noop, run_all } from './utils';
|
import { CssTransitionConfig } from '../transition';
|
||||||
import { now } from "./environment";
|
import { Fragment } from './Component';
|
||||||
import { loop } from './loop';
|
|
||||||
import { create_rule, delete_rule } from './style_manager';
|
|
||||||
import { custom_event } from './dom';
|
import { custom_event } from './dom';
|
||||||
import { add_render_callback } from './scheduler';
|
import { now, noop } from './environment';
|
||||||
import { TransitionConfig } from '../transition';
|
import { setFrameTimeout, setTweenTimeout } from './loop';
|
||||||
|
import { add_measure_callback } from './scheduler';
|
||||||
|
import { animate_css } from './style_manager';
|
||||||
|
import { linear } from 'svelte/easing';
|
||||||
|
|
||||||
let promise: Promise<void>|null;
|
type TransitionFn = (node: HTMLElement, params: any) => CssTransitionConfig;
|
||||||
|
export type StopResetReverseFn = (t?: number | -1) => StopResetReverseFn | void;
|
||||||
|
|
||||||
function wait() {
|
export const transition_in = (block: Fragment, local?) => {
|
||||||
if (!promise) {
|
if (!block || !block.i) return;
|
||||||
promise = Promise.resolve();
|
|
||||||
promise.then(() => {
|
|
||||||
promise = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispatch(node: Element, direction: boolean, kind: 'start' | 'end') {
|
|
||||||
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
const outroing = new Set();
|
|
||||||
let outros;
|
|
||||||
|
|
||||||
export function group_outros() {
|
|
||||||
outros = {
|
|
||||||
r: 0, // remaining outros
|
|
||||||
c: [], // callbacks
|
|
||||||
p: outros // parent group
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function check_outros() {
|
|
||||||
if (!outros.r) {
|
|
||||||
run_all(outros.c);
|
|
||||||
}
|
|
||||||
outros = outros.p;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transition_in(block, local?: 0 | 1) {
|
|
||||||
if (block && block.i) {
|
|
||||||
outroing.delete(block);
|
outroing.delete(block);
|
||||||
block.i(local);
|
block.i(local);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export function transition_out(block, local: 0 | 1, detach: 0 | 1, callback) {
|
export const transition_out = (block: Fragment, local?) => {
|
||||||
if (block && block.o) {
|
if (!block || !block.o || outroing.has(block)) return;
|
||||||
if (outroing.has(block)) return;
|
|
||||||
outroing.add(block);
|
outroing.add(block);
|
||||||
|
block.o(local);
|
||||||
outros.c.push(() => {
|
};
|
||||||
|
type TransitionGroup = {
|
||||||
|
/* parent group */ p: TransitionGroup;
|
||||||
|
/* callbacks */ c: ((cancelled: boolean) => void)[];
|
||||||
|
/* running outros */ r: number;
|
||||||
|
/* stop callbacks */ s: ((t: number) => void)[];
|
||||||
|
/* outro timeout */ t: number;
|
||||||
|
};
|
||||||
|
let transition_group: TransitionGroup;
|
||||||
|
const outroing = new Set();
|
||||||
|
export const group_transition_out = (fn) => {
|
||||||
|
const c = [];
|
||||||
|
const current_group = (transition_group = { p: transition_group, c, r: 0, s: [], t: 0 });
|
||||||
|
fn((block, callback, detach = true) => {
|
||||||
|
if (!block || !block.o || outroing.has(block)) return;
|
||||||
|
outroing.add(block);
|
||||||
|
c.push((cancelled = false) => {
|
||||||
|
if (cancelled) {
|
||||||
|
// block was destroyed before outro ended
|
||||||
|
outroing.delete(block);
|
||||||
|
} else if (outroing.has(block)) {
|
||||||
outroing.delete(block);
|
outroing.delete(block);
|
||||||
if (callback) {
|
|
||||||
if (detach) block.d(1);
|
if (detach) block.d(1);
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
block.o(1);
|
||||||
block.o(local);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const null_transition: TransitionConfig = { duration: 0 };
|
|
||||||
|
|
||||||
type TransitionFn = (node: Element, params: any) => TransitionConfig;
|
|
||||||
|
|
||||||
export function create_in_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
|
|
||||||
let config = fn(node, params);
|
|
||||||
let running = false;
|
|
||||||
let animation_name;
|
|
||||||
let task;
|
|
||||||
let uid = 0;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
if (animation_name) delete_rule(node, animation_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function go() {
|
|
||||||
const {
|
|
||||||
delay = 0,
|
|
||||||
duration = 300,
|
|
||||||
easing = linear,
|
|
||||||
tick = noop,
|
|
||||||
css
|
|
||||||
} = config || null_transition;
|
|
||||||
|
|
||||||
if (css) animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++);
|
|
||||||
tick(0, 1);
|
|
||||||
|
|
||||||
const start_time = now() + delay;
|
|
||||||
const end_time = start_time + duration;
|
|
||||||
|
|
||||||
if (task) task.abort();
|
|
||||||
running = true;
|
|
||||||
|
|
||||||
add_render_callback(() => dispatch(node, true, 'start'));
|
|
||||||
|
|
||||||
task = loop(now => {
|
|
||||||
if (running) {
|
|
||||||
if (now >= end_time) {
|
|
||||||
tick(1, 0);
|
|
||||||
|
|
||||||
dispatch(node, true, 'end');
|
|
||||||
|
|
||||||
cleanup();
|
|
||||||
return running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now >= start_time) {
|
|
||||||
const t = easing((now - start_time) / duration);
|
|
||||||
tick(t, 1 - t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return running;
|
|
||||||
});
|
});
|
||||||
}
|
if (!current_group.r) for (let i = 0; i < c.length; i++) c[i]();
|
||||||
|
transition_group = transition_group.p;
|
||||||
let started = false;
|
};
|
||||||
|
|
||||||
return {
|
const swap = (fn, rx) =>
|
||||||
start() {
|
fn.length === 1
|
||||||
if (started) return;
|
? rx & tx.intro
|
||||||
|
? fn
|
||||||
delete_rule(node);
|
: (t) => fn(1 - t)
|
||||||
|
: rx & tx.intro
|
||||||
if (is_function(config)) {
|
? (t) => fn(t, 1 - t)
|
||||||
config = config();
|
: (t) => fn(1 - t, t);
|
||||||
wait().then(go);
|
|
||||||
} else {
|
const mirrored = (fn, rx, easing) => {
|
||||||
go();
|
const run = swap(fn, rx);
|
||||||
}
|
return easing
|
||||||
},
|
? rx & tx.intro
|
||||||
|
? (t) => run(easing(t))
|
||||||
invalidate() {
|
: (t) => run(1 - easing(1 - t))
|
||||||
started = false;
|
: run;
|
||||||
},
|
};
|
||||||
|
const reversed = (fn, rx, easing, start = 0, end = 1) => {
|
||||||
end() {
|
const run = swap(fn, rx);
|
||||||
if (running) {
|
const difference = end - start;
|
||||||
cleanup();
|
return easing
|
||||||
running = false;
|
? (t) => run(start + difference * easing(t))
|
||||||
}
|
: (t) => run(start + difference * t);
|
||||||
}
|
};
|
||||||
};
|
export const enum tx {
|
||||||
|
intro = 1,
|
||||||
|
outro = 2,
|
||||||
|
reverse = 3,
|
||||||
|
bidirectional = 4,
|
||||||
|
animation = 8,
|
||||||
}
|
}
|
||||||
|
export const run_transition = /*#__PURE__*/ Function.prototype.call.bind(function transition(
|
||||||
|
this: HTMLElement,
|
||||||
|
fn: TransitionFn,
|
||||||
|
rx: tx,
|
||||||
|
params = {},
|
||||||
|
/* internal to this file */
|
||||||
|
elapsed_duration = 0,
|
||||||
|
delay_left = -1,
|
||||||
|
elapsed_ratio = 0
|
||||||
|
) {
|
||||||
|
let config;
|
||||||
|
|
||||||
export function create_out_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any) {
|
|
||||||
let config = fn(node, params);
|
|
||||||
let running = true;
|
let running = true;
|
||||||
let animation_name;
|
|
||||||
|
|
||||||
const group = outros;
|
|
||||||
|
|
||||||
group.r += 1;
|
let cancel_css,
|
||||||
|
cancel_raf;
|
||||||
|
|
||||||
function go() {
|
let start_time = 0,
|
||||||
const {
|
end_time = 0;
|
||||||
delay = 0,
|
|
||||||
duration = 300,
|
|
||||||
easing = linear,
|
|
||||||
tick = noop,
|
|
||||||
css
|
|
||||||
} = config || null_transition;
|
|
||||||
|
|
||||||
if (css) animation_name = create_rule(node, 1, 0, duration, delay, easing, css);
|
const current_group = transition_group;
|
||||||
|
if (rx & tx.outro) current_group.r++;
|
||||||
|
|
||||||
const start_time = now() + delay;
|
add_measure_callback(() => {
|
||||||
const end_time = start_time + duration;
|
if (null === (config = fn(this, params))) return noop;
|
||||||
|
return (current_frame_time) => {
|
||||||
|
if (false === running) return;
|
||||||
|
|
||||||
add_render_callback(() => dispatch(node, false, 'start'));
|
let { delay = 0, duration = 300, easing, tick, css, strategy = 'reverse' }: CssTransitionConfig =
|
||||||
|
'function' === typeof config ? (config = config()) : config;
|
||||||
|
|
||||||
loop(now => {
|
const solver = 'reverse' === strategy ? reversed : mirrored;
|
||||||
if (running) {
|
const runner = (fn) => solver(fn, rx, easing, elapsed_ratio, 1);
|
||||||
if (now >= end_time) {
|
|
||||||
tick(0, 1);
|
|
||||||
|
|
||||||
dispatch(node, false, 'end');
|
if (rx & tx.bidirectional) {
|
||||||
|
if (-1 !== delay_left) delay = delay_left;
|
||||||
if (!--group.r) {
|
if (solver === reversed) duration -= elapsed_duration;
|
||||||
// this will result in `end()` being called,
|
else if (solver === mirrored) delay -= elapsed_duration;
|
||||||
// so we don't need to clean up here
|
|
||||||
run_all(group.c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
end_time = (start_time = current_frame_time + delay) + duration;
|
||||||
}
|
|
||||||
|
|
||||||
if (now >= start_time) {
|
if (0 === (rx & tx.animation)) {
|
||||||
const t = easing((now - start_time) / duration);
|
this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}trostart`));
|
||||||
tick(1 - t, t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return running;
|
if (css) cancel_css = animate_css(this, runner(css), duration, delay);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_function(config)) {
|
if (rx & tx.outro) {
|
||||||
wait().then(() => {
|
if (current_group.s.push(stop) === current_group.r) {
|
||||||
// @ts-ignore
|
setFrameTimeout((t) => {
|
||||||
config = config();
|
for (let i = 0; i < current_group.s.length; i++) current_group.s[i](t);
|
||||||
go();
|
}, Math.max(end_time, current_group.t));
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
go();
|
current_group.t = Math.max(end_time, current_group.t);
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
end(reset) {
|
|
||||||
if (reset && config.tick) {
|
|
||||||
config.tick(1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (running) {
|
|
||||||
if (animation_name) delete_rule(node, animation_name);
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
if (tick) cancel_raf = setTweenTimeout(noop, end_time, runner(tick), duration);
|
||||||
|
} else {
|
||||||
|
cancel_raf = tick ? setTweenTimeout(stop, end_time, runner(tick), duration) : setFrameTimeout(stop, end_time);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
export function create_bidirectional_transition(node: Element & ElementCSSInlineStyle, fn: TransitionFn, params: any, intro: boolean) {
|
const stop: StopResetReverseFn = (t?: number | 1 | -1) => {
|
||||||
let config = fn(node, params);
|
// resetting `out:` in intros
|
||||||
|
if (t === 1 && rx & tx.outro && 0 === (rx & tx.bidirectional) && 'tick' in config) config.tick(1, 0);
|
||||||
|
|
||||||
let t = intro ? 0 : 1;
|
if (false === running) return;
|
||||||
|
else running = false;
|
||||||
|
|
||||||
let running_program = null;
|
if (cancel_css) cancel_css();
|
||||||
let pending_program = null;
|
if (cancel_raf) cancel_raf(rx & tx.outro && t >= end_time);
|
||||||
let animation_name = null;
|
|
||||||
|
|
||||||
function clear_animation() {
|
if (rx & tx.animation) return;
|
||||||
if (animation_name) delete_rule(node, animation_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function init(program, duration) {
|
if (t >= end_time) this.dispatchEvent(custom_event(`${rx & tx.intro ? 'in' : 'ou'}troend`));
|
||||||
const d = program.b - t;
|
|
||||||
duration *= Math.abs(d);
|
|
||||||
|
|
||||||
return {
|
if (rx & tx.outro && !--current_group.r)
|
||||||
a: t,
|
for (let i = 0; i < current_group.c.length; i++) current_group.c[i](t === void 0);
|
||||||
b: program.b,
|
|
||||||
d,
|
|
||||||
duration,
|
|
||||||
start: program.start,
|
|
||||||
end: program.start + duration,
|
|
||||||
group: program.group
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function go(b) {
|
if (0 === (rx & tx.bidirectional)) return;
|
||||||
const {
|
|
||||||
delay = 0,
|
|
||||||
duration = 300,
|
|
||||||
easing = linear,
|
|
||||||
tick = noop,
|
|
||||||
css
|
|
||||||
} = config || null_transition;
|
|
||||||
|
|
||||||
const program = {
|
if (-1 === t)
|
||||||
start: now() + delay,
|
return (
|
||||||
b
|
(t = now()) < end_time &&
|
||||||
|
run_transition(
|
||||||
|
this,
|
||||||
|
() => config,
|
||||||
|
rx ^ tx.reverse,
|
||||||
|
params,
|
||||||
|
end_time - t,
|
||||||
|
start_time > t ? start_time - t : 0,
|
||||||
|
(1 - elapsed_ratio) * (1 - (config.easing || linear)(1 - (end_time - t) / (end_time - start_time)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
else running_bidi.delete(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!b) {
|
return stop;
|
||||||
// @ts-ignore todo: improve typings
|
});
|
||||||
program.group = outros;
|
|
||||||
outros.r += 1;
|
const running_bidi: Map<HTMLElement, StopResetReverseFn> = new Map();
|
||||||
}
|
export const run_bidirectional_transition = /*#__PURE__*/ Function.prototype.call.bind(function bidirectional(
|
||||||
|
this: HTMLElement,
|
||||||
if (running_program) {
|
fn: TransitionFn,
|
||||||
pending_program = program;
|
rx: tx.intro | tx.outro,
|
||||||
} else {
|
params: any
|
||||||
// if this is an intro, and there's a delay, we need to do
|
) {
|
||||||
// an initial tick and/or apply CSS animation immediately
|
let cancel;
|
||||||
if (css) {
|
running_bidi.set(
|
||||||
clear_animation();
|
this,
|
||||||
animation_name = create_rule(node, t, b, duration, delay, easing, css);
|
(cancel =
|
||||||
}
|
(running_bidi.has(this) && running_bidi.get(this)(-1)) || run_transition(this, fn, rx | tx.bidirectional, params))
|
||||||
|
);
|
||||||
if (b) tick(0, 1);
|
return cancel;
|
||||||
|
});
|
||||||
running_program = init(program, duration);
|
export const run_duration = (duration, value1, value2?): number =>
|
||||||
add_render_callback(() => dispatch(node, b, 'start'));
|
typeof duration === 'function' ? duration(value1, value2) : duration;
|
||||||
|
|
||||||
loop(now => {
|
|
||||||
if (pending_program && now > pending_program.start) {
|
|
||||||
running_program = init(pending_program, duration);
|
|
||||||
pending_program = null;
|
|
||||||
|
|
||||||
dispatch(node, running_program.b, 'start');
|
|
||||||
|
|
||||||
if (css) {
|
|
||||||
clear_animation();
|
|
||||||
animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (running_program) {
|
|
||||||
if (now >= running_program.end) {
|
|
||||||
tick(t = running_program.b, 1 - t);
|
|
||||||
dispatch(node, running_program.b, 'end');
|
|
||||||
|
|
||||||
if (!pending_program) {
|
|
||||||
// we're done
|
|
||||||
if (running_program.b) {
|
|
||||||
// intro — we can tidy up immediately
|
|
||||||
clear_animation();
|
|
||||||
} else {
|
|
||||||
// outro — needs to be coordinated
|
|
||||||
if (!--running_program.group.r) run_all(running_program.group.c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
running_program = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (now >= running_program.start) {
|
|
||||||
const p = now - running_program.start;
|
|
||||||
t = running_program.a + running_program.d * easing(p / running_program.duration);
|
|
||||||
tick(t, 1 - t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!(running_program || pending_program);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
run(b) {
|
|
||||||
if (is_function(config)) {
|
|
||||||
wait().then(() => {
|
|
||||||
// @ts-ignore
|
|
||||||
config = config();
|
|
||||||
go(b);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
go(b);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
end() {
|
|
||||||
clear_animation();
|
|
||||||
running_program = pending_program = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in new issue