@zoompinch/react 0.0.4
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 +578 -0
- package/dist/react.css +1 -0
- package/dist/zoompinch-react.es.js +408 -0
- package/dist/zoompinch-react.umd.js +22 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# @zoompinch/react
|
|
2
|
+
|
|
3
|
+
React bindings for [@zoompinch/core](https://github.com/ElyaConrad/zoompinch) - Apply a pinch-and-zoom experience that feels native and communicates the transform reactively and lets you project any layer on top of the transformed canvas.
|
|
4
|
+
|
|
5
|
+
**Play with the demo:** [https://zoompinch.pages.dev](https://zoompinch.pages.dev)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
### Mathematical correct pinch on touch
|
|
10
|
+
|
|
11
|
+
Unlike other libraries, _Zoompinch_ does not just use the center point between two fingers as projection center. The fingers get correctly projected on the virtual canvas. This makes pinching on touch devices feel native-like.
|
|
12
|
+
|
|
13
|
+
### Touch, Wheel, Mouse and Trackpad Gestures!
|
|
14
|
+
|
|
15
|
+
Aside from touch, mouse and wheel events, **gesture events** (Safari Desktop) are supported as well! Try it out on the [demo](https://zoompinch.pages.dev)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @zoompinch/react
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Complete Example
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import React, { useRef, useState } from 'react';
|
|
27
|
+
import { Zoompinch, ZoompinchRef } from '@zoompinch/react';
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
const zoompinchRef = useRef<ZoompinchRef>(null);
|
|
31
|
+
const [transform, setTransform] = useState({
|
|
32
|
+
translateX: 0,
|
|
33
|
+
translateY: 0,
|
|
34
|
+
scale: 1,
|
|
35
|
+
rotate: 0
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function handleInit() {
|
|
39
|
+
// Center canvas on initialization
|
|
40
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5], 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleTransformChange(newTransform) {
|
|
44
|
+
console.log('Transform updated:', newTransform);
|
|
45
|
+
setTransform(newTransform);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleClick(event: React.MouseEvent) {
|
|
49
|
+
if (!zoompinchRef.current) return;
|
|
50
|
+
const [x, y] = zoompinchRef.current.normalizeClientCoords(event.clientX, event.clientY);
|
|
51
|
+
console.log('Clicked at canvas position:', x, y);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Zoompinch
|
|
56
|
+
ref={zoompinchRef}
|
|
57
|
+
style={{ width: '800px', height: '600px', border: '1px solid #ccc' }}
|
|
58
|
+
transform={transform}
|
|
59
|
+
onTransformChange={handleTransformChange}
|
|
60
|
+
offset={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
|
61
|
+
minScale={0.5}
|
|
62
|
+
maxScale={4}
|
|
63
|
+
clampBounds={false}
|
|
64
|
+
rotation={true}
|
|
65
|
+
mouse={true}
|
|
66
|
+
wheel={true}
|
|
67
|
+
touch={true}
|
|
68
|
+
gesture={true}
|
|
69
|
+
onInit={handleInit}
|
|
70
|
+
onClick={handleClick}
|
|
71
|
+
>
|
|
72
|
+
<img
|
|
73
|
+
width="1536"
|
|
74
|
+
height="2048"
|
|
75
|
+
src="https://imagedelivery.net/mudX-CmAqIANL8bxoNCToA/489df5b2-38ce-46e7-32e0-d50170e8d800/public"
|
|
76
|
+
draggable={false}
|
|
77
|
+
style={{ userSelect: 'none' }}
|
|
78
|
+
/>
|
|
79
|
+
</Zoompinch>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default App;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### With Matrix Overlay
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<Zoompinch
|
|
90
|
+
ref={zoompinchRef}
|
|
91
|
+
style={{ width: '800px', height: '600px' }}
|
|
92
|
+
onInit={handleInit}
|
|
93
|
+
matrix={({ composePoint, normalizeClientCoords, canvasWidth, canvasHeight }) => (
|
|
94
|
+
<svg width="100%" height="100%">
|
|
95
|
+
{/* Center marker */}
|
|
96
|
+
<circle
|
|
97
|
+
cx={composePoint(canvasWidth / 2, canvasHeight / 2)[0]}
|
|
98
|
+
cy={composePoint(canvasWidth / 2, canvasHeight / 2)[1]}
|
|
99
|
+
r="8"
|
|
100
|
+
fill="red"
|
|
101
|
+
/>
|
|
102
|
+
</svg>
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
<img width="1536" height="2048" src="image.jpg" />
|
|
106
|
+
</Zoompinch>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## API Reference
|
|
112
|
+
|
|
113
|
+
### Props
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Default | Description |
|
|
116
|
+
|------|------|---------|-------------|
|
|
117
|
+
| `transform` | `Transform` | `{ translateX: 0, translateY: 0, scale: 1, rotate: 0 }` | Current transform state |
|
|
118
|
+
| `onTransformChange` | `(transform: Transform) => void` | - | Callback when transform changes |
|
|
119
|
+
| `offset` | `Offset` | `{ top: 0, right: 0, bottom: 0, left: 0 }` | Inner padding/offset within container |
|
|
120
|
+
| `minScale` | `number` | `0.5` | Minimum scale (user gestures only) |
|
|
121
|
+
| `maxScale` | `number` | `10` | Maximum scale (user gestures only) |
|
|
122
|
+
| `clampBounds` | `boolean` | `false` | Clamp panning within bounds (user gestures only) |
|
|
123
|
+
| `rotation` | `boolean` | `true` | Enable rotation gestures |
|
|
124
|
+
| `mouse` | `boolean` | `true` | Enable mouse drag |
|
|
125
|
+
| `wheel` | `boolean` | `true` | Enable wheel/trackpad |
|
|
126
|
+
| `touch` | `boolean` | `true` | Enable touch gestures |
|
|
127
|
+
| `gesture` | `boolean` | `true` | Enable Safari gesture events |
|
|
128
|
+
| `style` | `React.CSSProperties` | - | Inline styles for container |
|
|
129
|
+
| `children` | `ReactNode` | - | Canvas content |
|
|
130
|
+
| `matrix` | `ReactNode \| Function` | - | Overlay content (see Matrix Prop) |
|
|
131
|
+
|
|
132
|
+
**Note:** `minScale`, `maxScale`, `rotation`, and `clampBounds` only apply during user interaction. Programmatic changes via ref methods are unrestricted.
|
|
133
|
+
|
|
134
|
+
### Events
|
|
135
|
+
|
|
136
|
+
| Event | Payload | Description |
|
|
137
|
+
|-------|---------|-------------|
|
|
138
|
+
| `onInit` | `void` | Fired when canvas dimensions are available |
|
|
139
|
+
| `onTransformChange` | `Transform` | Fired when transform changes |
|
|
140
|
+
| `onClick` | `React.MouseEvent` | Standard click event |
|
|
141
|
+
| `onMouseDown` | `React.MouseEvent` | Standard mousedown event |
|
|
142
|
+
| `onTouchStart` | `React.TouchEvent` | Standard touchstart event |
|
|
143
|
+
| `onMouseUp` | `React.MouseEvent` | Standard mouseup event |
|
|
144
|
+
| `onTouchEnd` | `React.TouchEvent` | Standard touchend event |
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Zoompinch
|
|
148
|
+
onInit={handleInit}
|
|
149
|
+
onTransformChange={handleTransformChange}
|
|
150
|
+
onClick={handleClick}
|
|
151
|
+
>
|
|
152
|
+
{/* content */}
|
|
153
|
+
</Zoompinch>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Ref Methods
|
|
157
|
+
|
|
158
|
+
Access methods via ref:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { ZoompinchRef } from '@zoompinch/react';
|
|
162
|
+
|
|
163
|
+
const zoompinchRef = useRef<ZoompinchRef>(null);
|
|
164
|
+
|
|
165
|
+
// Call methods
|
|
166
|
+
zoompinchRef.current?.applyTransform(scale, wrapperCoords, canvasCoords, rotate?);
|
|
167
|
+
zoompinchRef.current?.normalizeClientCoords(clientX, clientY);
|
|
168
|
+
zoompinchRef.current?.composePoint(x, y);
|
|
169
|
+
zoompinchRef.current?.rotateCanvas(x, y, radians);
|
|
170
|
+
|
|
171
|
+
// Access properties
|
|
172
|
+
zoompinchRef.current?.canvasWidth;
|
|
173
|
+
zoompinchRef.current?.canvasHeight;
|
|
174
|
+
zoompinchRef.current?.zoompinchEngine; // Access core engine directly
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### `applyTransform(scale, wrapperCoords, canvasCoords, rotate?)`
|
|
178
|
+
|
|
179
|
+
Apply transform by anchoring a canvas point to a wrapper point.
|
|
180
|
+
|
|
181
|
+
**Parameters:**
|
|
182
|
+
- `scale: number` - Target scale
|
|
183
|
+
- `wrapperCoords: [number, number]` - Wrapper position (0-1, 0.5 = center)
|
|
184
|
+
- `canvasCoords: [number, number]` - Canvas position (0-1, 0.5 = center)
|
|
185
|
+
- `rotate?: number` - Optional rotation in radians
|
|
186
|
+
|
|
187
|
+
**Examples:**
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Center canvas at scale 1
|
|
191
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5]);
|
|
192
|
+
|
|
193
|
+
// Zoom to 2x, keep centered
|
|
194
|
+
zoompinchRef.current?.applyTransform(2, [0.5, 0.5], [0.5, 0.5]);
|
|
195
|
+
|
|
196
|
+
// Anchor canvas top-left to wrapper center
|
|
197
|
+
zoompinchRef.current?.applyTransform(1.5, [0.5, 0.5], [0, 0]);
|
|
198
|
+
|
|
199
|
+
// Set rotation
|
|
200
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5], Math.PI / 4);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### `normalizeClientCoords(clientX, clientY)`
|
|
204
|
+
|
|
205
|
+
Convert global client coordinates to canvas coordinates.
|
|
206
|
+
|
|
207
|
+
**Parameters:**
|
|
208
|
+
- `clientX: number` - Global X from event
|
|
209
|
+
- `clientY: number` - Global Y from event
|
|
210
|
+
|
|
211
|
+
**Returns:** `[number, number]` - Canvas coordinates in pixels
|
|
212
|
+
|
|
213
|
+
**Example:**
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
function handleClick(event: React.MouseEvent) {
|
|
217
|
+
const [x, y] = zoompinchRef.current!.normalizeClientCoords(
|
|
218
|
+
event.clientX,
|
|
219
|
+
event.clientY
|
|
220
|
+
);
|
|
221
|
+
console.log('Canvas position:', x, y);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `composePoint(x, y)`
|
|
226
|
+
|
|
227
|
+
Convert canvas coordinates to wrapper coordinates (accounts for transform).
|
|
228
|
+
|
|
229
|
+
**Parameters:**
|
|
230
|
+
- `x: number` - Canvas X in pixels
|
|
231
|
+
- `y: number` - Canvas Y in pixels
|
|
232
|
+
|
|
233
|
+
**Returns:** `[number, number]` - Wrapper coordinates in pixels
|
|
234
|
+
|
|
235
|
+
**Example:**
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// Get wrapper position for canvas center
|
|
239
|
+
const [wrapperX, wrapperY] = zoompinchRef.current!.composePoint(
|
|
240
|
+
zoompinchRef.current!.canvasWidth / 2,
|
|
241
|
+
zoompinchRef.current!.canvasHeight / 2
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `rotateCanvas(x, y, radians)`
|
|
246
|
+
|
|
247
|
+
Rotate canvas around a specific canvas point.
|
|
248
|
+
|
|
249
|
+
**Parameters:**
|
|
250
|
+
- `x: number` - Canvas X (rotation center)
|
|
251
|
+
- `y: number` - Canvas Y (rotation center)
|
|
252
|
+
- `radians: number` - Rotation angle
|
|
253
|
+
|
|
254
|
+
**Example:**
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Rotate 90° around canvas center
|
|
258
|
+
const centerX = zoompinchRef.current!.canvasWidth / 2;
|
|
259
|
+
const centerY = zoompinchRef.current!.canvasHeight / 2;
|
|
260
|
+
zoompinchRef.current?.rotateCanvas(centerX, centerY, Math.PI / 2);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Ref Properties
|
|
264
|
+
|
|
265
|
+
Access current canvas dimensions and engine:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const width = zoompinchRef.current?.canvasWidth; // number
|
|
269
|
+
const height = zoompinchRef.current?.canvasHeight; // number
|
|
270
|
+
const engine = zoompinchRef.current?.zoompinchEngine; // ZoompinchCore | null
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Matrix Prop
|
|
274
|
+
|
|
275
|
+
Render overlay elements that follow the canvas transform.
|
|
276
|
+
|
|
277
|
+
**Type:** `ReactNode | ((props: MatrixProps) => ReactNode)`
|
|
278
|
+
|
|
279
|
+
**MatrixProps:**
|
|
280
|
+
|
|
281
|
+
| Prop | Type | Description |
|
|
282
|
+
|------|------|-------------|
|
|
283
|
+
| `composePoint` | `(x: number, y: number) => [number, number]` | Canvas → Wrapper coords |
|
|
284
|
+
| `normalizeClientCoords` | `(clientX: number, clientY: number) => [number, number]` | Client → Canvas coords |
|
|
285
|
+
| `canvasWidth` | `number` | Current canvas width |
|
|
286
|
+
| `canvasHeight` | `number` | Current canvas height |
|
|
287
|
+
|
|
288
|
+
**Note:** `applyTransform` and `rotateCanvas` are NOT available in the matrix function. Use component ref instead.
|
|
289
|
+
|
|
290
|
+
**Example:**
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
<Zoompinch
|
|
294
|
+
matrix={({ composePoint, normalizeClientCoords, canvasWidth, canvasHeight }) => (
|
|
295
|
+
<svg width="100%" height="100%">
|
|
296
|
+
<circle
|
|
297
|
+
cx={composePoint(canvasWidth / 2, canvasHeight / 2)[0]}
|
|
298
|
+
cy={composePoint(canvasWidth / 2, canvasHeight / 2)[1]}
|
|
299
|
+
r="8"
|
|
300
|
+
fill="red"
|
|
301
|
+
/>
|
|
302
|
+
</svg>
|
|
303
|
+
)}
|
|
304
|
+
>
|
|
305
|
+
<img width="1920" height="1080" src="image.jpg" />
|
|
306
|
+
</Zoompinch>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Coordinate Systems
|
|
310
|
+
|
|
311
|
+
### 1. Canvas Coordinates (Absolute)
|
|
312
|
+
|
|
313
|
+
Absolute pixels within canvas content.
|
|
314
|
+
- Origin: `(0, 0)` at top-left
|
|
315
|
+
- Range: `0` to `canvasWidth`, `0` to `canvasHeight`
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const [canvasX, canvasY] = normalizeClientCoords(event.clientX, event.clientY);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 2. Wrapper Coordinates (Absolute)
|
|
322
|
+
|
|
323
|
+
Absolute pixels within viewport/wrapper.
|
|
324
|
+
- Origin: `(0, 0)` at top-left (accounting for offset)
|
|
325
|
+
- Range: `0` to `wrapperWidth`, `0` to `wrapperHeight`
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
const [wrapperX, wrapperY] = composePoint(canvasX, canvasY);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 3. Relative Coordinates (0-1)
|
|
332
|
+
|
|
333
|
+
Normalized coordinates for `applyTransform`.
|
|
334
|
+
- Range: `0.0` to `1.0`
|
|
335
|
+
- `0.5` = center, `1.0` = bottom-right
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
[0, 0] // top-left
|
|
339
|
+
[0.5, 0.5] // center
|
|
340
|
+
[1, 1] // bottom-right
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Conversion Flow:**
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
Client Coords → normalizeClientCoords() → Canvas Coords → composePoint() → Wrapper Coords
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Complete Playground Example
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import React, { useRef, useState } from 'react';
|
|
353
|
+
import { Zoompinch, ZoompinchRef } from '@zoompinch/react';
|
|
354
|
+
|
|
355
|
+
function App() {
|
|
356
|
+
const zoompinchRef = useRef<ZoompinchRef>(null);
|
|
357
|
+
const [transform, setTransform] = useState({
|
|
358
|
+
translateX: 0,
|
|
359
|
+
translateY: 0,
|
|
360
|
+
scale: 1,
|
|
361
|
+
rotate: 0
|
|
362
|
+
});
|
|
363
|
+
const [clickPoint, setClickPoint] = useState<[number, number] | null>(null);
|
|
364
|
+
|
|
365
|
+
function handleInit() {
|
|
366
|
+
// Center canvas on initialization
|
|
367
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5], 0);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function handleClick(event: React.MouseEvent) {
|
|
371
|
+
if (!zoompinchRef.current) return;
|
|
372
|
+
const [x, y] = zoompinchRef.current.normalizeClientCoords(
|
|
373
|
+
event.clientX,
|
|
374
|
+
event.clientY
|
|
375
|
+
);
|
|
376
|
+
setClickPoint([x, y]);
|
|
377
|
+
console.log('Clicked at:', x, y);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function handleZoomIn() {
|
|
381
|
+
if (!zoompinchRef.current) return;
|
|
382
|
+
const newScale = Math.min(transform.scale * 1.5, 4);
|
|
383
|
+
zoompinchRef.current.applyTransform(newScale, [0.5, 0.5], [0.5, 0.5]);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function handleZoomOut() {
|
|
387
|
+
if (!zoompinchRef.current) return;
|
|
388
|
+
const newScale = Math.max(transform.scale / 1.5, 0.5);
|
|
389
|
+
zoompinchRef.current.applyTransform(newScale, [0.5, 0.5], [0.5, 0.5]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function handleReset() {
|
|
393
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5], 0);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function handleRotate() {
|
|
397
|
+
if (!zoompinchRef.current) return;
|
|
398
|
+
const centerX = zoompinchRef.current.canvasWidth / 2;
|
|
399
|
+
const centerY = zoompinchRef.current.canvasHeight / 2;
|
|
400
|
+
zoompinchRef.current.rotateCanvas(centerX, centerY, Math.PI / 4);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div style={{ padding: '20px' }}>
|
|
405
|
+
<div style={{ marginBottom: '10px' }}>
|
|
406
|
+
<button onClick={handleZoomIn}>Zoom In</button>
|
|
407
|
+
<button onClick={handleZoomOut}>Zoom Out</button>
|
|
408
|
+
<button onClick={handleReset}>Reset</button>
|
|
409
|
+
<button onClick={handleRotate}>Rotate 45°</button>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
<div style={{ marginBottom: '10px' }}>
|
|
413
|
+
<strong>Transform:</strong> Scale: {transform.scale.toFixed(2)},
|
|
414
|
+
Rotate: {(transform.rotate * 180 / Math.PI).toFixed(0)}°
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
{clickPoint && (
|
|
418
|
+
<div style={{ marginBottom: '10px' }}>
|
|
419
|
+
<strong>Last click:</strong> ({clickPoint[0].toFixed(0)}, {clickPoint[1].toFixed(0)})
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
|
|
423
|
+
<Zoompinch
|
|
424
|
+
ref={zoompinchRef}
|
|
425
|
+
style={{
|
|
426
|
+
width: '800px',
|
|
427
|
+
height: '600px',
|
|
428
|
+
border: '2px solid #333',
|
|
429
|
+
borderRadius: '8px'
|
|
430
|
+
}}
|
|
431
|
+
transform={transform}
|
|
432
|
+
onTransformChange={setTransform}
|
|
433
|
+
offset={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
|
434
|
+
minScale={0.5}
|
|
435
|
+
maxScale={4}
|
|
436
|
+
clampBounds={false}
|
|
437
|
+
rotation={true}
|
|
438
|
+
mouse={true}
|
|
439
|
+
wheel={true}
|
|
440
|
+
touch={true}
|
|
441
|
+
gesture={true}
|
|
442
|
+
onInit={handleInit}
|
|
443
|
+
onClick={handleClick}
|
|
444
|
+
matrix={({ composePoint, canvasWidth, canvasHeight }) => (
|
|
445
|
+
<svg width="100%" height="100%">
|
|
446
|
+
{/* Center marker */}
|
|
447
|
+
<circle
|
|
448
|
+
cx={composePoint(canvasWidth / 2, canvasHeight / 2)[0]}
|
|
449
|
+
cy={composePoint(canvasWidth / 2, canvasHeight / 2)[1]}
|
|
450
|
+
r="8"
|
|
451
|
+
fill="red"
|
|
452
|
+
/>
|
|
453
|
+
|
|
454
|
+
{/* Click point marker */}
|
|
455
|
+
{clickPoint && (
|
|
456
|
+
<circle
|
|
457
|
+
cx={composePoint(clickPoint[0], clickPoint[1])[0]}
|
|
458
|
+
cy={composePoint(clickPoint[0], clickPoint[1])[1]}
|
|
459
|
+
r="5"
|
|
460
|
+
fill="blue"
|
|
461
|
+
/>
|
|
462
|
+
)}
|
|
463
|
+
</svg>
|
|
464
|
+
)}
|
|
465
|
+
>
|
|
466
|
+
<img
|
|
467
|
+
width="1536"
|
|
468
|
+
height="2048"
|
|
469
|
+
src="https://imagedelivery.net/mudX-CmAqIANL8bxoNCToA/489df5b2-38ce-46e7-32e0-d50170e8d800/public"
|
|
470
|
+
draggable={false}
|
|
471
|
+
style={{ userSelect: 'none' }}
|
|
472
|
+
/>
|
|
473
|
+
</Zoompinch>
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export default App;
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Best Practices
|
|
482
|
+
|
|
483
|
+
1. **Always specify image dimensions** to avoid layout shifts:
|
|
484
|
+
```tsx
|
|
485
|
+
<img width="1920" height="1080" src="image.jpg" />
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
2. **Center content on init:**
|
|
489
|
+
```typescript
|
|
490
|
+
function handleInit() {
|
|
491
|
+
zoompinchRef.current?.applyTransform(1, [0.5, 0.5], [0.5, 0.5]);
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
3. **Prevent image drag:**
|
|
496
|
+
```tsx
|
|
497
|
+
<img
|
|
498
|
+
src="image.jpg"
|
|
499
|
+
draggable={false}
|
|
500
|
+
style={{ userSelect: 'none' }}
|
|
501
|
+
/>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
4. **Use controlled transform state:**
|
|
505
|
+
```tsx
|
|
506
|
+
const [transform, setTransform] = useState({
|
|
507
|
+
translateX: 0, translateY: 0, scale: 1, rotate: 0
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
<Zoompinch
|
|
511
|
+
transform={transform}
|
|
512
|
+
onTransformChange={setTransform}
|
|
513
|
+
/>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
5. **Enable clamp bounds for better UX:**
|
|
517
|
+
```tsx
|
|
518
|
+
<Zoompinch clampBounds={true} minScale={0.5} maxScale={4} />
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Styling
|
|
522
|
+
|
|
523
|
+
Minimal base styles are applied. Customize via style prop:
|
|
524
|
+
|
|
525
|
+
```tsx
|
|
526
|
+
<Zoompinch
|
|
527
|
+
style={{
|
|
528
|
+
width: '100%',
|
|
529
|
+
height: '600px',
|
|
530
|
+
border: '1px solid #ccc',
|
|
531
|
+
borderRadius: '8px'
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
{/* content */}
|
|
535
|
+
</Zoompinch>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Internal CSS classes:**
|
|
539
|
+
|
|
540
|
+
```css
|
|
541
|
+
.zoompinch /* Container */
|
|
542
|
+
.zoompinch > .canvas /* Canvas wrapper */
|
|
543
|
+
.zoompinch > .matrix /* Matrix overlay */
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## TypeScript Support
|
|
547
|
+
|
|
548
|
+
Full TypeScript support with exported types:
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
import {
|
|
552
|
+
Zoompinch,
|
|
553
|
+
ZoompinchRef,
|
|
554
|
+
ZoompinchProps
|
|
555
|
+
} from '@zoompinch/react';
|
|
556
|
+
|
|
557
|
+
import type { Transform } from '@zoompinch/core';
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Browser Support
|
|
561
|
+
|
|
562
|
+
- ✅ Chrome/Edge (latest)
|
|
563
|
+
- ✅ Firefox (latest)
|
|
564
|
+
- ✅ Safari (latest, including iOS)
|
|
565
|
+
- ✅ Mobile browsers (iOS Safari, Chrome Mobile)
|
|
566
|
+
|
|
567
|
+
## License
|
|
568
|
+
|
|
569
|
+
MIT
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
## Related
|
|
573
|
+
|
|
574
|
+
- [@zoompinch/core](https://www.npmjs.com/package/@zoompinch/core) - Core engine
|
|
575
|
+
- [@zoompinch/vue](https://www.npmjs.com/package/@zoompinch/vue) - Vue 3
|
|
576
|
+
- [@zoompinch/elements](https://www.npmjs.com/package/@zoompinch/elements) - Web Components
|
|
577
|
+
|
|
578
|
+
Built with ❤️ by Elya Maurice Conrad
|
package/dist/react.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.zoompinch{touch-action:none;overflow:hidden;position:relative;border:2px solid #ccc}.zoompinch>.canvas{position:absolute}.zoompinch>.matrix{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import te, { forwardRef as ne, useRef as F, useState as ae, useImperativeHandle as oe, useEffect as E } from "react";
|
|
2
|
+
import { Zoompinch as ue } from "@zoompinch/core";
|
|
3
|
+
var I = { exports: {} }, M = {};
|
|
4
|
+
/**
|
|
5
|
+
* @license React
|
|
6
|
+
* react-jsx-runtime.production.js
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
9
|
+
*
|
|
10
|
+
* This source code is licensed under the MIT license found in the
|
|
11
|
+
* LICENSE file in the root directory of this source tree.
|
|
12
|
+
*/
|
|
13
|
+
var Q;
|
|
14
|
+
function se() {
|
|
15
|
+
if (Q) return M;
|
|
16
|
+
Q = 1;
|
|
17
|
+
var a = Symbol.for("react.transitional.element"), m = Symbol.for("react.fragment");
|
|
18
|
+
function l(d, s, c) {
|
|
19
|
+
var i = null;
|
|
20
|
+
if (c !== void 0 && (i = "" + c), s.key !== void 0 && (i = "" + s.key), "key" in s) {
|
|
21
|
+
c = {};
|
|
22
|
+
for (var p in s)
|
|
23
|
+
p !== "key" && (c[p] = s[p]);
|
|
24
|
+
} else c = s;
|
|
25
|
+
return s = c.ref, {
|
|
26
|
+
$$typeof: a,
|
|
27
|
+
type: d,
|
|
28
|
+
key: i,
|
|
29
|
+
ref: s !== void 0 ? s : null,
|
|
30
|
+
props: c
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return M.Fragment = m, M.jsx = l, M.jsxs = l, M;
|
|
34
|
+
}
|
|
35
|
+
var W = {};
|
|
36
|
+
/**
|
|
37
|
+
* @license React
|
|
38
|
+
* react-jsx-runtime.development.js
|
|
39
|
+
*
|
|
40
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
41
|
+
*
|
|
42
|
+
* This source code is licensed under the MIT license found in the
|
|
43
|
+
* LICENSE file in the root directory of this source tree.
|
|
44
|
+
*/
|
|
45
|
+
var K;
|
|
46
|
+
function ce() {
|
|
47
|
+
return K || (K = 1, process.env.NODE_ENV !== "production" && (function() {
|
|
48
|
+
function a(e) {
|
|
49
|
+
if (e == null) return null;
|
|
50
|
+
if (typeof e == "function")
|
|
51
|
+
return e.$$typeof === X ? null : e.displayName || e.name || null;
|
|
52
|
+
if (typeof e == "string") return e;
|
|
53
|
+
switch (e) {
|
|
54
|
+
case O:
|
|
55
|
+
return "Fragment";
|
|
56
|
+
case H:
|
|
57
|
+
return "Profiler";
|
|
58
|
+
case U:
|
|
59
|
+
return "StrictMode";
|
|
60
|
+
case t:
|
|
61
|
+
return "Suspense";
|
|
62
|
+
case f:
|
|
63
|
+
return "SuspenseList";
|
|
64
|
+
case x:
|
|
65
|
+
return "Activity";
|
|
66
|
+
}
|
|
67
|
+
if (typeof e == "object")
|
|
68
|
+
switch (typeof e.tag == "number" && console.error(
|
|
69
|
+
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
|
70
|
+
), e.$$typeof) {
|
|
71
|
+
case G:
|
|
72
|
+
return "Portal";
|
|
73
|
+
case J:
|
|
74
|
+
return e.displayName || "Context";
|
|
75
|
+
case q:
|
|
76
|
+
return (e._context.displayName || "Context") + ".Consumer";
|
|
77
|
+
case _:
|
|
78
|
+
var r = e.render;
|
|
79
|
+
return e = e.displayName, e || (e = r.displayName || r.name || "", e = e !== "" ? "ForwardRef(" + e + ")" : "ForwardRef"), e;
|
|
80
|
+
case v:
|
|
81
|
+
return r = e.displayName || null, r !== null ? r : a(e.type) || "Memo";
|
|
82
|
+
case A:
|
|
83
|
+
r = e._payload, e = e._init;
|
|
84
|
+
try {
|
|
85
|
+
return a(e(r));
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function m(e) {
|
|
92
|
+
return "" + e;
|
|
93
|
+
}
|
|
94
|
+
function l(e) {
|
|
95
|
+
try {
|
|
96
|
+
m(e);
|
|
97
|
+
var r = !1;
|
|
98
|
+
} catch {
|
|
99
|
+
r = !0;
|
|
100
|
+
}
|
|
101
|
+
if (r) {
|
|
102
|
+
r = console;
|
|
103
|
+
var n = r.error, o = typeof Symbol == "function" && Symbol.toStringTag && e[Symbol.toStringTag] || e.constructor.name || "Object";
|
|
104
|
+
return n.call(
|
|
105
|
+
r,
|
|
106
|
+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
|
107
|
+
o
|
|
108
|
+
), m(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function d(e) {
|
|
112
|
+
if (e === O) return "<>";
|
|
113
|
+
if (typeof e == "object" && e !== null && e.$$typeof === A)
|
|
114
|
+
return "<...>";
|
|
115
|
+
try {
|
|
116
|
+
var r = a(e);
|
|
117
|
+
return r ? "<" + r + ">" : "<...>";
|
|
118
|
+
} catch {
|
|
119
|
+
return "<...>";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function s() {
|
|
123
|
+
var e = g.A;
|
|
124
|
+
return e === null ? null : e.getOwner();
|
|
125
|
+
}
|
|
126
|
+
function c() {
|
|
127
|
+
return Error("react-stack-top-frame");
|
|
128
|
+
}
|
|
129
|
+
function i(e) {
|
|
130
|
+
if (S.call(e, "key")) {
|
|
131
|
+
var r = Object.getOwnPropertyDescriptor(e, "key").get;
|
|
132
|
+
if (r && r.isReactWarning) return !1;
|
|
133
|
+
}
|
|
134
|
+
return e.key !== void 0;
|
|
135
|
+
}
|
|
136
|
+
function p(e, r) {
|
|
137
|
+
function n() {
|
|
138
|
+
j || (j = !0, console.error(
|
|
139
|
+
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
|
140
|
+
r
|
|
141
|
+
));
|
|
142
|
+
}
|
|
143
|
+
n.isReactWarning = !0, Object.defineProperty(e, "key", {
|
|
144
|
+
get: n,
|
|
145
|
+
configurable: !0
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function w() {
|
|
149
|
+
var e = a(this.type);
|
|
150
|
+
return L[e] || (L[e] = !0, console.error(
|
|
151
|
+
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
|
152
|
+
)), e = this.props.ref, e !== void 0 ? e : null;
|
|
153
|
+
}
|
|
154
|
+
function T(e, r, n, o, R, V) {
|
|
155
|
+
var u = n.ref;
|
|
156
|
+
return e = {
|
|
157
|
+
$$typeof: y,
|
|
158
|
+
type: e,
|
|
159
|
+
key: r,
|
|
160
|
+
props: n,
|
|
161
|
+
_owner: o
|
|
162
|
+
}, (u !== void 0 ? u : null) !== null ? Object.defineProperty(e, "ref", {
|
|
163
|
+
enumerable: !1,
|
|
164
|
+
get: w
|
|
165
|
+
}) : Object.defineProperty(e, "ref", { enumerable: !1, value: null }), e._store = {}, Object.defineProperty(e._store, "validated", {
|
|
166
|
+
configurable: !1,
|
|
167
|
+
enumerable: !1,
|
|
168
|
+
writable: !0,
|
|
169
|
+
value: 0
|
|
170
|
+
}), Object.defineProperty(e, "_debugInfo", {
|
|
171
|
+
configurable: !1,
|
|
172
|
+
enumerable: !1,
|
|
173
|
+
writable: !0,
|
|
174
|
+
value: null
|
|
175
|
+
}), Object.defineProperty(e, "_debugStack", {
|
|
176
|
+
configurable: !1,
|
|
177
|
+
enumerable: !1,
|
|
178
|
+
writable: !0,
|
|
179
|
+
value: R
|
|
180
|
+
}), Object.defineProperty(e, "_debugTask", {
|
|
181
|
+
configurable: !1,
|
|
182
|
+
enumerable: !1,
|
|
183
|
+
writable: !0,
|
|
184
|
+
value: V
|
|
185
|
+
}), Object.freeze && (Object.freeze(e.props), Object.freeze(e)), e;
|
|
186
|
+
}
|
|
187
|
+
function $(e, r, n, o, R, V) {
|
|
188
|
+
var u = r.children;
|
|
189
|
+
if (u !== void 0)
|
|
190
|
+
if (o)
|
|
191
|
+
if (z(u)) {
|
|
192
|
+
for (o = 0; o < u.length; o++)
|
|
193
|
+
b(u[o]);
|
|
194
|
+
Object.freeze && Object.freeze(u);
|
|
195
|
+
} else
|
|
196
|
+
console.error(
|
|
197
|
+
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
|
198
|
+
);
|
|
199
|
+
else b(u);
|
|
200
|
+
if (S.call(r, "key")) {
|
|
201
|
+
u = a(e);
|
|
202
|
+
var k = Object.keys(r).filter(function(re) {
|
|
203
|
+
return re !== "key";
|
|
204
|
+
});
|
|
205
|
+
o = 0 < k.length ? "{key: someKey, " + k.join(": ..., ") + ": ...}" : "{key: someKey}", Y[u + o] || (k = 0 < k.length ? "{" + k.join(": ..., ") + ": ...}" : "{}", console.error(
|
|
206
|
+
`A props object containing a "key" prop is being spread into JSX:
|
|
207
|
+
let props = %s;
|
|
208
|
+
<%s {...props} />
|
|
209
|
+
React keys must be passed directly to JSX without using spread:
|
|
210
|
+
let props = %s;
|
|
211
|
+
<%s key={someKey} {...props} />`,
|
|
212
|
+
o,
|
|
213
|
+
u,
|
|
214
|
+
k,
|
|
215
|
+
u
|
|
216
|
+
), Y[u + o] = !0);
|
|
217
|
+
}
|
|
218
|
+
if (u = null, n !== void 0 && (l(n), u = "" + n), i(r) && (l(r.key), u = "" + r.key), "key" in r) {
|
|
219
|
+
n = {};
|
|
220
|
+
for (var Z in r)
|
|
221
|
+
Z !== "key" && (n[Z] = r[Z]);
|
|
222
|
+
} else n = r;
|
|
223
|
+
return u && p(
|
|
224
|
+
n,
|
|
225
|
+
typeof e == "function" ? e.displayName || e.name || "Unknown" : e
|
|
226
|
+
), T(
|
|
227
|
+
e,
|
|
228
|
+
u,
|
|
229
|
+
n,
|
|
230
|
+
s(),
|
|
231
|
+
R,
|
|
232
|
+
V
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
function b(e) {
|
|
236
|
+
D(e) ? e._store && (e._store.validated = 1) : typeof e == "object" && e !== null && e.$$typeof === A && (e._payload.status === "fulfilled" ? D(e._payload.value) && e._payload.value._store && (e._payload.value._store.validated = 1) : e._store && (e._store.validated = 1));
|
|
237
|
+
}
|
|
238
|
+
function D(e) {
|
|
239
|
+
return typeof e == "object" && e !== null && e.$$typeof === y;
|
|
240
|
+
}
|
|
241
|
+
var h = te, y = Symbol.for("react.transitional.element"), G = Symbol.for("react.portal"), O = Symbol.for("react.fragment"), U = Symbol.for("react.strict_mode"), H = Symbol.for("react.profiler"), q = Symbol.for("react.consumer"), J = Symbol.for("react.context"), _ = Symbol.for("react.forward_ref"), t = Symbol.for("react.suspense"), f = Symbol.for("react.suspense_list"), v = Symbol.for("react.memo"), A = Symbol.for("react.lazy"), x = Symbol.for("react.activity"), X = Symbol.for("react.client.reference"), g = h.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, S = Object.prototype.hasOwnProperty, z = Array.isArray, P = console.createTask ? console.createTask : function() {
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
244
|
+
h = {
|
|
245
|
+
react_stack_bottom_frame: function(e) {
|
|
246
|
+
return e();
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var j, L = {}, N = h.react_stack_bottom_frame.bind(
|
|
250
|
+
h,
|
|
251
|
+
c
|
|
252
|
+
)(), C = P(d(c)), Y = {};
|
|
253
|
+
W.Fragment = O, W.jsx = function(e, r, n) {
|
|
254
|
+
var o = 1e4 > g.recentlyCreatedOwnerStacks++;
|
|
255
|
+
return $(
|
|
256
|
+
e,
|
|
257
|
+
r,
|
|
258
|
+
n,
|
|
259
|
+
!1,
|
|
260
|
+
o ? Error("react-stack-top-frame") : N,
|
|
261
|
+
o ? P(d(e)) : C
|
|
262
|
+
);
|
|
263
|
+
}, W.jsxs = function(e, r, n) {
|
|
264
|
+
var o = 1e4 > g.recentlyCreatedOwnerStacks++;
|
|
265
|
+
return $(
|
|
266
|
+
e,
|
|
267
|
+
r,
|
|
268
|
+
n,
|
|
269
|
+
!0,
|
|
270
|
+
o ? Error("react-stack-top-frame") : N,
|
|
271
|
+
o ? P(d(e)) : C
|
|
272
|
+
);
|
|
273
|
+
};
|
|
274
|
+
})()), W;
|
|
275
|
+
}
|
|
276
|
+
var ee;
|
|
277
|
+
function ie() {
|
|
278
|
+
return ee || (ee = 1, process.env.NODE_ENV === "production" ? I.exports = se() : I.exports = ce()), I.exports;
|
|
279
|
+
}
|
|
280
|
+
var B = ie();
|
|
281
|
+
const le = ne(({
|
|
282
|
+
transform: a = { translateX: 0, translateY: 0, scale: 1, rotate: 0 },
|
|
283
|
+
offset: m = { left: 0, top: 0, right: 0, bottom: 0 },
|
|
284
|
+
minScale: l = 0.5,
|
|
285
|
+
maxScale: d = 10,
|
|
286
|
+
clampBounds: s = !1,
|
|
287
|
+
rotation: c = !0,
|
|
288
|
+
mouse: i = !0,
|
|
289
|
+
wheel: p = !0,
|
|
290
|
+
touch: w = !0,
|
|
291
|
+
gesture: T = !0,
|
|
292
|
+
children: $,
|
|
293
|
+
matrix: b,
|
|
294
|
+
style: D,
|
|
295
|
+
onTransformChange: h,
|
|
296
|
+
onInit: y,
|
|
297
|
+
onClick: G,
|
|
298
|
+
onMouseDown: O,
|
|
299
|
+
onTouchStart: U,
|
|
300
|
+
onMouseup: H,
|
|
301
|
+
onTouchend: q
|
|
302
|
+
}, J) => {
|
|
303
|
+
const _ = F(null), t = F(null), f = F(!1), [v, A] = ae({
|
|
304
|
+
composePoint: (r, n) => [0, 0],
|
|
305
|
+
canvasWidth: 0,
|
|
306
|
+
canvasHeight: 0
|
|
307
|
+
}), x = F((r, n) => t.current ? t.current.normalizeClientCoords(r, n) : [0, 0]);
|
|
308
|
+
oe(J, () => ({
|
|
309
|
+
applyTransform: (r, n, o, R) => {
|
|
310
|
+
t.current && (R !== void 0 && (t.current.rotate = R), t.current.applyTransform(r, n, o));
|
|
311
|
+
},
|
|
312
|
+
composePoint: (r, n) => v.composePoint ? v.composePoint(r, n) : [0, 0],
|
|
313
|
+
normalizeClientCoords: (r, n) => x.current ? x.current(r, n) : [0, 0],
|
|
314
|
+
rotateCanvas: (r, n, o) => {
|
|
315
|
+
t.current && t.current.rotateCanvas(r, n, o);
|
|
316
|
+
},
|
|
317
|
+
zoompinchEngine: t.current,
|
|
318
|
+
canvasWidth: v.canvasWidth,
|
|
319
|
+
canvasHeight: v.canvasHeight
|
|
320
|
+
}));
|
|
321
|
+
function X() {
|
|
322
|
+
var r, n;
|
|
323
|
+
t.current && A({
|
|
324
|
+
composePoint: t.current.composePoint.bind(t.current),
|
|
325
|
+
canvasWidth: ((r = t.current.canvasBounds) == null ? void 0 : r.width) ?? 0,
|
|
326
|
+
canvasHeight: ((n = t.current.canvasBounds) == null ? void 0 : n.height) ?? 0
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
E(() => {
|
|
330
|
+
!_.current || t.current || (t.current = new ue(_.current, m, a.translateX, a.translateY, a.scale, a.rotate, l, d, s, c), console.log("Zoompinch initialisiert mit Props!"), t.current.addEventListener("update", () => {
|
|
331
|
+
if (!t.current) return;
|
|
332
|
+
const r = {
|
|
333
|
+
translateX: t.current.translateX,
|
|
334
|
+
translateY: t.current.translateY,
|
|
335
|
+
scale: t.current.scale,
|
|
336
|
+
rotate: t.current.rotate
|
|
337
|
+
};
|
|
338
|
+
h && (r.translateX !== a.translateX || r.translateY !== a.translateY || r.scale !== a.scale || r.rotate !== a.rotate) && h(r), X();
|
|
339
|
+
}), t.current.addEventListener("load", () => {
|
|
340
|
+
t.current && (f.current = !0, y && y(), X());
|
|
341
|
+
}));
|
|
342
|
+
}, []), E(() => {
|
|
343
|
+
!t.current || !f.current || (t.current.translateX !== a.translateX || t.current.translateY !== a.translateY || t.current.scale !== a.scale || t.current.rotate !== a.rotate) && (t.current.translateX = a.translateX, t.current.translateY = a.translateY, t.current.scale = a.scale, t.current.rotate = a.rotate, t.current.update());
|
|
344
|
+
}, [a]), E(() => {
|
|
345
|
+
!t.current || !f.current || (t.current.offset = m, t.current.update());
|
|
346
|
+
}, [m]), E(() => {
|
|
347
|
+
!t.current || !f.current || (t.current.minScale = l, t.current.update());
|
|
348
|
+
}, [l]), E(() => {
|
|
349
|
+
!t.current || !f.current || (t.current.maxScale = d, t.current.update());
|
|
350
|
+
}, [d]), E(() => {
|
|
351
|
+
!t.current || !f.current || (t.current.clampBounds = s, t.current.setTranslateFromUserGesture(t.current.translateX, t.current.translateY), t.current.update());
|
|
352
|
+
}, [s]), E(() => {
|
|
353
|
+
!t.current || !f.current || (t.current.rotation = c, t.current.update());
|
|
354
|
+
}, [c]);
|
|
355
|
+
const g = (r) => {
|
|
356
|
+
!t.current || !p || (r.preventDefault(), t.current.handleWheel(r));
|
|
357
|
+
}, S = (r) => {
|
|
358
|
+
!t.current || !T || t.current.handleGesturestart(r);
|
|
359
|
+
}, z = (r) => {
|
|
360
|
+
!t.current || !T || t.current.handleGesturechange(r);
|
|
361
|
+
}, P = (r) => {
|
|
362
|
+
!t.current || !T || t.current.handleGestureend(r);
|
|
363
|
+
}, j = (r) => {
|
|
364
|
+
!t.current || !i || t.current.handleMousedown(r);
|
|
365
|
+
}, L = (r) => {
|
|
366
|
+
!t.current || !i || t.current.handleMousemove(r);
|
|
367
|
+
}, N = (r) => {
|
|
368
|
+
!t.current || !i || t.current.handleMouseup(r);
|
|
369
|
+
}, C = (r) => {
|
|
370
|
+
!t.current || !w || t.current.handleTouchstart(r);
|
|
371
|
+
}, Y = (r) => {
|
|
372
|
+
!t.current || !w || t.current.handleTouchmove(r);
|
|
373
|
+
}, e = (r) => {
|
|
374
|
+
!t.current || !w || t.current.handleTouchend(r);
|
|
375
|
+
};
|
|
376
|
+
return E(() => {
|
|
377
|
+
if (!_.current) return;
|
|
378
|
+
const r = _.current;
|
|
379
|
+
return window.addEventListener("gesturechange", z), window.addEventListener("gestureend", P), window.addEventListener("mousemove", L), window.addEventListener("mouseup", N), window.addEventListener("touchmove", Y), window.addEventListener("touchend", e), r.addEventListener("wheel", g, { passive: !1 }), r.addEventListener("gesturestart", S, { passive: !1 }), r.addEventListener("mousedown", j, { passive: !1 }), r.addEventListener("touchstart", C, { passive: !1 }), () => {
|
|
380
|
+
window.removeEventListener("gesturechange", z), window.removeEventListener("gestureend", P), window.removeEventListener("mousemove", L), window.removeEventListener("mouseup", N), window.removeEventListener("touchmove", Y), window.removeEventListener("touchend", e), r.removeEventListener("wheel", g), r.removeEventListener("gesturestart", S), r.removeEventListener("mousedown", j), r.removeEventListener("touchstart", C);
|
|
381
|
+
};
|
|
382
|
+
}, [i, w, T, p]), /* @__PURE__ */ B.jsxs(
|
|
383
|
+
"div",
|
|
384
|
+
{
|
|
385
|
+
ref: _,
|
|
386
|
+
className: "zoompinch",
|
|
387
|
+
style: D,
|
|
388
|
+
onClick: G,
|
|
389
|
+
onMouseDown: O,
|
|
390
|
+
onTouchStart: U,
|
|
391
|
+
onMouseUp: H,
|
|
392
|
+
onTouchEnd: q,
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ B.jsx("div", { className: "canvas", children: $ }),
|
|
395
|
+
/* @__PURE__ */ B.jsx("div", { className: "matrix", children: typeof b == "function" ? b({
|
|
396
|
+
composePoint: v.composePoint,
|
|
397
|
+
normalizeClientCoords: x.current,
|
|
398
|
+
canvasWidth: v.canvasWidth,
|
|
399
|
+
canvasHeight: v.canvasHeight
|
|
400
|
+
}) : b })
|
|
401
|
+
]
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
le.displayName = "Zoompinch";
|
|
406
|
+
export {
|
|
407
|
+
le as Zoompinch
|
|
408
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(function(p,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("react"),require("@zoompinch/core")):typeof define=="function"&&define.amd?define(["exports","react","@zoompinch/core"],u):(p=typeof globalThis<"u"?globalThis:p||self,u(p.ZoompinchReact={},p.React,p.ZoompinchCore))})(this,(function(p,u,te){"use strict";var D={exports:{}},O={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var q;function ne(){if(q)return O;q=1;var a=Symbol.for("react.transitional.element"),E=Symbol.for("react.fragment");function f(d,c,i){var l=null;if(i!==void 0&&(l=""+i),c.key!==void 0&&(l=""+c.key),"key"in c){i={};for(var h in c)h!=="key"&&(i[h]=c[h])}else i=c;return c=i.ref,{$$typeof:a,type:d,key:l,ref:c!==void 0?c:null,props:i}}return O.Fragment=E,O.jsx=f,O.jsxs=f,O}var x={};/**
|
|
10
|
+
* @license React
|
|
11
|
+
* react-jsx-runtime.development.js
|
|
12
|
+
*
|
|
13
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
+
*
|
|
15
|
+
* This source code is licensed under the MIT license found in the
|
|
16
|
+
* LICENSE file in the root directory of this source tree.
|
|
17
|
+
*/var $;function ae(){return $||($=1,process.env.NODE_ENV!=="production"&&(function(){function a(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===I?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case S:return"Fragment";case J:return"Profiler";case Z:return"StrictMode";case t:return"Suspense";case v:return"SuspenseList";case C:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case H:return"Portal";case B:return e.displayName||"Context";case V:return(e._context.displayName||"Context")+".Consumer";case _:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case m:return r=e.displayName||null,r!==null?r:a(e.type)||"Memo";case j:r=e._payload,e=e._init;try{return a(e(r))}catch{}}return null}function E(e){return""+e}function f(e){try{E(e);var r=!1}catch{r=!0}if(r){r=console;var n=r.error,o=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return n.call(r,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",o),E(e)}}function d(e){if(e===S)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===j)return"<...>";try{var r=a(e);return r?"<"+r+">":"<...>"}catch{return"<...>"}}function c(){var e=P.A;return e===null?null:e.getOwner()}function i(){return Error("react-stack-top-frame")}function l(e){if(L.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function h(e,r){function n(){N||(N=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",r))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}function w(){var e=a(this.type);return Y[e]||(Y[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function b(e,r,n,o,T,Q){var s=n.ref;return e={$$typeof:A,type:e,key:r,props:n,_owner:o},(s!==void 0?s:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:w}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:T}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:Q}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function X(e,r,n,o,T,Q){var s=r.children;if(s!==void 0)if(o)if(G(s)){for(o=0;o<s.length;o++)g(s[o]);Object.freeze&&Object.freeze(s)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else g(s);if(L.call(r,"key")){s=a(e);var k=Object.keys(r).filter(function(ue){return ue!=="key"});o=0<k.length?"{key: someKey, "+k.join(": ..., ")+": ...}":"{key: someKey}",z[s+o]||(k=0<k.length?"{"+k.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
18
|
+
let props = %s;
|
|
19
|
+
<%s {...props} />
|
|
20
|
+
React keys must be passed directly to JSX without using spread:
|
|
21
|
+
let props = %s;
|
|
22
|
+
<%s key={someKey} {...props} />`,o,s,k,s),z[s+o]=!0)}if(s=null,n!==void 0&&(f(n),s=""+n),l(r)&&(f(r.key),s=""+r.key),"key"in r){n={};for(var K in r)K!=="key"&&(n[K]=r[K])}else n=r;return s&&h(n,typeof e=="function"?e.displayName||e.name||"Unknown":e),b(e,s,n,c(),T,Q)}function g(e){F(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===j&&(e._payload.status==="fulfilled"?F(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function F(e){return typeof e=="object"&&e!==null&&e.$$typeof===A}var R=u,A=Symbol.for("react.transitional.element"),H=Symbol.for("react.portal"),S=Symbol.for("react.fragment"),Z=Symbol.for("react.strict_mode"),J=Symbol.for("react.profiler"),V=Symbol.for("react.consumer"),B=Symbol.for("react.context"),_=Symbol.for("react.forward_ref"),t=Symbol.for("react.suspense"),v=Symbol.for("react.suspense_list"),m=Symbol.for("react.memo"),j=Symbol.for("react.lazy"),C=Symbol.for("react.activity"),I=Symbol.for("react.client.reference"),P=R.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,L=Object.prototype.hasOwnProperty,G=Array.isArray,y=console.createTask?console.createTask:function(){return null};R={react_stack_bottom_frame:function(e){return e()}};var N,Y={},M=R.react_stack_bottom_frame.bind(R,i)(),W=y(d(i)),z={};x.Fragment=S,x.jsx=function(e,r,n){var o=1e4>P.recentlyCreatedOwnerStacks++;return X(e,r,n,!1,o?Error("react-stack-top-frame"):M,o?y(d(e)):W)},x.jsxs=function(e,r,n){var o=1e4>P.recentlyCreatedOwnerStacks++;return X(e,r,n,!0,o?Error("react-stack-top-frame"):M,o?y(d(e)):W)}})()),x}var ee;function oe(){return ee||(ee=1,process.env.NODE_ENV==="production"?D.exports=ne():D.exports=ae()),D.exports}var U=oe();const re=u.forwardRef(({transform:a={translateX:0,translateY:0,scale:1,rotate:0},offset:E={left:0,top:0,right:0,bottom:0},minScale:f=.5,maxScale:d=10,clampBounds:c=!1,rotation:i=!0,mouse:l=!0,wheel:h=!0,touch:w=!0,gesture:b=!0,children:X,matrix:g,style:F,onTransformChange:R,onInit:A,onClick:H,onMouseDown:S,onTouchStart:Z,onMouseup:J,onTouchend:V},B)=>{const _=u.useRef(null),t=u.useRef(null),v=u.useRef(!1),[m,j]=u.useState({composePoint:(r,n)=>[0,0],canvasWidth:0,canvasHeight:0}),C=u.useRef((r,n)=>t.current?t.current.normalizeClientCoords(r,n):[0,0]);u.useImperativeHandle(B,()=>({applyTransform:(r,n,o,T)=>{t.current&&(T!==void 0&&(t.current.rotate=T),t.current.applyTransform(r,n,o))},composePoint:(r,n)=>m.composePoint?m.composePoint(r,n):[0,0],normalizeClientCoords:(r,n)=>C.current?C.current(r,n):[0,0],rotateCanvas:(r,n,o)=>{t.current&&t.current.rotateCanvas(r,n,o)},zoompinchEngine:t.current,canvasWidth:m.canvasWidth,canvasHeight:m.canvasHeight}));function I(){var r,n;t.current&&j({composePoint:t.current.composePoint.bind(t.current),canvasWidth:((r=t.current.canvasBounds)==null?void 0:r.width)??0,canvasHeight:((n=t.current.canvasBounds)==null?void 0:n.height)??0})}u.useEffect(()=>{!_.current||t.current||(t.current=new te.Zoompinch(_.current,E,a.translateX,a.translateY,a.scale,a.rotate,f,d,c,i),console.log("Zoompinch initialisiert mit Props!"),t.current.addEventListener("update",()=>{if(!t.current)return;const r={translateX:t.current.translateX,translateY:t.current.translateY,scale:t.current.scale,rotate:t.current.rotate};R&&(r.translateX!==a.translateX||r.translateY!==a.translateY||r.scale!==a.scale||r.rotate!==a.rotate)&&R(r),I()}),t.current.addEventListener("load",()=>{t.current&&(v.current=!0,A&&A(),I())}))},[]),u.useEffect(()=>{!t.current||!v.current||(t.current.translateX!==a.translateX||t.current.translateY!==a.translateY||t.current.scale!==a.scale||t.current.rotate!==a.rotate)&&(t.current.translateX=a.translateX,t.current.translateY=a.translateY,t.current.scale=a.scale,t.current.rotate=a.rotate,t.current.update())},[a]),u.useEffect(()=>{!t.current||!v.current||(t.current.offset=E,t.current.update())},[E]),u.useEffect(()=>{!t.current||!v.current||(t.current.minScale=f,t.current.update())},[f]),u.useEffect(()=>{!t.current||!v.current||(t.current.maxScale=d,t.current.update())},[d]),u.useEffect(()=>{!t.current||!v.current||(t.current.clampBounds=c,t.current.setTranslateFromUserGesture(t.current.translateX,t.current.translateY),t.current.update())},[c]),u.useEffect(()=>{!t.current||!v.current||(t.current.rotation=i,t.current.update())},[i]);const P=r=>{!t.current||!h||(r.preventDefault(),t.current.handleWheel(r))},L=r=>{!t.current||!b||t.current.handleGesturestart(r)},G=r=>{!t.current||!b||t.current.handleGesturechange(r)},y=r=>{!t.current||!b||t.current.handleGestureend(r)},N=r=>{!t.current||!l||t.current.handleMousedown(r)},Y=r=>{!t.current||!l||t.current.handleMousemove(r)},M=r=>{!t.current||!l||t.current.handleMouseup(r)},W=r=>{!t.current||!w||t.current.handleTouchstart(r)},z=r=>{!t.current||!w||t.current.handleTouchmove(r)},e=r=>{!t.current||!w||t.current.handleTouchend(r)};return u.useEffect(()=>{if(!_.current)return;const r=_.current;return window.addEventListener("gesturechange",G),window.addEventListener("gestureend",y),window.addEventListener("mousemove",Y),window.addEventListener("mouseup",M),window.addEventListener("touchmove",z),window.addEventListener("touchend",e),r.addEventListener("wheel",P,{passive:!1}),r.addEventListener("gesturestart",L,{passive:!1}),r.addEventListener("mousedown",N,{passive:!1}),r.addEventListener("touchstart",W,{passive:!1}),()=>{window.removeEventListener("gesturechange",G),window.removeEventListener("gestureend",y),window.removeEventListener("mousemove",Y),window.removeEventListener("mouseup",M),window.removeEventListener("touchmove",z),window.removeEventListener("touchend",e),r.removeEventListener("wheel",P),r.removeEventListener("gesturestart",L),r.removeEventListener("mousedown",N),r.removeEventListener("touchstart",W)}},[l,w,b,h]),U.jsxs("div",{ref:_,className:"zoompinch",style:F,onClick:H,onMouseDown:S,onTouchStart:Z,onMouseUp:J,onTouchEnd:V,children:[U.jsx("div",{className:"canvas",children:X}),U.jsx("div",{className:"matrix",children:typeof g=="function"?g({composePoint:m.composePoint,normalizeClientCoords:C.current,canvasWidth:m.canvasWidth,canvasHeight:m.canvasHeight}):g})]})});re.displayName="Zoompinch",p.Zoompinch=re,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zoompinch/react",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "React components for Zoompinch",
|
|
5
|
+
"main": "./dist/zoompinch-react.umd.js",
|
|
6
|
+
"module": "./dist/zoompinch-react.es.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/zoompinch-react.es.js",
|
|
11
|
+
"require": "./dist/zoompinch-react.umd.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "vite",
|
|
20
|
+
"build": "tsc && vite build",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react",
|
|
25
|
+
"zoompinch",
|
|
26
|
+
"zoom images",
|
|
27
|
+
"pinch to zoom",
|
|
28
|
+
"image zoom",
|
|
29
|
+
"pan gesture",
|
|
30
|
+
"pan and zoom",
|
|
31
|
+
"panzoom",
|
|
32
|
+
"gesture library",
|
|
33
|
+
"touch gestures"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://zoompinch.pages.dev/",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/ElyaConrad/zoompinch"
|
|
42
|
+
},
|
|
43
|
+
"author": "Elya Maurice Conrad",
|
|
44
|
+
"license": "ISC",
|
|
45
|
+
"type": "module",
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
48
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/react": "^19.2.7",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
54
|
+
"react": "^19.2.3",
|
|
55
|
+
"react-dom": "^19.2.3",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"vite": "^6.4.1"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@zoompinch/core": "^0.0.27"
|
|
61
|
+
}
|
|
62
|
+
}
|