|
import { identity as linear, is_function, noop, run_all } from './utils.js'; |
|
import { now } from './environment.js'; |
|
import { loop } from './loop.js'; |
|
import { create_rule, delete_rule } from './style_manager.js'; |
|
import { custom_event } from './dom.js'; |
|
import { add_render_callback } from './scheduler.js'; |
|
|
|
|
|
|
|
|
|
let promise; |
|
|
|
|
|
|
|
|
|
function wait() { |
|
if (!promise) { |
|
promise = Promise.resolve(); |
|
promise.then(() => { |
|
promise = null; |
|
}); |
|
} |
|
return promise; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function dispatch(node, direction, kind) { |
|
node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`)); |
|
} |
|
|
|
const outroing = new Set(); |
|
|
|
|
|
|
|
|
|
let outros; |
|
|
|
|
|
|
|
export function group_outros() { |
|
outros = { |
|
r: 0, |
|
c: [], |
|
p: outros |
|
}; |
|
} |
|
|
|
|
|
|
|
export function check_outros() { |
|
if (!outros.r) { |
|
run_all(outros.c); |
|
} |
|
outros = outros.p; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export function transition_in(block, local) { |
|
if (block && block.i) { |
|
outroing.delete(block); |
|
block.i(local); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function transition_out(block, local, detach, callback) { |
|
if (block && block.o) { |
|
if (outroing.has(block)) return; |
|
outroing.add(block); |
|
outros.c.push(() => { |
|
outroing.delete(block); |
|
if (callback) { |
|
if (detach) block.d(1); |
|
callback(); |
|
} |
|
}); |
|
block.o(local); |
|
} else if (callback) { |
|
callback(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
const null_transition = { duration: 0 }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function create_in_transition(node, fn, params) { |
|
|
|
|
|
const options = { direction: 'in' }; |
|
let config = fn(node, params, options); |
|
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; |
|
}); |
|
} |
|
let started = false; |
|
return { |
|
start() { |
|
if (started) return; |
|
started = true; |
|
delete_rule(node); |
|
if (is_function(config)) { |
|
config = config(options); |
|
wait().then(go); |
|
} else { |
|
go(); |
|
} |
|
}, |
|
invalidate() { |
|
started = false; |
|
}, |
|
end() { |
|
if (running) { |
|
cleanup(); |
|
running = false; |
|
} |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function create_out_transition(node, fn, params) { |
|
|
|
const options = { direction: 'out' }; |
|
let config = fn(node, params, options); |
|
let running = true; |
|
let animation_name; |
|
const group = outros; |
|
group.r += 1; |
|
|
|
let original_inert_value; |
|
|
|
|
|
|
|
function go() { |
|
const { |
|
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 start_time = now() + delay; |
|
const end_time = start_time + duration; |
|
add_render_callback(() => dispatch(node, false, 'start')); |
|
|
|
if ('inert' in node) { |
|
original_inert_value = (node).inert; |
|
node.inert = true; |
|
} |
|
|
|
loop((now) => { |
|
if (running) { |
|
if (now >= end_time) { |
|
tick(0, 1); |
|
dispatch(node, false, 'end'); |
|
if (!--group.r) { |
|
|
|
|
|
run_all(group.c); |
|
} |
|
return false; |
|
} |
|
if (now >= start_time) { |
|
const t = easing((now - start_time) / duration); |
|
tick(1 - t, t); |
|
} |
|
} |
|
return running; |
|
}); |
|
} |
|
|
|
if (is_function(config)) { |
|
wait().then(() => { |
|
|
|
config = config(options); |
|
go(); |
|
}); |
|
} else { |
|
go(); |
|
} |
|
|
|
return { |
|
end(reset) { |
|
if (reset && 'inert' in node) { |
|
node.inert = original_inert_value; |
|
} |
|
if (reset && config.tick) { |
|
config.tick(1, 0); |
|
} |
|
if (running) { |
|
if (animation_name) delete_rule(node, animation_name); |
|
running = false; |
|
} |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function create_bidirectional_transition(node, fn, params, intro) { |
|
|
|
|
|
const options = { direction: 'both' }; |
|
let config = fn(node, params, options); |
|
let t = intro ? 0 : 1; |
|
|
|
|
|
|
|
let running_program = null; |
|
|
|
|
|
|
|
let pending_program = null; |
|
let animation_name = null; |
|
|
|
|
|
let original_inert_value; |
|
|
|
|
|
|
|
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) { |
|
|
|
program.group = outros; |
|
outros.r += 1; |
|
} |
|
|
|
if ('inert' in node) { |
|
if (b) { |
|
if (original_inert_value !== undefined) { |
|
|
|
node.inert = original_inert_value; |
|
} |
|
} else { |
|
original_inert_value = (node).inert; |
|
node.inert = true; |
|
} |
|
} |
|
|
|
if (running_program || pending_program) { |
|
pending_program = program; |
|
} else { |
|
|
|
|
|
if (css) { |
|
clear_animation(); |
|
animation_name = create_rule(node, t, b, duration, delay, easing, css); |
|
} |
|
if (b) tick(0, 1); |
|
running_program = init(program, duration); |
|
add_render_callback(() => dispatch(node, b, 'start')); |
|
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) { |
|
|
|
if (running_program.b) { |
|
|
|
clear_animation(); |
|
} else { |
|
|
|
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(() => { |
|
const opts = { direction: b ? 'in' : 'out' }; |
|
|
|
config = config(opts); |
|
go(b); |
|
}); |
|
} else { |
|
go(b); |
|
} |
|
}, |
|
end() { |
|
clear_animation(); |
|
running_program = pending_program = null; |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|