@ssgoi/core
Version:
Core animation engine for SSGOI - Native app-like page transitions with spring physics
3 lines (2 loc) • 20 kB
JavaScript
"use strict";var H=Object.defineProperty;var N=(n,t,e)=>t in n?H(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var m=(n,t,e)=>N(n,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const M=require("./types.cjs"),Y=require("./jaemin-BG8HRXG1.cjs");class W{constructor(){m(this,"listeners",new Set);m(this,"rafId",null);m(this,"isRunning",!1);m(this,"startTime",Date.now());m(this,"lastUpdate",this.startTime);m(this,"elapsed",0);m(this,"lagThreshold",500);m(this,"adjustedLag",33);m(this,"maxDeltaTime",33);m(this,"gap",1e3/240);m(this,"nextTime",this.gap);m(this,"tick",()=>{const t=Date.now();let i=t-this.lastUpdate;i>this.lagThreshold||i<0?(this.startTime+=i-this.adjustedLag,i=this.adjustedLag):i>this.maxDeltaTime&&(i=this.maxDeltaTime),this.lastUpdate=t;const r=t-this.startTime,o=r-this.nextTime;if(o>0){const s=i/1e3;this.elapsed=r/1e3,this.nextTime+=o+(o>=this.gap?4:this.gap-o),this.listeners.forEach(d=>{d(s,this.elapsed)})}this.listeners.size>0?this.rafId=requestAnimationFrame(this.tick):(this.isRunning=!1,this.rafId=null)})}subscribe(t){return this.listeners.add(t),this.isRunning||(this.isRunning=!0,this.rafId=requestAnimationFrame(this.tick)),()=>{this.unsubscribe(t)}}unsubscribe(t){this.listeners.delete(t),this.listeners.size===0&&this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.isRunning=!1,this.rafId=null)}lagSmoothing(t,e){this.lagThreshold=t,this.adjustedLag=Math.min(e,t)}fps(t){this.gap=1e3/t,this.nextTime=this.elapsed*1e3+this.gap}getListenerCount(){return this.listeners.size}getElapsed(){return this.elapsed}}const x=new W;function k(n){const{from:t,to:e,stiffness:i,damping:r,mass:o,velocity:s=0,onUpdate:d,onComplete:l}=n;let h=t,u=s,a=!0;const p=Math.sqrt(i/o),c=r/(2*Math.sqrt(i*o)),g=.01,w=.01;let f=0;const y=.05,S=T=>{if(!a)return;const A=Math.min(T,.033),v=-2*A*c*p*u,b=A*p*p*(e-h);u+=v+b,h+=A*u;const E=Math.abs(e-h),C=Math.abs(u);if(E<g&&C<w){if(f+=A,f>=y){h=e,u=0,a=!1,d(h),x.unsubscribe(S),l();return}}else f=0;d(h)};return x.subscribe(S),{stop:()=>{a&&(a=!1,x.unsubscribe(S))}}}class O{constructor(t){m(this,"options");m(this,"currentValue");m(this,"velocity");m(this,"isAnimating",!1);m(this,"controls",null);m(this,"animationMap",null);m(this,"activeAnimations",new Set);m(this,"completedAnimations",new Set);m(this,"updatedProperties",new Set);m(this,"animate",(t=!1)=>{!this.isAnimating&&this.options.onStart&&this.options.onStart(),this.isAnimating=!0;const e=t?this.options.from:this.options.to;if(typeof this.currentValue=="object"&&this.currentValue!==null){this.animateObject(e);return}let i=this.currentValue,r=performance.now();const o={from:this.currentValue,to:e,stiffness:this.options.spring.stiffness,damping:this.options.spring.damping,mass:1};typeof this.velocity=="number"&&(o.velocity=this.velocity*1e3),this.controls=k({...o,onUpdate:s=>{const d=performance.now(),l=(d-r)/1e3;if(l>0){if(typeof s=="number"&&typeof i=="number"){const h=(s-i)/l;this.velocity=h/1e3}else if(typeof s=="object"&&s!==null){const h={};Object.keys(s).forEach(u=>{const a=s[u],p=i[u];if(typeof a=="number"&&typeof p=="number"){const c=(a-p)/l;h[u]=c/1e3}}),this.velocity=h}i=s,r=d}this.currentValue=s,this.options.onUpdate(s)},onComplete:()=>{this.currentValue=e,this.isAnimating=!1,this.controls=null,typeof this.velocity=="object"&&this.velocity!==null?Object.keys(this.velocity).forEach(s=>{this.velocity[s]=0}):this.velocity=0,this.options.onComplete()}})});m(this,"animateObject",t=>{const e=this.currentValue,i=Object.keys(e);this.animationMap=new Map,this.activeAnimations.clear(),this.completedAnimations.clear(),this.updatedProperties.clear();const r={...e},o={};let s=!1;const d=()=>{const l=i.filter(u=>!this.completedAnimations.has(u));if(l.every(u=>this.updatedProperties.has(u))&&l.length>0){if(typeof this.velocity=="object"&&this.velocity){const u=performance.now();Object.keys(r).forEach(a=>{if(o[a]){const p=(u-o[a])/1e3,c=this.currentValue[a],g=r[a];typeof c=="number"&&typeof g=="number"&&p>0&&(this.velocity[a]=(g-c)/p/1e3)}o[a]=u})}this.currentValue={...r},this.options.onUpdate(this.currentValue),this.updatedProperties.clear(),!s&&this.options.onStart&&(s=!0,this.options.onStart())}};i.forEach(l=>{this.activeAnimations.add(l);const h={from:e[l],to:t[l],stiffness:this.options.spring.stiffness,damping:this.options.spring.damping,mass:1};typeof this.velocity=="object"&&this.velocity&&l in this.velocity&&(h.velocity=this.velocity[l]*1e3);const u=k({...h,onUpdate:a=>{r[l]=a,this.updatedProperties.add(l),d()},onComplete:()=>{this.completedAnimations.add(l),this.updatedProperties.delete(l),this.completedAnimations.size===i.length&&(this.currentValue=t,this.isAnimating=!1,this.animationMap=null,typeof this.velocity=="object"&&this.velocity&&Object.keys(this.velocity).forEach(a=>{this.velocity[a]=0}),this.options.onComplete())}});this.animationMap.set(l,u)})});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 e={};Object.keys(this.options.from).forEach(i=>{e[i]=0}),this.velocity=e}else this.velocity=0}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 e=this.shouldReverse();this.stop(),this.animate(!e)}}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()}getVelocity(){return this.velocity}getCurrentValue(){return this.currentValue}getIsAnimating(){return this.isAnimating}getCurrentState(){return{type:"single",position:this.currentValue,velocity:this.velocity,from:this.options.from,to:this.options.to}}setVelocity(t){this.velocity=t}setValue(t){this.currentValue=t}updateOptions(t){if(this.options={...this.options,...t},this.isAnimating&&this.controls){const e=this.shouldReverse();this.stop(),this.animate(!e)}}static fromState(t,e){const i=new O(e);return i.setValue(t.position),i.setVelocity(t.velocity),i}}class D{constructor(t){m(this,"config");m(this,"animators",new Map);m(this,"springOrder",[]);m(this,"completedCount",0);m(this,"completedAnimators",new Set);m(this,"direction","forward");m(this,"idCounter",0);m(this,"timeoutIds",[]);this.config=t,this.initializeAnimators()}generateId(){return`spring_${this.idCounter++}`}initializeAnimators(){this.config.springs.forEach(t=>{const e=this.generateId(),i=new O({from:t.from,to:t.to,spring:t.spring,onUpdate:t.tick,onComplete:()=>this.onAnimatorComplete(e),onStart:t.onStart});this.animators.set(e,{id:e,item:t,animator:i,startTime:null}),this.springOrder.push(e)})}normalizeSchedule(t){const e=this.config.schedule??"overlap";return this.springOrder.map((i,r)=>{const o=this.animators.get(i);if(!o)throw new Error(`AnimationScheduler: animator with id "${i}" not found during normalization`);if(e==="overlap")return{type:"offset",id:i,delay:0};if(e==="wait")return t==="forward"&&r===0?{type:"offset",id:i,delay:0}:t==="backward"&&r===this.springOrder.length-1?{type:"offset",id:i,delay:0}:{type:"wait",id:i};const s=o.item.offset??0;if(t==="forward")return{type:"offset",id:i,delay:s};const d=this.animators.size===0?0:Math.max(...Array.from(this.animators.values()).map(l=>l.item.offset??0));return{type:"offset",id:i,delay:d-s}})}onAnimatorComplete(t){var i,r,o,s,d,l;const e=this.animators.get(t);if(!e){console.warn(`AnimationScheduler: animator with id "${t}" not found on completion`);return}this.completedAnimators.has(t)||(this.completedAnimators.add(t),this.completedCount++,(r=(i=e.item).onComplete)==null||r.call(i),(s=(o=this.config).onProgress)==null||s.call(o,this.completedCount,this.config.springs.length),this.handleWaitModeChaining(t),this.completedCount===this.config.springs.length&&((l=(d=this.config).onEnd)==null||l.call(d)))}handleWaitModeChaining(t){if(this.config.schedule!=="wait")return;const e=this.springOrder.indexOf(t);if(this.direction==="forward"){const o=this.springOrder[e+1];if(!o)return;const s=this.animators.get(o);s&&s.startTime===null&&this.startAnimator(o);return}const i=this.springOrder[e-1];if(!i)return;const r=this.animators.get(i);r&&r.startTime===null&&this.startBackwardAnimator(i)}startAnimator(t){const e=this.animators.get(t);if(!e){console.warn(`AnimationScheduler: animator with id "${t}" not found on start`);return}if(e.startTime!==null){console.warn(`AnimationScheduler: animator with id "${t}" already started`);return}e.startTime=Date.now(),e.animator.forward()}startBackwardAnimator(t){const e=this.animators.get(t);if(!e){console.warn(`AnimationScheduler: animator with id "${t}" not found on backward start`);return}if(e.startTime!==null){console.warn(`AnimationScheduler: animator with id "${t}" already started`);return}e.startTime=Date.now(),e.animator.backward()}forward(){var e,i;this.stop(),this.direction="forward",this.completedCount=0,this.completedAnimators.clear(),(i=(e=this.config).onStart)==null||i.call(e),this.normalizeSchedule("forward").forEach(r=>{if(r.type==="wait")return;if(r.delay===0){this.startAnimator(r.id);return}const o=window.setTimeout(()=>this.startAnimator(r.id),r.delay);this.timeoutIds.push(o)})}backward(){var e,i;this.stop(),this.direction="backward",this.completedCount=0,this.completedAnimators.clear(),(i=(e=this.config).onStart)==null||i.call(e),this.normalizeSchedule("backward").forEach(r=>{if(r.type==="wait")return;if(r.delay===0){this.startBackwardAnimator(r.id);return}const o=window.setTimeout(()=>this.startBackwardAnimator(r.id),r.delay);this.timeoutIds.push(o)})}stop(){this.timeoutIds.forEach(t=>clearTimeout(t)),this.timeoutIds=[],this.animators.forEach(t=>{t.animator.stop(),t.startTime=null})}reverse(t){((t==null?void 0:t.offsetMode)??"immediate")!=="immediate"&&console.warn('AnimationScheduler.reverse(): Only "immediate" offsetMode is currently supported. Falling back to "immediate".'),this.direction=this.direction==="forward"?"backward":"forward",this.animators.forEach(i=>{if(i.startTime===null)return;const r=i.animator.getCurrentState();if(r.type!=="single")return;if(r.position===r.to){const s=O.fromState({position:1,velocity:0},{from:i.item.to??1,to:i.item.from??0,spring:i.item.spring,onUpdate:i.item.tick,onComplete:()=>this.onAnimatorComplete(i.id),onStart:i.item.onStart});s.forward(),i.animator=s;return}i.animator.reverse()})}getCurrentState(){return{type:"multi",completed:this.completedCount,total:this.config.springs.length,direction:this.direction}}}const j=Symbol.for("TRANSITION_STRATEGY"),z=n=>({runIn:async t=>{const{currentAnimation:e}=n;if(e&&e.direction==="out"){const s=e.controller.getCurrentState();if(e.controller.stop(),s.type==="multi")return e.controller.reverse(),{state:{position:0,velocity:0},from:0,to:1,direction:"forward"};if(t.out){const d=await t.out,{from:l=1,to:h=0}=d;return{config:d,state:{position:s.position,velocity:s.velocity},from:l,to:h,direction:"backward"}}}const i=await t.in;if(!i)return{state:{position:0,velocity:0},from:0,to:1,direction:"forward"};if("springs"in i)return{config:i,state:{position:0,velocity:0},from:0,to:1,direction:"forward"};const{from:r=0,to:o=1}=i;return{config:i,state:{position:r,velocity:0},from:r,to:o,direction:"forward"}},runOut:async t=>{const{currentAnimation:e}=n;if(e&&e.direction==="in"){const s=e.controller.getCurrentState();if(e.controller.stop(),s.type==="multi")return e.controller.reverse(),{state:{position:1,velocity:0},from:1,to:0,direction:"forward"};if(t.in){const d=await t.in,{from:l=0,to:h=1}=d;return{config:d,state:{position:s.position,velocity:s.velocity},from:l,to:h,direction:"backward"}}}const i=await t.out;if(!i)return{state:{position:1,velocity:0},from:1,to:0,direction:"forward"};if("springs"in i)return{config:i,state:{position:1,velocity:0},from:1,to:0,direction:"forward"};const{from:r=1,to:o=0}=i;return{config:i,state:{position:r,velocity:0},from:r,to:o,direction:"forward"}}}),_=()=>({runIn:async n=>{const t=await n.in;if(!t)return{state:{position:0,velocity:0},from:0,to:1,direction:"forward"};if("springs"in t)return{config:t,state:{position:0,velocity:0},from:0,to:1,direction:"forward"};const{from:e=0,to:i=1}=t;return{config:t,state:{position:e,velocity:0},from:e,to:i,direction:"forward"}},runOut:async n=>{const t=await n.out;if(!t)return{state:{position:1,velocity:0},from:1,to:0,direction:"forward"};if("springs"in t)return{config:t,state:{position:1,velocity:0},from:1,to:0,direction:"forward"};const{from:e=1,to:i=0}=t;return{config:t,state:{position:e,velocity:0},from:e,to:i,direction:"forward"}}});function X(n,t){var u;let e=null,i=null,r=null,o=null;const s={get currentAnimation(){return e}},d=((u=t==null?void 0:t.strategy)==null?void 0:u.call(t,s))||z(s),l=async a=>{var S,T,A;i&&(i.remove(),i=null);const p=n(),c=p.in&&await p.in(a),g=!(t!=null&&t.strategy)&&p.out?p.out(a):void 0;if(!c)return;if(M.isMultiSpring(c)){const v={in:Promise.resolve(c),out:g&&Promise.resolve(g)};if(!(await d.runIn(v)).config){e&&(e.direction="in");return}(S=c.prepare)==null||S.call(c,a),c.wait&&await c.wait();const E=new D({...c,onEnd:()=>{var C;e=null,(C=c.onEnd)==null||C.call(c)}});e={controller:E,direction:"in"},E.forward();return}const w={in:Promise.resolve(c),out:g&&Promise.resolve(g)},f=await d.runIn(w);if(!f.config)return;if("springs"in f.config){console.error("Unexpected MultiSpringConfig in single-spring path");return}(A=(T=f.config).prepare)==null||A.call(T,a),f.config.wait&&await f.config.wait();const y=O.fromState(f.state,{from:f.from,to:f.to,spring:f.config.spring,onStart:f.config.onStart,onUpdate:f.config.tick,onComplete:()=>{var v,b;e=null,(b=(v=f.config)==null?void 0:v.onEnd)==null||b.call(v)}});e={controller:y,direction:"in"},f.direction==="forward"?y.forward():y.backward()},h=async a=>{var S,T,A;i=a;const p=n(),c=p.out&&await p.out(a);if(!c)return;if(M.isMultiSpring(c)){const v={in:void 0,out:Promise.resolve(c)};if(!(await d.runOut(v)).config){e&&(e.direction="out"),i&&(i.remove(),i=null);return}(S=c.prepare)==null||S.call(c,a),y(),c.wait&&await c.wait();const E=new D({...c,onEnd:()=>{var C,I;(C=c.onEnd)==null||C.call(c),i&&(i.remove(),i=null),e=null,(I=t==null?void 0:t.onCleanupEnd)==null||I.call(t)}});e={controller:E,direction:"out"},E.forward();return}const g={in:void 0,out:Promise.resolve(c)},w=await d.runOut(g);if(!w.config)return;if("springs"in w.config){console.error("Unexpected MultiSpringConfig in single-spring path");return}(A=(T=w.config).prepare)==null||A.call(T,a),y(),w.config.wait&&await w.config.wait();const f=O.fromState(w.state,{from:w.from,to:w.to,spring:w.config.spring,onStart:w.config.onStart,onUpdate:w.config.tick,onComplete:()=>{var v,b,E;(b=(v=w.config)==null?void 0:v.onEnd)==null||b.call(v),i&&(i.remove(),i=null),e=null,(E=t==null?void 0:t.onCleanupEnd)==null||E.call(t)}});e={controller:f,direction:"out"},w.direction==="forward"?f.forward():f.backward();function y(){!r||!i||(o&&r.contains(o)?r.insertBefore(i,o):r.appendChild(i))}};return a=>{if(a)return r=a.parentElement,o=a.nextElementSibling,l(a),()=>{if(a.isConnected){const p=a.cloneNode(!0);h(p)}else h(a)}}}const q=3,K=n=>{if(!n)return null;const t=n.split(`
`).map(e=>e.trim());for(let e=q;e<Math.min(t.length,6);e++){const i=t[e],r=i==null?void 0:i.match(/\(?([^):]+):(\d+):(\d+)\)?$/);if(!r)continue;const o=r[1]??"",s=r[2]??"0",d=r[3]??"0";return{file:o.split(/[\\/]/).pop()||o,line:s,column:d}}return null},R=new Map,V=new Map;function B(n,t,e){R.set(n,t);let i=V.get(n);return i||(i=X(()=>{const r=R.get(n);return r||(console.warn(`Transition "${String(n)}" not found`),{})},{strategy:e,onCleanupEnd:()=>F(n)}),V.set(n,i),i)}function F(n){R.delete(n),V.delete(n)}function $(){const n=K(new Error().stack);return n?`auto_${n.file}_${n.line}_${n.column}`:Symbol(`ssgoi_auto_${Date.now()}`)}const L=globalThis.FinalizationRegistry,U=L?new L(n=>{try{F(n)}catch{}}):void 0;function G(n){const t=n.key??$();if(n.ref&&U)try{U.register(n.ref,t)}catch{}return B(t,{in:n.in,out:n.out},n[j])}function J(n){const t=n.filter(e=>e.symmetric).map(e=>({from:e.to,to:e.from,transition:e.transition}));return[...n,...t]}function Q(n){let t=!1,e=0,i=0,r=!1;const o=50,s=50,d=c=>{if(!n)return;const g=c.touches[0];g&&(r=g.clientX<s,r&&(e=g.clientX,i=g.clientY))},l=c=>{if(!n||!r||t)return;const g=c.touches[0];if(!g)return;const w=g.clientX-e,f=g.clientY-i;w>o&&Math.abs(w)>Math.abs(f)&&(t=!0,setTimeout(()=>{t=!1},500))},h=()=>{n&&(r=!1,e=0,i=0)};return{initialize:()=>{typeof window>"u"||n&&(window.addEventListener("touchstart",d,{passive:!0}),window.addEventListener("touchmove",l,{passive:!0}),window.addEventListener("touchend",h,{passive:!0}))},cleanup:()=>{typeof window>"u"||(window.removeEventListener("touchstart",d),window.removeEventListener("touchmove",l),window.removeEventListener("touchend",h))},isSwipePending:()=>t}}const Z=n=>{let t=n.parentElement;for(;t&&t!==document.body;){const e=window.getComputedStyle(t),i=e.overflow+e.overflowY+e.overflowX;if(i.includes("auto")||i.includes("scroll")||t.scrollHeight>t.clientHeight||t.scrollWidth>t.clientWidth)return t;t=t.parentElement}return document.documentElement},tt=n=>{let t=n.parentElement;for(;t&&t!==document.body;){const e=window.getComputedStyle(t).position;if(e==="relative"||e==="absolute"||e==="fixed"||e==="sticky")return t;t=t.parentElement}return document.body};function et(){let n=null,t=null;const e=new Map;let i=null;const r=()=>{n&&i&&e.set(i,{x:n.scrollLeft,y:n.scrollTop})};return{initializeContext:(u,a)=>{t=u,n||(n=Z(u),(n===document.documentElement?window:n).addEventListener("scroll",r,{passive:!0})),i=a},calculateScrollOffset:(u,a)=>{const p=u&&e.has(u)?e.get(u):{x:0,y:0},c=a&&e.has(a)?e.get(a):{x:0,y:0};return{x:-c.x+p.x,y:-c.y+p.y}},getScrollContainer:()=>n,getPositionedParentElement:()=>t?tt(t):document.body,getScrollPosition:u=>u&&e.has(u)?e.get(u):{x:0,y:0}}}function P(n,t){if(t==="*")return!0;if(t.endsWith("/*")){const e=t.slice(0,-2);return n===e||n.startsWith(e+"/")}return n===t}function it(n,t,e){for(const i of e)if(P(n,i.from)&&P(t,i.to))return i.transition;for(const i of e)if((i.from==="*"||P(n,i.from))&&(i.to==="*"||P(t,i.to)))return i.transition;return null}function nt(n){const{transitions:t=[],defaultTransition:e,middleware:i=(f,y)=>({from:f,to:y}),skipOnIosSwipe:r=!0}=n;let o=null;const s=J(t),{initializeContext:d,calculateScrollOffset:l,getScrollContainer:h,getPositionedParentElement:u,getScrollPosition:a}=et(),p=Q(r);p.initialize();function c(){if(o!=null&&o.from&&(o!=null&&o.to)){const{from:f,to:y}=i(o.from,o.to),T=it(f,y,s)||e,A=l(o.from,o.to),v={scrollOffset:A,scroll:a(o.from),get scrollingElement(){return h()||document.documentElement},get positionedParent(){return u()}},b={scrollOffset:A,get scroll(){return o?a(o.to):{x:0,y:0}},get scrollingElement(){return h()||document.documentElement},get positionedParent(){return u()}};T&&(T.out&&o.outResolve&&o.outResolve(E=>T.out(E,v)),T.in&&o.inResolve&&o.inResolve(E=>T.in(E,b))),o=null}}const g=async(f,y)=>p.isSwipePending()?()=>({}):y==="in"&&(!o||!o.from)?()=>({}):(o||(o={}),y==="out"?(o.from=f,new Promise(S=>{o.outResolve=S,c()})):(o.to=f,new Promise(S=>{o.inResolve=S,c()})));return f=>({key:f,in:async y=>(d(y,f),(await g(f,"in"))(y)),out:async y=>(await g(f,"out"))(y),[j]:_})}exports.isMultiSpring=M.isMultiSpring;exports.isSingleSpring=M.isSingleSpring;exports.jaeminInternal=Y.jaeminInternal;exports.TRANSITION_STRATEGY=j;exports.createDefaultStrategy=z;exports.createPageTransitionStrategy=_;exports.createSggoiTransitionContext=nt;exports.generateAutoKey=$;exports.transition=G;