@ssgoi/core
Version:
Core animation engine for SSGOI - Native app-like page transitions with spring physics
427 lines (426 loc) • 13.3 kB
JavaScript
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
};