UNPKG

@ssgoi/core

Version:

Core animation engine for SSGOI - Native app-like page transitions with spring physics

427 lines (426 loc) 13.3 kB
var j = Object.defineProperty; var T = (e, t, n) => t in e ? j(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n; var y = (e, t, n) => T(e, typeof t != "symbol" ? t + "" : t, n); import { animate as R } from "popmotion"; import { g as P } from "./utils-DR41YsAh.js"; class V { constructor(t) { y(this, "options"); y(this, "currentValue"); y(this, "velocity"); y(this, "isAnimating", !1); y(this, "controls", null); // For object animations y(this, "animationMap", null); y(this, "activeAnimations", /* @__PURE__ */ new Set()); y(this, "completedAnimations", /* @__PURE__ */ new Set()); y(this, "updatedProperties", /* @__PURE__ */ new Set()); y(this, "animate", (t = !1) => { !this.isAnimating && this.options.onStart && this.options.onStart(), this.isAnimating = !0; const n = t ? this.options.from : this.options.to; if (typeof this.currentValue == "object" && this.currentValue !== null) { this.animateObject(n, t); return; } let i = this.currentValue, a = performance.now(); const u = { from: this.currentValue, to: n, stiffness: this.options.spring.stiffness, damping: this.options.spring.damping, mass: 1 }; typeof this.velocity == "number" && (u.velocity = this.velocity * 1e3), this.controls = R({ ...u, onUpdate: (l) => { const m = performance.now(), p = (m - a) / 1e3; if (p > 0) { if (typeof l == "number" && typeof i == "number") { const f = (l - i) / p; this.velocity = f / 1e3; } else if (typeof l == "object" && l !== null) { const f = {}; Object.keys(l).forEach((d) => { const r = l[d], o = i[d]; if (typeof r == "number" && typeof o == "number") { const c = (r - o) / p; f[d] = c / 1e3; } }), this.velocity = f; } i = l, a = m; } this.currentValue = l, this.options.onUpdate(l); }, onComplete: () => { this.currentValue = n, this.isAnimating = !1, this.controls = null, typeof this.velocity == "object" && this.velocity !== null ? Object.keys(this.velocity).forEach((l) => { this.velocity[l] = 0; }) : this.velocity = 0, this.options.onComplete(); } }); }); y(this, "animateObject", (t, n) => { const i = this.currentValue, a = Object.keys(i); this.animationMap = /* @__PURE__ */ new Map(), this.activeAnimations.clear(), this.completedAnimations.clear(), this.updatedProperties.clear(); const u = { ...i }, l = {}; let m = !1; const p = () => { const f = a.filter((r) => !this.completedAnimations.has(r)); if (f.every((r) => this.updatedProperties.has(r)) && f.length > 0) { if (typeof this.velocity == "object" && this.velocity) { const r = performance.now(); Object.keys(u).forEach((o) => { if (l[o]) { const c = (r - l[o]) / 1e3, s = this.currentValue[o], h = u[o]; typeof s == "number" && typeof h == "number" && c > 0 && (this.velocity[o] = (h - s) / c / 1e3); } l[o] = r; }); } this.currentValue = { ...u }, this.options.onUpdate(this.currentValue), this.updatedProperties.clear(), !m && this.options.onStart && (m = !0, this.options.onStart()); } }; a.forEach((f) => { this.activeAnimations.add(f); const d = { from: i[f], to: t[f], stiffness: this.options.spring.stiffness, damping: this.options.spring.damping, mass: 1 }; typeof this.velocity == "object" && this.velocity && f in this.velocity && (d.velocity = this.velocity[f] * 1e3); const r = R({ ...d, onUpdate: (o) => { u[f] = o, this.updatedProperties.add(f), p(); }, onComplete: () => { this.completedAnimations.add(f), this.updatedProperties.delete(f), this.completedAnimations.size === a.length && (this.currentValue = t, this.isAnimating = !1, this.animationMap = null, typeof this.velocity == "object" && this.velocity && Object.keys(this.velocity).forEach((o) => { this.velocity[o] = 0; }), this.options.onComplete()); } }); this.animationMap.set(f, r); }); }); if (this.options = { from: t.from ?? 0, to: t.to ?? 1, spring: t.spring ?? { stiffness: 100, damping: 10 }, onUpdate: t.onUpdate ?? (() => { }), onComplete: t.onComplete ?? (() => { }), onStart: t.onStart }, this.currentValue = this.options.from, typeof this.options.from == "object" && this.options.from !== null) { const n = {}; Object.keys(this.options.from).forEach((i) => { n[i] = 0; }), this.velocity = n; } else this.velocity = 0; } // Animation control methods forward() { this.stop(), this.animate(!1); } backward() { this.stop(), this.animate(!0); } reverse() { const t = this.options.from; if (this.options.from = this.options.to, this.options.to = t, this.isAnimating) { const n = this.shouldReverse(); this.stop(), this.animate(!n); } } shouldReverse() { return typeof this.currentValue == "number" && typeof this.options.from == "number" && typeof this.options.to == "number" ? this.currentValue > (this.options.from + this.options.to) / 2 : !1; } stop() { this.isAnimating = !1, this.controls && (this.controls.stop(), this.controls = null), this.animationMap && (this.animationMap.forEach((t) => { t.stop(); }), this.animationMap.clear(), this.animationMap = null), this.updatedProperties.clear(); } // State getters getVelocity() { return this.velocity; } getCurrentValue() { return this.currentValue; } getIsAnimating() { return this.isAnimating; } getCurrentState() { return { position: this.currentValue, velocity: this.velocity, from: this.options.from, to: this.options.to }; } // State setters setVelocity(t) { this.velocity = t; } setValue(t) { this.currentValue = t; } // Configuration updateOptions(t) { if (this.options = { ...this.options, ...t }, this.isAnimating && this.controls) { const n = this.shouldReverse(); this.stop(), this.animate(!n); } } // Static factory method static fromState(t, n) { const i = new V(n); return i.setValue(t.position), i.setVelocity(t.velocity), i; } } const x = Symbol.for("TRANSITION_STRATEGY"), M = (e) => ({ runIn: async (t) => { const { currentAnimation: n } = e; if (n && n.direction === "out") { const l = n.animator.getCurrentState(); if (n.animator.stop(), t.out) { const m = await t.out, { from: p = 1, to: f = 0 } = m; return { config: m, state: l, from: p, // OUT animation's from to: f, // OUT animation's to direction: "backward" // Will actually go 0→1 }; } } const i = await t.in; if (!i) return { state: { position: 0, velocity: 0 }, from: 0, to: 1, direction: "forward" }; const { from: a = 0, to: u = 1 } = i; return { config: i, state: { position: a, velocity: 0 }, from: a, to: u, direction: "forward" }; }, runOut: async (t) => { const { currentAnimation: n } = e; if (n && n.direction === "in") { const l = n.animator.getCurrentState(); if (n.animator.stop(), t.in) { const m = await t.in, { from: p = 0, to: f = 1 } = m; return { config: m, state: { position: l.position, velocity: l.velocity }, from: p, // IN animation's from to: f, // IN animation's to direction: "backward" // Will actually go 1→0 }; } } const i = await t.out; if (!i) return { state: { position: 1, velocity: 0 }, from: 1, to: 0, direction: "forward" }; const { from: a = 1, to: u = 0 } = i; return { config: i, state: { position: a, velocity: 0 }, from: a, to: u, direction: "forward" }; } }); function U(e, t) { var d; let n = null, i = null, a = null, u = null; const l = { get currentAnimation() { return n; } }, m = ((d = t == null ? void 0 : t.strategy) == null ? void 0 : d.call(t, l)) || M(l), p = async (r) => { var g, b; i && (i.remove(), i = null); const o = e(), c = { in: o.in && Promise.resolve(o.in(r)), out: o.out && Promise.resolve(o.out(r)) }, s = await m.runIn(c); if (!s.config) return; (b = (g = s.config).prepare) == null || b.call(g, r); const h = V.fromState(s.state, { from: s.from, to: s.to, spring: s.config.spring, onStart: s.config.onStart, onUpdate: s.config.tick, onComplete: () => { var v, w; n = null, (w = (v = s.config) == null ? void 0 : v.onEnd) == null || w.call(v); } }); n = { animator: h, direction: "in" }, s.direction === "forward" ? h.forward() : h.backward(); }, f = async (r) => { var b, v; i = r; const o = e(), c = { in: o.in && Promise.resolve(o.in(r)), out: o.out && Promise.resolve(o.out(r)) }, s = await m.runOut(c); if (!s.config) return; (v = (b = s.config).prepare) == null || v.call(b, r), g(); const h = V.fromState(s.state, { from: s.from, to: s.to, spring: s.config.spring, onStart: s.config.onStart, onUpdate: s.config.tick, onComplete: () => { var w, E, O; (E = (w = s.config) == null ? void 0 : w.onEnd) == null || E.call(w), i && (i.remove(), i = null), n = null, (O = t == null ? void 0 : t.onCleanupEnd) == null || O.call(t); } }); n = { animator: h, direction: "out" }, s.direction === "forward" ? h.forward() : h.backward(); function g() { !a || !i || (u && a.contains(u) ? a.insertBefore(i, u) : a.appendChild(i)); } }; return (r) => { if (r) return a = r.parentElement, u = r.nextElementSibling, p(r), () => { const o = r.cloneNode(!0); f(o); }; }; } const A = /* @__PURE__ */ new Map(), C = /* @__PURE__ */ new Map(); function I(e, t, n) { A.set(e, t); let i = C.get(e); return i || (i = U( () => { const a = A.get(e); return a || (console.warn(`Transition "${String(e)}" not found`), {}); }, { strategy: n, onCleanupEnd: () => D(e) } ), C.set(e, i), i); } function D(e) { A.delete(e), C.delete(e); } function Y(e) { return I(e.key, { in: e.in, out: e.out }, e[x]); } function _(e) { let t = null; const n = [...e.transitions], i = []; for (const o of e.transitions) o.symmetric && (n.some( (s) => s.from === o.to && s.to === o.from ) || i.push({ from: o.to, to: o.from, transition: o.transition // Don't add symmetric flag to the reverse to avoid infinite loop })); n.push(...i); let a = null; const u = /* @__PURE__ */ new Map(); let l = null; const m = () => { a && l && u.set(l, { x: a.scrollLeft, y: a.scrollTop }); }, p = (o, c) => { a || (a = P(o), a.addEventListener("scroll", m, { passive: !0 })), l = c; }, f = () => { const o = t == null ? void 0 : t.from, c = t == null ? void 0 : t.to, s = o && u.has(o) ? u.get(o) : { x: 0, y: 0 }, h = c && u.has(c) ? u.get(c) : { x: 0, y: 0 }; return { x: -h.x + s.x, y: -h.y + s.y }; }; function d() { if (t != null && t.from && (t != null && t.to)) { const c = N( t.from, t.to, n ) || e.defaultTransition, h = { scrollOffset: f() }; c && (c.out && t.outResolve && t.outResolve( (g) => c.out(g, h) ), c.in && t.inResolve && t.inResolve( (g) => c.in(g, h) )), t = null; } } const r = async (o, c) => c === "in" && (!t || !t.from) ? () => ({}) : (t || (t = {}), c === "out" ? (t.from = o, new Promise((s) => { t.outResolve = s, d(); })) : (t.to = o, new Promise((s) => { t.inResolve = s, d(); }))); return (o) => ({ key: o, in: async (c) => (p(c, o), (await r(o, "in"))(c)), out: async (c) => (await r(o, "out"))(c) }); } function N(e, t, n) { for (const i of n) if (S(e, i.from) && S(t, i.to)) return i.transition; for (const i of n) if ((i.from === "*" || S(e, i.from)) && (i.to === "*" || S(t, i.to))) return i.transition; return null; } function S(e, t) { if (t === "*") return !0; if (t.endsWith("/*")) { const n = t.slice(0, -2); return e === n || e.startsWith(n + "/"); } return e === t; } export { x as TRANSITION_STRATEGY, M as createDefaultStrategy, _ as createSggoiTransitionContext, Y as transition };