UNPKG

react-fidget-spinner

Version:

Turn any React component into an interactive clickable fidget spinner! 🪿

3 lines (2 loc) • 16.2 kB
(function(C,w){typeof exports=="object"&&typeof module<"u"?w(exports,require("react/jsx-runtime"),require("react"),require("usehooks-ts"),require("valibot"),require("bezier-easing")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","usehooks-ts","valibot","bezier-easing"],w):(C=typeof globalThis<"u"?globalThis:C||self,w(C["Library name"]={},C.jsxRuntime,C.React,C.usehooksTs,C.v,C.BezierEasing))})(this,function(C,w,n,Re,Ge,Ke){"use strict";function Qe(t){const s=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const o in t)if(o!=="default"){const a=Object.getOwnPropertyDescriptor(t,o);Object.defineProperty(s,o,a.get?a:{enumerable:!0,get:()=>t[o]})}}return s.default=t,Object.freeze(s)}const e=Qe(Ge),ie=(t,s=!0)=>{const o=n.useRef(0),a=n.useRef(0),i=n.useCallback(u=>{if(a.current!=null){const b=u-a.current;t(b)}a.current=u,o.current=requestAnimationFrame(i)},[t]);n.useEffect(()=>{if(s)return o.current=requestAnimationFrame(i),()=>cancelAnimationFrame(o.current)},[s,i])},ae=e.tuple([e.number(),e.number(),e.number(),e.number()]),_=t=>Ke(t[0],t[1],t[2],t[3]);var $=(t=>(t.Plus="Plus",t.Minus="Minus",t.PlusMinus="PlusMinus",t))($||{}),D=(t=>(t.Percent="Percent",t.Absolute="Absolute",t))(D||{});const we=e.object({type:e.union([e.literal("Plus"),e.literal("Minus"),e.literal("PlusMinus")]),unit:e.union([e.literal("Percent"),e.literal("Absolute")]),value:e.number()});e.object({value:e.number(),variation:e.optional(we)});const g=e.union([e.number(),e.object({value:e.number(),variation:e.optional(we)})]),l=t=>{if(typeof t=="number")return t;if(!t.variation)return t.value;const{variation:s,value:o}=t,a=Math.random()*2-1;if(s.unit==="Absolute"){if(s.type==="Plus")return o+Math.random()*s.value;if(s.type==="Minus")return o-Math.random()*s.value;if(s.type==="PlusMinus")return o+a*s.value;throw new Error("Invalid variation type")}else if(s.unit==="Percent"){const i=s.value/100;if(s.type==="Plus")return o+o*Math.random()*i;if(s.type==="Minus")return o-o*Math.random()*i;if(s.type==="PlusMinus")return o+o*a*i;throw new Error("Invalid variation type")}else throw new Error("Invalid variation unit")},Ee=e.object({active:e.boolean(),components:e.array(e.string()),durationMs:g,scaleEnd:g,frameRate:e.pipe(e.number(),e.toMinValue(0)),spawnIntervalMs:g,onRemove:e.function(),onSpawn:e.function(),opacityEasing:ae,opacityEnd:g,opacityStart:g,scaleEasing:ae,scaleStart:g,wobbleAmplitude:g,wobbleFrequency:g,yEasing:ae,yEnd:g,yStart:g,xStart:g}),Ze={active:!1,components:["💸","🔥"],durationMs:{value:1e3,variation:{type:$.Plus,unit:D.Absolute,value:1e3}},scaleEnd:{value:2,variation:{type:$.PlusMinus,unit:D.Percent,value:20}},frameRate:60,spawnIntervalMs:{value:600,variation:{type:$.PlusMinus,unit:D.Absolute,value:400}},onRemove:()=>{},onSpawn:()=>{},opacityEasing:[.25,-.75,.8,1.2],opacityEnd:0,opacityStart:1,scaleEasing:[.25,-.75,.8,1.2],scaleStart:{value:1,variation:{type:$.PlusMinus,unit:D.Percent,value:50}},wobbleAmplitude:{value:1,variation:{type:$.Plus,unit:D.Absolute,value:40}},wobbleFrequency:{value:.1,variation:{type:$.Plus,unit:D.Absolute,value:.4}},yEasing:[.25,0,.8,1.2],yEnd:{value:100,variation:{type:$.Plus,unit:D.Absolute,value:200}},yStart:0,xStart:{value:0,variation:{type:$.PlusMinus,unit:D.Absolute,value:100}}},ue=(t={})=>{const s={...Ze,...t};return e.parse(Ee,s)},Pe=()=>Math.random().toString(36).substring(2,15),Ae=t=>{const{spawnIntervalMs:s,components:o,durationMs:a,opacityEasing:i,opacityStart:u,opacityEnd:b,scaleStart:m,scaleEasing:f,scaleEnd:M,wobbleFrequency:y,wobbleAmplitude:P,onSpawn:S,onRemove:I,yEasing:z,frameRate:O,yStart:j,yEnd:T,xStart:E,active:F}=ue(t),[c,h]=n.useState({}),R=n.useMemo(()=>Object.values(c),[c]),B=n.useCallback((d,V)=>{h(k=>({...k,[d]:V}))},[h]),U=n.useCallback(d=>{h(V=>{const k={...V};return delete k[d],k})},[h]),p=n.useRef(performance.now()),A=n.useRef(l(s)),v=n.useCallback(()=>{const d=performance.now();if(d-p.current>A.current){p.current=d;const k=l(s);A.current=k;const q=l(P),J=l(y),H=Math.random()<.5?-1:1,X=L=>{const ee=L/1e3,ce=Math.sin(ee*Math.PI*2*J)*q*.6+Math.cos(ee*Math.PI*3.7*J)*q*.4+Math.sin(ee*Math.PI*5.3*J)*q*.2;return H*ce},Y=l(a),W=-l(T),N=Pe(),Z=o[Math.floor(Math.random()*o.length)],G={id:N,durationMs:Y,scaleStart:l(m),scaleEnd:l(M),scaleEasing:_(f),opacityStart:l(u),opacityEnd:l(b),opacityEasing:_(i),yStart:l(j),yEnd:W,yEasing:_(z),xStart:l(E),xWobbleFunction:X,cleanup:()=>{U(N)},children:Z,frameRate:O,onSpawn:S,onRemove:I};B(N,G)}},[P,y,a,m,M,f,i,u,b,T,z,j,o,O,S,I,B,U,E,s]);return ie(v,F),w.jsx("div",{style:{position:"relative"},children:R.map(d=>w.jsx(en,{...d},d.id))})},en=({durationMs:t,scaleStart:s,scaleEnd:o,scaleEasing:a,opacityStart:i,opacityEnd:u,opacityEasing:b,yStart:m,yEnd:f,yEasing:M,xStart:y,xWobbleFunction:P,cleanup:S,frameRate:I,children:z,onSpawn:O,onRemove:j})=>{const T=n.useRef(performance.now()),E=n.useRef(s),F=n.useRef(i),c=n.useRef(y),h=n.useRef(m),[R,B]=n.useState({x:y,y:m,scale:s,opacity:i}),U=1e3/I,p=Re.useDebounceCallback(B,U,{maxWait:U}),[A,v]=n.useState(!0);n.useEffect(()=>(O(),()=>{j()}),[O,j]);const d=n.useCallback(()=>{const V=performance.now()-T.current,k=Math.min(V/t,1),q=b(k);F.current=i+(u-i)*q;const J=M(k);h.current=m+J*(f-m);const H=a(k);E.current=s+(o-s)*H;const X=P(V)+y;c.current=X,p({x:X,y:h.current,scale:E.current,opacity:F.current}),k>=1&&(v(!1),S())},[i,u,m,f,P,s,o,b,M,a,y,t,S,p]);return ie(d,A),A?w.jsx("div",{style:{left:"50%",top:"50%",position:"absolute",scale:R.scale,opacity:R.opacity.toString(),userSelect:"none",WebkitUserSelect:"none",MozUserSelect:"none",transform:`translate(calc(${R.x}px - 50%), calc(${R.y}px - 50%)) scale(${R.scale})`},children:z}):null},Ve=e.object({active:e.boolean(),components:e.array(e.any()),distanceEasing:e.tuple([e.number(),e.number(),e.number(),e.number()]),distanceStart:g,distanceEnd:g,durationMs:g,frameRate:g,intensity:g,onRemove:e.function(),onSpawn:e.function(),opacityEasing:e.tuple([e.number(),e.number(),e.number(),e.number()]),opacityEnd:g,opacityStart:g,scaleEasing:e.tuple([e.number(),e.number(),e.number(),e.number()]),scaleEnd:g,scaleStart:g,spawnIntervalMs:g}),nn={active:!0,components:["💸","🔥"],distanceEasing:[.25,0,.8,1.2],distanceStart:0,durationMs:1e3,frameRate:50,intensity:1,distanceEnd:{value:400,variation:{type:$.PlusMinus,unit:D.Percent,value:50}},onRemove:()=>{},onSpawn:()=>{},opacityEasing:[.25,0,.8,1.2],opacityEnd:0,opacityStart:1,scaleEasing:[.25,0,.8,1.2],scaleEnd:5,scaleStart:.5,spawnIntervalMs:{value:500,variation:{type:$.PlusMinus,unit:D.Percent,value:50}}},fe=(t={})=>e.parse(Ve,{...nn,...t}),xe=t=>{const{components:s,durationMs:o,distanceStart:a,distanceEnd:i,distanceEasing:u,opacityEasing:b,opacityStart:m,opacityEnd:f,scaleEasing:M,scaleStart:y,scaleEnd:P,onSpawn:S,onRemove:I,frameRate:z,active:O,spawnIntervalMs:j}=fe(t),[T,E]=n.useState({}),F=n.useMemo(()=>Object.values(T),[T]),c=n.useCallback((p,A)=>{E(v=>({...v,[p]:A}))},[E]),h=n.useCallback(p=>{E(A=>{const v={...A};return delete v[p],v})},[E]),R=n.useRef(performance.now()),B=n.useRef(l(j)),U=n.useCallback(()=>{const p=performance.now();if(p-R.current>B.current){R.current=p,B.current=l(j);const v=Pe(),d=s[Math.floor(Math.random()*s.length)],V=Math.random()*2*Math.PI,k={id:v,durationMs:l(o),frameRate:l(z),opacityStart:l(m),opacityEnd:l(f),opacityEasing:b,distanceStart:l(a),distanceEnd:l(i),distanceEasing:u,onSpawn:S,onRemove:I,cleanup:()=>{h(v)},angleRadians:V,scaleStart:l(y),scaleEnd:l(P),scaleEasing:M,Component:d};c(v,k)}},[R,c,h,s,o,z,m,f,b,u,S,I,y,P,M,a,j,i]);return ie(U,O),w.jsx("div",{style:{position:"relative"},children:F.map(p=>w.jsx(tn,{...p},p.id))})},tn=({durationMs:t,frameRate:s,angleRadians:o,scaleStart:a,scaleEnd:i,scaleEasing:u,opacityStart:b,opacityEnd:m,opacityEasing:f,distanceStart:M,distanceEnd:y,distanceEasing:P,onSpawn:S,onRemove:I,cleanup:z,Component:O})=>{const j=n.useRef(performance.now()),T=Math.cos(o)*M,E=Math.sin(o)*M,F=n.useRef(T),c=n.useRef(E),h=n.useRef(a),R=n.useRef(b),[B,U]=n.useState(!0);n.useEffect(()=>(S(),()=>{I()}),[S,I]);const[p,A]=n.useState({x:T,y:E,scale:a,opacity:b}),v=1e3/s,d=Re.useDebounceCallback(A,v,{maxWait:v}),V=n.useCallback(()=>{const k=performance.now()-j.current,q=Math.min(k/t,1),J=_(f)(q);R.current=b+(m-b)*J;const H=_(u)(q);h.current=a+(i-a)*H;const X=_(P)(q),Y=M+(y-M)*X,W=o;F.current=Math.cos(W)*Y,c.current=Math.sin(W)*Y,d({x:F.current,y:c.current,opacity:R.current,scale:h.current}),q>=1&&(U(!1),z())},[d,z,M,y,P,b,m,f,a,i,u,o,t]);return ie(V,B),B?w.jsx("div",{style:{left:"50%",top:"50%",position:"absolute",userSelect:"none",WebkitUserSelect:"none",MozUserSelect:"none",opacity:p.opacity,transform:`translate(calc(${p.x}px - 50%), calc(${p.y}px - 50%)) scale(${p.scale})`},children:O}):null},Ie=e.object({dampingCoefficient:e.pipe(e.number(),e.toMinValue(0),e.toMaxValue(1)),initialAngle:e.pipe(e.number(),e.toMinValue(0),e.toMaxValue(Math.PI*2)),initialAngularVelocity:e.pipe(e.number(),e.toMinValue(0)),maxAngularVelocity:e.pipe(e.number(),e.toMinValue(0)),onMaxAngularVelocity:e.function(),onClick:e.function(),direction:e.union([e.literal("clockwise"),e.literal("antiClockwise")])}),on={dampingCoefficient:.5,initialAngle:0,initialAngularVelocity:0,maxAngularVelocity:Math.PI*20,onMaxAngularVelocity:()=>{},onClick:()=>{},direction:"clockwise"},Ce=(t={})=>e.parse(Ie,{...on,...t}),je=e.object({onScaleChange:e.function(),onScaleEnd:e.function(),onScaleStart:e.function(),scale:e.pipe(e.number(),e.toMinValue(0)),scaleDurationMs:e.pipe(e.number(),e.toMinValue(0)),scaleEasing:ae}),sn={onScaleChange:()=>{},onScaleEnd:()=>{},onScaleStart:()=>{},scale:1,scaleDurationMs:500,scaleEasing:[.25,-.75,.8,1.2]},me=(t={})=>e.parse(je,{...sn,...t}),Te=e.object({durationMs:e.pipe(e.number(),e.toMinValue(0)),easing:ae,onResetStart:e.function(),onResetEnd:e.function(),onResetCancel:e.function()}),an={durationMs:200,easing:[.25,-.75,.8,1.2],onResetStart:()=>{},onResetEnd:()=>{},onResetCancel:()=>{}},Se=(t={})=>e.parse(Te,{...an,...t}),Be=e.object({angularVelocityPerClick:g,onSpawn:e.function(),onRemove:e.function(),active:e.boolean()}),cn={angularVelocityPerClick:Math.PI*2,onSpawn:()=>{},onRemove:()=>{},active:!0},ye=(t={})=>e.parse(Be,{...cn,...t}),rn=e.object({scaleConfig:je,bubbleConfig:Ee,sparkConfig:Ve,resetConfig:Te,spinnerConfig:Ie,clickConfig:Be}),qe=e.object({breakpoint:e.pipe(e.number(),e.toMinValue(0),e.toMaxValue(1)),config:rn}),ln=e.array(qe),un=[{breakpoint:.9,config:{scaleConfig:{scale:3}}},{breakpoint:.7,config:{scaleConfig:{scale:2}}},{breakpoint:.3,config:{scaleConfig:{scale:1.5}}}],fn=t=>{const{breakpoint:s,config:o}=t,{scaleConfig:a,bubbleConfig:i,sparkConfig:u,resetConfig:b,spinnerConfig:m,clickConfig:f}=o;return e.parse(qe,{breakpoint:s,config:{scaleConfig:me(a),bubbleConfig:ue(i),sparkConfig:fe(u),resetConfig:Se(b),spinnerConfig:Ce(m),clickConfig:ye(f)}})},ze=(t=un,s)=>{const o=t.map(a=>{const i=a.config,u={scaleConfig:{...s.scaleConfig,...i.scaleConfig},bubbleConfig:{...s.bubbleConfig,...i.bubbleConfig},sparkConfig:{...s.sparkConfig,...i.sparkConfig},resetConfig:{...s.resetConfig,...i.resetConfig},spinnerConfig:{...s.spinnerConfig,...i.spinnerConfig},clickConfig:{...s.clickConfig,...i.clickConfig}};return fn({...a,config:u})});return e.parse(ln,o)},se=(t={},s)=>{const o=s(t),[a,i]=n.useState(o),u=n.useMemo(()=>JSON.stringify(t),[t]);n.useEffect(()=>{const m=s(JSON.parse(u));i(m)},[u,s]);const b=n.useCallback(()=>{i(o)},[o]);return[a,i,o,b]},pn="fidget-spinner-container",gn=({bubbleConfig:t,children:s,resetConfig:o,scaleConfig:a,sparkConfig:i,spinnerConfig:u,velocityBreakpoints:b,clickConfig:m})=>{const[f,M,y,P]=se(a,me),[S,I,z,O]=se(o,Se),[j,T,E,F]=se(i,fe),[c,h,R,B]=se(u,Ce),[U,p,A,v]=se(t,ue),[d,V,k,q]=se(m,ye),H=ze(b,{scaleConfig:y,resetConfig:z,bubbleConfig:A,sparkConfig:E,spinnerConfig:R,clickConfig:k}),[X,Y]=n.useState(c.initialAngle),W=n.useRef(c.initialAngle),N=n.useRef(c.initialAngularVelocity),Z=n.useRef(!1),G=n.useRef(null),L=n.useRef(null),ee=n.useRef(null),ce=n.useRef(null),ve=n.useRef(null),Oe=n.useRef(!1),Fe=n.useRef(y.scale),re=n.useRef(null),[dn,$e]=n.useState(y.scale),[pe,De]=n.useState(!1),Ue=n.useMemo(()=>[...H].sort((x,K)=>K.breakpoint-x.breakpoint),[H]),We=n.useCallback(()=>{const x=Math.abs(N.current)/c.maxAngularVelocity;return Ue.find(ne=>x>=ne.breakpoint)||null},[Ue,c.maxAngularVelocity]),Ne=n.useCallback(()=>{F(),v(),P(),O(),B(),q()},[F,v,P,O,B,q]),Me=n.useCallback(({newScale:x=1})=>{ee.current=performance.now(),ce.current=Fe.current,ve.current=x,Oe.current=!0,f.onScaleStart(),f.onScaleChange(x)},[f]),Le=n.useCallback(()=>{ee.current=null,ce.current=null,ve.current=null,Oe.current=!1,f.onScaleEnd()},[f]),Xe=n.useCallback(()=>{const x=f.scaleDurationMs,K=ee.current,ne=ce.current,te=ve.current;if(!K||!ne||!te)return;const r=performance.now()-K,ge=Math.min(r/x,1),oe=_(f.scaleEasing)(ge),Q=ne+(te-ne)*oe;Fe.current=Q,$e(Q),ge>=1&&Le()},[$e,Le,f.scaleDurationMs,f.scaleEasing]),_e=n.useCallback(()=>{Y(c.initialAngle),W.current=c.initialAngle,N.current=c.initialAngularVelocity,Z.current=!1,G.current=null,L.current=null,re.current=null},[c.initialAngle,c.initialAngularVelocity]),Je=n.useCallback(()=>{S.onResetStart(),Z.current=!0,G.current=performance.now(),L.current=W.current},[S]),He=n.useCallback(()=>{Z.current=!1,G.current=null,L.current=null,S.onResetCancel()},[S]),Ye=n.useCallback(x=>{if(Z.current){G.current===null&&(G.current=performance.now());const oe=performance.now()-G.current,Q=Math.min(oe/S.durationMs,1),be=_(S.easing)(Q);L.current===null&&(L.current=W.current);const de=L.current<0?-2*Math.PI:0,le=L.current+(de-L.current)*be;if(Q>=1){_e(),S.onResetEnd(),De(!1);return}W.current=le,Y(le);return}const K=x/1e3,ne=N.current<15?c.dampingCoefficient*6:c.dampingCoefficient,te=Math.min(N.current*Math.exp(-ne*K),c.maxAngularVelocity);te===c.maxAngularVelocity&&c.onMaxAngularVelocity();const r=We();if(r&&r.breakpoint!==re.current){re.current=r==null?void 0:r.breakpoint;const oe=r==null?void 0:r.config.scaleConfig;oe&&(M(oe),Me({newScale:oe.scale}));const Q=r==null?void 0:r.config.bubbleConfig;Q&&p(Q);const he=r==null?void 0:r.config.sparkConfig;he&&T(he);const be=r==null?void 0:r.config.resetConfig;be&&I(be);const de=r==null?void 0:r.config.spinnerConfig;de&&h(de);const le=r==null?void 0:r.config.clickConfig;le&&V(le)}else!r&&re.current!==null&&(re.current=null,Me({newScale:y.scale}),Ne());te<2&&Je();const ge=te*K,ke=(W.current+ge)%(2*Math.PI);W.current=ke,N.current=te,Y(ke)},[Je,_e,Me,c,S,We,Ne,y,M,p,T,I,h,V]),Cn=n.useCallback(x=>{pe&&(Ye(x),Xe())},[pe,Ye,Xe]),mn=n.useCallback(()=>{Z.current===!0&&He(),De(!0),N.current=N.current+l(d.angularVelocityPerClick)},[He,d]);ie(Cn);const Sn=c.direction==="clockwise"?X:-X;return w.jsxs("div",{id:pn,style:{position:"relative",userSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none"},children:[w.jsx("div",{onClick:x=>{mn(),d.active&&bn(x,d)},style:{cursor:"pointer",position:"absolute",left:"50%",top:"50%",userSelect:"none",MozUserSelect:"none",WebkitUserSelect:"none",WebkitTapHighlightColor:"transparent",transform:`translate(-50%, -50%) rotate(${Sn}rad) scale(${dn})`,zIndex:100},children:s}),w.jsxs("div",{style:{position:"relative",left:"50%",top:"50%"},children:[w.jsx(Ae,{...U,active:pe}),w.jsx(xe,{...j,active:pe})]})]})};function bn(t,s){const o=document.createElement("div");o.style.left=`${t.pageX}px`,o.style.top=`${t.pageY}px`,o.style.zIndex="200",o.style.background="rgb(255 0 0 / 80%)",o.style.borderRadius="50%",o.style.height="40px",o.style.pointerEvents="none",o.style.position="absolute",o.style.transform="translate(-50%, -50%) scale(0)",o.style.transition="all 300ms ease-out",o.style.width="40px",o.style.zIndex="200";const a=300;document.body.appendChild(o),s.onSpawn(),requestAnimationFrame(()=>{o.style.transform="translate(-50%, -50%) scale(1)",o.style.opacity="0"}),setTimeout(()=>{o.remove(),s.onRemove()},a)}C.Bubbles=Ae,C.FidgetSpinner=gn,C.Sparks=xe,C.buildBubbleConfig=ue,C.buildClickConfig=ye,C.buildResetConfig=Se,C.buildScaleConfig=me,C.buildSparkConfig=fe,C.buildSpinnerConfig=Ce,C.buildVelocityBreakpointConfigs=ze,Object.defineProperty(C,Symbol.toStringTag,{value:"Module"})}); //# sourceMappingURL=index.umd.cjs.map