diff --git a/README.md b/README.md index 5ab9e5c1..68af2067 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ With a script tag: With a script tag from a CDN: ```html - + ``` ## Usage @@ -168,7 +168,7 @@ See this [stackoverflow question](https://stackoverflow.com/questions/70428922/p #### Defined in -[panzoom.ts:59](https://github.com/timmywil/panzoom/blob/6ed904e/src/panzoom.ts#L59) +[panzoom.ts:59](https://github.com/timmywil/panzoom/blob/8205b07/src/panzoom.ts#L59) ## `PanzoomOptions` @@ -186,7 +186,7 @@ Whether to animate transitions #### Defined in -[types.ts:21](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L21) +[types.ts:21](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L21) ### canvas @@ -203,7 +203,7 @@ where the `cursor` style is applied (i.e. the parent). #### Defined in -[types.ts:32](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L32) +[types.ts:32](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L32) ### duration @@ -213,7 +213,7 @@ Duration of the transition (ms) #### Defined in -[types.ts:34](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L34) +[types.ts:34](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L34) ### easing @@ -223,7 +223,7 @@ CSS Easing used for transitions #### Defined in -[types.ts:36](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L36) +[types.ts:36](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L36) ### exclude @@ -236,7 +236,7 @@ e.g. links and buttons that should not propagate the click event. #### Defined in -[types.ts:43](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L43) +[types.ts:43](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L43) ### excludeClass @@ -249,7 +249,7 @@ e.g. links and buttons that should not propagate the click event. #### Defined in -[types.ts:50](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L50) +[types.ts:50](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L50) ### force @@ -271,7 +271,7 @@ panzoom.zoom(1, { force: true }) #### Defined in -[types.ts:66](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L66) +[types.ts:66](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L66) ### handleStartEvent @@ -315,7 +315,7 @@ Panzoom(elem, { #### Defined in -[types.ts:91](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L91) +[types.ts:91](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L91) ### noBind @@ -325,7 +325,7 @@ Skip binding the default Panzoom event listeners #### Defined in -[types.ts:95](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L95) +[types.ts:95](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L95) ### origin @@ -345,7 +345,7 @@ And again, changing this for SVG in IE doesn't work at all. #### Defined in -[types.ts:109](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L109) +[types.ts:109](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L109) ### overflow @@ -355,7 +355,7 @@ The overflow CSS value for the parent. Defaults to 'hidden' #### Defined in -[types.ts:111](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L111) +[types.ts:111](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L111) ### pinchAndPan @@ -374,7 +374,7 @@ https://github.com/timmywil/panzoom/issues/606 #### Defined in -[types.ts:124](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L124) +[types.ts:124](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L124) ### setTransform @@ -412,7 +412,7 @@ const panzoom = Panzoom(elem, { #### Defined in -[types.ts:128](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L128) +[types.ts:128](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L128) ### silent @@ -422,7 +422,7 @@ Silence all events #### Defined in -[types.ts:130](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L130) +[types.ts:130](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L130) ### startScale @@ -432,7 +432,7 @@ Scale used to set the beginning transform #### Defined in -[types.ts:136](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L136) +[types.ts:136](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L136) ### startX @@ -442,7 +442,7 @@ X Value used to set the beginning transform #### Defined in -[types.ts:132](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L132) +[types.ts:132](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L132) ### startY @@ -452,7 +452,7 @@ Y Value used to set the beginning transform #### Defined in -[types.ts:134](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L134) +[types.ts:134](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L134) ### touchAction @@ -468,7 +468,7 @@ cannot work at the same time. #### Defined in -[types.ts:146](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L146) +[types.ts:146](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L146) ## PanOptions (includes MiscOptions) @@ -490,7 +490,7 @@ empty space around the element will be shown. #### Defined in -[types.ts:165](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L165) +[types.ts:165](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L165) ### cursor @@ -500,7 +500,7 @@ The cursor style to set on the panzoom element #### Defined in -[types.ts:167](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L167) +[types.ts:167](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L167) ### disablePan @@ -512,7 +512,7 @@ The element will still pan accordingly. #### Defined in -[types.ts:173](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L173) +[types.ts:173](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L173) ### disableXAxis @@ -522,7 +522,7 @@ Pan only on the Y axis #### Defined in -[types.ts:175](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L175) +[types.ts:175](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L175) ### disableYAxis @@ -532,7 +532,7 @@ Pan only on the X axis #### Defined in -[types.ts:177](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L177) +[types.ts:177](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L177) ### panOnlyWhenZoomed @@ -542,7 +542,7 @@ Disable panning while the scale is equal to the starting value #### Defined in -[types.ts:181](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L181) +[types.ts:181](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L181) ### relative @@ -552,7 +552,7 @@ When passing x and y values to .pan(), treat the values as relative to their cur #### Defined in -[types.ts:179](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L179) +[types.ts:179](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L179) ### roundPixels @@ -567,7 +567,7 @@ zooming in when using this option. #### Defined in -[types.ts:190](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L190) +[types.ts:190](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L190) ## ZoomOptions (includes MiscOptions) @@ -579,7 +579,7 @@ Disable zooming functionality #### Defined in -[types.ts:195](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L195) +[types.ts:195](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L195) ### focal @@ -599,7 +599,7 @@ to the parent dimensions. #### Defined in -[types.ts:202](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L202) +[types.ts:202](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L202) ### maxScale @@ -609,7 +609,7 @@ The maximum scale when zooming #### Defined in -[types.ts:206](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L206) +[types.ts:206](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L206) ### minScale @@ -619,7 +619,7 @@ The minimum scale when zooming #### Defined in -[types.ts:204](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L204) +[types.ts:204](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L204) ### step @@ -629,7 +629,7 @@ The step affects zoom calculation when zooming with a mouse wheel, when pinch zo #### Defined in -[types.ts:208](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L208) +[types.ts:208](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L208) ## PanzoomObject @@ -656,7 +656,7 @@ panzoom.bind() #### Defined in -[types.ts:235](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L235) +[types.ts:235](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L235) ### destroy @@ -670,7 +670,7 @@ Remove all event listeners bound to the the Panzoom element #### Defined in -[types.ts:237](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L237) +[types.ts:237](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L237) ### eventNames @@ -690,7 +690,7 @@ Pointer or Touch events. #### Defined in -[types.ts:243](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L243) +[types.ts:243](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L243) ### getOptions @@ -704,7 +704,7 @@ Returns a _copy_ of the current options object #### Defined in -[types.ts:249](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L249) +[types.ts:249](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L249) ### getPan @@ -723,7 +723,7 @@ Get the current x/y translation #### Defined in -[types.ts:245](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L245) +[types.ts:245](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L245) ### getScale @@ -737,7 +737,7 @@ Get the current scale #### Defined in -[types.ts:247](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L247) +[types.ts:247](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L247) ### handleDown @@ -775,7 +775,7 @@ document.addEventListener('pointerup', panzoom.handleUp) #### Defined in -[types.ts:271](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L271) +[types.ts:271](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L271) ### handleMove @@ -793,7 +793,7 @@ document.addEventListener('pointerup', panzoom.handleUp) #### Defined in -[types.ts:272](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L272) +[types.ts:272](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L272) ### handleUp @@ -811,7 +811,7 @@ document.addEventListener('pointerup', panzoom.handleUp) #### Defined in -[types.ts:273](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L273) +[types.ts:273](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L273) ### pan @@ -840,7 +840,7 @@ panzoom.pan(10, 10, { relative: true }) #### Defined in -[types.ts:284](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L284) +[types.ts:284](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L284) ### reset @@ -869,7 +869,7 @@ panzoom.reset({ animate: false }) #### Defined in -[types.ts:297](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L297) +[types.ts:297](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L297) ### resetStyle @@ -888,7 +888,7 @@ panzoom.resetStyle() #### Defined in -[types.ts:306](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L306) +[types.ts:306](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L306) ### setOptions @@ -917,7 +917,7 @@ panzoom.setOptions({ cursor: 'default' }) #### Defined in -[types.ts:319](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L319) +[types.ts:319](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L319) ### setStyle @@ -938,7 +938,7 @@ A convenience method for setting prefixed styles on the Panzoom element #### Defined in -[types.ts:321](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L321) +[types.ts:321](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L321) ### zoom @@ -964,7 +964,7 @@ panzoom.zoom(2.2, { animate: true }) #### Defined in -[types.ts:330](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L330) +[types.ts:330](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L330) ### zoomIn @@ -991,7 +991,7 @@ panzoom.zoomIn({ animate: false }) #### Defined in -[types.ts:341](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L341) +[types.ts:341](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L341) ### zoomOut @@ -1018,7 +1018,7 @@ panzoom.zoomOut({ animate: false }) #### Defined in -[types.ts:352](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L352) +[types.ts:352](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L352) ### zoomToPoint @@ -1049,7 +1049,7 @@ panzoom.zoomToPoint(1.2, pointerEvent) #### Defined in -[types.ts:363](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L363) +[types.ts:363](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L363) ### zoomWithWheel @@ -1095,7 +1095,7 @@ elem.parentElement.addEventListener('wheel', function (event) { #### Defined in -[types.ts:396](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L396) +[types.ts:396](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L396) ## CurrentValues @@ -1105,7 +1105,7 @@ elem.parentElement.addEventListener('wheel', function (event) { #### Defined in -[types.ts:219](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L219) +[types.ts:219](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L219) ### scale @@ -1113,7 +1113,7 @@ elem.parentElement.addEventListener('wheel', function (event) { #### Defined in -[types.ts:218](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L218) +[types.ts:218](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L218) ### x @@ -1121,7 +1121,7 @@ elem.parentElement.addEventListener('wheel', function (event) { #### Defined in -[types.ts:216](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L216) +[types.ts:216](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L216) ### y @@ -1129,7 +1129,7 @@ elem.parentElement.addEventListener('wheel', function (event) { #### Defined in -[types.ts:217](https://github.com/timmywil/panzoom/blob/6ed904e/src/types.ts#L217) +[types.ts:217](https://github.com/timmywil/panzoom/blob/8205b07/src/types.ts#L217) ## Events diff --git a/dist/panzoom.es.js b/dist/panzoom.es.js new file mode 100644 index 00000000..3194d08d --- /dev/null +++ b/dist/panzoom.es.js @@ -0,0 +1,784 @@ +/** +* Panzoom for panning and zooming elements using CSS transforms +* Copyright Timmy Willison and other contributors +* https://github.com/timmywil/panzoom/blob/main/MIT-License.txt +*/ +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; + +/* eslint-disable no-var */ +if (typeof window !== 'undefined') { + // Support: IE11 only + if (window.NodeList && !NodeList.prototype.forEach) { + NodeList.prototype.forEach = Array.prototype.forEach; + } + // Support: IE11 only + // CustomEvent is an object instead of a constructor + if (typeof window.CustomEvent !== 'function') { + window.CustomEvent = function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: null }; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt + }; + } +} + +/** + * Utilites for working with multiple pointer events + */ +function findEventIndex(pointers, event) { + var i = pointers.length; + while (i--) { + if (pointers[i].pointerId === event.pointerId) { + return i; + } + } + return -1; +} +function addPointer(pointers, event) { + var i; + // Add touches if applicable + if (event.touches) { + i = 0; + for (var _i = 0, _a = event.touches; _i < _a.length; _i++) { + var touch = _a[_i]; + touch.pointerId = i++; + addPointer(pointers, touch); + } + return; + } + i = findEventIndex(pointers, event); + // Update if already present + if (i > -1) { + pointers.splice(i, 1); + } + pointers.push(event); +} +function removePointer(pointers, event) { + // Add touches if applicable + if (event.touches) { + // Remove all touches + while (pointers.length) { + pointers.pop(); + } + return; + } + var i = findEventIndex(pointers, event); + if (i > -1) { + pointers.splice(i, 1); + } +} +/** + * Calculates a center point between + * the given pointer events, for panning + * with multiple pointers. + */ +function getMiddle(pointers) { + // Copy to avoid changing by reference + pointers = pointers.slice(0); + var event1 = pointers.pop(); + var event2; + while ((event2 = pointers.pop())) { + event1 = { + clientX: (event2.clientX - event1.clientX) / 2 + event1.clientX, + clientY: (event2.clientY - event1.clientY) / 2 + event1.clientY + }; + } + return event1; +} +/** + * Calculates the distance between two points + * for pinch zooming. + * Limits to the first 2 + */ +function getDistance(pointers) { + if (pointers.length < 2) { + return 0; + } + var event1 = pointers[0]; + var event2 = pointers[1]; + return Math.sqrt(Math.pow(Math.abs(event2.clientX - event1.clientX), 2) + + Math.pow(Math.abs(event2.clientY - event1.clientY), 2)); +} + +var events = { + down: 'mousedown', + move: 'mousemove', + up: 'mouseup mouseleave' +}; +if (typeof window !== 'undefined') { + if (typeof window.PointerEvent === 'function') { + events = { + down: 'pointerdown', + move: 'pointermove', + up: 'pointerup pointerleave pointercancel' + }; + } + else if (typeof window.TouchEvent === 'function') { + events = { + down: 'touchstart', + move: 'touchmove', + up: 'touchend touchcancel' + }; + } +} +function onPointer(event, elem, handler, eventOpts) { + events[event].split(' ').forEach(function (name) { + elem.addEventListener(name, handler, eventOpts); + }); +} +function destroyPointer(event, elem, handler) { + events[event].split(' ').forEach(function (name) { + elem.removeEventListener(name, handler); + }); +} + +var isIE = typeof document !== 'undefined' && !!document.documentMode; +/** + * Lazy creation of a CSS style declaration + */ +var divStyle; +function createStyle() { + if (divStyle) { + return divStyle; + } + return (divStyle = document.createElement('div').style); +} +/** + * Proper prefixing for cross-browser compatibility + */ +var prefixes = ['webkit', 'moz', 'ms']; +var prefixCache = {}; +function getPrefixedName(name) { + if (prefixCache[name]) { + return prefixCache[name]; + } + var divStyle = createStyle(); + if (name in divStyle) { + return (prefixCache[name] = name); + } + var capName = name[0].toUpperCase() + name.slice(1); + var i = prefixes.length; + while (i--) { + var prefixedName = "".concat(prefixes[i]).concat(capName); + if (prefixedName in divStyle) { + return (prefixCache[name] = prefixedName); + } + } +} +/** + * Gets a style value expected to be a number + */ +function getCSSNum(name, style) { + return parseFloat(style[getPrefixedName(name)]) || 0; +} +function getBoxStyle(elem, name, style) { + if (style === void 0) { style = window.getComputedStyle(elem); } + // Support: FF 68+ + // Firefox requires specificity for border + var suffix = name === 'border' ? 'Width' : ''; + return { + left: getCSSNum("".concat(name, "Left").concat(suffix), style), + right: getCSSNum("".concat(name, "Right").concat(suffix), style), + top: getCSSNum("".concat(name, "Top").concat(suffix), style), + bottom: getCSSNum("".concat(name, "Bottom").concat(suffix), style) + }; +} +/** + * Set a style using the properly prefixed name + */ +function setStyle(elem, name, value) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + elem.style[getPrefixedName(name)] = value; +} +/** + * Constructs the transition from panzoom options + * and takes care of prefixing the transition and transform + */ +function setTransition(elem, options) { + var transform = getPrefixedName('transform'); + setStyle(elem, 'transition', "".concat(transform, " ").concat(options.duration, "ms ").concat(options.easing)); +} +/** + * Set the transform using the proper prefix + * + * Override the transform setter. + * This is exposed mostly so the user could + * set other parts of a transform + * aside from scale and translate. + * Default is defined in src/css.ts. + * + * ```js + * // This example always sets a rotation + * // when setting the scale and translation + * const panzoom = Panzoom(elem, { + * setTransform: (elem, { scale, x, y }) => { + * panzoom.setStyle('transform', `rotate(0.5turn) scale(${scale}) translate(${x}px, ${y}px)`) + * } + * }) + * ``` + */ +function setTransform(elem, _a, _options) { + var x = _a.x, y = _a.y, scale = _a.scale, isSVG = _a.isSVG; + setStyle(elem, 'transform', "scale(".concat(scale, ") translate(").concat(x, "px, ").concat(y, "px)")); + if (isSVG && isIE) { + var matrixValue = window.getComputedStyle(elem).getPropertyValue('transform'); + elem.setAttribute('transform', matrixValue); + } +} +/** + * Dimensions used in containment and focal point zooming + */ +function getDimensions(elem) { + var parent = elem.parentNode; + var style = window.getComputedStyle(elem); + var parentStyle = window.getComputedStyle(parent); + var rectElem = elem.getBoundingClientRect(); + var rectParent = parent.getBoundingClientRect(); + return { + elem: { + style: style, + width: rectElem.width, + height: rectElem.height, + top: rectElem.top, + bottom: rectElem.bottom, + left: rectElem.left, + right: rectElem.right, + margin: getBoxStyle(elem, 'margin', style), + border: getBoxStyle(elem, 'border', style) + }, + parent: { + style: parentStyle, + width: rectParent.width, + height: rectParent.height, + top: rectParent.top, + bottom: rectParent.bottom, + left: rectParent.left, + right: rectParent.right, + padding: getBoxStyle(parent, 'padding', parentStyle), + border: getBoxStyle(parent, 'border', parentStyle) + } + }; +} + +/** + * Determine if an element is attached to the DOM + * Panzoom requires this so events work properly + */ +function isAttached(elem) { + var doc = elem.ownerDocument; + var parent = elem.parentNode; + return (doc && + parent && + doc.nodeType === 9 && + parent.nodeType === 1 && + doc.documentElement.contains(parent)); +} + +function getClass(elem) { + return (elem.getAttribute('class') || '').trim(); +} +function hasClass(elem, className) { + return elem.nodeType === 1 && " ".concat(getClass(elem), " ").indexOf(" ".concat(className, " ")) > -1; +} +function isExcluded(elem, options) { + for (var cur = elem; cur != null; cur = cur.parentNode) { + if (hasClass(cur, options.excludeClass) || options.exclude.indexOf(cur) > -1) { + return true; + } + } + return false; +} + +/** + * Determine if an element is SVG by checking the namespace + * Exception: the element itself should be treated like HTML + */ +var rsvg = /^http:[\w\.\/]+svg$/; +function isSVGElement(elem) { + return rsvg.test(elem.namespaceURI) && elem.nodeName.toLowerCase() !== 'svg'; +} + +function shallowClone(obj) { + var clone = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = obj[key]; + } + } + return clone; +} + +var defaultOptions = { + animate: false, + canvas: false, + cursor: 'move', + disablePan: false, + disableZoom: false, + disableXAxis: false, + disableYAxis: false, + duration: 200, + easing: 'ease-in-out', + exclude: [], + excludeClass: 'panzoom-exclude', + handleStartEvent: function (e) { + e.preventDefault(); + e.stopPropagation(); + }, + maxScale: 4, + minScale: 0.125, + overflow: 'hidden', + panOnlyWhenZoomed: false, + pinchAndPan: false, + relative: false, + setTransform: setTransform, + startX: 0, + startY: 0, + startScale: 1, + step: 0.3, + touchAction: 'none' +}; +function Panzoom(elem, options) { + if (!elem) { + throw new Error('Panzoom requires an element as an argument'); + } + if (elem.nodeType !== 1) { + throw new Error('Panzoom requires an element with a nodeType of 1'); + } + if (!isAttached(elem)) { + throw new Error('Panzoom should be called on elements that have been attached to the DOM'); + } + options = __assign(__assign({}, defaultOptions), options); + var isSVG = isSVGElement(elem); + var parent = elem.parentNode; + // Set parent styles + parent.style.overflow = options.overflow; + parent.style.userSelect = 'none'; + // This is important for mobile to + // prevent scrolling while panning + parent.style.touchAction = options.touchAction; + (options.canvas ? parent : elem).style.cursor = options.cursor; + // Set element styles + elem.style.userSelect = 'none'; + elem.style.touchAction = options.touchAction; + // The default for HTML is '50% 50%' + // The default for SVG is '0 0' + // SVG can't be changed in IE + setStyle(elem, 'transformOrigin', typeof options.origin === 'string' ? options.origin : isSVG ? '0 0' : '50% 50%'); + function resetStyle() { + parent.style.overflow = ''; + parent.style.userSelect = ''; + parent.style.touchAction = ''; + parent.style.cursor = ''; + elem.style.cursor = ''; + elem.style.userSelect = ''; + elem.style.touchAction = ''; + setStyle(elem, 'transformOrigin', ''); + } + function setOptions(opts) { + if (opts === void 0) { opts = {}; } + for (var key in opts) { + if (opts.hasOwnProperty(key)) { + options[key] = opts[key]; + } + } + // Handle option side-effects + if (opts.hasOwnProperty('cursor') || opts.hasOwnProperty('canvas')) { + parent.style.cursor = elem.style.cursor = ''; + (options.canvas ? parent : elem).style.cursor = options.cursor; + } + if (opts.hasOwnProperty('overflow')) { + parent.style.overflow = opts.overflow; + } + if (opts.hasOwnProperty('touchAction')) { + parent.style.touchAction = opts.touchAction; + elem.style.touchAction = opts.touchAction; + } + } + var x = 0; + var y = 0; + var scale = 1; + var isPanning = false; + zoom(options.startScale, { animate: false, force: true }); + // Wait for scale to update + // for accurate dimensions + // to constrain initial values + setTimeout(function () { + pan(options.startX, options.startY, { animate: false, force: true }); + }); + function trigger(eventName, detail, opts) { + if (opts.silent) { + return; + } + var event = new CustomEvent(eventName, { detail: detail }); + elem.dispatchEvent(event); + } + function setTransformWithEvent(eventName, opts, originalEvent) { + var value = { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: originalEvent }; + requestAnimationFrame(function () { + if (typeof opts.animate === 'boolean') { + if (opts.animate) { + setTransition(elem, opts); + } + else { + setStyle(elem, 'transition', 'none'); + } + } + opts.setTransform(elem, value, opts); + trigger(eventName, value, opts); + trigger('panzoomchange', value, opts); + }); + return value; + } + function constrainXY(toX, toY, toScale, panOptions) { + var opts = __assign(__assign({}, options), panOptions); + var result = { x: x, y: y, opts: opts }; + if (!opts.force && (opts.disablePan || (opts.panOnlyWhenZoomed && scale === opts.startScale))) { + return result; + } + toX = parseFloat(toX); + toY = parseFloat(toY); + if (!opts.disableXAxis) { + result.x = (opts.relative ? x : 0) + toX; + } + if (!opts.disableYAxis) { + result.y = (opts.relative ? y : 0) + toY; + } + if (opts.contain) { + var dims = getDimensions(elem); + var realWidth = dims.elem.width / scale; + var realHeight = dims.elem.height / scale; + var scaledWidth = realWidth * toScale; + var scaledHeight = realHeight * toScale; + var diffHorizontal = (scaledWidth - realWidth) / 2; + var diffVertical = (scaledHeight - realHeight) / 2; + if (opts.contain === 'inside') { + var minX = (-dims.elem.margin.left - dims.parent.padding.left + diffHorizontal) / toScale; + var maxX = (dims.parent.width - + scaledWidth - + dims.parent.padding.left - + dims.elem.margin.left - + dims.parent.border.left - + dims.parent.border.right + + diffHorizontal) / + toScale; + result.x = Math.max(Math.min(result.x, maxX), minX); + var minY = (-dims.elem.margin.top - dims.parent.padding.top + diffVertical) / toScale; + var maxY = (dims.parent.height - + scaledHeight - + dims.parent.padding.top - + dims.elem.margin.top - + dims.parent.border.top - + dims.parent.border.bottom + + diffVertical) / + toScale; + result.y = Math.max(Math.min(result.y, maxY), minY); + } + else if (opts.contain === 'outside') { + var minX = (-(scaledWidth - dims.parent.width) - + dims.parent.padding.left - + dims.parent.border.left - + dims.parent.border.right + + diffHorizontal) / + toScale; + var maxX = (diffHorizontal - dims.parent.padding.left) / toScale; + result.x = Math.max(Math.min(result.x, maxX), minX); + var minY = (-(scaledHeight - dims.parent.height) - + dims.parent.padding.top - + dims.parent.border.top - + dims.parent.border.bottom + + diffVertical) / + toScale; + var maxY = (diffVertical - dims.parent.padding.top) / toScale; + result.y = Math.max(Math.min(result.y, maxY), minY); + } + } + if (opts.roundPixels) { + result.x = Math.round(result.x); + result.y = Math.round(result.y); + } + return result; + } + function constrainScale(toScale, zoomOptions) { + var opts = __assign(__assign({}, options), zoomOptions); + var result = { scale: scale, opts: opts }; + if (!opts.force && opts.disableZoom) { + return result; + } + var minScale = options.minScale; + var maxScale = options.maxScale; + if (opts.contain) { + var dims = getDimensions(elem); + var elemWidth = dims.elem.width / scale; + var elemHeight = dims.elem.height / scale; + if (elemWidth > 1 && elemHeight > 1) { + var parentWidth = dims.parent.width - dims.parent.border.left - dims.parent.border.right; + var parentHeight = dims.parent.height - dims.parent.border.top - dims.parent.border.bottom; + var elemScaledWidth = parentWidth / elemWidth; + var elemScaledHeight = parentHeight / elemHeight; + if (options.contain === 'inside') { + maxScale = Math.min(maxScale, elemScaledWidth, elemScaledHeight); + } + else if (options.contain === 'outside') { + minScale = Math.max(minScale, elemScaledWidth, elemScaledHeight); + } + } + } + result.scale = Math.min(Math.max(toScale, minScale), maxScale); + return result; + } + function pan(toX, toY, panOptions, originalEvent) { + var result = constrainXY(toX, toY, scale, panOptions); + // Only try to set if the result is somehow different + if (x !== result.x || y !== result.y) { + x = result.x; + y = result.y; + return setTransformWithEvent('panzoompan', result.opts, originalEvent); + } + return { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: originalEvent }; + } + function zoom(toScale, zoomOptions, originalEvent) { + var result = constrainScale(toScale, zoomOptions); + var opts = result.opts; + if (!opts.force && opts.disableZoom) { + return; + } + toScale = result.scale; + var toX = x; + var toY = y; + if (opts.focal) { + // The difference between the point after the scale and the point before the scale + // plus the current translation after the scale + // neutralized to no scale (as the transform scale will apply to the translation) + var focal = opts.focal; + toX = (focal.x / toScale - focal.x / scale + x * toScale) / toScale; + toY = (focal.y / toScale - focal.y / scale + y * toScale) / toScale; + } + var panResult = constrainXY(toX, toY, toScale, { relative: false, force: true }); + x = panResult.x; + y = panResult.y; + scale = toScale; + return setTransformWithEvent('panzoomzoom', opts, originalEvent); + } + function zoomInOut(isIn, zoomOptions) { + var opts = __assign(__assign(__assign({}, options), { animate: true }), zoomOptions); + return zoom(scale * Math.exp((isIn ? 1 : -1) * opts.step), opts); + } + function zoomIn(zoomOptions) { + return zoomInOut(true, zoomOptions); + } + function zoomOut(zoomOptions) { + return zoomInOut(false, zoomOptions); + } + function zoomToPoint(toScale, point, zoomOptions, originalEvent) { + var dims = getDimensions(elem); + // Instead of thinking of operating on the panzoom element, + // think of operating on the area inside the panzoom + // element's parent + // Subtract padding and border + var effectiveArea = { + width: dims.parent.width - + dims.parent.padding.left - + dims.parent.padding.right - + dims.parent.border.left - + dims.parent.border.right, + height: dims.parent.height - + dims.parent.padding.top - + dims.parent.padding.bottom - + dims.parent.border.top - + dims.parent.border.bottom + }; + // Adjust the clientX/clientY to ignore the area + // outside the effective area + var clientX = point.clientX - + dims.parent.left - + dims.parent.padding.left - + dims.parent.border.left - + dims.elem.margin.left; + var clientY = point.clientY - + dims.parent.top - + dims.parent.padding.top - + dims.parent.border.top - + dims.elem.margin.top; + // Adjust the clientX/clientY for HTML elements, + // because they have a transform-origin of 50% 50% + if (!isSVG) { + clientX -= dims.elem.width / scale / 2; + clientY -= dims.elem.height / scale / 2; + } + // Convert the mouse point from it's position over the + // effective area before the scale to the position + // over the effective area after the scale. + var focal = { + x: (clientX / effectiveArea.width) * (effectiveArea.width * toScale), + y: (clientY / effectiveArea.height) * (effectiveArea.height * toScale) + }; + return zoom(toScale, __assign(__assign({}, zoomOptions), { animate: false, focal: focal }), originalEvent); + } + function zoomWithWheel(event, zoomOptions) { + // Need to prevent the default here + // or it conflicts with regular page scroll + event.preventDefault(); + var opts = __assign(__assign(__assign({}, options), zoomOptions), { animate: false }); + // Normalize to deltaX in case shift modifier is used on Mac + var delta = event.deltaY === 0 && event.deltaX ? event.deltaX : event.deltaY; + var wheel = delta < 0 ? 1 : -1; + var toScale = constrainScale(scale * Math.exp((wheel * opts.step) / 3), opts).scale; + return zoomToPoint(toScale, event, opts, event); + } + function reset(resetOptions) { + var opts = __assign(__assign(__assign({}, options), { animate: true, force: true }), resetOptions); + scale = constrainScale(opts.startScale, opts).scale; + var panResult = constrainXY(opts.startX, opts.startY, scale, opts); + x = panResult.x; + y = panResult.y; + return setTransformWithEvent('panzoomreset', opts); + } + var origX; + var origY; + var startClientX; + var startClientY; + var startScale; + var startDistance; + var pointers = []; + function handleDown(event) { + // Don't handle this event if the target is excluded + if (isExcluded(event.target, options)) { + return; + } + addPointer(pointers, event); + isPanning = true; + options.handleStartEvent(event); + origX = x; + origY = y; + trigger('panzoomstart', { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: event }, options); + // This works whether there are multiple + // pointers or not + var point = getMiddle(pointers); + startClientX = point.clientX; + startClientY = point.clientY; + startScale = scale; + startDistance = getDistance(pointers); + } + function handleMove(event) { + if (!isPanning || + origX === undefined || + origY === undefined || + startClientX === undefined || + startClientY === undefined) { + return; + } + addPointer(pointers, event); + var current = getMiddle(pointers); + var hasMultiple = pointers.length > 1; + var toScale = scale; + if (hasMultiple) { + // A startDistance of 0 means + // that there weren't 2 pointers + // handled on start + if (startDistance === 0) { + startDistance = getDistance(pointers); + } + // Use the distance between the first 2 pointers + // to determine the current scale + var diff = getDistance(pointers) - startDistance; + toScale = constrainScale((diff * options.step) / 80 + startScale).scale; + zoomToPoint(toScale, current, { animate: false }, event); + } + // Pan during pinch if pinchAndPan is true. + // Note: some calculations may be off because the zoom + // above has not yet rendered. However, the behavior + // was removed before the new scale was used in the following + // pan calculation. + // See https://github.com/timmywil/panzoom/issues/512 + // and https://github.com/timmywil/panzoom/issues/606 + if (!hasMultiple || options.pinchAndPan) { + pan(origX + (current.clientX - startClientX) / toScale, origY + (current.clientY - startClientY) / toScale, { + animate: false + }, event); + } + } + function handleUp(event) { + // Don't call panzoomend when panning with 2 touches + // until both touches end + if (pointers.length === 1) { + trigger('panzoomend', { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: event }, options); + } + // Note: don't remove all pointers + // Can restart without having to reinitiate all of them + // Remove the pointer regardless of the isPanning state + removePointer(pointers, event); + if (!isPanning) { + return; + } + isPanning = false; + origX = origY = startClientX = startClientY = undefined; + } + var bound = false; + function bind() { + if (bound) { + return; + } + bound = true; + onPointer('down', options.canvas ? parent : elem, handleDown); + onPointer('move', document, handleMove, { passive: true }); + onPointer('up', document, handleUp, { passive: true }); + } + function destroy() { + bound = false; + destroyPointer('down', options.canvas ? parent : elem, handleDown); + destroyPointer('move', document, handleMove); + destroyPointer('up', document, handleUp); + } + if (!options.noBind) { + bind(); + } + return { + bind: bind, + destroy: destroy, + eventNames: events, + getPan: function () { return ({ x: x, y: y }); }, + getScale: function () { return scale; }, + getOptions: function () { return shallowClone(options); }, + handleDown: handleDown, + handleMove: handleMove, + handleUp: handleUp, + pan: pan, + reset: reset, + resetStyle: resetStyle, + setOptions: setOptions, + setStyle: function (name, value) { return setStyle(elem, name, value); }, + zoom: zoom, + zoomIn: zoomIn, + zoomOut: zoomOut, + zoomToPoint: zoomToPoint, + zoomWithWheel: zoomWithWheel + }; +} +Panzoom.defaultOptions = defaultOptions; + +export { Panzoom as default }; diff --git a/dist/panzoom.js b/dist/panzoom.js new file mode 100644 index 00000000..b580d0b3 --- /dev/null +++ b/dist/panzoom.js @@ -0,0 +1,792 @@ +/** +* Panzoom for panning and zooming elements using CSS transforms +* Copyright Timmy Willison and other contributors +* https://github.com/timmywil/panzoom/blob/main/MIT-License.txt +*/ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Panzoom = factory()); +})(this, (function () { 'use strict'; + + /****************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + /* eslint-disable no-var */ + if (typeof window !== 'undefined') { + // Support: IE11 only + if (window.NodeList && !NodeList.prototype.forEach) { + NodeList.prototype.forEach = Array.prototype.forEach; + } + // Support: IE11 only + // CustomEvent is an object instead of a constructor + if (typeof window.CustomEvent !== 'function') { + window.CustomEvent = function CustomEvent(event, params) { + params = params || { bubbles: false, cancelable: false, detail: null }; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt + }; + } + } + + /** + * Utilites for working with multiple pointer events + */ + function findEventIndex(pointers, event) { + var i = pointers.length; + while (i--) { + if (pointers[i].pointerId === event.pointerId) { + return i; + } + } + return -1; + } + function addPointer(pointers, event) { + var i; + // Add touches if applicable + if (event.touches) { + i = 0; + for (var _i = 0, _a = event.touches; _i < _a.length; _i++) { + var touch = _a[_i]; + touch.pointerId = i++; + addPointer(pointers, touch); + } + return; + } + i = findEventIndex(pointers, event); + // Update if already present + if (i > -1) { + pointers.splice(i, 1); + } + pointers.push(event); + } + function removePointer(pointers, event) { + // Add touches if applicable + if (event.touches) { + // Remove all touches + while (pointers.length) { + pointers.pop(); + } + return; + } + var i = findEventIndex(pointers, event); + if (i > -1) { + pointers.splice(i, 1); + } + } + /** + * Calculates a center point between + * the given pointer events, for panning + * with multiple pointers. + */ + function getMiddle(pointers) { + // Copy to avoid changing by reference + pointers = pointers.slice(0); + var event1 = pointers.pop(); + var event2; + while ((event2 = pointers.pop())) { + event1 = { + clientX: (event2.clientX - event1.clientX) / 2 + event1.clientX, + clientY: (event2.clientY - event1.clientY) / 2 + event1.clientY + }; + } + return event1; + } + /** + * Calculates the distance between two points + * for pinch zooming. + * Limits to the first 2 + */ + function getDistance(pointers) { + if (pointers.length < 2) { + return 0; + } + var event1 = pointers[0]; + var event2 = pointers[1]; + return Math.sqrt(Math.pow(Math.abs(event2.clientX - event1.clientX), 2) + + Math.pow(Math.abs(event2.clientY - event1.clientY), 2)); + } + + var events = { + down: 'mousedown', + move: 'mousemove', + up: 'mouseup mouseleave' + }; + if (typeof window !== 'undefined') { + if (typeof window.PointerEvent === 'function') { + events = { + down: 'pointerdown', + move: 'pointermove', + up: 'pointerup pointerleave pointercancel' + }; + } + else if (typeof window.TouchEvent === 'function') { + events = { + down: 'touchstart', + move: 'touchmove', + up: 'touchend touchcancel' + }; + } + } + function onPointer(event, elem, handler, eventOpts) { + events[event].split(' ').forEach(function (name) { + elem.addEventListener(name, handler, eventOpts); + }); + } + function destroyPointer(event, elem, handler) { + events[event].split(' ').forEach(function (name) { + elem.removeEventListener(name, handler); + }); + } + + var isIE = typeof document !== 'undefined' && !!document.documentMode; + /** + * Lazy creation of a CSS style declaration + */ + var divStyle; + function createStyle() { + if (divStyle) { + return divStyle; + } + return (divStyle = document.createElement('div').style); + } + /** + * Proper prefixing for cross-browser compatibility + */ + var prefixes = ['webkit', 'moz', 'ms']; + var prefixCache = {}; + function getPrefixedName(name) { + if (prefixCache[name]) { + return prefixCache[name]; + } + var divStyle = createStyle(); + if (name in divStyle) { + return (prefixCache[name] = name); + } + var capName = name[0].toUpperCase() + name.slice(1); + var i = prefixes.length; + while (i--) { + var prefixedName = "".concat(prefixes[i]).concat(capName); + if (prefixedName in divStyle) { + return (prefixCache[name] = prefixedName); + } + } + } + /** + * Gets a style value expected to be a number + */ + function getCSSNum(name, style) { + return parseFloat(style[getPrefixedName(name)]) || 0; + } + function getBoxStyle(elem, name, style) { + if (style === void 0) { style = window.getComputedStyle(elem); } + // Support: FF 68+ + // Firefox requires specificity for border + var suffix = name === 'border' ? 'Width' : ''; + return { + left: getCSSNum("".concat(name, "Left").concat(suffix), style), + right: getCSSNum("".concat(name, "Right").concat(suffix), style), + top: getCSSNum("".concat(name, "Top").concat(suffix), style), + bottom: getCSSNum("".concat(name, "Bottom").concat(suffix), style) + }; + } + /** + * Set a style using the properly prefixed name + */ + function setStyle(elem, name, value) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + elem.style[getPrefixedName(name)] = value; + } + /** + * Constructs the transition from panzoom options + * and takes care of prefixing the transition and transform + */ + function setTransition(elem, options) { + var transform = getPrefixedName('transform'); + setStyle(elem, 'transition', "".concat(transform, " ").concat(options.duration, "ms ").concat(options.easing)); + } + /** + * Set the transform using the proper prefix + * + * Override the transform setter. + * This is exposed mostly so the user could + * set other parts of a transform + * aside from scale and translate. + * Default is defined in src/css.ts. + * + * ```js + * // This example always sets a rotation + * // when setting the scale and translation + * const panzoom = Panzoom(elem, { + * setTransform: (elem, { scale, x, y }) => { + * panzoom.setStyle('transform', `rotate(0.5turn) scale(${scale}) translate(${x}px, ${y}px)`) + * } + * }) + * ``` + */ + function setTransform(elem, _a, _options) { + var x = _a.x, y = _a.y, scale = _a.scale, isSVG = _a.isSVG; + setStyle(elem, 'transform', "scale(".concat(scale, ") translate(").concat(x, "px, ").concat(y, "px)")); + if (isSVG && isIE) { + var matrixValue = window.getComputedStyle(elem).getPropertyValue('transform'); + elem.setAttribute('transform', matrixValue); + } + } + /** + * Dimensions used in containment and focal point zooming + */ + function getDimensions(elem) { + var parent = elem.parentNode; + var style = window.getComputedStyle(elem); + var parentStyle = window.getComputedStyle(parent); + var rectElem = elem.getBoundingClientRect(); + var rectParent = parent.getBoundingClientRect(); + return { + elem: { + style: style, + width: rectElem.width, + height: rectElem.height, + top: rectElem.top, + bottom: rectElem.bottom, + left: rectElem.left, + right: rectElem.right, + margin: getBoxStyle(elem, 'margin', style), + border: getBoxStyle(elem, 'border', style) + }, + parent: { + style: parentStyle, + width: rectParent.width, + height: rectParent.height, + top: rectParent.top, + bottom: rectParent.bottom, + left: rectParent.left, + right: rectParent.right, + padding: getBoxStyle(parent, 'padding', parentStyle), + border: getBoxStyle(parent, 'border', parentStyle) + } + }; + } + + /** + * Determine if an element is attached to the DOM + * Panzoom requires this so events work properly + */ + function isAttached(elem) { + var doc = elem.ownerDocument; + var parent = elem.parentNode; + return (doc && + parent && + doc.nodeType === 9 && + parent.nodeType === 1 && + doc.documentElement.contains(parent)); + } + + function getClass(elem) { + return (elem.getAttribute('class') || '').trim(); + } + function hasClass(elem, className) { + return elem.nodeType === 1 && " ".concat(getClass(elem), " ").indexOf(" ".concat(className, " ")) > -1; + } + function isExcluded(elem, options) { + for (var cur = elem; cur != null; cur = cur.parentNode) { + if (hasClass(cur, options.excludeClass) || options.exclude.indexOf(cur) > -1) { + return true; + } + } + return false; + } + + /** + * Determine if an element is SVG by checking the namespace + * Exception: the element itself should be treated like HTML + */ + var rsvg = /^http:[\w\.\/]+svg$/; + function isSVGElement(elem) { + return rsvg.test(elem.namespaceURI) && elem.nodeName.toLowerCase() !== 'svg'; + } + + function shallowClone(obj) { + var clone = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = obj[key]; + } + } + return clone; + } + + var defaultOptions = { + animate: false, + canvas: false, + cursor: 'move', + disablePan: false, + disableZoom: false, + disableXAxis: false, + disableYAxis: false, + duration: 200, + easing: 'ease-in-out', + exclude: [], + excludeClass: 'panzoom-exclude', + handleStartEvent: function (e) { + e.preventDefault(); + e.stopPropagation(); + }, + maxScale: 4, + minScale: 0.125, + overflow: 'hidden', + panOnlyWhenZoomed: false, + pinchAndPan: false, + relative: false, + setTransform: setTransform, + startX: 0, + startY: 0, + startScale: 1, + step: 0.3, + touchAction: 'none' + }; + function Panzoom(elem, options) { + if (!elem) { + throw new Error('Panzoom requires an element as an argument'); + } + if (elem.nodeType !== 1) { + throw new Error('Panzoom requires an element with a nodeType of 1'); + } + if (!isAttached(elem)) { + throw new Error('Panzoom should be called on elements that have been attached to the DOM'); + } + options = __assign(__assign({}, defaultOptions), options); + var isSVG = isSVGElement(elem); + var parent = elem.parentNode; + // Set parent styles + parent.style.overflow = options.overflow; + parent.style.userSelect = 'none'; + // This is important for mobile to + // prevent scrolling while panning + parent.style.touchAction = options.touchAction; + (options.canvas ? parent : elem).style.cursor = options.cursor; + // Set element styles + elem.style.userSelect = 'none'; + elem.style.touchAction = options.touchAction; + // The default for HTML is '50% 50%' + // The default for SVG is '0 0' + // SVG can't be changed in IE + setStyle(elem, 'transformOrigin', typeof options.origin === 'string' ? options.origin : isSVG ? '0 0' : '50% 50%'); + function resetStyle() { + parent.style.overflow = ''; + parent.style.userSelect = ''; + parent.style.touchAction = ''; + parent.style.cursor = ''; + elem.style.cursor = ''; + elem.style.userSelect = ''; + elem.style.touchAction = ''; + setStyle(elem, 'transformOrigin', ''); + } + function setOptions(opts) { + if (opts === void 0) { opts = {}; } + for (var key in opts) { + if (opts.hasOwnProperty(key)) { + options[key] = opts[key]; + } + } + // Handle option side-effects + if (opts.hasOwnProperty('cursor') || opts.hasOwnProperty('canvas')) { + parent.style.cursor = elem.style.cursor = ''; + (options.canvas ? parent : elem).style.cursor = options.cursor; + } + if (opts.hasOwnProperty('overflow')) { + parent.style.overflow = opts.overflow; + } + if (opts.hasOwnProperty('touchAction')) { + parent.style.touchAction = opts.touchAction; + elem.style.touchAction = opts.touchAction; + } + } + var x = 0; + var y = 0; + var scale = 1; + var isPanning = false; + zoom(options.startScale, { animate: false, force: true }); + // Wait for scale to update + // for accurate dimensions + // to constrain initial values + setTimeout(function () { + pan(options.startX, options.startY, { animate: false, force: true }); + }); + function trigger(eventName, detail, opts) { + if (opts.silent) { + return; + } + var event = new CustomEvent(eventName, { detail: detail }); + elem.dispatchEvent(event); + } + function setTransformWithEvent(eventName, opts, originalEvent) { + var value = { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: originalEvent }; + requestAnimationFrame(function () { + if (typeof opts.animate === 'boolean') { + if (opts.animate) { + setTransition(elem, opts); + } + else { + setStyle(elem, 'transition', 'none'); + } + } + opts.setTransform(elem, value, opts); + trigger(eventName, value, opts); + trigger('panzoomchange', value, opts); + }); + return value; + } + function constrainXY(toX, toY, toScale, panOptions) { + var opts = __assign(__assign({}, options), panOptions); + var result = { x: x, y: y, opts: opts }; + if (!opts.force && (opts.disablePan || (opts.panOnlyWhenZoomed && scale === opts.startScale))) { + return result; + } + toX = parseFloat(toX); + toY = parseFloat(toY); + if (!opts.disableXAxis) { + result.x = (opts.relative ? x : 0) + toX; + } + if (!opts.disableYAxis) { + result.y = (opts.relative ? y : 0) + toY; + } + if (opts.contain) { + var dims = getDimensions(elem); + var realWidth = dims.elem.width / scale; + var realHeight = dims.elem.height / scale; + var scaledWidth = realWidth * toScale; + var scaledHeight = realHeight * toScale; + var diffHorizontal = (scaledWidth - realWidth) / 2; + var diffVertical = (scaledHeight - realHeight) / 2; + if (opts.contain === 'inside') { + var minX = (-dims.elem.margin.left - dims.parent.padding.left + diffHorizontal) / toScale; + var maxX = (dims.parent.width - + scaledWidth - + dims.parent.padding.left - + dims.elem.margin.left - + dims.parent.border.left - + dims.parent.border.right + + diffHorizontal) / + toScale; + result.x = Math.max(Math.min(result.x, maxX), minX); + var minY = (-dims.elem.margin.top - dims.parent.padding.top + diffVertical) / toScale; + var maxY = (dims.parent.height - + scaledHeight - + dims.parent.padding.top - + dims.elem.margin.top - + dims.parent.border.top - + dims.parent.border.bottom + + diffVertical) / + toScale; + result.y = Math.max(Math.min(result.y, maxY), minY); + } + else if (opts.contain === 'outside') { + var minX = (-(scaledWidth - dims.parent.width) - + dims.parent.padding.left - + dims.parent.border.left - + dims.parent.border.right + + diffHorizontal) / + toScale; + var maxX = (diffHorizontal - dims.parent.padding.left) / toScale; + result.x = Math.max(Math.min(result.x, maxX), minX); + var minY = (-(scaledHeight - dims.parent.height) - + dims.parent.padding.top - + dims.parent.border.top - + dims.parent.border.bottom + + diffVertical) / + toScale; + var maxY = (diffVertical - dims.parent.padding.top) / toScale; + result.y = Math.max(Math.min(result.y, maxY), minY); + } + } + if (opts.roundPixels) { + result.x = Math.round(result.x); + result.y = Math.round(result.y); + } + return result; + } + function constrainScale(toScale, zoomOptions) { + var opts = __assign(__assign({}, options), zoomOptions); + var result = { scale: scale, opts: opts }; + if (!opts.force && opts.disableZoom) { + return result; + } + var minScale = options.minScale; + var maxScale = options.maxScale; + if (opts.contain) { + var dims = getDimensions(elem); + var elemWidth = dims.elem.width / scale; + var elemHeight = dims.elem.height / scale; + if (elemWidth > 1 && elemHeight > 1) { + var parentWidth = dims.parent.width - dims.parent.border.left - dims.parent.border.right; + var parentHeight = dims.parent.height - dims.parent.border.top - dims.parent.border.bottom; + var elemScaledWidth = parentWidth / elemWidth; + var elemScaledHeight = parentHeight / elemHeight; + if (options.contain === 'inside') { + maxScale = Math.min(maxScale, elemScaledWidth, elemScaledHeight); + } + else if (options.contain === 'outside') { + minScale = Math.max(minScale, elemScaledWidth, elemScaledHeight); + } + } + } + result.scale = Math.min(Math.max(toScale, minScale), maxScale); + return result; + } + function pan(toX, toY, panOptions, originalEvent) { + var result = constrainXY(toX, toY, scale, panOptions); + // Only try to set if the result is somehow different + if (x !== result.x || y !== result.y) { + x = result.x; + y = result.y; + return setTransformWithEvent('panzoompan', result.opts, originalEvent); + } + return { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: originalEvent }; + } + function zoom(toScale, zoomOptions, originalEvent) { + var result = constrainScale(toScale, zoomOptions); + var opts = result.opts; + if (!opts.force && opts.disableZoom) { + return; + } + toScale = result.scale; + var toX = x; + var toY = y; + if (opts.focal) { + // The difference between the point after the scale and the point before the scale + // plus the current translation after the scale + // neutralized to no scale (as the transform scale will apply to the translation) + var focal = opts.focal; + toX = (focal.x / toScale - focal.x / scale + x * toScale) / toScale; + toY = (focal.y / toScale - focal.y / scale + y * toScale) / toScale; + } + var panResult = constrainXY(toX, toY, toScale, { relative: false, force: true }); + x = panResult.x; + y = panResult.y; + scale = toScale; + return setTransformWithEvent('panzoomzoom', opts, originalEvent); + } + function zoomInOut(isIn, zoomOptions) { + var opts = __assign(__assign(__assign({}, options), { animate: true }), zoomOptions); + return zoom(scale * Math.exp((isIn ? 1 : -1) * opts.step), opts); + } + function zoomIn(zoomOptions) { + return zoomInOut(true, zoomOptions); + } + function zoomOut(zoomOptions) { + return zoomInOut(false, zoomOptions); + } + function zoomToPoint(toScale, point, zoomOptions, originalEvent) { + var dims = getDimensions(elem); + // Instead of thinking of operating on the panzoom element, + // think of operating on the area inside the panzoom + // element's parent + // Subtract padding and border + var effectiveArea = { + width: dims.parent.width - + dims.parent.padding.left - + dims.parent.padding.right - + dims.parent.border.left - + dims.parent.border.right, + height: dims.parent.height - + dims.parent.padding.top - + dims.parent.padding.bottom - + dims.parent.border.top - + dims.parent.border.bottom + }; + // Adjust the clientX/clientY to ignore the area + // outside the effective area + var clientX = point.clientX - + dims.parent.left - + dims.parent.padding.left - + dims.parent.border.left - + dims.elem.margin.left; + var clientY = point.clientY - + dims.parent.top - + dims.parent.padding.top - + dims.parent.border.top - + dims.elem.margin.top; + // Adjust the clientX/clientY for HTML elements, + // because they have a transform-origin of 50% 50% + if (!isSVG) { + clientX -= dims.elem.width / scale / 2; + clientY -= dims.elem.height / scale / 2; + } + // Convert the mouse point from it's position over the + // effective area before the scale to the position + // over the effective area after the scale. + var focal = { + x: (clientX / effectiveArea.width) * (effectiveArea.width * toScale), + y: (clientY / effectiveArea.height) * (effectiveArea.height * toScale) + }; + return zoom(toScale, __assign(__assign({}, zoomOptions), { animate: false, focal: focal }), originalEvent); + } + function zoomWithWheel(event, zoomOptions) { + // Need to prevent the default here + // or it conflicts with regular page scroll + event.preventDefault(); + var opts = __assign(__assign(__assign({}, options), zoomOptions), { animate: false }); + // Normalize to deltaX in case shift modifier is used on Mac + var delta = event.deltaY === 0 && event.deltaX ? event.deltaX : event.deltaY; + var wheel = delta < 0 ? 1 : -1; + var toScale = constrainScale(scale * Math.exp((wheel * opts.step) / 3), opts).scale; + return zoomToPoint(toScale, event, opts, event); + } + function reset(resetOptions) { + var opts = __assign(__assign(__assign({}, options), { animate: true, force: true }), resetOptions); + scale = constrainScale(opts.startScale, opts).scale; + var panResult = constrainXY(opts.startX, opts.startY, scale, opts); + x = panResult.x; + y = panResult.y; + return setTransformWithEvent('panzoomreset', opts); + } + var origX; + var origY; + var startClientX; + var startClientY; + var startScale; + var startDistance; + var pointers = []; + function handleDown(event) { + // Don't handle this event if the target is excluded + if (isExcluded(event.target, options)) { + return; + } + addPointer(pointers, event); + isPanning = true; + options.handleStartEvent(event); + origX = x; + origY = y; + trigger('panzoomstart', { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: event }, options); + // This works whether there are multiple + // pointers or not + var point = getMiddle(pointers); + startClientX = point.clientX; + startClientY = point.clientY; + startScale = scale; + startDistance = getDistance(pointers); + } + function handleMove(event) { + if (!isPanning || + origX === undefined || + origY === undefined || + startClientX === undefined || + startClientY === undefined) { + return; + } + addPointer(pointers, event); + var current = getMiddle(pointers); + var hasMultiple = pointers.length > 1; + var toScale = scale; + if (hasMultiple) { + // A startDistance of 0 means + // that there weren't 2 pointers + // handled on start + if (startDistance === 0) { + startDistance = getDistance(pointers); + } + // Use the distance between the first 2 pointers + // to determine the current scale + var diff = getDistance(pointers) - startDistance; + toScale = constrainScale((diff * options.step) / 80 + startScale).scale; + zoomToPoint(toScale, current, { animate: false }, event); + } + // Pan during pinch if pinchAndPan is true. + // Note: some calculations may be off because the zoom + // above has not yet rendered. However, the behavior + // was removed before the new scale was used in the following + // pan calculation. + // See https://github.com/timmywil/panzoom/issues/512 + // and https://github.com/timmywil/panzoom/issues/606 + if (!hasMultiple || options.pinchAndPan) { + pan(origX + (current.clientX - startClientX) / toScale, origY + (current.clientY - startClientY) / toScale, { + animate: false + }, event); + } + } + function handleUp(event) { + // Don't call panzoomend when panning with 2 touches + // until both touches end + if (pointers.length === 1) { + trigger('panzoomend', { x: x, y: y, scale: scale, isSVG: isSVG, originalEvent: event }, options); + } + // Note: don't remove all pointers + // Can restart without having to reinitiate all of them + // Remove the pointer regardless of the isPanning state + removePointer(pointers, event); + if (!isPanning) { + return; + } + isPanning = false; + origX = origY = startClientX = startClientY = undefined; + } + var bound = false; + function bind() { + if (bound) { + return; + } + bound = true; + onPointer('down', options.canvas ? parent : elem, handleDown); + onPointer('move', document, handleMove, { passive: true }); + onPointer('up', document, handleUp, { passive: true }); + } + function destroy() { + bound = false; + destroyPointer('down', options.canvas ? parent : elem, handleDown); + destroyPointer('move', document, handleMove); + destroyPointer('up', document, handleUp); + } + if (!options.noBind) { + bind(); + } + return { + bind: bind, + destroy: destroy, + eventNames: events, + getPan: function () { return ({ x: x, y: y }); }, + getScale: function () { return scale; }, + getOptions: function () { return shallowClone(options); }, + handleDown: handleDown, + handleMove: handleMove, + handleUp: handleUp, + pan: pan, + reset: reset, + resetStyle: resetStyle, + setOptions: setOptions, + setStyle: function (name, value) { return setStyle(elem, name, value); }, + zoom: zoom, + zoomIn: zoomIn, + zoomOut: zoomOut, + zoomToPoint: zoomToPoint, + zoomWithWheel: zoomWithWheel + }; + } + Panzoom.defaultOptions = defaultOptions; + + return Panzoom; + +})); diff --git a/dist/panzoom.min.js b/dist/panzoom.min.js new file mode 100644 index 00000000..6c167e57 --- /dev/null +++ b/dist/panzoom.min.js @@ -0,0 +1,6 @@ +/** +* Panzoom for panning and zooming elements using CSS transforms +* Copyright Timmy Willison and other contributors +* https://github.com/timmywil/panzoom/blob/main/MIT-License.txt +*/ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Panzoom=e()}(this,function(){"use strict";var Y=function(){return(Y=Object.assign||function(t){for(var e,n=1,o=arguments.length;n { + * panzoom.setStyle('transform', `rotate(0.5turn) scale(${scale}) translate(${x}px, ${y}px)`) + * } + * }) + * ``` + */ +export declare function setTransform(elem: HTMLElement | SVGElement, { x, y, scale, isSVG }: CurrentValues, _options?: PanzoomOptions): void; +/** + * Dimensions used in containment and focal point zooming + */ +export declare function getDimensions(elem: HTMLElement | SVGElement): { + elem: { + style: CSSStyleDeclaration; + width: number; + height: number; + top: number; + bottom: number; + left: number; + right: number; + margin: { + left: number; + right: number; + top: number; + bottom: number; + }; + border: { + left: number; + right: number; + top: number; + bottom: number; + }; + }; + parent: { + style: CSSStyleDeclaration; + width: number; + height: number; + top: number; + bottom: number; + left: number; + right: number; + padding: { + left: number; + right: number; + top: number; + bottom: number; + }; + border: { + left: number; + right: number; + top: number; + bottom: number; + }; + }; +}; diff --git a/dist/src/events.d.ts b/dist/src/events.d.ts new file mode 100644 index 00000000..720ce971 --- /dev/null +++ b/dist/src/events.d.ts @@ -0,0 +1,8 @@ +declare let events: { + down: string; + move: string; + up: string; +}; +export { events as eventNames }; +export declare function onPointer(event: 'down' | 'move' | 'up', elem: HTMLElement | SVGElement | Document, handler: (event: PointerEvent) => void, eventOpts?: boolean | AddEventListenerOptions): void; +export declare function destroyPointer(event: 'down' | 'move' | 'up', elem: HTMLElement | SVGElement | Document, handler: (event: PointerEvent) => void): void; diff --git a/dist/src/isAttached.d.ts b/dist/src/isAttached.d.ts new file mode 100644 index 00000000..64be8691 --- /dev/null +++ b/dist/src/isAttached.d.ts @@ -0,0 +1,5 @@ +/** + * Determine if an element is attached to the DOM + * Panzoom requires this so events work properly + */ +export default function isAttached(elem: HTMLElement | SVGElement | Document): boolean; diff --git a/dist/src/isExcluded.d.ts b/dist/src/isExcluded.d.ts new file mode 100644 index 00000000..56d8a334 --- /dev/null +++ b/dist/src/isExcluded.d.ts @@ -0,0 +1,2 @@ +import type { PanzoomOptions } from './types'; +export default function isExcluded(elem: Element, options: PanzoomOptions): boolean; diff --git a/dist/src/isSVGElement.d.ts b/dist/src/isSVGElement.d.ts new file mode 100644 index 00000000..965bcf54 --- /dev/null +++ b/dist/src/isSVGElement.d.ts @@ -0,0 +1 @@ +export default function isSVGElement(elem: HTMLElement | SVGElement): boolean; diff --git a/dist/src/panzoom.d.ts b/dist/src/panzoom.d.ts new file mode 100644 index 00000000..aa864826 --- /dev/null +++ b/dist/src/panzoom.d.ts @@ -0,0 +1,17 @@ +/** + * Panzoom for panning and zooming elements using CSS transforms + * https://github.com/timmywil/panzoom + * + * Copyright Timmy Willison and other contributors + * Released under the MIT license + * https://github.com/timmywil/panzoom/blob/main/MIT-License.txt + * + */ +import './polyfills'; +import type { PanzoomObject, PanzoomOptions } from './types'; +declare function Panzoom(elem: HTMLElement | SVGElement, options?: Omit): PanzoomObject; +declare namespace Panzoom { + var defaultOptions: PanzoomOptions; +} +export * from './types'; +export default Panzoom; diff --git a/dist/src/pointers.d.ts b/dist/src/pointers.d.ts new file mode 100644 index 00000000..7a1526c8 --- /dev/null +++ b/dist/src/pointers.d.ts @@ -0,0 +1,17 @@ +/** + * Utilites for working with multiple pointer events + */ +export declare function addPointer(pointers: PointerEvent[], event: PointerEvent): void; +export declare function removePointer(pointers: PointerEvent[], event: PointerEvent): void; +/** + * Calculates a center point between + * the given pointer events, for panning + * with multiple pointers. + */ +export declare function getMiddle(pointers: PointerEvent[]): Pick; +/** + * Calculates the distance between two points + * for pinch zooming. + * Limits to the first 2 + */ +export declare function getDistance(pointers: PointerEvent[]): number; diff --git a/dist/src/shallowClone.d.ts b/dist/src/shallowClone.d.ts new file mode 100644 index 00000000..76570a92 --- /dev/null +++ b/dist/src/shallowClone.d.ts @@ -0,0 +1 @@ +export default function shallowClone(obj: any): any; diff --git a/dist/src/types.d.ts b/dist/src/types.d.ts new file mode 100644 index 00000000..3dd2e952 --- /dev/null +++ b/dist/src/types.d.ts @@ -0,0 +1,392 @@ +import { setTransform } from './css'; +export declare type PanzoomEvent = 'panzoomstart' | 'panzoomchange' | 'panzoompan' | 'panzoomzoom' | 'panzoomreset' | 'panzoomend'; +export interface PanzoomEventDetail { + x: number; + y: number; + scale: number; + isSVG: boolean; + originalEvent: PointerEvent | TouchEvent | MouseEvent; +} +export interface MiscOptions { + /** Whether to animate transitions */ + animate?: boolean; + /** + * This option treats the Panzoom element's parent + * as a canvas. Effectively, Panzoom binds the + * down handler to the parent instead of the Panzoom + * element, so that pointer events anywhere on the "canvas" + * moves its children. See issue #472. + * + * **Note**: setting this option to `true` also changes + * where the `cursor` style is applied (i.e. the parent). + */ + canvas?: boolean; + /** Duration of the transition (ms) */ + duration?: number; + /** CSS Easing used for transitions */ + easing?: string; + /** + * Add elements to this array that should be excluded + * from Panzoom handling. + * Ancestors of event targets are also checked. + * e.g. links and buttons that should not propagate the click event. + */ + exclude?: Element[]; + /** + * Add this class to any element within the Panzoom element + * that you want to exclude from Panzoom handling. That + * element's children will also be excluded. + * e.g. links and buttons that should not propagate the click event. + */ + excludeClass?: string; + /** + * `force` should be used sparingly to temporarily + * override and ignore options such as disablePan, + * disableZoom, and panOnlyWhenZoomed. + * This option cannot be passed to the + * Panzoom constructor or setOptions (to avoid + * setting this option globally). + * + * ```js + * // Overrides disablePan and panOnlyWhenZoomed + * panzoom.pan(50, 100, { force: true }) + * // Overrides disableZoom + * panzoom.zoom(1, { force: true }) + * ``` + */ + force?: boolean; + /** + * On the first pointer event, when panning starts, + * the default Panzoom behavior is to call + * `event.preventDefault()` and `event.stopPropagation()` + * on that event. The former is almost certainly a necessity; + * the latter enables Panzoom elements within Panzoom elements. + * + * But there are some cases where the default is + * not the desired behavior. Set this option to override that behavior. + * + * ```js + * // Only call preventDefault() + * Panzoom(elem, { + * handleStartEvent: (event) => { + * event.preventDefault() + * } + * }) + * // Do nothing. + * // This can change dragging behavior on mobile. + * Panzoom(elem, { + * handleStartEvent: () => {} + * }) + * ``` + */ + handleStartEvent?: (event: Event) => void; + /** + * Skip binding the default Panzoom event listeners + */ + noBind?: boolean; + /** + * **Change this at your own risk.** + * The `transform-origin` is the origin from which transforms are applied. + * Default: `'50% 50%'` for HTML and `'0 0'` for SVG. + * The defaults are set because changing the `transform-origin` on + * SVG elements doesn't work in IE. + * + * Changing this should work with many things, but + * it will break focal point zooming, which assumes the + * defaults are set to do the more complicated calculations. + * + * And again, changing this for SVG in IE doesn't work at all. + */ + origin?: string; + /** The overflow CSS value for the parent. Defaults to 'hidden' */ + overflow?: string; + /** + * Set to true to enable panning during pinch zoom. + * Note: this is zooming to a point and panning in the same + * frame. In other words, the zoom has not yet painted and + * therefore the pan is working with old dimensions. + * Essentially, it may be best to avoid using this option + * when using contain. + * + * Related issues: + * https://github.com/timmywil/panzoom/issues/512 + * https://github.com/timmywil/panzoom/issues/606 + */ + pinchAndPan?: boolean; + /** + * Set the transform using the proper prefix. + */ + setTransform?: typeof setTransform; + /** Silence all events */ + silent?: boolean; + /** X Value used to set the beginning transform */ + startX?: number; + /** Y Value used to set the beginning transform */ + startY?: number; + /** Scale used to set the beginning transform */ + startScale?: number; + /** + * This value is used to set touch-action on both the + * Panzoom element and its parent. + * It is needed because that the native scroll on mobile + * interferes with panning and pinch zooming. + * Set this to empty string to re-enable scrolling + * on mobile, but note that both scrolling and panning + * cannot work at the same time. + */ + touchAction?: string; + /** Pass through any options like data */ + [key: string]: any; +} +export interface PanOnlyOptions { + /** + * Contain the panzoom element either + * inside or outside the parent. + * Inside: The panzoom element is smaller + * than its parent and cannot be panned + * to the outside. + * Outside: The panzoom element is larger + * than its parent and cannot be panned + * to the inside. In other words, no + * empty space around the element will be shown. + * + * **Note**: the containment pan adjustment is not affected by the `disablePan` option. + */ + contain?: 'inside' | 'outside'; + /** The cursor style to set on the panzoom element */ + cursor?: string; + /** + * Disable panning functionality. + * Note: disablePan does not affect focal point zooming or the contain option. + * The element will still pan accordingly. + */ + disablePan?: boolean; + /** Pan only on the Y axis */ + disableXAxis?: boolean; + /** Pan only on the X axis */ + disableYAxis?: boolean; + /** When passing x and y values to .pan(), treat the values as relative to their current values */ + relative?: boolean; + /** Disable panning while the scale is equal to the starting value */ + panOnlyWhenZoomed?: boolean; + /** + * Round x and y values to whole numbers. + * This can help prevent images and text from looking blurry, + * but the higher the scale, the more it becomes + * necessary to use fractional pixels. + * Use your own judgment on how much to limit + * zooming in when using this option. + */ + roundPixels?: boolean; +} +export interface ZoomOnlyOptions { + /** Disable zooming functionality */ + disableZoom?: boolean; + /** + * Zoom to the given point on the panzoom element. + * This point is expected to be relative to + * the panzoom element's dimensions and is unrelated + * to the parent dimensions. + */ + focal?: { + x: number; + y: number; + }; + /** The minimum scale when zooming */ + minScale?: number; + /** The maximum scale when zooming */ + maxScale?: number; + /** The step affects zoom calculation when zooming with a mouse wheel, when pinch zooming, or when using zoomIn/zoomOut */ + step?: number; +} +export declare type PanOptions = MiscOptions & PanOnlyOptions; +export declare type ZoomOptions = MiscOptions & ZoomOnlyOptions; +export declare type PanzoomOptions = PanOptions & ZoomOptions & MiscOptions; +export interface CurrentValues { + x: number; + y: number; + scale: number; + isSVG?: boolean; +} +export interface PanzoomObject { + /** + * Bind the default down, move, and up event listeners to the Panzoom element. + * This does not normally need to be called. + * It gets called by default when creating a new Panzoom object, + * but can be skipped with the `noBind` option. + * + * ```js + * const panzoom = Panzoom(elem, { noBind: true }) + * // ... + * panzoom.bind() + * ``` + */ + bind: () => void; + /** Remove all event listeners bound to the the Panzoom element */ + destroy: () => void; + /** + * This object exposes the event names used by Panzoom, + * depending on the current browser's support for + * Pointer or Touch events. + */ + eventNames: { + down: string; + move: string; + up: string; + }; + /** Get the current x/y translation */ + getPan: () => { + x: number; + y: number; + }; + /** Get the current scale */ + getScale: () => number; + /** Returns a _copy_ of the current options object */ + getOptions: () => PanzoomOptions; + /** + * handleDown, handleMove, and handleUp + * are the exact event handlers that Panzoom + * binds to pointer events. They are exposed + * in case you prefer to bind your own events + * or extend them. + * Note that move and up are bound to the document, + * not the Panzoom element. Only the down event + * is bound to the Panzoom element. + * To avoid double-binding, also set noBind to true. + * + * ```js + * const panzoom = Panzoom(elem, { noBind: true }) + * elem.addEventListener('pointerdown', (event) => { + * console.log(event) + * panzoom.handleDown(event) + * }) + * document.addEventListener('pointermove', panzoom.handleMove) + * document.addEventListener('pointerup', panzoom.handleUp) + * ``` + */ + handleDown: (event: PointerEvent) => void; + handleMove: (event: PointerEvent) => void; + handleUp: (event: PointerEvent) => void; + /** + * Pan the Panzoom element to the given x and y coordinates + * + * ```js + * // Translates the element to 50px, 100px + * panzoom.pan(50, 100) + * // Pans the element right 10px and down 10px from its current position + * panzoom.pan(10, 10, { relative: true }) + * ``` + */ + pan: (x: number | string, y: number | string, panOptions?: PanOptions) => CurrentValues; + /** + * Reset the pan and zoom to startX, startY, and startScale. + * Animates by default, ignoring the global option. + * Pass `{ animate: false }` to override. + * Reset ignores the `disablePan`, `disableZoom`, and `panOnlyWhenZoomed` options. + * Pass `{ force: false }` to override. + * + * ```js + * panzoom.reset() + * panzoom.reset({ animate: false }) + * ``` + */ + reset: (resetOptions?: PanzoomOptions) => CurrentValues; + /** + * Reset the styles set on the Panzoom element + * and its parent (such as overflow, cursor, etc.) + * + * ```js + * panzoom.resetStyle() + * ``` + */ + resetStyle: () => void; + /** + * Change any number of options on a Panzoom instance. + * Setting some options will have side-effects. + * For instance, changing the cursor option + * will also set the cursor style. + * + * ```js + * const panzoom = Panzoom(elem, { cursor: 'move' }) + * // ... + * panzoom.setOptions({ cursor: 'default' }) + * ``` + */ + setOptions: (options?: PanzoomOptions) => void; + /** A convenience method for setting prefixed styles on the Panzoom element */ + setStyle: (name: string, value: string) => void; + /** + * Zoom the Panzoom element to the given scale + * + * ```js + * panzoom.zoom(2.2) + * panzoom.zoom(2.2, { animate: true }) + * ``` + */ + zoom: (scale: number, zoomOptions?: ZoomOptions) => CurrentValues; + /** + * Zoom in using the predetermined increment set in options. + * Animates by default, ignoring the global option. + * Pass `{ animate: false }` to override. + * + * ```js + * panzoom.zoomIn() + * panzoom.zoomIn({ animate: false }) + * ``` + */ + zoomIn: (zoomOptions?: ZoomOptions) => CurrentValues; + /** + * Zoom out using the predetermined increment set in options. + * Animates by default, ignoring the global option. + * Pass `{ animate: false }` to override. + * + * ```js + * panzoom.zoomOut() + * panzoom.zoomOut({ animate: false }) + * ``` + */ + zoomOut: (zoomOptions?: ZoomOptions) => CurrentValues; + /** + * Zoom the Panzoom element to a focal point using + * the given pointer/touch/mouse event or constructed point. + * The clientX/clientY values should be calculated + * the same way as a `pointermove` event on the Panzoom element's parent. + * + * ```js + * panzoom.zoomToPoint(1.2, pointerEvent) + * ``` + */ + zoomToPoint: (scale: number, point: { + clientX: number; + clientY: number; + }, zoomOptions?: ZoomOptions) => CurrentValues; + /** + * Zoom the Panzoom element to a focal point using the given WheelEvent + * + * This is a convenience function that may not handle all use cases. + * Other cases should handroll solutions using the `zoomToPoint` + * method or the `zoom` method's focal option. + * + * **Notes**: + * + * - the focal point zooming pan adjustment is + * not affected by the `disablePan` option. + * - animate should not be used when zooming with the wheel, + * and is therefore always disabled. + * + * ```js + * // Bind to mousewheel + * elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel) + * // Bind to shift+mousewheel + * elem.parentElement.addEventListener('wheel', function(event) { + * if (!event.shiftKey) return + * // Panzoom will automatically use `deltaX` here instead + * // of `deltaY`. On a mac, the shift modifier usually + * // translates to horizontal scrolling, but Panzoom assumes + * // the desired behavior is zooming. + * panzoom.zoomWithWheel(event) + * }) + * ``` + */ + zoomWithWheel: (event: WheelEvent, zoomOptions?: ZoomOptions) => CurrentValues; +} diff --git a/package.json b/package.json index 878bdde5..a4968e79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@panzoom/panzoom", - "version": "4.5.0", + "version": "4.5.1", "description": "Pan and zoom elements anywhere using native transformations", "main": "dist/panzoom.js", "module": "dist/panzoom.es.js",