svgmap 2.18.4 → 2.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,2276 @@
1
- const svgPanZoom = require('svg-pan-zoom');
1
+ function getDefaultExportFromCjs (x) {
2
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
3
+ }
4
+
5
+ var uniwheel;
6
+ var hasRequiredUniwheel;
7
+
8
+ function requireUniwheel () {
9
+ if (hasRequiredUniwheel) return uniwheel;
10
+ hasRequiredUniwheel = 1;
11
+ // uniwheel 0.1.2 (customized)
12
+ // A unified cross browser mouse wheel event handler
13
+ // https://github.com/teemualap/uniwheel
14
+
15
+ uniwheel = (function(){
16
+
17
+ //Full details: https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel
18
+
19
+ var prefix = "", _addEventListener, _removeEventListener, support, fns = [];
20
+ var passiveListenerOption = {passive: true};
21
+ var activeListenerOption = {passive: false};
22
+
23
+ // detect event model
24
+ if ( window.addEventListener ) {
25
+ _addEventListener = "addEventListener";
26
+ _removeEventListener = "removeEventListener";
27
+ } else {
28
+ _addEventListener = "attachEvent";
29
+ _removeEventListener = "detachEvent";
30
+ prefix = "on";
31
+ }
32
+
33
+ // detect available wheel event
34
+ support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
35
+ document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
36
+ "DOMMouseScroll"; // let's assume that remaining browsers are older Firefox
37
+
38
+
39
+ function createCallback(element,callback) {
40
+
41
+ var fn = function(originalEvent) {
42
+
43
+ !originalEvent && ( originalEvent = window.event );
44
+
45
+ // create a normalized event object
46
+ var event = {
47
+ // keep a ref to the original event object
48
+ originalEvent: originalEvent,
49
+ target: originalEvent.target || originalEvent.srcElement,
50
+ type: "wheel",
51
+ deltaMode: originalEvent.type == "MozMousePixelScroll" ? 0 : 1,
52
+ deltaX: 0,
53
+ delatZ: 0,
54
+ preventDefault: function() {
55
+ originalEvent.preventDefault ?
56
+ originalEvent.preventDefault() :
57
+ originalEvent.returnValue = false;
58
+ }
59
+ };
60
+
61
+ // calculate deltaY (and deltaX) according to the event
62
+ if ( support == "mousewheel" ) {
63
+ event.deltaY = -1/40 * originalEvent.wheelDelta;
64
+ // Webkit also support wheelDeltaX
65
+ originalEvent.wheelDeltaX && ( event.deltaX = -1/40 * originalEvent.wheelDeltaX );
66
+ } else {
67
+ event.deltaY = originalEvent.detail;
68
+ }
69
+
70
+ // it's time to fire the callback
71
+ return callback( event );
72
+
73
+ };
74
+
75
+ fns.push({
76
+ element: element,
77
+ fn: fn,
78
+ });
79
+
80
+ return fn;
81
+ }
82
+
83
+ function getCallback(element) {
84
+ for (var i = 0; i < fns.length; i++) {
85
+ if (fns[i].element === element) {
86
+ return fns[i].fn;
87
+ }
88
+ }
89
+ return function(){};
90
+ }
91
+
92
+ function removeCallback(element) {
93
+ for (var i = 0; i < fns.length; i++) {
94
+ if (fns[i].element === element) {
95
+ return fns.splice(i,1);
96
+ }
97
+ }
98
+ }
99
+
100
+ function _addWheelListener(elem, eventName, callback, isPassiveListener ) {
101
+ var cb;
102
+
103
+ if (support === "wheel") {
104
+ cb = callback;
105
+ } else {
106
+ cb = createCallback(elem, callback);
107
+ }
108
+
109
+ elem[_addEventListener](
110
+ prefix + eventName,
111
+ cb,
112
+ isPassiveListener ? passiveListenerOption : activeListenerOption
113
+ );
114
+ }
115
+
116
+ function _removeWheelListener(elem, eventName, callback, isPassiveListener ) {
117
+
118
+ var cb;
119
+
120
+ if (support === "wheel") {
121
+ cb = callback;
122
+ } else {
123
+ cb = getCallback(elem);
124
+ }
125
+
126
+ elem[_removeEventListener](
127
+ prefix + eventName,
128
+ cb,
129
+ isPassiveListener ? passiveListenerOption : activeListenerOption
130
+ );
131
+
132
+ removeCallback(elem);
133
+ }
134
+
135
+ function addWheelListener( elem, callback, isPassiveListener ) {
136
+ _addWheelListener(elem, support, callback, isPassiveListener );
137
+
138
+ // handle MozMousePixelScroll in older Firefox
139
+ if( support == "DOMMouseScroll" ) {
140
+ _addWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener );
141
+ }
142
+ }
143
+
144
+ function removeWheelListener(elem, callback, isPassiveListener){
145
+ _removeWheelListener(elem, support, callback, isPassiveListener);
146
+
147
+ // handle MozMousePixelScroll in older Firefox
148
+ if( support == "DOMMouseScroll" ) {
149
+ _removeWheelListener(elem, "MozMousePixelScroll", callback, isPassiveListener);
150
+ }
151
+ }
152
+
153
+ return {
154
+ on: addWheelListener,
155
+ off: removeWheelListener
156
+ };
157
+
158
+ })();
159
+ return uniwheel;
160
+ }
161
+
162
+ var utilities;
163
+ var hasRequiredUtilities;
164
+
165
+ function requireUtilities () {
166
+ if (hasRequiredUtilities) return utilities;
167
+ hasRequiredUtilities = 1;
168
+ utilities = {
169
+ /**
170
+ * Extends an object
171
+ *
172
+ * @param {Object} target object to extend
173
+ * @param {Object} source object to take properties from
174
+ * @return {Object} extended object
175
+ */
176
+ extend: function(target, source) {
177
+ target = target || {};
178
+ for (var prop in source) {
179
+ // Go recursively
180
+ if (this.isObject(source[prop])) {
181
+ target[prop] = this.extend(target[prop], source[prop]);
182
+ } else {
183
+ target[prop] = source[prop];
184
+ }
185
+ }
186
+ return target;
187
+ },
188
+
189
+ /**
190
+ * Checks if an object is a DOM element
191
+ *
192
+ * @param {Object} o HTML element or String
193
+ * @return {Boolean} returns true if object is a DOM element
194
+ */
195
+ isElement: function(o) {
196
+ return (
197
+ o instanceof HTMLElement ||
198
+ o instanceof SVGElement ||
199
+ o instanceof SVGSVGElement || //DOM2
200
+ (o &&
201
+ typeof o === "object" &&
202
+ o !== null &&
203
+ o.nodeType === 1 &&
204
+ typeof o.nodeName === "string")
205
+ );
206
+ },
207
+
208
+ /**
209
+ * Checks if an object is an Object
210
+ *
211
+ * @param {Object} o Object
212
+ * @return {Boolean} returns true if object is an Object
213
+ */
214
+ isObject: function(o) {
215
+ return Object.prototype.toString.call(o) === "[object Object]";
216
+ },
217
+
218
+ /**
219
+ * Checks if variable is Number
220
+ *
221
+ * @param {Integer|Float} n
222
+ * @return {Boolean} returns true if variable is Number
223
+ */
224
+ isNumber: function(n) {
225
+ return !isNaN(parseFloat(n)) && isFinite(n);
226
+ },
227
+
228
+ /**
229
+ * Search for an SVG element
230
+ *
231
+ * @param {Object|String} elementOrSelector DOM Element or selector String
232
+ * @return {Object|Null} SVG or null
233
+ */
234
+ getSvg: function(elementOrSelector) {
235
+ var element, svg;
236
+
237
+ if (!this.isElement(elementOrSelector)) {
238
+ // If selector provided
239
+ if (
240
+ typeof elementOrSelector === "string" ||
241
+ elementOrSelector instanceof String
242
+ ) {
243
+ // Try to find the element
244
+ element = document.querySelector(elementOrSelector);
245
+
246
+ if (!element) {
247
+ throw new Error(
248
+ "Provided selector did not find any elements. Selector: " +
249
+ elementOrSelector
250
+ );
251
+ }
252
+ } else {
253
+ throw new Error("Provided selector is not an HTML object nor String");
254
+ }
255
+ } else {
256
+ element = elementOrSelector;
257
+ }
258
+
259
+ if (element.tagName.toLowerCase() === "svg") {
260
+ svg = element;
261
+ } else {
262
+ if (element.tagName.toLowerCase() === "object") {
263
+ svg = element.contentDocument.documentElement;
264
+ } else {
265
+ if (element.tagName.toLowerCase() === "embed") {
266
+ svg = element.getSVGDocument().documentElement;
267
+ } else {
268
+ if (element.tagName.toLowerCase() === "img") {
269
+ throw new Error(
270
+ 'Cannot script an SVG in an "img" element. Please use an "object" element or an in-line SVG.'
271
+ );
272
+ } else {
273
+ throw new Error("Cannot get SVG.");
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ return svg;
280
+ },
281
+
282
+ /**
283
+ * Attach a given context to a function
284
+ * @param {Function} fn Function
285
+ * @param {Object} context Context
286
+ * @return {Function} Function with certain context
287
+ */
288
+ proxy: function(fn, context) {
289
+ return function() {
290
+ return fn.apply(context, arguments);
291
+ };
292
+ },
293
+
294
+ /**
295
+ * Returns object type
296
+ * Uses toString that returns [object SVGPoint]
297
+ * And than parses object type from string
298
+ *
299
+ * @param {Object} o Any object
300
+ * @return {String} Object type
301
+ */
302
+ getType: function(o) {
303
+ return Object.prototype.toString
304
+ .apply(o)
305
+ .replace(/^\[object\s/, "")
306
+ .replace(/\]$/, "");
307
+ },
308
+
309
+ /**
310
+ * If it is a touch event than add clientX and clientY to event object
311
+ *
312
+ * @param {Event} evt
313
+ * @param {SVGSVGElement} svg
314
+ */
315
+ mouseAndTouchNormalize: function(evt, svg) {
316
+ // If no clientX then fallback
317
+ if (evt.clientX === void 0 || evt.clientX === null) {
318
+ // Fallback
319
+ evt.clientX = 0;
320
+ evt.clientY = 0;
321
+
322
+ // If it is a touch event
323
+ if (evt.touches !== void 0 && evt.touches.length) {
324
+ if (evt.touches[0].clientX !== void 0) {
325
+ evt.clientX = evt.touches[0].clientX;
326
+ evt.clientY = evt.touches[0].clientY;
327
+ } else if (evt.touches[0].pageX !== void 0) {
328
+ var rect = svg.getBoundingClientRect();
329
+
330
+ evt.clientX = evt.touches[0].pageX - rect.left;
331
+ evt.clientY = evt.touches[0].pageY - rect.top;
332
+ }
333
+ // If it is a custom event
334
+ } else if (evt.originalEvent !== void 0) {
335
+ if (evt.originalEvent.clientX !== void 0) {
336
+ evt.clientX = evt.originalEvent.clientX;
337
+ evt.clientY = evt.originalEvent.clientY;
338
+ }
339
+ }
340
+ }
341
+ },
342
+
343
+ /**
344
+ * Check if an event is a double click/tap
345
+ * TODO: For touch gestures use a library (hammer.js) that takes in account other events
346
+ * (touchmove and touchend). It should take in account tap duration and traveled distance
347
+ *
348
+ * @param {Event} evt
349
+ * @param {Event} prevEvt Previous Event
350
+ * @return {Boolean}
351
+ */
352
+ isDblClick: function(evt, prevEvt) {
353
+ // Double click detected by browser
354
+ if (evt.detail === 2) {
355
+ return true;
356
+ }
357
+ // Try to compare events
358
+ else if (prevEvt !== void 0 && prevEvt !== null) {
359
+ var timeStampDiff = evt.timeStamp - prevEvt.timeStamp, // should be lower than 250 ms
360
+ touchesDistance = Math.sqrt(
361
+ Math.pow(evt.clientX - prevEvt.clientX, 2) +
362
+ Math.pow(evt.clientY - prevEvt.clientY, 2)
363
+ );
364
+
365
+ return timeStampDiff < 250 && touchesDistance < 10;
366
+ }
367
+
368
+ // Nothing found
369
+ return false;
370
+ },
371
+
372
+ /**
373
+ * Returns current timestamp as an integer
374
+ *
375
+ * @return {Number}
376
+ */
377
+ now:
378
+ Date.now ||
379
+ function() {
380
+ return new Date().getTime();
381
+ },
382
+
383
+ // From underscore.
384
+ // Returns a function, that, when invoked, will only be triggered at most once
385
+ // during a given window of time. Normally, the throttled function will run
386
+ // as much as it can, without ever going more than once per `wait` duration;
387
+ // but if you'd like to disable the execution on the leading edge, pass
388
+ // `{leading: false}`. To disable execution on the trailing edge, ditto.
389
+ throttle: function(func, wait, options) {
390
+ var that = this;
391
+ var context, args, result;
392
+ var timeout = null;
393
+ var previous = 0;
394
+ if (!options) {
395
+ options = {};
396
+ }
397
+ var later = function() {
398
+ previous = options.leading === false ? 0 : that.now();
399
+ timeout = null;
400
+ result = func.apply(context, args);
401
+ if (!timeout) {
402
+ context = args = null;
403
+ }
404
+ };
405
+ return function() {
406
+ var now = that.now();
407
+ if (!previous && options.leading === false) {
408
+ previous = now;
409
+ }
410
+ var remaining = wait - (now - previous);
411
+ context = this; // eslint-disable-line consistent-this
412
+ args = arguments;
413
+ if (remaining <= 0 || remaining > wait) {
414
+ clearTimeout(timeout);
415
+ timeout = null;
416
+ previous = now;
417
+ result = func.apply(context, args);
418
+ if (!timeout) {
419
+ context = args = null;
420
+ }
421
+ } else if (!timeout && options.trailing !== false) {
422
+ timeout = setTimeout(later, remaining);
423
+ }
424
+ return result;
425
+ };
426
+ },
427
+
428
+ /**
429
+ * Create a requestAnimationFrame simulation
430
+ *
431
+ * @param {Number|String} refreshRate
432
+ * @return {Function}
433
+ */
434
+ createRequestAnimationFrame: function(refreshRate) {
435
+ var timeout = null;
436
+
437
+ // Convert refreshRate to timeout
438
+ if (refreshRate !== "auto" && refreshRate < 60 && refreshRate > 1) {
439
+ timeout = Math.floor(1000 / refreshRate);
440
+ }
441
+
442
+ if (timeout === null) {
443
+ return window.requestAnimationFrame || requestTimeout(33);
444
+ } else {
445
+ return requestTimeout(timeout);
446
+ }
447
+ }
448
+ };
449
+
450
+ /**
451
+ * Create a callback that will execute after a given timeout
452
+ *
453
+ * @param {Function} timeout
454
+ * @return {Function}
455
+ */
456
+ function requestTimeout(timeout) {
457
+ return function(callback) {
458
+ window.setTimeout(callback, timeout);
459
+ };
460
+ }
461
+ return utilities;
462
+ }
463
+
464
+ var svgUtilities;
465
+ var hasRequiredSvgUtilities;
466
+
467
+ function requireSvgUtilities () {
468
+ if (hasRequiredSvgUtilities) return svgUtilities;
469
+ hasRequiredSvgUtilities = 1;
470
+ var Utils = requireUtilities(),
471
+ _browser = "unknown";
472
+
473
+ // http://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
474
+ if (/*@cc_on!@*/ !!document.documentMode) {
475
+ // internet explorer
476
+ _browser = "ie";
477
+ }
478
+
479
+ svgUtilities = {
480
+ svgNS: "http://www.w3.org/2000/svg",
481
+ xmlNS: "http://www.w3.org/XML/1998/namespace",
482
+ xmlnsNS: "http://www.w3.org/2000/xmlns/",
483
+ xlinkNS: "http://www.w3.org/1999/xlink",
484
+ evNS: "http://www.w3.org/2001/xml-events",
485
+
486
+ /**
487
+ * Get svg dimensions: width and height
488
+ *
489
+ * @param {SVGSVGElement} svg
490
+ * @return {Object} {width: 0, height: 0}
491
+ */
492
+ getBoundingClientRectNormalized: function(svg) {
493
+ if (svg.clientWidth && svg.clientHeight) {
494
+ return { width: svg.clientWidth, height: svg.clientHeight };
495
+ } else if (!!svg.getBoundingClientRect()) {
496
+ return svg.getBoundingClientRect();
497
+ } else {
498
+ throw new Error("Cannot get BoundingClientRect for SVG.");
499
+ }
500
+ },
501
+
502
+ /**
503
+ * Gets g element with class of "viewport" or creates it if it doesn't exist
504
+ *
505
+ * @param {SVGSVGElement} svg
506
+ * @return {SVGElement} g (group) element
507
+ */
508
+ getOrCreateViewport: function(svg, selector) {
509
+ var viewport = null;
510
+
511
+ if (Utils.isElement(selector)) {
512
+ viewport = selector;
513
+ } else {
514
+ viewport = svg.querySelector(selector);
515
+ }
516
+
517
+ // Check if there is just one main group in SVG
518
+ if (!viewport) {
519
+ var childNodes = Array.prototype.slice
520
+ .call(svg.childNodes || svg.children)
521
+ .filter(function(el) {
522
+ return el.nodeName !== "defs" && el.nodeName !== "#text";
523
+ });
524
+
525
+ // Node name should be SVGGElement and should have no transform attribute
526
+ // Groups with transform are not used as viewport because it involves parsing of all transform possibilities
527
+ if (
528
+ childNodes.length === 1 &&
529
+ childNodes[0].nodeName === "g" &&
530
+ childNodes[0].getAttribute("transform") === null
531
+ ) {
532
+ viewport = childNodes[0];
533
+ }
534
+ }
535
+
536
+ // If no favorable group element exists then create one
537
+ if (!viewport) {
538
+ var viewportId =
539
+ "viewport-" + new Date().toISOString().replace(/\D/g, "");
540
+ viewport = document.createElementNS(this.svgNS, "g");
541
+ viewport.setAttribute("id", viewportId);
542
+
543
+ // Internet Explorer (all versions?) can't use childNodes, but other browsers prefer (require?) using childNodes
544
+ var svgChildren = svg.childNodes || svg.children;
545
+ if (!!svgChildren && svgChildren.length > 0) {
546
+ for (var i = svgChildren.length; i > 0; i--) {
547
+ // Move everything into viewport except defs
548
+ if (svgChildren[svgChildren.length - i].nodeName !== "defs") {
549
+ viewport.appendChild(svgChildren[svgChildren.length - i]);
550
+ }
551
+ }
552
+ }
553
+ svg.appendChild(viewport);
554
+ }
555
+
556
+ // Parse class names
557
+ var classNames = [];
558
+ if (viewport.getAttribute("class")) {
559
+ classNames = viewport.getAttribute("class").split(" ");
560
+ }
561
+
562
+ // Set class (if not set already)
563
+ if (!~classNames.indexOf("svg-pan-zoom_viewport")) {
564
+ classNames.push("svg-pan-zoom_viewport");
565
+ viewport.setAttribute("class", classNames.join(" "));
566
+ }
567
+
568
+ return viewport;
569
+ },
570
+
571
+ /**
572
+ * Set SVG attributes
573
+ *
574
+ * @param {SVGSVGElement} svg
575
+ */
576
+ setupSvgAttributes: function(svg) {
577
+ // Setting default attributes
578
+ svg.setAttribute("xmlns", this.svgNS);
579
+ svg.setAttributeNS(this.xmlnsNS, "xmlns:xlink", this.xlinkNS);
580
+ svg.setAttributeNS(this.xmlnsNS, "xmlns:ev", this.evNS);
581
+
582
+ // Needed for Internet Explorer, otherwise the viewport overflows
583
+ if (svg.parentNode !== null) {
584
+ var style = svg.getAttribute("style") || "";
585
+ if (style.toLowerCase().indexOf("overflow") === -1) {
586
+ svg.setAttribute("style", "overflow: hidden; " + style);
587
+ }
588
+ }
589
+ },
590
+
591
+ /**
592
+ * How long Internet Explorer takes to finish updating its display (ms).
593
+ */
594
+ internetExplorerRedisplayInterval: 300,
595
+
596
+ /**
597
+ * Forces the browser to redisplay all SVG elements that rely on an
598
+ * element defined in a 'defs' section. It works globally, for every
599
+ * available defs element on the page.
600
+ * The throttling is intentionally global.
601
+ *
602
+ * This is only needed for IE. It is as a hack to make markers (and 'use' elements?)
603
+ * visible after pan/zoom when there are multiple SVGs on the page.
604
+ * See bug report: https://connect.microsoft.com/IE/feedback/details/781964/
605
+ * also see svg-pan-zoom issue: https://github.com/bumbu/svg-pan-zoom/issues/62
606
+ */
607
+ refreshDefsGlobal: Utils.throttle(
608
+ function() {
609
+ var allDefs = document.querySelectorAll("defs");
610
+ var allDefsCount = allDefs.length;
611
+ for (var i = 0; i < allDefsCount; i++) {
612
+ var thisDefs = allDefs[i];
613
+ thisDefs.parentNode.insertBefore(thisDefs, thisDefs);
614
+ }
615
+ },
616
+ svgUtilities ? svgUtilities.internetExplorerRedisplayInterval : null
617
+ ),
618
+
619
+ /**
620
+ * Sets the current transform matrix of an element
621
+ *
622
+ * @param {SVGElement} element
623
+ * @param {SVGMatrix} matrix CTM
624
+ * @param {SVGElement} defs
625
+ */
626
+ setCTM: function(element, matrix, defs) {
627
+ var that = this,
628
+ s =
629
+ "matrix(" +
630
+ matrix.a +
631
+ "," +
632
+ matrix.b +
633
+ "," +
634
+ matrix.c +
635
+ "," +
636
+ matrix.d +
637
+ "," +
638
+ matrix.e +
639
+ "," +
640
+ matrix.f +
641
+ ")";
642
+
643
+ element.setAttributeNS(null, "transform", s);
644
+ if ("transform" in element.style) {
645
+ element.style.transform = s;
646
+ } else if ("-ms-transform" in element.style) {
647
+ element.style["-ms-transform"] = s;
648
+ } else if ("-webkit-transform" in element.style) {
649
+ element.style["-webkit-transform"] = s;
650
+ }
651
+
652
+ // IE has a bug that makes markers disappear on zoom (when the matrix "a" and/or "d" elements change)
653
+ // see http://stackoverflow.com/questions/17654578/svg-marker-does-not-work-in-ie9-10
654
+ // and http://srndolha.wordpress.com/2013/11/25/svg-line-markers-may-disappear-in-internet-explorer-11/
655
+ if (_browser === "ie" && !!defs) {
656
+ // this refresh is intended for redisplaying the SVG during zooming
657
+ defs.parentNode.insertBefore(defs, defs);
658
+ // this refresh is intended for redisplaying the other SVGs on a page when panning a given SVG
659
+ // it is also needed for the given SVG itself, on zoomEnd, if the SVG contains any markers that
660
+ // are located under any other element(s).
661
+ window.setTimeout(function() {
662
+ that.refreshDefsGlobal();
663
+ }, that.internetExplorerRedisplayInterval);
664
+ }
665
+ },
666
+
667
+ /**
668
+ * Instantiate an SVGPoint object with given event coordinates
669
+ *
670
+ * @param {Event} evt
671
+ * @param {SVGSVGElement} svg
672
+ * @return {SVGPoint} point
673
+ */
674
+ getEventPoint: function(evt, svg) {
675
+ var point = svg.createSVGPoint();
676
+
677
+ Utils.mouseAndTouchNormalize(evt, svg);
678
+
679
+ point.x = evt.clientX;
680
+ point.y = evt.clientY;
681
+
682
+ return point;
683
+ },
684
+
685
+ /**
686
+ * Get SVG center point
687
+ *
688
+ * @param {SVGSVGElement} svg
689
+ * @return {SVGPoint}
690
+ */
691
+ getSvgCenterPoint: function(svg, width, height) {
692
+ return this.createSVGPoint(svg, width / 2, height / 2);
693
+ },
694
+
695
+ /**
696
+ * Create a SVGPoint with given x and y
697
+ *
698
+ * @param {SVGSVGElement} svg
699
+ * @param {Number} x
700
+ * @param {Number} y
701
+ * @return {SVGPoint}
702
+ */
703
+ createSVGPoint: function(svg, x, y) {
704
+ var point = svg.createSVGPoint();
705
+ point.x = x;
706
+ point.y = y;
707
+
708
+ return point;
709
+ }
710
+ };
711
+ return svgUtilities;
712
+ }
713
+
714
+ var controlIcons;
715
+ var hasRequiredControlIcons;
716
+
717
+ function requireControlIcons () {
718
+ if (hasRequiredControlIcons) return controlIcons;
719
+ hasRequiredControlIcons = 1;
720
+ var SvgUtils = requireSvgUtilities();
721
+
722
+ controlIcons = {
723
+ enable: function(instance) {
724
+ // Select (and create if necessary) defs
725
+ var defs = instance.svg.querySelector("defs");
726
+ if (!defs) {
727
+ defs = document.createElementNS(SvgUtils.svgNS, "defs");
728
+ instance.svg.appendChild(defs);
729
+ }
730
+
731
+ // Check for style element, and create it if it doesn't exist
732
+ var styleEl = defs.querySelector("style#svg-pan-zoom-controls-styles");
733
+ if (!styleEl) {
734
+ var style = document.createElementNS(SvgUtils.svgNS, "style");
735
+ style.setAttribute("id", "svg-pan-zoom-controls-styles");
736
+ style.setAttribute("type", "text/css");
737
+ style.textContent =
738
+ ".svg-pan-zoom-control { cursor: pointer; fill: black; fill-opacity: 0.333; } .svg-pan-zoom-control:hover { fill-opacity: 0.8; } .svg-pan-zoom-control-background { fill: white; fill-opacity: 0.5; } .svg-pan-zoom-control-background { fill-opacity: 0.8; }";
739
+ defs.appendChild(style);
740
+ }
741
+
742
+ // Zoom Group
743
+ var zoomGroup = document.createElementNS(SvgUtils.svgNS, "g");
744
+ zoomGroup.setAttribute("id", "svg-pan-zoom-controls");
745
+ zoomGroup.setAttribute(
746
+ "transform",
747
+ "translate(" +
748
+ (instance.width - 70) +
749
+ " " +
750
+ (instance.height - 76) +
751
+ ") scale(0.75)"
752
+ );
753
+ zoomGroup.setAttribute("class", "svg-pan-zoom-control");
754
+
755
+ // Control elements
756
+ zoomGroup.appendChild(this._createZoomIn(instance));
757
+ zoomGroup.appendChild(this._createZoomReset(instance));
758
+ zoomGroup.appendChild(this._createZoomOut(instance));
759
+
760
+ // Finally append created element
761
+ instance.svg.appendChild(zoomGroup);
762
+
763
+ // Cache control instance
764
+ instance.controlIcons = zoomGroup;
765
+ },
766
+
767
+ _createZoomIn: function(instance) {
768
+ var zoomIn = document.createElementNS(SvgUtils.svgNS, "g");
769
+ zoomIn.setAttribute("id", "svg-pan-zoom-zoom-in");
770
+ zoomIn.setAttribute("transform", "translate(30.5 5) scale(0.015)");
771
+ zoomIn.setAttribute("class", "svg-pan-zoom-control");
772
+ zoomIn.addEventListener(
773
+ "click",
774
+ function() {
775
+ instance.getPublicInstance().zoomIn();
776
+ },
777
+ false
778
+ );
779
+ zoomIn.addEventListener(
780
+ "touchstart",
781
+ function() {
782
+ instance.getPublicInstance().zoomIn();
783
+ },
784
+ false
785
+ );
786
+
787
+ var zoomInBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier
788
+ zoomInBackground.setAttribute("x", "0");
789
+ zoomInBackground.setAttribute("y", "0");
790
+ zoomInBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down
791
+ zoomInBackground.setAttribute("height", "1400");
792
+ zoomInBackground.setAttribute("class", "svg-pan-zoom-control-background");
793
+ zoomIn.appendChild(zoomInBackground);
794
+
795
+ var zoomInShape = document.createElementNS(SvgUtils.svgNS, "path");
796
+ zoomInShape.setAttribute(
797
+ "d",
798
+ "M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z"
799
+ );
800
+ zoomInShape.setAttribute("class", "svg-pan-zoom-control-element");
801
+ zoomIn.appendChild(zoomInShape);
802
+
803
+ return zoomIn;
804
+ },
805
+
806
+ _createZoomReset: function(instance) {
807
+ // reset
808
+ var resetPanZoomControl = document.createElementNS(SvgUtils.svgNS, "g");
809
+ resetPanZoomControl.setAttribute("id", "svg-pan-zoom-reset-pan-zoom");
810
+ resetPanZoomControl.setAttribute("transform", "translate(5 35) scale(0.4)");
811
+ resetPanZoomControl.setAttribute("class", "svg-pan-zoom-control");
812
+ resetPanZoomControl.addEventListener(
813
+ "click",
814
+ function() {
815
+ instance.getPublicInstance().reset();
816
+ },
817
+ false
818
+ );
819
+ resetPanZoomControl.addEventListener(
820
+ "touchstart",
821
+ function() {
822
+ instance.getPublicInstance().reset();
823
+ },
824
+ false
825
+ );
826
+
827
+ var resetPanZoomControlBackground = document.createElementNS(
828
+ SvgUtils.svgNS,
829
+ "rect"
830
+ ); // TODO change these background space fillers to rounded rectangles so they look prettier
831
+ resetPanZoomControlBackground.setAttribute("x", "2");
832
+ resetPanZoomControlBackground.setAttribute("y", "2");
833
+ resetPanZoomControlBackground.setAttribute("width", "182"); // larger than expected because the whole group is transformed to scale down
834
+ resetPanZoomControlBackground.setAttribute("height", "58");
835
+ resetPanZoomControlBackground.setAttribute(
836
+ "class",
837
+ "svg-pan-zoom-control-background"
838
+ );
839
+ resetPanZoomControl.appendChild(resetPanZoomControlBackground);
840
+
841
+ var resetPanZoomControlShape1 = document.createElementNS(
842
+ SvgUtils.svgNS,
843
+ "path"
844
+ );
845
+ resetPanZoomControlShape1.setAttribute(
846
+ "d",
847
+ "M33.051,20.632c-0.742-0.406-1.854-0.609-3.338-0.609h-7.969v9.281h7.769c1.543,0,2.701-0.188,3.473-0.562c1.365-0.656,2.048-1.953,2.048-3.891C35.032,22.757,34.372,21.351,33.051,20.632z"
848
+ );
849
+ resetPanZoomControlShape1.setAttribute(
850
+ "class",
851
+ "svg-pan-zoom-control-element"
852
+ );
853
+ resetPanZoomControl.appendChild(resetPanZoomControlShape1);
854
+
855
+ var resetPanZoomControlShape2 = document.createElementNS(
856
+ SvgUtils.svgNS,
857
+ "path"
858
+ );
859
+ resetPanZoomControlShape2.setAttribute(
860
+ "d",
861
+ "M170.231,0.5H15.847C7.102,0.5,0.5,5.708,0.5,11.84v38.861C0.5,56.833,7.102,61.5,15.847,61.5h154.384c8.745,0,15.269-4.667,15.269-10.798V11.84C185.5,5.708,178.976,0.5,170.231,0.5z M42.837,48.569h-7.969c-0.219-0.766-0.375-1.383-0.469-1.852c-0.188-0.969-0.289-1.961-0.305-2.977l-0.047-3.211c-0.03-2.203-0.41-3.672-1.142-4.406c-0.732-0.734-2.103-1.102-4.113-1.102h-7.05v13.547h-7.055V14.022h16.524c2.361,0.047,4.178,0.344,5.45,0.891c1.272,0.547,2.351,1.352,3.234,2.414c0.731,0.875,1.31,1.844,1.737,2.906s0.64,2.273,0.64,3.633c0,1.641-0.414,3.254-1.242,4.84s-2.195,2.707-4.102,3.363c1.594,0.641,2.723,1.551,3.387,2.73s0.996,2.98,0.996,5.402v2.32c0,1.578,0.063,2.648,0.19,3.211c0.19,0.891,0.635,1.547,1.333,1.969V48.569z M75.579,48.569h-26.18V14.022h25.336v6.117H56.454v7.336h16.781v6H56.454v8.883h19.125V48.569z M104.497,46.331c-2.44,2.086-5.887,3.129-10.34,3.129c-4.548,0-8.125-1.027-10.731-3.082s-3.909-4.879-3.909-8.473h6.891c0.224,1.578,0.662,2.758,1.316,3.539c1.196,1.422,3.246,2.133,6.15,2.133c1.739,0,3.151-0.188,4.236-0.562c2.058-0.719,3.087-2.055,3.087-4.008c0-1.141-0.504-2.023-1.512-2.648c-1.008-0.609-2.607-1.148-4.796-1.617l-3.74-0.82c-3.676-0.812-6.201-1.695-7.576-2.648c-2.328-1.594-3.492-4.086-3.492-7.477c0-3.094,1.139-5.664,3.417-7.711s5.623-3.07,10.036-3.07c3.685,0,6.829,0.965,9.431,2.895c2.602,1.93,3.966,4.73,4.093,8.402h-6.938c-0.128-2.078-1.057-3.555-2.787-4.43c-1.154-0.578-2.587-0.867-4.301-0.867c-1.907,0-3.428,0.375-4.565,1.125c-1.138,0.75-1.706,1.797-1.706,3.141c0,1.234,0.561,2.156,1.682,2.766c0.721,0.406,2.25,0.883,4.589,1.43l6.063,1.43c2.657,0.625,4.648,1.461,5.975,2.508c2.059,1.625,3.089,3.977,3.089,7.055C108.157,41.624,106.937,44.245,104.497,46.331z M139.61,48.569h-26.18V14.022h25.336v6.117h-18.281v7.336h16.781v6h-16.781v8.883h19.125V48.569z M170.337,20.14h-10.336v28.43h-7.266V20.14h-10.383v-6.117h27.984V20.14z"
862
+ );
863
+ resetPanZoomControlShape2.setAttribute(
864
+ "class",
865
+ "svg-pan-zoom-control-element"
866
+ );
867
+ resetPanZoomControl.appendChild(resetPanZoomControlShape2);
868
+
869
+ return resetPanZoomControl;
870
+ },
871
+
872
+ _createZoomOut: function(instance) {
873
+ // zoom out
874
+ var zoomOut = document.createElementNS(SvgUtils.svgNS, "g");
875
+ zoomOut.setAttribute("id", "svg-pan-zoom-zoom-out");
876
+ zoomOut.setAttribute("transform", "translate(30.5 70) scale(0.015)");
877
+ zoomOut.setAttribute("class", "svg-pan-zoom-control");
878
+ zoomOut.addEventListener(
879
+ "click",
880
+ function() {
881
+ instance.getPublicInstance().zoomOut();
882
+ },
883
+ false
884
+ );
885
+ zoomOut.addEventListener(
886
+ "touchstart",
887
+ function() {
888
+ instance.getPublicInstance().zoomOut();
889
+ },
890
+ false
891
+ );
892
+
893
+ var zoomOutBackground = document.createElementNS(SvgUtils.svgNS, "rect"); // TODO change these background space fillers to rounded rectangles so they look prettier
894
+ zoomOutBackground.setAttribute("x", "0");
895
+ zoomOutBackground.setAttribute("y", "0");
896
+ zoomOutBackground.setAttribute("width", "1500"); // larger than expected because the whole group is transformed to scale down
897
+ zoomOutBackground.setAttribute("height", "1400");
898
+ zoomOutBackground.setAttribute("class", "svg-pan-zoom-control-background");
899
+ zoomOut.appendChild(zoomOutBackground);
900
+
901
+ var zoomOutShape = document.createElementNS(SvgUtils.svgNS, "path");
902
+ zoomOutShape.setAttribute(
903
+ "d",
904
+ "M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z"
905
+ );
906
+ zoomOutShape.setAttribute("class", "svg-pan-zoom-control-element");
907
+ zoomOut.appendChild(zoomOutShape);
908
+
909
+ return zoomOut;
910
+ },
911
+
912
+ disable: function(instance) {
913
+ if (instance.controlIcons) {
914
+ instance.controlIcons.parentNode.removeChild(instance.controlIcons);
915
+ instance.controlIcons = null;
916
+ }
917
+ }
918
+ };
919
+ return controlIcons;
920
+ }
921
+
922
+ var shadowViewport;
923
+ var hasRequiredShadowViewport;
924
+
925
+ function requireShadowViewport () {
926
+ if (hasRequiredShadowViewport) return shadowViewport;
927
+ hasRequiredShadowViewport = 1;
928
+ var SvgUtils = requireSvgUtilities(),
929
+ Utils = requireUtilities();
930
+
931
+ var ShadowViewport = function(viewport, options) {
932
+ this.init(viewport, options);
933
+ };
934
+
935
+ /**
936
+ * Initialization
937
+ *
938
+ * @param {SVGElement} viewport
939
+ * @param {Object} options
940
+ */
941
+ ShadowViewport.prototype.init = function(viewport, options) {
942
+ // DOM Elements
943
+ this.viewport = viewport;
944
+ this.options = options;
945
+
946
+ // State cache
947
+ this.originalState = { zoom: 1, x: 0, y: 0 };
948
+ this.activeState = { zoom: 1, x: 0, y: 0 };
949
+
950
+ this.updateCTMCached = Utils.proxy(this.updateCTM, this);
951
+
952
+ // Create a custom requestAnimationFrame taking in account refreshRate
953
+ this.requestAnimationFrame = Utils.createRequestAnimationFrame(
954
+ this.options.refreshRate
955
+ );
956
+
957
+ // ViewBox
958
+ this.viewBox = { x: 0, y: 0, width: 0, height: 0 };
959
+ this.cacheViewBox();
960
+
961
+ // Process CTM
962
+ var newCTM = this.processCTM();
963
+
964
+ // Update viewport CTM and cache zoom and pan
965
+ this.setCTM(newCTM);
966
+
967
+ // Update CTM in this frame
968
+ this.updateCTM();
969
+ };
970
+
971
+ /**
972
+ * Cache initial viewBox value
973
+ * If no viewBox is defined, then use viewport size/position instead for viewBox values
974
+ */
975
+ ShadowViewport.prototype.cacheViewBox = function() {
976
+ var svgViewBox = this.options.svg.getAttribute("viewBox");
977
+
978
+ if (svgViewBox) {
979
+ var viewBoxValues = svgViewBox
980
+ .split(/[\s\,]/)
981
+ .filter(function(v) {
982
+ return v;
983
+ })
984
+ .map(parseFloat);
985
+
986
+ // Cache viewbox x and y offset
987
+ this.viewBox.x = viewBoxValues[0];
988
+ this.viewBox.y = viewBoxValues[1];
989
+ this.viewBox.width = viewBoxValues[2];
990
+ this.viewBox.height = viewBoxValues[3];
991
+
992
+ var zoom = Math.min(
993
+ this.options.width / this.viewBox.width,
994
+ this.options.height / this.viewBox.height
995
+ );
996
+
997
+ // Update active state
998
+ this.activeState.zoom = zoom;
999
+ this.activeState.x = (this.options.width - this.viewBox.width * zoom) / 2;
1000
+ this.activeState.y = (this.options.height - this.viewBox.height * zoom) / 2;
1001
+
1002
+ // Force updating CTM
1003
+ this.updateCTMOnNextFrame();
1004
+
1005
+ this.options.svg.removeAttribute("viewBox");
1006
+ } else {
1007
+ this.simpleViewBoxCache();
1008
+ }
1009
+ };
1010
+
1011
+ /**
1012
+ * Recalculate viewport sizes and update viewBox cache
1013
+ */
1014
+ ShadowViewport.prototype.simpleViewBoxCache = function() {
1015
+ var bBox = this.viewport.getBBox();
1016
+
1017
+ this.viewBox.x = bBox.x;
1018
+ this.viewBox.y = bBox.y;
1019
+ this.viewBox.width = bBox.width;
1020
+ this.viewBox.height = bBox.height;
1021
+ };
1022
+
1023
+ /**
1024
+ * Returns a viewbox object. Safe to alter
1025
+ *
1026
+ * @return {Object} viewbox object
1027
+ */
1028
+ ShadowViewport.prototype.getViewBox = function() {
1029
+ return Utils.extend({}, this.viewBox);
1030
+ };
1031
+
1032
+ /**
1033
+ * Get initial zoom and pan values. Save them into originalState
1034
+ * Parses viewBox attribute to alter initial sizes
1035
+ *
1036
+ * @return {CTM} CTM object based on options
1037
+ */
1038
+ ShadowViewport.prototype.processCTM = function() {
1039
+ var newCTM = this.getCTM();
1040
+
1041
+ if (this.options.fit || this.options.contain) {
1042
+ var newScale;
1043
+ if (this.options.fit) {
1044
+ newScale = Math.min(
1045
+ this.options.width / this.viewBox.width,
1046
+ this.options.height / this.viewBox.height
1047
+ );
1048
+ } else {
1049
+ newScale = Math.max(
1050
+ this.options.width / this.viewBox.width,
1051
+ this.options.height / this.viewBox.height
1052
+ );
1053
+ }
1054
+
1055
+ newCTM.a = newScale; //x-scale
1056
+ newCTM.d = newScale; //y-scale
1057
+ newCTM.e = -this.viewBox.x * newScale; //x-transform
1058
+ newCTM.f = -this.viewBox.y * newScale; //y-transform
1059
+ }
1060
+
1061
+ if (this.options.center) {
1062
+ var offsetX =
1063
+ (this.options.width -
1064
+ (this.viewBox.width + this.viewBox.x * 2) * newCTM.a) *
1065
+ 0.5,
1066
+ offsetY =
1067
+ (this.options.height -
1068
+ (this.viewBox.height + this.viewBox.y * 2) * newCTM.a) *
1069
+ 0.5;
1070
+
1071
+ newCTM.e = offsetX;
1072
+ newCTM.f = offsetY;
1073
+ }
1074
+
1075
+ // Cache initial values. Based on activeState and fix+center opitons
1076
+ this.originalState.zoom = newCTM.a;
1077
+ this.originalState.x = newCTM.e;
1078
+ this.originalState.y = newCTM.f;
1079
+
1080
+ return newCTM;
1081
+ };
1082
+
1083
+ /**
1084
+ * Return originalState object. Safe to alter
1085
+ *
1086
+ * @return {Object}
1087
+ */
1088
+ ShadowViewport.prototype.getOriginalState = function() {
1089
+ return Utils.extend({}, this.originalState);
1090
+ };
1091
+
1092
+ /**
1093
+ * Return actualState object. Safe to alter
1094
+ *
1095
+ * @return {Object}
1096
+ */
1097
+ ShadowViewport.prototype.getState = function() {
1098
+ return Utils.extend({}, this.activeState);
1099
+ };
1100
+
1101
+ /**
1102
+ * Get zoom scale
1103
+ *
1104
+ * @return {Float} zoom scale
1105
+ */
1106
+ ShadowViewport.prototype.getZoom = function() {
1107
+ return this.activeState.zoom;
1108
+ };
1109
+
1110
+ /**
1111
+ * Get zoom scale for pubilc usage
1112
+ *
1113
+ * @return {Float} zoom scale
1114
+ */
1115
+ ShadowViewport.prototype.getRelativeZoom = function() {
1116
+ return this.activeState.zoom / this.originalState.zoom;
1117
+ };
1118
+
1119
+ /**
1120
+ * Compute zoom scale for pubilc usage
1121
+ *
1122
+ * @return {Float} zoom scale
1123
+ */
1124
+ ShadowViewport.prototype.computeRelativeZoom = function(scale) {
1125
+ return scale / this.originalState.zoom;
1126
+ };
1127
+
1128
+ /**
1129
+ * Get pan
1130
+ *
1131
+ * @return {Object}
1132
+ */
1133
+ ShadowViewport.prototype.getPan = function() {
1134
+ return { x: this.activeState.x, y: this.activeState.y };
1135
+ };
1136
+
1137
+ /**
1138
+ * Return cached viewport CTM value that can be safely modified
1139
+ *
1140
+ * @return {SVGMatrix}
1141
+ */
1142
+ ShadowViewport.prototype.getCTM = function() {
1143
+ var safeCTM = this.options.svg.createSVGMatrix();
1144
+
1145
+ // Copy values manually as in FF they are not itterable
1146
+ safeCTM.a = this.activeState.zoom;
1147
+ safeCTM.b = 0;
1148
+ safeCTM.c = 0;
1149
+ safeCTM.d = this.activeState.zoom;
1150
+ safeCTM.e = this.activeState.x;
1151
+ safeCTM.f = this.activeState.y;
1152
+
1153
+ return safeCTM;
1154
+ };
1155
+
1156
+ /**
1157
+ * Set a new CTM
1158
+ *
1159
+ * @param {SVGMatrix} newCTM
1160
+ */
1161
+ ShadowViewport.prototype.setCTM = function(newCTM) {
1162
+ var willZoom = this.isZoomDifferent(newCTM),
1163
+ willPan = this.isPanDifferent(newCTM);
1164
+
1165
+ if (willZoom || willPan) {
1166
+ // Before zoom
1167
+ if (willZoom) {
1168
+ // If returns false then cancel zooming
1169
+ if (
1170
+ this.options.beforeZoom(
1171
+ this.getRelativeZoom(),
1172
+ this.computeRelativeZoom(newCTM.a)
1173
+ ) === false
1174
+ ) {
1175
+ newCTM.a = newCTM.d = this.activeState.zoom;
1176
+ willZoom = false;
1177
+ } else {
1178
+ this.updateCache(newCTM);
1179
+ this.options.onZoom(this.getRelativeZoom());
1180
+ }
1181
+ }
1182
+
1183
+ // Before pan
1184
+ if (willPan) {
1185
+ var preventPan = this.options.beforePan(this.getPan(), {
1186
+ x: newCTM.e,
1187
+ y: newCTM.f
1188
+ }),
1189
+ // If prevent pan is an object
1190
+ preventPanX = false,
1191
+ preventPanY = false;
1192
+
1193
+ // If prevent pan is Boolean false
1194
+ if (preventPan === false) {
1195
+ // Set x and y same as before
1196
+ newCTM.e = this.getPan().x;
1197
+ newCTM.f = this.getPan().y;
1198
+
1199
+ preventPanX = preventPanY = true;
1200
+ } else if (Utils.isObject(preventPan)) {
1201
+ // Check for X axes attribute
1202
+ if (preventPan.x === false) {
1203
+ // Prevent panning on x axes
1204
+ newCTM.e = this.getPan().x;
1205
+ preventPanX = true;
1206
+ } else if (Utils.isNumber(preventPan.x)) {
1207
+ // Set a custom pan value
1208
+ newCTM.e = preventPan.x;
1209
+ }
1210
+
1211
+ // Check for Y axes attribute
1212
+ if (preventPan.y === false) {
1213
+ // Prevent panning on x axes
1214
+ newCTM.f = this.getPan().y;
1215
+ preventPanY = true;
1216
+ } else if (Utils.isNumber(preventPan.y)) {
1217
+ // Set a custom pan value
1218
+ newCTM.f = preventPan.y;
1219
+ }
1220
+ }
1221
+
1222
+ // Update willPan flag
1223
+ // Check if newCTM is still different
1224
+ if ((preventPanX && preventPanY) || !this.isPanDifferent(newCTM)) {
1225
+ willPan = false;
1226
+ } else {
1227
+ this.updateCache(newCTM);
1228
+ this.options.onPan(this.getPan());
1229
+ }
1230
+ }
1231
+
1232
+ // Check again if should zoom or pan
1233
+ if (willZoom || willPan) {
1234
+ this.updateCTMOnNextFrame();
1235
+ }
1236
+ }
1237
+ };
1238
+
1239
+ ShadowViewport.prototype.isZoomDifferent = function(newCTM) {
1240
+ return this.activeState.zoom !== newCTM.a;
1241
+ };
1242
+
1243
+ ShadowViewport.prototype.isPanDifferent = function(newCTM) {
1244
+ return this.activeState.x !== newCTM.e || this.activeState.y !== newCTM.f;
1245
+ };
1246
+
1247
+ /**
1248
+ * Update cached CTM and active state
1249
+ *
1250
+ * @param {SVGMatrix} newCTM
1251
+ */
1252
+ ShadowViewport.prototype.updateCache = function(newCTM) {
1253
+ this.activeState.zoom = newCTM.a;
1254
+ this.activeState.x = newCTM.e;
1255
+ this.activeState.y = newCTM.f;
1256
+ };
1257
+
1258
+ ShadowViewport.prototype.pendingUpdate = false;
1259
+
1260
+ /**
1261
+ * Place a request to update CTM on next Frame
1262
+ */
1263
+ ShadowViewport.prototype.updateCTMOnNextFrame = function() {
1264
+ if (!this.pendingUpdate) {
1265
+ // Lock
1266
+ this.pendingUpdate = true;
1267
+
1268
+ // Throttle next update
1269
+ this.requestAnimationFrame.call(window, this.updateCTMCached);
1270
+ }
1271
+ };
1272
+
1273
+ /**
1274
+ * Update viewport CTM with cached CTM
1275
+ */
1276
+ ShadowViewport.prototype.updateCTM = function() {
1277
+ var ctm = this.getCTM();
1278
+
1279
+ // Updates SVG element
1280
+ SvgUtils.setCTM(this.viewport, ctm, this.defs);
1281
+
1282
+ // Free the lock
1283
+ this.pendingUpdate = false;
1284
+
1285
+ // Notify about the update
1286
+ if (this.options.onUpdatedCTM) {
1287
+ this.options.onUpdatedCTM(ctm);
1288
+ }
1289
+ };
1290
+
1291
+ shadowViewport = function(viewport, options) {
1292
+ return new ShadowViewport(viewport, options);
1293
+ };
1294
+ return shadowViewport;
1295
+ }
1296
+
1297
+ var svgPanZoom_1;
1298
+ var hasRequiredSvgPanZoom;
1299
+
1300
+ function requireSvgPanZoom () {
1301
+ if (hasRequiredSvgPanZoom) return svgPanZoom_1;
1302
+ hasRequiredSvgPanZoom = 1;
1303
+ var Wheel = requireUniwheel(),
1304
+ ControlIcons = requireControlIcons(),
1305
+ Utils = requireUtilities(),
1306
+ SvgUtils = requireSvgUtilities(),
1307
+ ShadowViewport = requireShadowViewport();
1308
+
1309
+ var SvgPanZoom = function(svg, options) {
1310
+ this.init(svg, options);
1311
+ };
1312
+
1313
+ var optionsDefaults = {
1314
+ viewportSelector: ".svg-pan-zoom_viewport", // Viewport selector. Can be querySelector string or SVGElement
1315
+ panEnabled: true, // enable or disable panning (default enabled)
1316
+ controlIconsEnabled: false, // insert icons to give user an option in addition to mouse events to control pan/zoom (default disabled)
1317
+ zoomEnabled: true, // enable or disable zooming (default enabled)
1318
+ dblClickZoomEnabled: true, // enable or disable zooming by double clicking (default enabled)
1319
+ mouseWheelZoomEnabled: true, // enable or disable zooming by mouse wheel (default enabled)
1320
+ preventMouseEventsDefault: true, // enable or disable preventDefault for mouse events
1321
+ zoomScaleSensitivity: 0.1, // Zoom sensitivity
1322
+ minZoom: 0.5, // Minimum Zoom level
1323
+ maxZoom: 10, // Maximum Zoom level
1324
+ fit: true, // enable or disable viewport fit in SVG (default true)
1325
+ contain: false, // enable or disable viewport contain the svg (default false)
1326
+ center: true, // enable or disable viewport centering in SVG (default true)
1327
+ refreshRate: "auto", // Maximum number of frames per second (altering SVG's viewport)
1328
+ beforeZoom: null,
1329
+ onZoom: null,
1330
+ beforePan: null,
1331
+ onPan: null,
1332
+ customEventsHandler: null,
1333
+ eventsListenerElement: null,
1334
+ onUpdatedCTM: null
1335
+ };
1336
+
1337
+ var passiveListenerOption = { passive: true };
1338
+
1339
+ SvgPanZoom.prototype.init = function(svg, options) {
1340
+ var that = this;
1341
+
1342
+ this.svg = svg;
1343
+ this.defs = svg.querySelector("defs");
1344
+
1345
+ // Add default attributes to SVG
1346
+ SvgUtils.setupSvgAttributes(this.svg);
1347
+
1348
+ // Set options
1349
+ this.options = Utils.extend(Utils.extend({}, optionsDefaults), options);
1350
+
1351
+ // Set default state
1352
+ this.state = "none";
1353
+
1354
+ // Get dimensions
1355
+ var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(
1356
+ svg
1357
+ );
1358
+ this.width = boundingClientRectNormalized.width;
1359
+ this.height = boundingClientRectNormalized.height;
1360
+
1361
+ // Init shadow viewport
1362
+ this.viewport = ShadowViewport(
1363
+ SvgUtils.getOrCreateViewport(this.svg, this.options.viewportSelector),
1364
+ {
1365
+ svg: this.svg,
1366
+ width: this.width,
1367
+ height: this.height,
1368
+ fit: this.options.fit,
1369
+ contain: this.options.contain,
1370
+ center: this.options.center,
1371
+ refreshRate: this.options.refreshRate,
1372
+ // Put callbacks into functions as they can change through time
1373
+ beforeZoom: function(oldScale, newScale) {
1374
+ if (that.viewport && that.options.beforeZoom) {
1375
+ return that.options.beforeZoom(oldScale, newScale);
1376
+ }
1377
+ },
1378
+ onZoom: function(scale) {
1379
+ if (that.viewport && that.options.onZoom) {
1380
+ return that.options.onZoom(scale);
1381
+ }
1382
+ },
1383
+ beforePan: function(oldPoint, newPoint) {
1384
+ if (that.viewport && that.options.beforePan) {
1385
+ return that.options.beforePan(oldPoint, newPoint);
1386
+ }
1387
+ },
1388
+ onPan: function(point) {
1389
+ if (that.viewport && that.options.onPan) {
1390
+ return that.options.onPan(point);
1391
+ }
1392
+ },
1393
+ onUpdatedCTM: function(ctm) {
1394
+ if (that.viewport && that.options.onUpdatedCTM) {
1395
+ return that.options.onUpdatedCTM(ctm);
1396
+ }
1397
+ }
1398
+ }
1399
+ );
1400
+
1401
+ // Wrap callbacks into public API context
1402
+ var publicInstance = this.getPublicInstance();
1403
+ publicInstance.setBeforeZoom(this.options.beforeZoom);
1404
+ publicInstance.setOnZoom(this.options.onZoom);
1405
+ publicInstance.setBeforePan(this.options.beforePan);
1406
+ publicInstance.setOnPan(this.options.onPan);
1407
+ publicInstance.setOnUpdatedCTM(this.options.onUpdatedCTM);
1408
+
1409
+ if (this.options.controlIconsEnabled) {
1410
+ ControlIcons.enable(this);
1411
+ }
1412
+
1413
+ // Init events handlers
1414
+ this.lastMouseWheelEventTime = Date.now();
1415
+ this.setupHandlers();
1416
+ };
1417
+
1418
+ /**
1419
+ * Register event handlers
1420
+ */
1421
+ SvgPanZoom.prototype.setupHandlers = function() {
1422
+ var that = this,
1423
+ prevEvt = null; // use for touchstart event to detect double tap
1424
+
1425
+ this.eventListeners = {
1426
+ // Mouse down group
1427
+ mousedown: function(evt) {
1428
+ var result = that.handleMouseDown(evt, prevEvt);
1429
+ prevEvt = evt;
1430
+ return result;
1431
+ },
1432
+ touchstart: function(evt) {
1433
+ var result = that.handleMouseDown(evt, prevEvt);
1434
+ prevEvt = evt;
1435
+ return result;
1436
+ },
1437
+
1438
+ // Mouse up group
1439
+ mouseup: function(evt) {
1440
+ return that.handleMouseUp(evt);
1441
+ },
1442
+ touchend: function(evt) {
1443
+ return that.handleMouseUp(evt);
1444
+ },
1445
+
1446
+ // Mouse move group
1447
+ mousemove: function(evt) {
1448
+ return that.handleMouseMove(evt);
1449
+ },
1450
+ touchmove: function(evt) {
1451
+ return that.handleMouseMove(evt);
1452
+ },
1453
+
1454
+ // Mouse leave group
1455
+ mouseleave: function(evt) {
1456
+ return that.handleMouseUp(evt);
1457
+ },
1458
+ touchleave: function(evt) {
1459
+ return that.handleMouseUp(evt);
1460
+ },
1461
+ touchcancel: function(evt) {
1462
+ return that.handleMouseUp(evt);
1463
+ }
1464
+ };
1465
+
1466
+ // Init custom events handler if available
1467
+ // eslint-disable-next-line eqeqeq
1468
+ if (this.options.customEventsHandler != null) {
1469
+ this.options.customEventsHandler.init({
1470
+ svgElement: this.svg,
1471
+ eventsListenerElement: this.options.eventsListenerElement,
1472
+ instance: this.getPublicInstance()
1473
+ });
1474
+
1475
+ // Custom event handler may halt builtin listeners
1476
+ var haltEventListeners = this.options.customEventsHandler
1477
+ .haltEventListeners;
1478
+ if (haltEventListeners && haltEventListeners.length) {
1479
+ for (var i = haltEventListeners.length - 1; i >= 0; i--) {
1480
+ if (this.eventListeners.hasOwnProperty(haltEventListeners[i])) {
1481
+ delete this.eventListeners[haltEventListeners[i]];
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+
1487
+ // Bind eventListeners
1488
+ for (var event in this.eventListeners) {
1489
+ // Attach event to eventsListenerElement or SVG if not available
1490
+ (this.options.eventsListenerElement || this.svg).addEventListener(
1491
+ event,
1492
+ this.eventListeners[event],
1493
+ !this.options.preventMouseEventsDefault ? passiveListenerOption : false
1494
+ );
1495
+ }
1496
+
1497
+ // Zoom using mouse wheel
1498
+ if (this.options.mouseWheelZoomEnabled) {
1499
+ this.options.mouseWheelZoomEnabled = false; // set to false as enable will set it back to true
1500
+ this.enableMouseWheelZoom();
1501
+ }
1502
+ };
1503
+
1504
+ /**
1505
+ * Enable ability to zoom using mouse wheel
1506
+ */
1507
+ SvgPanZoom.prototype.enableMouseWheelZoom = function() {
1508
+ if (!this.options.mouseWheelZoomEnabled) {
1509
+ var that = this;
1510
+
1511
+ // Mouse wheel listener
1512
+ this.wheelListener = function(evt) {
1513
+ return that.handleMouseWheel(evt);
1514
+ };
1515
+
1516
+ // Bind wheelListener
1517
+ var isPassiveListener = !this.options.preventMouseEventsDefault;
1518
+ Wheel.on(
1519
+ this.options.eventsListenerElement || this.svg,
1520
+ this.wheelListener,
1521
+ isPassiveListener
1522
+ );
1523
+
1524
+ this.options.mouseWheelZoomEnabled = true;
1525
+ }
1526
+ };
1527
+
1528
+ /**
1529
+ * Disable ability to zoom using mouse wheel
1530
+ */
1531
+ SvgPanZoom.prototype.disableMouseWheelZoom = function() {
1532
+ if (this.options.mouseWheelZoomEnabled) {
1533
+ var isPassiveListener = !this.options.preventMouseEventsDefault;
1534
+ Wheel.off(
1535
+ this.options.eventsListenerElement || this.svg,
1536
+ this.wheelListener,
1537
+ isPassiveListener
1538
+ );
1539
+ this.options.mouseWheelZoomEnabled = false;
1540
+ }
1541
+ };
1542
+
1543
+ /**
1544
+ * Handle mouse wheel event
1545
+ *
1546
+ * @param {Event} evt
1547
+ */
1548
+ SvgPanZoom.prototype.handleMouseWheel = function(evt) {
1549
+ if (!this.options.zoomEnabled || this.state !== "none") {
1550
+ return;
1551
+ }
1552
+
1553
+ if (this.options.preventMouseEventsDefault) {
1554
+ if (evt.preventDefault) {
1555
+ evt.preventDefault();
1556
+ } else {
1557
+ evt.returnValue = false;
1558
+ }
1559
+ }
1560
+
1561
+ // Default delta in case that deltaY is not available
1562
+ var delta = evt.deltaY || 1,
1563
+ timeDelta = Date.now() - this.lastMouseWheelEventTime,
1564
+ divider = 3 + Math.max(0, 30 - timeDelta);
1565
+
1566
+ // Update cache
1567
+ this.lastMouseWheelEventTime = Date.now();
1568
+
1569
+ // Make empirical adjustments for browsers that give deltaY in pixels (deltaMode=0)
1570
+ if ("deltaMode" in evt && evt.deltaMode === 0 && evt.wheelDelta) {
1571
+ delta = evt.deltaY === 0 ? 0 : Math.abs(evt.wheelDelta) / evt.deltaY;
1572
+ }
1573
+
1574
+ delta =
1575
+ -0.3 < delta && delta < 0.3
1576
+ ? delta
1577
+ : ((delta > 0 ? 1 : -1) * Math.log(Math.abs(delta) + 10)) / divider;
1578
+
1579
+ var inversedScreenCTM = this.svg.getScreenCTM().inverse(),
1580
+ relativeMousePoint = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
1581
+ inversedScreenCTM
1582
+ ),
1583
+ zoom = Math.pow(1 + this.options.zoomScaleSensitivity, -1 * delta); // multiplying by neg. 1 so as to make zoom in/out behavior match Google maps behavior
1584
+
1585
+ this.zoomAtPoint(zoom, relativeMousePoint);
1586
+ };
1587
+
1588
+ /**
1589
+ * Zoom in at a SVG point
1590
+ *
1591
+ * @param {SVGPoint} point
1592
+ * @param {Float} zoomScale Number representing how much to zoom
1593
+ * @param {Boolean} zoomAbsolute Default false. If true, zoomScale is treated as an absolute value.
1594
+ * Otherwise, zoomScale is treated as a multiplied (e.g. 1.10 would zoom in 10%)
1595
+ */
1596
+ SvgPanZoom.prototype.zoomAtPoint = function(zoomScale, point, zoomAbsolute) {
1597
+ var originalState = this.viewport.getOriginalState();
1598
+
1599
+ if (!zoomAbsolute) {
1600
+ // Fit zoomScale in set bounds
1601
+ if (
1602
+ this.getZoom() * zoomScale <
1603
+ this.options.minZoom * originalState.zoom
1604
+ ) {
1605
+ zoomScale = (this.options.minZoom * originalState.zoom) / this.getZoom();
1606
+ } else if (
1607
+ this.getZoom() * zoomScale >
1608
+ this.options.maxZoom * originalState.zoom
1609
+ ) {
1610
+ zoomScale = (this.options.maxZoom * originalState.zoom) / this.getZoom();
1611
+ }
1612
+ } else {
1613
+ // Fit zoomScale in set bounds
1614
+ zoomScale = Math.max(
1615
+ this.options.minZoom * originalState.zoom,
1616
+ Math.min(this.options.maxZoom * originalState.zoom, zoomScale)
1617
+ );
1618
+ // Find relative scale to achieve desired scale
1619
+ zoomScale = zoomScale / this.getZoom();
1620
+ }
1621
+
1622
+ var oldCTM = this.viewport.getCTM(),
1623
+ relativePoint = point.matrixTransform(oldCTM.inverse()),
1624
+ modifier = this.svg
1625
+ .createSVGMatrix()
1626
+ .translate(relativePoint.x, relativePoint.y)
1627
+ .scale(zoomScale)
1628
+ .translate(-relativePoint.x, -relativePoint.y),
1629
+ newCTM = oldCTM.multiply(modifier);
1630
+
1631
+ if (newCTM.a !== oldCTM.a) {
1632
+ this.viewport.setCTM(newCTM);
1633
+ }
1634
+ };
1635
+
1636
+ /**
1637
+ * Zoom at center point
1638
+ *
1639
+ * @param {Float} scale
1640
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
1641
+ */
1642
+ SvgPanZoom.prototype.zoom = function(scale, absolute) {
1643
+ this.zoomAtPoint(
1644
+ scale,
1645
+ SvgUtils.getSvgCenterPoint(this.svg, this.width, this.height),
1646
+ absolute
1647
+ );
1648
+ };
1649
+
1650
+ /**
1651
+ * Zoom used by public instance
1652
+ *
1653
+ * @param {Float} scale
1654
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
1655
+ */
1656
+ SvgPanZoom.prototype.publicZoom = function(scale, absolute) {
1657
+ if (absolute) {
1658
+ scale = this.computeFromRelativeZoom(scale);
1659
+ }
1660
+
1661
+ this.zoom(scale, absolute);
1662
+ };
1663
+
1664
+ /**
1665
+ * Zoom at point used by public instance
1666
+ *
1667
+ * @param {Float} scale
1668
+ * @param {SVGPoint|Object} point An object that has x and y attributes
1669
+ * @param {Boolean} absolute Marks zoom scale as relative or absolute
1670
+ */
1671
+ SvgPanZoom.prototype.publicZoomAtPoint = function(scale, point, absolute) {
1672
+ if (absolute) {
1673
+ // Transform zoom into a relative value
1674
+ scale = this.computeFromRelativeZoom(scale);
1675
+ }
1676
+
1677
+ // If not a SVGPoint but has x and y then create a SVGPoint
1678
+ if (Utils.getType(point) !== "SVGPoint") {
1679
+ if ("x" in point && "y" in point) {
1680
+ point = SvgUtils.createSVGPoint(this.svg, point.x, point.y);
1681
+ } else {
1682
+ throw new Error("Given point is invalid");
1683
+ }
1684
+ }
1685
+
1686
+ this.zoomAtPoint(scale, point, absolute);
1687
+ };
1688
+
1689
+ /**
1690
+ * Get zoom scale
1691
+ *
1692
+ * @return {Float} zoom scale
1693
+ */
1694
+ SvgPanZoom.prototype.getZoom = function() {
1695
+ return this.viewport.getZoom();
1696
+ };
1697
+
1698
+ /**
1699
+ * Get zoom scale for public usage
1700
+ *
1701
+ * @return {Float} zoom scale
1702
+ */
1703
+ SvgPanZoom.prototype.getRelativeZoom = function() {
1704
+ return this.viewport.getRelativeZoom();
1705
+ };
1706
+
1707
+ /**
1708
+ * Compute actual zoom from public zoom
1709
+ *
1710
+ * @param {Float} zoom
1711
+ * @return {Float} zoom scale
1712
+ */
1713
+ SvgPanZoom.prototype.computeFromRelativeZoom = function(zoom) {
1714
+ return zoom * this.viewport.getOriginalState().zoom;
1715
+ };
1716
+
1717
+ /**
1718
+ * Set zoom to initial state
1719
+ */
1720
+ SvgPanZoom.prototype.resetZoom = function() {
1721
+ var originalState = this.viewport.getOriginalState();
1722
+
1723
+ this.zoom(originalState.zoom, true);
1724
+ };
1725
+
1726
+ /**
1727
+ * Set pan to initial state
1728
+ */
1729
+ SvgPanZoom.prototype.resetPan = function() {
1730
+ this.pan(this.viewport.getOriginalState());
1731
+ };
1732
+
1733
+ /**
1734
+ * Set pan and zoom to initial state
1735
+ */
1736
+ SvgPanZoom.prototype.reset = function() {
1737
+ this.resetZoom();
1738
+ this.resetPan();
1739
+ };
1740
+
1741
+ /**
1742
+ * Handle double click event
1743
+ * See handleMouseDown() for alternate detection method
1744
+ *
1745
+ * @param {Event} evt
1746
+ */
1747
+ SvgPanZoom.prototype.handleDblClick = function(evt) {
1748
+ if (this.options.preventMouseEventsDefault) {
1749
+ if (evt.preventDefault) {
1750
+ evt.preventDefault();
1751
+ } else {
1752
+ evt.returnValue = false;
1753
+ }
1754
+ }
1755
+
1756
+ // Check if target was a control button
1757
+ if (this.options.controlIconsEnabled) {
1758
+ var targetClass = evt.target.getAttribute("class") || "";
1759
+ if (targetClass.indexOf("svg-pan-zoom-control") > -1) {
1760
+ return false;
1761
+ }
1762
+ }
1763
+
1764
+ var zoomFactor;
1765
+
1766
+ if (evt.shiftKey) {
1767
+ zoomFactor = 1 / ((1 + this.options.zoomScaleSensitivity) * 2); // zoom out when shift key pressed
1768
+ } else {
1769
+ zoomFactor = (1 + this.options.zoomScaleSensitivity) * 2;
1770
+ }
1771
+
1772
+ var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
1773
+ this.svg.getScreenCTM().inverse()
1774
+ );
1775
+ this.zoomAtPoint(zoomFactor, point);
1776
+ };
1777
+
1778
+ /**
1779
+ * Handle click event
1780
+ *
1781
+ * @param {Event} evt
1782
+ */
1783
+ SvgPanZoom.prototype.handleMouseDown = function(evt, prevEvt) {
1784
+ if (this.options.preventMouseEventsDefault) {
1785
+ if (evt.preventDefault) {
1786
+ evt.preventDefault();
1787
+ } else {
1788
+ evt.returnValue = false;
1789
+ }
1790
+ }
1791
+
1792
+ Utils.mouseAndTouchNormalize(evt, this.svg);
1793
+
1794
+ // Double click detection; more consistent than ondblclick
1795
+ if (this.options.dblClickZoomEnabled && Utils.isDblClick(evt, prevEvt)) {
1796
+ this.handleDblClick(evt);
1797
+ } else {
1798
+ // Pan mode
1799
+ this.state = "pan";
1800
+ this.firstEventCTM = this.viewport.getCTM();
1801
+ this.stateOrigin = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
1802
+ this.firstEventCTM.inverse()
1803
+ );
1804
+ }
1805
+ };
1806
+
1807
+ /**
1808
+ * Handle mouse move event
1809
+ *
1810
+ * @param {Event} evt
1811
+ */
1812
+ SvgPanZoom.prototype.handleMouseMove = function(evt) {
1813
+ if (this.options.preventMouseEventsDefault) {
1814
+ if (evt.preventDefault) {
1815
+ evt.preventDefault();
1816
+ } else {
1817
+ evt.returnValue = false;
1818
+ }
1819
+ }
1820
+
1821
+ if (this.state === "pan" && this.options.panEnabled) {
1822
+ // Pan mode
1823
+ var point = SvgUtils.getEventPoint(evt, this.svg).matrixTransform(
1824
+ this.firstEventCTM.inverse()
1825
+ ),
1826
+ viewportCTM = this.firstEventCTM.translate(
1827
+ point.x - this.stateOrigin.x,
1828
+ point.y - this.stateOrigin.y
1829
+ );
1830
+
1831
+ this.viewport.setCTM(viewportCTM);
1832
+ }
1833
+ };
1834
+
1835
+ /**
1836
+ * Handle mouse button release event
1837
+ *
1838
+ * @param {Event} evt
1839
+ */
1840
+ SvgPanZoom.prototype.handleMouseUp = function(evt) {
1841
+ if (this.options.preventMouseEventsDefault) {
1842
+ if (evt.preventDefault) {
1843
+ evt.preventDefault();
1844
+ } else {
1845
+ evt.returnValue = false;
1846
+ }
1847
+ }
1848
+
1849
+ if (this.state === "pan") {
1850
+ // Quit pan mode
1851
+ this.state = "none";
1852
+ }
1853
+ };
1854
+
1855
+ /**
1856
+ * Adjust viewport size (only) so it will fit in SVG
1857
+ * Does not center image
1858
+ */
1859
+ SvgPanZoom.prototype.fit = function() {
1860
+ var viewBox = this.viewport.getViewBox(),
1861
+ newScale = Math.min(
1862
+ this.width / viewBox.width,
1863
+ this.height / viewBox.height
1864
+ );
1865
+
1866
+ this.zoom(newScale, true);
1867
+ };
1868
+
1869
+ /**
1870
+ * Adjust viewport size (only) so it will contain the SVG
1871
+ * Does not center image
1872
+ */
1873
+ SvgPanZoom.prototype.contain = function() {
1874
+ var viewBox = this.viewport.getViewBox(),
1875
+ newScale = Math.max(
1876
+ this.width / viewBox.width,
1877
+ this.height / viewBox.height
1878
+ );
1879
+
1880
+ this.zoom(newScale, true);
1881
+ };
1882
+
1883
+ /**
1884
+ * Adjust viewport pan (only) so it will be centered in SVG
1885
+ * Does not zoom/fit/contain image
1886
+ */
1887
+ SvgPanZoom.prototype.center = function() {
1888
+ var viewBox = this.viewport.getViewBox(),
1889
+ offsetX =
1890
+ (this.width - (viewBox.width + viewBox.x * 2) * this.getZoom()) * 0.5,
1891
+ offsetY =
1892
+ (this.height - (viewBox.height + viewBox.y * 2) * this.getZoom()) * 0.5;
1893
+
1894
+ this.getPublicInstance().pan({ x: offsetX, y: offsetY });
1895
+ };
1896
+
1897
+ /**
1898
+ * Update content cached BorderBox
1899
+ * Use when viewport contents change
1900
+ */
1901
+ SvgPanZoom.prototype.updateBBox = function() {
1902
+ this.viewport.simpleViewBoxCache();
1903
+ };
1904
+
1905
+ /**
1906
+ * Pan to a rendered position
1907
+ *
1908
+ * @param {Object} point {x: 0, y: 0}
1909
+ */
1910
+ SvgPanZoom.prototype.pan = function(point) {
1911
+ var viewportCTM = this.viewport.getCTM();
1912
+ viewportCTM.e = point.x;
1913
+ viewportCTM.f = point.y;
1914
+ this.viewport.setCTM(viewportCTM);
1915
+ };
1916
+
1917
+ /**
1918
+ * Relatively pan the graph by a specified rendered position vector
1919
+ *
1920
+ * @param {Object} point {x: 0, y: 0}
1921
+ */
1922
+ SvgPanZoom.prototype.panBy = function(point) {
1923
+ var viewportCTM = this.viewport.getCTM();
1924
+ viewportCTM.e += point.x;
1925
+ viewportCTM.f += point.y;
1926
+ this.viewport.setCTM(viewportCTM);
1927
+ };
1928
+
1929
+ /**
1930
+ * Get pan vector
1931
+ *
1932
+ * @return {Object} {x: 0, y: 0}
1933
+ */
1934
+ SvgPanZoom.prototype.getPan = function() {
1935
+ var state = this.viewport.getState();
1936
+
1937
+ return { x: state.x, y: state.y };
1938
+ };
1939
+
1940
+ /**
1941
+ * Recalculates cached svg dimensions and controls position
1942
+ */
1943
+ SvgPanZoom.prototype.resize = function() {
1944
+ // Get dimensions
1945
+ var boundingClientRectNormalized = SvgUtils.getBoundingClientRectNormalized(
1946
+ this.svg
1947
+ );
1948
+ this.width = boundingClientRectNormalized.width;
1949
+ this.height = boundingClientRectNormalized.height;
1950
+
1951
+ // Recalculate original state
1952
+ var viewport = this.viewport;
1953
+ viewport.options.width = this.width;
1954
+ viewport.options.height = this.height;
1955
+ viewport.processCTM();
1956
+
1957
+ // Reposition control icons by re-enabling them
1958
+ if (this.options.controlIconsEnabled) {
1959
+ this.getPublicInstance().disableControlIcons();
1960
+ this.getPublicInstance().enableControlIcons();
1961
+ }
1962
+ };
1963
+
1964
+ /**
1965
+ * Unbind mouse events, free callbacks and destroy public instance
1966
+ */
1967
+ SvgPanZoom.prototype.destroy = function() {
1968
+ var that = this;
1969
+
1970
+ // Free callbacks
1971
+ this.beforeZoom = null;
1972
+ this.onZoom = null;
1973
+ this.beforePan = null;
1974
+ this.onPan = null;
1975
+ this.onUpdatedCTM = null;
1976
+
1977
+ // Destroy custom event handlers
1978
+ // eslint-disable-next-line eqeqeq
1979
+ if (this.options.customEventsHandler != null) {
1980
+ this.options.customEventsHandler.destroy({
1981
+ svgElement: this.svg,
1982
+ eventsListenerElement: this.options.eventsListenerElement,
1983
+ instance: this.getPublicInstance()
1984
+ });
1985
+ }
1986
+
1987
+ // Unbind eventListeners
1988
+ for (var event in this.eventListeners) {
1989
+ (this.options.eventsListenerElement || this.svg).removeEventListener(
1990
+ event,
1991
+ this.eventListeners[event],
1992
+ !this.options.preventMouseEventsDefault ? passiveListenerOption : false
1993
+ );
1994
+ }
1995
+
1996
+ // Unbind wheelListener
1997
+ this.disableMouseWheelZoom();
1998
+
1999
+ // Remove control icons
2000
+ this.getPublicInstance().disableControlIcons();
2001
+
2002
+ // Reset zoom and pan
2003
+ this.reset();
2004
+
2005
+ // Remove instance from instancesStore
2006
+ instancesStore = instancesStore.filter(function(instance) {
2007
+ return instance.svg !== that.svg;
2008
+ });
2009
+
2010
+ // Delete options and its contents
2011
+ delete this.options;
2012
+
2013
+ // Delete viewport to make public shadow viewport functions uncallable
2014
+ delete this.viewport;
2015
+
2016
+ // Destroy public instance and rewrite getPublicInstance
2017
+ delete this.publicInstance;
2018
+ delete this.pi;
2019
+ this.getPublicInstance = function() {
2020
+ return null;
2021
+ };
2022
+ };
2023
+
2024
+ /**
2025
+ * Returns a public instance object
2026
+ *
2027
+ * @return {Object} Public instance object
2028
+ */
2029
+ SvgPanZoom.prototype.getPublicInstance = function() {
2030
+ var that = this;
2031
+
2032
+ // Create cache
2033
+ if (!this.publicInstance) {
2034
+ this.publicInstance = this.pi = {
2035
+ // Pan
2036
+ enablePan: function() {
2037
+ that.options.panEnabled = true;
2038
+ return that.pi;
2039
+ },
2040
+ disablePan: function() {
2041
+ that.options.panEnabled = false;
2042
+ return that.pi;
2043
+ },
2044
+ isPanEnabled: function() {
2045
+ return !!that.options.panEnabled;
2046
+ },
2047
+ pan: function(point) {
2048
+ that.pan(point);
2049
+ return that.pi;
2050
+ },
2051
+ panBy: function(point) {
2052
+ that.panBy(point);
2053
+ return that.pi;
2054
+ },
2055
+ getPan: function() {
2056
+ return that.getPan();
2057
+ },
2058
+ // Pan event
2059
+ setBeforePan: function(fn) {
2060
+ that.options.beforePan =
2061
+ fn === null ? null : Utils.proxy(fn, that.publicInstance);
2062
+ return that.pi;
2063
+ },
2064
+ setOnPan: function(fn) {
2065
+ that.options.onPan =
2066
+ fn === null ? null : Utils.proxy(fn, that.publicInstance);
2067
+ return that.pi;
2068
+ },
2069
+ // Zoom and Control Icons
2070
+ enableZoom: function() {
2071
+ that.options.zoomEnabled = true;
2072
+ return that.pi;
2073
+ },
2074
+ disableZoom: function() {
2075
+ that.options.zoomEnabled = false;
2076
+ return that.pi;
2077
+ },
2078
+ isZoomEnabled: function() {
2079
+ return !!that.options.zoomEnabled;
2080
+ },
2081
+ enableControlIcons: function() {
2082
+ if (!that.options.controlIconsEnabled) {
2083
+ that.options.controlIconsEnabled = true;
2084
+ ControlIcons.enable(that);
2085
+ }
2086
+ return that.pi;
2087
+ },
2088
+ disableControlIcons: function() {
2089
+ if (that.options.controlIconsEnabled) {
2090
+ that.options.controlIconsEnabled = false;
2091
+ ControlIcons.disable(that);
2092
+ }
2093
+ return that.pi;
2094
+ },
2095
+ isControlIconsEnabled: function() {
2096
+ return !!that.options.controlIconsEnabled;
2097
+ },
2098
+ // Double click zoom
2099
+ enableDblClickZoom: function() {
2100
+ that.options.dblClickZoomEnabled = true;
2101
+ return that.pi;
2102
+ },
2103
+ disableDblClickZoom: function() {
2104
+ that.options.dblClickZoomEnabled = false;
2105
+ return that.pi;
2106
+ },
2107
+ isDblClickZoomEnabled: function() {
2108
+ return !!that.options.dblClickZoomEnabled;
2109
+ },
2110
+ // Mouse wheel zoom
2111
+ enableMouseWheelZoom: function() {
2112
+ that.enableMouseWheelZoom();
2113
+ return that.pi;
2114
+ },
2115
+ disableMouseWheelZoom: function() {
2116
+ that.disableMouseWheelZoom();
2117
+ return that.pi;
2118
+ },
2119
+ isMouseWheelZoomEnabled: function() {
2120
+ return !!that.options.mouseWheelZoomEnabled;
2121
+ },
2122
+ // Zoom scale and bounds
2123
+ setZoomScaleSensitivity: function(scale) {
2124
+ that.options.zoomScaleSensitivity = scale;
2125
+ return that.pi;
2126
+ },
2127
+ setMinZoom: function(zoom) {
2128
+ that.options.minZoom = zoom;
2129
+ return that.pi;
2130
+ },
2131
+ setMaxZoom: function(zoom) {
2132
+ that.options.maxZoom = zoom;
2133
+ return that.pi;
2134
+ },
2135
+ // Zoom event
2136
+ setBeforeZoom: function(fn) {
2137
+ that.options.beforeZoom =
2138
+ fn === null ? null : Utils.proxy(fn, that.publicInstance);
2139
+ return that.pi;
2140
+ },
2141
+ setOnZoom: function(fn) {
2142
+ that.options.onZoom =
2143
+ fn === null ? null : Utils.proxy(fn, that.publicInstance);
2144
+ return that.pi;
2145
+ },
2146
+ // Zooming
2147
+ zoom: function(scale) {
2148
+ that.publicZoom(scale, true);
2149
+ return that.pi;
2150
+ },
2151
+ zoomBy: function(scale) {
2152
+ that.publicZoom(scale, false);
2153
+ return that.pi;
2154
+ },
2155
+ zoomAtPoint: function(scale, point) {
2156
+ that.publicZoomAtPoint(scale, point, true);
2157
+ return that.pi;
2158
+ },
2159
+ zoomAtPointBy: function(scale, point) {
2160
+ that.publicZoomAtPoint(scale, point, false);
2161
+ return that.pi;
2162
+ },
2163
+ zoomIn: function() {
2164
+ this.zoomBy(1 + that.options.zoomScaleSensitivity);
2165
+ return that.pi;
2166
+ },
2167
+ zoomOut: function() {
2168
+ this.zoomBy(1 / (1 + that.options.zoomScaleSensitivity));
2169
+ return that.pi;
2170
+ },
2171
+ getZoom: function() {
2172
+ return that.getRelativeZoom();
2173
+ },
2174
+ // CTM update
2175
+ setOnUpdatedCTM: function(fn) {
2176
+ that.options.onUpdatedCTM =
2177
+ fn === null ? null : Utils.proxy(fn, that.publicInstance);
2178
+ return that.pi;
2179
+ },
2180
+ // Reset
2181
+ resetZoom: function() {
2182
+ that.resetZoom();
2183
+ return that.pi;
2184
+ },
2185
+ resetPan: function() {
2186
+ that.resetPan();
2187
+ return that.pi;
2188
+ },
2189
+ reset: function() {
2190
+ that.reset();
2191
+ return that.pi;
2192
+ },
2193
+ // Fit, Contain and Center
2194
+ fit: function() {
2195
+ that.fit();
2196
+ return that.pi;
2197
+ },
2198
+ contain: function() {
2199
+ that.contain();
2200
+ return that.pi;
2201
+ },
2202
+ center: function() {
2203
+ that.center();
2204
+ return that.pi;
2205
+ },
2206
+ // Size and Resize
2207
+ updateBBox: function() {
2208
+ that.updateBBox();
2209
+ return that.pi;
2210
+ },
2211
+ resize: function() {
2212
+ that.resize();
2213
+ return that.pi;
2214
+ },
2215
+ getSizes: function() {
2216
+ return {
2217
+ width: that.width,
2218
+ height: that.height,
2219
+ realZoom: that.getZoom(),
2220
+ viewBox: that.viewport.getViewBox()
2221
+ };
2222
+ },
2223
+ // Destroy
2224
+ destroy: function() {
2225
+ that.destroy();
2226
+ return that.pi;
2227
+ }
2228
+ };
2229
+ }
2230
+
2231
+ return this.publicInstance;
2232
+ };
2233
+
2234
+ /**
2235
+ * Stores pairs of instances of SvgPanZoom and SVG
2236
+ * Each pair is represented by an object {svg: SVGSVGElement, instance: SvgPanZoom}
2237
+ *
2238
+ * @type {Array}
2239
+ */
2240
+ var instancesStore = [];
2241
+
2242
+ var svgPanZoom = function(elementOrSelector, options) {
2243
+ var svg = Utils.getSvg(elementOrSelector);
2244
+
2245
+ if (svg === null) {
2246
+ return null;
2247
+ } else {
2248
+ // Look for existent instance
2249
+ for (var i = instancesStore.length - 1; i >= 0; i--) {
2250
+ if (instancesStore[i].svg === svg) {
2251
+ return instancesStore[i].instance.getPublicInstance();
2252
+ }
2253
+ }
2254
+
2255
+ // If instance not found - create one
2256
+ instancesStore.push({
2257
+ svg: svg,
2258
+ instance: new SvgPanZoom(svg, options)
2259
+ });
2260
+
2261
+ // Return just pushed instance
2262
+ return instancesStore[
2263
+ instancesStore.length - 1
2264
+ ].instance.getPublicInstance();
2265
+ }
2266
+ };
2267
+
2268
+ svgPanZoom_1 = svgPanZoom;
2269
+ return svgPanZoom_1;
2270
+ }
2271
+
2272
+ var svgPanZoomExports = requireSvgPanZoom();
2273
+ var svgPanZoom = /*@__PURE__*/getDefaultExportFromCjs(svgPanZoomExports);
2
2274
 
3
2275
  class svgMap {
4
2276
  constructor(options = {}) {
@@ -952,56 +3224,117 @@ class svgMap {
952
3224
 
953
3225
  this.mapImage.appendChild(countryElement);
954
3226
 
955
- // Tooltip events
956
3227
  // Add tooltip when touch is used
3228
+ function handlePointerMove(e) {
3229
+ if (e.pointerType === 'touch') return;
3230
+
3231
+ const target = document.elementFromPoint(e.clientX, e.clientY);
3232
+
3233
+ if (
3234
+ !target ||
3235
+ (!target.closest('.svgMap-country') &&
3236
+ !target.closest('.svgMap-tooltip'))
3237
+ ) {
3238
+ this.hideTooltip();
3239
+ document
3240
+ .querySelectorAll('.svgMap-active')
3241
+ .forEach(el => el.classList.remove('svgMap-active'));
3242
+ }
3243
+ }
3244
+
3245
+ const handlePointerMoveBound = handlePointerMove.bind(this);
3246
+
957
3247
  countryElement.addEventListener(
958
- 'touchstart',
3248
+ 'pointerenter',
959
3249
  function (e) {
960
- var activeCountries = document.querySelectorAll('.svgMap-active');
961
- activeCountries.forEach(function (element) {
962
- element.classList.remove('svgMap-active');
963
- });
3250
+ // Only add pointermove listener for non-touch pointers
3251
+ if (e.pointerType !== 'touch') {
3252
+ document.addEventListener(
3253
+ 'pointermove',
3254
+ handlePointerMoveBound,
3255
+ { passive: true }
3256
+ );
3257
+ }
3258
+
3259
+ document
3260
+ .querySelectorAll('.svgMap-active')
3261
+ .forEach(el => el.classList.remove('svgMap-active'));
3262
+
964
3263
  countryElement.parentNode.appendChild(countryElement);
965
3264
  countryElement.classList.add('svgMap-active');
966
3265
 
967
- var countryID = countryElement.getAttribute('data-id');
968
- var countryLink = countryElement.getAttribute('data-link');
969
- if (this.options.touchLink) {
970
- if (countryLink) {
971
- window.location.href = countryLink;
972
- return;
973
- }
974
- }
3266
+ const countryID = countryElement.getAttribute('data-id');
975
3267
  this.setTooltipContent(this.getTooltipContent(countryID));
976
3268
  this.showTooltip(e);
3269
+
3270
+ // For touch, move tooltip to the touch position and keep it there
3271
+ if (e.pointerType === 'touch') {
3272
+ this.moveTooltip(e);
3273
+ }
3274
+ }.bind(this)
3275
+ );
3276
+
3277
+ // Handle touch move - update tooltip position while panning
3278
+ countryElement.addEventListener(
3279
+ 'touchmove',
3280
+ function (e) {
977
3281
  this.moveTooltip(e);
3282
+ }.bind(this),
3283
+ { passive: true }
3284
+ );
978
3285
 
979
- countryElement.addEventListener(
980
- 'touchmove',
981
- this.tooltipMoveEvent,
982
- {
983
- passive: true
984
- }
985
- );
3286
+ // Handle touch end - remove active state and hide tooltip
3287
+ countryElement.addEventListener(
3288
+ 'touchend',
3289
+ function (e) {
3290
+ const touch = e.changedTouches[0];
3291
+ const elementAtEnd = document.elementFromPoint(touch.clientX, touch.clientY);
3292
+
3293
+ // Only hide if touch ended outside the country or tooltip
3294
+ if (
3295
+ !elementAtEnd ||
3296
+ (!elementAtEnd.closest('.svgMap-country') &&
3297
+ !elementAtEnd.closest('.svgMap-tooltip'))
3298
+ ) {
3299
+ this.hideTooltip();
3300
+ document
3301
+ .querySelectorAll('.svgMap-active')
3302
+ .forEach(el => el.classList.remove('svgMap-active'));
3303
+ }
986
3304
  }.bind(this),
987
3305
  { passive: true }
988
3306
  );
989
3307
 
3308
+ // Remove pointermove listener when leaving non-touch pointer
990
3309
  countryElement.addEventListener(
991
- 'mouseenter',
3310
+ 'pointerleave',
992
3311
  function (e) {
993
- countryElement.parentNode.appendChild(countryElement);
994
- countryElement.classList.add('svgMap-active');
995
- var countryID = countryElement.getAttribute('data-id');
996
- this.setTooltipContent(this.getTooltipContent(countryID));
997
- this.showTooltip(e);
998
- countryElement.addEventListener(
999
- 'mousemove',
1000
- this.tooltipMoveEvent,
1001
- {
1002
- passive: true
1003
- }
1004
- );
3312
+ if (e.pointerType !== 'touch') {
3313
+ document.removeEventListener('pointermove', handlePointerMoveBound);
3314
+ this.hideTooltip();
3315
+ document
3316
+ .querySelectorAll('.svgMap-active')
3317
+ .forEach(el => el.classList.remove('svgMap-active'));
3318
+ }
3319
+ }.bind(this)
3320
+ );
3321
+
3322
+ document.addEventListener(
3323
+ 'pointerover',
3324
+ function (e) {
3325
+ if (e.pointerType !== 'touch') return;
3326
+
3327
+ if (
3328
+ e.target.closest('.svgMap-country') ||
3329
+ e.target.closest('.svgMap-tooltip')
3330
+ ) {
3331
+ return;
3332
+ }
3333
+
3334
+ this.hideTooltip();
3335
+ document
3336
+ .querySelectorAll('.svgMap-active')
3337
+ .forEach(el => el.classList.remove('svgMap-active'));
1005
3338
  }.bind(this),
1006
3339
  { passive: true }
1007
3340
  );
@@ -1021,134 +3354,64 @@ class svgMap {
1021
3354
  this.options.data.values[countryID]['linkTarget']
1022
3355
  );
1023
3356
  }
3357
+ }
3358
+ }.bind(this)
3359
+ );
1024
3360
 
1025
- let dragged = false;
1026
- countryElement.addEventListener('mousedown', function () {
1027
- dragged = false;
1028
- });
1029
- countryElement.addEventListener('touchstart', function () {
1030
- dragged = false;
1031
- });
1032
- countryElement.addEventListener('mousemove', function () {
1033
- dragged = true;
1034
- });
1035
- countryElement.addEventListener('touchmove', function () {
1036
- dragged = true;
1037
- });
1038
- const clickHandler = function (e) {
1039
- if (dragged) {
1040
- return;
1041
- }
3361
+ let pointerStart = null;
3362
+ let activeCountry = null;
1042
3363
 
1043
- const link = countryElement.getAttribute('data-link');
1044
- const target = countryElement.getAttribute('data-link-target');
3364
+ this.mapImage.addEventListener('pointerdown', e => {
3365
+ // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3366
+ if (e.button !== 0) return;
1045
3367
 
1046
- if (target) {
1047
- window.open(link, target);
1048
- } else {
1049
- window.location.href = link;
1050
- }
1051
- };
3368
+ pointerStart = { x: e.clientX, y: e.clientY };
3369
+ }, { passive: true });
1052
3370
 
1053
- countryElement.addEventListener('click', clickHandler);
1054
- countryElement.addEventListener('touchend', clickHandler);
1055
- }
3371
+ this.mapImage.addEventListener('pointerup', e => {
3372
+ // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
3373
+ if (e.button !== 0) return;
1056
3374
 
1057
- // Hide tooltip when mouse leaves the country area
1058
- countryElement.addEventListener(
1059
- 'mouseleave',
1060
- function () {
1061
- this.hideTooltip();
1062
- countryElement.classList.remove('svgMap-active');
1063
- countryElement.removeEventListener(
1064
- 'mousemove',
1065
- this.tooltipMoveEvent,
1066
- {
1067
- passive: true
1068
- }
1069
- );
1070
- }.bind(this),
1071
- { passive: true }
1072
- );
3375
+ if (!pointerStart) return;
1073
3376
 
1074
- // Hide tooltip when touch ends
1075
- countryElement.addEventListener(
1076
- 'touchend',
1077
- function () {
1078
- // Do not hide the tooltip, so it stays open on mobile
1079
- }.bind(this),
1080
- { passive: true }
1081
- );
3377
+ const dragThreshold = 5; // pixels
3378
+ const moved =
3379
+ Math.abs(pointerStart.x - e.clientX) > dragThreshold ||
3380
+ Math.abs(pointerStart.y - e.clientY) > dragThreshold;
1082
3381
 
1083
- // Show/hide tooltip on click
1084
- countryElement.addEventListener(
1085
- 'click',
1086
- function (e) {
1087
- e.stopPropagation();
1088
- var countryID = countryElement.getAttribute('data-id');
1089
- if (countryElement.getAttribute('data-tooltip-open') === 'true') {
1090
- this.hideTooltip();
1091
- countryElement.setAttribute('data-tooltip-open', 'false');
1092
- } else {
1093
- this.setTooltipContent(this.getTooltipContent(countryID));
1094
- this.showTooltip(e);
1095
- countryElement.setAttribute('data-tooltip-open', 'true');
1096
- }
1097
- }.bind(this),
1098
- { passive: true }
1099
- );
1100
- }.bind(this)
1101
- );
3382
+ pointerStart = null;
1102
3383
 
1103
- // Hide tooltip on touch outside
1104
- document.addEventListener(
1105
- 'touchend',
1106
- function (e) {
1107
- if (
1108
- e.target.closest('.svgMap-country') ||
1109
- e.target.closest('.svgMap-tooltip')
1110
- ) {
1111
- return;
1112
- }
1113
- this.hideTooltip();
1114
- var openTooltips = document.querySelectorAll(
1115
- '[data-tooltip-open="true"]'
1116
- );
1117
- openTooltips.forEach(function (element) {
1118
- element.setAttribute('data-tooltip-open', 'false');
1119
- });
1120
- var activeCountries = document.querySelectorAll('.svgMap-active');
1121
- activeCountries.forEach(function (element) {
1122
- element.classList.remove('svgMap-active');
1123
- });
1124
- }.bind(this),
1125
- { passive: true }
1126
- );
3384
+ if (moved) return;
1127
3385
 
1128
- // Hide tooltip on click outside
1129
- document.addEventListener(
1130
- 'click',
1131
- function (e) {
1132
- if (
1133
- e.target.closest('.svgMap-country') ||
1134
- e.target.closest('.svgMap-tooltip')
1135
- ) {
1136
- return;
3386
+ const countryElement = e.target.closest('.svgMap-country');
3387
+ if (!countryElement) return;
3388
+
3389
+ const countryID = countryElement.getAttribute('data-id');
3390
+ const link = countryElement.getAttribute('data-link');
3391
+ const target = countryElement.getAttribute('data-link-target');
3392
+ if (!link) return;
3393
+
3394
+ const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
3395
+
3396
+ if (isTouch) {
3397
+ // Touch: only open if already active
3398
+ if (countryElement.classList.contains('svgMap-active')) {
3399
+ if (target) window.open(link, target);
3400
+ else window.location.href = link;
3401
+ } else {
3402
+ // first tap shows tooltip
3403
+ if (activeCountry) activeCountry.classList.remove('svgMap-active');
3404
+ activeCountry = countryElement;
3405
+ countryElement.classList.add('svgMap-active');
3406
+ this.setTooltipContent(this.getTooltipContent(countryID));
3407
+ this.showTooltip(e);
1137
3408
  }
1138
- this.hideTooltip();
1139
- var openTooltips = document.querySelectorAll(
1140
- '[data-tooltip-open="true"]'
1141
- );
1142
- openTooltips.forEach(function (element) {
1143
- element.setAttribute('data-tooltip-open', 'false');
1144
- });
1145
- var activeCountries = document.querySelectorAll('.svgMap-active');
1146
- activeCountries.forEach(function (element) {
1147
- element.classList.remove('svgMap-active');
1148
- });
1149
- }.bind(this),
1150
- { passive: true }
1151
- );
3409
+ } else {
3410
+ // Desktop: open immediately
3411
+ if (target) window.open(link, target);
3412
+ else window.location.href = link;
3413
+ }
3414
+ });
1152
3415
 
1153
3416
  // Expose instance
1154
3417
  var me = this;