swipeon-react 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.md +332 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +100 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +100 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 react-forge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# ðī SwipeOn React
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/swipeon-react)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
A high-performance, zero-dependency React swipe card library with smooth animations and multi-directional swipe support. Perfect for building Tinder-like interfaces, interactive card stacks, and gesture-based UI components.
|
|
8
|
+
|
|
9
|
+
## âĻ Features
|
|
10
|
+
|
|
11
|
+
- ð **High Performance**: Optimized with `requestAnimationFrame` and hardware-accelerated transforms
|
|
12
|
+
- ðŊ **4-Way Swipe**: Support for left, right, up, and down swipes
|
|
13
|
+
- ðą **Touch & Mouse**: Works seamlessly on both desktop and mobile devices
|
|
14
|
+
- ðĻ **Smooth Animations**: Butter-smooth 60fps animations with spring-back effects
|
|
15
|
+
- ðŠ **TypeScript**: Full TypeScript support with comprehensive type definitions
|
|
16
|
+
- ðŠķ **Zero Dependencies**: No external dependencies except React
|
|
17
|
+
- âïļ **Highly Configurable**: Customizable thresholds, velocities, and animations
|
|
18
|
+
- ð **Rotation Effects**: Optional card rotation during drag for realistic feel
|
|
19
|
+
|
|
20
|
+
## ðĶ Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install swipeon-react
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add swipeon-react
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add swipeon-react
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ð Quick Start
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import React from 'react';
|
|
38
|
+
import { SwipeCard } from 'swipeon-react';
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
return (
|
|
42
|
+
<SwipeCard
|
|
43
|
+
onSwipeLeft={() => console.log('Swiped left! ð')}
|
|
44
|
+
onSwipeRight={() => console.log('Swiped right! ð')}
|
|
45
|
+
onSwipeUp={() => console.log('Swiped up! ð')}
|
|
46
|
+
onSwipeDown={() => console.log('Swiped down! ð')}
|
|
47
|
+
>
|
|
48
|
+
<div style={{ padding: '40px', background: '#fff', borderRadius: '10px' }}>
|
|
49
|
+
<h2>Swipe me in any direction!</h2>
|
|
50
|
+
<p>Works with mouse and touch</p>
|
|
51
|
+
</div>
|
|
52
|
+
</SwipeCard>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default App;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## ð API Reference
|
|
60
|
+
|
|
61
|
+
### SwipeCard Component
|
|
62
|
+
|
|
63
|
+
#### Props
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Default | Description |
|
|
66
|
+
|------|------|---------|-------------|
|
|
67
|
+
| `children` | `ReactNode` | **required** | Content to render inside the card |
|
|
68
|
+
| `onSwipeLeft` | `() => void` | `undefined` | Callback triggered when card is swiped left |
|
|
69
|
+
| `onSwipeRight` | `() => void` | `undefined` | Callback triggered when card is swiped right |
|
|
70
|
+
| `onSwipeUp` | `() => void` | `undefined` | Callback triggered when card is swiped up |
|
|
71
|
+
| `onSwipeDown` | `() => void` | `undefined` | Callback triggered when card is swiped down |
|
|
72
|
+
| `onSwipeStart` | `() => void` | `undefined` | Callback triggered when swipe starts |
|
|
73
|
+
| `onSwipeEnd` | `() => void` | `undefined` | Callback triggered when swipe ends |
|
|
74
|
+
| `threshold` | `number` | `100` | Minimum distance (px) required to trigger a swipe |
|
|
75
|
+
| `velocityThreshold` | `number` | `0.5` | Minimum velocity required to trigger a swipe |
|
|
76
|
+
| `maxRotation` | `number` | `15` | Maximum rotation angle (degrees) during drag |
|
|
77
|
+
| `exitDuration` | `number` | `300` | Duration (ms) of the exit animation |
|
|
78
|
+
| `returnDuration` | `number` | `200` | Duration (ms) of the spring-back animation |
|
|
79
|
+
| `enableRotation` | `boolean` | `true` | Enable/disable rotation effect during drag |
|
|
80
|
+
| `className` | `string` | `''` | Additional CSS class name |
|
|
81
|
+
| `style` | `CSSProperties` | `{}` | Additional inline styles |
|
|
82
|
+
|
|
83
|
+
### useSwipe Hook
|
|
84
|
+
|
|
85
|
+
For advanced use cases, you can use the `useSwipe` hook directly:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import { useSwipe } from 'swipeon-react';
|
|
89
|
+
|
|
90
|
+
function CustomCard() {
|
|
91
|
+
const { ref, transform, opacity, transition, isDragging } = useSwipe(
|
|
92
|
+
{
|
|
93
|
+
onSwipeLeft: () => console.log('Left!'),
|
|
94
|
+
onSwipeRight: () => console.log('Right!'),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
threshold: 100,
|
|
98
|
+
velocityThreshold: 0.5,
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div
|
|
104
|
+
ref={ref}
|
|
105
|
+
style={{
|
|
106
|
+
transform,
|
|
107
|
+
opacity,
|
|
108
|
+
transition,
|
|
109
|
+
cursor: isDragging ? 'grabbing' : 'grab',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
Your content here
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Hook Return Values
|
|
119
|
+
|
|
120
|
+
| Property | Type | Description |
|
|
121
|
+
|----------|------|-------------|
|
|
122
|
+
| `ref` | `RefObject<HTMLDivElement>` | Ref to attach to your swipeable element |
|
|
123
|
+
| `transform` | `string` | CSS transform value for positioning/rotation |
|
|
124
|
+
| `opacity` | `number` | Opacity value based on drag distance |
|
|
125
|
+
| `transition` | `string` | CSS transition value for animations |
|
|
126
|
+
| `isDragging` | `boolean` | Whether the element is currently being dragged |
|
|
127
|
+
|
|
128
|
+
## ðĄ Examples
|
|
129
|
+
|
|
130
|
+
### Card Stack (Tinder-like)
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import React, { useState } from 'react';
|
|
134
|
+
import { SwipeCard } from 'swipeon-react';
|
|
135
|
+
|
|
136
|
+
const cards = [
|
|
137
|
+
{ id: 1, name: 'Card 1', image: '/card1.jpg' },
|
|
138
|
+
{ id: 2, name: 'Card 2', image: '/card2.jpg' },
|
|
139
|
+
{ id: 3, name: 'Card 3', image: '/card3.jpg' },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
function CardStack() {
|
|
143
|
+
const [deck, setDeck] = useState(cards);
|
|
144
|
+
|
|
145
|
+
const handleRemove = () => {
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
setDeck((prev) => prev.slice(0, -1));
|
|
148
|
+
}, 300);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (deck.length === 0) {
|
|
152
|
+
return <div>No more cards!</div>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div style={{ position: 'relative', width: '300px', height: '400px' }}>
|
|
157
|
+
{deck.map((card, index) => (
|
|
158
|
+
<SwipeCard
|
|
159
|
+
key={card.id}
|
|
160
|
+
onSwipeLeft={handleRemove}
|
|
161
|
+
onSwipeRight={handleRemove}
|
|
162
|
+
onSwipeUp={handleRemove}
|
|
163
|
+
onSwipeDown={handleRemove}
|
|
164
|
+
style={{
|
|
165
|
+
position: 'absolute',
|
|
166
|
+
width: '100%',
|
|
167
|
+
height: '100%',
|
|
168
|
+
zIndex: deck.length - index,
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<div style={{
|
|
172
|
+
width: '100%',
|
|
173
|
+
height: '100%',
|
|
174
|
+
background: '#fff',
|
|
175
|
+
borderRadius: '10px',
|
|
176
|
+
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
|
|
177
|
+
}}>
|
|
178
|
+
<img src={card.image} alt={card.name} style={{ width: '100%' }} />
|
|
179
|
+
<h3>{card.name}</h3>
|
|
180
|
+
</div>
|
|
181
|
+
</SwipeCard>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Custom Thresholds
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
<SwipeCard
|
|
192
|
+
threshold={150} // Require 150px movement
|
|
193
|
+
velocityThreshold={0.8} // Require faster swipes
|
|
194
|
+
maxRotation={25} // More dramatic rotation
|
|
195
|
+
exitDuration={500} // Slower exit animation
|
|
196
|
+
>
|
|
197
|
+
<YourContent />
|
|
198
|
+
</SwipeCard>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Disable Rotation
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
<SwipeCard
|
|
205
|
+
enableRotation={false}
|
|
206
|
+
onSwipeLeft={() => console.log('Left')}
|
|
207
|
+
onSwipeRight={() => console.log('Right')}
|
|
208
|
+
>
|
|
209
|
+
<YourContent />
|
|
210
|
+
</SwipeCard>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Only Horizontal Swipes
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<SwipeCard
|
|
217
|
+
onSwipeLeft={() => handleSwipe('left')}
|
|
218
|
+
onSwipeRight={() => handleSwipe('right')}
|
|
219
|
+
// Don't provide onSwipeUp/onSwipeDown
|
|
220
|
+
>
|
|
221
|
+
<YourContent />
|
|
222
|
+
</SwipeCard>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## ðĻ Styling
|
|
226
|
+
|
|
227
|
+
The component applies minimal styling by default. You can customize appearance using:
|
|
228
|
+
|
|
229
|
+
1. **CSS Classes**:
|
|
230
|
+
```tsx
|
|
231
|
+
<SwipeCard className="my-custom-card">
|
|
232
|
+
<YourContent />
|
|
233
|
+
</SwipeCard>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
2. **Inline Styles**:
|
|
237
|
+
```tsx
|
|
238
|
+
<SwipeCard
|
|
239
|
+
style={{
|
|
240
|
+
borderRadius: '20px',
|
|
241
|
+
boxShadow: '0 10px 40px rgba(0,0,0,0.2)'
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<YourContent />
|
|
245
|
+
</SwipeCard>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
3. **Styled Components / Emotion**:
|
|
249
|
+
```tsx
|
|
250
|
+
const StyledSwipeCard = styled(SwipeCard)`
|
|
251
|
+
border-radius: 20px;
|
|
252
|
+
background: white;
|
|
253
|
+
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
|
254
|
+
`;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## ⥠Performance Tips
|
|
258
|
+
|
|
259
|
+
1. **Use `key` prop**: When rendering multiple cards, always provide a unique `key`
|
|
260
|
+
2. **Optimize callbacks**: Use `useCallback` for swipe handlers to prevent unnecessary re-renders
|
|
261
|
+
3. **Limit card stack**: Don't render more than 3-5 cards in a stack
|
|
262
|
+
4. **Optimize images**: Use appropriately sized images to improve performance
|
|
263
|
+
5. **CSS containment**: The library uses `will-change` and `transform3d` for hardware acceleration
|
|
264
|
+
|
|
265
|
+
## ð Browser Support
|
|
266
|
+
|
|
267
|
+
- Chrome (latest)
|
|
268
|
+
- Firefox (latest)
|
|
269
|
+
- Safari (latest)
|
|
270
|
+
- Edge (latest)
|
|
271
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
272
|
+
|
|
273
|
+
## ðą Running the Demo
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Clone the repository
|
|
277
|
+
git clone https://github.com/react-forge/swipeon-react.git
|
|
278
|
+
cd swipeon-react
|
|
279
|
+
|
|
280
|
+
# Install dependencies
|
|
281
|
+
npm install
|
|
282
|
+
|
|
283
|
+
# Run the demo
|
|
284
|
+
cd example
|
|
285
|
+
npm install
|
|
286
|
+
npm run dev
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The demo will open at `http://localhost:3000`
|
|
290
|
+
|
|
291
|
+
## ð ïļ Development
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
# Install dependencies
|
|
295
|
+
npm install
|
|
296
|
+
|
|
297
|
+
# Build the library
|
|
298
|
+
npm run build
|
|
299
|
+
|
|
300
|
+
# Watch mode for development
|
|
301
|
+
npm run dev
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## ðĪ Contributing
|
|
305
|
+
|
|
306
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
307
|
+
|
|
308
|
+
1. Fork the repository
|
|
309
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
|
310
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
311
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
312
|
+
5. Open a Pull Request
|
|
313
|
+
|
|
314
|
+
## ð License
|
|
315
|
+
|
|
316
|
+
MIT ÂĐ [react-forge](https://github.com/react-forge)
|
|
317
|
+
|
|
318
|
+
## ð Acknowledgments
|
|
319
|
+
|
|
320
|
+
- Inspired by Tinder's swipe interface
|
|
321
|
+
- Built with React and TypeScript
|
|
322
|
+
- Uses Pointer Events API for unified input handling
|
|
323
|
+
|
|
324
|
+
## ð§ Support
|
|
325
|
+
|
|
326
|
+
- ð [Report a bug](https://github.com/react-forge/swipeon-react/issues)
|
|
327
|
+
- ðĄ [Request a feature](https://github.com/react-forge/swipeon-react/issues)
|
|
328
|
+
- ð [Read the docs](https://github.com/react-forge/swipeon-react#readme)
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
Made with âĪïļ by [react-forge](https://github.com/react-forge)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +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},h=(e={},t={})=>{let{threshold:n,velocityThreshold:r,maxRotation:i,exitDuration:a,returnDuration:o,enableRotation:s}={...m,...t},h=(0,c.useRef)(null),g=(0,c.useRef)(null),[_,v]=(0,c.useState)({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),[y,b]=(0,c.useState)(`translate3d(0px, 0px, 0px) rotate(0deg)`),[x,S]=(0,c.useState)(1),[C,w]=(0,c.useState)(`none`),T=(0,c.useCallback)(t=>{let n=p(t),r=Date.now();v({isDragging:!0,startX:n.x,startY:n.y,currentX:n.x,currentY:n.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:r}),w(`none`),e.onSwipeStart&&e.onSwipeStart()},[e]),E=(0,c.useCallback)(e=>{if(!_.isDragging)return;let t=p(e),r=t.x-_.startX,a=t.y-_.startY;v(e=>({...e,currentX:t.x,currentY:t.y,deltaX:r,deltaY:a})),g.current&&cancelAnimationFrame(g.current),g.current=requestAnimationFrame(()=>{let e=s?d(r,i):0,t=f(r,a,n);b(`translate3d(${r}px, ${a}px, 0px) rotate(${e}deg)`),S(t)})},[_.isDragging,_.startX,_.startY,s,i,n]),D=(0,c.useCallback)(()=>{if(!_.isDragging)return;let{deltaX:t,deltaY:c,startTime:f}=_,p=Date.now()-f,m=Math.sqrt(t*t+c*c),h=l(m,p),g=u(t,c,n/2);if((m>=n||h>=r)&&g){w(`transform ${a}ms ease-out, opacity ${a}ms ease-out`);let n=t*2;b(`translate3d(${n}px, ${c*2}px, 0px) rotate(${s?d(n,i*2):0}deg)`),S(0),setTimeout(()=>{switch(g){case`left`:e.onSwipeLeft?.();break;case`right`:e.onSwipeRight?.();break;case`up`:e.onSwipeUp?.();break;case`down`:e.onSwipeDown?.();break}e.onSwipeEnd&&e.onSwipeEnd(),setTimeout(()=>{w(`none`),b(`translate3d(0px, 0px, 0px) rotate(0deg)`),S(1)},50)},a)}else w(`transform ${o}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${o}ms ease-out`),b(`translate3d(0px, 0px, 0px) rotate(0deg)`),S(1),e.onSwipeEnd&&e.onSwipeEnd();v(e=>({...e,isDragging:!1}))},[_,n,r,a,o,s,i,e]);return(0,c.useEffect)(()=>{let e=h.current;if(!e)return;let t=e=>{T(e)},n=e=>{_.isDragging&&E(e)},r=()=>{D()};return e.addEventListener(`pointerdown`,t),_.isDragging&&(document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r),document.addEventListener(`pointercancel`,r)),()=>{e.removeEventListener(`pointerdown`,t),document.removeEventListener(`pointermove`,n),document.removeEventListener(`pointerup`,r),document.removeEventListener(`pointercancel`,r),g.current&&cancelAnimationFrame(g.current)}},[_.isDragging,T,E,D]),{ref:h,transform:y,opacity:x,transition:C,isDragging:_.isDragging}},g=({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})=>{let{ref:_,transform:v,opacity:y,transition:b,isDragging:x}=h({onSwipeLeft:r,onSwipeRight:i,onSwipeUp:a,onSwipeDown:o,onSwipeStart:s,onSwipeEnd:l},{threshold:u,velocityThreshold:d,maxRotation:f,exitDuration:p,returnDuration:m,enableRotation:g}),S={...n,transform:v,opacity:y,transition:b,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:x?`grabbing`:`grab`,willChange:x?`transform, opacity`:`auto`,position:`relative`};return c.default.createElement(`div`,{ref:_,className:`swipeon-card ${t}`,style:S},e)};exports.SwipeCard=g,exports.useSwipe=h;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","SwipeCard: React.FC<SwipeCardProps>","cardStyle: 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 } 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};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\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 const [transform, setTransform] = useState('translate3d(0px, 0px, 0px) rotate(0deg)');\n const [opacity, setOpacity] = useState(1);\n const [transition, setTransition] = useState('none');\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 setTransition('none');\n \n if (callbacks.onSwipeStart) {\n callbacks.onSwipeStart();\n }\n },\n [callbacks]\n );\n\n // Handle swipe move\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n if (!gestureState.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n const deltaX = coords.x - gestureState.startX;\n const deltaY = coords.y - gestureState.startY;\n\n // Update gesture state\n setGestureState((prev: GestureState) => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates\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 = calculateOpacity(deltaX, deltaY, threshold);\n\n setTransform(\n `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`\n );\n setOpacity(newOpacity);\n });\n },\n [gestureState.isDragging, gestureState.startX, gestureState.startY, enableRotation, maxRotation, threshold]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n if (!gestureState.isDragging) return;\n\n const { deltaX, deltaY, startTime } = gestureState;\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 =\n distance >= threshold || velocity >= velocityThreshold;\n\n if (shouldSwipe && direction) {\n // Execute swipe\n setTransition(`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\n setTransform(\n `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`\n );\n setOpacity(0);\n\n // Trigger callback after animation\n setTimeout(() => {\n switch (direction) {\n case 'left':\n callbacks.onSwipeLeft?.();\n break;\n case 'right':\n callbacks.onSwipeRight?.();\n break;\n case 'up':\n callbacks.onSwipeUp?.();\n break;\n case 'down':\n callbacks.onSwipeDown?.();\n break;\n }\n \n if (callbacks.onSwipeEnd) {\n callbacks.onSwipeEnd();\n }\n\n // Reset after callback\n setTimeout(() => {\n setTransition('none');\n setTransform('translate3d(0px, 0px, 0px) rotate(0deg)');\n setOpacity(1);\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n setTransition(\n `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`\n );\n setTransform('translate3d(0px, 0px, 0px) rotate(0deg)');\n setOpacity(1);\n\n if (callbacks.onSwipeEnd) {\n callbacks.onSwipeEnd();\n }\n }\n\n setGestureState((prev: GestureState) => ({ ...prev, isDragging: false }));\n }, [\n gestureState,\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n callbacks,\n ]);\n\n // Attach event listeners\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const handlePointerDown = (e: PointerEvent) => {\n handleSwipeStart(e);\n };\n\n const handlePointerMove = (e: PointerEvent) => {\n if (gestureState.isDragging) {\n handleSwipeMove(e);\n }\n };\n\n const handlePointerUp = () => {\n handleSwipeEnd();\n };\n\n element.addEventListener('pointerdown', handlePointerDown);\n\n if (gestureState.isDragging) {\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n document.addEventListener('pointercancel', handlePointerUp);\n }\n\n return () => {\n element.removeEventListener('pointerdown', handlePointerDown);\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n document.removeEventListener('pointercancel', handlePointerUp);\n \n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, [gestureState.isDragging, handleSwipeStart, handleSwipeMove, handleSwipeEnd]);\n\n return {\n ref,\n transform,\n opacity,\n transition,\n isDragging: gestureState.isDragging,\n };\n};\n\n","import React, { CSSProperties } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps } 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 * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\nexport const SwipeCard: 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}) => {\n const callbacks = {\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n };\n\n const config = {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n };\n\n const { ref, transform, opacity, transition, isDragging } = useSwipe(callbacks, config);\n\n const cardStyle: CSSProperties = {\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none', // Prevent default touch behaviors\n userSelect: 'none', // Prevent text selection during drag\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'relative',\n };\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n </div>\n );\n};\n\nexport default SwipeCard;\n\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React$1, { CSSProperties, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/types/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Swipe direction types
|
|
7
|
+
*/
|
|
8
|
+
type SwipeDirection = 'left' | 'right' | 'up' | 'down';
|
|
9
|
+
/**
|
|
10
|
+
* Callback function signatures for swipe events
|
|
11
|
+
*/
|
|
12
|
+
interface SwipeCallbacks {
|
|
13
|
+
onSwipeLeft?: () => void;
|
|
14
|
+
onSwipeRight?: () => void;
|
|
15
|
+
onSwipeUp?: () => void;
|
|
16
|
+
onSwipeDown?: () => void;
|
|
17
|
+
onSwipeStart?: () => void;
|
|
18
|
+
onSwipeEnd?: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for swipe behavior
|
|
22
|
+
*/
|
|
23
|
+
interface SwipeConfig {
|
|
24
|
+
/** Minimum distance (in pixels) required to trigger a swipe */
|
|
25
|
+
threshold?: number;
|
|
26
|
+
/** Minimum velocity required to trigger a swipe */
|
|
27
|
+
velocityThreshold?: number;
|
|
28
|
+
/** Maximum rotation angle in degrees during drag */
|
|
29
|
+
maxRotation?: number;
|
|
30
|
+
/** Duration of the exit animation in milliseconds */
|
|
31
|
+
exitDuration?: number;
|
|
32
|
+
/** Duration of the spring-back animation in milliseconds */
|
|
33
|
+
returnDuration?: number;
|
|
34
|
+
/** Enable/disable rotation effect during drag */
|
|
35
|
+
enableRotation?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Props for the SwipeCard component
|
|
39
|
+
*/
|
|
40
|
+
interface SwipeCardProps extends SwipeCallbacks, SwipeConfig {
|
|
41
|
+
/** Content to render inside the card */
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
/** Additional CSS class name */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Additional inline styles */
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
/** Unique key for the card (useful in lists) */
|
|
48
|
+
cardKey?: string | number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Internal gesture state for tracking swipe progress
|
|
52
|
+
*/
|
|
53
|
+
interface GestureState {
|
|
54
|
+
isDragging: boolean;
|
|
55
|
+
startX: number;
|
|
56
|
+
startY: number;
|
|
57
|
+
currentX: number;
|
|
58
|
+
currentY: number;
|
|
59
|
+
deltaX: number;
|
|
60
|
+
deltaY: number;
|
|
61
|
+
velocity: number;
|
|
62
|
+
direction: SwipeDirection | null;
|
|
63
|
+
startTime: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Return type of the useSwipe hook
|
|
67
|
+
*/
|
|
68
|
+
interface UseSwipeReturn {
|
|
69
|
+
ref: React.RefObject<HTMLDivElement>;
|
|
70
|
+
transform: string;
|
|
71
|
+
opacity: number;
|
|
72
|
+
transition: string;
|
|
73
|
+
isDragging: boolean;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/components/SwipeCard.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* SwipeCard Component
|
|
79
|
+
*
|
|
80
|
+
* A performant swipeable card component with smooth animations.
|
|
81
|
+
* Supports swipe in all four directions: left, right, up, and down.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <SwipeCard
|
|
86
|
+
* onSwipeLeft={() => console.log('Swiped left')}
|
|
87
|
+
* onSwipeRight={() => console.log('Swiped right')}
|
|
88
|
+
* threshold={100}
|
|
89
|
+
* >
|
|
90
|
+
* <div>Your content here</div>
|
|
91
|
+
* </SwipeCard>
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare const SwipeCard: React$1.FC<SwipeCardProps>;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/hooks/useSwipe.d.ts
|
|
97
|
+
declare const useSwipe: (callbacks?: SwipeCallbacks, config?: SwipeConfig) => UseSwipeReturn;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeConfig, type SwipeDirection, type UseSwipeReturn, useSwipe };
|
|
100
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +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;AAkBA;;AAMU,UApCO,cAAA,CAoCP;EAN8B,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAgB,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAW,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAclD,WAAA,CAAA,EAAA,GAAY,GAAA,IAAA;EAgBZ,YAAA,CAAA,EAAA,GAAc,GAAA,IAAA;;;;ACjD/B;;UDCiB,WAAA;;EEHJ,SAAA,CAAA,EAgOZ,MAAA;EA/NY;EACH,iBAAA,CAAA,EAAA,MAAA;EACP;EA6NF,WAAA,CAAA,EAAA,MAAA;;;;;;;;;;;UF3MgB,cAAA,SAAuB,gBAAgB;;YAE5C;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;;AAlEvB;AAKA;AAYA;AAkBA;;;;;;AAcA;AAgBA;;;;ACjDA;;cAAa,WAAW,OAAA,CAAM,GAAG;;;cCFpB,uBACA,yBACH,gBACP"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React$1, { CSSProperties, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/types/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Swipe direction types
|
|
7
|
+
*/
|
|
8
|
+
type SwipeDirection = 'left' | 'right' | 'up' | 'down';
|
|
9
|
+
/**
|
|
10
|
+
* Callback function signatures for swipe events
|
|
11
|
+
*/
|
|
12
|
+
interface SwipeCallbacks {
|
|
13
|
+
onSwipeLeft?: () => void;
|
|
14
|
+
onSwipeRight?: () => void;
|
|
15
|
+
onSwipeUp?: () => void;
|
|
16
|
+
onSwipeDown?: () => void;
|
|
17
|
+
onSwipeStart?: () => void;
|
|
18
|
+
onSwipeEnd?: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for swipe behavior
|
|
22
|
+
*/
|
|
23
|
+
interface SwipeConfig {
|
|
24
|
+
/** Minimum distance (in pixels) required to trigger a swipe */
|
|
25
|
+
threshold?: number;
|
|
26
|
+
/** Minimum velocity required to trigger a swipe */
|
|
27
|
+
velocityThreshold?: number;
|
|
28
|
+
/** Maximum rotation angle in degrees during drag */
|
|
29
|
+
maxRotation?: number;
|
|
30
|
+
/** Duration of the exit animation in milliseconds */
|
|
31
|
+
exitDuration?: number;
|
|
32
|
+
/** Duration of the spring-back animation in milliseconds */
|
|
33
|
+
returnDuration?: number;
|
|
34
|
+
/** Enable/disable rotation effect during drag */
|
|
35
|
+
enableRotation?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Props for the SwipeCard component
|
|
39
|
+
*/
|
|
40
|
+
interface SwipeCardProps extends SwipeCallbacks, SwipeConfig {
|
|
41
|
+
/** Content to render inside the card */
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
/** Additional CSS class name */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Additional inline styles */
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
/** Unique key for the card (useful in lists) */
|
|
48
|
+
cardKey?: string | number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Internal gesture state for tracking swipe progress
|
|
52
|
+
*/
|
|
53
|
+
interface GestureState {
|
|
54
|
+
isDragging: boolean;
|
|
55
|
+
startX: number;
|
|
56
|
+
startY: number;
|
|
57
|
+
currentX: number;
|
|
58
|
+
currentY: number;
|
|
59
|
+
deltaX: number;
|
|
60
|
+
deltaY: number;
|
|
61
|
+
velocity: number;
|
|
62
|
+
direction: SwipeDirection | null;
|
|
63
|
+
startTime: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Return type of the useSwipe hook
|
|
67
|
+
*/
|
|
68
|
+
interface UseSwipeReturn {
|
|
69
|
+
ref: React.RefObject<HTMLDivElement>;
|
|
70
|
+
transform: string;
|
|
71
|
+
opacity: number;
|
|
72
|
+
transition: string;
|
|
73
|
+
isDragging: boolean;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/components/SwipeCard.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* SwipeCard Component
|
|
79
|
+
*
|
|
80
|
+
* A performant swipeable card component with smooth animations.
|
|
81
|
+
* Supports swipe in all four directions: left, right, up, and down.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```tsx
|
|
85
|
+
* <SwipeCard
|
|
86
|
+
* onSwipeLeft={() => console.log('Swiped left')}
|
|
87
|
+
* onSwipeRight={() => console.log('Swiped right')}
|
|
88
|
+
* threshold={100}
|
|
89
|
+
* >
|
|
90
|
+
* <div>Your content here</div>
|
|
91
|
+
* </SwipeCard>
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare const SwipeCard: React$1.FC<SwipeCardProps>;
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/hooks/useSwipe.d.ts
|
|
97
|
+
declare const useSwipe: (callbacks?: SwipeCallbacks, config?: SwipeConfig) => UseSwipeReturn;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { type GestureState, type SwipeCallbacks, SwipeCard, type SwipeCardProps, type SwipeConfig, type SwipeDirection, type UseSwipeReturn, useSwipe };
|
|
100
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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;AAkBA;;AAMU,UApCO,cAAA,CAoCP;EAN8B,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAgB,YAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAAW,SAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EAclD,WAAA,CAAA,EAAA,GAAY,GAAA,IAAA;EAgBZ,YAAA,CAAA,EAAA,GAAc,GAAA,IAAA;;;;ACjD/B;;UDCiB,WAAA;;EEHJ,SAAA,CAAA,EAgOZ,MAAA;EA/NY;EACH,iBAAA,CAAA,EAAA,MAAA;EACP;EA6NF,WAAA,CAAA,EAAA,MAAA;;;;;;;;;;;UF3MgB,cAAA,SAAuB,gBAAgB;;YAE5C;;;;UAIF;;;;;;;UAQO,YAAA;;;;;;;;;aASJ;;;;;;UAOI,cAAA;OACV,KAAA,CAAM,UAAU;;;;;;;;;AAlEvB;AAKA;AAYA;AAkBA;;;;;;AAcA;AAgBA;;;;ACjDA;;cAAa,WAAW,OAAA,CAAM,GAAG;;;cCFpB,uBACA,yBACH,gBACP"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e,{useCallback as t,useEffect as n,useRef as r,useState as i}from"react";const a=(e,t)=>t===0?0:Math.abs(e/t),o=(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`},s=(e,t=15,n=300)=>{let r=e/n*t;return Math.max(-t,Math.min(t,r))},c=(e,t,n=100)=>{let r=1-Math.sqrt(e*e+t*t)/(n*3);return Math.max(.5,Math.min(1,r))},l=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},u={threshold:100,velocityThreshold:.5,maxRotation:15,exitDuration:300,returnDuration:200,enableRotation:!0},d=(e={},d={})=>{let{threshold:f,velocityThreshold:p,maxRotation:m,exitDuration:h,returnDuration:g,enableRotation:_}={...u,...d},v=r(null),y=r(null),[b,x]=i({isDragging:!1,startX:0,startY:0,currentX:0,currentY:0,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:0}),[S,C]=i(`translate3d(0px, 0px, 0px) rotate(0deg)`),[w,T]=i(1),[E,D]=i(`none`),O=t(t=>{let n=l(t),r=Date.now();x({isDragging:!0,startX:n.x,startY:n.y,currentX:n.x,currentY:n.y,deltaX:0,deltaY:0,velocity:0,direction:null,startTime:r}),D(`none`),e.onSwipeStart&&e.onSwipeStart()},[e]),k=t(e=>{if(!b.isDragging)return;let t=l(e),n=t.x-b.startX,r=t.y-b.startY;x(e=>({...e,currentX:t.x,currentY:t.y,deltaX:n,deltaY:r})),y.current&&cancelAnimationFrame(y.current),y.current=requestAnimationFrame(()=>{let e=_?s(n,m):0,t=c(n,r,f);C(`translate3d(${n}px, ${r}px, 0px) rotate(${e}deg)`),T(t)})},[b.isDragging,b.startX,b.startY,_,m,f]),A=t(()=>{if(!b.isDragging)return;let{deltaX:t,deltaY:n,startTime:r}=b,i=Date.now()-r,c=Math.sqrt(t*t+n*n),l=a(c,i),u=o(t,n,f/2);if((c>=f||l>=p)&&u){D(`transform ${h}ms ease-out, opacity ${h}ms ease-out`);let r=t*2;C(`translate3d(${r}px, ${n*2}px, 0px) rotate(${_?s(r,m*2):0}deg)`),T(0),setTimeout(()=>{switch(u){case`left`:e.onSwipeLeft?.();break;case`right`:e.onSwipeRight?.();break;case`up`:e.onSwipeUp?.();break;case`down`:e.onSwipeDown?.();break}e.onSwipeEnd&&e.onSwipeEnd(),setTimeout(()=>{D(`none`),C(`translate3d(0px, 0px, 0px) rotate(0deg)`),T(1)},50)},h)}else D(`transform ${g}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${g}ms ease-out`),C(`translate3d(0px, 0px, 0px) rotate(0deg)`),T(1),e.onSwipeEnd&&e.onSwipeEnd();x(e=>({...e,isDragging:!1}))},[b,f,p,h,g,_,m,e]);return n(()=>{let e=v.current;if(!e)return;let t=e=>{O(e)},n=e=>{b.isDragging&&k(e)},r=()=>{A()};return e.addEventListener(`pointerdown`,t),b.isDragging&&(document.addEventListener(`pointermove`,n),document.addEventListener(`pointerup`,r),document.addEventListener(`pointercancel`,r)),()=>{e.removeEventListener(`pointerdown`,t),document.removeEventListener(`pointermove`,n),document.removeEventListener(`pointerup`,r),document.removeEventListener(`pointercancel`,r),y.current&&cancelAnimationFrame(y.current)}},[b.isDragging,O,k,A]),{ref:v,transform:S,opacity:w,transition:E,isDragging:b.isDragging}},f=({children:t,className:n=``,style:r={},onSwipeLeft:i,onSwipeRight:a,onSwipeUp:o,onSwipeDown:s,onSwipeStart:c,onSwipeEnd:l,threshold:u=100,velocityThreshold:f=.5,maxRotation:p=15,exitDuration:m=300,returnDuration:h=200,enableRotation:g=!0})=>{let{ref:_,transform:v,opacity:y,transition:b,isDragging:x}=d({onSwipeLeft:i,onSwipeRight:a,onSwipeUp:o,onSwipeDown:s,onSwipeStart:c,onSwipeEnd:l},{threshold:u,velocityThreshold:f,maxRotation:p,exitDuration:m,returnDuration:h,enableRotation:g}),S={...r,transform:v,opacity:y,transition:b,touchAction:`none`,userSelect:`none`,WebkitUserSelect:`none`,cursor:x?`grabbing`:`grab`,willChange:x?`transform, opacity`:`auto`,position:`relative`};return e.createElement(`div`,{ref:_,className:`swipeon-card ${n}`,style:S},t)};export{f as SwipeCard,d as useSwipe};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["DEFAULT_CONFIG: Required<SwipeConfig>","SwipeCard: React.FC<SwipeCardProps>","cardStyle: 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 } 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};\n\nexport const useSwipe = (\n callbacks: SwipeCallbacks = {},\n config: SwipeConfig = {}\n): UseSwipeReturn => {\n const mergedConfig = { ...DEFAULT_CONFIG, ...config };\n const {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n } = mergedConfig;\n\n const ref = useRef<HTMLDivElement>(null);\n const animationFrameRef = useRef<number | null>(null);\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 const [transform, setTransform] = useState('translate3d(0px, 0px, 0px) rotate(0deg)');\n const [opacity, setOpacity] = useState(1);\n const [transition, setTransition] = useState('none');\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 setTransition('none');\n \n if (callbacks.onSwipeStart) {\n callbacks.onSwipeStart();\n }\n },\n [callbacks]\n );\n\n // Handle swipe move\n const handleSwipeMove = useCallback(\n (event: PointerEvent | TouchEvent | MouseEvent) => {\n if (!gestureState.isDragging) return;\n\n const coords = getEventCoordinates(event as any);\n const deltaX = coords.x - gestureState.startX;\n const deltaY = coords.y - gestureState.startY;\n\n // Update gesture state\n setGestureState((prev: GestureState) => ({\n ...prev,\n currentX: coords.x,\n currentY: coords.y,\n deltaX,\n deltaY,\n }));\n\n // Use RAF for smooth updates\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 = calculateOpacity(deltaX, deltaY, threshold);\n\n setTransform(\n `translate3d(${deltaX}px, ${deltaY}px, 0px) rotate(${rotation}deg)`\n );\n setOpacity(newOpacity);\n });\n },\n [gestureState.isDragging, gestureState.startX, gestureState.startY, enableRotation, maxRotation, threshold]\n );\n\n // Handle swipe end\n const handleSwipeEnd = useCallback(() => {\n if (!gestureState.isDragging) return;\n\n const { deltaX, deltaY, startTime } = gestureState;\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 =\n distance >= threshold || velocity >= velocityThreshold;\n\n if (shouldSwipe && direction) {\n // Execute swipe\n setTransition(`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\n setTransform(\n `translate3d(${exitX}px, ${exitY}px, 0px) rotate(${rotation}deg)`\n );\n setOpacity(0);\n\n // Trigger callback after animation\n setTimeout(() => {\n switch (direction) {\n case 'left':\n callbacks.onSwipeLeft?.();\n break;\n case 'right':\n callbacks.onSwipeRight?.();\n break;\n case 'up':\n callbacks.onSwipeUp?.();\n break;\n case 'down':\n callbacks.onSwipeDown?.();\n break;\n }\n \n if (callbacks.onSwipeEnd) {\n callbacks.onSwipeEnd();\n }\n\n // Reset after callback\n setTimeout(() => {\n setTransition('none');\n setTransform('translate3d(0px, 0px, 0px) rotate(0deg)');\n setOpacity(1);\n }, 50);\n }, exitDuration);\n } else {\n // Spring back\n setTransition(\n `transform ${returnDuration}ms cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity ${returnDuration}ms ease-out`\n );\n setTransform('translate3d(0px, 0px, 0px) rotate(0deg)');\n setOpacity(1);\n\n if (callbacks.onSwipeEnd) {\n callbacks.onSwipeEnd();\n }\n }\n\n setGestureState((prev: GestureState) => ({ ...prev, isDragging: false }));\n }, [\n gestureState,\n threshold,\n velocityThreshold,\n exitDuration,\n returnDuration,\n enableRotation,\n maxRotation,\n callbacks,\n ]);\n\n // Attach event listeners\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n\n const handlePointerDown = (e: PointerEvent) => {\n handleSwipeStart(e);\n };\n\n const handlePointerMove = (e: PointerEvent) => {\n if (gestureState.isDragging) {\n handleSwipeMove(e);\n }\n };\n\n const handlePointerUp = () => {\n handleSwipeEnd();\n };\n\n element.addEventListener('pointerdown', handlePointerDown);\n\n if (gestureState.isDragging) {\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n document.addEventListener('pointercancel', handlePointerUp);\n }\n\n return () => {\n element.removeEventListener('pointerdown', handlePointerDown);\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n document.removeEventListener('pointercancel', handlePointerUp);\n \n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, [gestureState.isDragging, handleSwipeStart, handleSwipeMove, handleSwipeEnd]);\n\n return {\n ref,\n transform,\n opacity,\n transition,\n isDragging: gestureState.isDragging,\n };\n};\n\n","import React, { CSSProperties } from 'react';\nimport { useSwipe } from '../hooks/useSwipe';\nimport { SwipeCardProps } 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 * >\n * <div>Your content here</div>\n * </SwipeCard>\n * ```\n */\nexport const SwipeCard: 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}) => {\n const callbacks = {\n onSwipeLeft,\n onSwipeRight,\n onSwipeUp,\n onSwipeDown,\n onSwipeStart,\n onSwipeEnd,\n };\n\n const config = {\n threshold,\n velocityThreshold,\n maxRotation,\n exitDuration,\n returnDuration,\n enableRotation,\n };\n\n const { ref, transform, opacity, transition, isDragging } = useSwipe(callbacks, config);\n\n const cardStyle: CSSProperties = {\n ...style,\n transform,\n opacity,\n transition,\n touchAction: 'none', // Prevent default touch behaviors\n userSelect: 'none', // Prevent text selection during drag\n WebkitUserSelect: 'none',\n cursor: isDragging ? 'grabbing' : 'grab',\n willChange: isDragging ? 'transform, opacity' : 'auto',\n position: 'relative',\n };\n\n return (\n <div\n ref={ref}\n className={`swipeon-card ${className}`}\n style={cardStyle}\n >\n {children}\n </div>\n );\n};\n\nexport default SwipeCard;\n\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "swipeon-react",
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A high-performance React swipe card library with smooth animations and multi-directional swipe support",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.esm.js",
|
|
17
|
+
"require": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsdown",
|
|
23
|
+
"dev": "tsdown --watch",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"clean": "rm -rf dist",
|
|
26
|
+
"prebuild": "npm run clean",
|
|
27
|
+
"publish:alpha": "npm version prerelease --preid=alpha && npm run build && npm publish --tag alpha",
|
|
28
|
+
"publish:rc": "npm version prerelease --preid=rc && npm run build && npm publish --tag next",
|
|
29
|
+
"publish:stable": "npm version patch && npm run build && npm publish --tag latest"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"react",
|
|
33
|
+
"swipe",
|
|
34
|
+
"swipe-cards",
|
|
35
|
+
"tinder",
|
|
36
|
+
"gestures",
|
|
37
|
+
"touch",
|
|
38
|
+
"animation",
|
|
39
|
+
"typescript"
|
|
40
|
+
],
|
|
41
|
+
"author": "react-forge",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/react-forge/swipeon-react"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=16.8.0",
|
|
49
|
+
"react-dom": ">=16.8.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "^18.2.45",
|
|
53
|
+
"@types/react-dom": "^18.2.18",
|
|
54
|
+
"react": "^18.2.0",
|
|
55
|
+
"react-dom": "^18.2.0",
|
|
56
|
+
"tsdown": "0.18.2",
|
|
57
|
+
"tslib": "^2.6.2",
|
|
58
|
+
"typescript": "^5.3.3"
|
|
59
|
+
}
|
|
60
|
+
}
|