vendor: 51,581 bytes, lines 1-1818
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;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ 2'use strict'; 3/** 4 * Allows to drag and zoom svg elements 5 */ 6var wheel = require('wheel'); 7var animate = require('amator'); 8var eventify = require('ngraph.events'); 9var kinetic = require('./lib/kinetic.js'); 10var createTextSelectionInterceptor = require('./lib/makeTextSelectionInterceptor.js'); 11var domTextSelectionInterceptor = createTextSelectionInterceptor(); 12var fakeTextSelectorInterceptor = createTextSelectionInterceptor(true); 13var Transform = require('./lib/transform.js'); 14var makeSvgController = require('./lib/makeSvgController.js'); 15var makeDomController = require('./lib/makeDomController.js'); 16 17var defaultZoomSpeed = 1; 18var defaultDoubleTapZoomSpeed = 1.75; 19var doubleTapSpeedInMS = 300; 20var clickEventTimeInMS = 200; 21 22module.exports = createPanZoom; 23 24/** 25 * Creates a new instance of panzoom, so that an object can be panned and zoomed 26 * 27 * @param {DOMElement} domElement where panzoom should be attached. 28 * @param {Object} options that configure behavior. 29 */ 30function createPanZoom(domElement, options) { 31 options = options || {}; 32 33 var panController = options.controller; 34 35 if (!panController) { 36 if (makeSvgController.canAttach(domElement)) { 37 panController = makeSvgController(domElement, options); 38 } else if (makeDomController.canAttach(domElement)) { 39 panController = makeDomController(domElement, options); 40 } 41 } 42 43 if (!panController) { 44 throw new Error( 45 'Cannot create panzoom for the current type of dom element' 46 ); 47 } 48 var owner = panController.getOwner(); 49 // just to avoid GC pressure, every time we do intermediate transform 50 // we return this object. For internal use only. Never give it back to the consumer of this library 51 var storedCTMResult = { x: 0, y: 0 }; 52 53 var isDirty = false; 54 var transform = new Transform(); 55 56 if (panController.initTransform) { 57 panController.initTransform(transform); 58 } 59 60 var filterKey = typeof options.filterKey === 'function' ? options.filterKey : noop; 61 // TODO: likely need to unite pinchSpeed with zoomSpeed 62 var pinchSpeed = typeof options.pinchSpeed === 'number' ? options.pinchSpeed : 1; 63 var bounds = options.bounds; 64 var maxZoom = typeof options.maxZoom === 'number' ? options.maxZoom : Number.POSITIVE_INFINITY; 65 var minZoom = typeof options.minZoom === 'number' ? options.minZoom : 0; 66 67 var boundsPadding = typeof options.boundsPadding === 'number' ? options.boundsPadding : 0.05; 68 var zoomDoubleClickSpeed = typeof options.zoomDoubleClickSpeed === 'number' ? options.zoomDoubleClickSpeed : defaultDoubleTapZoomSpeed; 69 var beforeWheel = options.beforeWheel || noop; 70 var beforeMouseDown = options.beforeMouseDown || noop; 71 var speed = typeof options.zoomSpeed === 'number' ? options.zoomSpeed : defaultZoomSpeed; 72 var transformOrigin = parseTransformOrigin(options.transformOrigin); 73 var textSelection = options.enableTextSelection ? fakeTextSelectorInterceptor : domTextSelectionInterceptor; 74 75 validateBounds(bounds); 76 77 if (options.autocenter) { 78 autocenter(); 79 } 80 81 var frameAnimation; 82 var lastTouchEndTime = 0; 83 var lastTouchStartTime = 0; 84 var pendingClickEventTimeout = 0; 85 var lastMouseDownedEvent = null; 86 var lastMouseDownTime = new Date(); 87 var lastSingleFingerOffset; 88 var touchInProgress = false; 89 90 // We only need to fire panstart when actual move happens 91 var panstartFired = false; 92 93 // cache mouse coordinates here 94 var mouseX; 95 var mouseY; 96 97 // Where the first click has happened, so that we can differentiate 98 // between pan and click 99 var clickX; 100 var clickY; 101 102 var pinchZoomLength; 103 104 var smoothScroll; 105 if ('smoothScroll' in options && !options.smoothScroll) { 106 // If user explicitly asked us not to use smooth scrolling, we obey 107 smoothScroll = rigidScroll(); 108 } else { 109 // otherwise we use forward smoothScroll settings to kinetic API 110 // which makes scroll smoothing. 111 smoothScroll = kinetic(getPoint, scroll, options.smoothScroll); 112 } 113 114 var moveByAnimation; 115 var zoomToAnimation; 116 117 var multiTouch; 118 var paused = false; 119 120 listenForEvents(); 121 122 var api = { 123 dispose: dispose, 124 moveBy: internalMoveBy, 125 moveTo: moveTo, 126 smoothMoveTo: smoothMoveTo, 127 centerOn: centerOn, 128 zoomTo: publicZoomTo, 129 zoomAbs: zoomAbs, 130 smoothZoom: smoothZoom, 131 smoothZoomAbs: smoothZoomAbs, 132 showRectangle: showRectangle, 133 134 pause: pause, 135 resume: resume, 136 isPaused: isPaused, 137 138 getTransform: getTransformModel, 139 140 getMinZoom: getMinZoom, 141 setMinZoom: setMinZoom, 142 143 getMaxZoom: getMaxZoom, 144 setMaxZoom: setMaxZoom, 145 146 getTransformOrigin: getTransformOrigin, 147 setTransformOrigin: setTransformOrigin, 148 149 getZoomSpeed: getZoomSpeed, 150 setZoomSpeed: setZoomSpeed 151 }; 152 153 eventify(api); 154 155 var initialX = typeof options.initialX === 'number' ? options.initialX : transform.x; 156 var initialY = typeof options.initialY === 'number' ? options.initialY : transform.y; 157 var initialZoom = typeof options.initialZoom === 'number' ? options.initialZoom : transform.scale; 158 159 if(initialX != transform.x || initialY != transform.y || initialZoom != transform.scale){ 160 zoomAbs(initialX, initialY, initialZoom); 161 } 162 163 return api; 164 165 function pause() { 166 releaseEvents(); 167 paused = true; 168 } 169 170 function resume() { 171 if (paused) { 172 listenForEvents(); 173 paused = false; 174 } 175 } 176 177 function isPaused() { 178 return paused; 179 } 180 181 function showRectangle(rect) { 182 // TODO: this duplicates autocenter. I think autocenter should go. 183 var clientRect = owner.getBoundingClientRect(); 184 var size = transformToScreen(clientRect.width, clientRect.height); 185 186 var rectWidth = rect.right - rect.left; 187 var rectHeight = rect.bottom - rect.top; 188 if (!Number.isFinite(rectWidth) || !Number.isFinite(rectHeight)) { 189 throw new Error('Invalid rectangle'); 190 } 191 192 var dw = size.x / rectWidth; 193 var dh = size.y / rectHeight; 194 var scale = Math.min(dw, dh); 195 transform.x = -(rect.left + rectWidth / 2) * scale + size.x / 2; 196 transform.y = -(rect.top + rectHeight / 2) * scale + size.y / 2; 197 transform.scale = scale; 198 } 199 200 function transformToScreen(x, y) { 201 if (panController.getScreenCTM) { 202 var parentCTM = panController.getScreenCTM(); 203 var parentScaleX = parentCTM.a; 204 var parentScaleY = parentCTM.d; 205 var parentOffsetX = parentCTM.e; 206 var parentOffsetY = parentCTM.f; 207 storedCTMResult.x = x * parentScaleX - parentOffsetX; 208 storedCTMResult.y = y * parentScaleY - parentOffsetY; 209 } else { 210 storedCTMResult.x = x; 211 storedCTMResult.y = y; 212 } 213 214 return storedCTMResult; 215 } 216 217 function autocenter() { 218 var w; // width of the parent 219 var h; // height of the parent 220 var left = 0; 221 var top = 0; 222 var sceneBoundingBox = getBoundingBox(); 223 if (sceneBoundingBox) { 224 // If we have bounding box - use it. 225 left = sceneBoundingBox.left; 226 top = sceneBoundingBox.top; 227 w = sceneBoundingBox.right - sceneBoundingBox.left; 228 h = sceneBoundingBox.bottom - sceneBoundingBox.top; 229 } else { 230 // otherwise just use whatever space we have 231 var ownerRect = owner.getBoundingClientRect(); 232 w = ownerRect.width; 233 h = ownerRect.height; 234 } 235 var bbox = panController.getBBox(); 236 if (bbox.width === 0 || bbox.height === 0) { 237 // we probably do not have any elements in the SVG 238 // just bail out; 239 return; 240 } 241 var dh = h / bbox.height; 242 var dw = w / bbox.width; 243 var scale = Math.min(dw, dh); 244 transform.x = -(bbox.left + bbox.width / 2) * scale + w / 2 + left; 245 transform.y = -(bbox.top + bbox.height / 2) * scale + h / 2 + top; 246 transform.scale = scale; 247 } 248 249 function getTransformModel() { 250 // TODO: should this be read only? 251 return transform; 252 } 253 254 function getMinZoom() { 255 return minZoom; 256 } 257 258 function setMinZoom(newMinZoom) { 259 minZoom = newMinZoom; 260 } 261 262 function getMaxZoom() { 263 return maxZoom; 264 } 265 266 function setMaxZoom(newMaxZoom) { 267 maxZoom = newMaxZoom; 268 } 269 270 function getTransformOrigin() { 271 return transformOrigin; 272 } 273 274 function setTransformOrigin(newTransformOrigin) { 275 transformOrigin = parseTransformOrigin(newTransformOrigin); 276 } 277 278 function getZoomSpeed() { 279 return speed; 280 } 281 282 function setZoomSpeed(newSpeed) { 283 if (!Number.isFinite(newSpeed)) { 284 throw new Error('Zoom speed should be a number'); 285 } 286 speed = newSpeed; 287 } 288 289 function getPoint() { 290 return { 291 x: transform.x, 292 y: transform.y 293 }; 294 } 295 296 function moveTo(x, y) { 297 transform.x = x; 298 transform.y = y; 299 300 keepTransformInsideBounds(); 301 302 triggerEvent('pan'); 303 makeDirty(); 304 } 305 306 function moveBy(dx, dy) { 307 moveTo(transform.x + dx, transform.y + dy); 308 } 309 310 function keepTransformInsideBounds() { 311 var boundingBox = getBoundingBox(); 312 if (!boundingBox) return; 313 314 var adjusted = false; 315 var clientRect = getClientRect(); 316 317 var diff = boundingBox.left - clientRect.right; 318 if (diff > 0) { 319 transform.x += diff; 320 adjusted = true; 321 } 322 // check the other side: 323 diff = boundingBox.right - clientRect.left; 324 if (diff < 0) { 325 transform.x += diff; 326 adjusted = true; 327 } 328 329 // y axis: 330 diff = boundingBox.top - clientRect.bottom; 331 if (diff > 0) { 332 // we adjust transform, so that it matches exactly our bounding box: 333 // transform.y = boundingBox.top - (boundingBox.height + boundingBox.y) * transform.scale => 334 // transform.y = boundingBox.top - (clientRect.bottom - transform.y) => 335 // transform.y = diff + transform.y => 336 transform.y += diff; 337 adjusted = true; 338 } 339 340 diff = boundingBox.bottom - clientRect.top; 341 if (diff < 0) { 342 transform.y += diff; 343 adjusted = true; 344 } 345 return adjusted; 346 } 347 348 /** 349 * Returns bounding box that should be used to restrict scene movement. 350 */ 351 function getBoundingBox() { 352 if (!bounds) return; // client does not want to restrict movement 353 354 if (typeof bounds === 'boolean') { 355 // for boolean type we use parent container bounds 356 var ownerRect = owner.getBoundingClientRect(); 357 var sceneWidth = ownerRect.width; 358 var sceneHeight = ownerRect.height; 359 360 return { 361 left: sceneWidth * boundsPadding, 362 top: sceneHeight * boundsPadding, 363 right: sceneWidth * (1 - boundsPadding), 364 bottom: sceneHeight * (1 - boundsPadding) 365 }; 366 } 367 368 return bounds; 369 } 370 371 function getClientRect() { 372 var bbox = panController.getBBox(); 373 var leftTop = client(bbox.left, bbox.top); 374 375 return { 376 left: leftTop.x, 377 top: leftTop.y, 378 right: bbox.width * transform.scale + leftTop.x, 379 bottom: bbox.height * transform.scale + leftTop.y 380 }; 381 } 382 383 function client(x, y) { 384 return { 385 x: x * transform.scale + transform.x, 386 y: y * transform.scale + transform.y 387 }; 388 } 389 390 function makeDirty() { 391 isDirty = true; 392 frameAnimation = window.requestAnimationFrame(frame); 393 } 394 395 function zoomByRatio(clientX, clientY, ratio) { 396 if (isNaN(clientX) || isNaN(clientY) || isNaN(ratio)) { 397 throw new Error('zoom requires valid numbers'); 398 } 399 400 var newScale = transform.scale * ratio; 401 402 if (newScale < minZoom) { 403 if (transform.scale === minZoom) return; 404 405 ratio = minZoom / transform.scale; 406 } 407 if (newScale > maxZoom) { 408 if (transform.scale === maxZoom) return; 409 410 ratio = maxZoom / transform.scale; 411 } 412 413 var size = transformToScreen(clientX, clientY); 414 415 transform.x = size.x - ratio * (size.x - transform.x); 416 transform.y = size.y - ratio * (size.y - transform.y); 417 418 // TODO: https://github.com/anvaka/panzoom/issues/112 419 if (bounds && boundsPadding === 1 && minZoom === 1) { 420 transform.scale *= ratio; 421 keepTransformInsideBounds(); 422 } else { 423 var transformAdjusted = keepTransformInsideBounds(); 424 if (!transformAdjusted) transform.scale *= ratio; 425 } 426 427 triggerEvent('zoom'); 428 429 makeDirty(); 430 } 431 432 function zoomAbs(clientX, clientY, zoomLevel) { 433 var ratio = zoomLevel / transform.scale; 434 zoomByRatio(clientX, clientY, ratio); 435 } 436 437 function centerOn(ui) { 438 var parent = ui.ownerSVGElement; 439 if (!parent) 440 throw new Error('ui element is required to be within the scene'); 441 442 // TODO: should i use controller's screen CTM? 443 var clientRect = ui.getBoundingClientRect(); 444 var cx = clientRect.left + clientRect.width / 2; 445 var cy = clientRect.top + clientRect.height / 2; 446 447 var container = parent.getBoundingClientRect(); 448 var dx = container.width / 2 - cx; 449 var dy = container.height / 2 - cy; 450 451 internalMoveBy(dx, dy, true); 452 } 453 454 function smoothMoveTo(x, y){ 455 internalMoveBy(x - transform.x, y - transform.y, true); 456 } 457 458 function internalMoveBy(dx, dy, smooth) { 459 if (!smooth) { 460 return moveBy(dx, dy); 461 } 462 463 if (moveByAnimation) moveByAnimation.cancel(); 464 465 var from = { x: 0, y: 0 }; 466 var to = { x: dx, y: dy }; 467 var lastX = 0; 468 var lastY = 0; 469 470 moveByAnimation = animate(from, to, { 471 step: function (v) { 472 moveBy(v.x - lastX, v.y - lastY); 473 474 lastX = v.x; 475 lastY = v.y; 476 } 477 }); 478 } 479 480 function scroll(x, y) { 481 cancelZoomAnimation(); 482 moveTo(x, y); 483 } 484 485 function dispose() { 486 releaseEvents(); 487 } 488 489 function listenForEvents() { 490 owner.addEventListener('mousedown', onMouseDown, { passive: false }); 491 owner.addEventListener('dblclick', onDoubleClick, { passive: false }); 492 owner.addEventListener('touchstart', onTouch, { passive: false }); 493 owner.addEventListener('keydown', onKeyDown, { passive: false }); 494 495 // Need to listen on the owner container, so that we are not limited 496 // by the size of the scrollable domElement 497 wheel.addWheelListener(owner, onMouseWheel, { passive: false }); 498 499 makeDirty(); 500 } 501 502 function releaseEvents() { 503 wheel.removeWheelListener(owner, onMouseWheel); 504 owner.removeEventListener('mousedown', onMouseDown); 505 owner.removeEventListener('keydown', onKeyDown); 506 owner.removeEventListener('dblclick', onDoubleClick); 507 owner.removeEventListener('touchstart', onTouch); 508 509 if (frameAnimation) { 510 window.cancelAnimationFrame(frameAnimation); 511 frameAnimation = 0; 512 } 513 514 smoothScroll.cancel(); 515 516 releaseDocumentMouse(); 517 releaseTouches(); 518 textSelection.release(); 519 520 triggerPanEnd(); 521 } 522 523 function frame() { 524 if (isDirty) applyTransform(); 525 } 526 527 function applyTransform() { 528 isDirty = false; 529 530 // TODO: Should I allow to cancel this? 531 panController.applyTransform(transform); 532 533 triggerEvent('transform'); 534 frameAnimation = 0; 535 } 536 537 function onKeyDown(e) { 538 var x = 0, 539 y = 0, 540 z = 0; 541 if (e.keyCode === 38) { 542 y = 1; // up 543 } else if (e.keyCode === 40) { 544 y = -1; // down 545 } else if (e.keyCode === 37) { 546 x = 1; // left 547 } else if (e.keyCode === 39) { 548 x = -1; // right 549 } else if (e.keyCode === 189 || e.keyCode === 109) { 550 // DASH or SUBTRACT 551 z = 1; // `-` - zoom out 552 } else if (e.keyCode === 187 || e.keyCode === 107) { 553 // EQUAL SIGN or ADD 554 z = -1; // `=` - zoom in (equal sign on US layout is under `+`) 555 } 556 557 if (filterKey(e, x, y, z)) { 558 // They don't want us to handle the key: https://github.com/anvaka/panzoom/issues/45 559 return; 560 } 561 562 if (x || y) { 563 e.preventDefault(); 564 e.stopPropagation(); 565 566 var clientRect = owner.getBoundingClientRect(); 567 // movement speed should be the same in both X and Y direction: 568 var offset = Math.min(clientRect.width, clientRect.height); 569 var moveSpeedRatio = 0.05; 570 var dx = offset * moveSpeedRatio * x; 571 var dy = offset * moveSpeedRatio * y; 572 573 // TODO: currently we do not animate this. It could be better to have animation 574 internalMoveBy(dx, dy); 575 } 576 577 if (z) { 578 var scaleMultiplier = getScaleMultiplier(z * 100); 579 var offset = transformOrigin ? getTransformOriginOffset() : midPoint(); 580 publicZoomTo(offset.x, offset.y, scaleMultiplier); 581 } 582 } 583 584 function midPoint() { 585 var ownerRect = owner.getBoundingClientRect(); 586 return { 587 x: ownerRect.width / 2, 588 y: ownerRect.height / 2 589 }; 590 } 591 592 function onTouch(e) { 593 // let them override the touch behavior 594 beforeTouch(e); 595 clearPendingClickEventTimeout(); 596 597 if (e.touches.length === 1) { 598 return handleSingleFingerTouch(e, e.touches[0]); 599 } else if (e.touches.length === 2) { 600 // handleTouchMove() will care about pinch zoom. 601 pinchZoomLength = getPinchZoomLength(e.touches[0], e.touches[1]); 602 multiTouch = true; 603 startTouchListenerIfNeeded(); 604 } 605 } 606 607 function beforeTouch(e) { 608 // TODO: Need to unify this filtering names. E.g. use `beforeTouch` 609 if (options.onTouch && !options.onTouch(e)) { 610 // if they return `false` from onTouch, we don't want to stop 611 // events propagation. Fixes https://github.com/anvaka/panzoom/issues/12 612 return; 613 } 614 615 e.stopPropagation(); 616 e.preventDefault(); 617 } 618 619 function beforeDoubleClick(e) { 620 clearPendingClickEventTimeout(); 621 622 // TODO: Need to unify this filtering names. E.g. use `beforeDoubleClick`` 623 if (options.onDoubleClick && !options.onDoubleClick(e)) { 624 // if they return `false` from onTouch, we don't want to stop 625 // events propagation. Fixes https://github.com/anvaka/panzoom/issues/46 626 return; 627 } 628 629 e.preventDefault(); 630 e.stopPropagation(); 631 } 632 633 function handleSingleFingerTouch(e) { 634 lastTouchStartTime = new Date(); 635 var touch = e.touches[0]; 636 var offset = getOffsetXY(touch); 637 lastSingleFingerOffset = offset; 638 var point = transformToScreen(offset.x, offset.y); 639 mouseX = point.x; 640 mouseY = point.y; 641 clickX = mouseX; 642 clickY = mouseY; 643 644 smoothScroll.cancel(); 645 startTouchListenerIfNeeded(); 646 } 647 648 function startTouchListenerIfNeeded() { 649 if (touchInProgress) { 650 // no need to do anything, as we already listen to events; 651 return; 652 } 653 654 touchInProgress = true; 655 document.addEventListener('touchmove', handleTouchMove); 656 document.addEventListener('touchend', handleTouchEnd); 657 document.addEventListener('touchcancel', handleTouchEnd); 658 } 659 660 function handleTouchMove(e) { 661 if (e.touches.length === 1) { 662 e.stopPropagation(); 663 var touch = e.touches[0]; 664 665 var offset = getOffsetXY(touch); 666 var point = transformToScreen(offset.x, offset.y); 667 668 var dx = point.x - mouseX; 669 var dy = point.y - mouseY; 670 671 if (dx !== 0 && dy !== 0) { 672 triggerPanStart(); 673 } 674 mouseX = point.x; 675 mouseY = point.y; 676 internalMoveBy(dx, dy); 677 } else if (e.touches.length === 2) { 678 // it's a zoom, let's find direction 679 multiTouch = true; 680 var t1 = e.touches[0]; 681 var t2 = e.touches[1]; 682 var currentPinchLength = getPinchZoomLength(t1, t2); 683 684 // since the zoom speed is always based on distance from 1, we need to apply 685 // pinch speed only on that distance from 1: 686 var scaleMultiplier = 687 1 + (currentPinchLength / pinchZoomLength - 1) * pinchSpeed; 688 689 var firstTouchPoint = getOffsetXY(t1); 690 var secondTouchPoint = getOffsetXY(t2); 691 mouseX = (firstTouchPoint.x + secondTouchPoint.x) / 2; 692 mouseY = (firstTouchPoint.y + secondTouchPoint.y) / 2; 693 if (transformOrigin) { 694 var offset = getTransformOriginOffset(); 695 mouseX = offset.x; 696 mouseY = offset.y; 697 } 698 699 publicZoomTo(mouseX, mouseY, scaleMultiplier); 700 701 pinchZoomLength = currentPinchLength; 702 e.stopPropagation(); 703 e.preventDefault(); 704 } 705 } 706 707 function clearPendingClickEventTimeout() { 708 if (pendingClickEventTimeout) { 709 clearTimeout(pendingClickEventTimeout); 710 pendingClickEventTimeout = 0; 711 } 712 } 713 714 function handlePotentialClickEvent(e) { 715 // we could still be in the double tap mode, let's wait until double tap expires, 716 // and then notify: 717 if (!options.onClick) return; 718 clearPendingClickEventTimeout(); 719 var dx = mouseX - clickX; 720 var dy = mouseY - clickY; 721 var l = Math.sqrt(dx * dx + dy * dy); 722 if (l > 5) return; // probably they are panning, ignore it 723 724 pendingClickEventTimeout = setTimeout(function() { 725 pendingClickEventTimeout = 0; 726 options.onClick(e); 727 }, doubleTapSpeedInMS); 728 } 729 730 function handleTouchEnd(e) { 731 clearPendingClickEventTimeout(); 732 if (e.touches.length > 0) { 733 var offset = getOffsetXY(e.touches[0]); 734 var point = transformToScreen(offset.x, offset.y); 735 mouseX = point.x; 736 mouseY = point.y; 737 } else { 738 var now = new Date(); 739 if (now - lastTouchEndTime < doubleTapSpeedInMS) { 740 // They did a double tap here 741 if (transformOrigin) { 742 var offset = getTransformOriginOffset(); 743 smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed); 744 } else { 745 // We want untransformed x/y here. 746 smoothZoom(lastSingleFingerOffset.x, lastSingleFingerOffset.y, zoomDoubleClickSpeed); 747 } 748 } else if (now - lastTouchStartTime < clickEventTimeInMS) { 749 handlePotentialClickEvent(e); 750 } 751 752 lastTouchEndTime = now; 753 754 triggerPanEnd(); 755 releaseTouches(); 756 } 757 } 758 759 function getPinchZoomLength(finger1, finger2) { 760 var dx = finger1.clientX - finger2.clientX; 761 var dy = finger1.clientY - finger2.clientY; 762 return Math.sqrt(dx * dx + dy * dy); 763 } 764 765 function onDoubleClick(e) { 766 beforeDoubleClick(e); 767 var offset = getOffsetXY(e); 768 if (transformOrigin) { 769 // TODO: looks like this is duplicated in the file. 770 // Need to refactor 771 offset = getTransformOriginOffset(); 772 } 773 smoothZoom(offset.x, offset.y, zoomDoubleClickSpeed); 774 } 775 776 function onMouseDown(e) { 777 clearPendingClickEventTimeout(); 778 779 // if client does not want to handle this event - just ignore the call 780 if (beforeMouseDown(e)) return; 781 782 lastMouseDownedEvent = e; 783 lastMouseDownTime = new Date(); 784 785 if (touchInProgress) { 786 // modern browsers will fire mousedown for touch events too 787 // we do not want this: touch is handled separately. 788 e.stopPropagation(); 789 return false; 790 } 791 // for IE, left click == 1 792 // for Firefox, left click == 0 793 var isLeftButton = 794 (e.button === 1 && window.event !== null) || e.button === 0; 795 if (!isLeftButton) return; 796 797 smoothScroll.cancel(); 798 799 var offset = getOffsetXY(e); 800 var point = transformToScreen(offset.x, offset.y); 801 clickX = mouseX = point.x; 802 clickY = mouseY = point.y; 803 804 // We need to listen on document itself, since mouse can go outside of the 805 // window, and we will loose it 806 document.addEventListener('mousemove', onMouseMove); 807 document.addEventListener('mouseup', onMouseUp); 808 textSelection.capture(e.target || e.srcElement); 809 810 return false; 811 } 812 813 function onMouseMove(e) { 814 // no need to worry about mouse events when touch is happening 815 if (touchInProgress) return; 816 817 triggerPanStart(); 818 819 var offset = getOffsetXY(e); 820 var point = transformToScreen(offset.x, offset.y); 821 var dx = point.x - mouseX; 822 var dy = point.y - mouseY; 823 824 mouseX = point.x; 825 mouseY = point.y; 826 827 internalMoveBy(dx, dy); 828 } 829 830 function onMouseUp() { 831 var now = new Date(); 832 if (now - lastMouseDownTime < clickEventTimeInMS) handlePotentialClickEvent(lastMouseDownedEvent); 833 textSelection.release(); 834 triggerPanEnd(); 835 releaseDocumentMouse(); 836 } 837 838 function releaseDocumentMouse() { 839 document.removeEventListener('mousemove', onMouseMove); 840 document.removeEventListener('mouseup', onMouseUp); 841 panstartFired = false; 842 } 843 844 function releaseTouches() { 845 document.removeEventListener('touchmove', handleTouchMove); 846 document.removeEventListener('touchend', handleTouchEnd); 847 document.removeEventListener('touchcancel', handleTouchEnd); 848 panstartFired = false; 849 multiTouch = false; 850 touchInProgress = false; 851 } 852 853 function onMouseWheel(e) { 854 // if client does not want to handle this event - just ignore the call 855 if (beforeWheel(e)) return; 856 857 smoothScroll.cancel(); 858 859 var delta = e.deltaY; 860 if (e.deltaMode > 0) delta *= 100; 861 862 var scaleMultiplier = getScaleMultiplier(delta); 863 864 if (scaleMultiplier !== 1) { 865 var offset = transformOrigin 866 ? getTransformOriginOffset() 867 : getOffsetXY(e); 868 publicZoomTo(offset.x, offset.y, scaleMultiplier); 869 e.preventDefault(); 870 } 871 } 872 873 function getOffsetXY(e) { 874 var offsetX, offsetY; 875 // I tried using e.offsetX, but that gives wrong results for svg, when user clicks on a path. 876 var ownerRect = owner.getBoundingClientRect(); 877 offsetX = e.clientX - ownerRect.left; 878 offsetY = e.clientY - ownerRect.top; 879 880 return { x: offsetX, y: offsetY }; 881 } 882 883 function smoothZoom(clientX, clientY, scaleMultiplier) { 884 var fromValue = transform.scale; 885 var from = { scale: fromValue }; 886 var to = { scale: scaleMultiplier * fromValue }; 887 888 smoothScroll.cancel(); 889 cancelZoomAnimation(); 890 891 zoomToAnimation = animate(from, to, { 892 step: function (v) { 893 zoomAbs(clientX, clientY, v.scale); 894 }, 895 done: triggerZoomEnd 896 }); 897 } 898 899 function smoothZoomAbs(clientX, clientY, toScaleValue) { 900 var fromValue = transform.scale; 901 var from = { scale: fromValue }; 902 var to = { scale: toScaleValue }; 903 904 smoothScroll.cancel(); 905 cancelZoomAnimation(); 906 907 zoomToAnimation = animate(from, to, { 908 step: function (v) { 909 zoomAbs(clientX, clientY, v.scale); 910 } 911 }); 912 } 913 914 function getTransformOriginOffset() { 915 var ownerRect = owner.getBoundingClientRect(); 916 return { 917 x: ownerRect.width * transformOrigin.x, 918 y: ownerRect.height * transformOrigin.y 919 }; 920 } 921 922 function publicZoomTo(clientX, clientY, scaleMultiplier) { 923 smoothScroll.cancel(); 924 cancelZoomAnimation(); 925 return zoomByRatio(clientX, clientY, scaleMultiplier); 926 } 927 928 function cancelZoomAnimation() { 929 if (zoomToAnimation) { 930 zoomToAnimation.cancel(); 931 zoomToAnimation = null; 932 } 933 } 934 935 function getScaleMultiplier(delta) { 936 var sign = Math.sign(delta); 937 var deltaAdjustedSpeed = Math.min(0.25, Math.abs(speed * delta / 128)); 938 return 1 - sign * deltaAdjustedSpeed; 939 } 940 941 function triggerPanStart() { 942 if (!panstartFired) { 943 triggerEvent('panstart'); 944 panstartFired = true; 945 smoothScroll.start(); 946 } 947 } 948 949 function triggerPanEnd() { 950 if (panstartFired) { 951 // we should never run smooth scrolling if it was multiTouch (pinch zoom animation): 952 if (!multiTouch) smoothScroll.stop(); 953 triggerEvent('panend'); 954 } 955 } 956 957 function triggerZoomEnd() { 958 triggerEvent('zoomend'); 959 } 960 961 function triggerEvent(name) { 962 api.fire(name, api); 963 } 964} 965 966function parseTransformOrigin(options) { 967 if (!options) return; 968 if (typeof options === 'object') { 969 if (!isNumber(options.x) || !isNumber(options.y)) 970 failTransformOrigin(options); 971 return options; 972 } 973 974 failTransformOrigin(); 975} 976 977function failTransformOrigin(options) { 978 console.error(options); 979 throw new Error( 980 [ 981 'Cannot parse transform origin.', 982 'Some good examples:', 983 ' "center center" can be achieved with {x: 0.5, y: 0.5}', 984 ' "top center" can be achieved with {x: 0.5, y: 0}', 985 ' "bottom right" can be achieved with {x: 1, y: 1}' 986 ].join('\n') 987 ); 988} 989 990function noop() { } 991 992function validateBounds(bounds) { 993 var boundsType = typeof bounds; 994 if (boundsType === 'undefined' || boundsType === 'boolean') return; // this is okay 995 // otherwise need to be more thorough: 996 var validBounds = 997 isNumber(bounds.left) && 998 isNumber(bounds.top) && 999 isNumber(bounds.bottom) && 1000 isNumber(bounds.right); 1001 1002 if (!validBounds) 1003 throw new Error( 1004 'Bounds object is not valid. It can be: ' + 1005 'undefined, boolean (true|false) or an object {left, top, right, bottom}' 1006 ); 1007} 1008 1009function isNumber(x) { 1010 return Number.isFinite(x); 1011} 1012 1013// IE 11 does not support isNaN: 1014function isNaN(value) { 1015 if (Number.isNaN) { 1016 return Number.isNaN(value); 1017 } 1018 1019 return value !== value; 1020} 1021 1022function rigidScroll() { 1023 return { 1024 start: noop, 1025 stop: noop, 1026 cancel: noop 1027 }; 1028} 1029 1030function autoRun() { 1031 if (typeof document === 'undefined') return; 1032 1033 var scripts = document.getElementsByTagName('script'); 1034 if (!scripts) return; 1035 var panzoomScript; 1036 1037 for (var i = 0; i < scripts.length; ++i) { 1038 var x = scripts[i]; 1039 if (x.src && x.src.match(/\bpanzoom(\.min)?\.js/)) { 1040 panzoomScript = x; 1041 break; 1042 } 1043 } 1044 1045 if (!panzoomScript) return; 1046 1047 var query = panzoomScript.getAttribute('query'); 1048 if (!query) return; 1049 1050 var globalName = panzoomScript.getAttribute('name') || 'pz'; 1051 var started = Date.now(); 1052 1053 tryAttach(); 1054 1055 function tryAttach() { 1056 var el = document.querySelector(query); 1057 if (!el) { 1058 var now = Date.now(); 1059 var elapsed = now - started; 1060 if (elapsed < 2000) { 1061 // Let's wait a bit 1062 setTimeout(tryAttach, 100); 1063 return; 1064 } 1065 // If we don't attach within 2 seconds to the target element, consider it a failure 1066 console.error('Cannot find the panzoom element', globalName); 1067 return; 1068 } 1069 var options = collectOptions(panzoomScript); 1070 console.log(options); 1071 window[globalName] = createPanZoom(el, options); 1072 } 1073 1074 function collectOptions(script) { 1075 var attrs = script.attributes; 1076 var options = {}; 1077 for (var j = 0; j < attrs.length; ++j) { 1078 var attr = attrs[j]; 1079 var nameValue = getPanzoomAttributeNameValue(attr); 1080 if (nameValue) { 1081 options[nameValue.name] = nameValue.value; 1082 } 1083 } 1084 1085 return options; 1086 } 1087 1088 function getPanzoomAttributeNameValue(attr) { 1089 if (!attr.name) return; 1090 var isPanZoomAttribute = 1091 attr.name[0] === 'p' && attr.name[1] === 'z' && attr.name[2] === '-'; 1092 1093 if (!isPanZoomAttribute) return; 1094 1095 var name = attr.name.substr(3); 1096 var value = JSON.parse(attr.value); 1097 return { name: name, value: value }; 1098 } 1099} 1100 1101autoRun(); 1102 1103},{"./lib/kinetic.js":2,"./lib/makeDomController.js":3,"./lib/makeSvgController.js":4,"./lib/makeTextSelectionInterceptor.js":5,"./lib/transform.js":6,"amator":7,"ngraph.events":9,"wheel":10}],2:[function(require,module,exports){ 1104/** 1105 * Allows smooth kinetic scrolling of the surface 1106 */ 1107module.exports = kinetic; 1108 1109function kinetic(getPoint, scroll, settings) { 1110 if (typeof settings !== 'object') { 1111 // setting could come as boolean, we should ignore it, and use an object. 1112 settings = {}; 1113 } 1114 1115 var minVelocity = typeof settings.minVelocity === 'number' ? settings.minVelocity : 5; 1116 var amplitude = typeof settings.amplitude === 'number' ? settings.amplitude : 0.25; 1117 var cancelAnimationFrame = typeof settings.cancelAnimationFrame === 'function' ? settings.cancelAnimationFrame : getCancelAnimationFrame(); 1118 var requestAnimationFrame = typeof settings.requestAnimationFrame === 'function' ? settings.requestAnimationFrame : getRequestAnimationFrame(); 1119 1120 var lastPoint; 1121 var timestamp; 1122 var timeConstant = 342; 1123 1124 var ticker; 1125 var vx, targetX, ax; 1126 var vy, targetY, ay; 1127 1128 var raf; 1129 1130 return { 1131 start: start, 1132 stop: stop, 1133 cancel: dispose 1134 }; 1135 1136 function dispose() { 1137 cancelAnimationFrame(ticker); 1138 cancelAnimationFrame(raf); 1139 } 1140 1141 function start() { 1142 lastPoint = getPoint(); 1143 1144 ax = ay = vx = vy = 0; 1145 timestamp = new Date(); 1146 1147 cancelAnimationFrame(ticker); 1148 cancelAnimationFrame(raf); 1149 1150 // we start polling the point position to accumulate velocity 1151 // Once we stop(), we will use accumulated velocity to keep scrolling 1152 // an object. 1153 ticker = requestAnimationFrame(track); 1154 } 1155 1156 function track() { 1157 var now = Date.now(); 1158 var elapsed = now - timestamp; 1159 timestamp = now; 1160 1161 var currentPoint = getPoint(); 1162 1163 var dx = currentPoint.x - lastPoint.x; 1164 var dy = currentPoint.y - lastPoint.y; 1165 1166 lastPoint = currentPoint; 1167 1168 var dt = 1000 / (1 + elapsed); 1169 1170 // moving average 1171 vx = 0.8 * dx * dt + 0.2 * vx; 1172 vy = 0.8 * dy * dt + 0.2 * vy; 1173 1174 ticker = requestAnimationFrame(track); 1175 } 1176 1177 function stop() { 1178 cancelAnimationFrame(ticker); 1179 cancelAnimationFrame(raf); 1180 1181 var currentPoint = getPoint(); 1182 1183 targetX = currentPoint.x; 1184 targetY = currentPoint.y; 1185 timestamp = Date.now(); 1186 1187 if (vx < -minVelocity || vx > minVelocity) { 1188 ax = amplitude * vx; 1189 targetX += ax; 1190 } 1191 1192 if (vy < -minVelocity || vy > minVelocity) { 1193 ay = amplitude * vy; 1194 targetY += ay; 1195 } 1196 1197 raf = requestAnimationFrame(autoScroll); 1198 } 1199 1200 function autoScroll() { 1201 var elapsed = Date.now() - timestamp; 1202 1203 var moving = false; 1204 var dx = 0; 1205 var dy = 0; 1206 1207 if (ax) { 1208 dx = -ax * Math.exp(-elapsed / timeConstant); 1209 1210 if (dx > 0.5 || dx < -0.5) moving = true; 1211 else dx = ax = 0; 1212 } 1213 1214 if (ay) { 1215 dy = -ay * Math.exp(-elapsed / timeConstant); 1216 1217 if (dy > 0.5 || dy < -0.5) moving = true; 1218 else dy = ay = 0; 1219 } 1220 1221 if (moving) { 1222 scroll(targetX + dx, targetY + dy); 1223 raf = requestAnimationFrame(autoScroll); 1224 } 1225 } 1226} 1227 1228function getCancelAnimationFrame() { 1229 if (typeof cancelAnimationFrame === 'function') return cancelAnimationFrame; 1230 return clearTimeout; 1231} 1232 1233function getRequestAnimationFrame() { 1234 if (typeof requestAnimationFrame === 'function') return requestAnimationFrame; 1235 1236 return function (handler) { 1237 return setTimeout(handler, 16); 1238 }; 1239} 1240},{}],3:[function(require,module,exports){ 1241module.exports = makeDomController; 1242 1243module.exports.canAttach = isDomElement; 1244 1245function makeDomController(domElement, options) { 1246 var elementValid = isDomElement(domElement); 1247 if (!elementValid) { 1248 throw new Error('panzoom requires DOM element to be attached to the DOM tree'); 1249 } 1250 1251 var owner = domElement.parentElement; 1252 domElement.scrollTop = 0; 1253 1254 if (!options.disableKeyboardInteraction) { 1255 owner.setAttribute('tabindex', 0); 1256 } 1257 1258 var api = { 1259 getBBox: getBBox, 1260 getOwner: getOwner, 1261 applyTransform: applyTransform, 1262 }; 1263 1264 return api; 1265 1266 function getOwner() { 1267 return owner; 1268 } 1269 1270 function getBBox() { 1271 // TODO: We should probably cache this? 1272 return { 1273 left: 0, 1274 top: 0, 1275 width: domElement.clientWidth, 1276 height: domElement.clientHeight 1277 }; 1278 } 1279 1280 function applyTransform(transform) { 1281 // TODO: Should we cache this? 1282 domElement.style.transformOrigin = '0 0 0'; 1283 domElement.style.transform = 'matrix(' + 1284 transform.scale + ', 0, 0, ' + 1285 transform.scale + ', ' + 1286 transform.x + ', ' + transform.y + ')'; 1287 } 1288} 1289 1290function isDomElement(element) { 1291 return element && element.parentElement && element.style; 1292} 1293 1294},{}],4:[function(require,module,exports){ 1295module.exports = makeSvgController; 1296module.exports.canAttach = isSVGElement; 1297 1298function makeSvgController(svgElement, options) { 1299 if (!isSVGElement(svgElement)) { 1300 throw new Error('svg element is required for svg.panzoom to work'); 1301 } 1302 1303 var owner = svgElement.ownerSVGElement; 1304 if (!owner) { 1305 throw new Error( 1306 'Do not apply panzoom to the root <svg> element. ' + 1307 'Use its child instead (e.g. <g></g>). ' + 1308 'As of March 2016 only FireFox supported transform on the root element'); 1309 } 1310 1311 if (!options.disableKeyboardInteraction) { 1312 owner.setAttribute('tabindex', 0); 1313 } 1314 1315 var api = { 1316 getBBox: getBBox, 1317 getScreenCTM: getScreenCTM, 1318 getOwner: getOwner, 1319 applyTransform: applyTransform, 1320 initTransform: initTransform 1321 }; 1322 1323 return api; 1324 1325 function getOwner() { 1326 return owner; 1327 } 1328 1329 function getBBox() { 1330 var bbox = svgElement.getBBox(); 1331 return { 1332 left: bbox.x, 1333 top: bbox.y, 1334 width: bbox.width, 1335 height: bbox.height, 1336 }; 1337 } 1338 1339 function getScreenCTM() { 1340 var ctm = owner.getCTM(); 1341 if (!ctm) { 1342 // This is likely firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=873106 1343 // The code below is not entirely correct, but still better than nothing 1344 return owner.getScreenCTM(); 1345 } 1346 return ctm; 1347 } 1348 1349 function initTransform(transform) { 1350 var screenCTM = svgElement.getCTM(); 1351 1352 // The above line returns null on Firefox 1353 if (screenCTM === null) { 1354 screenCTM = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix(); 1355 } 1356 1357 transform.x = screenCTM.e; 1358 transform.y = screenCTM.f; 1359 transform.scale = screenCTM.a; 1360 owner.removeAttributeNS(null, 'viewBox'); 1361 } 1362 1363 function applyTransform(transform) { 1364 svgElement.setAttribute('transform', 'matrix(' + 1365 transform.scale + ' 0 0 ' + 1366 transform.scale + ' ' + 1367 transform.x + ' ' + transform.y + ')'); 1368 } 1369} 1370 1371function isSVGElement(element) { 1372 return element && element.ownerSVGElement && element.getCTM; 1373} 1374},{}],5:[function(require,module,exports){ 1375/** 1376 * Disallows selecting text. 1377 */ 1378module.exports = makeTextSelectionInterceptor; 1379 1380function makeTextSelectionInterceptor(useFake) { 1381 if (useFake) { 1382 return { 1383 capture: noop, 1384 release: noop 1385 }; 1386 } 1387 1388 var dragObject; 1389 var prevSelectStart; 1390 var prevDragStart; 1391 var wasCaptured = false; 1392 1393 return { 1394 capture: capture, 1395 release: release 1396 }; 1397 1398 function capture(domObject) { 1399 wasCaptured = true; 1400 prevSelectStart = window.document.onselectstart; 1401 prevDragStart = window.document.ondragstart; 1402 1403 window.document.onselectstart = disabled; 1404 1405 dragObject = domObject; 1406 dragObject.ondragstart = disabled; 1407 } 1408 1409 function release() { 1410 if (!wasCaptured) return; 1411 1412 wasCaptured = false; 1413 window.document.onselectstart = prevSelectStart; 1414 if (dragObject) dragObject.ondragstart = prevDragStart; 1415 } 1416} 1417 1418function disabled(e) { 1419 e.stopPropagation(); 1420 return false; 1421} 1422 1423function noop() {} 1424 1425},{}],6:[function(require,module,exports){ 1426module.exports = Transform; 1427 1428function Transform() { 1429 this.x = 0; 1430 this.y = 0; 1431 this.scale = 1; 1432} 1433 1434},{}],7:[function(require,module,exports){ 1435var BezierEasing = require('bezier-easing') 1436 1437// Predefined set of animations. Similar to CSS easing functions 1438var animations = { 1439 ease: BezierEasing(0.25, 0.1, 0.25, 1), 1440 easeIn: BezierEasing(0.42, 0, 1, 1), 1441 easeOut: BezierEasing(0, 0, 0.58, 1), 1442 easeInOut: BezierEasing(0.42, 0, 0.58, 1), 1443 linear: BezierEasing(0, 0, 1, 1) 1444} 1445 1446 1447module.exports = animate; 1448module.exports.makeAggregateRaf = makeAggregateRaf; 1449module.exports.sharedScheduler = makeAggregateRaf(); 1450 1451 1452function animate(source, target, options) { 1453 var start = Object.create(null) 1454 var diff = Object.create(null) 1455 options = options || {} 1456 // We let clients specify their own easing function 1457 var easing = (typeof options.easing === 'function') ? options.easing : animations[options.easing] 1458 1459 // if nothing is specified, default to ease (similar to CSS animations) 1460 if (!easing) { 1461 if (options.easing) { 1462 console.warn('Unknown easing function in amator: ' + options.easing); 1463 } 1464 easing = animations.ease 1465 } 1466 1467 var step = typeof options.step === 'function' ? options.step : noop 1468 var done = typeof options.done === 'function' ? options.done : noop 1469 1470 var scheduler = getScheduler(options.scheduler) 1471 1472 var keys = Object.keys(target) 1473 keys.forEach(function(key) { 1474 start[key] = source[key] 1475 diff[key] = target[key] - source[key] 1476 }) 1477 1478 var durationInMs = typeof options.duration === 'number' ? options.duration : 400 1479 var durationInFrames = Math.max(1, durationInMs * 0.06) // 0.06 because 60 frames pers 1,000 ms 1480 var previousAnimationId 1481 var frame = 0 1482 1483 previousAnimationId = scheduler.next(loop) 1484 1485 return { 1486 cancel: cancel 1487 } 1488 1489 function cancel() { 1490 scheduler.cancel(previousAnimationId) 1491 previousAnimationId = 0 1492 } 1493 1494 function loop() { 1495 var t = easing(frame/durationInFrames) 1496 frame += 1 1497 setValues(t) 1498 if (frame <= durationInFrames) { 1499 previousAnimationId = scheduler.next(loop) 1500 step(source) 1501 } else { 1502 previousAnimationId = 0 1503 setTimeout(function() { done(source) }, 0) 1504 } 1505 } 1506 1507 function setValues(t) { 1508 keys.forEach(function(key) { 1509 source[key] = diff[key] * t + start[key] 1510 }) 1511 } 1512} 1513 1514function noop() { } 1515 1516function getScheduler(scheduler) { 1517 if (!scheduler) { 1518 var canRaf = typeof window !== 'undefined' && window.requestAnimationFrame 1519 return canRaf ? rafScheduler() : timeoutScheduler() 1520 } 1521 if (typeof scheduler.next !== 'function') throw new Error('Scheduler is supposed to have next(cb) function') 1522 if (typeof scheduler.cancel !== 'function') throw new Error('Scheduler is supposed to have cancel(handle) function') 1523 1524 return scheduler 1525} 1526 1527function rafScheduler() { 1528 return { 1529 next: window.requestAnimationFrame.bind(window), 1530 cancel: window.cancelAnimationFrame.bind(window) 1531 } 1532} 1533 1534function timeoutScheduler() { 1535 return { 1536 next: function(cb) { 1537 return setTimeout(cb, 1000/60) 1538 }, 1539 cancel: function (id) { 1540 return clearTimeout(id) 1541 } 1542 } 1543} 1544 1545function makeAggregateRaf() { 1546 var frontBuffer = new Set(); 1547 var backBuffer = new Set(); 1548 var frameToken = 0; 1549 1550 return { 1551 next: next, 1552 cancel: next, 1553 clearAll: clearAll 1554 } 1555 1556 function clearAll() { 1557 frontBuffer.clear(); 1558 backBuffer.clear(); 1559 cancelAnimationFrame(frameToken); 1560 frameToken = 0; 1561 } 1562 1563 function next(callback) { 1564 backBuffer.add(callback); 1565 renderNextFrame(); 1566 } 1567 1568 function renderNextFrame() { 1569 if (!frameToken) frameToken = requestAnimationFrame(renderFrame); 1570 } 1571 1572 function renderFrame() { 1573 frameToken = 0; 1574 1575 var t = backBuffer; 1576 backBuffer = frontBuffer; 1577 frontBuffer = t; 1578 1579 frontBuffer.forEach(function(callback) { 1580 callback(); 1581 }); 1582 frontBuffer.clear(); 1583 } 1584 1585 function cancel(callback) { 1586 backBuffer.delete(callback); 1587 } 1588} 1589 1590},{"bezier-easing":8}],8:[function(require,module,exports){ 1591/** 1592 * https://github.com/gre/bezier-easing 1593 * BezierEasing - use bezier curve for transition easing function 1594 * by Gaëtan Renaudeau 2014 - 2015 â MIT License 1595 */ 1596 1597// These values are established by empiricism with tests (tradeoff: performance VS precision) 1598var NEWTON_ITERATIONS = 4; 1599var NEWTON_MIN_SLOPE = 0.001; 1600var SUBDIVISION_PRECISION = 0.0000001; 1601var SUBDIVISION_MAX_ITERATIONS = 10; 1602 1603var kSplineTableSize = 11; 1604var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); 1605 1606var float32ArraySupported = typeof Float32Array === 'function'; 1607 1608function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } 1609function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } 1610function C (aA1) { return 3.0 * aA1; } 1611 1612// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. 1613function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } 1614 1615// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. 1616function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } 1617 1618function binarySubdivide (aX, aA, aB, mX1, mX2) { 1619 var currentX, currentT, i = 0; 1620 do { 1621 currentT = aA + (aB - aA) / 2.0; 1622 currentX = calcBezier(currentT, mX1, mX2) - aX; 1623 if (currentX > 0.0) { 1624 aB = currentT; 1625 } else { 1626 aA = currentT; 1627 } 1628 } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); 1629 return currentT; 1630} 1631 1632function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { 1633 for (var i = 0; i < NEWTON_ITERATIONS; ++i) { 1634 var currentSlope = getSlope(aGuessT, mX1, mX2); 1635 if (currentSlope === 0.0) { 1636 return aGuessT; 1637 } 1638 var currentX = calcBezier(aGuessT, mX1, mX2) - aX; 1639 aGuessT -= currentX / currentSlope; 1640 } 1641 return aGuessT; 1642} 1643 1644function LinearEasing (x) { 1645 return x; 1646} 1647 1648module.exports = function bezier (mX1, mY1, mX2, mY2) { 1649 if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { 1650 throw new Error('bezier x values must be in [0, 1] range'); 1651 } 1652 1653 if (mX1 === mY1 && mX2 === mY2) { 1654 return LinearEasing; 1655 } 1656 1657 // Precompute samples table 1658 var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); 1659 for (var i = 0; i < kSplineTableSize; ++i) { 1660 sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); 1661 } 1662 1663 function getTForX (aX) { 1664 var intervalStart = 0.0; 1665 var currentSample = 1; 1666 var lastSample = kSplineTableSize - 1; 1667 1668 for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { 1669 intervalStart += kSampleStepSize; 1670 } 1671 --currentSample; 1672 1673 // Interpolate to provide an initial guess for t 1674 var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); 1675 var guessForT = intervalStart + dist * kSampleStepSize; 1676 1677 var initialSlope = getSlope(guessForT, mX1, mX2); 1678 if (initialSlope >= NEWTON_MIN_SLOPE) { 1679 return newtonRaphsonIterate(aX, guessForT, mX1, mX2); 1680 } else if (initialSlope === 0.0) { 1681 return guessForT; 1682 } else { 1683 return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); 1684 } 1685 } 1686 1687 return function BezierEasing (x) { 1688 // Because JavaScript number are imprecise, we should guarantee the extremes are right. 1689 if (x === 0) { 1690 return 0; 1691 } 1692 if (x === 1) { 1693 return 1; 1694 } 1695 return calcBezier(getTForX(x), mY1, mY2); 1696 }; 1697}; 1698 1699},{}],9:[function(require,module,exports){ 1700module.exports = function eventify(subject) { 1701 validateSubject(subject); 1702 1703 var eventsStorage = createEventsStorage(subject); 1704 subject.on = eventsStorage.on; 1705 subject.off = eventsStorage.off; 1706 subject.fire = eventsStorage.fire; 1707 return subject; 1708}; 1709 1710function createEventsStorage(subject) { 1711 // Store all event listeners to this hash. Key is event name, value is array 1712 // of callback records. 1713 // 1714 // A callback record consists of callback function and its optional context: 1715 // { 'eventName' => [{callback: function, ctx: object}] } 1716 var registeredEvents = Object.create(null); 1717 1718 return { 1719 on: function (eventName, callback, ctx) { 1720 if (typeof callback !== 'function') { 1721 throw new Error('callback is expected to be a function'); 1722 } 1723 var handlers = registeredEvents[eventName]; 1724 if (!handlers) { 1725 handlers = registeredEvents[eventName] = []; 1726 } 1727 handlers.push({callback: callback, ctx: ctx}); 1728 1729 return subject; 1730 }, 1731 1732 off: function (eventName, callback) { 1733 var wantToRemoveAll = (typeof eventName === 'undefined'); 1734 if (wantToRemoveAll) { 1735 // Killing old events storage should be enough in this case: 1736 registeredEvents = Object.create(null); 1737 return subject; 1738 } 1739 1740 if (registeredEvents[eventName]) { 1741 var deleteAllCallbacksForEvent = (typeof callback !== 'function'); 1742 if (deleteAllCallbacksForEvent) { 1743 delete registeredEvents[eventName]; 1744 } else { 1745 var callbacks = registeredEvents[eventName]; 1746 for (var i = 0; i < callbacks.length; ++i) { 1747 if (callbacks[i].callback === callback) { 1748 callbacks.splice(i, 1); 1749 } 1750 } 1751 } 1752 } 1753 1754 return subject; 1755 }, 1756 1757 fire: function (eventName) { 1758 var callbacks = registeredEvents[eventName]; 1759 if (!callbacks) { 1760 return subject; 1761 } 1762 1763 var fireArguments; 1764 if (arguments.length > 1) { 1765 fireArguments = Array.prototype.splice.call(arguments, 1); 1766 } 1767 for(var i = 0; i < callbacks.length; ++i) { 1768 var callbackInfo = callbacks[i]; 1769 callbackInfo.callback.apply(callbackInfo.ctx, fireArguments); 1770 } 1771 1772 return subject; 1773 } 1774 }; 1775} 1776 1777function validateSubject(subject) { 1778 if (!subject) { 1779 throw new Error('Eventify cannot use falsy object as events subject'); 1780 } 1781 var reservedWords = ['on', 'fire', 'off']; 1782 for (var i = 0; i < reservedWords.length; ++i) { 1783 if (subject.hasOwnProperty(reservedWords[i])) { 1784 throw new Error("Subject cannot be eventified, since it already has property '" + reservedWords[i] + "'"); 1785 } 1786 } 1787} 1788 1789},{}],10:[function(require,module,exports){ 1790/** 1791 * This module used to unify mouse wheel behavior between different browsers in 2014 1792 * Now it's just a wrapper around addEventListener('wheel'); 1793 * 1794 * Usage: 1795 * var addWheelListener = require('wheel').addWheelListener; 1796 * var removeWheelListener = require('wheel').removeWheelListener; 1797 * addWheelListener(domElement, function (e) { 1798 * // mouse wheel event 1799 * }); 1800 * removeWheelListener(domElement, function); 1801 */ 1802 1803module.exports = addWheelListener; 1804 1805// But also expose "advanced" api with unsubscribe: 1806module.exports.addWheelListener = addWheelListener; 1807module.exports.removeWheelListener = removeWheelListener; 1808 1809 1810function addWheelListener(element, listener, useCapture) { 1811 element.addEventListener('wheel', listener, useCapture); 1812} 1813 1814function removeWheelListener( element, listener, useCapture ) { 1815 element.removeEventListener('wheel', listener, useCapture); 1816} 1817},{}]},{},[1])(1) 1818});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.