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 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
  [![bundle size](https://img.shields.io/bundlephobia/minzip/use-zoom-pinch)](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.2 KB** minified + gzipped
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 `{ x: 0, y: 0, zoom: 1 }` |
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.2 KB** | ~13.2 KB | ~8.9 KB | ~41.6 KB |
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 content-space pixels. */
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 content-space pixels. */
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.3.0",
4
- "description": "Lightweight React hook for pan, pinch-to-zoom, rotation, and scroll zoom with trackpad and touch support. ~5.2 KB gzipped, zero dependencies.",
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"