mirror of https://github.com/sveltejs/svelte
parent
feadc6cfd0
commit
b87f930002
@ -0,0 +1,38 @@
|
||||
const tasks = new Set();
|
||||
let running = false;
|
||||
|
||||
function run_tasks() {
|
||||
tasks.forEach(task => {
|
||||
if (!task[0](window.performance.now())) {
|
||||
tasks.delete(task);
|
||||
task[1]();
|
||||
}
|
||||
});
|
||||
|
||||
running = tasks.size > 0;
|
||||
if (running) requestAnimationFrame(run_tasks);
|
||||
}
|
||||
|
||||
export function clear_loops() {
|
||||
// for testing...
|
||||
tasks.forEach(task => tasks.delete(task));
|
||||
running = false;
|
||||
}
|
||||
|
||||
export function loop(fn) {
|
||||
let task;
|
||||
|
||||
if (!running) {
|
||||
running = true;
|
||||
requestAnimationFrame(run_tasks);
|
||||
}
|
||||
|
||||
return {
|
||||
promise: new Promise(fulfil => {
|
||||
tasks.add(task = [fn, fulfil]);
|
||||
}),
|
||||
abort() {
|
||||
tasks.delete(task);
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
import { createElement } from './dom.js';
|
||||
|
||||
let stylesheet;
|
||||
let active = 0;
|
||||
let current_rules = {};
|
||||
|
||||
// https://github.com/darkskyapp/string-hash/blob/master/index.js
|
||||
function hash(str) {
|
||||
let hash = 5381;
|
||||
let i = str.length;
|
||||
|
||||
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
||||
return hash >>> 0;
|
||||
}
|
||||
|
||||
export function create_rule({ a, b, delta, duration }, ease, fn) {
|
||||
const step = 16.666 / duration;
|
||||
let keyframes = '{\n';
|
||||
|
||||
for (let p = 0; p <= 1; p += step) {
|
||||
const t = a + delta * ease(p);
|
||||
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
|
||||
}
|
||||
|
||||
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
|
||||
const name = `__svelte_${hash(rule)}`;
|
||||
|
||||
if (!current_rules[name]) {
|
||||
if (!stylesheet) {
|
||||
const style = createElement('style');
|
||||
document.head.appendChild(style);
|
||||
stylesheet = style.sheet;
|
||||
}
|
||||
|
||||
current_rules[name] = true;
|
||||
stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);
|
||||
}
|
||||
|
||||
active += 1;
|
||||
return name;
|
||||
}
|
||||
|
||||
export function delete_rule(node, name) {
|
||||
node.style.animation = node.style.animation
|
||||
.split(', ')
|
||||
.filter(anim => anim && anim.indexOf(name) === -1)
|
||||
.join(', ');
|
||||
|
||||
if (--active <= 0) clear_rules();
|
||||
}
|
||||
|
||||
export function clear_rules() {
|
||||
let i = stylesheet.cssRules.length;
|
||||
while (i--) stylesheet.deleteRule(i);
|
||||
current_rules = {};
|
||||
}
|
@ -1,256 +1,175 @@
|
||||
import { createElement } from './dom.js';
|
||||
import { noop, run } from './utils.js';
|
||||
|
||||
export function linear(t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
export function generateRule({ a, b, delta, duration }, ease, fn) {
|
||||
const step = 16.666 / duration;
|
||||
let keyframes = '{\n';
|
||||
|
||||
for (let p = 0; p <= 1; p += step) {
|
||||
const t = a + delta * ease(p);
|
||||
keyframes += p * 100 + `%{${fn(t, 1 - t)}}\n`;
|
||||
import { identity as linear, noop, run } from './utils.js';
|
||||
import { loop } from './loop.js';
|
||||
import { create_rule, delete_rule } from './style_manager.js';
|
||||
|
||||
let promise;
|
||||
|
||||
function wait() {
|
||||
if (!promise) {
|
||||
promise = Promise.resolve();
|
||||
promise.then(() => {
|
||||
promise = null;
|
||||
});
|
||||
}
|
||||
|
||||
return keyframes + `100% {${fn(b, 1 - b)}}\n}`;
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://github.com/darkskyapp/string-hash/blob/master/index.js
|
||||
export function hash(str) {
|
||||
let hash = 5381;
|
||||
let i = str.length;
|
||||
let outros;
|
||||
|
||||
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
|
||||
return hash >>> 0;
|
||||
export function group_outros() {
|
||||
outros = {
|
||||
remaining: 0,
|
||||
callbacks: []
|
||||
};
|
||||
}
|
||||
|
||||
export function wrapTransition(component, node, fn, params, intro) {
|
||||
let obj = fn.call(component, node, params);
|
||||
let duration;
|
||||
let ease;
|
||||
export function create_transition(node, fn, params, intro) {
|
||||
let config = fn(node, params);
|
||||
let cssText;
|
||||
|
||||
let initialised = false;
|
||||
let ready = !intro;
|
||||
let t = intro ? 0 : 1;
|
||||
|
||||
return {
|
||||
t: intro ? 0 : 1,
|
||||
running: false,
|
||||
program: null,
|
||||
pending: null,
|
||||
|
||||
run(b, callback) {
|
||||
if (typeof obj === 'function') {
|
||||
transitionManager.wait().then(() => {
|
||||
obj = obj();
|
||||
this._run(b, callback);
|
||||
});
|
||||
} else {
|
||||
this._run(b, callback);
|
||||
}
|
||||
},
|
||||
let running = false;
|
||||
let running_program = null;
|
||||
let pending_program = null;
|
||||
|
||||
_run(b, callback) {
|
||||
duration = obj.duration || 300;
|
||||
ease = obj.easing || linear;
|
||||
function start(program, delay, duration, easing) {
|
||||
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`));
|
||||
|
||||
const program = {
|
||||
start: window.performance.now() + (obj.delay || 0),
|
||||
b,
|
||||
callback: callback || noop
|
||||
};
|
||||
program.a = t;
|
||||
program.d = program.b - program.a;
|
||||
program.duration = duration * Math.abs(program.b - program.a);
|
||||
program.end = program.start + program.duration;
|
||||
|
||||
if (intro && !initialised) {
|
||||
if (obj.css && obj.delay) {
|
||||
cssText = node.style.cssText;
|
||||
node.style.cssText += obj.css(0, 1);
|
||||
}
|
||||
if (config.css) {
|
||||
if (delay) node.style.cssText = cssText;
|
||||
|
||||
if (obj.tick) obj.tick(0, 1);
|
||||
initialised = true;
|
||||
}
|
||||
program.name = create_rule(program, easing, config.css);
|
||||
|
||||
if (!b) {
|
||||
program.group = outros.current;
|
||||
outros.current.remaining += 1;
|
||||
}
|
||||
node.style.animation = (node.style.animation || '')
|
||||
.split(', ')
|
||||
.filter(anim => anim && (program.d < 0 || !/__svelte/.test(anim)))
|
||||
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
if (obj.delay) {
|
||||
this.pending = program;
|
||||
} else {
|
||||
this.start(program);
|
||||
}
|
||||
running_program = program;
|
||||
pending_program = null;
|
||||
}
|
||||
|
||||
if (!this.running) {
|
||||
this.running = true;
|
||||
transitionManager.add(this);
|
||||
}
|
||||
},
|
||||
function done() {
|
||||
const program = running_program;
|
||||
running_program = null;
|
||||
|
||||
start(program) {
|
||||
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}start`));
|
||||
t = program.b;
|
||||
|
||||
program.a = this.t;
|
||||
program.delta = program.b - program.a;
|
||||
program.duration = duration * Math.abs(program.b - program.a);
|
||||
program.end = program.start + program.duration;
|
||||
if (config.tick) config.tick(t, 1 - t);
|
||||
|
||||
if (obj.css) {
|
||||
if (obj.delay) node.style.cssText = cssText;
|
||||
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}end`));
|
||||
|
||||
const rule = generateRule(program, ease, obj.css);
|
||||
transitionManager.addRule(rule, program.name = '__svelte_' + hash(rule));
|
||||
if (!program.b && !program.invalidated) {
|
||||
program.group.callbacks.push(() => {
|
||||
program.callback();
|
||||
if (config.css) delete_rule(node, program.name);
|
||||
});
|
||||
|
||||
node.style.animation = (node.style.animation || '')
|
||||
.split(', ')
|
||||
.filter(anim => anim && (program.delta < 0 || !/__svelte/.test(anim)))
|
||||
.concat(`${program.name} ${program.duration}ms linear 1 forwards`)
|
||||
.join(', ');
|
||||
if (--program.group.remaining === 0) {
|
||||
program.group.callbacks.forEach(run);
|
||||
}
|
||||
} else {
|
||||
if (config.css) delete_rule(node, program.name);
|
||||
}
|
||||
|
||||
this.program = program;
|
||||
this.pending = null;
|
||||
},
|
||||
running = !!pending_program;
|
||||
}
|
||||
|
||||
update(now) {
|
||||
const program = this.program;
|
||||
if (!program) return;
|
||||
function go(b, callback) {
|
||||
const {
|
||||
delay = 0,
|
||||
duration = 300,
|
||||
easing = linear
|
||||
} = config;
|
||||
|
||||
const program = {
|
||||
start: window.performance.now() + delay,
|
||||
b,
|
||||
callback
|
||||
};
|
||||
|
||||
if (!ready) {
|
||||
if (config.css && delay) {
|
||||
cssText = node.style.cssText;
|
||||
node.style.cssText += config.css(0, 1);
|
||||
}
|
||||
|
||||
const p = now - program.start;
|
||||
this.t = program.a + program.delta * ease(p / program.duration);
|
||||
if (obj.tick) obj.tick(this.t, 1 - this.t);
|
||||
},
|
||||
if (config.tick) config.tick(0, 1);
|
||||
ready = true;
|
||||
}
|
||||
|
||||
done() {
|
||||
const program = this.program;
|
||||
this.t = program.b;
|
||||
if (!b) {
|
||||
program.group = outros;
|
||||
outros.remaining += 1;
|
||||
}
|
||||
|
||||
if (obj.tick) obj.tick(this.t, 1 - this.t);
|
||||
if (delay) {
|
||||
pending_program = program;
|
||||
} else {
|
||||
start(program, delay, duration, easing);
|
||||
}
|
||||
|
||||
node.dispatchEvent(new window.CustomEvent(`${program.b ? 'intro' : 'outro'}end`));
|
||||
if (!running) {
|
||||
running = true;
|
||||
|
||||
if (!program.b && !program.invalidated) {
|
||||
program.group.callbacks.push(() => {
|
||||
program.callback();
|
||||
if (obj.css) transitionManager.deleteRule(node, program.name);
|
||||
});
|
||||
const { abort, promise } = loop(now => {
|
||||
if (running_program && now >= running_program.end) {
|
||||
done();
|
||||
}
|
||||
|
||||
if (--program.group.remaining === 0) {
|
||||
program.group.callbacks.forEach(run);
|
||||
if (pending_program && now >= pending_program.start) {
|
||||
start(pending_program, delay, duration, easing);
|
||||
}
|
||||
|
||||
if (running) {
|
||||
if (running_program) {
|
||||
const p = now - running_program.start;
|
||||
t = running_program.a + running_program.d * easing(p / running_program.duration);
|
||||
if (config.tick) config.tick(t, 1 - t);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
run(b, callback = noop) {
|
||||
if (typeof config === 'function') {
|
||||
wait().then(() => {
|
||||
config = config();
|
||||
go(b, callback);
|
||||
});
|
||||
} else {
|
||||
if (obj.css) transitionManager.deleteRule(node, program.name);
|
||||
go(b, callback);
|
||||
}
|
||||
|
||||
this.running = !!this.pending;
|
||||
},
|
||||
|
||||
abort(reset) {
|
||||
if (this.program) {
|
||||
if (reset && obj.tick) obj.tick(1, 0);
|
||||
if (obj.css) transitionManager.deleteRule(node, this.program.name);
|
||||
this.program = this.pending = null;
|
||||
this.running = false;
|
||||
if (reset && config.tick) config.tick(1, 0);
|
||||
|
||||
if (running_program) {
|
||||
if (config.css) delete_rule(node, running_program.name);
|
||||
running_program = pending_program = null;
|
||||
running = false;
|
||||
}
|
||||
},
|
||||
|
||||
invalidate() {
|
||||
if (this.program) {
|
||||
this.program.invalidated = true;
|
||||
if (running_program) {
|
||||
running_program.invalidated = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export let outros = {};
|
||||
|
||||
export function groupOutros() {
|
||||
outros.current = {
|
||||
remaining: 0,
|
||||
callbacks: []
|
||||
};
|
||||
}
|
||||
|
||||
export var transitionManager = {
|
||||
running: false,
|
||||
transitions: [],
|
||||
bound: null,
|
||||
stylesheet: null,
|
||||
activeRules: {},
|
||||
promise: null,
|
||||
|
||||
add(transition) {
|
||||
this.transitions.push(transition);
|
||||
|
||||
if (!this.running) {
|
||||
this.running = true;
|
||||
requestAnimationFrame(this.bound || (this.bound = this.next.bind(this)));
|
||||
}
|
||||
},
|
||||
|
||||
addRule(rule, name) {
|
||||
if (!this.stylesheet) {
|
||||
const style = createElement('style');
|
||||
document.head.appendChild(style);
|
||||
transitionManager.stylesheet = style.sheet;
|
||||
}
|
||||
|
||||
if (!this.activeRules[name]) {
|
||||
this.activeRules[name] = true;
|
||||
this.stylesheet.insertRule(`@keyframes ${name} ${rule}`, this.stylesheet.cssRules.length);
|
||||
}
|
||||
},
|
||||
|
||||
next() {
|
||||
this.running = false;
|
||||
|
||||
const now = window.performance.now();
|
||||
let i = this.transitions.length;
|
||||
|
||||
while (i--) {
|
||||
const transition = this.transitions[i];
|
||||
|
||||
if (transition.program && now >= transition.program.end) {
|
||||
transition.done();
|
||||
}
|
||||
|
||||
if (transition.pending && now >= transition.pending.start) {
|
||||
transition.start(transition.pending);
|
||||
}
|
||||
|
||||
if (transition.running) {
|
||||
transition.update(now);
|
||||
this.running = true;
|
||||
} else if (!transition.pending) {
|
||||
this.transitions.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
requestAnimationFrame(this.bound);
|
||||
} else if (this.stylesheet) {
|
||||
let i = this.stylesheet.cssRules.length;
|
||||
while (i--) this.stylesheet.deleteRule(i);
|
||||
this.activeRules = {};
|
||||
}
|
||||
},
|
||||
|
||||
deleteRule(node, name) {
|
||||
node.style.animation = node.style.animation
|
||||
.split(', ')
|
||||
.filter(anim => anim && anim.indexOf(name) === -1)
|
||||
.join(', ');
|
||||
},
|
||||
|
||||
wait() {
|
||||
if (!transitionManager.promise) {
|
||||
transitionManager.promise = Promise.resolve();
|
||||
transitionManager.promise.then(() => {
|
||||
transitionManager.promise = null;
|
||||
});
|
||||
}
|
||||
|
||||
return transitionManager.promise;
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export * from './spring.js';
|
||||
export * from './tweened.js';
|
@ -0,0 +1,155 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { loop } from 'svelte/internal';
|
||||
import { is_date } from './utils.js';
|
||||
|
||||
function get_initial_velocity(value) {
|
||||
if (typeof value === 'number' || is_date(value)) return 0;
|
||||
|
||||
if (Array.isArray(value)) return value.map(get_initial_velocity);
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
const velocities = {};
|
||||
for (const k in value) velocities[k] = get_initial_velocity(value[k]);
|
||||
return velocities;
|
||||
}
|
||||
|
||||
throw new Error(`Cannot spring ${typeof value} values`);
|
||||
}
|
||||
|
||||
function get_threshold(value, target_value, precision) {
|
||||
if (typeof value === 'number' || is_date(value)) return precision * Math.abs((target_value - value));
|
||||
|
||||
if (Array.isArray(value)) return value.map((v, i) => get_threshold(v, target_value[i], precision));
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
const threshold = {};
|
||||
for (const k in value) threshold[k] = get_threshold(value[k], target_value[k], precision);
|
||||
return threshold;
|
||||
}
|
||||
|
||||
throw new Error(`Cannot spring ${typeof value} values`);
|
||||
}
|
||||
|
||||
function tick_spring(velocity, current_value, target_value, stiffness, damping, multiplier, threshold) {
|
||||
let settled = true;
|
||||
let value;
|
||||
|
||||
if (typeof current_value === 'number' || is_date(current_value)) {
|
||||
const delta = target_value - current_value;
|
||||
const spring = stiffness * delta;
|
||||
const damper = damping * velocity;
|
||||
|
||||
const acceleration = spring - damper;
|
||||
|
||||
velocity += acceleration;
|
||||
const d = velocity * multiplier;
|
||||
|
||||
if (is_date(current_value)) {
|
||||
value = new Date(current_value.getTime() + d);
|
||||
} else {
|
||||
value = current_value + d;
|
||||
}
|
||||
|
||||
if (Math.abs(d) > threshold) settled = false;
|
||||
}
|
||||
|
||||
else if (Array.isArray(current_value)) {
|
||||
value = current_value.map((v, i) => {
|
||||
const result = tick_spring(
|
||||
velocity[i],
|
||||
v,
|
||||
target_value[i],
|
||||
stiffness,
|
||||
damping,
|
||||
multiplier,
|
||||
threshold[i]
|
||||
);
|
||||
|
||||
velocity[i] = result.velocity;
|
||||
if (!result.settled) settled = false;
|
||||
return result.value;
|
||||
});
|
||||
}
|
||||
|
||||
else if (typeof current_value === 'object') {
|
||||
value = {};
|
||||
for (const k in current_value) {
|
||||
const result = tick_spring(
|
||||
velocity[k],
|
||||
current_value[k],
|
||||
target_value[k],
|
||||
stiffness,
|
||||
damping,
|
||||
multiplier,
|
||||
threshold[k]
|
||||
);
|
||||
|
||||
velocity[k] = result.velocity;
|
||||
if (!result.settled) settled = false;
|
||||
value[k] = result.value;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
throw new Error(`Cannot spring ${typeof value} values`);
|
||||
}
|
||||
|
||||
return { velocity, value, settled };
|
||||
}
|
||||
|
||||
export function spring(value, opts = {}) {
|
||||
const store = writable(value);
|
||||
|
||||
const { stiffness = 0.15, damping = 0.8 } = opts;
|
||||
const velocity = get_initial_velocity(value);
|
||||
|
||||
let task;
|
||||
let target_value = value;
|
||||
let last_time;
|
||||
let settled;
|
||||
let threshold;
|
||||
|
||||
function set(new_value) {
|
||||
target_value = new_value;
|
||||
threshold = get_threshold(value, target_value, 0.000001); // TODO make precision configurable?
|
||||
|
||||
if (!task) {
|
||||
last_time = window.performance.now();
|
||||
settled = false;
|
||||
|
||||
task = loop(now=> {
|
||||
({ value, settled } = tick_spring(
|
||||
velocity,
|
||||
value,
|
||||
target_value,
|
||||
spring.stiffness,
|
||||
spring.damping,
|
||||
(now - last_time) * 60 / 1000,
|
||||
threshold
|
||||
));
|
||||
|
||||
last_time = now;
|
||||
|
||||
if (settled) {
|
||||
value = target_value;
|
||||
task = null;
|
||||
}
|
||||
|
||||
store.set(value);
|
||||
return !settled;
|
||||
});
|
||||
}
|
||||
|
||||
return task.promise;
|
||||
}
|
||||
|
||||
const spring = {
|
||||
set,
|
||||
update: fn => set(fn(target_value, value)),
|
||||
subscribe: store.subscribe,
|
||||
stiffness,
|
||||
damping
|
||||
};
|
||||
|
||||
return spring;
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { assign, loop } from 'svelte/internal';
|
||||
import { linear } from 'svelte/easing';
|
||||
import { is_date } from './utils.js';
|
||||
|
||||
function get_interpolator(a, b) {
|
||||
if (a === b || a !== a) return () => a;
|
||||
|
||||
const type = typeof a;
|
||||
|
||||
if (type !== typeof b || Array.isArray(a) !== Array.isArray(b)) {
|
||||
throw new Error('Cannot interpolate values of different type');
|
||||
}
|
||||
|
||||
if (Array.isArray(a)) {
|
||||
const arr = b.map((bi, i) => {
|
||||
return get_interpolator(a[i], bi);
|
||||
});
|
||||
|
||||
return t => arr.map(fn => fn(t));
|
||||
}
|
||||
|
||||
if (type === 'object') {
|
||||
if (!a || !b) throw new Error('Object cannot be null');
|
||||
|
||||
if (is_date(a) && is_date(b)) {
|
||||
a = a.getTime();
|
||||
b = b.getTime();
|
||||
const delta = b - a;
|
||||
return t => new Date(a + t * delta);
|
||||
}
|
||||
|
||||
const keys = Object.keys(b);
|
||||
const interpolators = {};
|
||||
|
||||
keys.forEach(key => {
|
||||
interpolators[key] = get_interpolator(a[key], b[key]);
|
||||
});
|
||||
|
||||
return t => {
|
||||
const result = {};
|
||||
keys.forEach(key => {
|
||||
result[key] = interpolators[key](t);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'number') {
|
||||
const delta = b - a;
|
||||
return t => a + t * delta;
|
||||
}
|
||||
|
||||
throw new Error(`Cannot interpolate ${type} values`);
|
||||
}
|
||||
|
||||
export function tweened(value, defaults = {}) {
|
||||
const store = writable(value);
|
||||
|
||||
let task;
|
||||
let target_value = value;
|
||||
|
||||
function set(new_value, opts) {
|
||||
target_value = new_value;
|
||||
|
||||
let previous_task = task;
|
||||
let started = false;
|
||||
|
||||
let {
|
||||
delay = 0,
|
||||
duration = 400,
|
||||
easing = linear,
|
||||
interpolate = get_interpolator
|
||||
} = assign(assign({}, defaults), opts);
|
||||
|
||||
const start = window.performance.now() + delay;
|
||||
let fn;
|
||||
|
||||
task = loop(now => {
|
||||
if (now < start) return true;
|
||||
|
||||
if (!started) {
|
||||
fn = interpolate(value, new_value);
|
||||
if (typeof duration === 'function') duration = duration(value, new_value);
|
||||
started = true;
|
||||
}
|
||||
|
||||
if (previous_task) {
|
||||
previous_task.abort();
|
||||
previous_task = null;
|
||||
}
|
||||
|
||||
const elapsed = now - start;
|
||||
|
||||
if (elapsed > duration) {
|
||||
store.set(value = new_value);
|
||||
return false;
|
||||
}
|
||||
|
||||
store.set(value = fn(easing(elapsed / duration)));
|
||||
return true;
|
||||
});
|
||||
|
||||
return task.promise;
|
||||
}
|
||||
|
||||
return {
|
||||
set,
|
||||
update: (fn, opts) => set(fn(target_value, value), opts),
|
||||
subscribe: store.subscribe
|
||||
};
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export function is_date(obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Date]';
|
||||
}
|
Loading…
Reference in new issue