svgmap 2.20.0 → 2.21.0

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/README.md CHANGED
@@ -102,6 +102,17 @@ You can pass the following options into svgMap:
102
102
  | `showTooltips` | `boolean` | `true` | When `false`, disables hover and touch-following tooltips only. Persistent on-map labels from `persistentTooltips` are unaffected. On touch devices, countries with a `link` open it on the first tap instead of using the two-tap pattern (first tap preview, second tap navigate). |
103
103
  | `tooltipTrigger` | `'hover'`, `'click'` | `'hover'` | How the floating tooltip opens with the **mouse**: `'hover'` opens on mouseenter/mouseleave.`'click'` opens on primary click and closes when clicking outside the map countries or tooltip. Only applies when `showTooltips` is `true`. |
104
104
  | `persistentTooltips` | `false`, `array`, `function` | `false` | Persistent tooltips fixed on the map when it loads: an array of country IDs, or a function (`function (countryID, countryValues) { … }`) to decide per country. Independent of `showTooltips`. Best used with `showTooltips: false` or `tooltipTrigger: 'click'`. |
105
+ | `staticPins` | `false`, `array`, `function` | `false` | Static pins on the map at load time: an array of [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country IDs (e.g. `['DE', 'FR']`), or a function (`function (countryID, countryValues) { … }`) to decide per country. Pins are placed at the geographic center of each country (largest landmass). Independent of `showTooltips`. |
106
+ | `pinColor` | `string` | `'#000000'` | Default fill color for circle pins. Accepts CSS vars, color names, rgb or hex values. Can be overridden per country via `data.values[id].pinColor`. |
107
+ | `pinStrokeColor` | `string` | `'#ffffff'` | Default stroke color for circle pins. Can be overridden per country via `data.values[id].pinStrokeColor`. |
108
+ | `pinStrokeWidth` | `number` | `1` | Default stroke width for circle pins, in screen pixels (non-scaling stroke). Can be overridden per country via `data.values[id].pinStrokeWidth`. Set to `0` for no stroke. |
109
+ | `pinSize` | `number` | `8` | Default radius for circle pins, in SVG units (viewBox is 2000 × 1001). Can be overridden per country via `data.values[id].pinSize`. |
110
+ | `pinImage` | `string` | | Image URL used as a pin instead of the default circle. Can be overridden per country via `data.values[id].pinImage`. |
111
+ | `pinImageWidth` | `number` | `20` | Width of the pin image in SVG units. Can be overridden per country via `data.values[id].pinImageWidth`. |
112
+ | `pinImageHeight` | `number` | `20` | Height of the pin image in SVG units. Can be overridden per country via `data.values[id].pinImageHeight`. |
113
+ | `pinOffsetX` | `number` | `0` | Horizontal offset from the pin position, in SVG units. Added after auto placement or `pinX`/`pinY`. Can be overridden per country via `data.values[id].pinOffsetX`. |
114
+ | `pinOffsetY` | `number` | `0` | Vertical offset from the pin position, in SVG units. Added after auto placement or `pinX`/`pinY`. Can be overridden per country via `data.values[id].pinOffsetY`. |
115
+ | `onGetPin` | `function` | | Custom pin element. Signature: `function (countryID, countryValues) { return svgElement; }`. Return an SVG element (e.g. `<g>`, `<path>`) to use instead of the default circle or image pin. The library positions it at the pin coordinates via `transform`. Return `null` to fall back to the default pin. |
105
116
  | `onGetTooltip` | `function` | | Called when a tooltip is created to custimize the tooltip content (`function (tooltipDiv, countryID, countryValues) { return 'Custom HTML'; }`) |
106
117
  | `onCountryClick` | `function` | | Called when the user clicks a country (primary button, pointer released without dragging). Signature: `function (countryID, event) { … }`. Use this for custom actions instead of or in addition to `data.values.link`. Return `false` to skip opening the URL when the country has a `link`. On touch devices with a link, the callback runs when the tap would navigate (not on the first tap that only shows the tooltip). Countries show a pointer cursor while this option is set. |
107
118
  | `countries` | `object` | | Additional options specific to countries: |
@@ -117,6 +128,17 @@ You can pass the following options into svgMap:
117
128
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ color` | `string` | | Forces a color for this country |
118
129
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ link` | `string` | | An URL to redirect to when clicking the country |
119
130
  | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ linkTarget` | `string` | | The target of the link. By default the link will be opened in the same tab. Use `'_blank'` to open the link in a new tab |
131
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinColor` | `string` | | Pin fill color for this country (circle pins only) |
132
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinStrokeColor` | `string` | | Pin stroke color for this country (circle pins only) |
133
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinStrokeWidth` | `number` | | Pin stroke width for this country, in screen pixels (circle pins only) |
134
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinSize` | `number` | | Pin radius for this country (circle pins only) |
135
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinX` | `number` | | Pin X position in SVG units; use with `pinY` to override auto placement |
136
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinY` | `number` | | Pin Y position in SVG units; use with `pinX` to override auto placement |
137
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinOffsetX` | `number` | | Horizontal offset from the pin position, in SVG units (after auto placement or `pinX`/`pinY`) |
138
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinOffsetY` | `number` | | Vertical offset from the pin position, in SVG units (after auto placement or `pinX`/`pinY`) |
139
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinImage` | `string` | | Image URL used as the pin for this country |
140
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinImageWidth` | `number` | | Width of the pin image for this country |
141
+ | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`↳ pinImageHeight` | `number` | | Height of the pin image for this country |
120
142
  | `countryNames` | `object` | | An object with the [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code as key and the country name as value |
121
143
  ---
122
144
 
package/dist/index.cjs CHANGED
@@ -2375,7 +2375,34 @@ class svgMap {
2375
2375
  showContinentSelector: false,
2376
2376
 
2377
2377
  // Reset zoom on resize
2378
- resetZoomOnResize: false
2378
+ resetZoomOnResize: false,
2379
+
2380
+ // Static pins: false | string[] | function(countryID, countryValues) => boolean
2381
+ staticPins: false,
2382
+
2383
+ // Default pin fill color
2384
+ pinColor: '#000000',
2385
+
2386
+ // Default pin stroke color and width (circle pins; width is in screen pixels with non-scaling stroke)
2387
+ pinStrokeColor: '#ffffff',
2388
+ pinStrokeWidth: 1.5,
2389
+
2390
+ // Default pin radius in SVG units (viewBox is 2000 × 1001)
2391
+ pinSize: 8,
2392
+
2393
+ // Custom pin element: function(countryID, countryValues) => SVGElement | null
2394
+ onGetPin: null,
2395
+
2396
+ // Image URL to use as a pin instead of the default circle (can also be set per-country via values[id].pinImage)
2397
+ pinImage: null,
2398
+
2399
+ // Width and height of the pin image in SVG units (viewBox is 2000 × 1001)
2400
+ pinImageWidth: 20,
2401
+ pinImageHeight: 20,
2402
+
2403
+ // Offset from computed pin position, in SVG units (added after auto center or pinX/pinY)
2404
+ pinOffsetX: 0,
2405
+ pinOffsetY: 0
2379
2406
  };
2380
2407
 
2381
2408
  this.options = Object.assign({}, defaultOptions, options);
@@ -3227,6 +3254,102 @@ class svgMap {
3227
3254
 
3228
3255
  // Add map elements
3229
3256
  var countryElements = [];
3257
+
3258
+ const clearActive = function clearActive() {
3259
+ this.mapImage
3260
+ .querySelectorAll('.svgMap-active')
3261
+ .forEach((el) => el.classList.remove('svgMap-active'));
3262
+ }.bind(this);
3263
+
3264
+ const isClickTooltip =
3265
+ this.options.showTooltips && this.options.tooltipTrigger === 'click';
3266
+
3267
+ const getCountryFromEvent = function (e) {
3268
+ return e.target && e.target.closest
3269
+ ? e.target.closest('.svgMap-country')
3270
+ : null;
3271
+ };
3272
+
3273
+ const raiseCountry = function (countryElement, setActive) {
3274
+ if (setActive) {
3275
+ clearActive();
3276
+ }
3277
+ countryElement.parentNode.insertBefore(
3278
+ countryElement,
3279
+ this.persistentTooltipGroup || this.pinGroup || null
3280
+ );
3281
+ if (setActive) {
3282
+ countryElement.classList.add('svgMap-active');
3283
+ }
3284
+ }.bind(this);
3285
+
3286
+ const showCountryTooltip = function (countryElement, e, setActive) {
3287
+ raiseCountry(countryElement, setActive);
3288
+ this.setTooltipContent(this.getTooltipContent(countryElement.dataset.id));
3289
+ this.showTooltip(e);
3290
+ }.bind(this);
3291
+
3292
+ // Touch only: preview tooltip on finger down without marking country active
3293
+ // (active is set on pointerup so link countries keep two-tap navigation)
3294
+ this.mapImage.addEventListener(
3295
+ 'pointerdown',
3296
+ (e) => {
3297
+ if (!this.options.showTooltips || e.pointerType !== 'touch') {
3298
+ return;
3299
+ }
3300
+
3301
+ const countryElement = getCountryFromEvent(e);
3302
+ if (!countryElement) {
3303
+ this.hideTooltip();
3304
+ return;
3305
+ }
3306
+
3307
+ showCountryTooltip(countryElement, e, false);
3308
+ this.moveTooltip(e);
3309
+ },
3310
+ { passive: true }
3311
+ );
3312
+
3313
+ this.mapImage.addEventListener(
3314
+ 'pointercancel',
3315
+ (e) => {
3316
+ if (e.pointerType === 'touch') {
3317
+ this.hideTooltip();
3318
+ }
3319
+ },
3320
+ { passive: true }
3321
+ );
3322
+
3323
+ // Hover (mouse/pen) and touch drag: raise country + optional floating tooltip
3324
+ this.mapImage.addEventListener(
3325
+ 'pointermove',
3326
+ (e) => {
3327
+ const countryElement = getCountryFromEvent(e);
3328
+ if (!countryElement) {
3329
+ clearActive();
3330
+ if (this.options.showTooltips) {
3331
+ this.hideTooltip();
3332
+ }
3333
+ return;
3334
+ }
3335
+
3336
+ const mouseClickMode = e.pointerType === 'mouse' && isClickTooltip;
3337
+
3338
+ // Always raise hovered country (SVG paint order + .svgMap-active stroke)
3339
+ if (!this.options.showTooltips || mouseClickMode) {
3340
+ raiseCountry(countryElement, true);
3341
+ return;
3342
+ }
3343
+
3344
+ showCountryTooltip(countryElement, e, true);
3345
+
3346
+ if (e.pointerType === 'touch') {
3347
+ this.moveTooltip(e);
3348
+ }
3349
+ },
3350
+ { passive: true }
3351
+ );
3352
+
3230
3353
  Object.keys(mapPaths).forEach(
3231
3354
  function (countryID) {
3232
3355
  var countryData = this.mapPaths[countryID];
@@ -3244,166 +3367,22 @@ class svgMap {
3244
3367
  'id',
3245
3368
  this.id + '-map-country-' + countryID
3246
3369
  );
3247
- countryElement.setAttribute('data-id', countryID);
3370
+ countryElement.dataset.id = countryID;
3248
3371
  countryElement.classList.add('svgMap-country');
3249
3372
 
3250
3373
  this.mapImage.appendChild(countryElement);
3251
3374
  countryElements.push(countryElement);
3252
3375
 
3253
- // Add tooltip when touch is used
3254
- function handlePointerMove(e) {
3255
- if (e.pointerType === 'touch') return;
3256
-
3257
- const target = document.elementFromPoint(e.clientX, e.clientY);
3258
-
3259
- if (
3260
- !target ||
3261
- (!target.closest('.svgMap-country') &&
3262
- !target.closest('.svgMap-tooltip'))
3263
- ) {
3264
- this.hideTooltip();
3265
- document
3266
- .querySelectorAll('.svgMap-active')
3267
- .forEach((el) => el.classList.remove('svgMap-active'));
3268
- }
3269
- }
3270
-
3271
- const handlePointerMoveBound = handlePointerMove.bind(this);
3272
-
3273
- countryElement.addEventListener(
3274
- 'pointerenter',
3275
- function (e) {
3276
- if (
3277
- e.pointerType === 'mouse' &&
3278
- this.options.showTooltips &&
3279
- this.options.tooltipTrigger === 'click'
3280
- ) {
3281
- return;
3282
- }
3283
-
3284
- // Only add pointermove listener for non-touch pointers
3285
- if (e.pointerType !== 'touch') {
3286
- document.addEventListener('pointermove', handlePointerMoveBound, {
3287
- passive: true
3288
- });
3289
- }
3290
-
3291
- document
3292
- .querySelectorAll('.svgMap-active')
3293
- .forEach((el) => el.classList.remove('svgMap-active'));
3294
-
3295
- countryElement.parentNode.insertBefore(
3296
- countryElement,
3297
- this.persistentTooltipGroup || null
3298
- );
3299
- countryElement.classList.add('svgMap-active');
3300
-
3301
- const countryID = countryElement.getAttribute('data-id');
3302
- if (this.options.showTooltips) {
3303
- this.setTooltipContent(this.getTooltipContent(countryID));
3304
- this.showTooltip(e);
3305
-
3306
- // For touch, move tooltip to the touch position and keep it there
3307
- if (e.pointerType === 'touch') {
3308
- this.moveTooltip(e);
3309
- }
3310
- }
3311
- }.bind(this)
3312
- );
3313
-
3314
- // Handle touch move - update tooltip position while panning
3315
- countryElement.addEventListener(
3316
- 'touchmove',
3317
- function (e) {
3318
- this.moveTooltip(e);
3319
- }.bind(this),
3320
- { passive: true }
3321
- );
3322
-
3323
- // Handle touch end - remove active state and hide tooltip
3324
- countryElement.addEventListener(
3325
- 'touchend',
3326
- function (e) {
3327
- const touch = e.changedTouches[0];
3328
- const elementAtEnd = document.elementFromPoint(
3329
- touch.clientX,
3330
- touch.clientY
3331
- );
3332
-
3333
- // Only hide if touch ended outside the country or tooltip
3334
- if (
3335
- !elementAtEnd ||
3336
- (!elementAtEnd.closest('.svgMap-country') &&
3337
- !elementAtEnd.closest('.svgMap-tooltip'))
3338
- ) {
3339
- this.hideTooltip();
3340
- document
3341
- .querySelectorAll('.svgMap-active')
3342
- .forEach((el) => el.classList.remove('svgMap-active'));
3343
- }
3344
- }.bind(this),
3345
- { passive: true }
3346
- );
3347
-
3348
- // Remove pointermove listener when leaving non-touch pointer
3349
- countryElement.addEventListener(
3350
- 'pointerleave',
3351
- function (e) {
3352
- if (e.pointerType !== 'touch') {
3353
- document.removeEventListener(
3354
- 'pointermove',
3355
- handlePointerMoveBound
3356
- );
3357
- if (
3358
- !(
3359
- e.pointerType === 'mouse' &&
3360
- this.options.showTooltips &&
3361
- this.options.tooltipTrigger === 'click'
3362
- )
3363
- ) {
3364
- this.hideTooltip();
3365
- document
3366
- .querySelectorAll('.svgMap-active')
3367
- .forEach((el) => el.classList.remove('svgMap-active'));
3368
- }
3369
- }
3370
- }.bind(this)
3371
- );
3372
-
3373
- document.addEventListener(
3374
- 'pointerover',
3375
- function (e) {
3376
- if (e.pointerType !== 'touch') return;
3377
-
3378
- if (
3379
- e.target.closest('.svgMap-country') ||
3380
- e.target.closest('.svgMap-tooltip')
3381
- ) {
3382
- return;
3383
- }
3384
-
3385
- this.hideTooltip();
3386
- document
3387
- .querySelectorAll('.svgMap-active')
3388
- .forEach((el) => el.classList.remove('svgMap-active'));
3389
- }.bind(this),
3390
- { passive: true }
3391
- );
3392
-
3393
3376
  if (
3394
3377
  this.options.data.values &&
3395
3378
  this.options.data.values[countryID] &&
3396
3379
  this.options.data.values[countryID]['link']
3397
3380
  ) {
3398
- countryElement.setAttribute(
3399
- 'data-link',
3400
- this.options.data.values[countryID]['link']
3401
- );
3381
+ countryElement.dataset.link =
3382
+ this.options.data.values[countryID]['link'];
3402
3383
  if (this.options.data.values[countryID]['linkTarget']) {
3403
- countryElement.setAttribute(
3404
- 'data-link-target',
3405
- this.options.data.values[countryID]['linkTarget']
3406
- );
3384
+ countryElement.dataset.linkTarget =
3385
+ this.options.data.values[countryID]['linkTarget'];
3407
3386
  }
3408
3387
  }
3409
3388
  }.bind(this)
@@ -3417,6 +3396,10 @@ class svgMap {
3417
3396
  this.createPersistentTooltips(countryElements);
3418
3397
  }
3419
3398
 
3399
+ if (this.options.staticPins) {
3400
+ this.createStaticPins(countryElements);
3401
+ }
3402
+
3420
3403
  let pointerStart = null;
3421
3404
  let activeCountry = null;
3422
3405
 
@@ -3449,21 +3432,18 @@ class svgMap {
3449
3432
  const countryElement = e.target.closest('.svgMap-country');
3450
3433
  if (!countryElement) return;
3451
3434
 
3452
- const countryID = countryElement.getAttribute('data-id');
3453
- const link = countryElement.getAttribute('data-link');
3454
- const linkTarget = countryElement.getAttribute('data-link-target');
3435
+ const countryID = countryElement.dataset.id;
3436
+ const link = countryElement.dataset.link;
3437
+ const linkTarget = countryElement.dataset.linkTarget;
3455
3438
  const hasCallback = typeof this.options.onCountryClick === 'function';
3456
3439
  const hasLink = !!link;
3457
3440
  const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
3458
3441
 
3459
- const isClickTooltipMouse =
3460
- e.pointerType === 'mouse' &&
3461
- this.options.showTooltips &&
3462
- this.options.tooltipTrigger === 'click';
3442
+ const isClickTooltipMouse = e.pointerType === 'mouse' && isClickTooltip;
3463
3443
 
3464
3444
  if (!hasLink && !hasCallback && !isClickTooltipMouse) return;
3465
3445
 
3466
- if (isClickTooltipMouse && this.options.showTooltips) {
3446
+ if (isClickTooltipMouse) {
3467
3447
  const willNavigate =
3468
3448
  hasLink && countryElement.classList.contains('svgMap-active');
3469
3449
  const shouldFireCallback = hasCallback && (!hasLink || willNavigate);
@@ -3479,12 +3459,10 @@ class svgMap {
3479
3459
  if (linkTarget) window.open(link, linkTarget);
3480
3460
  else window.location.href = link;
3481
3461
  } else {
3482
- this.mapImage
3483
- .querySelectorAll('.svgMap-country.svgMap-active')
3484
- .forEach((el) => el.classList.remove('svgMap-active'));
3462
+ clearActive();
3485
3463
  countryElement.parentNode.insertBefore(
3486
3464
  countryElement,
3487
- this.persistentTooltipGroup || null
3465
+ this.persistentTooltipGroup || this.pinGroup || null
3488
3466
  );
3489
3467
  countryElement.classList.add('svgMap-active');
3490
3468
  this.setTooltipContent(this.getTooltipContent(countryID));
@@ -3495,12 +3473,10 @@ class svgMap {
3495
3473
 
3496
3474
  if (callbackResultClick === false) return;
3497
3475
 
3498
- this.mapImage
3499
- .querySelectorAll('.svgMap-country.svgMap-active')
3500
- .forEach((el) => el.classList.remove('svgMap-active'));
3476
+ clearActive();
3501
3477
  countryElement.parentNode.insertBefore(
3502
3478
  countryElement,
3503
- this.persistentTooltipGroup || null
3479
+ this.persistentTooltipGroup || this.pinGroup || null
3504
3480
  );
3505
3481
  countryElement.classList.add('svgMap-active');
3506
3482
  this.setTooltipContent(this.getTooltipContent(countryID));
@@ -3550,15 +3526,9 @@ class svgMap {
3550
3526
  });
3551
3527
 
3552
3528
  this._clickTooltipOutsideHandler = function (ev) {
3553
- if (ev.pointerType !== 'mouse') return;
3554
- if (
3555
- !this.options.showTooltips ||
3556
- this.options.tooltipTrigger !== 'click' ||
3557
- !this.tooltip
3558
- ) {
3529
+ if (!this.tooltip || !this.tooltip.classList.contains('svgMap-active')) {
3559
3530
  return;
3560
3531
  }
3561
- if (!this.tooltip.classList.contains('svgMap-active')) return;
3562
3532
  var node = ev.target;
3563
3533
  if (
3564
3534
  node &&
@@ -3576,11 +3546,6 @@ class svgMap {
3576
3546
  });
3577
3547
  }
3578
3548
  }.bind(this);
3579
- document.addEventListener(
3580
- 'pointerdown',
3581
- this._clickTooltipOutsideHandler,
3582
- true
3583
- );
3584
3549
 
3585
3550
  // Expose instance
3586
3551
  var me = this;
@@ -3663,7 +3628,7 @@ class svgMap {
3663
3628
 
3664
3629
  countryElements.forEach(
3665
3630
  function (countryElement) {
3666
- var countryID = countryElement.getAttribute('data-id');
3631
+ var countryID = countryElement.dataset.id;
3667
3632
  if (!this.shouldShowTooltipOnLoad(countryID)) {
3668
3633
  return;
3669
3634
  }
@@ -3699,6 +3664,157 @@ class svgMap {
3699
3664
  );
3700
3665
  }
3701
3666
 
3667
+ // Create static pins on the map
3668
+
3669
+ createStaticPins(countryElements) {
3670
+ if (this.pinGroup) {
3671
+ this.pinGroup.remove();
3672
+ }
3673
+
3674
+ this.pinGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
3675
+ this.pinGroup.classList.add('svgMap-pin-group');
3676
+ this.mapImage.appendChild(this.pinGroup);
3677
+
3678
+ countryElements.forEach(
3679
+ function (countryElement) {
3680
+ var countryID = countryElement.getAttribute('data-id');
3681
+ if (!this.shouldShowPin(countryID)) {
3682
+ return;
3683
+ }
3684
+
3685
+ var countryValues = this.options.data.values[countryID];
3686
+ var cx, cy;
3687
+
3688
+ if (
3689
+ countryValues &&
3690
+ countryValues.pinX != null &&
3691
+ countryValues.pinY != null
3692
+ ) {
3693
+ cx = countryValues.pinX;
3694
+ cy = countryValues.pinY;
3695
+ } else {
3696
+ // Split the path at absolute M commands and use the largest sub-path
3697
+ // to avoid overseas territories (islands, colonies) skewing the center.
3698
+ var d = countryElement.getAttribute('d');
3699
+ var subPaths = d.split(/(?=M)/).filter((s) => s.trim().length > 0);
3700
+ var largestBB = null;
3701
+ var largestArea = -1;
3702
+
3703
+ subPaths.forEach(
3704
+ function (subPath) {
3705
+ var tmp = document.createElementNS(
3706
+ 'http://www.w3.org/2000/svg',
3707
+ 'path'
3708
+ );
3709
+ tmp.setAttribute('d', subPath);
3710
+ this.mapImage.appendChild(tmp);
3711
+ var bb = tmp.getBBox();
3712
+ var area = bb.width * bb.height;
3713
+ if (area > largestArea) {
3714
+ largestArea = area;
3715
+ largestBB = bb;
3716
+ }
3717
+ this.mapImage.removeChild(tmp);
3718
+ }.bind(this)
3719
+ );
3720
+
3721
+ cx = largestBB.x + largestBB.width / 2;
3722
+ cy = largestBB.y + largestBB.height / 2;
3723
+ }
3724
+
3725
+ var offsetX =
3726
+ countryValues && countryValues.pinOffsetX != null
3727
+ ? countryValues.pinOffsetX
3728
+ : this.options.pinOffsetX;
3729
+ var offsetY =
3730
+ countryValues && countryValues.pinOffsetY != null
3731
+ ? countryValues.pinOffsetY
3732
+ : this.options.pinOffsetY;
3733
+ cx += offsetX;
3734
+ cy += offsetY;
3735
+
3736
+ var color =
3737
+ (countryValues && countryValues.pinColor) || this.options.pinColor;
3738
+ var size =
3739
+ (countryValues && countryValues.pinSize) || this.options.pinSize;
3740
+ var strokeColor =
3741
+ (countryValues && countryValues.pinStrokeColor) ||
3742
+ this.options.pinStrokeColor;
3743
+ var strokeWidth =
3744
+ (countryValues && countryValues.pinStrokeWidth) ||
3745
+ this.options.pinStrokeWidth;
3746
+
3747
+ if (typeof this.options.onGetPin === 'function') {
3748
+ var custom = this.options.onGetPin(countryID, countryValues);
3749
+ if (custom) {
3750
+ custom.setAttribute(
3751
+ 'transform',
3752
+ 'translate(' + cx + ',' + cy + ')'
3753
+ );
3754
+ this.pinGroup.appendChild(custom);
3755
+ return;
3756
+ }
3757
+ }
3758
+
3759
+ var pinImage =
3760
+ (countryValues && countryValues.pinImage) || this.options.pinImage;
3761
+
3762
+ if (pinImage) {
3763
+ var pinW =
3764
+ (countryValues && countryValues.pinImageWidth) ||
3765
+ this.options.pinImageWidth;
3766
+ var pinH =
3767
+ (countryValues && countryValues.pinImageHeight) ||
3768
+ this.options.pinImageHeight;
3769
+ var img = document.createElementNS(
3770
+ 'http://www.w3.org/2000/svg',
3771
+ 'image'
3772
+ );
3773
+ img.setAttribute('href', pinImage);
3774
+ img.setAttribute('x', cx - pinW / 2);
3775
+ img.setAttribute('y', cy - pinH / 2);
3776
+ img.setAttribute('width', pinW);
3777
+ img.setAttribute('height', pinH);
3778
+ img.setAttribute('data-id', countryID);
3779
+ img.classList.add('svgMap-pin');
3780
+ this.pinGroup.appendChild(img);
3781
+ return;
3782
+ }
3783
+
3784
+ var circle = document.createElementNS(
3785
+ 'http://www.w3.org/2000/svg',
3786
+ 'circle'
3787
+ );
3788
+ circle.setAttribute('cx', cx);
3789
+ circle.setAttribute('cy', cy);
3790
+ circle.setAttribute('r', size);
3791
+ circle.setAttribute('fill', color);
3792
+ if (strokeWidth > 0) {
3793
+ circle.setAttribute('stroke', strokeColor);
3794
+ circle.setAttribute('stroke-width', strokeWidth);
3795
+ circle.setAttribute('vector-effect', 'non-scaling-stroke');
3796
+ }
3797
+ circle.setAttribute('data-id', countryID);
3798
+ circle.classList.add('svgMap-pin');
3799
+ this.pinGroup.appendChild(circle);
3800
+ }.bind(this)
3801
+ );
3802
+ }
3803
+
3804
+ // Check if a static pin should be shown for a country
3805
+
3806
+ shouldShowPin(countryID) {
3807
+ var pins = this.options.staticPins;
3808
+ var countryValues = this.options.data.values[countryID];
3809
+ if (Array.isArray(pins)) {
3810
+ return pins.indexOf(countryID) !== -1;
3811
+ }
3812
+ if (typeof pins === 'function') {
3813
+ return pins(countryID, countryValues);
3814
+ }
3815
+ return false;
3816
+ }
3817
+
3702
3818
  // Check if a persistent tooltip should be shown on load
3703
3819
 
3704
3820
  shouldShowTooltipOnLoad(countryID) {
@@ -4708,6 +4824,23 @@ class svgMap {
4708
4824
  return;
4709
4825
  }
4710
4826
  this.tooltip.classList.add('svgMap-active');
4827
+
4828
+ if (
4829
+ this.options.showTooltips &&
4830
+ this.options.tooltipTrigger === 'click' &&
4831
+ e.pointerType === 'mouse'
4832
+ ) {
4833
+ // don't register event listener in the same frame
4834
+ // to prevent it from being triggered immediately
4835
+ requestAnimationFrame(() => {
4836
+ document.addEventListener(
4837
+ 'pointerdown',
4838
+ this._clickTooltipOutsideHandler,
4839
+ { once: true, passive: true }
4840
+ );
4841
+ });
4842
+ }
4843
+
4711
4844
  this.moveTooltip(e);
4712
4845
  }
4713
4846
 
@@ -4717,7 +4850,12 @@ class svgMap {
4717
4850
  if (!this.tooltip) {
4718
4851
  return;
4719
4852
  }
4853
+
4720
4854
  this.tooltip.classList.remove('svgMap-active');
4855
+ document.removeEventListener(
4856
+ 'pointerdown',
4857
+ this._clickTooltipOutsideHandler
4858
+ );
4721
4859
  }
4722
4860
 
4723
4861
  // Move the tooltip