Files
v10/dist/dev/dom/ui/transition.js

111 lines
2.9 KiB
JavaScript

import { createState } from "@videojs/store";
import { noop } from "@videojs/utils/function";
//#region src/dom/ui/transition.ts
/**
* Manages open/close transition lifecycle via `createState`.
*
* **Open:** patches `{ active: true, status: 'starting' }`, then after a
* double-RAF patches `{ status: 'idle' }` so the browser paints the
* initial ("from") state before transitioning. Reopening an active transition
* flushes styles first so CSS transitions can restart.
*
* **Close:** patches `{ status: 'ending' }` (keeping `active: true` so the
* element stays mounted), then after a double-RAF waits for
* `getAnimations()` to settle before patching `{ active: false, status: 'idle' }`.
*/
function createTransition() {
const state = createState({
active: false,
status: "idle"
});
let destroyed = false;
let rafId1 = 0;
let rafId2 = 0;
function open(el = null) {
cancelAnimationFrame(rafId1);
cancelAnimationFrame(rafId2);
rafId1 = 0;
rafId2 = 0;
const restarting = state.current.active;
if (restarting) state.patch({ status: "idle" });
state.patch({
active: true,
status: "starting"
});
return new Promise((resolve) => {
rafId1 = requestAnimationFrame(() => {
rafId1 = 0;
if (restarting) {
cancelAnimations(el);
flushStyles(el);
}
rafId2 = requestAnimationFrame(() => {
rafId2 = 0;
if (destroyed || !state.current.active) return resolve();
state.patch({ status: "idle" });
resolve();
});
});
});
}
function close(el) {
cancelAnimationFrame(rafId1);
cancelAnimationFrame(rafId2);
rafId1 = 0;
rafId2 = 0;
state.patch({ status: "ending" });
return new Promise((resolve) => {
rafId1 = requestAnimationFrame(() => {
rafId1 = 0;
rafId2 = requestAnimationFrame(() => {
rafId2 = 0;
if (destroyed) return resolve();
waitForAnimations(el).finally(() => {
if (destroyed || state.current.status !== "ending") return resolve();
state.patch({
active: false,
status: "idle"
});
resolve();
});
});
});
});
}
function cancel() {
cancelAnimationFrame(rafId1);
cancelAnimationFrame(rafId2);
rafId1 = 0;
rafId2 = 0;
if (state.current.status !== "idle") state.patch({ status: "idle" });
}
return {
state,
open,
close,
cancel,
destroy() {
if (destroyed) return;
destroyed = true;
cancel();
}
};
}
function flushStyles(el) {
if (!el) return;
el.offsetHeight;
}
function cancelAnimations(el) {
const animations = el?.getAnimations?.({ subtree: true }) ?? [];
for (const animation of animations) animation.cancel();
}
function waitForAnimations(el) {
if (!el) return Promise.resolve();
const animations = el.getAnimations?.() ?? [];
if (animations.length === 0) return Promise.resolve();
return Promise.all(animations.map((a) => a.finished)).then(noop, noop);
}
//#endregion
export { createTransition };
//# sourceMappingURL=transition.js.map