diff options
Diffstat (limited to 'node_modules/svelte/src/runtime/internal/transitions.js')
| -rw-r--r-- | node_modules/svelte/src/runtime/internal/transitions.js | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/node_modules/svelte/src/runtime/internal/transitions.js b/node_modules/svelte/src/runtime/internal/transitions.js new file mode 100644 index 0000000..4575e18 --- /dev/null +++ b/node_modules/svelte/src/runtime/internal/transitions.js @@ -0,0 +1,461 @@ +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'; + +/** + * @type {Promise<void> | null} + */ +let promise; + +/** + * @returns {Promise<void>} + */ +function wait() { + if (!promise) { + promise = Promise.resolve(); + promise.then(() => { + promise = null; + }); + } + return promise; +} + +/** + * @param {Element} node + * @param {INTRO | OUTRO | boolean} direction + * @param {'start' | 'end'} kind + * @returns {void} + */ +function dispatch(node, direction, kind) { + node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`)); +} + +const outroing = new Set(); + +/** + * @type {Outro} + */ +let outros; + +/** + * @returns {void} */ +export function group_outros() { + outros = { + r: 0, + c: [], + p: outros // parent group + }; +} + +/** + * @returns {void} */ +export function check_outros() { + if (!outros.r) { + run_all(outros.c); + } + outros = outros.p; +} + +/** + * @param {import('./private.js').Fragment} block + * @param {0 | 1} [local] + * @returns {void} + */ +export function transition_in(block, local) { + if (block && block.i) { + outroing.delete(block); + block.i(local); + } +} + +/** + * @param {import('./private.js').Fragment} block + * @param {0 | 1} local + * @param {0 | 1} [detach] + * @param {() => void} [callback] + * @returns {void} + */ +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(); + } +} + +/** + * @type {import('../transition/public.js').TransitionConfig} + */ +const null_transition = { duration: 0 }; + +/** + * @param {Element & ElementCSSInlineStyle} node + * @param {TransitionFn} fn + * @param {any} params + * @returns {{ start(): void; invalidate(): void; end(): void; }} + */ +export function create_in_transition(node, fn, params) { + /** + * @type {TransitionOptions} */ + const options = { direction: 'in' }; + let config = fn(node, params, options); + let running = false; + let animation_name; + let task; + let uid = 0; + + /** + * @returns {void} */ + function cleanup() { + if (animation_name) delete_rule(node, animation_name); + } + + /** + * @returns {void} */ + 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; + } + } + }; +} + +/** + * @param {Element & ElementCSSInlineStyle} node + * @param {TransitionFn} fn + * @param {any} params + * @returns {{ end(reset: any): void; }} + */ +export function create_out_transition(node, fn, params) { + /** @type {TransitionOptions} */ + const options = { direction: 'out' }; + let config = fn(node, params, options); + let running = true; + let animation_name; + const group = outros; + group.r += 1; + /** @type {boolean} */ + let original_inert_value; + + /** + * @returns {void} */ + 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 = /** @type {HTMLElement} */ (node).inert; + node.inert = true; + } + + 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 (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(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; + } + } + }; +} + +/** + * @param {Element & ElementCSSInlineStyle} node + * @param {TransitionFn} fn + * @param {any} params + * @param {boolean} intro + * @returns {{ run(b: 0 | 1): void; end(): void; }} + */ +export function create_bidirectional_transition(node, fn, params, intro) { + /** + * @type {TransitionOptions} */ + const options = { direction: 'both' }; + let config = fn(node, params, options); + let t = intro ? 0 : 1; + + /** + * @type {Program | null} */ + let running_program = null; + + /** + * @type {PendingProgram | null} */ + let pending_program = null; + let animation_name = null; + + /** @type {boolean} */ + let original_inert_value; + + /** + * @returns {void} */ + function clear_animation() { + if (animation_name) delete_rule(node, animation_name); + } + + /** + * @param {PendingProgram} program + * @param {number} duration + * @returns {Program} + */ + function init(program, duration) { + const d = /** @type {Program['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 + }; + } + + /** + * @param {INTRO | OUTRO} b + * @returns {void} + */ + function go(b) { + const { + delay = 0, + duration = 300, + easing = linear, + tick = noop, + css + } = config || null_transition; + + /** + * @type {PendingProgram} */ + const program = { + start: now() + delay, + b + }; + + if (!b) { + // @ts-ignore todo: improve typings + program.group = outros; + outros.r += 1; + } + + if ('inert' in node) { + if (b) { + if (original_inert_value !== undefined) { + // aborted/reversed outro — restore previous inert value + node.inert = original_inert_value; + } + } else { + original_inert_value = /** @type {HTMLElement} */ (node).inert; + node.inert = true; + } + } + + if (running_program || pending_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); + 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) { + // 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(() => { + const opts = { direction: b ? 'in' : 'out' }; + // @ts-ignore + config = config(opts); + go(b); + }); + } else { + go(b); + } + }, + end() { + clear_animation(); + running_program = pending_program = null; + } + }; +} + +/** @typedef {1} INTRO */ +/** @typedef {0} OUTRO */ +/** @typedef {{ direction: 'in' | 'out' | 'both' }} TransitionOptions */ +/** @typedef {(node: Element, params: any, options: TransitionOptions) => import('../transition/public.js').TransitionConfig} TransitionFn */ + +/** + * @typedef {Object} Outro + * @property {number} r + * @property {Function[]} c + * @property {Object} p + */ + +/** + * @typedef {Object} PendingProgram + * @property {number} start + * @property {INTRO|OUTRO} b + * @property {Outro} [group] + */ + +/** + * @typedef {Object} Program + * @property {number} a + * @property {INTRO|OUTRO} b + * @property {1|-1} d + * @property {number} duration + * @property {number} start + * @property {number} end + * @property {Outro} [group] + */ |
