react-prize-wheel
Version:
A simple, performant React prize wheel component
3 lines (2 loc) • 9.55 kB
JavaScript
;var e=require("react/jsx-runtime"),t=require("react");function n(e){if(0===e.length)throw new Error("Cannot select from empty segments array");const t=e.filter(e=>!e.disabled);if(0===t.length)throw new Error("No enabled segments available for selection");const n=t.reduce((e,t)=>e+(t.weight??1),0);if(n<=0){const n=t[Math.floor(Math.random()*t.length)];return{segment:n,index:e.indexOf(n)}}const r=Math.random()*n;let s=0;for(const n of t)if(s+=n.weight??1,r<=s)return{segment:n,index:e.indexOf(n)};const i=t[t.length-1];return{segment:i,index:e.indexOf(i)}}function r(e,t,n=5,r="top"){let i;if("string"==typeof t){if(i=e.findIndex(e=>e.id===t),-1===i)throw new Error(`Segment with ID "${t}" not found`)}else if(i=t,i<0||i>=e.length)throw new Error(`Target index ${i} is out of bounds`);if(e[i]?.disabled)throw new Error("Target segment is disabled and cannot be targeted");const o=e.map(e=>e.weight??1),a=o.reduce((e,t)=>e+t,0);let l=0;for(let e=0;e<i;e++){l+=o[e]/a*360}const d=o[i]/a*360;return 360*n+(l+d/2+s(r))%360+(Math.random()-.5)*d*.8}function s(e){switch(e){case"top":default:return 0;case"right":return 90;case"bottom":return 180;case"left":return 270}}function i(e,t,n="top"){let r=(t%360+360)%360;r=(r-s(n)+360)%360,r=(270-r+360)%360;const i=e.map(e=>e.weight??1),o=i.reduce((e,t)=>e+t,0);let a=0,l=null,d=-1;for(let t=0;t<e.length;t++){const n=a+i[t]/o*360;if(r>=a&&r<n){l=e[t],d=t;break}a=n}return null===l&&(l=e[e.length-1],d=e.length-1),{segment:l,index:d}}function o(e){if("string"!=typeof e)return!1;return/^(#([0-9A-Fa-f]{3}){1,2}|rgb\((\d{1,3},\s*){2}\d{1,3}\)|rgba\((\d{1,3},\s*){3}(0(\.\d+)?|1(\.0+)?)\)|hsl\(\d{1,3},\s*\d{1,3}%,\s*\d{1,3}%\)|hsla\(\d{1,3},\s*\d{1,3}%,\s*\d{1,3}%,\s*(0(\.\d+)?|1(\.0+)?)\)|[a-zA-Z]+)$/.test(e.trim())}function a({segments:e,animation:s={},onSpinStart:a,onSpinComplete:l,disabled:d=!1,predefinedResult:c,pointerPosition:u="top"}){const g=t.useMemo(()=>({duration:3e3,easing:"ease-out",spins:5,...s}),[s]),[p,f]=t.useState({rotation:0,isSpinning:!1,lastResult:null,animationStartTime:0,progress:0}),h=t.useRef(),m=t.useRef(0),b=t.useRef(0),x=t.useRef(0),S=function(e){const t=[];if(!e||!Array.isArray(e))return t.push("Segments must be a valid array"),{isValid:!1,errors:t};e.length<2&&t.push("Wheel must have at least 2 segments"),e.length>100&&t.push("Wheel cannot have more than 100 segments"),e.forEach((e,n)=>{var r;e.id||t.push(`Segment ${n+1}: Missing required 'id' property`),e.text?e.text="string"!=typeof(r=e.text)||null==r?"":r.replace(/[<>'"]/g,"").replace(/&/g," ").replace(/\s+/g," ").trim().substring(0,100):t.push(`Segment ${n+1}: Missing required 'text' property`),e.color?o(e.color)||t.push(`Segment ${n+1}: Invalid color format`):t.push(`Segment ${n+1}: Missing required 'color' property`),e.textColor&&!o(e.textColor)&&t.push(`Segment ${n+1}: Invalid text color format`),e.borderColor&&!o(e.borderColor)&&t.push(`Segment ${n+1}: Invalid border color format`),void 0!==e.weight&&e.weight<=0&&t.push(`Segment ${n+1}: Weight must be positive`)});const n=e.map(e=>e.id),r=n.filter((e,t)=>n.indexOf(e)!==t);return r.length>0&&t.push(`Duplicate segment IDs: ${r.join(", ")}`),{isValid:0===t.length,errors:t}}(e),w=t.useCallback(()=>{if(void 0!==c)if("string"==typeof c){const t=e.findIndex(e=>e.id===c);if(-1!==t){if(!e[t]?.disabled)return{targetIndex:t,targetSegment:e[t]};console.warn(`Predefined result segment "${c}" is disabled, using random selection`)}else console.warn(`Predefined result segment "${c}" not found, using random selection`)}else if("number"==typeof c&&c>=0&&c<e.length){if(!e[c]?.disabled)return{targetIndex:c,targetSegment:e[c]};console.warn(`Predefined result segment at index ${c} is disabled, using random selection`)}const t=n(e);return{targetIndex:t.index,targetSegment:t.segment}},[e,c]),y=t.useCallback((e,t)=>{switch(t){case"linear":default:return e;case"ease-in":return e*e;case"ease-out":return 1-(1-e)**2;case"ease-in-out":return e<.5?2*e*e:1-(-2*e+2)**2/2}},[]),v=t.useCallback(t=>{m.current||(m.current=t);const n=t-m.current,s=Math.min(n/g.duration,1),o=y(s,g.easing),a=x.current-b.current,d=b.current+a*o;if(f(e=>({...e,rotation:d,progress:s})),s<1)h.current=requestAnimationFrame(v);else{const t=d%360,s=i(e,t,u);if(s.segment.disabled){const t=w(),n=r(e,t.targetIndex,2,u);return m.current=0,b.current=d,x.current=d+n,void(h.current=requestAnimationFrame(v))}const o={segment:s.segment,index:s.index,angle:t,duration:n,timestamp:Date.now()};f(e=>({...e,isSpinning:!1,lastResult:o,rotation:d})),l?.(o)}},[g,e,l,y,u,w]),k=t.useCallback(()=>{if(d||p.isSpinning||!S.isValid)return;const{targetIndex:t}=w(),n=r(e,t,g.spins,u);m.current=0,b.current=p.rotation,x.current=p.rotation+n,f(e=>({...e,isSpinning:!0,animationStartTime:Date.now(),progress:0})),a?.(),h.current=requestAnimationFrame(v)},[d,p.isSpinning,p.rotation,S.isValid,w,e,g,u,a,v]),C=t.useCallback(()=>{h.current&&(cancelAnimationFrame(h.current),h.current=void 0)},[]),I=t.useCallback(()=>{C(),f({rotation:0,isSpinning:!1,lastResult:null,animationStartTime:0,progress:0})},[C]);return t.useEffect(()=>()=>{C()},[C]),{rotation:p.rotation,isSpinning:p.isSpinning,lastResult:p.lastResult,progress:p.progress,isValid:S.isValid,validationErrors:S.errors,spin:k,reset:I,animationConfig:g}}exports.SpinWheel=function({segments:n,size:r=400,onSpinComplete:s,onSpinStart:i,theme:o={},animation:l={},pointer:d={},disabled:c=!1,className:u="",style:g={},showSpinButton:p=!0,spinButtonText:f="SPIN",predefinedResult:h}){const m=t.useRef(null),b=t.useRef(null),x={background:"#ffffff",border:"#dee2e6",text:"#212529",...o},S=t.useMemo(()=>({style:"arrow",color:"#ff0000",size:20,position:"top",...d}),[d]),{rotation:w,isSpinning:y,lastResult:v,isValid:k,validationErrors:C,spin:I}=a({segments:n,animation:l,pointerPosition:S.position,...i&&{onSpinStart:i},...s&&{onSpinComplete:s},disabled:c,...void 0!==h&&{predefinedResult:h}}),T=t.useMemo(()=>function(e){const t=e.map(e=>e.weight??1),n=t.reduce((e,t)=>e+t,0);return t.map(e=>e/n*360)}(n),[n]),$=t.useCallback((e,t,n,r)=>{const{style:s,color:i,size:o,position:a}=S;switch(e.save(),e.translate(t,n),a){case"right":e.rotate(Math.PI/2);break;case"bottom":e.rotate(Math.PI);break;case"left":e.rotate(-Math.PI/2)}const l=-(r+10);switch(e.beginPath(),e.fillStyle=i,s){case"arrow":e.moveTo(0,l+o),e.lineTo(-o/4,l+2*o/3),e.lineTo(-o/8,l+2*o/3),e.lineTo(-o/8,l),e.lineTo(o/8,l),e.lineTo(o/8,l+2*o/3),e.lineTo(o/4,l+2*o/3),e.lineTo(0,l+o);break;case"triangle":e.moveTo(0,l+o),e.lineTo(-o/2,l),e.lineTo(o/2,l),e.lineTo(0,l+o);break;case"circle":e.arc(0,l+o/2,o/2,0,2*Math.PI)}e.closePath(),e.fill(),e.restore()},[S]),R=t.useCallback((e,t,n,r,s,i)=>{const o=t.split(" ");let a="";const l=[];for(let t=0;t<o.length;t++){const n=`${a+o[t]} `;e.measureText(n).width>s&&t>0?(l.push(a),a=`${o[t]} `):a=n}l.push(a);const d=r-(l.length-1)*i/2;l.forEach((t,r)=>{e.fillText(t.trim(),n,d+r*i)})},[]),M=t.useCallback(()=>{const e=m.current;if(!e||!k)return;const t=e.getContext("2d");if(!t)return;const s=r/2,i=r/2,o=(r-40)/2;t.clearRect(0,0,r,r),t.save(),t.translate(s,i),t.rotate(w*Math.PI/180);let a=0;n.forEach((e,r)=>{const s=T[r]*Math.PI/180;t.beginPath(),t.moveTo(0,0),t.arc(0,0,o,a,a+s),t.closePath(),t.fillStyle=e.color,t.fill(),e.borderColor&&e.borderWidth&&(t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.stroke());const i=a+s/2,l=.7*o,d=Math.cos(i)*l,c=Math.sin(i)*l;t.save(),t.translate(d,c),t.rotate(i+Math.PI/2);const u=Math.min(16,o/n.length);t.font=`bold ${u}px Arial, sans-serif`,t.fillStyle=e.textColor||function(e){const t=e.replace("#","");return(.299*Number.parseInt(t.substr(0,2),16)+.587*Number.parseInt(t.substr(2,2),16)+.114*Number.parseInt(t.substr(4,2),16))/255>.5?"#000000":"#ffffff"}(e.color),t.textAlign="center",t.textBaseline="middle";const g=.4*o;R(t,e.text,0,0,g,1.2*u),t.restore(),a+=s}),t.restore(),$(t,s,i,o)},[r,k,w,n,T,$,R]);return t.useEffect(()=>{M()},[M]),t.useEffect(()=>{const e=m.current;e&&(e.width=r,e.height=r,e.style.width=`${r}px`,e.style.height=`${r}px`,M())},[r,M]),k?e.jsxs("div",{ref:b,className:`spin-wheel-container ${u}`,style:{display:"inline-block",textAlign:"center",userSelect:"none",...g},children:[e.jsxs("button",{type:"button",className:"spin-wheel",style:{position:"relative",display:"inline-block",background:x.background,borderRadius:"50%",border:`2px solid ${x.border}`,cursor:c?"not-allowed":"pointer",opacity:c?.6:1,padding:0},onClick:c||y?void 0:I,disabled:c||y,"aria-label":"Spin the wheel",children:[e.jsx("canvas",{ref:m,style:{display:"block",borderRadius:"50%"}}),p&&e.jsx("button",{type:"button",className:"spin-wheel-button",onClick:I,disabled:c||y,style:{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",padding:"8px 16px",backgroundColor:"#007bff",color:"white",border:"none",borderRadius:"20px",cursor:c||y?"not-allowed":"pointer",fontSize:"14px",fontWeight:"bold",opacity:c||y?.6:1,zIndex:10},children:y?"Spinning...":f})]}),v&&!y&&e.jsxs("div",{className:"spin-wheel-result",style:{marginTop:"16px",padding:"12px",backgroundColor:x.background,border:`1px solid ${x.border}`,borderRadius:"8px",color:x.text},children:[e.jsx("strong",{children:"Result: "}),v.segment.text]})]}):e.jsxs("div",{className:`spin-wheel-error ${u}`,style:g,children:[e.jsx("p",{children:"Invalid wheel configuration:"}),e.jsx("ul",{children:C.map(t=>e.jsx("li",{children:t},t))})]})},exports.VERSION="1.0.0",exports.calculateTargetAngle=r,exports.getSegmentAtAngle=i,exports.getWeightedSegment=n,exports.useSpinWheel=a;
//# sourceMappingURL=index.js.map