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