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