use-zoom-pinch 0.3.0 → 0.4.0
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/CHANGELOG.md +83 -0
- package/README.md +48 -23
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +1 -1
- package/package.json +5 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
For pre-1.0 releases (`0.x.y`), minor version bumps may include breaking changes.
|
|
8
|
+
|
|
9
|
+
## [0.4.0] — 2026-07-13
|
|
10
|
+
|
|
11
|
+
### Breaking
|
|
12
|
+
|
|
13
|
+
These changes affect `resetView`, the keyboard `0` key, and `doubleTap` modes
|
|
14
|
+
`"reset"` / `"toggle"`. Apps that relied on the previous hardcoded reset target
|
|
15
|
+
should adjust.
|
|
16
|
+
|
|
17
|
+
- **`resetView()` now returns to `initialViewState`** instead of the hardcoded
|
|
18
|
+
`{ x: 0, y: 0, zoom: 1, rotation: 0 }`. If `initialViewState` is not provided,
|
|
19
|
+
the default `{ 0, 0, 1 }` is still used.
|
|
20
|
+
- **Keyboard `0`** resets to `initialViewState` (was: hardcoded reset).
|
|
21
|
+
- **`doubleTap` `"reset"` and `"toggle"`** modes animate back to `initialViewState`,
|
|
22
|
+
preserving the initial `x` / `y` / `zoom` / `rotation`. Previously they reset to
|
|
23
|
+
`{ 0, 0, 1 }` and silently dropped rotation.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`setView(view, { skipConstraints: true })`** — bypasses bounds clamping, axis
|
|
28
|
+
locking, and snap-to-grid for precise programmatic positioning (even outside
|
|
29
|
+
bounds). Only affects `setView`; gestures are always constrained.
|
|
30
|
+
- **`activationKeys.rotate`** is now honored (previously declared in the type but
|
|
31
|
+
ignored). Rotation via touch pinch, the Safari `GestureEvent`, and keyboard
|
|
32
|
+
`[` / `]` is gated by the configured activation key.
|
|
33
|
+
- **`cursor.zooming`** is now applied while wheel-zooming, and the cursor restores
|
|
34
|
+
to `idle` once the debounced zoom-end fires (previously the option was unused).
|
|
35
|
+
- **Exported math helpers** `clamp`, `distance`, `angleBetween` for manual
|
|
36
|
+
coordinate conversion.
|
|
37
|
+
- **Debounced `ResizeObserver`** (150ms) for `fitToContent` auto-resize, so window
|
|
38
|
+
resize no longer yanks the viewport on every intermediate frame.
|
|
39
|
+
- **Unified `onTransformEnd`** now fires after `doubleTap` and after **inertia
|
|
40
|
+
fully settles** (previously it fired only after pan/zoom/pinch end, and during
|
|
41
|
+
inertia it fired too early at pointer-up).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **Wheel `preventDefault` ordering** — the wheel handler no longer calls
|
|
46
|
+
`preventDefault()` when it will not handle the event. Previously, with
|
|
47
|
+
`activationKeys.zoom` set but the key not held, or with the relevant gesture
|
|
48
|
+
disabled, the hook still suppressed the browser's native wheel behavior
|
|
49
|
+
(page scroll, ctrl+wheel page zoom).
|
|
50
|
+
- **`animateTo` with `duration <= 0`** no longer poisons the view with `NaN`.
|
|
51
|
+
It now jumps to the target immediately.
|
|
52
|
+
- **`activationKeys` no longer "stick"** when the user alt-tabs while holding a
|
|
53
|
+
modifier — pressed keys are cleared on `window blur` and `visibilitychange`.
|
|
54
|
+
- **`contextmenu` is suppressed** when `panButton: 2` (right mouse), making
|
|
55
|
+
right-button panning usable.
|
|
56
|
+
|
|
57
|
+
### Tests
|
|
58
|
+
|
|
59
|
+
- Added `src/__tests__/bugfixes.test.ts` with 27 tests covering all fixes above,
|
|
60
|
+
including wheel `preventDefault` ordering, the duration guard, `skipConstraints`,
|
|
61
|
+
blur-clear, and `onTransformEnd` consistency after double-tap and inertia.
|
|
62
|
+
- Fixed a dead test for rotation snap (it had no assertions); replaced with a real
|
|
63
|
+
pinch-gesture-based assertion.
|
|
64
|
+
- Updated existing tests to match the new `resetView` / `initialViewState` semantics.
|
|
65
|
+
|
|
66
|
+
## [0.3.0]
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- Bounds bounce (rubber-band overscroll with snap-back), axis locking, cursor
|
|
71
|
+
management, `wheelMode`, rotation snap levels, activation keys, `onTransformEnd`,
|
|
72
|
+
navigation helpers (`panTo`, `panBy`, `zoomTo`, `fitToRect`), double-tap zoom,
|
|
73
|
+
inertia, animated transitions, controlled mode.
|
|
74
|
+
- Test coverage thresholds and `eslint-plugin-react-hooks`.
|
|
75
|
+
- README improvements and bundle-size badge.
|
|
76
|
+
|
|
77
|
+
### Fixed
|
|
78
|
+
|
|
79
|
+
- Initial release bug fixes.
|
|
80
|
+
|
|
81
|
+
## [0.1.0]
|
|
82
|
+
|
|
83
|
+
- Initial release: `useZoomPinch` React hook.
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Lightweight React hook for **pan**, **pinch-to-zoom**, **rotation**, and **scrol
|
|
|
7
7
|
[](https://bundlephobia.com/package/use-zoom-pinch)
|
|
8
8
|
|
|
9
9
|
[**Live Demo**](https://nemezzizz.github.io/use-zoom-pinch/)
|
|
10
|
+
[**Changelog**](./CHANGELOG.md)
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
|
@@ -39,7 +40,7 @@ Lightweight React hook for **pan**, **pinch-to-zoom**, **rotation**, and **scrol
|
|
|
39
40
|
- **Stable listeners** — config changes don't re-register event listeners
|
|
40
41
|
- **TypeScript-first** with full type exports
|
|
41
42
|
- **Tree-shakeable** ESM + CJS dual build
|
|
42
|
-
- **~5.
|
|
43
|
+
- **~5.8 KB** minified + gzipped
|
|
43
44
|
|
|
44
45
|
## Installation
|
|
45
46
|
|
|
@@ -141,6 +142,9 @@ setView({ x: 100, y: 200, zoom: 2 })
|
|
|
141
142
|
// Animated
|
|
142
143
|
setView({ x: 100, y: 200, zoom: 2 }, { animate: true, duration: 300 })
|
|
143
144
|
|
|
145
|
+
// Skip bounds/axis/snap constraints (precise positioning even outside bounds)
|
|
146
|
+
setView({ x: 1000, y: 1000, zoom: 2 }, { skipConstraints: true })
|
|
147
|
+
|
|
144
148
|
// With custom easing
|
|
145
149
|
import { easeInOut } from "use-zoom-pinch"
|
|
146
150
|
resetView({ animate: true, duration: 500, easing: easeInOut })
|
|
@@ -156,6 +160,26 @@ zoomToElement(elRef.current!, 2, { animate: true })
|
|
|
156
160
|
- `easeOut` — fast start, slow end (default)
|
|
157
161
|
- `easeInOut` — slow start, fast middle, slow end
|
|
158
162
|
|
|
163
|
+
### Skipping Constraints
|
|
164
|
+
|
|
165
|
+
`setView` normally applies bounds clamping, axis locking, and snap-to-grid. Pass `skipConstraints: true` to bypass them when you need precise programmatic positioning (even outside bounds):
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
setView({ x: 9999, y: 9999, zoom: 2 }, { skipConstraints: true })
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Exported Helpers
|
|
172
|
+
|
|
173
|
+
The library also exports a few pure math helpers for manual coordinate work:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import { clamp, distance, angleBetween } from "use-zoom-pinch"
|
|
177
|
+
|
|
178
|
+
clamp(15, 0, 10) // 10
|
|
179
|
+
distance(0, 0, 3, 4) // 5
|
|
180
|
+
angleBetween(0, 0, 0, 10) // 90 (degrees)
|
|
181
|
+
```
|
|
182
|
+
|
|
159
183
|
## Navigation
|
|
160
184
|
|
|
161
185
|
```tsx
|
|
@@ -164,7 +188,7 @@ const { panTo, panBy, zoomTo, fitToRect } = useZoomPinch({ containerRef })
|
|
|
164
188
|
// Center on a point in content space
|
|
165
189
|
panTo(500, 300, { animate: true })
|
|
166
190
|
|
|
167
|
-
// Shift viewport by 100px right, 50px down
|
|
191
|
+
// Shift viewport by 100px right, 50px down (screen-space delta)
|
|
168
192
|
panBy(100, 50)
|
|
169
193
|
|
|
170
194
|
// Zoom to 3x centered on a content-space point
|
|
@@ -408,26 +432,26 @@ useZoomPinch({ containerRef, panButton: 1 })
|
|
|
408
432
|
|
|
409
433
|
#### Returns
|
|
410
434
|
|
|
411
|
-
| Property | Type | Description
|
|
412
|
-
| ----------------- | ---------------------------------- |
|
|
413
|
-
| `view` | `ViewState` | Current view state
|
|
414
|
-
| `isAnimating` | `boolean` | Whether an animation is running
|
|
415
|
-
| `setView` | `(view, options?) => void` | Imperatively set the view
|
|
416
|
-
| `centerZoom` | `(zoom, options?) => void` | Zoom to level, centered in container
|
|
417
|
-
| `resetView` | `(options?) => void` | Reset to `
|
|
418
|
-
| `zoomIn` | `(step?, options?) => void` | Zoom in by step (default 1.5x)
|
|
419
|
-
| `zoomOut` | `(step?, options?) => void` | Zoom out by step (default 1.5x)
|
|
420
|
-
| `zoomToElement` | `(el, scale?, options?) => void` | Zoom and center on an element
|
|
421
|
-
| `panTo` | `(x, y, options?) => void` | Center viewport on content-space point
|
|
422
|
-
| `panBy` | `(dx, dy, options?) => void` | Shift viewport by relative offset
|
|
423
|
-
| `zoomTo` | `(zoom, point?, options?) => void` | Zoom to level, optionally anchored
|
|
424
|
-
| `fitToRect` | `(rect, options?) => void` | Fit a content-space rectangle into view
|
|
425
|
-
| `rotateTo` | `(angle, options?) => void` | Set rotation to absolute angle (degrees)
|
|
426
|
-
| `rotateBy` | `(delta, options?) => void` | Rotate by relative delta (degrees)
|
|
427
|
-
| `screenToContent` | `(screenX, screenY) => { x, y }` | Convert screen to content coordinates
|
|
428
|
-
| `contentToScreen` | `(contentX, contentY) => { x, y }` | Convert content to screen coordinates
|
|
429
|
-
| `fitToContent` | `(options?) => void` | Fit content to container (needs `contentRect`)
|
|
430
|
-
| `snapZoom` | `(options?) => void` | Snap zoom to nearest `zoomSnapLevels`
|
|
435
|
+
| Property | Type | Description |
|
|
436
|
+
| ----------------- | ---------------------------------- | ----------------------------------------------------- |
|
|
437
|
+
| `view` | `ViewState` | Current view state |
|
|
438
|
+
| `isAnimating` | `boolean` | Whether an animation is running |
|
|
439
|
+
| `setView` | `(view, options?) => void` | Imperatively set the view |
|
|
440
|
+
| `centerZoom` | `(zoom, options?) => void` | Zoom to level, centered in container |
|
|
441
|
+
| `resetView` | `(options?) => void` | Reset to `initialViewState` (or `{0,0,1,0}` if unset) |
|
|
442
|
+
| `zoomIn` | `(step?, options?) => void` | Zoom in by step (default 1.5x) |
|
|
443
|
+
| `zoomOut` | `(step?, options?) => void` | Zoom out by step (default 1.5x) |
|
|
444
|
+
| `zoomToElement` | `(el, scale?, options?) => void` | Zoom and center on an element |
|
|
445
|
+
| `panTo` | `(x, y, options?) => void` | Center viewport on content-space point |
|
|
446
|
+
| `panBy` | `(dx, dy, options?) => void` | Shift viewport by relative offset (screen px) |
|
|
447
|
+
| `zoomTo` | `(zoom, point?, options?) => void` | Zoom to level, optionally anchored |
|
|
448
|
+
| `fitToRect` | `(rect, options?) => void` | Fit a content-space rectangle into view |
|
|
449
|
+
| `rotateTo` | `(angle, options?) => void` | Set rotation to absolute angle (degrees) |
|
|
450
|
+
| `rotateBy` | `(delta, options?) => void` | Rotate by relative delta (degrees) |
|
|
451
|
+
| `screenToContent` | `(screenX, screenY) => { x, y }` | Convert screen to content coordinates |
|
|
452
|
+
| `contentToScreen` | `(contentX, contentY) => { x, y }` | Convert content to screen coordinates |
|
|
453
|
+
| `fitToContent` | `(options?) => void` | Fit content to container (needs `contentRect`) |
|
|
454
|
+
| `snapZoom` | `(options?) => void` | Snap zoom to nearest `zoomSnapLevels` |
|
|
431
455
|
|
|
432
456
|
### Types
|
|
433
457
|
|
|
@@ -449,6 +473,7 @@ interface AnimationOptions {
|
|
|
449
473
|
animate?: boolean // default false
|
|
450
474
|
duration?: number // default 300ms
|
|
451
475
|
easing?: EasingFunction // default easeOut
|
|
476
|
+
skipConstraints?: boolean // default false (setView only)
|
|
452
477
|
}
|
|
453
478
|
|
|
454
479
|
type EasingFunction = (t: number) => number
|
|
@@ -536,7 +561,7 @@ The hook uses standard Web APIs available in all modern browsers:
|
|
|
536
561
|
|
|
537
562
|
| Feature | useZoomPinch | react-zoom-pan-pinch | @use-gesture/react | motion/react |
|
|
538
563
|
| --------------------- | ------------ | -------------------- | ------------------ | ------------------- |
|
|
539
|
-
| Size (min+gzip) | **~5.
|
|
564
|
+
| Size (min+gzip) | **~5.8 KB** | ~13.2 KB | ~8.9 KB | ~41.6 KB |
|
|
540
565
|
| Approach | Hook | Components + hook | Gesture primitives | Animation + gesture |
|
|
541
566
|
| Controlled mode | ✅ Native | ❌ | ❌ | ❌ |
|
|
542
567
|
| DOM wrappers | ✅ None | +2 divs | ✅ None | +1 div |
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
'use strict';var react=require('react');var fn=y=>y,Vt=y=>1-(1-y)**3,dn=y=>y<.5?4*y*y*y:1-(-2*y+2)**3/2;var Pt={x:0,y:0,zoom:1,rotation:0},yn={pan:true,zoom:true,rotate:false},Bt={enabled:false,panStep:50,zoomStep:1.5,rotateStep:15};function pn({containerRef:y,minScale:B=.1,maxScale:F=50,panSpeed:q=1,zoomSpeed:J=1,initialViewState:st=Pt,viewState:qe,onViewStateChange:Ft,enabled:Ut=true,shouldHandleEvent:Kt,gestures:_e,bounds:Wt,panButton:Gt=0,keyboard:gt,zoomSnapLevels:Ht,snapToGrid:bt,contentRect:qt,rotation:_t,wheelMode:Nt="pan",cursor:jt,axis:Jt,activationKeys:Qt,onPanStart:$t,onPanEnd:te,onZoomStart:ee,onZoomEnd:ne,onPinchStart:oe,onPinchEnd:re,onRotateStart:se,onRotateEnd:ce,onTransformEnd:ie,doubleTap:ae,inertia:ue}){let[Ne,je]=react.useState(st),zt=qe??Ne,g=react.useRef(zt);g.current=zt;let W=react.useRef(Ut);W.current=Ut;let vt=react.useRef(Ft);vt.current=Ft;let k=react.useRef({minScale:B,maxScale:F,panSpeed:q,zoomSpeed:J});k.current={minScale:B,maxScale:F,panSpeed:q,zoomSpeed:J};let C=react.useRef(Kt);C.current=Kt;let le=ae===false?false:{enabled:true,mode:"toggle",step:2,...ae},me=react.useRef(le);me.current=le;let fe=ue===false?false:{enabled:true,friction:.92,...ue},de=react.useRef(fe);de.current=fe;let ye=react.useRef($t);ye.current=$t;let pe=react.useRef(te);pe.current=te;let he=react.useRef(ee);he.current=ee;let xe=react.useRef(ne);xe.current=ne;let ge=react.useRef(oe);ge.current=oe;let be=react.useRef(re);be.current=re;let ze=react.useRef(se);ze.current=se;let ve=react.useRef(ce);ve.current=ce;let ct=react.useRef(ie);ct.current=ie;let we={...yn,..._e},U=react.useRef(we);U.current=we;let wt=react.useRef(Wt);wt.current=Wt;let Ee=react.useRef(Gt);Ee.current=Gt;let Se=gt===true?{...Bt,enabled:true}:gt?{...Bt,...gt}:Bt,Et=react.useRef(Se);Et.current=Se;let St=react.useRef(Ht);St.current=Ht;let Oe=bt===false||bt===void 0?false:{mode:"end",...bt},Ot=react.useRef(Oe);Ot.current=Oe;let Tt=react.useRef(qt);Tt.current=qt;let Lt=react.useRef(_t);Lt.current=_t;let Te=react.useRef(Nt);Te.current=Nt;let Le=jt===false?false:{enabled:true,idle:"grab",dragging:"grabbing",zooming:"zoom-in",...jt},it=react.useRef(Le);it.current=Le;let Me=react.useRef(Jt);Me.current=Jt;let Mt=react.useRef(Qt);Mt.current=Qt;let[Je,At]=react.useState(false),_=react.useRef(null),N=react.useRef(null),Rt=react.useCallback(()=>{N.current!==null&&(cancelAnimationFrame(N.current),N.current=null);},[]),w=react.useCallback(()=>{_.current!==null&&(cancelAnimationFrame(_.current),_.current=null,At(false)),Rt();},[Rt]),h=react.useCallback((e,{isBounceSnap:n=false}={})=>{let r=g.current,t=e(r),s=Me.current;s==="x"?t={...t,y:r.y}:s==="y"&&(t={...t,x:r.x});let u=wt.current;if(u){let d=u.minX??-1/0,f=u.maxX??1/0,x=u.minY??-1/0,b=u.maxY??1/0;if(u.mode==="bounce"&&!n){let L=u.bounceFactor??.3;t.x<d?t={...t,x:d+(t.x-d)*L}:t.x>f&&(t={...t,x:f+(t.x-f)*L}),t.y<x?t={...t,y:x+(t.y-x)*L}:t.y>b&&(t={...t,y:b+(t.y-b)*L});}else t={...t,x:R(t.x,d,f),y:R(t.y,x,b)};}let i=Lt.current;i&&t.rotation!==void 0&&(t={...t,rotation:R(t.rotation,i.minAngle??-1/0,i.maxAngle??1/0)});let l=Ot.current;l&&l.mode==="always"&&(t={...t,x:Math.round(t.x/l.size)*l.size,y:Math.round(t.y/l.size)*l.size}),!(t.x===r.x&&t.y===r.y&&t.zoom===r.zoom&&(t.rotation??0)===(r.rotation??0))&&(vt.current?vt.current(t):je(t));},[]),S=react.useCallback((e,n={})=>{w(),At(true);let{duration:r=300,easing:t=Vt}=n,s={...g.current},u=performance.now(),i=s.rotation??0,l=e.rotation??0,d=()=>{let f=performance.now()-u,x=Math.min(f/r,1),b=t(x);h(()=>({x:s.x+(e.x-s.x)*b,y:s.y+(e.y-s.y)*b,zoom:s.zoom+(e.zoom-s.zoom)*b,rotation:i+(l-i)*b}),{isBounceSnap:true}),x<1?_.current=requestAnimationFrame(d):(_.current=null,At(false));};_.current=requestAnimationFrame(d);},[w,h]),Q=react.useCallback(()=>{let e=g.current,n={...e},r=false,t=St.current;if(t&&t.length>0){let l=t[0],d=Math.abs(e.zoom-l);for(let f=1;f<t.length;f++){let x=Math.abs(e.zoom-t[f]);x<d&&(d=x,l=t[f]);}if(l!==e.zoom){let{minScale:f,maxScale:x}=k.current,b=R(l,f,x),L=y.current;if(L){let K=L.offsetWidth/2,H=L.offsetHeight/2,Z=b/e.zoom;n={...n,zoom:b,x:K-(K-n.x)*Z,y:H-(H-n.y)*Z},r=true;}}}let s=wt.current;if(s&&s.mode==="bounce"){let l=s.minX??-1/0,d=s.maxX??1/0,f=s.minY??-1/0,x=s.maxY??1/0,b=R(n.x,l,d),L=R(n.y,f,x);(b!==n.x||L!==n.y)&&(n={...n,x:b,y:L},r=true);}let u=Lt.current;if(u?.snapLevels&&u.snapLevels.length>0){let l=n.rotation??0,d=u.snapLevels[0],f=Math.abs(l-d);for(let x=1;x<u.snapLevels.length;x++){let b=Math.abs(l-u.snapLevels[x]);b<f&&(f=b,d=u.snapLevels[x]);}d!==l&&(n={...n,rotation:d},r=true);}let i=Ot.current;if(i&&i.mode==="end"){let l=Math.round(n.x/i.size)*i.size,d=Math.round(n.y/i.size)*i.size;(l!==n.x||d!==n.y)&&(n={...n,x:l,y:d},r=true);}r&&S(n,{duration:150});},[S,y]);react.useEffect(()=>{let e=y.current;if(!e)return;let n=(o,a)=>{let c=e.getBoundingClientRect();return {px:o-c.left,py:a-c.top}},r=new Set,t=o=>r.add(o.key),s=o=>r.delete(o.key);window.addEventListener("keydown",t),window.addEventListener("keyup",s);let u=false,i=null,l=false,d=o=>{if(!W.current||C.current&&!C.current(o))return;o.preventDefault(),w();let a=o.deltaX,c=o.deltaY,{ctrlKey:p,clientX:v,clientY:m}=o,A=o.deltaMode===0&&(Math.abs(c)<100||!Number.isInteger(c));A||(Math.abs(c)>=100&&(c=Math.sign(c)*(Math.abs(c)/120)),Math.abs(a)>=100&&(a=Math.sign(a)*(Math.abs(a)/120)));let{minScale:O,maxScale:I,panSpeed:Y,zoomSpeed:E}=k.current,M=U.current,D=Mt.current,P=Te.current==="zoom"?!p:p;P&&D?.zoom&&!r.has(D.zoom)||!P&&D?.pan&&!r.has(D.pan)||h(X=>{if(P){if(!M.zoom||l)return X;u||(u=true,he.current?.(X)),i&&clearTimeout(i),i=setTimeout(()=>{u=false,xe.current?.(g.current),ct.current?.(g.current),Q();},150);let pt=A?.01:.1*E,ht=1-c*pt,rt=R(X.zoom*ht,O,I);if(rt===X.zoom)return X;let{px:xt,py:Fe}=n(v,m),Ue=rt/X.zoom;return {zoom:rt,x:xt-(xt-X.x)*Ue,y:Fe-(Fe-X.y)*Ue}}if(!M.pan)return X;let ot=A?1:25*Y;return {...X,x:X.x-a*ot,y:X.y-c*ot}});},f=0,x=0,b=0,L=300,K=25,H=(o,a)=>{let c=me.current;if(c===false||!c.enabled)return;let{mode:p="toggle",step:v=2}=c,{minScale:m,maxScale:A}=k.current,O=g.current,{px:I,py:Y}=n(o,a),E;if(p==="zoomIn"?E=R(O.zoom*v,m,A):p==="reset"?E=1:E=O.zoom>1.05?1:R(O.zoom*v,m,A),p==="reset"||p==="toggle"&&O.zoom>1.05)S({x:0,y:0,zoom:1},{duration:300});else {let M=E/O.zoom;S({zoom:E,x:I-(I-O.x)*M,y:Y-(Y-O.y)*M},{duration:300});}},Z=false,ut={x:0,y:0},Xt=false,$=new Set,tt=0,et=0,Dt=0,Ae=o=>{if(!W.current||C.current&&!C.current(o)||(w(),$.add(o.pointerId),o.pointerType==="mouse"&&o.button!==Ee.current))return;if($.size>=2){Z=false;return}if(!U.current.pan)return;let a=Mt.current?.pan;if(a&&!r.has(a))return;Z=true,Xt=false;let c=it.current;c&&c.enabled&&Ct(c.dragging??"grabbing"),ut={x:o.clientX,y:o.clientY},tt=0,et=0,Dt=performance.now(),ye.current?.(g.current);},Re=o=>{if(!Z)return;o.preventDefault();let a=performance.now(),c=o.clientX-ut.x,p=o.clientY-ut.y,v=a-Dt;v>0&&(tt=.8*(c/v*16)+(1-.8)*tt,et=.8*(p/v*16)+(1-.8)*et),ut={x:o.clientX,y:o.clientY},Dt=a,Xt=true,h(m=>({...m,x:m.x+c,y:m.y+p}));},mn=()=>{let o=de.current;if(o===false||!o.enabled||Math.abs(tt)<.5&&Math.abs(et)<.5)return false;let{friction:a=.92}=o,c=tt,p=et,v=()=>{if(c*=a,p*=a,Math.abs(c)<.5&&Math.abs(p)<.5){N.current=null,Q();return}h(m=>({...m,x:m.x+c,y:m.y+p})),N.current=requestAnimationFrame(v);};return N.current=requestAnimationFrame(v),true},lt=o=>{let a=Z;if($.delete(o.pointerId),$.size===0&&(Z=false),a&&$.size===0){pe.current?.(g.current),ct.current?.(g.current);let c=it.current;if(c&&c.enabled&&Ct(c.idle??"grab"),Xt)mn()||Q(),f=0;else {let p=performance.now(),v=o.clientX-x,m=o.clientY-b,A=Math.sqrt(v*v+m*m);p-f<L&&A<K?(H(o.clientX,o.clientY),f=0):(f=p,x=o.clientX,b=o.clientY);}}},V={zoom:1,x:0,y:0,rotation:0},Ie=0,Ye=0,nt={x:0,y:0},mt=false,Xe=o=>{if(!W.current||o.touches.length<2||C.current&&!C.current(o))return;let a=U.current;if(!a.zoom&&!a.rotate)return;w(),Z=false,mt=true;let c=o.touches[0],p=o.touches[1];Ie=Ge(c.clientX,c.clientY,p.clientX,p.clientY),Ye=He(c.clientX,c.clientY,p.clientX,p.clientY),nt={x:(c.clientX+p.clientX)/2,y:(c.clientY+p.clientY)/2},V={...g.current,rotation:g.current.rotation??0},ge.current?.(g.current),a.rotate&&ze.current?.(g.current);},De=o=>{if(o.touches.length<2||!mt||C.current&&!C.current(o))return;o.preventDefault();let a=o.touches[0],c=o.touches[1],p=U.current,{minScale:v,maxScale:m}=k.current,A=Ge(a.clientX,a.clientY,c.clientX,c.clientY),O={x:(a.clientX+c.clientX)/2,y:(a.clientY+c.clientY)/2},I=p.zoom?R(V.zoom*(A/Ie),v,m):V.zoom,Y=I/V.zoom,{px:E,py:M}=n(nt.x,nt.y),D=He(a.clientX,a.clientY,c.clientX,c.clientY),j=p.rotate&&!l?V.rotation+(D-Ye):V.rotation??0,P=(j-(V.rotation??0))*Math.PI/180,X=Math.cos(P),ot=Math.sin(P),pt=E-V.x,ht=M-V.y,rt=O.x-nt.x,xt=O.y-nt.y;h(()=>({zoom:I,x:E+rt-Y*(pt*X-ht*ot),y:M+xt-Y*(pt*ot+ht*X),rotation:j}));},ft=o=>{o.touches.length<2&&mt&&(mt=false,be.current?.(g.current),U.current.rotate&&ve.current?.(g.current),ct.current?.(g.current),Q());},dt=0,yt=1,ke=0,Ce=0,Ze=o=>{if(!W.current)return;o.preventDefault(),w(),l=true;let a=g.current;dt=a.rotation??0,yt=a.zoom,ke=a.x,Ce=a.y;},Ve=o=>{if(!W.current||!l)return;o.preventDefault();let a=o,c=U.current,{minScale:p,maxScale:v}=k.current,m=c.zoom?R(yt*a.scale,p,v):yt,A=c.rotate?dt+a.rotation:dt,O=(e.offsetWidth??0)/2,I=(e.offsetHeight??0)/2,Y=m/yt,E=(A-dt)*Math.PI/180,M=Math.cos(E),D=Math.sin(E),j=O-ke,P=I-Ce;h(()=>({zoom:m,x:O-Y*(j*M-P*D),y:I-Y*(j*D+P*M),rotation:A}));},Pe=o=>{o.preventDefault(),l=false;};Et.current.enabled&&!e.hasAttribute("tabindex")&&e.setAttribute("tabindex","0");let Be=o=>{if(!W.current)return;let a=Et.current;if(!a.enabled)return;let c=o.target?.tagName;if(c==="INPUT"||c==="TEXTAREA"||c==="SELECT")return;let p=U.current,v=false;switch(o.key){case "ArrowLeft":p.pan&&(h(m=>({...m,x:m.x+a.panStep})),v=true);break;case "ArrowRight":p.pan&&(h(m=>({...m,x:m.x-a.panStep})),v=true);break;case "ArrowUp":p.pan&&(h(m=>({...m,x:m.x,y:m.y+a.panStep})),v=true);break;case "ArrowDown":p.pan&&(h(m=>({...m,x:m.x,y:m.y-a.panStep})),v=true);break;case "+":case "=":if(p.zoom){let{minScale:m,maxScale:A}=k.current,O=e,I=O.offsetWidth/2,Y=O.offsetHeight/2;h(E=>{let M=R(E.zoom*a.zoomStep,m,A),D=M/E.zoom;return {...E,zoom:M,x:I-(I-E.x)*D,y:Y-(Y-E.y)*D}}),v=true;}break;case "-":if(p.zoom){let{minScale:m,maxScale:A}=k.current,O=e,I=O.offsetWidth/2,Y=O.offsetHeight/2;h(E=>{let M=R(E.zoom/a.zoomStep,m,A),D=M/E.zoom;return {...E,zoom:M,x:I-(I-E.x)*D,y:Y-(Y-E.y)*D}}),v=true;}break;case "0":w(),h(()=>Pt),v=true;break;case "[":p.rotate&&(h(m=>({...m,rotation:(m.rotation??0)-a.rotateStep})),v=true);break;case "]":p.rotate&&(h(m=>({...m,rotation:(m.rotation??0)+a.rotateStep})),v=true);break}v&&o.preventDefault();},kt=null,Ct=o=>{e.style.cursor=o;},Zt=it.current;return Zt&&Zt.enabled&&(kt=e.style.cursor,Ct(Zt.idle??"grab")),e.addEventListener("wheel",d,{passive:false}),e.addEventListener("pointerdown",Ae),window.addEventListener("pointermove",Re,{passive:false}),window.addEventListener("pointerup",lt),window.addEventListener("pointercancel",lt),e.addEventListener("touchstart",Xe,{passive:false}),e.addEventListener("touchmove",De,{passive:false}),e.addEventListener("touchend",ft),e.addEventListener("touchcancel",ft),e.addEventListener("gesturestart",Ze,{passive:false}),e.addEventListener("gesturechange",Ve,{passive:false}),e.addEventListener("gestureend",Pe,{passive:false}),e.addEventListener("keydown",Be),()=>{w(),i&&clearTimeout(i),kt!==null&&(e.style.cursor=kt),e.removeEventListener("wheel",d),e.removeEventListener("pointerdown",Ae),window.removeEventListener("pointermove",Re),window.removeEventListener("pointerup",lt),window.removeEventListener("pointercancel",lt),e.removeEventListener("touchstart",Xe),e.removeEventListener("touchmove",De),e.removeEventListener("touchend",ft),e.removeEventListener("touchcancel",ft),e.removeEventListener("gesturestart",Ze),e.removeEventListener("gesturechange",Ve),e.removeEventListener("gestureend",Pe),e.removeEventListener("keydown",Be),window.removeEventListener("keydown",t),window.removeEventListener("keyup",s);}},[y,h,w,S,Rt,Q]);let It=react.useCallback((e,n)=>{if(n?.animate){S(e,n);return}w(),h(()=>e);},[S,w,h]),G=react.useCallback((e,n)=>{let{minScale:r,maxScale:t}=k.current,s=R(e,r,t),u=(y.current?.offsetWidth??0)/2,i=(y.current?.offsetHeight??0)/2,l=g.current,d=s/l.zoom,f={zoom:s,x:u-(u-l.x)*d,y:i-(i-l.y)*d};n?.animate?S(f,n):(w(),h(()=>f));},[y,h,S,w]),Qe=react.useCallback(e=>It(Pt,e),[It]),$e=react.useCallback((e=1.5,n)=>{G(g.current.zoom*e,n);},[G]),tn=react.useCallback((e=1.5,n)=>{G(g.current.zoom/e,n);},[G]),en=react.useCallback((e,n,r)=>{let t=y.current;if(!t)return;let s=g.current,u={x:t.offsetWidth/2-e*s.zoom,y:t.offsetHeight/2-n*s.zoom,zoom:s.zoom};r?.animate?S(u,r):(w(),h(()=>u));},[y,h,S,w]),nn=react.useCallback((e,n,r)=>{let t=g.current,s={x:t.x+e,y:t.y+n,zoom:t.zoom};r?.animate?S(s,r):(w(),h(()=>s));},[h,S,w]),on=react.useCallback((e,n,r)=>{let t=y.current;if(!t)return;let{minScale:s,maxScale:u}=k.current,i=R(e,s,u),l=g.current,d=i/l.zoom,f;if(n)f={zoom:i,x:t.offsetWidth/2-n.x*i,y:t.offsetHeight/2-n.y*i};else {let x=t.offsetWidth/2,b=t.offsetHeight/2;f={zoom:i,x:x-(x-l.x)*d,y:b-(b-l.y)*d};}r?.animate?S(f,r):(w(),h(()=>f));},[y,h,S,w]),Yt=react.useCallback((e,n)=>{let r=y.current;if(!r)return;let{minScale:t,maxScale:s}=k.current,u=n?.padding??0,i=r.offsetWidth-u*2,l=r.offsetHeight-u*2;if(i<=0||l<=0)return;let d=R(Math.min(i/e.width,l/e.height),t,s),f={zoom:d,x:r.offsetWidth/2-(e.x+e.width/2)*d,y:r.offsetHeight/2-(e.y+e.height/2)*d};n?.animate?S(f,n):(w(),h(()=>f));},[y,h,S,w]),rn=react.useCallback((e,n,r)=>{let t=y.current;if(!t)return;let s=t.getBoundingClientRect(),u=e.getBoundingClientRect(),i=g.current,l=(u.left-s.left-i.x)/i.zoom,d=(u.top-s.top-i.y)/i.zoom,f=u.width/i.zoom,x=u.height/i.zoom,{minScale:b,maxScale:L}=k.current,K=R(n??i.zoom,b,L),H={x:s.width/2-(l+f/2)*K,y:s.height/2-(d+x/2)*K,zoom:K};r?.animate?S(H,r):(w(),h(()=>H));},[y,h,S,w]),sn=react.useCallback((e,n)=>{let t={...g.current,rotation:e};n?.animate?S(t,n):(w(),h(()=>t));},[h,S,w]),cn=react.useCallback((e,n)=>{let r=g.current,t={...r,rotation:(r.rotation??0)+e};n?.animate?S(t,n):(w(),h(()=>t));},[h,S,w]),an=react.useCallback((e,n)=>{let r=y.current;if(!r)return {x:0,y:0};let t=r.getBoundingClientRect(),s=g.current,u=e-t.left-s.x,i=n-t.top-s.y,l=s.rotation??0;if(l===0)return {x:u/s.zoom,y:i/s.zoom};let d=-l*Math.PI/180,f=Math.cos(d),x=Math.sin(d),b=u*f-i*x,L=u*x+i*f;return {x:b/s.zoom,y:L/s.zoom}},[y]),un=react.useCallback((e,n)=>{let r=y.current;if(!r)return {x:0,y:0};let t=r.getBoundingClientRect(),s=g.current,u=s.rotation??0,i=e*s.zoom,l=n*s.zoom;if(u===0)return {x:i+s.x+t.left,y:l+s.y+t.top};let d=u*Math.PI/180,f=Math.cos(d),x=Math.sin(d),b=i*f-l*x,L=i*x+l*f;return {x:b+s.x+t.left,y:L+s.y+t.top}},[y]),at=react.useCallback(e=>{let n=y.current,r=Tt.current;!n||!r||Yt({x:0,y:0,width:r.width,height:r.height},e);},[y,Yt]),ln=react.useCallback(e=>{let n=St.current;if(!n||n.length===0)return;let r=g.current,t=n[0],s=Math.abs(r.zoom-t);for(let u=1;u<n.length;u++){let i=Math.abs(r.zoom-n[u]);i<s&&(s=i,t=n[u]);}G(t,e);},[G]);return react.useEffect(()=>{let e=y.current,n=Tt.current;if(!e||!n)return;at();let r=new ResizeObserver(()=>{at();});return r.observe(e),()=>r.disconnect()},[y,at]),{view:zt,isAnimating:Je,setView:It,centerZoom:G,resetView:Qe,zoomIn:$e,zoomOut:tn,zoomToElement:rn,panTo:en,panBy:nn,zoomTo:on,fitToRect:Yt,rotateTo:sn,rotateBy:cn,screenToContent:an,contentToScreen:un,fitToContent:at,snapZoom:ln}}function R(y,B,F){return Math.max(B,Math.min(y,F))}function Ge(y,B,F,q){let J=y-F,st=B-q;return Math.sqrt(J*J+st*st)}function He(y,B,F,q){return Math.atan2(q-B,F-y)*(180/Math.PI)}exports.easeInOut=dn;exports.easeOut=Vt;exports.linear=fn;exports.useZoomPinch=pn;
|
|
1
|
+
'use strict';var react=require('react');var hn=y=>y,Gt=y=>1-(1-y)**3,xn=y=>y<.5?4*y*y*y:1-(-2*y+2)**3/2;var gn={x:0,y:0,zoom:1,rotation:0},bn={pan:true,zoom:true,rotate:false},Ht={enabled:false,panStep:50,zoomStep:1.5,rotateStep:15};function vn({containerRef:y,minScale:B=.1,maxScale:F=50,panSpeed:J=1,zoomSpeed:st=1,initialViewState:Q=gn,viewState:Je,onViewStateChange:Nt,enabled:jt=true,shouldHandleEvent:Jt,gestures:Qe,bounds:Qt,panButton:$t=0,keyboard:Tt,zoomSnapLevels:te,snapToGrid:Lt,contentRect:ee,rotation:ne,wheelMode:oe="pan",cursor:re,axis:se,activationKeys:ce,onPanStart:ie,onPanEnd:ae,onZoomStart:ue,onZoomEnd:le,onPinchStart:me,onPinchEnd:fe,onRotateStart:de,onRotateEnd:ye,onTransformEnd:pe,doubleTap:he,inertia:xe}){let[$e,tn]=react.useState(Q),Ot=Je??$e,x=react.useRef(Ot);x.current=Ot;let G=react.useRef(jt);G.current=jt;let At=react.useRef(Nt);At.current=Nt;let D=react.useRef({minScale:B,maxScale:F,panSpeed:J,zoomSpeed:st});D.current={minScale:B,maxScale:F,panSpeed:J,zoomSpeed:st};let $=react.useRef(Q);$.current=Q;let C=react.useRef(Jt);C.current=Jt;let ge=he===false?false:{enabled:true,mode:"toggle",step:2,...he},be=react.useRef(ge);be.current=ge;let ve=xe===false?false:{enabled:true,friction:.92,...xe},ze=react.useRef(ve);ze.current=ve;let we=react.useRef(ie);we.current=ie;let Ee=react.useRef(ae);Ee.current=ae;let Se=react.useRef(ue);Se.current=ue;let Te=react.useRef(le);Te.current=le;let Le=react.useRef(me);Le.current=me;let Oe=react.useRef(fe);Oe.current=fe;let Ae=react.useRef(de);Ae.current=de;let Me=react.useRef(ye);Me.current=ye;let H=react.useRef(pe);H.current=pe;let Re={...bn,...Qe},U=react.useRef(Re);U.current=Re;let Mt=react.useRef(Qt);Mt.current=Qt;let Rt=react.useRef($t);Rt.current=$t;let Ie=Tt===true?{...Ht,enabled:true}:Tt?{...Ht,...Tt}:Ht,It=react.useRef(Ie);It.current=Ie;let Yt=react.useRef(te);Yt.current=te;let Ye=Lt===false||Lt===void 0?false:{mode:"end",...Lt},Xt=react.useRef(Ye);Xt.current=Ye;let kt=react.useRef(ee);kt.current=ee;let Dt=react.useRef(ne);Dt.current=ne;let Xe=react.useRef(oe);Xe.current=oe;let ke=re===false?false:{enabled:true,idle:"grab",dragging:"grabbing",zooming:"zoom-in",...re},tt=react.useRef(ke);tt.current=ke;let De=react.useRef(se);De.current=se;let q=react.useRef(ce);q.current=ce;let[en,Ct]=react.useState(false),et=react.useRef(null),nt=react.useRef(null),Zt=react.useCallback(()=>{nt.current!==null&&(cancelAnimationFrame(nt.current),nt.current=null);},[]),w=react.useCallback(()=>{et.current!==null&&(cancelAnimationFrame(et.current),et.current=null,Ct(false)),Zt();},[Zt]),ft=react.useCallback(t=>{At.current?At.current(t):tn(t);},[]),h=react.useCallback((t,{isBounceSnap:n=false}={})=>{let o=x.current,e=t(o),c=De.current;c==="x"?e={...e,y:o.y}:c==="y"&&(e={...e,x:o.x});let s=Mt.current;if(s){let d=s.minX??-1/0,f=s.maxX??1/0,g=s.minY??-1/0,z=s.maxY??1/0;if(s.mode==="bounce"&&!n){let S=s.bounceFactor??.3;e.x<d?e={...e,x:d+(e.x-d)*S}:e.x>f&&(e={...e,x:f+(e.x-f)*S}),e.y<g?e={...e,y:g+(e.y-g)*S}:e.y>z&&(e={...e,y:z+(e.y-z)*S});}else e={...e,x:I(e.x,d,f),y:I(e.y,g,z)};}let a=Dt.current;a&&e.rotation!==void 0&&(e={...e,rotation:I(e.rotation,a.minAngle??-1/0,a.maxAngle??1/0)});let u=Xt.current;u&&u.mode==="always"&&(e={...e,x:Math.round(e.x/u.size)*u.size,y:Math.round(e.y/u.size)*u.size}),!(e.x===o.x&&e.y===o.y&&e.zoom===o.zoom&&(e.rotation??0)===(o.rotation??0))&&ft(e);},[ft]),T=react.useCallback((t,n={},o)=>{w();let{duration:e=300,easing:c=Gt}=n;if(e<=0){h(()=>t),o?.();return}Ct(true);let s={...x.current},a=performance.now(),u=s.rotation??0,d=t.rotation??0,f=()=>{let g=performance.now()-a,z=Math.min(g/e,1),S=c(z);h(()=>({x:s.x+(t.x-s.x)*S,y:s.y+(t.y-s.y)*S,zoom:s.zoom+(t.zoom-s.zoom)*S,rotation:u+(d-u)*S}),{isBounceSnap:true}),z<1?et.current=requestAnimationFrame(f):(et.current=null,Ct(false),o?.());};et.current=requestAnimationFrame(f);},[w,h]),ct=react.useCallback(()=>{let t=x.current,n={...t},o=false,e=Yt.current;if(e&&e.length>0){let u=e[0],d=Math.abs(t.zoom-u);for(let f=1;f<e.length;f++){let g=Math.abs(t.zoom-e[f]);g<d&&(d=g,u=e[f]);}if(u!==t.zoom){let{minScale:f,maxScale:g}=D.current,z=I(u,f,g),S=y.current;if(S){let K=S.offsetWidth/2,N=S.offsetHeight/2,Z=z/t.zoom;n={...n,zoom:z,x:K-(K-n.x)*Z,y:N-(N-n.y)*Z},o=true;}}}let c=Mt.current;if(c&&c.mode==="bounce"){let u=c.minX??-1/0,d=c.maxX??1/0,f=c.minY??-1/0,g=c.maxY??1/0,z=I(n.x,u,d),S=I(n.y,f,g);(z!==n.x||S!==n.y)&&(n={...n,x:z,y:S},o=true);}let s=Dt.current;if(s?.snapLevels&&s.snapLevels.length>0){let u=n.rotation??0,d=s.snapLevels[0],f=Math.abs(u-d);for(let g=1;g<s.snapLevels.length;g++){let z=Math.abs(u-s.snapLevels[g]);z<f&&(f=z,d=s.snapLevels[g]);}d!==u&&(n={...n,rotation:d},o=true);}let a=Xt.current;if(a&&a.mode==="end"){let u=Math.round(n.x/a.size)*a.size,d=Math.round(n.y/a.size)*a.size;(u!==n.x||d!==n.y)&&(n={...n,x:u,y:d},o=true);}o&&T(n,{duration:150});},[T,y]);react.useEffect(()=>{let t=y.current;if(!t)return;let n=(r,l)=>{let i=t.getBoundingClientRect();return {px:r-i.left,py:l-i.top}},o=new Set,e=r=>o.add(r.key),c=r=>o.delete(r.key);window.addEventListener("keydown",e),window.addEventListener("keyup",c);let s=false,a=null,u=false,d=r=>{if(!G.current||C.current&&!C.current(r))return;let l=r.deltaX,i=r.deltaY,{ctrlKey:p,clientX:v,clientY:m}=r,L=r.deltaMode===0&&(Math.abs(i)<100||!Number.isInteger(i));L||(Math.abs(i)>=100&&(i=Math.sign(i)*(Math.abs(i)/120)),Math.abs(l)>=100&&(l=Math.sign(l)*(Math.abs(l)/120)));let{minScale:A,maxScale:Y,panSpeed:X,zoomSpeed:E}=D.current,M=U.current,k=q.current,P=Xe.current==="zoom"?!p:p;P&&(!M.zoom||u)||P&&k?.zoom&&!o.has(k.zoom)||!P&&!M.pan||!P&&k?.pan&&!o.has(k.pan)||(r.preventDefault(),w(),h(R=>{if(P){if(!s){s=true,Se.current?.(R);let j=tt.current;j&&j.enabled&&(t.style.cursor=j.zooming??"zoom-in");}a&&clearTimeout(a),a=setTimeout(()=>{s=false,Te.current?.(x.current),H.current?.(x.current);let j=tt.current;j&&j.enabled&&(t.style.cursor=j.idle??"grab"),ct();},150);let zt=L?.01:.1*E,wt=1-i*zt,rt=I(R.zoom*wt,A,Y);if(rt===R.zoom)return R;let{px:mt,py:Et}=n(v,m),St=rt/R.zoom;return {zoom:rt,x:mt-(mt-R.x)*St,y:Et-(Et-R.y)*St}}if(!M.pan)return R;let W=L?1:25*X;return {...R,x:R.x-l*W,y:R.y-i*W}}));},f=0,g=0,z=0,S=300,K=25,N=(r,l)=>{let i=be.current;if(i===false||!i.enabled)return;let{mode:p="toggle",step:v=2}=i,{minScale:m,maxScale:L}=D.current,A=x.current,{px:Y,py:X}=n(r,l),E;if(p==="zoomIn"?E=I(A.zoom*v,m,L):p==="reset"?E=$.current.zoom:E=A.zoom>1.05?$.current.zoom:I(A.zoom*v,m,L),p==="reset"||p==="toggle"&&A.zoom>1.05)T($.current,{duration:300},()=>H.current?.(x.current));else {let M=E/A.zoom;T({zoom:E,x:Y-(Y-A.x)*M,y:X-(X-A.y)*M},{duration:300},()=>H.current?.(x.current));}},Z=false,yt={x:0,y:0},Bt=false,it=new Set,at=0,ut=0,Ft=0,Ce=r=>{if(!G.current||C.current&&!C.current(r)||(w(),it.add(r.pointerId),r.pointerType==="mouse"&&r.button!==Rt.current))return;if(it.size>=2){Z=false;return}if(!U.current.pan)return;let l=q.current?.pan;if(l&&!o.has(l))return;Z=true,Bt=false;let i=tt.current;i&&i.enabled&&Kt(i.dragging??"grabbing"),yt={x:r.clientX,y:r.clientY},at=0,ut=0,Ft=performance.now(),we.current?.(x.current);},Ze=r=>{if(!Z)return;r.preventDefault();let l=performance.now(),i=r.clientX-yt.x,p=r.clientY-yt.y,v=l-Ft;v>0&&(at=.8*(i/v*16)+(1-.8)*at,ut=.8*(p/v*16)+(1-.8)*ut),yt={x:r.clientX,y:r.clientY},Ft=l,Bt=true,h(m=>({...m,x:m.x+i,y:m.y+p}));},pn=()=>{let r=ze.current;if(r===false||!r.enabled||Math.abs(at)<.5&&Math.abs(ut)<.5)return false;let{friction:l=.92}=r,i=at,p=ut,v=()=>{if(i*=l,p*=l,Math.abs(i)<.5&&Math.abs(p)<.5){nt.current=null,ct(),H.current?.(x.current);return}h(m=>({...m,x:m.x+i,y:m.y+p})),nt.current=requestAnimationFrame(v);};return nt.current=requestAnimationFrame(v),true},pt=r=>{let l=Z;if(it.delete(r.pointerId),it.size===0&&(Z=false),l&&it.size===0){Ee.current?.(x.current);let i=tt.current;if(i&&i.enabled&&Kt(i.idle??"grab"),Bt)pn()||(ct(),H.current?.(x.current)),f=0;else {let p=performance.now(),v=r.clientX-g,m=r.clientY-z,L=Math.sqrt(v*v+m*m);p-f<S&&L<K?(N(r.clientX,r.clientY),f=0):(f=p,g=r.clientX,z=r.clientY);}}},V={zoom:1,x:0,y:0,rotation:0},Ve=0,Pe=0,lt={x:0,y:0},ht=false,Be=r=>{if(!G.current||r.touches.length<2||C.current&&!C.current(r))return;let l=U.current;if(!l.zoom&&!l.rotate)return;w(),Z=false,ht=true;let i=r.touches[0],p=r.touches[1];Ve=qt(i.clientX,i.clientY,p.clientX,p.clientY),Pe=_t(i.clientX,i.clientY,p.clientX,p.clientY),lt={x:(i.clientX+p.clientX)/2,y:(i.clientY+p.clientY)/2},V={...x.current,rotation:x.current.rotation??0},Le.current?.(x.current),l.rotate&&Ae.current?.(x.current);},Fe=r=>{if(r.touches.length<2||!ht||C.current&&!C.current(r))return;r.preventDefault();let l=r.touches[0],i=r.touches[1],p=U.current,{minScale:v,maxScale:m}=D.current,L=qt(l.clientX,l.clientY,i.clientX,i.clientY),A={x:(l.clientX+i.clientX)/2,y:(l.clientY+i.clientY)/2},Y=p.zoom?I(V.zoom*(L/Ve),v,m):V.zoom,X=Y/V.zoom,{px:E,py:M}=n(lt.x,lt.y),k=_t(l.clientX,l.clientY,i.clientX,i.clientY),ot=q.current?.rotate,R=p.rotate&&!u&&(!ot||o.has(ot))?V.rotation+(k-Pe):V.rotation??0,W=(R-(V.rotation??0))*Math.PI/180,zt=Math.cos(W),wt=Math.sin(W),rt=E-V.x,mt=M-V.y,Et=A.x-lt.x,St=A.y-lt.y;h(()=>({zoom:Y,x:E+Et-X*(rt*zt-mt*wt),y:M+St-X*(rt*wt+mt*zt),rotation:R}));},xt=r=>{r.touches.length<2&&ht&&(ht=false,Oe.current?.(x.current),U.current.rotate&&Me.current?.(x.current),H.current?.(x.current),ct());},gt=0,bt=1,Ue=0,Ke=0,We=r=>{if(!G.current)return;r.preventDefault(),w(),u=true;let l=x.current;gt=l.rotation??0,bt=l.zoom,Ue=l.x,Ke=l.y;},Ge=r=>{if(!G.current||!u)return;r.preventDefault();let l=r,i=U.current,{minScale:p,maxScale:v}=D.current,m=i.zoom?I(bt*l.scale,p,v):bt,L=q.current?.rotate,Y=i.rotate&&(!L||o.has(L))?gt+l.rotation:gt,X=(t.offsetWidth??0)/2,E=(t.offsetHeight??0)/2,M=m/bt,k=(Y-gt)*Math.PI/180,ot=Math.cos(k),P=Math.sin(k),R=X-Ue,W=E-Ke;h(()=>({zoom:m,x:X-M*(R*ot-W*P),y:E-M*(R*P+W*ot),rotation:Y}));},He=r=>{r.preventDefault(),u=false;};It.current.enabled&&!t.hasAttribute("tabindex")&&t.setAttribute("tabindex","0");let qe=r=>{if(!G.current)return;let l=It.current;if(!l.enabled)return;let i=r.target?.tagName;if(i==="INPUT"||i==="TEXTAREA"||i==="SELECT")return;let p=U.current,v=false;switch(r.key){case "ArrowLeft":p.pan&&(h(m=>({...m,x:m.x+l.panStep})),v=true);break;case "ArrowRight":p.pan&&(h(m=>({...m,x:m.x-l.panStep})),v=true);break;case "ArrowUp":p.pan&&(h(m=>({...m,x:m.x,y:m.y+l.panStep})),v=true);break;case "ArrowDown":p.pan&&(h(m=>({...m,x:m.x,y:m.y-l.panStep})),v=true);break;case "+":case "=":if(p.zoom){let{minScale:m,maxScale:L}=D.current,A=t,Y=A.offsetWidth/2,X=A.offsetHeight/2;h(E=>{let M=I(E.zoom*l.zoomStep,m,L),k=M/E.zoom;return {...E,zoom:M,x:Y-(Y-E.x)*k,y:X-(X-E.y)*k}}),v=true;}break;case "-":if(p.zoom){let{minScale:m,maxScale:L}=D.current,A=t,Y=A.offsetWidth/2,X=A.offsetHeight/2;h(E=>{let M=I(E.zoom/l.zoomStep,m,L),k=M/E.zoom;return {...E,zoom:M,x:Y-(Y-E.x)*k,y:X-(X-E.y)*k}}),v=true;}break;case "0":w(),h(()=>$.current),v=true;break;case "[":if(p.rotate){let m=q.current?.rotate;if(m&&!o.has(m))break;h(L=>({...L,rotation:(L.rotation??0)-l.rotateStep})),v=true;}break;case "]":if(p.rotate){let m=q.current?.rotate;if(m&&!o.has(m))break;h(L=>({...L,rotation:(L.rotation??0)+l.rotateStep})),v=true;}break}v&&r.preventDefault();},Ut=null,Kt=r=>{t.style.cursor=r;},Wt=tt.current;Wt&&Wt.enabled&&(Ut=t.style.cursor,Kt(Wt.idle??"grab"));let vt=()=>o.clear();window.addEventListener("blur",vt),document.addEventListener("visibilitychange",vt);let _e=r=>{Rt.current===2&&r.preventDefault();};return t.addEventListener("contextmenu",_e),t.addEventListener("wheel",d,{passive:false}),t.addEventListener("pointerdown",Ce),window.addEventListener("pointermove",Ze,{passive:false}),window.addEventListener("pointerup",pt),window.addEventListener("pointercancel",pt),t.addEventListener("touchstart",Be,{passive:false}),t.addEventListener("touchmove",Fe,{passive:false}),t.addEventListener("touchend",xt),t.addEventListener("touchcancel",xt),t.addEventListener("gesturestart",We,{passive:false}),t.addEventListener("gesturechange",Ge,{passive:false}),t.addEventListener("gestureend",He,{passive:false}),t.addEventListener("keydown",qe),()=>{w(),a&&clearTimeout(a),Ut!==null&&(t.style.cursor=Ut),t.removeEventListener("wheel",d),t.removeEventListener("pointerdown",Ce),window.removeEventListener("pointermove",Ze),window.removeEventListener("pointerup",pt),window.removeEventListener("pointercancel",pt),t.removeEventListener("touchstart",Be),t.removeEventListener("touchmove",Fe),t.removeEventListener("touchend",xt),t.removeEventListener("touchcancel",xt),t.removeEventListener("gesturestart",We),t.removeEventListener("gesturechange",Ge),t.removeEventListener("gestureend",He),t.removeEventListener("keydown",qe),window.removeEventListener("blur",vt),document.removeEventListener("visibilitychange",vt),t.removeEventListener("contextmenu",_e),window.removeEventListener("keydown",e),window.removeEventListener("keyup",c);}},[y,h,w,T,Zt,ct]);let Vt=react.useCallback((t,n)=>{if(n?.animate){T(t,n);return}if(w(),n?.skipConstraints){ft(t);return}h(()=>t);},[T,w,h,ft]),_=react.useCallback((t,n)=>{let{minScale:o,maxScale:e}=D.current,c=I(t,o,e),s=(y.current?.offsetWidth??0)/2,a=(y.current?.offsetHeight??0)/2,u=x.current,d=c/u.zoom,f={zoom:c,x:s-(s-u.x)*d,y:a-(a-u.y)*d};n?.animate?T(f,n):(w(),h(()=>f));},[y,h,T,w]),nn=react.useCallback(t=>Vt($.current,t),[Vt]),on=react.useCallback((t=1.5,n)=>{_(x.current.zoom*t,n);},[_]),rn=react.useCallback((t=1.5,n)=>{_(x.current.zoom/t,n);},[_]),sn=react.useCallback((t,n,o)=>{let e=y.current;if(!e)return;let c=x.current,s={x:e.offsetWidth/2-t*c.zoom,y:e.offsetHeight/2-n*c.zoom,zoom:c.zoom};o?.animate?T(s,o):(w(),h(()=>s));},[y,h,T,w]),cn=react.useCallback((t,n,o)=>{let e=x.current,c={x:e.x+t,y:e.y+n,zoom:e.zoom};o?.animate?T(c,o):(w(),h(()=>c));},[h,T,w]),an=react.useCallback((t,n,o)=>{let e=y.current;if(!e)return;let{minScale:c,maxScale:s}=D.current,a=I(t,c,s),u=x.current,d=a/u.zoom,f;if(n)f={zoom:a,x:e.offsetWidth/2-n.x*a,y:e.offsetHeight/2-n.y*a};else {let g=e.offsetWidth/2,z=e.offsetHeight/2;f={zoom:a,x:g-(g-u.x)*d,y:z-(z-u.y)*d};}o?.animate?T(f,o):(w(),h(()=>f));},[y,h,T,w]),Pt=react.useCallback((t,n)=>{let o=y.current;if(!o)return;let{minScale:e,maxScale:c}=D.current,s=n?.padding??0,a=o.offsetWidth-s*2,u=o.offsetHeight-s*2;if(a<=0||u<=0)return;let d=I(Math.min(a/t.width,u/t.height),e,c),f={zoom:d,x:o.offsetWidth/2-(t.x+t.width/2)*d,y:o.offsetHeight/2-(t.y+t.height/2)*d};n?.animate?T(f,n):(w(),h(()=>f));},[y,h,T,w]),un=react.useCallback((t,n,o)=>{let e=y.current;if(!e)return;let c=e.getBoundingClientRect(),s=t.getBoundingClientRect(),a=x.current,u=(s.left-c.left-a.x)/a.zoom,d=(s.top-c.top-a.y)/a.zoom,f=s.width/a.zoom,g=s.height/a.zoom,{minScale:z,maxScale:S}=D.current,K=I(n??a.zoom,z,S),N={x:c.width/2-(u+f/2)*K,y:c.height/2-(d+g/2)*K,zoom:K};o?.animate?T(N,o):(w(),h(()=>N));},[y,h,T,w]),ln=react.useCallback((t,n)=>{let e={...x.current,rotation:t};n?.animate?T(e,n):(w(),h(()=>e));},[h,T,w]),mn=react.useCallback((t,n)=>{let o=x.current,e={...o,rotation:(o.rotation??0)+t};n?.animate?T(e,n):(w(),h(()=>e));},[h,T,w]),fn=react.useCallback((t,n)=>{let o=y.current;if(!o)return {x:0,y:0};let e=o.getBoundingClientRect(),c=x.current,s=t-e.left-c.x,a=n-e.top-c.y,u=c.rotation??0;if(u===0)return {x:s/c.zoom,y:a/c.zoom};let d=-u*Math.PI/180,f=Math.cos(d),g=Math.sin(d),z=s*f-a*g,S=s*g+a*f;return {x:z/c.zoom,y:S/c.zoom}},[y]),dn=react.useCallback((t,n)=>{let o=y.current;if(!o)return {x:0,y:0};let e=o.getBoundingClientRect(),c=x.current,s=c.rotation??0,a=t*c.zoom,u=n*c.zoom;if(s===0)return {x:a+c.x+e.left,y:u+c.y+e.top};let d=s*Math.PI/180,f=Math.cos(d),g=Math.sin(d),z=a*f-u*g,S=a*g+u*f;return {x:z+c.x+e.left,y:S+c.y+e.top}},[y]),dt=react.useCallback(t=>{let n=y.current,o=kt.current;!n||!o||Pt({x:0,y:0,width:o.width,height:o.height},t);},[y,Pt]),yn=react.useCallback(t=>{let n=Yt.current;if(!n||n.length===0)return;let o=x.current,e=n[0],c=Math.abs(o.zoom-e);for(let s=1;s<n.length;s++){let a=Math.abs(o.zoom-n[s]);a<c&&(c=a,e=n[s]);}_(e,t);},[_]);return react.useEffect(()=>{let t=y.current,n=kt.current;if(!t||!n)return;dt();let o=null,e=new ResizeObserver(()=>{o&&clearTimeout(o),o=setTimeout(()=>{o=null,dt();},150);});return e.observe(t),()=>{o&&clearTimeout(o),e.disconnect();}},[y,dt]),{view:Ot,isAnimating:en,setView:Vt,centerZoom:_,resetView:nn,zoomIn:on,zoomOut:rn,zoomToElement:un,panTo:sn,panBy:cn,zoomTo:an,fitToRect:Pt,rotateTo:ln,rotateBy:mn,screenToContent:fn,contentToScreen:dn,fitToContent:dt,snapZoom:yn}}function I(y,B,F){return Math.max(B,Math.min(y,F))}function qt(y,B,F,J){let st=y-F,Q=B-J;return Math.sqrt(st*st+Q*Q)}function _t(y,B,F,J){return Math.atan2(J-B,F-y)*(180/Math.PI)}exports.angleBetween=_t;exports.clamp=I;exports.distance=qt;exports.easeInOut=xn;exports.easeOut=Gt;exports.linear=hn;exports.useZoomPinch=vn;
|
package/dist/index.d.cts
CHANGED
|
@@ -15,6 +15,13 @@ interface AnimationOptions {
|
|
|
15
15
|
duration?: number;
|
|
16
16
|
/** Easing function. @default easeOut */
|
|
17
17
|
easing?: EasingFunction;
|
|
18
|
+
/**
|
|
19
|
+
* Skip bounds clamping, axis locking, and snap-to-grid when set via `setView`.
|
|
20
|
+
* Useful when you need to position the viewport precisely, even outside bounds.
|
|
21
|
+
* Only affects `setView`; gestures are always constrained.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
skipConstraints?: boolean;
|
|
18
25
|
}
|
|
19
26
|
interface DoubleTapOptions {
|
|
20
27
|
/** Enable double-tap gesture. @default true */
|
|
@@ -185,7 +192,7 @@ interface UseZoomPinchReturn {
|
|
|
185
192
|
zoomToElement: (el: HTMLElement, scale?: number, options?: AnimationOptions) => void;
|
|
186
193
|
/** Pan to a content-space coordinate, keeping current zoom. */
|
|
187
194
|
panTo: (x: number, y: number, options?: AnimationOptions) => void;
|
|
188
|
-
/** Pan by a relative delta in
|
|
195
|
+
/** Pan by a relative delta in screen-space pixels (applied directly to x/y). */
|
|
189
196
|
panBy: (dx: number, dy: number, options?: AnimationOptions) => void;
|
|
190
197
|
/** Zoom to a specific level, optionally centered on a content-space point. */
|
|
191
198
|
zoomTo: (zoom: number, point?: {
|
|
@@ -230,9 +237,15 @@ interface UseZoomPinchReturn {
|
|
|
230
237
|
* Works in both controlled (`viewState` + `onViewStateChange`) and uncontrolled modes.
|
|
231
238
|
*/
|
|
232
239
|
declare function useZoomPinch({ containerRef, minScale, maxScale, panSpeed, zoomSpeed, initialViewState, viewState, onViewStateChange, enabled, shouldHandleEvent, gestures: gesturesProp, bounds, panButton, keyboard: keyboardProp, zoomSnapLevels, snapToGrid: snapToGridProp, contentRect, rotation: rotationOptions, wheelMode, cursor: cursorProp, axis, activationKeys, onPanStart, onPanEnd, onZoomStart, onZoomEnd, onPinchStart, onPinchEnd, onRotateStart, onRotateEnd, onTransformEnd, doubleTap: doubleTapProp, inertia: inertiaProp, }: UseZoomPinchOptions): UseZoomPinchReturn;
|
|
240
|
+
/** Clamp `value` to the inclusive [min, max] range. */
|
|
241
|
+
declare function clamp(value: number, min: number, max: number): number;
|
|
242
|
+
/** Euclidean distance between two points (x1,y1) and (x2,y2). */
|
|
243
|
+
declare function distance(x1: number, y1: number, x2: number, y2: number): number;
|
|
244
|
+
/** Angle in degrees of the vector from (x1,y1) to (x2,y2). */
|
|
245
|
+
declare function angleBetween(x1: number, y1: number, x2: number, y2: number): number;
|
|
233
246
|
|
|
234
247
|
declare const linear: EasingFunction;
|
|
235
248
|
declare const easeOut: EasingFunction;
|
|
236
249
|
declare const easeInOut: EasingFunction;
|
|
237
250
|
|
|
238
|
-
export { type ActivationKeyOptions, type AnimationOptions, type BoundsOptions, type CursorOptions, type DoubleTapOptions, type EasingFunction, type GesturesOptions, type InertiaOptions, type KeyboardOptions, type RotationOptions, type SnapToGridOptions, type UseZoomPinchOptions, type UseZoomPinchReturn, type ViewState, type ZoomSnapLevel, easeInOut, easeOut, linear, useZoomPinch };
|
|
251
|
+
export { type ActivationKeyOptions, type AnimationOptions, type BoundsOptions, type CursorOptions, type DoubleTapOptions, type EasingFunction, type GesturesOptions, type InertiaOptions, type KeyboardOptions, type RotationOptions, type SnapToGridOptions, type UseZoomPinchOptions, type UseZoomPinchReturn, type ViewState, type ZoomSnapLevel, angleBetween, clamp, distance, easeInOut, easeOut, linear, useZoomPinch };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ interface AnimationOptions {
|
|
|
15
15
|
duration?: number;
|
|
16
16
|
/** Easing function. @default easeOut */
|
|
17
17
|
easing?: EasingFunction;
|
|
18
|
+
/**
|
|
19
|
+
* Skip bounds clamping, axis locking, and snap-to-grid when set via `setView`.
|
|
20
|
+
* Useful when you need to position the viewport precisely, even outside bounds.
|
|
21
|
+
* Only affects `setView`; gestures are always constrained.
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
skipConstraints?: boolean;
|
|
18
25
|
}
|
|
19
26
|
interface DoubleTapOptions {
|
|
20
27
|
/** Enable double-tap gesture. @default true */
|
|
@@ -185,7 +192,7 @@ interface UseZoomPinchReturn {
|
|
|
185
192
|
zoomToElement: (el: HTMLElement, scale?: number, options?: AnimationOptions) => void;
|
|
186
193
|
/** Pan to a content-space coordinate, keeping current zoom. */
|
|
187
194
|
panTo: (x: number, y: number, options?: AnimationOptions) => void;
|
|
188
|
-
/** Pan by a relative delta in
|
|
195
|
+
/** Pan by a relative delta in screen-space pixels (applied directly to x/y). */
|
|
189
196
|
panBy: (dx: number, dy: number, options?: AnimationOptions) => void;
|
|
190
197
|
/** Zoom to a specific level, optionally centered on a content-space point. */
|
|
191
198
|
zoomTo: (zoom: number, point?: {
|
|
@@ -230,9 +237,15 @@ interface UseZoomPinchReturn {
|
|
|
230
237
|
* Works in both controlled (`viewState` + `onViewStateChange`) and uncontrolled modes.
|
|
231
238
|
*/
|
|
232
239
|
declare function useZoomPinch({ containerRef, minScale, maxScale, panSpeed, zoomSpeed, initialViewState, viewState, onViewStateChange, enabled, shouldHandleEvent, gestures: gesturesProp, bounds, panButton, keyboard: keyboardProp, zoomSnapLevels, snapToGrid: snapToGridProp, contentRect, rotation: rotationOptions, wheelMode, cursor: cursorProp, axis, activationKeys, onPanStart, onPanEnd, onZoomStart, onZoomEnd, onPinchStart, onPinchEnd, onRotateStart, onRotateEnd, onTransformEnd, doubleTap: doubleTapProp, inertia: inertiaProp, }: UseZoomPinchOptions): UseZoomPinchReturn;
|
|
240
|
+
/** Clamp `value` to the inclusive [min, max] range. */
|
|
241
|
+
declare function clamp(value: number, min: number, max: number): number;
|
|
242
|
+
/** Euclidean distance between two points (x1,y1) and (x2,y2). */
|
|
243
|
+
declare function distance(x1: number, y1: number, x2: number, y2: number): number;
|
|
244
|
+
/** Angle in degrees of the vector from (x1,y1) to (x2,y2). */
|
|
245
|
+
declare function angleBetween(x1: number, y1: number, x2: number, y2: number): number;
|
|
233
246
|
|
|
234
247
|
declare const linear: EasingFunction;
|
|
235
248
|
declare const easeOut: EasingFunction;
|
|
236
249
|
declare const easeInOut: EasingFunction;
|
|
237
250
|
|
|
238
|
-
export { type ActivationKeyOptions, type AnimationOptions, type BoundsOptions, type CursorOptions, type DoubleTapOptions, type EasingFunction, type GesturesOptions, type InertiaOptions, type KeyboardOptions, type RotationOptions, type SnapToGridOptions, type UseZoomPinchOptions, type UseZoomPinchReturn, type ViewState, type ZoomSnapLevel, easeInOut, easeOut, linear, useZoomPinch };
|
|
251
|
+
export { type ActivationKeyOptions, type AnimationOptions, type BoundsOptions, type CursorOptions, type DoubleTapOptions, type EasingFunction, type GesturesOptions, type InertiaOptions, type KeyboardOptions, type RotationOptions, type SnapToGridOptions, type UseZoomPinchOptions, type UseZoomPinchReturn, type ViewState, type ZoomSnapLevel, angleBetween, clamp, distance, easeInOut, easeOut, linear, useZoomPinch };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import {useState,useRef,useCallback,useEffect}from'react';var fn=y=>y,Vt=y=>1-(1-y)**3,dn=y=>y<.5?4*y*y*y:1-(-2*y+2)**3/2;var Pt={x:0,y:0,zoom:1,rotation:0},yn={pan:true,zoom:true,rotate:false},Bt={enabled:false,panStep:50,zoomStep:1.5,rotateStep:15};function pn({containerRef:y,minScale:B=.1,maxScale:F=50,panSpeed:q=1,zoomSpeed:J=1,initialViewState:st=Pt,viewState:qe,onViewStateChange:Ft,enabled:Ut=true,shouldHandleEvent:Kt,gestures:_e,bounds:Wt,panButton:Gt=0,keyboard:gt,zoomSnapLevels:Ht,snapToGrid:bt,contentRect:qt,rotation:_t,wheelMode:Nt="pan",cursor:jt,axis:Jt,activationKeys:Qt,onPanStart:$t,onPanEnd:te,onZoomStart:ee,onZoomEnd:ne,onPinchStart:oe,onPinchEnd:re,onRotateStart:se,onRotateEnd:ce,onTransformEnd:ie,doubleTap:ae,inertia:ue}){let[Ne,je]=useState(st),zt=qe??Ne,g=useRef(zt);g.current=zt;let W=useRef(Ut);W.current=Ut;let vt=useRef(Ft);vt.current=Ft;let k=useRef({minScale:B,maxScale:F,panSpeed:q,zoomSpeed:J});k.current={minScale:B,maxScale:F,panSpeed:q,zoomSpeed:J};let C=useRef(Kt);C.current=Kt;let le=ae===false?false:{enabled:true,mode:"toggle",step:2,...ae},me=useRef(le);me.current=le;let fe=ue===false?false:{enabled:true,friction:.92,...ue},de=useRef(fe);de.current=fe;let ye=useRef($t);ye.current=$t;let pe=useRef(te);pe.current=te;let he=useRef(ee);he.current=ee;let xe=useRef(ne);xe.current=ne;let ge=useRef(oe);ge.current=oe;let be=useRef(re);be.current=re;let ze=useRef(se);ze.current=se;let ve=useRef(ce);ve.current=ce;let ct=useRef(ie);ct.current=ie;let we={...yn,..._e},U=useRef(we);U.current=we;let wt=useRef(Wt);wt.current=Wt;let Ee=useRef(Gt);Ee.current=Gt;let Se=gt===true?{...Bt,enabled:true}:gt?{...Bt,...gt}:Bt,Et=useRef(Se);Et.current=Se;let St=useRef(Ht);St.current=Ht;let Oe=bt===false||bt===void 0?false:{mode:"end",...bt},Ot=useRef(Oe);Ot.current=Oe;let Tt=useRef(qt);Tt.current=qt;let Lt=useRef(_t);Lt.current=_t;let Te=useRef(Nt);Te.current=Nt;let Le=jt===false?false:{enabled:true,idle:"grab",dragging:"grabbing",zooming:"zoom-in",...jt},it=useRef(Le);it.current=Le;let Me=useRef(Jt);Me.current=Jt;let Mt=useRef(Qt);Mt.current=Qt;let[Je,At]=useState(false),_=useRef(null),N=useRef(null),Rt=useCallback(()=>{N.current!==null&&(cancelAnimationFrame(N.current),N.current=null);},[]),w=useCallback(()=>{_.current!==null&&(cancelAnimationFrame(_.current),_.current=null,At(false)),Rt();},[Rt]),h=useCallback((e,{isBounceSnap:n=false}={})=>{let r=g.current,t=e(r),s=Me.current;s==="x"?t={...t,y:r.y}:s==="y"&&(t={...t,x:r.x});let u=wt.current;if(u){let d=u.minX??-1/0,f=u.maxX??1/0,x=u.minY??-1/0,b=u.maxY??1/0;if(u.mode==="bounce"&&!n){let L=u.bounceFactor??.3;t.x<d?t={...t,x:d+(t.x-d)*L}:t.x>f&&(t={...t,x:f+(t.x-f)*L}),t.y<x?t={...t,y:x+(t.y-x)*L}:t.y>b&&(t={...t,y:b+(t.y-b)*L});}else t={...t,x:R(t.x,d,f),y:R(t.y,x,b)};}let i=Lt.current;i&&t.rotation!==void 0&&(t={...t,rotation:R(t.rotation,i.minAngle??-1/0,i.maxAngle??1/0)});let l=Ot.current;l&&l.mode==="always"&&(t={...t,x:Math.round(t.x/l.size)*l.size,y:Math.round(t.y/l.size)*l.size}),!(t.x===r.x&&t.y===r.y&&t.zoom===r.zoom&&(t.rotation??0)===(r.rotation??0))&&(vt.current?vt.current(t):je(t));},[]),S=useCallback((e,n={})=>{w(),At(true);let{duration:r=300,easing:t=Vt}=n,s={...g.current},u=performance.now(),i=s.rotation??0,l=e.rotation??0,d=()=>{let f=performance.now()-u,x=Math.min(f/r,1),b=t(x);h(()=>({x:s.x+(e.x-s.x)*b,y:s.y+(e.y-s.y)*b,zoom:s.zoom+(e.zoom-s.zoom)*b,rotation:i+(l-i)*b}),{isBounceSnap:true}),x<1?_.current=requestAnimationFrame(d):(_.current=null,At(false));};_.current=requestAnimationFrame(d);},[w,h]),Q=useCallback(()=>{let e=g.current,n={...e},r=false,t=St.current;if(t&&t.length>0){let l=t[0],d=Math.abs(e.zoom-l);for(let f=1;f<t.length;f++){let x=Math.abs(e.zoom-t[f]);x<d&&(d=x,l=t[f]);}if(l!==e.zoom){let{minScale:f,maxScale:x}=k.current,b=R(l,f,x),L=y.current;if(L){let K=L.offsetWidth/2,H=L.offsetHeight/2,Z=b/e.zoom;n={...n,zoom:b,x:K-(K-n.x)*Z,y:H-(H-n.y)*Z},r=true;}}}let s=wt.current;if(s&&s.mode==="bounce"){let l=s.minX??-1/0,d=s.maxX??1/0,f=s.minY??-1/0,x=s.maxY??1/0,b=R(n.x,l,d),L=R(n.y,f,x);(b!==n.x||L!==n.y)&&(n={...n,x:b,y:L},r=true);}let u=Lt.current;if(u?.snapLevels&&u.snapLevels.length>0){let l=n.rotation??0,d=u.snapLevels[0],f=Math.abs(l-d);for(let x=1;x<u.snapLevels.length;x++){let b=Math.abs(l-u.snapLevels[x]);b<f&&(f=b,d=u.snapLevels[x]);}d!==l&&(n={...n,rotation:d},r=true);}let i=Ot.current;if(i&&i.mode==="end"){let l=Math.round(n.x/i.size)*i.size,d=Math.round(n.y/i.size)*i.size;(l!==n.x||d!==n.y)&&(n={...n,x:l,y:d},r=true);}r&&S(n,{duration:150});},[S,y]);useEffect(()=>{let e=y.current;if(!e)return;let n=(o,a)=>{let c=e.getBoundingClientRect();return {px:o-c.left,py:a-c.top}},r=new Set,t=o=>r.add(o.key),s=o=>r.delete(o.key);window.addEventListener("keydown",t),window.addEventListener("keyup",s);let u=false,i=null,l=false,d=o=>{if(!W.current||C.current&&!C.current(o))return;o.preventDefault(),w();let a=o.deltaX,c=o.deltaY,{ctrlKey:p,clientX:v,clientY:m}=o,A=o.deltaMode===0&&(Math.abs(c)<100||!Number.isInteger(c));A||(Math.abs(c)>=100&&(c=Math.sign(c)*(Math.abs(c)/120)),Math.abs(a)>=100&&(a=Math.sign(a)*(Math.abs(a)/120)));let{minScale:O,maxScale:I,panSpeed:Y,zoomSpeed:E}=k.current,M=U.current,D=Mt.current,P=Te.current==="zoom"?!p:p;P&&D?.zoom&&!r.has(D.zoom)||!P&&D?.pan&&!r.has(D.pan)||h(X=>{if(P){if(!M.zoom||l)return X;u||(u=true,he.current?.(X)),i&&clearTimeout(i),i=setTimeout(()=>{u=false,xe.current?.(g.current),ct.current?.(g.current),Q();},150);let pt=A?.01:.1*E,ht=1-c*pt,rt=R(X.zoom*ht,O,I);if(rt===X.zoom)return X;let{px:xt,py:Fe}=n(v,m),Ue=rt/X.zoom;return {zoom:rt,x:xt-(xt-X.x)*Ue,y:Fe-(Fe-X.y)*Ue}}if(!M.pan)return X;let ot=A?1:25*Y;return {...X,x:X.x-a*ot,y:X.y-c*ot}});},f=0,x=0,b=0,L=300,K=25,H=(o,a)=>{let c=me.current;if(c===false||!c.enabled)return;let{mode:p="toggle",step:v=2}=c,{minScale:m,maxScale:A}=k.current,O=g.current,{px:I,py:Y}=n(o,a),E;if(p==="zoomIn"?E=R(O.zoom*v,m,A):p==="reset"?E=1:E=O.zoom>1.05?1:R(O.zoom*v,m,A),p==="reset"||p==="toggle"&&O.zoom>1.05)S({x:0,y:0,zoom:1},{duration:300});else {let M=E/O.zoom;S({zoom:E,x:I-(I-O.x)*M,y:Y-(Y-O.y)*M},{duration:300});}},Z=false,ut={x:0,y:0},Xt=false,$=new Set,tt=0,et=0,Dt=0,Ae=o=>{if(!W.current||C.current&&!C.current(o)||(w(),$.add(o.pointerId),o.pointerType==="mouse"&&o.button!==Ee.current))return;if($.size>=2){Z=false;return}if(!U.current.pan)return;let a=Mt.current?.pan;if(a&&!r.has(a))return;Z=true,Xt=false;let c=it.current;c&&c.enabled&&Ct(c.dragging??"grabbing"),ut={x:o.clientX,y:o.clientY},tt=0,et=0,Dt=performance.now(),ye.current?.(g.current);},Re=o=>{if(!Z)return;o.preventDefault();let a=performance.now(),c=o.clientX-ut.x,p=o.clientY-ut.y,v=a-Dt;v>0&&(tt=.8*(c/v*16)+(1-.8)*tt,et=.8*(p/v*16)+(1-.8)*et),ut={x:o.clientX,y:o.clientY},Dt=a,Xt=true,h(m=>({...m,x:m.x+c,y:m.y+p}));},mn=()=>{let o=de.current;if(o===false||!o.enabled||Math.abs(tt)<.5&&Math.abs(et)<.5)return false;let{friction:a=.92}=o,c=tt,p=et,v=()=>{if(c*=a,p*=a,Math.abs(c)<.5&&Math.abs(p)<.5){N.current=null,Q();return}h(m=>({...m,x:m.x+c,y:m.y+p})),N.current=requestAnimationFrame(v);};return N.current=requestAnimationFrame(v),true},lt=o=>{let a=Z;if($.delete(o.pointerId),$.size===0&&(Z=false),a&&$.size===0){pe.current?.(g.current),ct.current?.(g.current);let c=it.current;if(c&&c.enabled&&Ct(c.idle??"grab"),Xt)mn()||Q(),f=0;else {let p=performance.now(),v=o.clientX-x,m=o.clientY-b,A=Math.sqrt(v*v+m*m);p-f<L&&A<K?(H(o.clientX,o.clientY),f=0):(f=p,x=o.clientX,b=o.clientY);}}},V={zoom:1,x:0,y:0,rotation:0},Ie=0,Ye=0,nt={x:0,y:0},mt=false,Xe=o=>{if(!W.current||o.touches.length<2||C.current&&!C.current(o))return;let a=U.current;if(!a.zoom&&!a.rotate)return;w(),Z=false,mt=true;let c=o.touches[0],p=o.touches[1];Ie=Ge(c.clientX,c.clientY,p.clientX,p.clientY),Ye=He(c.clientX,c.clientY,p.clientX,p.clientY),nt={x:(c.clientX+p.clientX)/2,y:(c.clientY+p.clientY)/2},V={...g.current,rotation:g.current.rotation??0},ge.current?.(g.current),a.rotate&&ze.current?.(g.current);},De=o=>{if(o.touches.length<2||!mt||C.current&&!C.current(o))return;o.preventDefault();let a=o.touches[0],c=o.touches[1],p=U.current,{minScale:v,maxScale:m}=k.current,A=Ge(a.clientX,a.clientY,c.clientX,c.clientY),O={x:(a.clientX+c.clientX)/2,y:(a.clientY+c.clientY)/2},I=p.zoom?R(V.zoom*(A/Ie),v,m):V.zoom,Y=I/V.zoom,{px:E,py:M}=n(nt.x,nt.y),D=He(a.clientX,a.clientY,c.clientX,c.clientY),j=p.rotate&&!l?V.rotation+(D-Ye):V.rotation??0,P=(j-(V.rotation??0))*Math.PI/180,X=Math.cos(P),ot=Math.sin(P),pt=E-V.x,ht=M-V.y,rt=O.x-nt.x,xt=O.y-nt.y;h(()=>({zoom:I,x:E+rt-Y*(pt*X-ht*ot),y:M+xt-Y*(pt*ot+ht*X),rotation:j}));},ft=o=>{o.touches.length<2&&mt&&(mt=false,be.current?.(g.current),U.current.rotate&&ve.current?.(g.current),ct.current?.(g.current),Q());},dt=0,yt=1,ke=0,Ce=0,Ze=o=>{if(!W.current)return;o.preventDefault(),w(),l=true;let a=g.current;dt=a.rotation??0,yt=a.zoom,ke=a.x,Ce=a.y;},Ve=o=>{if(!W.current||!l)return;o.preventDefault();let a=o,c=U.current,{minScale:p,maxScale:v}=k.current,m=c.zoom?R(yt*a.scale,p,v):yt,A=c.rotate?dt+a.rotation:dt,O=(e.offsetWidth??0)/2,I=(e.offsetHeight??0)/2,Y=m/yt,E=(A-dt)*Math.PI/180,M=Math.cos(E),D=Math.sin(E),j=O-ke,P=I-Ce;h(()=>({zoom:m,x:O-Y*(j*M-P*D),y:I-Y*(j*D+P*M),rotation:A}));},Pe=o=>{o.preventDefault(),l=false;};Et.current.enabled&&!e.hasAttribute("tabindex")&&e.setAttribute("tabindex","0");let Be=o=>{if(!W.current)return;let a=Et.current;if(!a.enabled)return;let c=o.target?.tagName;if(c==="INPUT"||c==="TEXTAREA"||c==="SELECT")return;let p=U.current,v=false;switch(o.key){case "ArrowLeft":p.pan&&(h(m=>({...m,x:m.x+a.panStep})),v=true);break;case "ArrowRight":p.pan&&(h(m=>({...m,x:m.x-a.panStep})),v=true);break;case "ArrowUp":p.pan&&(h(m=>({...m,x:m.x,y:m.y+a.panStep})),v=true);break;case "ArrowDown":p.pan&&(h(m=>({...m,x:m.x,y:m.y-a.panStep})),v=true);break;case "+":case "=":if(p.zoom){let{minScale:m,maxScale:A}=k.current,O=e,I=O.offsetWidth/2,Y=O.offsetHeight/2;h(E=>{let M=R(E.zoom*a.zoomStep,m,A),D=M/E.zoom;return {...E,zoom:M,x:I-(I-E.x)*D,y:Y-(Y-E.y)*D}}),v=true;}break;case "-":if(p.zoom){let{minScale:m,maxScale:A}=k.current,O=e,I=O.offsetWidth/2,Y=O.offsetHeight/2;h(E=>{let M=R(E.zoom/a.zoomStep,m,A),D=M/E.zoom;return {...E,zoom:M,x:I-(I-E.x)*D,y:Y-(Y-E.y)*D}}),v=true;}break;case "0":w(),h(()=>Pt),v=true;break;case "[":p.rotate&&(h(m=>({...m,rotation:(m.rotation??0)-a.rotateStep})),v=true);break;case "]":p.rotate&&(h(m=>({...m,rotation:(m.rotation??0)+a.rotateStep})),v=true);break}v&&o.preventDefault();},kt=null,Ct=o=>{e.style.cursor=o;},Zt=it.current;return Zt&&Zt.enabled&&(kt=e.style.cursor,Ct(Zt.idle??"grab")),e.addEventListener("wheel",d,{passive:false}),e.addEventListener("pointerdown",Ae),window.addEventListener("pointermove",Re,{passive:false}),window.addEventListener("pointerup",lt),window.addEventListener("pointercancel",lt),e.addEventListener("touchstart",Xe,{passive:false}),e.addEventListener("touchmove",De,{passive:false}),e.addEventListener("touchend",ft),e.addEventListener("touchcancel",ft),e.addEventListener("gesturestart",Ze,{passive:false}),e.addEventListener("gesturechange",Ve,{passive:false}),e.addEventListener("gestureend",Pe,{passive:false}),e.addEventListener("keydown",Be),()=>{w(),i&&clearTimeout(i),kt!==null&&(e.style.cursor=kt),e.removeEventListener("wheel",d),e.removeEventListener("pointerdown",Ae),window.removeEventListener("pointermove",Re),window.removeEventListener("pointerup",lt),window.removeEventListener("pointercancel",lt),e.removeEventListener("touchstart",Xe),e.removeEventListener("touchmove",De),e.removeEventListener("touchend",ft),e.removeEventListener("touchcancel",ft),e.removeEventListener("gesturestart",Ze),e.removeEventListener("gesturechange",Ve),e.removeEventListener("gestureend",Pe),e.removeEventListener("keydown",Be),window.removeEventListener("keydown",t),window.removeEventListener("keyup",s);}},[y,h,w,S,Rt,Q]);let It=useCallback((e,n)=>{if(n?.animate){S(e,n);return}w(),h(()=>e);},[S,w,h]),G=useCallback((e,n)=>{let{minScale:r,maxScale:t}=k.current,s=R(e,r,t),u=(y.current?.offsetWidth??0)/2,i=(y.current?.offsetHeight??0)/2,l=g.current,d=s/l.zoom,f={zoom:s,x:u-(u-l.x)*d,y:i-(i-l.y)*d};n?.animate?S(f,n):(w(),h(()=>f));},[y,h,S,w]),Qe=useCallback(e=>It(Pt,e),[It]),$e=useCallback((e=1.5,n)=>{G(g.current.zoom*e,n);},[G]),tn=useCallback((e=1.5,n)=>{G(g.current.zoom/e,n);},[G]),en=useCallback((e,n,r)=>{let t=y.current;if(!t)return;let s=g.current,u={x:t.offsetWidth/2-e*s.zoom,y:t.offsetHeight/2-n*s.zoom,zoom:s.zoom};r?.animate?S(u,r):(w(),h(()=>u));},[y,h,S,w]),nn=useCallback((e,n,r)=>{let t=g.current,s={x:t.x+e,y:t.y+n,zoom:t.zoom};r?.animate?S(s,r):(w(),h(()=>s));},[h,S,w]),on=useCallback((e,n,r)=>{let t=y.current;if(!t)return;let{minScale:s,maxScale:u}=k.current,i=R(e,s,u),l=g.current,d=i/l.zoom,f;if(n)f={zoom:i,x:t.offsetWidth/2-n.x*i,y:t.offsetHeight/2-n.y*i};else {let x=t.offsetWidth/2,b=t.offsetHeight/2;f={zoom:i,x:x-(x-l.x)*d,y:b-(b-l.y)*d};}r?.animate?S(f,r):(w(),h(()=>f));},[y,h,S,w]),Yt=useCallback((e,n)=>{let r=y.current;if(!r)return;let{minScale:t,maxScale:s}=k.current,u=n?.padding??0,i=r.offsetWidth-u*2,l=r.offsetHeight-u*2;if(i<=0||l<=0)return;let d=R(Math.min(i/e.width,l/e.height),t,s),f={zoom:d,x:r.offsetWidth/2-(e.x+e.width/2)*d,y:r.offsetHeight/2-(e.y+e.height/2)*d};n?.animate?S(f,n):(w(),h(()=>f));},[y,h,S,w]),rn=useCallback((e,n,r)=>{let t=y.current;if(!t)return;let s=t.getBoundingClientRect(),u=e.getBoundingClientRect(),i=g.current,l=(u.left-s.left-i.x)/i.zoom,d=(u.top-s.top-i.y)/i.zoom,f=u.width/i.zoom,x=u.height/i.zoom,{minScale:b,maxScale:L}=k.current,K=R(n??i.zoom,b,L),H={x:s.width/2-(l+f/2)*K,y:s.height/2-(d+x/2)*K,zoom:K};r?.animate?S(H,r):(w(),h(()=>H));},[y,h,S,w]),sn=useCallback((e,n)=>{let t={...g.current,rotation:e};n?.animate?S(t,n):(w(),h(()=>t));},[h,S,w]),cn=useCallback((e,n)=>{let r=g.current,t={...r,rotation:(r.rotation??0)+e};n?.animate?S(t,n):(w(),h(()=>t));},[h,S,w]),an=useCallback((e,n)=>{let r=y.current;if(!r)return {x:0,y:0};let t=r.getBoundingClientRect(),s=g.current,u=e-t.left-s.x,i=n-t.top-s.y,l=s.rotation??0;if(l===0)return {x:u/s.zoom,y:i/s.zoom};let d=-l*Math.PI/180,f=Math.cos(d),x=Math.sin(d),b=u*f-i*x,L=u*x+i*f;return {x:b/s.zoom,y:L/s.zoom}},[y]),un=useCallback((e,n)=>{let r=y.current;if(!r)return {x:0,y:0};let t=r.getBoundingClientRect(),s=g.current,u=s.rotation??0,i=e*s.zoom,l=n*s.zoom;if(u===0)return {x:i+s.x+t.left,y:l+s.y+t.top};let d=u*Math.PI/180,f=Math.cos(d),x=Math.sin(d),b=i*f-l*x,L=i*x+l*f;return {x:b+s.x+t.left,y:L+s.y+t.top}},[y]),at=useCallback(e=>{let n=y.current,r=Tt.current;!n||!r||Yt({x:0,y:0,width:r.width,height:r.height},e);},[y,Yt]),ln=useCallback(e=>{let n=St.current;if(!n||n.length===0)return;let r=g.current,t=n[0],s=Math.abs(r.zoom-t);for(let u=1;u<n.length;u++){let i=Math.abs(r.zoom-n[u]);i<s&&(s=i,t=n[u]);}G(t,e);},[G]);return useEffect(()=>{let e=y.current,n=Tt.current;if(!e||!n)return;at();let r=new ResizeObserver(()=>{at();});return r.observe(e),()=>r.disconnect()},[y,at]),{view:zt,isAnimating:Je,setView:It,centerZoom:G,resetView:Qe,zoomIn:$e,zoomOut:tn,zoomToElement:rn,panTo:en,panBy:nn,zoomTo:on,fitToRect:Yt,rotateTo:sn,rotateBy:cn,screenToContent:an,contentToScreen:un,fitToContent:at,snapZoom:ln}}function R(y,B,F){return Math.max(B,Math.min(y,F))}function Ge(y,B,F,q){let J=y-F,st=B-q;return Math.sqrt(J*J+st*st)}function He(y,B,F,q){return Math.atan2(q-B,F-y)*(180/Math.PI)}export{dn as easeInOut,Vt as easeOut,fn as linear,pn as useZoomPinch};
|
|
1
|
+
import {useState,useRef,useCallback,useEffect}from'react';var hn=y=>y,Gt=y=>1-(1-y)**3,xn=y=>y<.5?4*y*y*y:1-(-2*y+2)**3/2;var gn={x:0,y:0,zoom:1,rotation:0},bn={pan:true,zoom:true,rotate:false},Ht={enabled:false,panStep:50,zoomStep:1.5,rotateStep:15};function vn({containerRef:y,minScale:B=.1,maxScale:F=50,panSpeed:J=1,zoomSpeed:st=1,initialViewState:Q=gn,viewState:Je,onViewStateChange:Nt,enabled:jt=true,shouldHandleEvent:Jt,gestures:Qe,bounds:Qt,panButton:$t=0,keyboard:Tt,zoomSnapLevels:te,snapToGrid:Lt,contentRect:ee,rotation:ne,wheelMode:oe="pan",cursor:re,axis:se,activationKeys:ce,onPanStart:ie,onPanEnd:ae,onZoomStart:ue,onZoomEnd:le,onPinchStart:me,onPinchEnd:fe,onRotateStart:de,onRotateEnd:ye,onTransformEnd:pe,doubleTap:he,inertia:xe}){let[$e,tn]=useState(Q),Ot=Je??$e,x=useRef(Ot);x.current=Ot;let G=useRef(jt);G.current=jt;let At=useRef(Nt);At.current=Nt;let D=useRef({minScale:B,maxScale:F,panSpeed:J,zoomSpeed:st});D.current={minScale:B,maxScale:F,panSpeed:J,zoomSpeed:st};let $=useRef(Q);$.current=Q;let C=useRef(Jt);C.current=Jt;let ge=he===false?false:{enabled:true,mode:"toggle",step:2,...he},be=useRef(ge);be.current=ge;let ve=xe===false?false:{enabled:true,friction:.92,...xe},ze=useRef(ve);ze.current=ve;let we=useRef(ie);we.current=ie;let Ee=useRef(ae);Ee.current=ae;let Se=useRef(ue);Se.current=ue;let Te=useRef(le);Te.current=le;let Le=useRef(me);Le.current=me;let Oe=useRef(fe);Oe.current=fe;let Ae=useRef(de);Ae.current=de;let Me=useRef(ye);Me.current=ye;let H=useRef(pe);H.current=pe;let Re={...bn,...Qe},U=useRef(Re);U.current=Re;let Mt=useRef(Qt);Mt.current=Qt;let Rt=useRef($t);Rt.current=$t;let Ie=Tt===true?{...Ht,enabled:true}:Tt?{...Ht,...Tt}:Ht,It=useRef(Ie);It.current=Ie;let Yt=useRef(te);Yt.current=te;let Ye=Lt===false||Lt===void 0?false:{mode:"end",...Lt},Xt=useRef(Ye);Xt.current=Ye;let kt=useRef(ee);kt.current=ee;let Dt=useRef(ne);Dt.current=ne;let Xe=useRef(oe);Xe.current=oe;let ke=re===false?false:{enabled:true,idle:"grab",dragging:"grabbing",zooming:"zoom-in",...re},tt=useRef(ke);tt.current=ke;let De=useRef(se);De.current=se;let q=useRef(ce);q.current=ce;let[en,Ct]=useState(false),et=useRef(null),nt=useRef(null),Zt=useCallback(()=>{nt.current!==null&&(cancelAnimationFrame(nt.current),nt.current=null);},[]),w=useCallback(()=>{et.current!==null&&(cancelAnimationFrame(et.current),et.current=null,Ct(false)),Zt();},[Zt]),ft=useCallback(t=>{At.current?At.current(t):tn(t);},[]),h=useCallback((t,{isBounceSnap:n=false}={})=>{let o=x.current,e=t(o),c=De.current;c==="x"?e={...e,y:o.y}:c==="y"&&(e={...e,x:o.x});let s=Mt.current;if(s){let d=s.minX??-1/0,f=s.maxX??1/0,g=s.minY??-1/0,z=s.maxY??1/0;if(s.mode==="bounce"&&!n){let S=s.bounceFactor??.3;e.x<d?e={...e,x:d+(e.x-d)*S}:e.x>f&&(e={...e,x:f+(e.x-f)*S}),e.y<g?e={...e,y:g+(e.y-g)*S}:e.y>z&&(e={...e,y:z+(e.y-z)*S});}else e={...e,x:I(e.x,d,f),y:I(e.y,g,z)};}let a=Dt.current;a&&e.rotation!==void 0&&(e={...e,rotation:I(e.rotation,a.minAngle??-1/0,a.maxAngle??1/0)});let u=Xt.current;u&&u.mode==="always"&&(e={...e,x:Math.round(e.x/u.size)*u.size,y:Math.round(e.y/u.size)*u.size}),!(e.x===o.x&&e.y===o.y&&e.zoom===o.zoom&&(e.rotation??0)===(o.rotation??0))&&ft(e);},[ft]),T=useCallback((t,n={},o)=>{w();let{duration:e=300,easing:c=Gt}=n;if(e<=0){h(()=>t),o?.();return}Ct(true);let s={...x.current},a=performance.now(),u=s.rotation??0,d=t.rotation??0,f=()=>{let g=performance.now()-a,z=Math.min(g/e,1),S=c(z);h(()=>({x:s.x+(t.x-s.x)*S,y:s.y+(t.y-s.y)*S,zoom:s.zoom+(t.zoom-s.zoom)*S,rotation:u+(d-u)*S}),{isBounceSnap:true}),z<1?et.current=requestAnimationFrame(f):(et.current=null,Ct(false),o?.());};et.current=requestAnimationFrame(f);},[w,h]),ct=useCallback(()=>{let t=x.current,n={...t},o=false,e=Yt.current;if(e&&e.length>0){let u=e[0],d=Math.abs(t.zoom-u);for(let f=1;f<e.length;f++){let g=Math.abs(t.zoom-e[f]);g<d&&(d=g,u=e[f]);}if(u!==t.zoom){let{minScale:f,maxScale:g}=D.current,z=I(u,f,g),S=y.current;if(S){let K=S.offsetWidth/2,N=S.offsetHeight/2,Z=z/t.zoom;n={...n,zoom:z,x:K-(K-n.x)*Z,y:N-(N-n.y)*Z},o=true;}}}let c=Mt.current;if(c&&c.mode==="bounce"){let u=c.minX??-1/0,d=c.maxX??1/0,f=c.minY??-1/0,g=c.maxY??1/0,z=I(n.x,u,d),S=I(n.y,f,g);(z!==n.x||S!==n.y)&&(n={...n,x:z,y:S},o=true);}let s=Dt.current;if(s?.snapLevels&&s.snapLevels.length>0){let u=n.rotation??0,d=s.snapLevels[0],f=Math.abs(u-d);for(let g=1;g<s.snapLevels.length;g++){let z=Math.abs(u-s.snapLevels[g]);z<f&&(f=z,d=s.snapLevels[g]);}d!==u&&(n={...n,rotation:d},o=true);}let a=Xt.current;if(a&&a.mode==="end"){let u=Math.round(n.x/a.size)*a.size,d=Math.round(n.y/a.size)*a.size;(u!==n.x||d!==n.y)&&(n={...n,x:u,y:d},o=true);}o&&T(n,{duration:150});},[T,y]);useEffect(()=>{let t=y.current;if(!t)return;let n=(r,l)=>{let i=t.getBoundingClientRect();return {px:r-i.left,py:l-i.top}},o=new Set,e=r=>o.add(r.key),c=r=>o.delete(r.key);window.addEventListener("keydown",e),window.addEventListener("keyup",c);let s=false,a=null,u=false,d=r=>{if(!G.current||C.current&&!C.current(r))return;let l=r.deltaX,i=r.deltaY,{ctrlKey:p,clientX:v,clientY:m}=r,L=r.deltaMode===0&&(Math.abs(i)<100||!Number.isInteger(i));L||(Math.abs(i)>=100&&(i=Math.sign(i)*(Math.abs(i)/120)),Math.abs(l)>=100&&(l=Math.sign(l)*(Math.abs(l)/120)));let{minScale:A,maxScale:Y,panSpeed:X,zoomSpeed:E}=D.current,M=U.current,k=q.current,P=Xe.current==="zoom"?!p:p;P&&(!M.zoom||u)||P&&k?.zoom&&!o.has(k.zoom)||!P&&!M.pan||!P&&k?.pan&&!o.has(k.pan)||(r.preventDefault(),w(),h(R=>{if(P){if(!s){s=true,Se.current?.(R);let j=tt.current;j&&j.enabled&&(t.style.cursor=j.zooming??"zoom-in");}a&&clearTimeout(a),a=setTimeout(()=>{s=false,Te.current?.(x.current),H.current?.(x.current);let j=tt.current;j&&j.enabled&&(t.style.cursor=j.idle??"grab"),ct();},150);let zt=L?.01:.1*E,wt=1-i*zt,rt=I(R.zoom*wt,A,Y);if(rt===R.zoom)return R;let{px:mt,py:Et}=n(v,m),St=rt/R.zoom;return {zoom:rt,x:mt-(mt-R.x)*St,y:Et-(Et-R.y)*St}}if(!M.pan)return R;let W=L?1:25*X;return {...R,x:R.x-l*W,y:R.y-i*W}}));},f=0,g=0,z=0,S=300,K=25,N=(r,l)=>{let i=be.current;if(i===false||!i.enabled)return;let{mode:p="toggle",step:v=2}=i,{minScale:m,maxScale:L}=D.current,A=x.current,{px:Y,py:X}=n(r,l),E;if(p==="zoomIn"?E=I(A.zoom*v,m,L):p==="reset"?E=$.current.zoom:E=A.zoom>1.05?$.current.zoom:I(A.zoom*v,m,L),p==="reset"||p==="toggle"&&A.zoom>1.05)T($.current,{duration:300},()=>H.current?.(x.current));else {let M=E/A.zoom;T({zoom:E,x:Y-(Y-A.x)*M,y:X-(X-A.y)*M},{duration:300},()=>H.current?.(x.current));}},Z=false,yt={x:0,y:0},Bt=false,it=new Set,at=0,ut=0,Ft=0,Ce=r=>{if(!G.current||C.current&&!C.current(r)||(w(),it.add(r.pointerId),r.pointerType==="mouse"&&r.button!==Rt.current))return;if(it.size>=2){Z=false;return}if(!U.current.pan)return;let l=q.current?.pan;if(l&&!o.has(l))return;Z=true,Bt=false;let i=tt.current;i&&i.enabled&&Kt(i.dragging??"grabbing"),yt={x:r.clientX,y:r.clientY},at=0,ut=0,Ft=performance.now(),we.current?.(x.current);},Ze=r=>{if(!Z)return;r.preventDefault();let l=performance.now(),i=r.clientX-yt.x,p=r.clientY-yt.y,v=l-Ft;v>0&&(at=.8*(i/v*16)+(1-.8)*at,ut=.8*(p/v*16)+(1-.8)*ut),yt={x:r.clientX,y:r.clientY},Ft=l,Bt=true,h(m=>({...m,x:m.x+i,y:m.y+p}));},pn=()=>{let r=ze.current;if(r===false||!r.enabled||Math.abs(at)<.5&&Math.abs(ut)<.5)return false;let{friction:l=.92}=r,i=at,p=ut,v=()=>{if(i*=l,p*=l,Math.abs(i)<.5&&Math.abs(p)<.5){nt.current=null,ct(),H.current?.(x.current);return}h(m=>({...m,x:m.x+i,y:m.y+p})),nt.current=requestAnimationFrame(v);};return nt.current=requestAnimationFrame(v),true},pt=r=>{let l=Z;if(it.delete(r.pointerId),it.size===0&&(Z=false),l&&it.size===0){Ee.current?.(x.current);let i=tt.current;if(i&&i.enabled&&Kt(i.idle??"grab"),Bt)pn()||(ct(),H.current?.(x.current)),f=0;else {let p=performance.now(),v=r.clientX-g,m=r.clientY-z,L=Math.sqrt(v*v+m*m);p-f<S&&L<K?(N(r.clientX,r.clientY),f=0):(f=p,g=r.clientX,z=r.clientY);}}},V={zoom:1,x:0,y:0,rotation:0},Ve=0,Pe=0,lt={x:0,y:0},ht=false,Be=r=>{if(!G.current||r.touches.length<2||C.current&&!C.current(r))return;let l=U.current;if(!l.zoom&&!l.rotate)return;w(),Z=false,ht=true;let i=r.touches[0],p=r.touches[1];Ve=qt(i.clientX,i.clientY,p.clientX,p.clientY),Pe=_t(i.clientX,i.clientY,p.clientX,p.clientY),lt={x:(i.clientX+p.clientX)/2,y:(i.clientY+p.clientY)/2},V={...x.current,rotation:x.current.rotation??0},Le.current?.(x.current),l.rotate&&Ae.current?.(x.current);},Fe=r=>{if(r.touches.length<2||!ht||C.current&&!C.current(r))return;r.preventDefault();let l=r.touches[0],i=r.touches[1],p=U.current,{minScale:v,maxScale:m}=D.current,L=qt(l.clientX,l.clientY,i.clientX,i.clientY),A={x:(l.clientX+i.clientX)/2,y:(l.clientY+i.clientY)/2},Y=p.zoom?I(V.zoom*(L/Ve),v,m):V.zoom,X=Y/V.zoom,{px:E,py:M}=n(lt.x,lt.y),k=_t(l.clientX,l.clientY,i.clientX,i.clientY),ot=q.current?.rotate,R=p.rotate&&!u&&(!ot||o.has(ot))?V.rotation+(k-Pe):V.rotation??0,W=(R-(V.rotation??0))*Math.PI/180,zt=Math.cos(W),wt=Math.sin(W),rt=E-V.x,mt=M-V.y,Et=A.x-lt.x,St=A.y-lt.y;h(()=>({zoom:Y,x:E+Et-X*(rt*zt-mt*wt),y:M+St-X*(rt*wt+mt*zt),rotation:R}));},xt=r=>{r.touches.length<2&&ht&&(ht=false,Oe.current?.(x.current),U.current.rotate&&Me.current?.(x.current),H.current?.(x.current),ct());},gt=0,bt=1,Ue=0,Ke=0,We=r=>{if(!G.current)return;r.preventDefault(),w(),u=true;let l=x.current;gt=l.rotation??0,bt=l.zoom,Ue=l.x,Ke=l.y;},Ge=r=>{if(!G.current||!u)return;r.preventDefault();let l=r,i=U.current,{minScale:p,maxScale:v}=D.current,m=i.zoom?I(bt*l.scale,p,v):bt,L=q.current?.rotate,Y=i.rotate&&(!L||o.has(L))?gt+l.rotation:gt,X=(t.offsetWidth??0)/2,E=(t.offsetHeight??0)/2,M=m/bt,k=(Y-gt)*Math.PI/180,ot=Math.cos(k),P=Math.sin(k),R=X-Ue,W=E-Ke;h(()=>({zoom:m,x:X-M*(R*ot-W*P),y:E-M*(R*P+W*ot),rotation:Y}));},He=r=>{r.preventDefault(),u=false;};It.current.enabled&&!t.hasAttribute("tabindex")&&t.setAttribute("tabindex","0");let qe=r=>{if(!G.current)return;let l=It.current;if(!l.enabled)return;let i=r.target?.tagName;if(i==="INPUT"||i==="TEXTAREA"||i==="SELECT")return;let p=U.current,v=false;switch(r.key){case "ArrowLeft":p.pan&&(h(m=>({...m,x:m.x+l.panStep})),v=true);break;case "ArrowRight":p.pan&&(h(m=>({...m,x:m.x-l.panStep})),v=true);break;case "ArrowUp":p.pan&&(h(m=>({...m,x:m.x,y:m.y+l.panStep})),v=true);break;case "ArrowDown":p.pan&&(h(m=>({...m,x:m.x,y:m.y-l.panStep})),v=true);break;case "+":case "=":if(p.zoom){let{minScale:m,maxScale:L}=D.current,A=t,Y=A.offsetWidth/2,X=A.offsetHeight/2;h(E=>{let M=I(E.zoom*l.zoomStep,m,L),k=M/E.zoom;return {...E,zoom:M,x:Y-(Y-E.x)*k,y:X-(X-E.y)*k}}),v=true;}break;case "-":if(p.zoom){let{minScale:m,maxScale:L}=D.current,A=t,Y=A.offsetWidth/2,X=A.offsetHeight/2;h(E=>{let M=I(E.zoom/l.zoomStep,m,L),k=M/E.zoom;return {...E,zoom:M,x:Y-(Y-E.x)*k,y:X-(X-E.y)*k}}),v=true;}break;case "0":w(),h(()=>$.current),v=true;break;case "[":if(p.rotate){let m=q.current?.rotate;if(m&&!o.has(m))break;h(L=>({...L,rotation:(L.rotation??0)-l.rotateStep})),v=true;}break;case "]":if(p.rotate){let m=q.current?.rotate;if(m&&!o.has(m))break;h(L=>({...L,rotation:(L.rotation??0)+l.rotateStep})),v=true;}break}v&&r.preventDefault();},Ut=null,Kt=r=>{t.style.cursor=r;},Wt=tt.current;Wt&&Wt.enabled&&(Ut=t.style.cursor,Kt(Wt.idle??"grab"));let vt=()=>o.clear();window.addEventListener("blur",vt),document.addEventListener("visibilitychange",vt);let _e=r=>{Rt.current===2&&r.preventDefault();};return t.addEventListener("contextmenu",_e),t.addEventListener("wheel",d,{passive:false}),t.addEventListener("pointerdown",Ce),window.addEventListener("pointermove",Ze,{passive:false}),window.addEventListener("pointerup",pt),window.addEventListener("pointercancel",pt),t.addEventListener("touchstart",Be,{passive:false}),t.addEventListener("touchmove",Fe,{passive:false}),t.addEventListener("touchend",xt),t.addEventListener("touchcancel",xt),t.addEventListener("gesturestart",We,{passive:false}),t.addEventListener("gesturechange",Ge,{passive:false}),t.addEventListener("gestureend",He,{passive:false}),t.addEventListener("keydown",qe),()=>{w(),a&&clearTimeout(a),Ut!==null&&(t.style.cursor=Ut),t.removeEventListener("wheel",d),t.removeEventListener("pointerdown",Ce),window.removeEventListener("pointermove",Ze),window.removeEventListener("pointerup",pt),window.removeEventListener("pointercancel",pt),t.removeEventListener("touchstart",Be),t.removeEventListener("touchmove",Fe),t.removeEventListener("touchend",xt),t.removeEventListener("touchcancel",xt),t.removeEventListener("gesturestart",We),t.removeEventListener("gesturechange",Ge),t.removeEventListener("gestureend",He),t.removeEventListener("keydown",qe),window.removeEventListener("blur",vt),document.removeEventListener("visibilitychange",vt),t.removeEventListener("contextmenu",_e),window.removeEventListener("keydown",e),window.removeEventListener("keyup",c);}},[y,h,w,T,Zt,ct]);let Vt=useCallback((t,n)=>{if(n?.animate){T(t,n);return}if(w(),n?.skipConstraints){ft(t);return}h(()=>t);},[T,w,h,ft]),_=useCallback((t,n)=>{let{minScale:o,maxScale:e}=D.current,c=I(t,o,e),s=(y.current?.offsetWidth??0)/2,a=(y.current?.offsetHeight??0)/2,u=x.current,d=c/u.zoom,f={zoom:c,x:s-(s-u.x)*d,y:a-(a-u.y)*d};n?.animate?T(f,n):(w(),h(()=>f));},[y,h,T,w]),nn=useCallback(t=>Vt($.current,t),[Vt]),on=useCallback((t=1.5,n)=>{_(x.current.zoom*t,n);},[_]),rn=useCallback((t=1.5,n)=>{_(x.current.zoom/t,n);},[_]),sn=useCallback((t,n,o)=>{let e=y.current;if(!e)return;let c=x.current,s={x:e.offsetWidth/2-t*c.zoom,y:e.offsetHeight/2-n*c.zoom,zoom:c.zoom};o?.animate?T(s,o):(w(),h(()=>s));},[y,h,T,w]),cn=useCallback((t,n,o)=>{let e=x.current,c={x:e.x+t,y:e.y+n,zoom:e.zoom};o?.animate?T(c,o):(w(),h(()=>c));},[h,T,w]),an=useCallback((t,n,o)=>{let e=y.current;if(!e)return;let{minScale:c,maxScale:s}=D.current,a=I(t,c,s),u=x.current,d=a/u.zoom,f;if(n)f={zoom:a,x:e.offsetWidth/2-n.x*a,y:e.offsetHeight/2-n.y*a};else {let g=e.offsetWidth/2,z=e.offsetHeight/2;f={zoom:a,x:g-(g-u.x)*d,y:z-(z-u.y)*d};}o?.animate?T(f,o):(w(),h(()=>f));},[y,h,T,w]),Pt=useCallback((t,n)=>{let o=y.current;if(!o)return;let{minScale:e,maxScale:c}=D.current,s=n?.padding??0,a=o.offsetWidth-s*2,u=o.offsetHeight-s*2;if(a<=0||u<=0)return;let d=I(Math.min(a/t.width,u/t.height),e,c),f={zoom:d,x:o.offsetWidth/2-(t.x+t.width/2)*d,y:o.offsetHeight/2-(t.y+t.height/2)*d};n?.animate?T(f,n):(w(),h(()=>f));},[y,h,T,w]),un=useCallback((t,n,o)=>{let e=y.current;if(!e)return;let c=e.getBoundingClientRect(),s=t.getBoundingClientRect(),a=x.current,u=(s.left-c.left-a.x)/a.zoom,d=(s.top-c.top-a.y)/a.zoom,f=s.width/a.zoom,g=s.height/a.zoom,{minScale:z,maxScale:S}=D.current,K=I(n??a.zoom,z,S),N={x:c.width/2-(u+f/2)*K,y:c.height/2-(d+g/2)*K,zoom:K};o?.animate?T(N,o):(w(),h(()=>N));},[y,h,T,w]),ln=useCallback((t,n)=>{let e={...x.current,rotation:t};n?.animate?T(e,n):(w(),h(()=>e));},[h,T,w]),mn=useCallback((t,n)=>{let o=x.current,e={...o,rotation:(o.rotation??0)+t};n?.animate?T(e,n):(w(),h(()=>e));},[h,T,w]),fn=useCallback((t,n)=>{let o=y.current;if(!o)return {x:0,y:0};let e=o.getBoundingClientRect(),c=x.current,s=t-e.left-c.x,a=n-e.top-c.y,u=c.rotation??0;if(u===0)return {x:s/c.zoom,y:a/c.zoom};let d=-u*Math.PI/180,f=Math.cos(d),g=Math.sin(d),z=s*f-a*g,S=s*g+a*f;return {x:z/c.zoom,y:S/c.zoom}},[y]),dn=useCallback((t,n)=>{let o=y.current;if(!o)return {x:0,y:0};let e=o.getBoundingClientRect(),c=x.current,s=c.rotation??0,a=t*c.zoom,u=n*c.zoom;if(s===0)return {x:a+c.x+e.left,y:u+c.y+e.top};let d=s*Math.PI/180,f=Math.cos(d),g=Math.sin(d),z=a*f-u*g,S=a*g+u*f;return {x:z+c.x+e.left,y:S+c.y+e.top}},[y]),dt=useCallback(t=>{let n=y.current,o=kt.current;!n||!o||Pt({x:0,y:0,width:o.width,height:o.height},t);},[y,Pt]),yn=useCallback(t=>{let n=Yt.current;if(!n||n.length===0)return;let o=x.current,e=n[0],c=Math.abs(o.zoom-e);for(let s=1;s<n.length;s++){let a=Math.abs(o.zoom-n[s]);a<c&&(c=a,e=n[s]);}_(e,t);},[_]);return useEffect(()=>{let t=y.current,n=kt.current;if(!t||!n)return;dt();let o=null,e=new ResizeObserver(()=>{o&&clearTimeout(o),o=setTimeout(()=>{o=null,dt();},150);});return e.observe(t),()=>{o&&clearTimeout(o),e.disconnect();}},[y,dt]),{view:Ot,isAnimating:en,setView:Vt,centerZoom:_,resetView:nn,zoomIn:on,zoomOut:rn,zoomToElement:un,panTo:sn,panBy:cn,zoomTo:an,fitToRect:Pt,rotateTo:ln,rotateBy:mn,screenToContent:fn,contentToScreen:dn,fitToContent:dt,snapZoom:yn}}function I(y,B,F){return Math.max(B,Math.min(y,F))}function qt(y,B,F,J){let st=y-F,Q=B-J;return Math.sqrt(st*st+Q*Q)}function _t(y,B,F,J){return Math.atan2(J-B,F-y)*(180/Math.PI)}export{_t as angleBetween,I as clamp,qt as distance,xn as easeInOut,Gt as easeOut,hn as linear,vn as useZoomPinch};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-zoom-pinch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lightweight React hook for pan, pinch-to-zoom, rotation, and scroll zoom with trackpad and touch support. ~5.
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Lightweight React hook for pan, pinch-to-zoom, rotation, and scroll zoom with trackpad and touch support. ~5.8 KB gzipped, zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
7
7
|
"hook",
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
"files": [
|
|
60
60
|
"dist",
|
|
61
61
|
"LICENSE",
|
|
62
|
-
"README.md"
|
|
62
|
+
"README.md",
|
|
63
|
+
"CHANGELOG.md"
|
|
63
64
|
],
|
|
64
65
|
"scripts": {
|
|
65
66
|
"build": "tsup",
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
"test:watch": "vitest",
|
|
74
75
|
"docs:dev": "cd docs && npm run dev",
|
|
75
76
|
"docs:build": "cd docs && npm run build",
|
|
76
|
-
"prepublishOnly": "npm run test && npm run typecheck && npm run lint && npm run build"
|
|
77
|
+
"prepublishOnly": "npm run test && npm run typecheck && npm run lint && npm run format:check && npm run build"
|
|
77
78
|
},
|
|
78
79
|
"peerDependencies": {
|
|
79
80
|
"react": ">=18.0.0"
|