diff --git a/README.md b/README.md
index bbf00a8..3d4e67b 100644
--- a/README.md
+++ b/README.md
@@ -259,6 +259,18 @@ assert(instance.getMaxZoom() === 1);
assert(instance.getMinZoom() === 0.1);
```
+## Two Finger Pan
+
+You can enable panning while pinch-zooming, similar to Google/Apple Maps, by passing the optional enableTwoFingerPan argument:
+
+``` js
+panzoom(element, {
+ enableTwoFingerPan: true
+});
+```
+
+Note: When enabled, `transformOrigin` is ignored during pinch-zooming.
+
## Disable Smooth Scroll
You can disable smooth scroll, by passing optional `smoothScroll` argument:
diff --git a/demo/two-finger-pan.html b/demo/two-finger-pan.html
new file mode 100644
index 0000000..80396af
--- /dev/null
+++ b/demo/two-finger-pan.html
@@ -0,0 +1,770 @@
+
+
+
+
+
+
+
+
+
+
+ SVG panzoom demo
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/panzoom.js b/dist/panzoom.js
index 3ed6a77..5b29d20 100644
--- a/dist/panzoom.js
+++ b/dist/panzoom.js
@@ -71,6 +71,7 @@ function createPanZoom(domElement, options) {
var speed = typeof options.zoomSpeed === 'number' ? options.zoomSpeed : defaultZoomSpeed;
var transformOrigin = parseTransformOrigin(options.transformOrigin);
var textSelection = options.enableTextSelection ? fakeTextSelectorInterceptor : domTextSelectionInterceptor;
+ var enableTwoFingerPan = !!options.enableTwoFingerPan;
validateBounds(bounds);
@@ -108,7 +109,7 @@ function createPanZoom(domElement, options) {
} else {
// otherwise we use forward smoothScroll settings to kinetic API
// which makes scroll smoothing.
- smoothScroll = kinetic(getPoint, scroll, options.smoothScroll);
+ smoothScroll = kinetic(getPoint, getTarget, scroll, options.smoothScroll);
}
var moveByAnimation;
@@ -287,24 +288,31 @@ function createPanZoom(domElement, options) {
}
function getPoint() {
+ return {
+ x: mouseX,
+ y: mouseY,
+ };
+ }
+
+ function getTarget() {
return {
x: transform.x,
y: transform.y
};
}
- function moveTo(x, y) {
+ function moveTo(x, y, dirty = true) {
transform.x = x;
transform.y = y;
keepTransformInsideBounds();
triggerEvent('pan');
- makeDirty();
+ if (dirty) makeDirty();
}
- function moveBy(dx, dy) {
- moveTo(transform.x + dx, transform.y + dy);
+ function moveBy(dx, dy, dirty = true) {
+ moveTo(transform.x + dx, transform.y + dy, dirty);
}
function keepTransformInsideBounds() {
@@ -595,12 +603,9 @@ function createPanZoom(domElement, options) {
clearPendingClickEventTimeout();
if (e.touches.length === 1) {
- return handleSingleFingerTouch(e, e.touches[0]);
+ return handleSingleFingerTouch(e);
} else if (e.touches.length === 2) {
- // handleTouchMove() will care about pinch zoom.
- pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]);
- multiTouch = true;
- startTouchListenerIfNeeded();
+ return handleTwoFingerTouch(e);
}
}
@@ -645,6 +650,20 @@ function createPanZoom(domElement, options) {
startTouchListenerIfNeeded();
}
+ function handleTwoFingerTouch(e) {
+ // handleTouchMove() will care about pan and pinch zoom.
+ pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]);
+
+ // pan init
+ var offset = calcMidOffset(e.touches[0], e.touches[1]);
+ var point = transformToScreen(offset.x, offset.y);
+ mouseX = point.x;
+ mouseY = point.y;
+
+ multiTouch = true;
+ startTouchListenerIfNeeded();
+ }
+
function startTouchListenerIfNeeded() {
if (touchInProgress) {
// no need to do anything, as we already listen to events;
@@ -658,50 +677,61 @@ function createPanZoom(domElement, options) {
}
function handleTouchMove(e) {
- if (e.touches.length === 1) {
+ if (e.touches.length > 2) return;
+ if (multiTouch && e.touches.length === 1) return;
+ if (!multiTouch && e.touches.length === 2) return;
+
+ var offset = e.touches.length === 1
+ ? getOffsetXY(e.touches[0])
+ : calcMidOffset(e.touches[0], e.touches[1]);
+
+ if (!multiTouch) {
+ handleTouchMovePan(e, offset);
+ } else {
+ handleTouchMoveZoom(e, offset);
+ handleTouchMovePan(e, offset);
+ }
+ }
+
+ function handleTouchMovePan(e, offset) {
+ if (multiTouch && !enableTwoFingerPan) {
e.stopPropagation();
- var touch = e.touches[0];
+ return;
+ }
+
+ var point = transformToScreen(offset.x, offset.y);
+ var dx = point.x - mouseX;
+ var dy = point.y - mouseY;
- var offset = getOffsetXY(touch);
- var point = transformToScreen(offset.x, offset.y);
+ if (dx !== 0 && dy !== 0) {
+ triggerPanStart();
+ }
- var dx = point.x - mouseX;
- var dy = point.y - mouseY;
+ mouseX = point.x;
+ mouseY = point.y;
- if (dx !== 0 && dy !== 0) {
- triggerPanStart();
- }
- mouseX = point.x;
- mouseY = point.y;
- internalMoveBy(dx, dy);
- } else if (e.touches.length === 2) {
- // it's a zoom, let's find direction
- multiTouch = true;
- var t1 = e.touches[0];
- var t2 = e.touches[1];
- var currentPinchLength = getPinchZoomLength(t1, t2);
-
- // since the zoom speed is always based on distance from 1, we need to apply
- // pinch speed only on that distance from 1:
- var scaleMultiplier =
- 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed;
-
- var firstTouchPoint = getOffsetXY(t1);
- var secondTouchPoint = getOffsetXY(t2);
- mouseX = (firstTouchPoint.x + secondTouchPoint.x) / 2;
- mouseY = (firstTouchPoint.y + secondTouchPoint.y) / 2;
- if (transformOrigin) {
- var offset = getTransformOriginOffset();
- mouseX = offset.x;
- mouseY = offset.y;
- }
+ moveBy(dx, dy, !multiTouch);
+ e.stopPropagation();
+ }
- publicZoomTo(mouseX, mouseY, scaleMultiplier);
+ function handleTouchMoveZoom(e, offset) {
+ var currentPinchLength = getPinchZoomLength(e.touches[0], e.touches[1]);
+ // since the zoom speed is always based on distance from 1, we need to apply
+ // pinch speed only on that distance from 1:
+ var scaleMultiplier =
+ 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed;
- pinchZoomLength = currentPinchLength;
- e.stopPropagation();
- e.preventDefault();
+ if (transformOrigin && !enableTwoFingerPan) {
+ offset = getTransformOriginOffset();
+ mouseX = offset.x;
+ mouseY = offset.y;
}
+
+ publicZoomTo(offset.x, offset.y, scaleMultiplier);
+
+ pinchZoomLength = currentPinchLength;
+
+ e.preventDefault();
}
function clearPendingClickEventTimeout() {
@@ -730,10 +760,13 @@ function createPanZoom(domElement, options) {
function handleTouchEnd(e) {
clearPendingClickEventTimeout();
if (e.touches.length > 0) {
- var offset = getOffsetXY(e.touches[0]);
- var point = transformToScreen(offset.x, offset.y);
- mouseX = point.x;
- mouseY = point.y;
+ // prevents conflict with smooth scroll and pinch zoom when two finger pan is enabled.
+ if (!multiTouch) {
+ var offset = getOffsetXY(e.touches[0]);
+ var point = transformToScreen(offset.x, offset.y);
+ mouseX = point.x;
+ mouseY = point.y;
+ }
} else {
var now = new Date();
if (now - lastTouchEndTime < doubleTapSpeedInMS) {
@@ -762,6 +795,14 @@ function createPanZoom(domElement, options) {
return Math.sqrt(dx * dx + dy * dy);
}
+ function calcMidOffset(t1, t2) {
+ const mid = (num1, num2) => (num1 + num2) / 2;
+ var x = mid(t1.clientX, t2.clientX);
+ var y = mid(t1.clientY, t2.clientY);
+
+ return getOffsetXY({clientX: x, clientY: y});
+ }
+
function onDoubleClick(e) {
beforeDoubleClick(e);
var offset = getOffsetXY(e);
@@ -920,7 +961,6 @@ function createPanZoom(domElement, options) {
}
function publicZoomTo(clientX, clientY, scaleMultiplier) {
- smoothScroll.cancel();
cancelZoomAnimation();
return zoomByRatio(clientX, clientY, scaleMultiplier);
}
@@ -948,8 +988,8 @@ function createPanZoom(domElement, options) {
function triggerPanEnd() {
if (panstartFired) {
- // we should never run smooth scrolling if it was multiTouch (pinch zoom animation):
- if (!multiTouch) smoothScroll.stop();
+ // we should never run smooth scrolling if it was multiTouch and enableTwoFingerPan disabled:
+ if (!multiTouch || enableTwoFingerPan) smoothScroll.stop();
triggerEvent('panend');
}
}
@@ -1106,7 +1146,7 @@ autoRun();
*/
module.exports = kinetic;
-function kinetic(getPoint, scroll, settings) {
+function kinetic(getPoint, getTarget, scroll, settings) {
if (typeof settings !== 'object') {
// setting could come as boolean, we should ignore it, and use an object.
settings = {};
@@ -1178,10 +1218,10 @@ function kinetic(getPoint, scroll, settings) {
cancelAnimationFrame(ticker);
cancelAnimationFrame(raf);
- var currentPoint = getPoint();
+ var target = getTarget();
- targetX = currentPoint.x;
- targetY = currentPoint.y;
+ targetX = target.x;
+ targetY = target.y;
timestamp = Date.now();
if (vx < -minVelocity || vx > minVelocity) {
@@ -1327,12 +1367,12 @@ function makeSvgController(svgElement, options) {
}
function getBBox() {
- var bbox = svgElement.getBBox();
+ var boundingBox = svgElement.getBBox();
return {
- left: bbox.x,
- top: bbox.y,
- width: bbox.width,
- height: bbox.height,
+ left: boundingBox.x,
+ top: boundingBox.y,
+ width: boundingBox.width,
+ height: boundingBox.height,
};
}
diff --git a/dist/panzoom.min.js b/dist/panzoom.min.js
index a8984e4..193d1c9 100644
--- a/dist/panzoom.min.js
+++ b/dist/panzoom.min.js
@@ -1 +1 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.panzoom=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i0){transform.x+=diff;adjusted=true}diff=boundingBox.right-clientRect.left;if(diff<0){transform.x+=diff;adjusted=true}diff=boundingBox.top-clientRect.bottom;if(diff>0){transform.y+=diff;adjusted=true}diff=boundingBox.bottom-clientRect.top;if(diff<0){transform.y+=diff;adjusted=true}return adjusted}function getBoundingBox(){if(!bounds)return;if(typeof bounds==="boolean"){var ownerRect=owner.getBoundingClientRect();var sceneWidth=ownerRect.width;var sceneHeight=ownerRect.height;return{left:sceneWidth*boundsPadding,top:sceneHeight*boundsPadding,right:sceneWidth*(1-boundsPadding),bottom:sceneHeight*(1-boundsPadding)}}return bounds}function getClientRect(){var bbox=panController.getBBox();var leftTop=client(bbox.left,bbox.top);return{left:leftTop.x,top:leftTop.y,right:bbox.width*transform.scale+leftTop.x,bottom:bbox.height*transform.scale+leftTop.y}}function client(x,y){return{x:x*transform.scale+transform.x,y:y*transform.scale+transform.y}}function makeDirty(){isDirty=true;frameAnimation=window.requestAnimationFrame(frame)}function zoomByRatio(clientX,clientY,ratio){if(isNaN(clientX)||isNaN(clientY)||isNaN(ratio)){throw new Error("zoom requires valid numbers")}var newScale=transform.scale*ratio;if(newScalemaxZoom){if(transform.scale===maxZoom)return;ratio=maxZoom/transform.scale}var size=transformToScreen(clientX,clientY);transform.x=size.x-ratio*(size.x-transform.x);transform.y=size.y-ratio*(size.y-transform.y);if(bounds&&boundsPadding===1&&minZoom===1){transform.scale*=ratio;keepTransformInsideBounds()}else{var transformAdjusted=keepTransformInsideBounds();if(!transformAdjusted)transform.scale*=ratio}triggerEvent("zoom");makeDirty()}function zoomAbs(clientX,clientY,zoomLevel){var ratio=zoomLevel/transform.scale;zoomByRatio(clientX,clientY,ratio)}function centerOn(ui){var parent=ui.ownerSVGElement;if(!parent)throw new Error("ui element is required to be within the scene");var clientRect=ui.getBoundingClientRect();var cx=clientRect.left+clientRect.width/2;var cy=clientRect.top+clientRect.height/2;var container=parent.getBoundingClientRect();var dx=container.width/2-cx;var dy=container.height/2-cy;internalMoveBy(dx,dy,true)}function smoothMoveTo(x,y){internalMoveBy(x-transform.x,y-transform.y,true)}function internalMoveBy(dx,dy,smooth){if(!smooth){return moveBy(dx,dy)}if(moveByAnimation)moveByAnimation.cancel();var from={x:0,y:0};var to={x:dx,y:dy};var lastX=0;var lastY=0;moveByAnimation=animate(from,to,{step:function(v){moveBy(v.x-lastX,v.y-lastY);lastX=v.x;lastY=v.y}})}function scroll(x,y){cancelZoomAnimation();moveTo(x,y)}function dispose(){releaseEvents()}function listenForEvents(){owner.addEventListener("mousedown",onMouseDown,{passive:false});owner.addEventListener("dblclick",onDoubleClick,{passive:false});owner.addEventListener("touchstart",onTouch,{passive:false});owner.addEventListener("keydown",onKeyDown,{passive:false});wheel.addWheelListener(owner,onMouseWheel,{passive:false});makeDirty()}function releaseEvents(){wheel.removeWheelListener(owner,onMouseWheel);owner.removeEventListener("mousedown",onMouseDown);owner.removeEventListener("keydown",onKeyDown);owner.removeEventListener("dblclick",onDoubleClick);owner.removeEventListener("touchstart",onTouch);if(frameAnimation){window.cancelAnimationFrame(frameAnimation);frameAnimation=0}smoothScroll.cancel();releaseDocumentMouse();releaseTouches();textSelection.release();triggerPanEnd()}function frame(){if(isDirty)applyTransform()}function applyTransform(){isDirty=false;panController.applyTransform(transform);triggerEvent("transform");frameAnimation=0}function onKeyDown(e){var x=0,y=0,z=0;if(e.keyCode===38){y=1}else if(e.keyCode===40){y=-1}else if(e.keyCode===37){x=1}else if(e.keyCode===39){x=-1}else if(e.keyCode===189||e.keyCode===109){z=1}else if(e.keyCode===187||e.keyCode===107){z=-1}if(filterKey(e,x,y,z)){return}if(x||y){e.preventDefault();e.stopPropagation();var clientRect=owner.getBoundingClientRect();var offset=Math.min(clientRect.width,clientRect.height);var moveSpeedRatio=.05;var dx=offset*moveSpeedRatio*x;var dy=offset*moveSpeedRatio*y;internalMoveBy(dx,dy)}if(z){var scaleMultiplier=getScaleMultiplier(z*100);var offset=transformOrigin?getTransformOriginOffset():midPoint();publicZoomTo(offset.x,offset.y,scaleMultiplier)}}function midPoint(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width/2,y:ownerRect.height/2}}function onTouch(e){beforeTouch(e);clearPendingClickEventTimeout();if(e.touches.length===1){return handleSingleFingerTouch(e,e.touches[0])}else if(e.touches.length===2){pinchZoomLength=getPinchZoomLength(e.touches[0],e.touches[1]);multiTouch=true;startTouchListenerIfNeeded()}}function beforeTouch(e){if(options.onTouch&&!options.onTouch(e)){return}e.stopPropagation();e.preventDefault()}function beforeDoubleClick(e){clearPendingClickEventTimeout();if(options.onDoubleClick&&!options.onDoubleClick(e)){return}e.preventDefault();e.stopPropagation()}function handleSingleFingerTouch(e){lastTouchStartTime=new Date;var touch=e.touches[0];var offset=getOffsetXY(touch);lastSingleFingerOffset=offset;var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y;clickX=mouseX;clickY=mouseY;smoothScroll.cancel();startTouchListenerIfNeeded()}function startTouchListenerIfNeeded(){if(touchInProgress){return}touchInProgress=true;document.addEventListener("touchmove",handleTouchMove);document.addEventListener("touchend",handleTouchEnd);document.addEventListener("touchcancel",handleTouchEnd)}function handleTouchMove(e){if(e.touches.length===1){e.stopPropagation();var touch=e.touches[0];var offset=getOffsetXY(touch);var point=transformToScreen(offset.x,offset.y);var dx=point.x-mouseX;var dy=point.y-mouseY;if(dx!==0&&dy!==0){triggerPanStart()}mouseX=point.x;mouseY=point.y;internalMoveBy(dx,dy)}else if(e.touches.length===2){multiTouch=true;var t1=e.touches[0];var t2=e.touches[1];var currentPinchLength=getPinchZoomLength(t1,t2);var scaleMultiplier=1+(currentPinchLength/pinchZoomLength-1)*pinchSpeed;var firstTouchPoint=getOffsetXY(t1);var secondTouchPoint=getOffsetXY(t2);mouseX=(firstTouchPoint.x+secondTouchPoint.x)/2;mouseY=(firstTouchPoint.y+secondTouchPoint.y)/2;if(transformOrigin){var offset=getTransformOriginOffset();mouseX=offset.x;mouseY=offset.y}publicZoomTo(mouseX,mouseY,scaleMultiplier);pinchZoomLength=currentPinchLength;e.stopPropagation();e.preventDefault()}}function clearPendingClickEventTimeout(){if(pendingClickEventTimeout){clearTimeout(pendingClickEventTimeout);pendingClickEventTimeout=0}}function handlePotentialClickEvent(e){if(!options.onClick)return;clearPendingClickEventTimeout();var dx=mouseX-clickX;var dy=mouseY-clickY;var l=Math.sqrt(dx*dx+dy*dy);if(l>5)return;pendingClickEventTimeout=setTimeout(function(){pendingClickEventTimeout=0;options.onClick(e)},doubleTapSpeedInMS)}function handleTouchEnd(e){clearPendingClickEventTimeout();if(e.touches.length>0){var offset=getOffsetXY(e.touches[0]);var point=transformToScreen(offset.x,offset.y);mouseX=point.x;mouseY=point.y}else{var now=new Date;if(now-lastTouchEndTime0)delta*=100;var scaleMultiplier=getScaleMultiplier(delta);if(scaleMultiplier!==1){var offset=transformOrigin?getTransformOriginOffset():getOffsetXY(e);publicZoomTo(offset.x,offset.y,scaleMultiplier);e.preventDefault()}}function getOffsetXY(e){var offsetX,offsetY;var ownerRect=owner.getBoundingClientRect();offsetX=e.clientX-ownerRect.left;offsetY=e.clientY-ownerRect.top;return{x:offsetX,y:offsetY}}function smoothZoom(clientX,clientY,scaleMultiplier){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:scaleMultiplier*fromValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)},done:triggerZoomEnd})}function smoothZoomAbs(clientX,clientY,toScaleValue){var fromValue=transform.scale;var from={scale:fromValue};var to={scale:toScaleValue};smoothScroll.cancel();cancelZoomAnimation();zoomToAnimation=animate(from,to,{step:function(v){zoomAbs(clientX,clientY,v.scale)}})}function getTransformOriginOffset(){var ownerRect=owner.getBoundingClientRect();return{x:ownerRect.width*transformOrigin.x,y:ownerRect.height*transformOrigin.y}}function publicZoomTo(clientX,clientY,scaleMultiplier){smoothScroll.cancel();cancelZoomAnimation();return zoomByRatio(clientX,clientY,scaleMultiplier)}function cancelZoomAnimation(){if(zoomToAnimation){zoomToAnimation.cancel();zoomToAnimation=null}}function getScaleMultiplier(delta){var sign=Math.sign(delta);var deltaAdjustedSpeed=Math.min(.25,Math.abs(speed*delta/128));return 1-sign*deltaAdjustedSpeed}function triggerPanStart(){if(!panstartFired){triggerEvent("panstart");panstartFired=true;smoothScroll.start()}}function triggerPanEnd(){if(panstartFired){if(!multiTouch)smoothScroll.stop();triggerEvent("panend")}}function triggerZoomEnd(){triggerEvent("zoomend")}function triggerEvent(name){api.fire(name,api)}}function parseTransformOrigin(options){if(!options)return;if(typeof options==="object"){if(!isNumber(options.x)||!isNumber(options.y))failTransformOrigin(options);return options}failTransformOrigin()}function failTransformOrigin(options){console.error(options);throw new Error(["Cannot parse transform origin.","Some good examples:",' "center center" can be achieved with {x: 0.5, y: 0.5}',' "top center" can be achieved with {x: 0.5, y: 0}',' "bottom right" can be achieved with {x: 1, y: 1}'].join("\n"))}function noop(){}function validateBounds(bounds){var boundsType=typeof bounds;if(boundsType==="undefined"||boundsType==="boolean")return;var validBounds=isNumber(bounds.left)&&isNumber(bounds.top)&&isNumber(bounds.bottom)&&isNumber(bounds.right);if(!validBounds)throw new Error("Bounds object is not valid. It can be: "+"undefined, boolean (true|false) or an object {left, top, right, bottom}")}function isNumber(x){return Number.isFinite(x)}function isNaN(value){if(Number.isNaN){return Number.isNaN(value)}return value!==value}function rigidScroll(){return{start:noop,stop:noop,cancel:noop}}function autoRun(){if(typeof document==="undefined")return;var scripts=document.getElementsByTagName("script");if(!scripts)return;var panzoomScript;for(var i=0;iminVelocity){ax=amplitude*vx;targetX+=ax}if(vy<-minVelocity||vy>minVelocity){ay=amplitude*vy;targetY+=ay}raf=requestAnimationFrame(autoScroll)}function autoScroll(){var elapsed=Date.now()-timestamp;var moving=false;var dx=0;var dy=0;if(ax){dx=-ax*Math.exp(-elapsed/timeConstant);if(dx>.5||dx<-.5)moving=true;else dx=ax=0}if(ay){dy=-ay*Math.exp(-elapsed/timeConstant);if(dy>.5||dy<-.5)moving=true;else dy=ay=0}if(moving){scroll(targetX+dx,targetY+dy);raf=requestAnimationFrame(autoScroll)}}}function getCancelAnimationFrame(){if(typeof cancelAnimationFrame==="function")return cancelAnimationFrame;return clearTimeout}function getRequestAnimationFrame(){if(typeof requestAnimationFrame==="function")return requestAnimationFrame;return function(handler){return setTimeout(handler,16)}}},{}],3:[function(require,module,exports){module.exports=makeDomController;module.exports.canAttach=isDomElement;function makeDomController(domElement,options){var elementValid=isDomElement(domElement);if(!elementValid){throw new Error("panzoom requires DOM element to be attached to the DOM tree")}var owner=domElement.parentElement;domElement.scrollTop=0;if(!options.disableKeyboardInteraction){owner.setAttribute("tabindex",0)}var api={getBBox:getBBox,getOwner:getOwner,applyTransform:applyTransform};return api;function getOwner(){return owner}function getBBox(){return{left:0,top:0,width:domElement.clientWidth,height:domElement.clientHeight}}function applyTransform(transform){domElement.style.transformOrigin="0 0 0";domElement.style.transform="matrix("+transform.scale+", 0, 0, "+transform.scale+", "+transform.x+", "+transform.y+")"}}function isDomElement(element){return element&&element.parentElement&&element.style}},{}],4:[function(require,module,exports){module.exports=makeSvgController;module.exports.canAttach=isSVGElement;function makeSvgController(svgElement,options){if(!isSVGElement(svgElement)){throw new Error("svg element is required for svg.panzoom to work")}var owner=svgElement.ownerSVGElement;if(!owner){throw new Error("Do not apply panzoom to the root