svgmap 2.20.0 → 2.20.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svgmap",
3
3
  "description": "svgMap is a JavaScript library that lets you easily create an interactable world map comparing customizable data for each country.",
4
- "version": "2.20.0",
4
+ "version": "2.20.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -43,12 +43,12 @@
43
43
  "prepublishOnly": "npm run build && node test/assets.js"
44
44
  },
45
45
  "devDependencies": {
46
- "@rollup/plugin-commonjs": "^29.0.2",
46
+ "@rollup/plugin-commonjs": "^29.0.3",
47
47
  "@rollup/plugin-node-resolve": "^16.0.3",
48
48
  "@rollup/plugin-terser": "^1.0.0",
49
49
  "csso": "^5.0.5",
50
- "rollup": "^4.60.2",
50
+ "rollup": "^4.61.0",
51
51
  "rollup-plugin-postcss": "^4.0.2",
52
- "sass": "^1.99.0"
52
+ "sass": "^1.100.0"
53
53
  }
54
54
  }
@@ -953,6 +953,102 @@ export default class svgMap {
953
953
 
954
954
  // Add map elements
955
955
  var countryElements = [];
956
+
957
+ const clearActive = function clearActive() {
958
+ this.mapImage
959
+ .querySelectorAll('.svgMap-active')
960
+ .forEach((el) => el.classList.remove('svgMap-active'));
961
+ }.bind(this);
962
+
963
+ const isClickTooltip =
964
+ this.options.showTooltips && this.options.tooltipTrigger === 'click';
965
+
966
+ const getCountryFromEvent = function (e) {
967
+ return e.target && e.target.closest
968
+ ? e.target.closest('.svgMap-country')
969
+ : null;
970
+ };
971
+
972
+ const raiseCountry = function (countryElement, setActive) {
973
+ if (setActive) {
974
+ clearActive();
975
+ }
976
+ countryElement.parentNode.insertBefore(
977
+ countryElement,
978
+ this.persistentTooltipGroup || null
979
+ );
980
+ if (setActive) {
981
+ countryElement.classList.add('svgMap-active');
982
+ }
983
+ }.bind(this);
984
+
985
+ const showCountryTooltip = function (countryElement, e, setActive) {
986
+ raiseCountry(countryElement, setActive);
987
+ this.setTooltipContent(this.getTooltipContent(countryElement.dataset.id));
988
+ this.showTooltip(e);
989
+ }.bind(this);
990
+
991
+ // Touch only: preview tooltip on finger down without marking country active
992
+ // (active is set on pointerup so link countries keep two-tap navigation)
993
+ this.mapImage.addEventListener(
994
+ 'pointerdown',
995
+ (e) => {
996
+ if (!this.options.showTooltips || e.pointerType !== 'touch') {
997
+ return;
998
+ }
999
+
1000
+ const countryElement = getCountryFromEvent(e);
1001
+ if (!countryElement) {
1002
+ this.hideTooltip();
1003
+ return;
1004
+ }
1005
+
1006
+ showCountryTooltip(countryElement, e, false);
1007
+ this.moveTooltip(e);
1008
+ },
1009
+ { passive: true }
1010
+ );
1011
+
1012
+ this.mapImage.addEventListener(
1013
+ 'pointercancel',
1014
+ (e) => {
1015
+ if (e.pointerType === 'touch') {
1016
+ this.hideTooltip();
1017
+ }
1018
+ },
1019
+ { passive: true }
1020
+ );
1021
+
1022
+ // Hover (mouse/pen) and touch drag: raise country + optional floating tooltip
1023
+ this.mapImage.addEventListener(
1024
+ 'pointermove',
1025
+ (e) => {
1026
+ const countryElement = getCountryFromEvent(e);
1027
+ if (!countryElement) {
1028
+ clearActive();
1029
+ if (this.options.showTooltips) {
1030
+ this.hideTooltip();
1031
+ }
1032
+ return;
1033
+ }
1034
+
1035
+ const mouseClickMode = e.pointerType === 'mouse' && isClickTooltip;
1036
+
1037
+ // Always raise hovered country (SVG paint order + .svgMap-active stroke)
1038
+ if (!this.options.showTooltips || mouseClickMode) {
1039
+ raiseCountry(countryElement, true);
1040
+ return;
1041
+ }
1042
+
1043
+ showCountryTooltip(countryElement, e, true);
1044
+
1045
+ if (e.pointerType === 'touch') {
1046
+ this.moveTooltip(e);
1047
+ }
1048
+ },
1049
+ { passive: true }
1050
+ );
1051
+
956
1052
  Object.keys(mapPaths).forEach(
957
1053
  function (countryID) {
958
1054
  var countryData = this.mapPaths[countryID];
@@ -970,166 +1066,22 @@ export default class svgMap {
970
1066
  'id',
971
1067
  this.id + '-map-country-' + countryID
972
1068
  );
973
- countryElement.setAttribute('data-id', countryID);
1069
+ countryElement.dataset.id = countryID;
974
1070
  countryElement.classList.add('svgMap-country');
975
1071
 
976
1072
  this.mapImage.appendChild(countryElement);
977
1073
  countryElements.push(countryElement);
978
1074
 
979
- // Add tooltip when touch is used
980
- function handlePointerMove(e) {
981
- if (e.pointerType === 'touch') return;
982
-
983
- const target = document.elementFromPoint(e.clientX, e.clientY);
984
-
985
- if (
986
- !target ||
987
- (!target.closest('.svgMap-country') &&
988
- !target.closest('.svgMap-tooltip'))
989
- ) {
990
- this.hideTooltip();
991
- document
992
- .querySelectorAll('.svgMap-active')
993
- .forEach((el) => el.classList.remove('svgMap-active'));
994
- }
995
- }
996
-
997
- const handlePointerMoveBound = handlePointerMove.bind(this);
998
-
999
- countryElement.addEventListener(
1000
- 'pointerenter',
1001
- function (e) {
1002
- if (
1003
- e.pointerType === 'mouse' &&
1004
- this.options.showTooltips &&
1005
- this.options.tooltipTrigger === 'click'
1006
- ) {
1007
- return;
1008
- }
1009
-
1010
- // Only add pointermove listener for non-touch pointers
1011
- if (e.pointerType !== 'touch') {
1012
- document.addEventListener('pointermove', handlePointerMoveBound, {
1013
- passive: true
1014
- });
1015
- }
1016
-
1017
- document
1018
- .querySelectorAll('.svgMap-active')
1019
- .forEach((el) => el.classList.remove('svgMap-active'));
1020
-
1021
- countryElement.parentNode.insertBefore(
1022
- countryElement,
1023
- this.persistentTooltipGroup || null
1024
- );
1025
- countryElement.classList.add('svgMap-active');
1026
-
1027
- const countryID = countryElement.getAttribute('data-id');
1028
- if (this.options.showTooltips) {
1029
- this.setTooltipContent(this.getTooltipContent(countryID));
1030
- this.showTooltip(e);
1031
-
1032
- // For touch, move tooltip to the touch position and keep it there
1033
- if (e.pointerType === 'touch') {
1034
- this.moveTooltip(e);
1035
- }
1036
- }
1037
- }.bind(this)
1038
- );
1039
-
1040
- // Handle touch move - update tooltip position while panning
1041
- countryElement.addEventListener(
1042
- 'touchmove',
1043
- function (e) {
1044
- this.moveTooltip(e);
1045
- }.bind(this),
1046
- { passive: true }
1047
- );
1048
-
1049
- // Handle touch end - remove active state and hide tooltip
1050
- countryElement.addEventListener(
1051
- 'touchend',
1052
- function (e) {
1053
- const touch = e.changedTouches[0];
1054
- const elementAtEnd = document.elementFromPoint(
1055
- touch.clientX,
1056
- touch.clientY
1057
- );
1058
-
1059
- // Only hide if touch ended outside the country or tooltip
1060
- if (
1061
- !elementAtEnd ||
1062
- (!elementAtEnd.closest('.svgMap-country') &&
1063
- !elementAtEnd.closest('.svgMap-tooltip'))
1064
- ) {
1065
- this.hideTooltip();
1066
- document
1067
- .querySelectorAll('.svgMap-active')
1068
- .forEach((el) => el.classList.remove('svgMap-active'));
1069
- }
1070
- }.bind(this),
1071
- { passive: true }
1072
- );
1073
-
1074
- // Remove pointermove listener when leaving non-touch pointer
1075
- countryElement.addEventListener(
1076
- 'pointerleave',
1077
- function (e) {
1078
- if (e.pointerType !== 'touch') {
1079
- document.removeEventListener(
1080
- 'pointermove',
1081
- handlePointerMoveBound
1082
- );
1083
- if (
1084
- !(
1085
- e.pointerType === 'mouse' &&
1086
- this.options.showTooltips &&
1087
- this.options.tooltipTrigger === 'click'
1088
- )
1089
- ) {
1090
- this.hideTooltip();
1091
- document
1092
- .querySelectorAll('.svgMap-active')
1093
- .forEach((el) => el.classList.remove('svgMap-active'));
1094
- }
1095
- }
1096
- }.bind(this)
1097
- );
1098
-
1099
- document.addEventListener(
1100
- 'pointerover',
1101
- function (e) {
1102
- if (e.pointerType !== 'touch') return;
1103
-
1104
- if (
1105
- e.target.closest('.svgMap-country') ||
1106
- e.target.closest('.svgMap-tooltip')
1107
- ) {
1108
- return;
1109
- }
1110
-
1111
- this.hideTooltip();
1112
- document
1113
- .querySelectorAll('.svgMap-active')
1114
- .forEach((el) => el.classList.remove('svgMap-active'));
1115
- }.bind(this),
1116
- { passive: true }
1117
- );
1118
-
1119
1075
  if (
1120
1076
  this.options.data.values &&
1121
1077
  this.options.data.values[countryID] &&
1122
1078
  this.options.data.values[countryID]['link']
1123
1079
  ) {
1124
- countryElement.setAttribute(
1125
- 'data-link',
1126
- this.options.data.values[countryID]['link']
1127
- );
1080
+ countryElement.dataset.link =
1081
+ this.options.data.values[countryID]['link'];
1128
1082
  if (this.options.data.values[countryID]['linkTarget']) {
1129
- countryElement.setAttribute(
1130
- 'data-link-target',
1131
- this.options.data.values[countryID]['linkTarget']
1132
- );
1083
+ countryElement.dataset.linkTarget =
1084
+ this.options.data.values[countryID]['linkTarget'];
1133
1085
  }
1134
1086
  }
1135
1087
  }.bind(this)
@@ -1175,21 +1127,18 @@ export default class svgMap {
1175
1127
  const countryElement = e.target.closest('.svgMap-country');
1176
1128
  if (!countryElement) return;
1177
1129
 
1178
- const countryID = countryElement.getAttribute('data-id');
1179
- const link = countryElement.getAttribute('data-link');
1180
- const linkTarget = countryElement.getAttribute('data-link-target');
1130
+ const countryID = countryElement.dataset.id;
1131
+ const link = countryElement.dataset.link;
1132
+ const linkTarget = countryElement.dataset.linkTarget;
1181
1133
  const hasCallback = typeof this.options.onCountryClick === 'function';
1182
1134
  const hasLink = !!link;
1183
1135
  const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
1184
1136
 
1185
- const isClickTooltipMouse =
1186
- e.pointerType === 'mouse' &&
1187
- this.options.showTooltips &&
1188
- this.options.tooltipTrigger === 'click';
1137
+ const isClickTooltipMouse = e.pointerType === 'mouse' && isClickTooltip;
1189
1138
 
1190
1139
  if (!hasLink && !hasCallback && !isClickTooltipMouse) return;
1191
1140
 
1192
- if (isClickTooltipMouse && this.options.showTooltips) {
1141
+ if (isClickTooltipMouse) {
1193
1142
  const willNavigate =
1194
1143
  hasLink && countryElement.classList.contains('svgMap-active');
1195
1144
  const shouldFireCallback = hasCallback && (!hasLink || willNavigate);
@@ -1205,9 +1154,7 @@ export default class svgMap {
1205
1154
  if (linkTarget) window.open(link, linkTarget);
1206
1155
  else window.location.href = link;
1207
1156
  } else {
1208
- this.mapImage
1209
- .querySelectorAll('.svgMap-country.svgMap-active')
1210
- .forEach((el) => el.classList.remove('svgMap-active'));
1157
+ clearActive();
1211
1158
  countryElement.parentNode.insertBefore(
1212
1159
  countryElement,
1213
1160
  this.persistentTooltipGroup || null
@@ -1221,9 +1168,7 @@ export default class svgMap {
1221
1168
 
1222
1169
  if (callbackResultClick === false) return;
1223
1170
 
1224
- this.mapImage
1225
- .querySelectorAll('.svgMap-country.svgMap-active')
1226
- .forEach((el) => el.classList.remove('svgMap-active'));
1171
+ clearActive();
1227
1172
  countryElement.parentNode.insertBefore(
1228
1173
  countryElement,
1229
1174
  this.persistentTooltipGroup || null
@@ -1276,15 +1221,9 @@ export default class svgMap {
1276
1221
  });
1277
1222
 
1278
1223
  this._clickTooltipOutsideHandler = function (ev) {
1279
- if (ev.pointerType !== 'mouse') return;
1280
- if (
1281
- !this.options.showTooltips ||
1282
- this.options.tooltipTrigger !== 'click' ||
1283
- !this.tooltip
1284
- ) {
1224
+ if (!this.tooltip || !this.tooltip.classList.contains('svgMap-active')) {
1285
1225
  return;
1286
1226
  }
1287
- if (!this.tooltip.classList.contains('svgMap-active')) return;
1288
1227
  var node = ev.target;
1289
1228
  if (
1290
1229
  node &&
@@ -1302,11 +1241,6 @@ export default class svgMap {
1302
1241
  });
1303
1242
  }
1304
1243
  }.bind(this);
1305
- document.addEventListener(
1306
- 'pointerdown',
1307
- this._clickTooltipOutsideHandler,
1308
- true
1309
- );
1310
1244
 
1311
1245
  // Expose instance
1312
1246
  var me = this;
@@ -1389,7 +1323,7 @@ export default class svgMap {
1389
1323
 
1390
1324
  countryElements.forEach(
1391
1325
  function (countryElement) {
1392
- var countryID = countryElement.getAttribute('data-id');
1326
+ var countryID = countryElement.dataset.id;
1393
1327
  if (!this.shouldShowTooltipOnLoad(countryID)) {
1394
1328
  return;
1395
1329
  }
@@ -2434,6 +2368,23 @@ export default class svgMap {
2434
2368
  return;
2435
2369
  }
2436
2370
  this.tooltip.classList.add('svgMap-active');
2371
+
2372
+ if (
2373
+ this.options.showTooltips &&
2374
+ this.options.tooltipTrigger === 'click' &&
2375
+ e.pointerType === 'mouse'
2376
+ ) {
2377
+ // don't register event listener in the same frame
2378
+ // to prevent it from being triggered immediately
2379
+ requestAnimationFrame(() => {
2380
+ document.addEventListener(
2381
+ 'pointerdown',
2382
+ this._clickTooltipOutsideHandler,
2383
+ { once: true, passive: true }
2384
+ );
2385
+ });
2386
+ }
2387
+
2437
2388
  this.moveTooltip(e);
2438
2389
  }
2439
2390
 
@@ -2443,7 +2394,12 @@ export default class svgMap {
2443
2394
  if (!this.tooltip) {
2444
2395
  return;
2445
2396
  }
2397
+
2446
2398
  this.tooltip.classList.remove('svgMap-active');
2399
+ document.removeEventListener(
2400
+ 'pointerdown',
2401
+ this._clickTooltipOutsideHandler
2402
+ );
2447
2403
  }
2448
2404
 
2449
2405
  // Move the tooltip