swipeon-react 1.0.4 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,12 +9,10 @@ A high-performance, zero-dependency React swipe card library with smooth animati
9
9
  ## 🎬 Demo
10
10
 
11
11
  <p align="center">
12
- <a href="assets/tinder-swipe-example.mp4">
13
- <img src="https://img.shields.io/badge/▶_Watch_Demo_Video-FF0000?style=for-the-badge&logo=googlechrome&logoColor=white" alt="Watch Demo Video" />
14
- </a>
12
+ <img src="assets/tinder-swipe-example.gif" alt="SwipeOn React Demo - Tinder-style swipe cards" width="300" />
15
13
  </p>
16
14
 
17
- > 📹 **[Watch Demo Video](assets/tinder-swipe-example.mp4)** - Tinder-style profile cards with 4-direction swipes, custom overlay labels (LIKE/NOPE), and smooth 60fps animations.
15
+ > Tinder-style profile cards with 4-direction swipes, custom overlay labels (LIKE/NOPE), and smooth 60fps animations.
18
16
 
19
17
  ## ✨ Features
20
18
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c);const l=(e,t)=>t===0?0:Math.abs(e/t),u=(e,t,n=50)=>{let r=Math.abs(e),i=Math.abs(t);return r<n&&i<n?null:r>i?e>0?`right`:`left`:t>0?`down`:`up`},d=(e,t=15,n=300)=>{let r=e/n*t;return Math.max(-t,Math.min(t,r))},f=(e,t,n=100)=>{let r=1-Math.sqrt(e*e+t*t)/(n*3);return Math.max(.5,Math.min(1,r))},p=e=>`touches`in e&&e.touches&&e.touches.length>0?{x:e.touches[0].clientX,y:e.touches[0].clientY}:`clientX`in e&&`clientY`in e?{x:e.clientX,y:e.clientY}:{x:0,y:0},m={threshold:100,velocityThreshold:.5,maxRotation:15,exitDuration:300,returnDuration:200,enableRotation:!0,preventSwipe:[],fadeOnSwipe:!0},h=(e={},t={})=>{let{threshold:n,velocityThreshold:r,maxRotation:i,exitDuration:a,returnDuration:o,enableRotation:s,preventSwipe:h,fadeOnSwipe:g}=(0,c.useMemo)(()=>({...m,...t}),[t.threshold,t.velocityThreshold,t.maxRotation,t.exitDuration,t.returnDuration,t.enableRotation,t.fadeOnSwipe,JSON.stringify(t.preventSwipe)]),_=(0,c.useRef)(null),v=(0,c.useRef)(null),y=(0,c.useRef)(new Set),b=(0,c.useRef)(e);b.current=e;let x=(0,c.useRef)(`translate3d(0px, 0px, 0px) rotate(0deg)`),S=(0,c.useRef)(1),C=(0,c.useRef)(`none`),[w,T]=(0,c.useState)({transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1,transition:`none`}),[E,D]=(0,c.useState)({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),O=(0,c.useCallback)(e=>{T(t=>{let n={...t,...e};return t.transform===n.transform&&t.opacity===n.opacity&&t.transition===n.transition?t:n})},[]),k=(0,c.useCallback)((e,t)=>{let n=setTimeout(()=>{y.current.delete(n),e()},t);return y.current.add(n),n},[]),A=(0,c.useCallback)(e=>{let t=p(e),n=Date.now();D({isDragging:!0,startX:t.x,startY:t.y,currentX:t.x,currentY:t.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:n}),C.current=`none`,O({transition:`none`}),b.current.onSwipeStart?.()},[O]),j=(0,c.useRef)(E);j.current=E;let M=(0,c.useCallback)(e=>{let t=j.current;if(!t.isDragging)return;let r=p(e),a=r.x-t.startX,o=r.y-t.startY;h.includes(`left`)&&a<0&&(a=0),h.includes(`right`)&&a>0&&(a=0),h.includes(`up`)&&o<0&&(o=0),h.includes(`down`)&&o>0&&(o=0),D(e=>({...e,currentX:r.x,currentY:r.y,deltaX:a,deltaY:o})),v.current&&cancelAnimationFrame(v.current),v.current=requestAnimationFrame(()=>{let e=s?d(a,i):0,t=g?f(a,o,n):1,r=`translate3d(${a}px, ${o}px, 0px) rotate(${e}deg)`;x.current=r,S.current=t,O({transform:r,opacity:t})})},[s,i,n,h,g,O]),N=(0,c.useCallback)(()=>{let e=j.current;if(!e.isDragging)return;let{deltaX:t,deltaY:c,startTime:f}=e,p=Date.now()-f,m=Math.sqrt(t*t+c*c),g=l(m,p),_=u(t,c,n/2),v=m>=n||g>=r,y=_&&h.includes(_);if(v&&_&&!y){let e=`transform ${a}ms ease-out, opacity ${a}ms ease-out`,n=t*2;O({transition:e,transform:`translate3d(${n}px, ${c*2}px, 0px) rotate(${s?d(n,i*2):0}deg)`,opacity:0}),k(()=>{switch(_){case`left`:b.current.onSwipeLeft?.();break;case`right`:b.current.onSwipeRight?.();break;case`up`:b.current.onSwipeUp?.();break;case`down`:b.current.onSwipeDown?.();break}b.current.onSwipeEnd?.(),k(()=>{O({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},a)}else O({transition:`transform ${o}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${o}ms ease-out`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1}),b.current.onSwipeEnd?.();D(e=>({...e,isDragging:!1}))},[n,r,a,o,s,i,h,O,k]),P=(0,c.useRef)({handleSwipeStart:A,handleSwipeMove:M,handleSwipeEnd:N});return P.current={handleSwipeStart:A,handleSwipeMove:M,handleSwipeEnd:N},(0,c.useEffect)(()=>{let e=_.current;if(!e)return;let t=e=>{P.current.handleSwipeStart(e)};return e.addEventListener(`pointerdown`,t),()=>{e.removeEventListener(`pointerdown`,t)}},[]),(0,c.useEffect)(()=>{if(!E.isDragging)return;let e=e=>{P.current.handleSwipeMove(e)},t=()=>{P.current.handleSwipeEnd()};return document.addEventListener(`pointermove`,e),document.addEventListener(`pointerup`,t),document.addEventListener(`pointercancel`,t),()=>{document.removeEventListener(`pointermove`,e),document.removeEventListener(`pointerup`,t),document.removeEventListener(`pointercancel`,t)}},[E.isDragging]),(0,c.useEffect)(()=>()=>{v.current&&cancelAnimationFrame(v.current),y.current.forEach(e=>clearTimeout(e)),y.current.clear()},[]),{ref:_,transform:w.transform,opacity:w.opacity,transition:w.transition,isDragging:E.isDragging,deltaX:E.deltaX,deltaY:E.deltaY}},g={fontSize:`2rem`,fontWeight:`bold`,color:`#fff`,letterSpacing:`2px`,textShadow:`0 2px 8px rgba(0, 0, 0, 0.3)`},_={right:{backgroundColor:`transparent`,label:`✓ Accept`,labelStyle:g},left:{backgroundColor:`transparent`,label:`✗ Reject`,labelStyle:g},up:{backgroundColor:`transparent`,label:`⭐ Super`,labelStyle:g},down:{backgroundColor:`transparent`,label:`⏭ Skip`,labelStyle:g}},v=[],y=({children:e,className:t=``,style:n,onSwipeLeft:r,onSwipeRight:i,onSwipeUp:a,onSwipeDown:o,onSwipeStart:s,onSwipeEnd:l,threshold:u=100,velocityThreshold:d=.5,maxRotation:f=15,exitDuration:p=300,returnDuration:m=200,enableRotation:g=!0,preventSwipe:y=v,fadeOnSwipe:b=!0,swipeStyles:x,showOverlay:S=!0})=>{let{ref:C,transform:w,opacity:T,transition:E,isDragging:D,deltaX:O,deltaY:k}=h((0,c.useMemo)(()=>({onSwipeLeft:r,onSwipeRight:i,onSwipeUp:a,onSwipeDown:o,onSwipeStart:s,onSwipeEnd:l}),[r,i,a,o,s,l]),(0,c.useMemo)(()=>({threshold:u,velocityThreshold:d,maxRotation:f,exitDuration:p,returnDuration:m,enableRotation:g,preventSwipe:y,fadeOnSwipe:b}),[u,d,f,p,m,g,y,b])),A=(0,c.useMemo)(()=>({right:{..._.right,...x?.right,labelStyle:{..._.right.labelStyle,...x?.right?.labelStyle}},left:{..._.left,...x?.left,labelStyle:{..._.left.labelStyle,...x?.left?.labelStyle}},up:{..._.up,...x?.up,labelStyle:{..._.up.labelStyle,...x?.up?.labelStyle}},down:{..._.down,...x?.down,labelStyle:{..._.down.labelStyle,...x?.down?.labelStyle}}}),[x?.right,x?.left,x?.up,x?.down]),j=Math.abs(O),M=Math.abs(k),N=j>=M,P=N?j:M,F=Math.min(P/u,1),I=N&&O>20,L=N&&O<-20,R=!N&&k<-20,z=!N&&k>20,B=F>=.15,V=S&&D&&P>10,H=(0,c.useMemo)(()=>I?`right`:L?`left`:R?`up`:z?`down`:null,[I,L,R,z]),U=H?A[H]:null,W=U?.backgroundColor??`inherit`,G=U?{label:U.label,labelStyle:U.labelStyle}:null,K=(0,c.useMemo)(()=>({...n,transform:w,opacity:T,transition:E,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:D?`grabbing`:`grab`,willChange:D?`transform, opacity`:`auto`,position:`absolute`}),[n,w,T,E,D]),q=(0,c.useMemo)(()=>({position:`absolute`,top:0,left:0,right:0,bottom:0,display:`flex`,alignItems:`center`,justifyContent:`center`,borderRadius:`inherit`,pointerEvents:`none`,zIndex:10,opacity:V?F:0,transition:D?`none`:`opacity 200ms ease-out`,backgroundColor:V?W:`inherit`}),[V,F,D,W]),J=(0,c.useMemo)(()=>({...G?.labelStyle,transform:`scale(${.8+F*.4})`,opacity:F}),[G?.labelStyle,F]);return c.default.createElement(`div`,{ref:C,className:`swipeon-card ${t}`,style:K},e,c.default.createElement(`div`,{style:q},V&&B&&G?.label&&c.default.createElement(`span`,{style:J},G.label)))},b=(0,c.memo)(y);exports.SwipeCard=b,exports.useSwipe=h;
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c);const l=(e,t)=>t===0?0:Math.abs(e/t),u=(e,t,n=50)=>{let r=Math.abs(e),i=Math.abs(t);return r<n&&i<n?null:r>i?e>0?`right`:`left`:t>0?`down`:`up`},d=(e,t=15,n=300)=>{let r=e/n*t;return Math.max(-t,Math.min(t,r))},f=(e,t,n=100)=>{let r=1-Math.sqrt(e*e+t*t)/(n*3);return Math.max(.5,Math.min(1,r))},p=e=>`touches`in e&&e.touches&&e.touches.length>0?{x:e.touches[0].clientX,y:e.touches[0].clientY}:`clientX`in e&&`clientY`in e?{x:e.clientX,y:e.clientY}:{x:0,y:0},m={threshold:100,velocityThreshold:.5,maxRotation:15,exitDuration:300,returnDuration:200,enableRotation:!0,preventSwipe:[],fadeOnSwipe:!0},h=(e={},t={})=>{let{threshold:n,velocityThreshold:r,maxRotation:i,exitDuration:a,returnDuration:o,enableRotation:s,preventSwipe:h,fadeOnSwipe:g}=(0,c.useMemo)(()=>({...m,...t}),[t.threshold,t.velocityThreshold,t.maxRotation,t.exitDuration,t.returnDuration,t.enableRotation,t.fadeOnSwipe,JSON.stringify(t.preventSwipe)]),_=(0,c.useRef)(null),v=(0,c.useRef)(null),y=(0,c.useRef)(new Set),b=(0,c.useRef)(e);b.current=e;let x=(0,c.useRef)(`translate3d(0px, 0px, 0px) rotate(0deg)`),S=(0,c.useRef)(1),C=(0,c.useRef)(`none`),[w,T]=(0,c.useState)({transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1,transition:`none`}),[E,D]=(0,c.useState)({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),O=(0,c.useCallback)(e=>{T(t=>{let n={...t,...e};return t.transform===n.transform&&t.opacity===n.opacity&&t.transition===n.transition?t:n})},[]),k=(0,c.useCallback)((e,t)=>{let n=setTimeout(()=>{y.current.delete(n),e()},t);return y.current.add(n),n},[]),A=(0,c.useCallback)(e=>{let t=p(e),n=Date.now();D({isDragging:!0,startX:t.x,startY:t.y,currentX:t.x,currentY:t.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:n}),C.current=`none`,O({transition:`none`}),b.current.onSwipeStart?.()},[O]),j=(0,c.useRef)(E);j.current=E;let M=(0,c.useCallback)(e=>{let t=j.current;if(!t.isDragging)return;let r=p(e),a=r.x-t.startX,o=r.y-t.startY;h.includes(`left`)&&a<0&&(a=0),h.includes(`right`)&&a>0&&(a=0),h.includes(`up`)&&o<0&&(o=0),h.includes(`down`)&&o>0&&(o=0),D(e=>({...e,currentX:r.x,currentY:r.y,deltaX:a,deltaY:o})),v.current&&cancelAnimationFrame(v.current),v.current=requestAnimationFrame(()=>{let e=s?d(a,i):0,t=g?f(a,o,n):1,r=`translate3d(${a}px, ${o}px, 0px) rotate(${e}deg)`;x.current=r,S.current=t,O({transform:r,opacity:t})})},[s,i,n,h,g,O]),N=(0,c.useCallback)(()=>{let e=j.current;if(!e.isDragging)return;let{deltaX:t,deltaY:c,startTime:f}=e,p=Date.now()-f,m=Math.sqrt(t*t+c*c),g=l(m,p),_=u(t,c,n/2),v=m>=n||g>=r,y=_&&h.includes(_);if(v&&_&&!y){let e=`transform ${a}ms ease-out, opacity ${a}ms ease-out`,n=t*2;O({transition:e,transform:`translate3d(${n}px, ${c*2}px, 0px) rotate(${s?d(n,i*2):0}deg)`,opacity:0}),k(()=>{switch(_){case`left`:b.current.onSwipeLeft?.();break;case`right`:b.current.onSwipeRight?.();break;case`up`:b.current.onSwipeUp?.();break;case`down`:b.current.onSwipeDown?.();break}b.current.onSwipeEnd?.(),k(()=>{O({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},a)}else O({transition:`transform ${o}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${o}ms ease-out`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1}),b.current.onSwipeEnd?.();D(e=>({...e,isDragging:!1}))},[n,r,a,o,s,i,h,O,k]),P=(0,c.useRef)({handleSwipeStart:A,handleSwipeMove:M,handleSwipeEnd:N});P.current={handleSwipeStart:A,handleSwipeMove:M,handleSwipeEnd:N},(0,c.useEffect)(()=>{let e=_.current;if(!e)return;let t=e=>{P.current.handleSwipeStart(e)};return e.addEventListener(`pointerdown`,t),()=>{e.removeEventListener(`pointerdown`,t)}},[]),(0,c.useEffect)(()=>{if(!E.isDragging)return;let e=e=>{P.current.handleSwipeMove(e)},t=()=>{P.current.handleSwipeEnd()};return document.addEventListener(`pointermove`,e),document.addEventListener(`pointerup`,t),document.addEventListener(`pointercancel`,t),()=>{document.removeEventListener(`pointermove`,e),document.removeEventListener(`pointerup`,t),document.removeEventListener(`pointercancel`,t)}},[E.isDragging]),(0,c.useEffect)(()=>()=>{v.current&&cancelAnimationFrame(v.current),y.current.forEach(e=>clearTimeout(e)),y.current.clear()},[]);let F=(0,c.useCallback)(e=>{if(h.includes(e))return;let t=0,n=0;switch(e){case`left`:t=-500;break;case`right`:t=500;break;case`up`:n=-500;break;case`down`:n=500;break}let r=s?d(t,i*2):0,o=`translate3d(${t}px, ${n}px, 0px) rotate(${r}deg)`;O({transition:`transform ${a}ms ease-out, opacity ${a}ms ease-out`,transform:o,opacity:0}),k(()=>{switch(e){case`left`:b.current.onSwipeLeft?.();break;case`right`:b.current.onSwipeRight?.();break;case`up`:b.current.onSwipeUp?.();break;case`down`:b.current.onSwipeDown?.();break}b.current.onSwipeEnd?.(),k(()=>{O({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},a)},[h,s,i,a,O,k]);return{ref:_,transform:w.transform,opacity:w.opacity,transition:w.transition,isDragging:E.isDragging,deltaX:E.deltaX,deltaY:E.deltaY,swipe:F}},g={fontSize:`2rem`,fontWeight:`bold`,color:`#fff`,letterSpacing:`2px`,textShadow:`0 2px 8px rgba(0, 0, 0, 0.3)`},_={right:{backgroundColor:`transparent`,label:`✓ Accept`,labelStyle:g},left:{backgroundColor:`transparent`,label:`✗ Reject`,labelStyle:g},up:{backgroundColor:`transparent`,label:`⭐ Super`,labelStyle:g},down:{backgroundColor:`transparent`,label:`⏭ Skip`,labelStyle:g}},v=[],y=(0,c.forwardRef)(({children:e,className:t=``,style:n,onSwipeLeft:r,onSwipeRight:i,onSwipeUp:a,onSwipeDown:o,onSwipeStart:s,onSwipeEnd:l,threshold:u=100,velocityThreshold:d=.5,maxRotation:f=15,exitDuration:p=300,returnDuration:m=200,enableRotation:g=!0,preventSwipe:y=v,fadeOnSwipe:b=!0,swipeStyles:x,showOverlay:S=!0},C)=>{let{ref:w,transform:T,opacity:E,transition:D,isDragging:O,deltaX:k,deltaY:A,swipe:j}=h((0,c.useMemo)(()=>({onSwipeLeft:r,onSwipeRight:i,onSwipeUp:a,onSwipeDown:o,onSwipeStart:s,onSwipeEnd:l}),[r,i,a,o,s,l]),(0,c.useMemo)(()=>({threshold:u,velocityThreshold:d,maxRotation:f,exitDuration:p,returnDuration:m,enableRotation:g,preventSwipe:y,fadeOnSwipe:b}),[u,d,f,p,m,g,y,b]));(0,c.useImperativeHandle)(C,()=>({swipe:j}),[j]);let M=(0,c.useMemo)(()=>({right:{..._.right,...x?.right,labelStyle:{..._.right.labelStyle,...x?.right?.labelStyle}},left:{..._.left,...x?.left,labelStyle:{..._.left.labelStyle,...x?.left?.labelStyle}},up:{..._.up,...x?.up,labelStyle:{..._.up.labelStyle,...x?.up?.labelStyle}},down:{..._.down,...x?.down,labelStyle:{..._.down.labelStyle,...x?.down?.labelStyle}}}),[x?.right,x?.left,x?.up,x?.down]),N=Math.abs(k),P=Math.abs(A),F=N>=P,I=F?N:P,L=Math.min(I/u,1),R=F&&k>20,z=F&&k<-20,B=!F&&A<-20,V=!F&&A>20,H=L>=.15,U=S&&O&&I>10,W=(0,c.useMemo)(()=>R?`right`:z?`left`:B?`up`:V?`down`:null,[R,z,B,V]),G=W?M[W]:null,K=G?.backgroundColor??`inherit`,q=G?{label:G.label,labelStyle:G.labelStyle}:null,J=(0,c.useMemo)(()=>({...n,transform:T,opacity:E,transition:D,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:O?`grabbing`:`grab`,willChange:O?`transform, opacity`:`auto`,position:`absolute`}),[n,T,E,D,O]),Y=(0,c.useMemo)(()=>({position:`absolute`,top:0,left:0,right:0,bottom:0,display:`flex`,alignItems:`center`,justifyContent:`center`,borderRadius:`inherit`,pointerEvents:`none`,zIndex:10,opacity:U?L:0,transition:O?`none`:`opacity 200ms ease-out`,backgroundColor:U?K:`inherit`}),[U,L,O,K]),X=(0,c.useMemo)(()=>({...q?.labelStyle,transform:`scale(${.8+L*.4})`,opacity:L}),[q?.labelStyle,L]);return c.default.createElement(`div`,{ref:w,className:`swipeon-card ${t}`,style:J},e,c.default.createElement(`div`,{style:Y},U&&H&&q?.label&&c.default.createElement(`span`,{style:X},q.label)))}),b=(0,c.memo)(y);exports.SwipeCard=b,exports.useSwipe=h;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","DEFAULT_LABEL_STYLE: CSSProperties","EMPTY_PREVENT_SWIPE: readonly string[]","SwipeCardInner: React.FC<SwipeCardProps>","cardStyle: CSSProperties","overlayStyle: CSSProperties","computedLabelStyle: CSSProperties"],"sources":["../src/utils/helpers.ts","../src/hooks/useSwipe.ts","../src/components/SwipeCard.tsx"],"sourcesContent":["import { SwipeDirection } from '../types';\n\n/**\n * Calculate the distance between two points\n */\nexport const getDistance = (x1: number, y1: number, x2: number, y2: number): number => {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n};\n\n/**\n * Calculate velocity based on distance and time\n */\nexport const calculateVelocity = (distance: number, time: number): number => {\n if (time === 0) return 0;\n return Math.abs(distance / time);\n};\n\n/**\n * Determine the swipe direction based on deltas\n */\nexport const getSwipeDirection = (\n deltaX: number,\n deltaY: number,\n threshold: number = 50\n): SwipeDirection | null => {\n const absDeltaX = Math.abs(deltaX);\n const absDeltaY = Math.abs(deltaY);\n\n // Need minimum movement\n if (absDeltaX < threshold && absDeltaY < threshold) {\n return null;\n }\n\n // Determine primary direction\n if (absDeltaX > absDeltaY) {\n return deltaX > 0 ? 'right' : 'left';\n } else {\n return deltaY > 0 ? 'down' : 'up';\n }\n};\n\n/**\n * Calculate rotation angle based on horizontal movement\n */\nexport const calculateRotation = (\n deltaX: number,\n maxRotation: number = 15,\n containerWidth: number = 300\n): number => {\n const rotation = (deltaX / containerWidth) * maxRotation;\n return Math.max(-maxRotation, Math.min(maxRotation, rotation));\n};\n\n/**\n * Calculate opacity based on drag distance\n */\nexport const calculateOpacity = (\n deltaX: number,\n deltaY: number,\n threshold: number = 100\n): number => {\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const opacity = 1 - distance / (threshold * 3);\n return Math.max(0.5, Math.min(1, opacity));\n};\n\n/**\n * Clamp a value between min and max\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value));\n};\n\n/**\n * Get touch/pointer coordinates from event\n */\nexport const getEventCoordinates = (\n event: TouchEvent | PointerEvent | MouseEvent | any\n): { x: number; y: number } => {\n if ('touches' in event && event.touches && event.touches.length > 0) {\n return {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY,\n };\n }\n \n if ('clientX' in event && 'clientY' in event) {\n return {\n x: event.clientX,\n y: event.clientY,\n };\n }\n \n return { x: 0, y: 0 };\n};\n\n","import { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { SwipeConfig, SwipeCallbacks, GestureState, UseSwipeReturn } from '../types';\nimport {\n getSwipeDirection,\n calculateRotation,\n calculateOpacity,\n calculateVelocity,\n getEventCoordinates,\n} from '../utils/helpers';\n\nconst DEFAULT_CONFIG: Required<SwipeConfig> = {\n threshold: 100,\n velocityThreshold: 0.5,\n maxRotation: 15,\n exitDuration: 300,\n returnDuration: 200,\n enableRotation: true,\n preventSwipe: [],\n fadeOnSwipe: true,\n};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n // Memoize config to prevent unnecessary recalculations\n const mergedConfig = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [\n config.threshold,\n config.velocityThreshold,\n config.maxRotation,\n config.exitDuration,\n config.returnDuration,\n config.enableRotation,\n config.fadeOnSwipe,\n // Use JSON for array comparison (preventSwipe is typically small)\n JSON.stringify(config.preventSwipe),\n ]);\n\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const timeoutRefs = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());\n \n // Store callbacks in ref to avoid dependency issues\n const callbacksRef = useRef(callbacks);\n callbacksRef.current = callbacks;\n\n // Use refs for animation values that don't need to trigger re-renders during drag\n const transformRef = useRef('translate3d(0px, 0px, 0px) rotate(0deg)');\n const opacityRef = useRef(1);\n const transitionRef = useRef('none');\n\n // Single state for values that need to trigger re-renders\n const [renderState, setRenderState] = useState({\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n transition: 'none',\n });\n\n const [gestureState, setGestureState] = useState<GestureState>({\n isDragging: false,\n startX: 0,\n startY: 0,\n currentX: 0,\n currentY: 0,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: 0,\n });\n\n // Batch update render state\n const updateRenderState = useCallback((updates: Partial<typeof renderState>) => {\n setRenderState(prev => {\n const newState = { ...prev, ...updates };\n // Only update if values actually changed\n if (\n prev.transform === newState.transform &&\n prev.opacity === newState.opacity &&\n prev.transition === newState.transition\n ) {\n return prev;\n }\n return newState;\n });\n }, []);\n\n // Safe setTimeout that tracks timeouts for cleanup\n const safeTimeout = useCallback((fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n timeoutRefs.current.delete(id);\n fn();\n }, delay);\n timeoutRefs.current.add(id);\n return id;\n }, []);\n\n // Handle swipe start\n const handleSwipeStart = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const coords = getEventCoordinates(event as any);\n const now = Date.now();\n\n setGestureState({\n isDragging: true,\n startX: coords.x,\n startY: coords.y,\n currentX: coords.x,\n currentY: coords.y,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: now,\n });\n\n transitionRef.current = 'none';\n updateRenderState({ transition: 'none' });\n \n callbacksRef.current.onSwipeStart?.();\n },\n [updateRenderState]\n );\n\n // Handle swipe move - use refs to avoid closure issues\n const gestureStateRef = useRef(gestureState);\n gestureStateRef.current = gestureState;\n\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n let deltaX = coords.x - state.startX;\n let deltaY = coords.y - state.startY;\n\n // Constrain movement based on preventSwipe directions\n if (preventSwipe.includes('left') && deltaX < 0) deltaX = 0;\n if (preventSwipe.includes('right') && deltaX > 0) deltaX = 0;\n if (preventSwipe.includes('up') && deltaY < 0) deltaY = 0;\n if (preventSwipe.includes('down') && deltaY > 0) deltaY = 0;\n\n // Update gesture state\n setGestureState(prev => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates - cancel previous frame\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = requestAnimationFrame(() => {\n const rotation = enableRotation ? calculateRotation(deltaX, maxRotation) : 0;\n const newOpacity = fadeOnSwipe ? calculateOpacity(deltaX, deltaY, threshold) : 1;\n const newTransform = `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`;\n\n // Update refs immediately for internal use\n transformRef.current = newTransform;\n opacityRef.current = newOpacity;\n\n // Batch update to React state\n updateRenderState({\n transform: newTransform,\n opacity: newOpacity,\n });\n });\n },\n [enableRotation, maxRotation, threshold, preventSwipe, fadeOnSwipe, updateRenderState]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const { deltaX, deltaY, startTime } = state;\n const timeDelta = Date.now() - startTime;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const velocity = calculateVelocity(distance, timeDelta);\n\n const direction = getSwipeDirection(deltaX, deltaY, threshold / 2);\n const shouldSwipe = distance >= threshold || velocity >= velocityThreshold;\n\n // Check if direction is prevented\n const isDirectionPrevented = direction && preventSwipe.includes(direction);\n\n if (shouldSwipe && direction && !isDirectionPrevented) {\n // Execute swipe\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n \n // Calculate exit position (move far enough off screen)\n const multiplier = 2;\n const exitX = deltaX * multiplier;\n const exitY = deltaY * multiplier;\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n \n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n const returnTransition = `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`;\n \n updateRenderState({\n transition: returnTransition,\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n\n callbacksRef.current.onSwipeEnd?.();\n }\n\n setGestureState(prev => ({ ...prev, isDragging: false }));\n }, [\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n preventSwipe,\n updateRenderState,\n safeTimeout,\n ]);\n\n // Stable event handler refs to avoid re-attaching listeners\n const handlersRef = useRef({\n handleSwipeStart,\n handleSwipeMove,\n handleSwipeEnd,\n });\n handlersRef.current = { handleSwipeStart, handleSwipeMove, handleSwipeEnd };\n\n // Attach event listeners with stable references\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const onPointerDown = (e: PointerEvent) => {\n handlersRef.current.handleSwipeStart(e);\n };\n\n element.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n element.removeEventListener('pointerdown', onPointerDown);\n };\n }, []); // Empty deps - handlers are accessed via ref\n\n // Attach document listeners when dragging\n useEffect(() => {\n if (!gestureState.isDragging) return;\n\n const onPointerMove = (e: PointerEvent) => {\n handlersRef.current.handleSwipeMove(e);\n };\n\n const onPointerUp = () => {\n handlersRef.current.handleSwipeEnd();\n };\n\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', onPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', onPointerMove);\n document.removeEventListener('pointerup', onPointerUp);\n document.removeEventListener('pointercancel', onPointerUp);\n };\n }, [gestureState.isDragging]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Clear all pending timeouts\n timeoutRefs.current.forEach(id => clearTimeout(id));\n timeoutRefs.current.clear();\n };\n }, []);\n\n return {\n ref,\n transform: renderState.transform,\n opacity: renderState.opacity,\n transition: renderState.transition,\n isDragging: gestureState.isDragging,\n deltaX: gestureState.deltaX,\n deltaY: gestureState.deltaY,\n };\n};\n","import React, { CSSProperties, useMemo, memo } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps, SwipeCallbacks, SwipeConfig } from '../types';\n\n/**\n * SwipeCard Component\n * \n * A performant swipeable card component with smooth animations.\n * Supports swipe in all four directions: left, right, up, and down.\n * \n * @example\n * ```tsx\n * <SwipeCard\n * onSwipeLeft={() => console.log('Swiped left')}\n * onSwipeRight={() => console.log('Swiped right')}\n * threshold={100}\n * swipeStyles={{\n * // backgroundColor is optional - transparent by default (card content stays visible)\n * right: { \n * backgroundColor: 'rgba(34, 197, 94, 0.6)', // green overlay\n * label: '👍 Like',\n * labelStyle: { position: 'absolute', top: 20, right: 20 }\n * },\n * left: { \n * backgroundColor: 'rgba(239, 68, 68, 0.6)', // red overlay\n * label: '👎 Nope',\n * labelStyle: { position: 'absolute', top: 20, left: 20 }\n * },\n * }}\n * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\n\n// Default label style - constant, no need to recreate\nconst DEFAULT_LABEL_STYLE: CSSProperties = {\n fontSize: '2rem',\n fontWeight: 'bold',\n color: '#fff',\n letterSpacing: '2px',\n textShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',\n};\n\n// Default styles for each direction - constant\n// backgroundColor defaults to transparent so card content remains visible unless explicitly set\nconst DEFAULT_SWIPE_STYLES = {\n right: { backgroundColor: 'transparent', label: '✓ Accept', labelStyle: DEFAULT_LABEL_STYLE },\n left: { backgroundColor: 'transparent', label: '✗ Reject', labelStyle: DEFAULT_LABEL_STYLE },\n up: { backgroundColor: 'transparent', label: '⭐ Super', labelStyle: DEFAULT_LABEL_STYLE },\n down: { backgroundColor: 'transparent', label: '⏭ Skip', labelStyle: DEFAULT_LABEL_STYLE },\n} as const;\n\n// Empty array constant to prevent recreating on each render\nconst EMPTY_PREVENT_SWIPE: readonly string[] = [];\n\nconst SwipeCardInner: React.FC<SwipeCardProps> = ({\n children,\n className = '',\n style,\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n threshold = 100,\n velocityThreshold = 0.5,\n maxRotation = 15,\n exitDuration = 300,\n returnDuration = 200,\n enableRotation = true,\n preventSwipe = EMPTY_PREVENT_SWIPE as any,\n fadeOnSwipe = true,\n swipeStyles,\n showOverlay: showOverlayProp = true,\n}) => {\n // Memoize callbacks object to prevent unnecessary hook updates\n const callbacks: SwipeCallbacks = useMemo(() => ({\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n }), [onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, onSwipeStart, onSwipeEnd]);\n\n // Memoize config object\n const config: SwipeConfig = useMemo(() => ({\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n }), [threshold, velocityThreshold, maxRotation, exitDuration, returnDuration, enableRotation, preventSwipe, fadeOnSwipe]);\n\n const { ref, transform, opacity, transition, isDragging, deltaX, deltaY } = useSwipe(callbacks, config);\n\n // Memoize merged swipe styles\n const mergedSwipeStyles = useMemo(() => ({\n right: { \n ...DEFAULT_SWIPE_STYLES.right, \n ...swipeStyles?.right,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.right.labelStyle, ...swipeStyles?.right?.labelStyle }\n },\n left: { \n ...DEFAULT_SWIPE_STYLES.left, \n ...swipeStyles?.left,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.left.labelStyle, ...swipeStyles?.left?.labelStyle }\n },\n up: { \n ...DEFAULT_SWIPE_STYLES.up, \n ...swipeStyles?.up,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.up.labelStyle, ...swipeStyles?.up?.labelStyle }\n },\n down: { \n ...DEFAULT_SWIPE_STYLES.down, \n ...swipeStyles?.down,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.down.labelStyle, ...swipeStyles?.down?.labelStyle }\n },\n }), [\n swipeStyles?.right, \n swipeStyles?.left, \n swipeStyles?.up, \n swipeStyles?.down,\n ]);\n\n // Calculate swipe state - these are cheap, no need to memoize\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n const isPrimaryHorizontal = absX >= absY;\n const swipeDistance = isPrimaryHorizontal ? absX : absY;\n const swipeProgress = Math.min(swipeDistance / threshold, 1);\n \n const isSwipingRight = isPrimaryHorizontal && deltaX > 20;\n const isSwipingLeft = isPrimaryHorizontal && deltaX < -20;\n const isSwipingUp = !isPrimaryHorizontal && deltaY < -20;\n const isSwipingDown = !isPrimaryHorizontal && deltaY > 20;\n \n const isActionInProgress = swipeProgress >= 0.15;\n const showOverlay = showOverlayProp && isDragging && swipeDistance > 10;\n\n // Calculate current direction info - single calculation\n const currentDirection = useMemo(() => {\n if (isSwipingRight) return 'right' as const;\n if (isSwipingLeft) return 'left' as const;\n if (isSwipingUp) return 'up' as const;\n if (isSwipingDown) return 'down' as const;\n return null;\n }, [isSwipingRight, isSwipingLeft, isSwipingUp, isSwipingDown]);\n\n const currentStyle = currentDirection ? mergedSwipeStyles[currentDirection] : null;\n const backgroundColor = currentStyle?.backgroundColor ?? 'inherit';\n const labelConfig = currentStyle ? { label: currentStyle.label, labelStyle: currentStyle.labelStyle } : null;\n\n // Memoize card style - only recalculate when values change\n const cardStyle: CSSProperties = useMemo(() => ({\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none',\n userSelect: 'none',\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'absolute',\n }), [style, transform, opacity, transition, isDragging]);\n\n // Memoize overlay style\n const overlayStyle: CSSProperties = useMemo(() => ({\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'inherit',\n pointerEvents: 'none',\n zIndex: 10,\n opacity: showOverlay ? swipeProgress : 0,\n transition: isDragging ? 'none' : 'opacity 200ms ease-out',\n backgroundColor: showOverlay ? backgroundColor : 'inherit',\n }), [showOverlay, swipeProgress, isDragging, backgroundColor]);\n\n // Memoize computed label style\n const computedLabelStyle: CSSProperties = useMemo(() => ({\n ...labelConfig?.labelStyle,\n transform: `scale(${0.8 + swipeProgress * 0.4})`,\n opacity: swipeProgress,\n }), [labelConfig?.labelStyle, swipeProgress]);\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n <div style={overlayStyle}>\n {showOverlay && isActionInProgress && labelConfig?.label && (\n <span style={computedLabelStyle}>\n {labelConfig.label}\n </span>\n )}\n </div>\n </div>\n );\n};\n\n// Memoize the entire component to prevent unnecessary re-renders from parent\nexport const SwipeCard = memo(SwipeCardInner);\n\nexport default SwipeCard;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","DEFAULT_LABEL_STYLE: CSSProperties","EMPTY_PREVENT_SWIPE: readonly string[]","cardStyle: CSSProperties","overlayStyle: CSSProperties","computedLabelStyle: CSSProperties"],"sources":["../src/utils/helpers.ts","../src/hooks/useSwipe.ts","../src/components/SwipeCard.tsx"],"sourcesContent":["import { SwipeDirection } from '../types';\n\n/**\n * Calculate the distance between two points\n */\nexport const getDistance = (x1: number, y1: number, x2: number, y2: number): number => {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n};\n\n/**\n * Calculate velocity based on distance and time\n */\nexport const calculateVelocity = (distance: number, time: number): number => {\n if (time === 0) return 0;\n return Math.abs(distance / time);\n};\n\n/**\n * Determine the swipe direction based on deltas\n */\nexport const getSwipeDirection = (\n deltaX: number,\n deltaY: number,\n threshold: number = 50\n): SwipeDirection | null => {\n const absDeltaX = Math.abs(deltaX);\n const absDeltaY = Math.abs(deltaY);\n\n // Need minimum movement\n if (absDeltaX < threshold && absDeltaY < threshold) {\n return null;\n }\n\n // Determine primary direction\n if (absDeltaX > absDeltaY) {\n return deltaX > 0 ? 'right' : 'left';\n } else {\n return deltaY > 0 ? 'down' : 'up';\n }\n};\n\n/**\n * Calculate rotation angle based on horizontal movement\n */\nexport const calculateRotation = (\n deltaX: number,\n maxRotation: number = 15,\n containerWidth: number = 300\n): number => {\n const rotation = (deltaX / containerWidth) * maxRotation;\n return Math.max(-maxRotation, Math.min(maxRotation, rotation));\n};\n\n/**\n * Calculate opacity based on drag distance\n */\nexport const calculateOpacity = (\n deltaX: number,\n deltaY: number,\n threshold: number = 100\n): number => {\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const opacity = 1 - distance / (threshold * 3);\n return Math.max(0.5, Math.min(1, opacity));\n};\n\n/**\n * Clamp a value between min and max\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value));\n};\n\n/**\n * Get touch/pointer coordinates from event\n */\nexport const getEventCoordinates = (\n event: TouchEvent | PointerEvent | MouseEvent | any\n): { x: number; y: number } => {\n if ('touches' in event && event.touches && event.touches.length > 0) {\n return {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY,\n };\n }\n \n if ('clientX' in event && 'clientY' in event) {\n return {\n x: event.clientX,\n y: event.clientY,\n };\n }\n \n return { x: 0, y: 0 };\n};\n\n","import { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { SwipeConfig, SwipeCallbacks, GestureState, UseSwipeReturn, SwipeDirection } from '../types';\nimport {\n getSwipeDirection,\n calculateRotation,\n calculateOpacity,\n calculateVelocity,\n getEventCoordinates,\n} from '../utils/helpers';\n\nconst DEFAULT_CONFIG: Required<SwipeConfig> = {\n threshold: 100,\n velocityThreshold: 0.5,\n maxRotation: 15,\n exitDuration: 300,\n returnDuration: 200,\n enableRotation: true,\n preventSwipe: [],\n fadeOnSwipe: true,\n};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n // Memoize config to prevent unnecessary recalculations\n const mergedConfig = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [\n config.threshold,\n config.velocityThreshold,\n config.maxRotation,\n config.exitDuration,\n config.returnDuration,\n config.enableRotation,\n config.fadeOnSwipe,\n // Use JSON for array comparison (preventSwipe is typically small)\n JSON.stringify(config.preventSwipe),\n ]);\n\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const timeoutRefs = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());\n \n // Store callbacks in ref to avoid dependency issues\n const callbacksRef = useRef(callbacks);\n callbacksRef.current = callbacks;\n\n // Use refs for animation values that don't need to trigger re-renders during drag\n const transformRef = useRef('translate3d(0px, 0px, 0px) rotate(0deg)');\n const opacityRef = useRef(1);\n const transitionRef = useRef('none');\n\n // Single state for values that need to trigger re-renders\n const [renderState, setRenderState] = useState({\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n transition: 'none',\n });\n\n const [gestureState, setGestureState] = useState<GestureState>({\n isDragging: false,\n startX: 0,\n startY: 0,\n currentX: 0,\n currentY: 0,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: 0,\n });\n\n // Batch update render state\n const updateRenderState = useCallback((updates: Partial<typeof renderState>) => {\n setRenderState(prev => {\n const newState = { ...prev, ...updates };\n // Only update if values actually changed\n if (\n prev.transform === newState.transform &&\n prev.opacity === newState.opacity &&\n prev.transition === newState.transition\n ) {\n return prev;\n }\n return newState;\n });\n }, []);\n\n // Safe setTimeout that tracks timeouts for cleanup\n const safeTimeout = useCallback((fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n timeoutRefs.current.delete(id);\n fn();\n }, delay);\n timeoutRefs.current.add(id);\n return id;\n }, []);\n\n // Handle swipe start\n const handleSwipeStart = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const coords = getEventCoordinates(event as any);\n const now = Date.now();\n\n setGestureState({\n isDragging: true,\n startX: coords.x,\n startY: coords.y,\n currentX: coords.x,\n currentY: coords.y,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: now,\n });\n\n transitionRef.current = 'none';\n updateRenderState({ transition: 'none' });\n \n callbacksRef.current.onSwipeStart?.();\n },\n [updateRenderState]\n );\n\n // Handle swipe move - use refs to avoid closure issues\n const gestureStateRef = useRef(gestureState);\n gestureStateRef.current = gestureState;\n\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n let deltaX = coords.x - state.startX;\n let deltaY = coords.y - state.startY;\n\n // Constrain movement based on preventSwipe directions\n if (preventSwipe.includes('left') && deltaX < 0) deltaX = 0;\n if (preventSwipe.includes('right') && deltaX > 0) deltaX = 0;\n if (preventSwipe.includes('up') && deltaY < 0) deltaY = 0;\n if (preventSwipe.includes('down') && deltaY > 0) deltaY = 0;\n\n // Update gesture state\n setGestureState(prev => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates - cancel previous frame\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = requestAnimationFrame(() => {\n const rotation = enableRotation ? calculateRotation(deltaX, maxRotation) : 0;\n const newOpacity = fadeOnSwipe ? calculateOpacity(deltaX, deltaY, threshold) : 1;\n const newTransform = `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`;\n\n // Update refs immediately for internal use\n transformRef.current = newTransform;\n opacityRef.current = newOpacity;\n\n // Batch update to React state\n updateRenderState({\n transform: newTransform,\n opacity: newOpacity,\n });\n });\n },\n [enableRotation, maxRotation, threshold, preventSwipe, fadeOnSwipe, updateRenderState]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const { deltaX, deltaY, startTime } = state;\n const timeDelta = Date.now() - startTime;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const velocity = calculateVelocity(distance, timeDelta);\n\n const direction = getSwipeDirection(deltaX, deltaY, threshold / 2);\n const shouldSwipe = distance >= threshold || velocity >= velocityThreshold;\n\n // Check if direction is prevented\n const isDirectionPrevented = direction && preventSwipe.includes(direction);\n\n if (shouldSwipe && direction && !isDirectionPrevented) {\n // Execute swipe\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n \n // Calculate exit position (move far enough off screen)\n const multiplier = 2;\n const exitX = deltaX * multiplier;\n const exitY = deltaY * multiplier;\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n \n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n const returnTransition = `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`;\n \n updateRenderState({\n transition: returnTransition,\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n\n callbacksRef.current.onSwipeEnd?.();\n }\n\n setGestureState(prev => ({ ...prev, isDragging: false }));\n }, [\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n preventSwipe,\n updateRenderState,\n safeTimeout,\n ]);\n\n // Stable event handler refs to avoid re-attaching listeners\n const handlersRef = useRef({\n handleSwipeStart,\n handleSwipeMove,\n handleSwipeEnd,\n });\n handlersRef.current = { handleSwipeStart, handleSwipeMove, handleSwipeEnd };\n\n // Attach event listeners with stable references\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const onPointerDown = (e: PointerEvent) => {\n handlersRef.current.handleSwipeStart(e);\n };\n\n element.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n element.removeEventListener('pointerdown', onPointerDown);\n };\n }, []); // Empty deps - handlers are accessed via ref\n\n // Attach document listeners when dragging\n useEffect(() => {\n if (!gestureState.isDragging) return;\n\n const onPointerMove = (e: PointerEvent) => {\n handlersRef.current.handleSwipeMove(e);\n };\n\n const onPointerUp = () => {\n handlersRef.current.handleSwipeEnd();\n };\n\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', onPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', onPointerMove);\n document.removeEventListener('pointerup', onPointerUp);\n document.removeEventListener('pointercancel', onPointerUp);\n };\n }, [gestureState.isDragging]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Clear all pending timeouts\n timeoutRefs.current.forEach(id => clearTimeout(id));\n timeoutRefs.current.clear();\n };\n }, []);\n\n // Programmatic swipe function\n const swipe = useCallback((direction: SwipeDirection) => {\n // Check if direction is prevented\n if (preventSwipe.includes(direction)) {\n return;\n }\n\n // Calculate exit coordinates based on direction\n const exitDistance = 500; // Distance to move off screen\n let exitX = 0;\n let exitY = 0;\n\n switch (direction) {\n case 'left':\n exitX = -exitDistance;\n break;\n case 'right':\n exitX = exitDistance;\n break;\n case 'up':\n exitY = -exitDistance;\n break;\n case 'down':\n exitY = exitDistance;\n break;\n }\n\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n\n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n }, [preventSwipe, enableRotation, maxRotation, exitDuration, updateRenderState, safeTimeout]);\n\n return {\n ref,\n transform: renderState.transform,\n opacity: renderState.opacity,\n transition: renderState.transition,\n isDragging: gestureState.isDragging,\n deltaX: gestureState.deltaX,\n deltaY: gestureState.deltaY,\n swipe,\n };\n};\n","import React, { CSSProperties, useMemo, memo, forwardRef, useImperativeHandle } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps, SwipeCallbacks, SwipeConfig, SwipeCardRef } from '../types';\n\n/**\n * SwipeCard Component\n * \n * A performant swipeable card component with smooth animations.\n * Supports swipe in all four directions: left, right, up, and down.\n * \n * @example\n * ```tsx\n * <SwipeCard\n * onSwipeLeft={() => console.log('Swiped left')}\n * onSwipeRight={() => console.log('Swiped right')}\n * threshold={100}\n * swipeStyles={{\n * // backgroundColor is optional - transparent by default (card content stays visible)\n * right: { \n * backgroundColor: 'rgba(34, 197, 94, 0.6)', // green overlay\n * label: '👍 Like',\n * labelStyle: { position: 'absolute', top: 20, right: 20 }\n * },\n * left: { \n * backgroundColor: 'rgba(239, 68, 68, 0.6)', // red overlay\n * label: '👎 Nope',\n * labelStyle: { position: 'absolute', top: 20, left: 20 }\n * },\n * }}\n * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\n\n// Default label style - constant, no need to recreate\nconst DEFAULT_LABEL_STYLE: CSSProperties = {\n fontSize: '2rem',\n fontWeight: 'bold',\n color: '#fff',\n letterSpacing: '2px',\n textShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',\n};\n\n// Default styles for each direction - constant\n// backgroundColor defaults to transparent so card content remains visible unless explicitly set\nconst DEFAULT_SWIPE_STYLES = {\n right: { backgroundColor: 'transparent', label: '✓ Accept', labelStyle: DEFAULT_LABEL_STYLE },\n left: { backgroundColor: 'transparent', label: '✗ Reject', labelStyle: DEFAULT_LABEL_STYLE },\n up: { backgroundColor: 'transparent', label: '⭐ Super', labelStyle: DEFAULT_LABEL_STYLE },\n down: { backgroundColor: 'transparent', label: '⏭ Skip', labelStyle: DEFAULT_LABEL_STYLE },\n} as const;\n\n// Empty array constant to prevent recreating on each render\nconst EMPTY_PREVENT_SWIPE: readonly string[] = [];\n\nconst SwipeCardInner = forwardRef<SwipeCardRef, SwipeCardProps>(({\n children,\n className = '',\n style,\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n threshold = 100,\n velocityThreshold = 0.5,\n maxRotation = 15,\n exitDuration = 300,\n returnDuration = 200,\n enableRotation = true,\n preventSwipe = EMPTY_PREVENT_SWIPE as any,\n fadeOnSwipe = true,\n swipeStyles,\n showOverlay: showOverlayProp = true,\n}, forwardedRef) => {\n // Memoize callbacks object to prevent unnecessary hook updates\n const callbacks: SwipeCallbacks = useMemo(() => ({\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n }), [onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, onSwipeStart, onSwipeEnd]);\n\n // Memoize config object\n const config: SwipeConfig = useMemo(() => ({\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n }), [threshold, velocityThreshold, maxRotation, exitDuration, returnDuration, enableRotation, preventSwipe, fadeOnSwipe]);\n\n const { ref, transform, opacity, transition, isDragging, deltaX, deltaY, swipe } = useSwipe(callbacks, config);\n\n // Expose swipe method via ref\n useImperativeHandle(forwardedRef, () => ({\n swipe,\n }), [swipe]);\n\n // Memoize merged swipe styles\n const mergedSwipeStyles = useMemo(() => ({\n right: { \n ...DEFAULT_SWIPE_STYLES.right, \n ...swipeStyles?.right,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.right.labelStyle, ...swipeStyles?.right?.labelStyle }\n },\n left: { \n ...DEFAULT_SWIPE_STYLES.left, \n ...swipeStyles?.left,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.left.labelStyle, ...swipeStyles?.left?.labelStyle }\n },\n up: { \n ...DEFAULT_SWIPE_STYLES.up, \n ...swipeStyles?.up,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.up.labelStyle, ...swipeStyles?.up?.labelStyle }\n },\n down: { \n ...DEFAULT_SWIPE_STYLES.down, \n ...swipeStyles?.down,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.down.labelStyle, ...swipeStyles?.down?.labelStyle }\n },\n }), [\n swipeStyles?.right, \n swipeStyles?.left, \n swipeStyles?.up, \n swipeStyles?.down,\n ]);\n\n // Calculate swipe state - these are cheap, no need to memoize\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n const isPrimaryHorizontal = absX >= absY;\n const swipeDistance = isPrimaryHorizontal ? absX : absY;\n const swipeProgress = Math.min(swipeDistance / threshold, 1);\n \n const isSwipingRight = isPrimaryHorizontal && deltaX > 20;\n const isSwipingLeft = isPrimaryHorizontal && deltaX < -20;\n const isSwipingUp = !isPrimaryHorizontal && deltaY < -20;\n const isSwipingDown = !isPrimaryHorizontal && deltaY > 20;\n \n const isActionInProgress = swipeProgress >= 0.15;\n const showOverlay = showOverlayProp && isDragging && swipeDistance > 10;\n\n // Calculate current direction info - single calculation\n const currentDirection = useMemo(() => {\n if (isSwipingRight) return 'right' as const;\n if (isSwipingLeft) return 'left' as const;\n if (isSwipingUp) return 'up' as const;\n if (isSwipingDown) return 'down' as const;\n return null;\n }, [isSwipingRight, isSwipingLeft, isSwipingUp, isSwipingDown]);\n\n const currentStyle = currentDirection ? mergedSwipeStyles[currentDirection] : null;\n const backgroundColor = currentStyle?.backgroundColor ?? 'inherit';\n const labelConfig = currentStyle ? { label: currentStyle.label, labelStyle: currentStyle.labelStyle } : null;\n\n // Memoize card style - only recalculate when values change\n const cardStyle: CSSProperties = useMemo(() => ({\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none',\n userSelect: 'none',\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'absolute',\n }), [style, transform, opacity, transition, isDragging]);\n\n // Memoize overlay style\n const overlayStyle: CSSProperties = useMemo(() => ({\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'inherit',\n pointerEvents: 'none',\n zIndex: 10,\n opacity: showOverlay ? swipeProgress : 0,\n transition: isDragging ? 'none' : 'opacity 200ms ease-out',\n backgroundColor: showOverlay ? backgroundColor : 'inherit',\n }), [showOverlay, swipeProgress, isDragging, backgroundColor]);\n\n // Memoize computed label style\n const computedLabelStyle: CSSProperties = useMemo(() => ({\n ...labelConfig?.labelStyle,\n transform: `scale(${0.8 + swipeProgress * 0.4})`,\n opacity: swipeProgress,\n }), [labelConfig?.labelStyle, swipeProgress]);\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n <div style={overlayStyle}>\n {showOverlay && isActionInProgress && labelConfig?.label && (\n <span style={computedLabelStyle}>\n {labelConfig.label}\n </span>\n )}\n </div>\n </div>\n );\n});\n\n// Memoize the entire component to prevent unnecessary re-renders from parent\nexport const SwipeCard = memo(SwipeCardInner);\n\nexport default SwipeCard;\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -110,13 +110,22 @@ interface UseSwipeReturn {
110
110
  isDragging: boolean;
111
111
  deltaX: number;
112
112
  deltaY: number;
113
+ /** Programmatically trigger a swipe in the specified direction */
114
+ swipe: (direction: SwipeDirection) => void;
115
+ }
116
+ /**
117
+ * Imperative handle exposed by SwipeCard via ref
118
+ */
119
+ interface SwipeCardRef {
120
+ /** Programmatically trigger a swipe in the specified direction */
121
+ swipe: (direction: SwipeDirection) => void;
113
122
  }
114
123
  //#endregion
115
124
  //#region src/components/SwipeCard.d.ts
116
- declare const SwipeCard: React$1.NamedExoticComponent<SwipeCardProps>;
125
+ declare const SwipeCard: React$1.MemoExoticComponent<React$1.ForwardRefExoticComponent<SwipeCardProps & React$1.RefAttributes<SwipeCardRef>>>;
117
126
  //#endregion
118
127
  //#region src/hooks/useSwipe.d.ts
119
128
  declare const useSwipe: (callbacks?: SwipeCallbacks, config?: SwipeConfig) => UseSwipeReturn;
120
129
  //#endregion
121
- export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeConfig, type SwipeDirection, type SwipeDirectionStyle, type SwipeOverlayConfig, type SwipeStyles, type UseSwipeReturn, useSwipe };
130
+ export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeCardRef, type SwipeConfig, type SwipeDirection, type SwipeDirectionStyle, type SwipeOverlayConfig, type SwipeStyles, type UseSwipeReturn, useSwipe };
122
131
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/index.ts","../src/components/SwipeCard.tsx","../src/hooks/useSwipe.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAKiB,KALL,cAAA,GAKmB,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,MAAA;AAY/B;AAsBA;AAYA;AAEU,UAhDO,cAAA,CAgDP;EAED,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEF,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEE,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAmB,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAMX,YAAA,CAAA,EAAA,GAAA,GAAkB,IAAA;EAUlB,UAAA,CAAA,EAAA,GAAA,GAAe,IAAA;;;;;AAAqC,UA1DpD,WAAA,CA0DoD;EAAkB;EActE,SAAA,CAAA,EAAA,MAAY;EAgBZ;;;;EC0GJ;;;;ECnMA;EACA,cAAA,CAAA,EAAA,OAAA;EACH;EACP,YAAA,CAAA,EFYc,cEZd,EAAA;EA8TF;;;;;;UF1SgB,mBAAA;;;;;;eAMF;;;;;UAME,WAAA;;UAEP;;SAED;;OAEF;;SAEE;;;;;UAMQ,kBAAA;;gBAED;;;;;;;UAQC,cAAA,SAAuB,gBAAgB,aAAa;;YAEzD;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;;;cCyGV,WAAS,OAAA,CAAA,qBAAA;;;cCnMT,uBACA,yBACH,gBACP"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types/index.ts","../src/components/SwipeCard.tsx","../src/hooks/useSwipe.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAKiB,KALL,cAAA,GAKmB,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,MAAA;AAY/B;AAsBA;AAYA;AAEU,UAhDO,cAAA,CAgDP;EAED,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEF,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEE,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAmB,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAMX,YAAA,CAAA,EAAA,GAAA,GAAkB,IAAA;EAUlB,UAAA,CAAA,EAAA,GAAA,GAAe,IAAA;;;;;AAAqC,UA1DpD,WAAA,CA0DoD;EAAkB;EActE,SAAA,CAAA,EAAA,MAAY;EAgBZ;EACM,iBAAA,CAAA,EAAA,MAAA;EAAhB;EAQc,WAAA,CAAA,EAAA,MAAA;EAAc;EAMlB,YAAA,CAAA,EAAY,MAAA;;;;ECgGhB,cAAgC,CAAA,EAAA,OAAA;EAAvB;EAAA,YAAA,CAAA,EDzLL,cCyLK,EAAA;EAAA;EAAA,WAAA,CAAA,EAAA,OAAA;;;;;UDjLL,mBAAA;EEvBJ;EACA,eAAA,CAAA,EAAA,MAAA;EACH;EACP,KAAA,CAAA,EAAA,MAAA;EAkYF;eFxWc;;;;;UAME,WAAA;;UAEP;;SAED;;OAEF;;SAEE;;;;;UAMQ,kBAAA;;gBAED;;;;;;;UAQC,cAAA,SAAuB,gBAAgB,aAAa;;YAEzD;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;qBAQF;;;;;UAMJ,YAAA;;qBAEI;;;;cC8FR,WAAS,OAAA,CAAA,oBAAA,OAAA,CAAA,0BAAA,iBAAA,OAAA,CAAA,cAAA;;;cCxMT,uBACA,yBACH,gBACP"}
package/dist/index.d.mts CHANGED
@@ -110,13 +110,22 @@ interface UseSwipeReturn {
110
110
  isDragging: boolean;
111
111
  deltaX: number;
112
112
  deltaY: number;
113
+ /** Programmatically trigger a swipe in the specified direction */
114
+ swipe: (direction: SwipeDirection) => void;
115
+ }
116
+ /**
117
+ * Imperative handle exposed by SwipeCard via ref
118
+ */
119
+ interface SwipeCardRef {
120
+ /** Programmatically trigger a swipe in the specified direction */
121
+ swipe: (direction: SwipeDirection) => void;
113
122
  }
114
123
  //#endregion
115
124
  //#region src/components/SwipeCard.d.ts
116
- declare const SwipeCard: React$1.NamedExoticComponent<SwipeCardProps>;
125
+ declare const SwipeCard: React$1.MemoExoticComponent<React$1.ForwardRefExoticComponent<SwipeCardProps & React$1.RefAttributes<SwipeCardRef>>>;
117
126
  //#endregion
118
127
  //#region src/hooks/useSwipe.d.ts
119
128
  declare const useSwipe: (callbacks?: SwipeCallbacks, config?: SwipeConfig) => UseSwipeReturn;
120
129
  //#endregion
121
- export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeConfig, type SwipeDirection, type SwipeDirectionStyle, type SwipeOverlayConfig, type SwipeStyles, type UseSwipeReturn, useSwipe };
130
+ export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeCardRef, type SwipeConfig, type SwipeDirection, type SwipeDirectionStyle, type SwipeOverlayConfig, type SwipeStyles, type UseSwipeReturn, useSwipe };
122
131
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/components/SwipeCard.tsx","../src/hooks/useSwipe.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAKiB,KALL,cAAA,GAKmB,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,MAAA;AAY/B;AAsBA;AAYA;AAEU,UAhDO,cAAA,CAgDP;EAED,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEF,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEE,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAmB,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAMX,YAAA,CAAA,EAAA,GAAA,GAAkB,IAAA;EAUlB,UAAA,CAAA,EAAA,GAAA,GAAe,IAAA;;;;;AAAqC,UA1DpD,WAAA,CA0DoD;EAAkB;EActE,SAAA,CAAA,EAAA,MAAY;EAgBZ;;;;EC0GJ;;;;ECnMA;EACA,cAAA,CAAA,EAAA,OAAA;EACH;EACP,YAAA,CAAA,EFYc,cEZd,EAAA;EA8TF;;;;;;UF1SgB,mBAAA;;;;;;eAMF;;;;;UAME,WAAA;;UAEP;;SAED;;OAEF;;SAEE;;;;;UAMQ,kBAAA;;gBAED;;;;;;;UAQC,cAAA,SAAuB,gBAAgB,aAAa;;YAEzD;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;;;cCyGV,WAAS,OAAA,CAAA,qBAAA;;;cCnMT,uBACA,yBACH,gBACP"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/index.ts","../src/components/SwipeCard.tsx","../src/hooks/useSwipe.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAKiB,KALL,cAAA,GAKmB,MAAA,GAAA,OAAA,GAAA,IAAA,GAAA,MAAA;AAY/B;AAsBA;AAYA;AAEU,UAhDO,cAAA,CAgDP;EAED,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEF,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAEE,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAmB,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAMX,YAAA,CAAA,EAAA,GAAA,GAAkB,IAAA;EAUlB,UAAA,CAAA,EAAA,GAAA,GAAe,IAAA;;;;;AAAqC,UA1DpD,WAAA,CA0DoD;EAAkB;EActE,SAAA,CAAA,EAAA,MAAY;EAgBZ;EACM,iBAAA,CAAA,EAAA,MAAA;EAAhB;EAQc,WAAA,CAAA,EAAA,MAAA;EAAc;EAMlB,YAAA,CAAA,EAAY,MAAA;;;;ECgGhB,cAAgC,CAAA,EAAA,OAAA;EAAvB;EAAA,YAAA,CAAA,EDzLL,cCyLK,EAAA;EAAA;EAAA,WAAA,CAAA,EAAA,OAAA;;;;;UDjLL,mBAAA;EEvBJ;EACA,eAAA,CAAA,EAAA,MAAA;EACH;EACP,KAAA,CAAA,EAAA,MAAA;EAkYF;eFxWc;;;;;UAME,WAAA;;UAEP;;SAED;;OAEF;;SAEE;;;;;UAMQ,kBAAA;;gBAED;;;;;;;UAQC,cAAA,SAAuB,gBAAgB,aAAa;;YAEzD;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;qBAQF;;;;;UAMJ,YAAA;;qBAEI;;;;cC8FR,WAAS,OAAA,CAAA,oBAAA,OAAA,CAAA,0BAAA,iBAAA,OAAA,CAAA,cAAA;;;cCxMT,uBACA,yBACH,gBACP"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import e,{memo as t,useCallback as n,useEffect as r,useMemo as i,useRef as a,useState as o}from"react";const s=(e,t)=>t===0?0:Math.abs(e/t),c=(e,t,n=50)=>{let r=Math.abs(e),i=Math.abs(t);return r<n&&i<n?null:r>i?e>0?`right`:`left`:t>0?`down`:`up`},l=(e,t=15,n=300)=>{let r=e/n*t;return Math.max(-t,Math.min(t,r))},u=(e,t,n=100)=>{let r=1-Math.sqrt(e*e+t*t)/(n*3);return Math.max(.5,Math.min(1,r))},d=e=>`touches`in e&&e.touches&&e.touches.length>0?{x:e.touches[0].clientX,y:e.touches[0].clientY}:`clientX`in e&&`clientY`in e?{x:e.clientX,y:e.clientY}:{x:0,y:0},f={threshold:100,velocityThreshold:.5,maxRotation:15,exitDuration:300,returnDuration:200,enableRotation:!0,preventSwipe:[],fadeOnSwipe:!0},p=(e={},t={})=>{let{threshold:p,velocityThreshold:m,maxRotation:h,exitDuration:g,returnDuration:_,enableRotation:v,preventSwipe:y,fadeOnSwipe:b}=i(()=>({...f,...t}),[t.threshold,t.velocityThreshold,t.maxRotation,t.exitDuration,t.returnDuration,t.enableRotation,t.fadeOnSwipe,JSON.stringify(t.preventSwipe)]),x=a(null),S=a(null),C=a(new Set),w=a(e);w.current=e;let T=a(`translate3d(0px, 0px, 0px) rotate(0deg)`),E=a(1),D=a(`none`),[O,k]=o({transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1,transition:`none`}),[A,j]=o({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),M=n(e=>{k(t=>{let n={...t,...e};return t.transform===n.transform&&t.opacity===n.opacity&&t.transition===n.transition?t:n})},[]),N=n((e,t)=>{let n=setTimeout(()=>{C.current.delete(n),e()},t);return C.current.add(n),n},[]),P=n(e=>{let t=d(e),n=Date.now();j({isDragging:!0,startX:t.x,startY:t.y,currentX:t.x,currentY:t.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:n}),D.current=`none`,M({transition:`none`}),w.current.onSwipeStart?.()},[M]),F=a(A);F.current=A;let I=n(e=>{let t=F.current;if(!t.isDragging)return;let n=d(e),r=n.x-t.startX,i=n.y-t.startY;y.includes(`left`)&&r<0&&(r=0),y.includes(`right`)&&r>0&&(r=0),y.includes(`up`)&&i<0&&(i=0),y.includes(`down`)&&i>0&&(i=0),j(e=>({...e,currentX:n.x,currentY:n.y,deltaX:r,deltaY:i})),S.current&&cancelAnimationFrame(S.current),S.current=requestAnimationFrame(()=>{let e=v?l(r,h):0,t=b?u(r,i,p):1,n=`translate3d(${r}px, ${i}px, 0px) rotate(${e}deg)`;T.current=n,E.current=t,M({transform:n,opacity:t})})},[v,h,p,y,b,M]),L=n(()=>{let e=F.current;if(!e.isDragging)return;let{deltaX:t,deltaY:n,startTime:r}=e,i=Date.now()-r,a=Math.sqrt(t*t+n*n),o=s(a,i),u=c(t,n,p/2),d=a>=p||o>=m,f=u&&y.includes(u);if(d&&u&&!f){let e=`transform ${g}ms ease-out, opacity ${g}ms ease-out`,r=t*2;M({transition:e,transform:`translate3d(${r}px, ${n*2}px, 0px) rotate(${v?l(r,h*2):0}deg)`,opacity:0}),N(()=>{switch(u){case`left`:w.current.onSwipeLeft?.();break;case`right`:w.current.onSwipeRight?.();break;case`up`:w.current.onSwipeUp?.();break;case`down`:w.current.onSwipeDown?.();break}w.current.onSwipeEnd?.(),N(()=>{M({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},g)}else M({transition:`transform ${_}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${_}ms ease-out`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1}),w.current.onSwipeEnd?.();j(e=>({...e,isDragging:!1}))},[p,m,g,_,v,h,y,M,N]),R=a({handleSwipeStart:P,handleSwipeMove:I,handleSwipeEnd:L});return R.current={handleSwipeStart:P,handleSwipeMove:I,handleSwipeEnd:L},r(()=>{let e=x.current;if(!e)return;let t=e=>{R.current.handleSwipeStart(e)};return e.addEventListener(`pointerdown`,t),()=>{e.removeEventListener(`pointerdown`,t)}},[]),r(()=>{if(!A.isDragging)return;let e=e=>{R.current.handleSwipeMove(e)},t=()=>{R.current.handleSwipeEnd()};return document.addEventListener(`pointermove`,e),document.addEventListener(`pointerup`,t),document.addEventListener(`pointercancel`,t),()=>{document.removeEventListener(`pointermove`,e),document.removeEventListener(`pointerup`,t),document.removeEventListener(`pointercancel`,t)}},[A.isDragging]),r(()=>()=>{S.current&&cancelAnimationFrame(S.current),C.current.forEach(e=>clearTimeout(e)),C.current.clear()},[]),{ref:x,transform:O.transform,opacity:O.opacity,transition:O.transition,isDragging:A.isDragging,deltaX:A.deltaX,deltaY:A.deltaY}},m={fontSize:`2rem`,fontWeight:`bold`,color:`#fff`,letterSpacing:`2px`,textShadow:`0 2px 8px rgba(0, 0, 0, 0.3)`},h={right:{backgroundColor:`transparent`,label:`✓ Accept`,labelStyle:m},left:{backgroundColor:`transparent`,label:`✗ Reject`,labelStyle:m},up:{backgroundColor:`transparent`,label:`⭐ Super`,labelStyle:m},down:{backgroundColor:`transparent`,label:`⏭ Skip`,labelStyle:m}},g=[],_=t(({children:t,className:n=``,style:r,onSwipeLeft:a,onSwipeRight:o,onSwipeUp:s,onSwipeDown:c,onSwipeStart:l,onSwipeEnd:u,threshold:d=100,velocityThreshold:f=.5,maxRotation:m=15,exitDuration:_=300,returnDuration:v=200,enableRotation:y=!0,preventSwipe:b=g,fadeOnSwipe:x=!0,swipeStyles:S,showOverlay:C=!0})=>{let{ref:w,transform:T,opacity:E,transition:D,isDragging:O,deltaX:k,deltaY:A}=p(i(()=>({onSwipeLeft:a,onSwipeRight:o,onSwipeUp:s,onSwipeDown:c,onSwipeStart:l,onSwipeEnd:u}),[a,o,s,c,l,u]),i(()=>({threshold:d,velocityThreshold:f,maxRotation:m,exitDuration:_,returnDuration:v,enableRotation:y,preventSwipe:b,fadeOnSwipe:x}),[d,f,m,_,v,y,b,x])),j=i(()=>({right:{...h.right,...S?.right,labelStyle:{...h.right.labelStyle,...S?.right?.labelStyle}},left:{...h.left,...S?.left,labelStyle:{...h.left.labelStyle,...S?.left?.labelStyle}},up:{...h.up,...S?.up,labelStyle:{...h.up.labelStyle,...S?.up?.labelStyle}},down:{...h.down,...S?.down,labelStyle:{...h.down.labelStyle,...S?.down?.labelStyle}}}),[S?.right,S?.left,S?.up,S?.down]),M=Math.abs(k),N=Math.abs(A),P=M>=N,F=P?M:N,I=Math.min(F/d,1),L=P&&k>20,R=P&&k<-20,z=!P&&A<-20,B=!P&&A>20,V=I>=.15,H=C&&O&&F>10,U=i(()=>L?`right`:R?`left`:z?`up`:B?`down`:null,[L,R,z,B]),W=U?j[U]:null,G=W?.backgroundColor??`inherit`,K=W?{label:W.label,labelStyle:W.labelStyle}:null,q=i(()=>({...r,transform:T,opacity:E,transition:D,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:O?`grabbing`:`grab`,willChange:O?`transform, opacity`:`auto`,position:`absolute`}),[r,T,E,D,O]),J=i(()=>({position:`absolute`,top:0,left:0,right:0,bottom:0,display:`flex`,alignItems:`center`,justifyContent:`center`,borderRadius:`inherit`,pointerEvents:`none`,zIndex:10,opacity:H?I:0,transition:O?`none`:`opacity 200ms ease-out`,backgroundColor:H?G:`inherit`}),[H,I,O,G]),Y=i(()=>({...K?.labelStyle,transform:`scale(${.8+I*.4})`,opacity:I}),[K?.labelStyle,I]);return e.createElement(`div`,{ref:w,className:`swipeon-card ${n}`,style:q},t,e.createElement(`div`,{style:J},H&&V&&K?.label&&e.createElement(`span`,{style:Y},K.label)))});export{_ as SwipeCard,p as useSwipe};
1
+ import e,{forwardRef as t,memo as n,useCallback as r,useEffect as i,useImperativeHandle as a,useMemo as o,useRef as s,useState as c}from"react";const l=(e,t)=>t===0?0:Math.abs(e/t),u=(e,t,n=50)=>{let r=Math.abs(e),i=Math.abs(t);return r<n&&i<n?null:r>i?e>0?`right`:`left`:t>0?`down`:`up`},d=(e,t=15,n=300)=>{let r=e/n*t;return Math.max(-t,Math.min(t,r))},f=(e,t,n=100)=>{let r=1-Math.sqrt(e*e+t*t)/(n*3);return Math.max(.5,Math.min(1,r))},p=e=>`touches`in e&&e.touches&&e.touches.length>0?{x:e.touches[0].clientX,y:e.touches[0].clientY}:`clientX`in e&&`clientY`in e?{x:e.clientX,y:e.clientY}:{x:0,y:0},m={threshold:100,velocityThreshold:.5,maxRotation:15,exitDuration:300,returnDuration:200,enableRotation:!0,preventSwipe:[],fadeOnSwipe:!0},h=(e={},t={})=>{let{threshold:n,velocityThreshold:a,maxRotation:h,exitDuration:g,returnDuration:_,enableRotation:v,preventSwipe:y,fadeOnSwipe:b}=o(()=>({...m,...t}),[t.threshold,t.velocityThreshold,t.maxRotation,t.exitDuration,t.returnDuration,t.enableRotation,t.fadeOnSwipe,JSON.stringify(t.preventSwipe)]),x=s(null),S=s(null),C=s(new Set),w=s(e);w.current=e;let T=s(`translate3d(0px, 0px, 0px) rotate(0deg)`),E=s(1),D=s(`none`),[O,k]=c({transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1,transition:`none`}),[A,j]=c({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),M=r(e=>{k(t=>{let n={...t,...e};return t.transform===n.transform&&t.opacity===n.opacity&&t.transition===n.transition?t:n})},[]),N=r((e,t)=>{let n=setTimeout(()=>{C.current.delete(n),e()},t);return C.current.add(n),n},[]),P=r(e=>{let t=p(e),n=Date.now();j({isDragging:!0,startX:t.x,startY:t.y,currentX:t.x,currentY:t.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:n}),D.current=`none`,M({transition:`none`}),w.current.onSwipeStart?.()},[M]),F=s(A);F.current=A;let I=r(e=>{let t=F.current;if(!t.isDragging)return;let r=p(e),i=r.x-t.startX,a=r.y-t.startY;y.includes(`left`)&&i<0&&(i=0),y.includes(`right`)&&i>0&&(i=0),y.includes(`up`)&&a<0&&(a=0),y.includes(`down`)&&a>0&&(a=0),j(e=>({...e,currentX:r.x,currentY:r.y,deltaX:i,deltaY:a})),S.current&&cancelAnimationFrame(S.current),S.current=requestAnimationFrame(()=>{let e=v?d(i,h):0,t=b?f(i,a,n):1,r=`translate3d(${i}px, ${a}px, 0px) rotate(${e}deg)`;T.current=r,E.current=t,M({transform:r,opacity:t})})},[v,h,n,y,b,M]),L=r(()=>{let e=F.current;if(!e.isDragging)return;let{deltaX:t,deltaY:r,startTime:i}=e,o=Date.now()-i,s=Math.sqrt(t*t+r*r),c=l(s,o),f=u(t,r,n/2),p=s>=n||c>=a,m=f&&y.includes(f);if(p&&f&&!m){let e=`transform ${g}ms ease-out, opacity ${g}ms ease-out`,n=t*2;M({transition:e,transform:`translate3d(${n}px, ${r*2}px, 0px) rotate(${v?d(n,h*2):0}deg)`,opacity:0}),N(()=>{switch(f){case`left`:w.current.onSwipeLeft?.();break;case`right`:w.current.onSwipeRight?.();break;case`up`:w.current.onSwipeUp?.();break;case`down`:w.current.onSwipeDown?.();break}w.current.onSwipeEnd?.(),N(()=>{M({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},g)}else M({transition:`transform ${_}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${_}ms ease-out`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1}),w.current.onSwipeEnd?.();j(e=>({...e,isDragging:!1}))},[n,a,g,_,v,h,y,M,N]),R=s({handleSwipeStart:P,handleSwipeMove:I,handleSwipeEnd:L});R.current={handleSwipeStart:P,handleSwipeMove:I,handleSwipeEnd:L},i(()=>{let e=x.current;if(!e)return;let t=e=>{R.current.handleSwipeStart(e)};return e.addEventListener(`pointerdown`,t),()=>{e.removeEventListener(`pointerdown`,t)}},[]),i(()=>{if(!A.isDragging)return;let e=e=>{R.current.handleSwipeMove(e)},t=()=>{R.current.handleSwipeEnd()};return document.addEventListener(`pointermove`,e),document.addEventListener(`pointerup`,t),document.addEventListener(`pointercancel`,t),()=>{document.removeEventListener(`pointermove`,e),document.removeEventListener(`pointerup`,t),document.removeEventListener(`pointercancel`,t)}},[A.isDragging]),i(()=>()=>{S.current&&cancelAnimationFrame(S.current),C.current.forEach(e=>clearTimeout(e)),C.current.clear()},[]);let z=r(e=>{if(y.includes(e))return;let t=0,n=0;switch(e){case`left`:t=-500;break;case`right`:t=500;break;case`up`:n=-500;break;case`down`:n=500;break}let r=v?d(t,h*2):0,i=`translate3d(${t}px, ${n}px, 0px) rotate(${r}deg)`;M({transition:`transform ${g}ms ease-out, opacity ${g}ms ease-out`,transform:i,opacity:0}),N(()=>{switch(e){case`left`:w.current.onSwipeLeft?.();break;case`right`:w.current.onSwipeRight?.();break;case`up`:w.current.onSwipeUp?.();break;case`down`:w.current.onSwipeDown?.();break}w.current.onSwipeEnd?.(),N(()=>{M({transition:`none`,transform:`translate3d(0px, 0px, 0px) rotate(0deg)`,opacity:1})},50)},g)},[y,v,h,g,M,N]);return{ref:x,transform:O.transform,opacity:O.opacity,transition:O.transition,isDragging:A.isDragging,deltaX:A.deltaX,deltaY:A.deltaY,swipe:z}},g={fontSize:`2rem`,fontWeight:`bold`,color:`#fff`,letterSpacing:`2px`,textShadow:`0 2px 8px rgba(0, 0, 0, 0.3)`},_={right:{backgroundColor:`transparent`,label:`✓ Accept`,labelStyle:g},left:{backgroundColor:`transparent`,label:`✗ Reject`,labelStyle:g},up:{backgroundColor:`transparent`,label:`⭐ Super`,labelStyle:g},down:{backgroundColor:`transparent`,label:`⏭ Skip`,labelStyle:g}},v=[],y=n(t(({children:t,className:n=``,style:r,onSwipeLeft:i,onSwipeRight:s,onSwipeUp:c,onSwipeDown:l,onSwipeStart:u,onSwipeEnd:d,threshold:f=100,velocityThreshold:p=.5,maxRotation:m=15,exitDuration:g=300,returnDuration:y=200,enableRotation:b=!0,preventSwipe:x=v,fadeOnSwipe:S=!0,swipeStyles:C,showOverlay:w=!0},T)=>{let{ref:E,transform:D,opacity:O,transition:k,isDragging:A,deltaX:j,deltaY:M,swipe:N}=h(o(()=>({onSwipeLeft:i,onSwipeRight:s,onSwipeUp:c,onSwipeDown:l,onSwipeStart:u,onSwipeEnd:d}),[i,s,c,l,u,d]),o(()=>({threshold:f,velocityThreshold:p,maxRotation:m,exitDuration:g,returnDuration:y,enableRotation:b,preventSwipe:x,fadeOnSwipe:S}),[f,p,m,g,y,b,x,S]));a(T,()=>({swipe:N}),[N]);let P=o(()=>({right:{..._.right,...C?.right,labelStyle:{..._.right.labelStyle,...C?.right?.labelStyle}},left:{..._.left,...C?.left,labelStyle:{..._.left.labelStyle,...C?.left?.labelStyle}},up:{..._.up,...C?.up,labelStyle:{..._.up.labelStyle,...C?.up?.labelStyle}},down:{..._.down,...C?.down,labelStyle:{..._.down.labelStyle,...C?.down?.labelStyle}}}),[C?.right,C?.left,C?.up,C?.down]),F=Math.abs(j),I=Math.abs(M),L=F>=I,R=L?F:I,z=Math.min(R/f,1),B=L&&j>20,V=L&&j<-20,H=!L&&M<-20,U=!L&&M>20,W=z>=.15,G=w&&A&&R>10,K=o(()=>B?`right`:V?`left`:H?`up`:U?`down`:null,[B,V,H,U]),q=K?P[K]:null,J=q?.backgroundColor??`inherit`,Y=q?{label:q.label,labelStyle:q.labelStyle}:null,X=o(()=>({...r,transform:D,opacity:O,transition:k,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:A?`grabbing`:`grab`,willChange:A?`transform, opacity`:`auto`,position:`absolute`}),[r,D,O,k,A]),Z=o(()=>({position:`absolute`,top:0,left:0,right:0,bottom:0,display:`flex`,alignItems:`center`,justifyContent:`center`,borderRadius:`inherit`,pointerEvents:`none`,zIndex:10,opacity:G?z:0,transition:A?`none`:`opacity 200ms ease-out`,backgroundColor:G?J:`inherit`}),[G,z,A,J]),Q=o(()=>({...Y?.labelStyle,transform:`scale(${.8+z*.4})`,opacity:z}),[Y?.labelStyle,z]);return e.createElement(`div`,{ref:E,className:`swipeon-card ${n}`,style:X},t,e.createElement(`div`,{style:Z},G&&W&&Y?.label&&e.createElement(`span`,{style:Q},Y.label)))}));export{y as SwipeCard,h as useSwipe};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","DEFAULT_LABEL_STYLE: CSSProperties","EMPTY_PREVENT_SWIPE: readonly string[]","SwipeCardInner: React.FC<SwipeCardProps>","cardStyle: CSSProperties","overlayStyle: CSSProperties","computedLabelStyle: CSSProperties"],"sources":["../src/utils/helpers.ts","../src/hooks/useSwipe.ts","../src/components/SwipeCard.tsx"],"sourcesContent":["import { SwipeDirection } from '../types';\n\n/**\n * Calculate the distance between two points\n */\nexport const getDistance = (x1: number, y1: number, x2: number, y2: number): number => {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n};\n\n/**\n * Calculate velocity based on distance and time\n */\nexport const calculateVelocity = (distance: number, time: number): number => {\n if (time === 0) return 0;\n return Math.abs(distance / time);\n};\n\n/**\n * Determine the swipe direction based on deltas\n */\nexport const getSwipeDirection = (\n deltaX: number,\n deltaY: number,\n threshold: number = 50\n): SwipeDirection | null => {\n const absDeltaX = Math.abs(deltaX);\n const absDeltaY = Math.abs(deltaY);\n\n // Need minimum movement\n if (absDeltaX < threshold && absDeltaY < threshold) {\n return null;\n }\n\n // Determine primary direction\n if (absDeltaX > absDeltaY) {\n return deltaX > 0 ? 'right' : 'left';\n } else {\n return deltaY > 0 ? 'down' : 'up';\n }\n};\n\n/**\n * Calculate rotation angle based on horizontal movement\n */\nexport const calculateRotation = (\n deltaX: number,\n maxRotation: number = 15,\n containerWidth: number = 300\n): number => {\n const rotation = (deltaX / containerWidth) * maxRotation;\n return Math.max(-maxRotation, Math.min(maxRotation, rotation));\n};\n\n/**\n * Calculate opacity based on drag distance\n */\nexport const calculateOpacity = (\n deltaX: number,\n deltaY: number,\n threshold: number = 100\n): number => {\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const opacity = 1 - distance / (threshold * 3);\n return Math.max(0.5, Math.min(1, opacity));\n};\n\n/**\n * Clamp a value between min and max\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value));\n};\n\n/**\n * Get touch/pointer coordinates from event\n */\nexport const getEventCoordinates = (\n event: TouchEvent | PointerEvent | MouseEvent | any\n): { x: number; y: number } => {\n if ('touches' in event && event.touches && event.touches.length > 0) {\n return {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY,\n };\n }\n \n if ('clientX' in event && 'clientY' in event) {\n return {\n x: event.clientX,\n y: event.clientY,\n };\n }\n \n return { x: 0, y: 0 };\n};\n\n","import { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { SwipeConfig, SwipeCallbacks, GestureState, UseSwipeReturn } from '../types';\nimport {\n getSwipeDirection,\n calculateRotation,\n calculateOpacity,\n calculateVelocity,\n getEventCoordinates,\n} from '../utils/helpers';\n\nconst DEFAULT_CONFIG: Required<SwipeConfig> = {\n threshold: 100,\n velocityThreshold: 0.5,\n maxRotation: 15,\n exitDuration: 300,\n returnDuration: 200,\n enableRotation: true,\n preventSwipe: [],\n fadeOnSwipe: true,\n};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n // Memoize config to prevent unnecessary recalculations\n const mergedConfig = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [\n config.threshold,\n config.velocityThreshold,\n config.maxRotation,\n config.exitDuration,\n config.returnDuration,\n config.enableRotation,\n config.fadeOnSwipe,\n // Use JSON for array comparison (preventSwipe is typically small)\n JSON.stringify(config.preventSwipe),\n ]);\n\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const timeoutRefs = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());\n \n // Store callbacks in ref to avoid dependency issues\n const callbacksRef = useRef(callbacks);\n callbacksRef.current = callbacks;\n\n // Use refs for animation values that don't need to trigger re-renders during drag\n const transformRef = useRef('translate3d(0px, 0px, 0px) rotate(0deg)');\n const opacityRef = useRef(1);\n const transitionRef = useRef('none');\n\n // Single state for values that need to trigger re-renders\n const [renderState, setRenderState] = useState({\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n transition: 'none',\n });\n\n const [gestureState, setGestureState] = useState<GestureState>({\n isDragging: false,\n startX: 0,\n startY: 0,\n currentX: 0,\n currentY: 0,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: 0,\n });\n\n // Batch update render state\n const updateRenderState = useCallback((updates: Partial<typeof renderState>) => {\n setRenderState(prev => {\n const newState = { ...prev, ...updates };\n // Only update if values actually changed\n if (\n prev.transform === newState.transform &&\n prev.opacity === newState.opacity &&\n prev.transition === newState.transition\n ) {\n return prev;\n }\n return newState;\n });\n }, []);\n\n // Safe setTimeout that tracks timeouts for cleanup\n const safeTimeout = useCallback((fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n timeoutRefs.current.delete(id);\n fn();\n }, delay);\n timeoutRefs.current.add(id);\n return id;\n }, []);\n\n // Handle swipe start\n const handleSwipeStart = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const coords = getEventCoordinates(event as any);\n const now = Date.now();\n\n setGestureState({\n isDragging: true,\n startX: coords.x,\n startY: coords.y,\n currentX: coords.x,\n currentY: coords.y,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: now,\n });\n\n transitionRef.current = 'none';\n updateRenderState({ transition: 'none' });\n \n callbacksRef.current.onSwipeStart?.();\n },\n [updateRenderState]\n );\n\n // Handle swipe move - use refs to avoid closure issues\n const gestureStateRef = useRef(gestureState);\n gestureStateRef.current = gestureState;\n\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n let deltaX = coords.x - state.startX;\n let deltaY = coords.y - state.startY;\n\n // Constrain movement based on preventSwipe directions\n if (preventSwipe.includes('left') && deltaX < 0) deltaX = 0;\n if (preventSwipe.includes('right') && deltaX > 0) deltaX = 0;\n if (preventSwipe.includes('up') && deltaY < 0) deltaY = 0;\n if (preventSwipe.includes('down') && deltaY > 0) deltaY = 0;\n\n // Update gesture state\n setGestureState(prev => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates - cancel previous frame\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = requestAnimationFrame(() => {\n const rotation = enableRotation ? calculateRotation(deltaX, maxRotation) : 0;\n const newOpacity = fadeOnSwipe ? calculateOpacity(deltaX, deltaY, threshold) : 1;\n const newTransform = `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`;\n\n // Update refs immediately for internal use\n transformRef.current = newTransform;\n opacityRef.current = newOpacity;\n\n // Batch update to React state\n updateRenderState({\n transform: newTransform,\n opacity: newOpacity,\n });\n });\n },\n [enableRotation, maxRotation, threshold, preventSwipe, fadeOnSwipe, updateRenderState]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const { deltaX, deltaY, startTime } = state;\n const timeDelta = Date.now() - startTime;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const velocity = calculateVelocity(distance, timeDelta);\n\n const direction = getSwipeDirection(deltaX, deltaY, threshold / 2);\n const shouldSwipe = distance >= threshold || velocity >= velocityThreshold;\n\n // Check if direction is prevented\n const isDirectionPrevented = direction && preventSwipe.includes(direction);\n\n if (shouldSwipe && direction && !isDirectionPrevented) {\n // Execute swipe\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n \n // Calculate exit position (move far enough off screen)\n const multiplier = 2;\n const exitX = deltaX * multiplier;\n const exitY = deltaY * multiplier;\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n \n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n const returnTransition = `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`;\n \n updateRenderState({\n transition: returnTransition,\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n\n callbacksRef.current.onSwipeEnd?.();\n }\n\n setGestureState(prev => ({ ...prev, isDragging: false }));\n }, [\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n preventSwipe,\n updateRenderState,\n safeTimeout,\n ]);\n\n // Stable event handler refs to avoid re-attaching listeners\n const handlersRef = useRef({\n handleSwipeStart,\n handleSwipeMove,\n handleSwipeEnd,\n });\n handlersRef.current = { handleSwipeStart, handleSwipeMove, handleSwipeEnd };\n\n // Attach event listeners with stable references\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const onPointerDown = (e: PointerEvent) => {\n handlersRef.current.handleSwipeStart(e);\n };\n\n element.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n element.removeEventListener('pointerdown', onPointerDown);\n };\n }, []); // Empty deps - handlers are accessed via ref\n\n // Attach document listeners when dragging\n useEffect(() => {\n if (!gestureState.isDragging) return;\n\n const onPointerMove = (e: PointerEvent) => {\n handlersRef.current.handleSwipeMove(e);\n };\n\n const onPointerUp = () => {\n handlersRef.current.handleSwipeEnd();\n };\n\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', onPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', onPointerMove);\n document.removeEventListener('pointerup', onPointerUp);\n document.removeEventListener('pointercancel', onPointerUp);\n };\n }, [gestureState.isDragging]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Clear all pending timeouts\n timeoutRefs.current.forEach(id => clearTimeout(id));\n timeoutRefs.current.clear();\n };\n }, []);\n\n return {\n ref,\n transform: renderState.transform,\n opacity: renderState.opacity,\n transition: renderState.transition,\n isDragging: gestureState.isDragging,\n deltaX: gestureState.deltaX,\n deltaY: gestureState.deltaY,\n };\n};\n","import React, { CSSProperties, useMemo, memo } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps, SwipeCallbacks, SwipeConfig } from '../types';\n\n/**\n * SwipeCard Component\n * \n * A performant swipeable card component with smooth animations.\n * Supports swipe in all four directions: left, right, up, and down.\n * \n * @example\n * ```tsx\n * <SwipeCard\n * onSwipeLeft={() => console.log('Swiped left')}\n * onSwipeRight={() => console.log('Swiped right')}\n * threshold={100}\n * swipeStyles={{\n * // backgroundColor is optional - transparent by default (card content stays visible)\n * right: { \n * backgroundColor: 'rgba(34, 197, 94, 0.6)', // green overlay\n * label: '👍 Like',\n * labelStyle: { position: 'absolute', top: 20, right: 20 }\n * },\n * left: { \n * backgroundColor: 'rgba(239, 68, 68, 0.6)', // red overlay\n * label: '👎 Nope',\n * labelStyle: { position: 'absolute', top: 20, left: 20 }\n * },\n * }}\n * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\n\n// Default label style - constant, no need to recreate\nconst DEFAULT_LABEL_STYLE: CSSProperties = {\n fontSize: '2rem',\n fontWeight: 'bold',\n color: '#fff',\n letterSpacing: '2px',\n textShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',\n};\n\n// Default styles for each direction - constant\n// backgroundColor defaults to transparent so card content remains visible unless explicitly set\nconst DEFAULT_SWIPE_STYLES = {\n right: { backgroundColor: 'transparent', label: '✓ Accept', labelStyle: DEFAULT_LABEL_STYLE },\n left: { backgroundColor: 'transparent', label: '✗ Reject', labelStyle: DEFAULT_LABEL_STYLE },\n up: { backgroundColor: 'transparent', label: '⭐ Super', labelStyle: DEFAULT_LABEL_STYLE },\n down: { backgroundColor: 'transparent', label: '⏭ Skip', labelStyle: DEFAULT_LABEL_STYLE },\n} as const;\n\n// Empty array constant to prevent recreating on each render\nconst EMPTY_PREVENT_SWIPE: readonly string[] = [];\n\nconst SwipeCardInner: React.FC<SwipeCardProps> = ({\n children,\n className = '',\n style,\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n threshold = 100,\n velocityThreshold = 0.5,\n maxRotation = 15,\n exitDuration = 300,\n returnDuration = 200,\n enableRotation = true,\n preventSwipe = EMPTY_PREVENT_SWIPE as any,\n fadeOnSwipe = true,\n swipeStyles,\n showOverlay: showOverlayProp = true,\n}) => {\n // Memoize callbacks object to prevent unnecessary hook updates\n const callbacks: SwipeCallbacks = useMemo(() => ({\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n }), [onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, onSwipeStart, onSwipeEnd]);\n\n // Memoize config object\n const config: SwipeConfig = useMemo(() => ({\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n }), [threshold, velocityThreshold, maxRotation, exitDuration, returnDuration, enableRotation, preventSwipe, fadeOnSwipe]);\n\n const { ref, transform, opacity, transition, isDragging, deltaX, deltaY } = useSwipe(callbacks, config);\n\n // Memoize merged swipe styles\n const mergedSwipeStyles = useMemo(() => ({\n right: { \n ...DEFAULT_SWIPE_STYLES.right, \n ...swipeStyles?.right,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.right.labelStyle, ...swipeStyles?.right?.labelStyle }\n },\n left: { \n ...DEFAULT_SWIPE_STYLES.left, \n ...swipeStyles?.left,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.left.labelStyle, ...swipeStyles?.left?.labelStyle }\n },\n up: { \n ...DEFAULT_SWIPE_STYLES.up, \n ...swipeStyles?.up,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.up.labelStyle, ...swipeStyles?.up?.labelStyle }\n },\n down: { \n ...DEFAULT_SWIPE_STYLES.down, \n ...swipeStyles?.down,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.down.labelStyle, ...swipeStyles?.down?.labelStyle }\n },\n }), [\n swipeStyles?.right, \n swipeStyles?.left, \n swipeStyles?.up, \n swipeStyles?.down,\n ]);\n\n // Calculate swipe state - these are cheap, no need to memoize\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n const isPrimaryHorizontal = absX >= absY;\n const swipeDistance = isPrimaryHorizontal ? absX : absY;\n const swipeProgress = Math.min(swipeDistance / threshold, 1);\n \n const isSwipingRight = isPrimaryHorizontal && deltaX > 20;\n const isSwipingLeft = isPrimaryHorizontal && deltaX < -20;\n const isSwipingUp = !isPrimaryHorizontal && deltaY < -20;\n const isSwipingDown = !isPrimaryHorizontal && deltaY > 20;\n \n const isActionInProgress = swipeProgress >= 0.15;\n const showOverlay = showOverlayProp && isDragging && swipeDistance > 10;\n\n // Calculate current direction info - single calculation\n const currentDirection = useMemo(() => {\n if (isSwipingRight) return 'right' as const;\n if (isSwipingLeft) return 'left' as const;\n if (isSwipingUp) return 'up' as const;\n if (isSwipingDown) return 'down' as const;\n return null;\n }, [isSwipingRight, isSwipingLeft, isSwipingUp, isSwipingDown]);\n\n const currentStyle = currentDirection ? mergedSwipeStyles[currentDirection] : null;\n const backgroundColor = currentStyle?.backgroundColor ?? 'inherit';\n const labelConfig = currentStyle ? { label: currentStyle.label, labelStyle: currentStyle.labelStyle } : null;\n\n // Memoize card style - only recalculate when values change\n const cardStyle: CSSProperties = useMemo(() => ({\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none',\n userSelect: 'none',\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'absolute',\n }), [style, transform, opacity, transition, isDragging]);\n\n // Memoize overlay style\n const overlayStyle: CSSProperties = useMemo(() => ({\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'inherit',\n pointerEvents: 'none',\n zIndex: 10,\n opacity: showOverlay ? swipeProgress : 0,\n transition: isDragging ? 'none' : 'opacity 200ms ease-out',\n backgroundColor: showOverlay ? backgroundColor : 'inherit',\n }), [showOverlay, swipeProgress, isDragging, backgroundColor]);\n\n // Memoize computed label style\n const computedLabelStyle: CSSProperties = useMemo(() => ({\n ...labelConfig?.labelStyle,\n transform: `scale(${0.8 + swipeProgress * 0.4})`,\n opacity: swipeProgress,\n }), [labelConfig?.labelStyle, swipeProgress]);\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n <div style={overlayStyle}>\n {showOverlay && isActionInProgress && labelConfig?.label && (\n <span style={computedLabelStyle}>\n {labelConfig.label}\n </span>\n )}\n </div>\n </div>\n );\n};\n\n// Memoize the entire component to prevent unnecessary re-renders from parent\nexport const SwipeCard = memo(SwipeCardInner);\n\nexport default SwipeCard;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","DEFAULT_LABEL_STYLE: CSSProperties","EMPTY_PREVENT_SWIPE: readonly string[]","cardStyle: CSSProperties","overlayStyle: CSSProperties","computedLabelStyle: CSSProperties"],"sources":["../src/utils/helpers.ts","../src/hooks/useSwipe.ts","../src/components/SwipeCard.tsx"],"sourcesContent":["import { SwipeDirection } from '../types';\n\n/**\n * Calculate the distance between two points\n */\nexport const getDistance = (x1: number, y1: number, x2: number, y2: number): number => {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n};\n\n/**\n * Calculate velocity based on distance and time\n */\nexport const calculateVelocity = (distance: number, time: number): number => {\n if (time === 0) return 0;\n return Math.abs(distance / time);\n};\n\n/**\n * Determine the swipe direction based on deltas\n */\nexport const getSwipeDirection = (\n deltaX: number,\n deltaY: number,\n threshold: number = 50\n): SwipeDirection | null => {\n const absDeltaX = Math.abs(deltaX);\n const absDeltaY = Math.abs(deltaY);\n\n // Need minimum movement\n if (absDeltaX < threshold && absDeltaY < threshold) {\n return null;\n }\n\n // Determine primary direction\n if (absDeltaX > absDeltaY) {\n return deltaX > 0 ? 'right' : 'left';\n } else {\n return deltaY > 0 ? 'down' : 'up';\n }\n};\n\n/**\n * Calculate rotation angle based on horizontal movement\n */\nexport const calculateRotation = (\n deltaX: number,\n maxRotation: number = 15,\n containerWidth: number = 300\n): number => {\n const rotation = (deltaX / containerWidth) * maxRotation;\n return Math.max(-maxRotation, Math.min(maxRotation, rotation));\n};\n\n/**\n * Calculate opacity based on drag distance\n */\nexport const calculateOpacity = (\n deltaX: number,\n deltaY: number,\n threshold: number = 100\n): number => {\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const opacity = 1 - distance / (threshold * 3);\n return Math.max(0.5, Math.min(1, opacity));\n};\n\n/**\n * Clamp a value between min and max\n */\nexport const clamp = (value: number, min: number, max: number): number => {\n return Math.max(min, Math.min(max, value));\n};\n\n/**\n * Get touch/pointer coordinates from event\n */\nexport const getEventCoordinates = (\n event: TouchEvent | PointerEvent | MouseEvent | any\n): { x: number; y: number } => {\n if ('touches' in event && event.touches && event.touches.length > 0) {\n return {\n x: event.touches[0].clientX,\n y: event.touches[0].clientY,\n };\n }\n \n if ('clientX' in event && 'clientY' in event) {\n return {\n x: event.clientX,\n y: event.clientY,\n };\n }\n \n return { x: 0, y: 0 };\n};\n\n","import { useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { SwipeConfig, SwipeCallbacks, GestureState, UseSwipeReturn, SwipeDirection } from '../types';\nimport {\n getSwipeDirection,\n calculateRotation,\n calculateOpacity,\n calculateVelocity,\n getEventCoordinates,\n} from '../utils/helpers';\n\nconst DEFAULT_CONFIG: Required<SwipeConfig> = {\n threshold: 100,\n velocityThreshold: 0.5,\n maxRotation: 15,\n exitDuration: 300,\n returnDuration: 200,\n enableRotation: true,\n preventSwipe: [],\n fadeOnSwipe: true,\n};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n // Memoize config to prevent unnecessary recalculations\n const mergedConfig = useMemo(() => ({ ...DEFAULT_CONFIG, ...config }), [\n config.threshold,\n config.velocityThreshold,\n config.maxRotation,\n config.exitDuration,\n config.returnDuration,\n config.enableRotation,\n config.fadeOnSwipe,\n // Use JSON for array comparison (preventSwipe is typically small)\n JSON.stringify(config.preventSwipe),\n ]);\n\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const timeoutRefs = useRef<Set<ReturnType<typeof setTimeout>>>(new Set());\n \n // Store callbacks in ref to avoid dependency issues\n const callbacksRef = useRef(callbacks);\n callbacksRef.current = callbacks;\n\n // Use refs for animation values that don't need to trigger re-renders during drag\n const transformRef = useRef('translate3d(0px, 0px, 0px) rotate(0deg)');\n const opacityRef = useRef(1);\n const transitionRef = useRef('none');\n\n // Single state for values that need to trigger re-renders\n const [renderState, setRenderState] = useState({\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n transition: 'none',\n });\n\n const [gestureState, setGestureState] = useState<GestureState>({\n isDragging: false,\n startX: 0,\n startY: 0,\n currentX: 0,\n currentY: 0,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: 0,\n });\n\n // Batch update render state\n const updateRenderState = useCallback((updates: Partial<typeof renderState>) => {\n setRenderState(prev => {\n const newState = { ...prev, ...updates };\n // Only update if values actually changed\n if (\n prev.transform === newState.transform &&\n prev.opacity === newState.opacity &&\n prev.transition === newState.transition\n ) {\n return prev;\n }\n return newState;\n });\n }, []);\n\n // Safe setTimeout that tracks timeouts for cleanup\n const safeTimeout = useCallback((fn: () => void, delay: number) => {\n const id = setTimeout(() => {\n timeoutRefs.current.delete(id);\n fn();\n }, delay);\n timeoutRefs.current.add(id);\n return id;\n }, []);\n\n // Handle swipe start\n const handleSwipeStart = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const coords = getEventCoordinates(event as any);\n const now = Date.now();\n\n setGestureState({\n isDragging: true,\n startX: coords.x,\n startY: coords.y,\n currentX: coords.x,\n currentY: coords.y,\n deltaX: 0,\n deltaY: 0,\n velocity: 0,\n direction: null,\n startTime: now,\n });\n\n transitionRef.current = 'none';\n updateRenderState({ transition: 'none' });\n \n callbacksRef.current.onSwipeStart?.();\n },\n [updateRenderState]\n );\n\n // Handle swipe move - use refs to avoid closure issues\n const gestureStateRef = useRef(gestureState);\n gestureStateRef.current = gestureState;\n\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n let deltaX = coords.x - state.startX;\n let deltaY = coords.y - state.startY;\n\n // Constrain movement based on preventSwipe directions\n if (preventSwipe.includes('left') && deltaX < 0) deltaX = 0;\n if (preventSwipe.includes('right') && deltaX > 0) deltaX = 0;\n if (preventSwipe.includes('up') && deltaY < 0) deltaY = 0;\n if (preventSwipe.includes('down') && deltaY > 0) deltaY = 0;\n\n // Update gesture state\n setGestureState(prev => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates - cancel previous frame\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = requestAnimationFrame(() => {\n const rotation = enableRotation ? calculateRotation(deltaX, maxRotation) : 0;\n const newOpacity = fadeOnSwipe ? calculateOpacity(deltaX, deltaY, threshold) : 1;\n const newTransform = `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`;\n\n // Update refs immediately for internal use\n transformRef.current = newTransform;\n opacityRef.current = newOpacity;\n\n // Batch update to React state\n updateRenderState({\n transform: newTransform,\n opacity: newOpacity,\n });\n });\n },\n [enableRotation, maxRotation, threshold, preventSwipe, fadeOnSwipe, updateRenderState]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n const state = gestureStateRef.current;\n if (!state.isDragging) return;\n\n const { deltaX, deltaY, startTime } = state;\n const timeDelta = Date.now() - startTime;\n const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);\n const velocity = calculateVelocity(distance, timeDelta);\n\n const direction = getSwipeDirection(deltaX, deltaY, threshold / 2);\n const shouldSwipe = distance >= threshold || velocity >= velocityThreshold;\n\n // Check if direction is prevented\n const isDirectionPrevented = direction && preventSwipe.includes(direction);\n\n if (shouldSwipe && direction && !isDirectionPrevented) {\n // Execute swipe\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n \n // Calculate exit position (move far enough off screen)\n const multiplier = 2;\n const exitX = deltaX * multiplier;\n const exitY = deltaY * multiplier;\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n \n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n const returnTransition = `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`;\n \n updateRenderState({\n transition: returnTransition,\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n\n callbacksRef.current.onSwipeEnd?.();\n }\n\n setGestureState(prev => ({ ...prev, isDragging: false }));\n }, [\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n preventSwipe,\n updateRenderState,\n safeTimeout,\n ]);\n\n // Stable event handler refs to avoid re-attaching listeners\n const handlersRef = useRef({\n handleSwipeStart,\n handleSwipeMove,\n handleSwipeEnd,\n });\n handlersRef.current = { handleSwipeStart, handleSwipeMove, handleSwipeEnd };\n\n // Attach event listeners with stable references\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const onPointerDown = (e: PointerEvent) => {\n handlersRef.current.handleSwipeStart(e);\n };\n\n element.addEventListener('pointerdown', onPointerDown);\n\n return () => {\n element.removeEventListener('pointerdown', onPointerDown);\n };\n }, []); // Empty deps - handlers are accessed via ref\n\n // Attach document listeners when dragging\n useEffect(() => {\n if (!gestureState.isDragging) return;\n\n const onPointerMove = (e: PointerEvent) => {\n handlersRef.current.handleSwipeMove(e);\n };\n\n const onPointerUp = () => {\n handlersRef.current.handleSwipeEnd();\n };\n\n document.addEventListener('pointermove', onPointerMove);\n document.addEventListener('pointerup', onPointerUp);\n document.addEventListener('pointercancel', onPointerUp);\n\n return () => {\n document.removeEventListener('pointermove', onPointerMove);\n document.removeEventListener('pointerup', onPointerUp);\n document.removeEventListener('pointercancel', onPointerUp);\n };\n }, [gestureState.isDragging]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Clear all pending timeouts\n timeoutRefs.current.forEach(id => clearTimeout(id));\n timeoutRefs.current.clear();\n };\n }, []);\n\n // Programmatic swipe function\n const swipe = useCallback((direction: SwipeDirection) => {\n // Check if direction is prevented\n if (preventSwipe.includes(direction)) {\n return;\n }\n\n // Calculate exit coordinates based on direction\n const exitDistance = 500; // Distance to move off screen\n let exitX = 0;\n let exitY = 0;\n\n switch (direction) {\n case 'left':\n exitX = -exitDistance;\n break;\n case 'right':\n exitX = exitDistance;\n break;\n case 'up':\n exitY = -exitDistance;\n break;\n case 'down':\n exitY = exitDistance;\n break;\n }\n\n const rotation = enableRotation ? calculateRotation(exitX, maxRotation * 2) : 0;\n const exitTransform = `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`;\n const exitTransition = `transform ${exitDuration}ms ease-out, opacity ${exitDuration}ms ease-out`;\n\n updateRenderState({\n transition: exitTransition,\n transform: exitTransform,\n opacity: 0,\n });\n\n // Trigger callback after animation\n safeTimeout(() => {\n switch (direction) {\n case 'left':\n callbacksRef.current.onSwipeLeft?.();\n break;\n case 'right':\n callbacksRef.current.onSwipeRight?.();\n break;\n case 'up':\n callbacksRef.current.onSwipeUp?.();\n break;\n case 'down':\n callbacksRef.current.onSwipeDown?.();\n break;\n }\n\n callbacksRef.current.onSwipeEnd?.();\n\n // Reset after callback\n safeTimeout(() => {\n updateRenderState({\n transition: 'none',\n transform: 'translate3d(0px, 0px, 0px) rotate(0deg)',\n opacity: 1,\n });\n }, 50);\n }, exitDuration);\n }, [preventSwipe, enableRotation, maxRotation, exitDuration, updateRenderState, safeTimeout]);\n\n return {\n ref,\n transform: renderState.transform,\n opacity: renderState.opacity,\n transition: renderState.transition,\n isDragging: gestureState.isDragging,\n deltaX: gestureState.deltaX,\n deltaY: gestureState.deltaY,\n swipe,\n };\n};\n","import React, { CSSProperties, useMemo, memo, forwardRef, useImperativeHandle } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps, SwipeCallbacks, SwipeConfig, SwipeCardRef } from '../types';\n\n/**\n * SwipeCard Component\n * \n * A performant swipeable card component with smooth animations.\n * Supports swipe in all four directions: left, right, up, and down.\n * \n * @example\n * ```tsx\n * <SwipeCard\n * onSwipeLeft={() => console.log('Swiped left')}\n * onSwipeRight={() => console.log('Swiped right')}\n * threshold={100}\n * swipeStyles={{\n * // backgroundColor is optional - transparent by default (card content stays visible)\n * right: { \n * backgroundColor: 'rgba(34, 197, 94, 0.6)', // green overlay\n * label: '👍 Like',\n * labelStyle: { position: 'absolute', top: 20, right: 20 }\n * },\n * left: { \n * backgroundColor: 'rgba(239, 68, 68, 0.6)', // red overlay\n * label: '👎 Nope',\n * labelStyle: { position: 'absolute', top: 20, left: 20 }\n * },\n * }}\n * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\n\n// Default label style - constant, no need to recreate\nconst DEFAULT_LABEL_STYLE: CSSProperties = {\n fontSize: '2rem',\n fontWeight: 'bold',\n color: '#fff',\n letterSpacing: '2px',\n textShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',\n};\n\n// Default styles for each direction - constant\n// backgroundColor defaults to transparent so card content remains visible unless explicitly set\nconst DEFAULT_SWIPE_STYLES = {\n right: { backgroundColor: 'transparent', label: '✓ Accept', labelStyle: DEFAULT_LABEL_STYLE },\n left: { backgroundColor: 'transparent', label: '✗ Reject', labelStyle: DEFAULT_LABEL_STYLE },\n up: { backgroundColor: 'transparent', label: '⭐ Super', labelStyle: DEFAULT_LABEL_STYLE },\n down: { backgroundColor: 'transparent', label: '⏭ Skip', labelStyle: DEFAULT_LABEL_STYLE },\n} as const;\n\n// Empty array constant to prevent recreating on each render\nconst EMPTY_PREVENT_SWIPE: readonly string[] = [];\n\nconst SwipeCardInner = forwardRef<SwipeCardRef, SwipeCardProps>(({\n children,\n className = '',\n style,\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n threshold = 100,\n velocityThreshold = 0.5,\n maxRotation = 15,\n exitDuration = 300,\n returnDuration = 200,\n enableRotation = true,\n preventSwipe = EMPTY_PREVENT_SWIPE as any,\n fadeOnSwipe = true,\n swipeStyles,\n showOverlay: showOverlayProp = true,\n}, forwardedRef) => {\n // Memoize callbacks object to prevent unnecessary hook updates\n const callbacks: SwipeCallbacks = useMemo(() => ({\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n }), [onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, onSwipeStart, onSwipeEnd]);\n\n // Memoize config object\n const config: SwipeConfig = useMemo(() => ({\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n preventSwipe,\n fadeOnSwipe,\n }), [threshold, velocityThreshold, maxRotation, exitDuration, returnDuration, enableRotation, preventSwipe, fadeOnSwipe]);\n\n const { ref, transform, opacity, transition, isDragging, deltaX, deltaY, swipe } = useSwipe(callbacks, config);\n\n // Expose swipe method via ref\n useImperativeHandle(forwardedRef, () => ({\n swipe,\n }), [swipe]);\n\n // Memoize merged swipe styles\n const mergedSwipeStyles = useMemo(() => ({\n right: { \n ...DEFAULT_SWIPE_STYLES.right, \n ...swipeStyles?.right,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.right.labelStyle, ...swipeStyles?.right?.labelStyle }\n },\n left: { \n ...DEFAULT_SWIPE_STYLES.left, \n ...swipeStyles?.left,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.left.labelStyle, ...swipeStyles?.left?.labelStyle }\n },\n up: { \n ...DEFAULT_SWIPE_STYLES.up, \n ...swipeStyles?.up,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.up.labelStyle, ...swipeStyles?.up?.labelStyle }\n },\n down: { \n ...DEFAULT_SWIPE_STYLES.down, \n ...swipeStyles?.down,\n labelStyle: { ...DEFAULT_SWIPE_STYLES.down.labelStyle, ...swipeStyles?.down?.labelStyle }\n },\n }), [\n swipeStyles?.right, \n swipeStyles?.left, \n swipeStyles?.up, \n swipeStyles?.down,\n ]);\n\n // Calculate swipe state - these are cheap, no need to memoize\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n const isPrimaryHorizontal = absX >= absY;\n const swipeDistance = isPrimaryHorizontal ? absX : absY;\n const swipeProgress = Math.min(swipeDistance / threshold, 1);\n \n const isSwipingRight = isPrimaryHorizontal && deltaX > 20;\n const isSwipingLeft = isPrimaryHorizontal && deltaX < -20;\n const isSwipingUp = !isPrimaryHorizontal && deltaY < -20;\n const isSwipingDown = !isPrimaryHorizontal && deltaY > 20;\n \n const isActionInProgress = swipeProgress >= 0.15;\n const showOverlay = showOverlayProp && isDragging && swipeDistance > 10;\n\n // Calculate current direction info - single calculation\n const currentDirection = useMemo(() => {\n if (isSwipingRight) return 'right' as const;\n if (isSwipingLeft) return 'left' as const;\n if (isSwipingUp) return 'up' as const;\n if (isSwipingDown) return 'down' as const;\n return null;\n }, [isSwipingRight, isSwipingLeft, isSwipingUp, isSwipingDown]);\n\n const currentStyle = currentDirection ? mergedSwipeStyles[currentDirection] : null;\n const backgroundColor = currentStyle?.backgroundColor ?? 'inherit';\n const labelConfig = currentStyle ? { label: currentStyle.label, labelStyle: currentStyle.labelStyle } : null;\n\n // Memoize card style - only recalculate when values change\n const cardStyle: CSSProperties = useMemo(() => ({\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none',\n userSelect: 'none',\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'absolute',\n }), [style, transform, opacity, transition, isDragging]);\n\n // Memoize overlay style\n const overlayStyle: CSSProperties = useMemo(() => ({\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'inherit',\n pointerEvents: 'none',\n zIndex: 10,\n opacity: showOverlay ? swipeProgress : 0,\n transition: isDragging ? 'none' : 'opacity 200ms ease-out',\n backgroundColor: showOverlay ? backgroundColor : 'inherit',\n }), [showOverlay, swipeProgress, isDragging, backgroundColor]);\n\n // Memoize computed label style\n const computedLabelStyle: CSSProperties = useMemo(() => ({\n ...labelConfig?.labelStyle,\n transform: `scale(${0.8 + swipeProgress * 0.4})`,\n opacity: swipeProgress,\n }), [labelConfig?.labelStyle, swipeProgress]);\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n <div style={overlayStyle}>\n {showOverlay && isActionInProgress && labelConfig?.label && (\n <span style={computedLabelStyle}>\n {labelConfig.label}\n </span>\n )}\n </div>\n </div>\n );\n});\n\n// Memoize the entire component to prevent unnecessary re-renders from parent\nexport const SwipeCard = memo(SwipeCardInner);\n\nexport default SwipeCard;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swipeon-react",
3
- "version": "1.0.4",
3
+ "version": "1.0.8",
4
4
  "type": "module",
5
5
  "description": "A high-performance React swipe card library with smooth animations and multi-directional swipe support",
6
6
  "main": "dist/index.js",
@@ -24,9 +24,14 @@
24
24
  "prepublishOnly": "npm run build",
25
25
  "clean": "rm -rf dist",
26
26
  "prebuild": "npm run clean",
27
+ "version": "node scripts/update-changelog.js && git add CHANGELOG.md",
27
28
  "publish:alpha": "npm version prerelease --preid=alpha && npm run build && npm publish --tag alpha",
28
29
  "publish:rc": "npm version prerelease --preid=rc && npm run build && npm publish --tag next",
30
+ "publish:patch": "npm version patch && npm run build && npm publish --tag latest",
31
+ "publish:minor": "npm version minor && npm run build && npm publish --tag latest",
32
+ "publish:major": "npm version major && npm run build && npm publish --tag latest",
29
33
  "publish:stable": "npm version patch && npm run build && npm publish --tag latest",
34
+ "changelog": "node scripts/update-changelog.js",
30
35
  "storybook": "storybook dev -p 6006",
31
36
  "build-storybook": "storybook build"
32
37
  },
@@ -70,5 +75,8 @@
70
75
  "typescript": "^5.3.3",
71
76
  "vite": "^5.0.0"
72
77
  },
78
+ "engines": {
79
+ "node": ">=20.0.0"
80
+ },
73
81
  "packageManager": "yarn@3.6.0+sha512.418e45c2268c4d6b69a28f3939084b5853d5f392c43c0b5588bd1995a96e328414ae4b7777a8980c64bad4328c52586ff879b289f98ae65372a55fa4d0ff70dd"
74
82
  }