svgmap 2.18.3 → 2.19.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.18.3",
4
+ "version": "2.19.1",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -41,11 +41,12 @@
41
41
  "prepublishOnly": "npm run build && node test/assets.js"
42
42
  },
43
43
  "devDependencies": {
44
+ "@rollup/plugin-commonjs": "^29.0.0",
44
45
  "@rollup/plugin-node-resolve": "^16.0.3",
45
46
  "@rollup/plugin-terser": "^0.4.4",
46
47
  "csso": "^5.0.5",
47
- "rollup": "^4.54.0",
48
+ "rollup": "^4.57.1",
48
49
  "rollup-plugin-postcss": "^4.0.2",
49
- "sass": "^1.97.1"
50
+ "sass": "^1.97.3"
50
51
  }
51
52
  }
@@ -1,4 +1,4 @@
1
- const svgPanZoom = require('svg-pan-zoom');
1
+ import svgPanZoom from 'svg-pan-zoom';
2
2
 
3
3
  export default class svgMap {
4
4
  constructor(options = {}) {
@@ -952,56 +952,117 @@ export default class svgMap {
952
952
 
953
953
  this.mapImage.appendChild(countryElement);
954
954
 
955
- // Tooltip events
956
955
  // Add tooltip when touch is used
956
+ function handlePointerMove(e) {
957
+ if (e.pointerType === 'touch') return;
958
+
959
+ const target = document.elementFromPoint(e.clientX, e.clientY);
960
+
961
+ if (
962
+ !target ||
963
+ (!target.closest('.svgMap-country') &&
964
+ !target.closest('.svgMap-tooltip'))
965
+ ) {
966
+ this.hideTooltip();
967
+ document
968
+ .querySelectorAll('.svgMap-active')
969
+ .forEach(el => el.classList.remove('svgMap-active'));
970
+ }
971
+ }
972
+
973
+ const handlePointerMoveBound = handlePointerMove.bind(this);
974
+
957
975
  countryElement.addEventListener(
958
- 'touchstart',
976
+ 'pointerenter',
959
977
  function (e) {
960
- var activeCountries = document.querySelectorAll('.svgMap-active');
961
- activeCountries.forEach(function (element) {
962
- element.classList.remove('svgMap-active');
963
- });
978
+ // Only add pointermove listener for non-touch pointers
979
+ if (e.pointerType !== 'touch') {
980
+ document.addEventListener(
981
+ 'pointermove',
982
+ handlePointerMoveBound,
983
+ { passive: true }
984
+ );
985
+ }
986
+
987
+ document
988
+ .querySelectorAll('.svgMap-active')
989
+ .forEach(el => el.classList.remove('svgMap-active'));
990
+
964
991
  countryElement.parentNode.appendChild(countryElement);
965
992
  countryElement.classList.add('svgMap-active');
966
993
 
967
- var countryID = countryElement.getAttribute('data-id');
968
- var countryLink = countryElement.getAttribute('data-link');
969
- if (this.options.touchLink) {
970
- if (countryLink) {
971
- window.location.href = countryLink;
972
- return;
973
- }
974
- }
994
+ const countryID = countryElement.getAttribute('data-id');
975
995
  this.setTooltipContent(this.getTooltipContent(countryID));
976
996
  this.showTooltip(e);
997
+
998
+ // For touch, move tooltip to the touch position and keep it there
999
+ if (e.pointerType === 'touch') {
1000
+ this.moveTooltip(e);
1001
+ }
1002
+ }.bind(this)
1003
+ );
1004
+
1005
+ // Handle touch move - update tooltip position while panning
1006
+ countryElement.addEventListener(
1007
+ 'touchmove',
1008
+ function (e) {
977
1009
  this.moveTooltip(e);
1010
+ }.bind(this),
1011
+ { passive: true }
1012
+ );
978
1013
 
979
- countryElement.addEventListener(
980
- 'touchmove',
981
- this.tooltipMoveEvent,
982
- {
983
- passive: true
984
- }
985
- );
1014
+ // Handle touch end - remove active state and hide tooltip
1015
+ countryElement.addEventListener(
1016
+ 'touchend',
1017
+ function (e) {
1018
+ const touch = e.changedTouches[0];
1019
+ const elementAtEnd = document.elementFromPoint(touch.clientX, touch.clientY);
1020
+
1021
+ // Only hide if touch ended outside the country or tooltip
1022
+ if (
1023
+ !elementAtEnd ||
1024
+ (!elementAtEnd.closest('.svgMap-country') &&
1025
+ !elementAtEnd.closest('.svgMap-tooltip'))
1026
+ ) {
1027
+ this.hideTooltip();
1028
+ document
1029
+ .querySelectorAll('.svgMap-active')
1030
+ .forEach(el => el.classList.remove('svgMap-active'));
1031
+ }
986
1032
  }.bind(this),
987
1033
  { passive: true }
988
1034
  );
989
1035
 
1036
+ // Remove pointermove listener when leaving non-touch pointer
990
1037
  countryElement.addEventListener(
991
- 'mouseenter',
1038
+ 'pointerleave',
992
1039
  function (e) {
993
- countryElement.parentNode.appendChild(countryElement);
994
- countryElement.classList.add('svgMap-active');
995
- var countryID = countryElement.getAttribute('data-id');
996
- this.setTooltipContent(this.getTooltipContent(countryID));
997
- this.showTooltip(e);
998
- countryElement.addEventListener(
999
- 'mousemove',
1000
- this.tooltipMoveEvent,
1001
- {
1002
- passive: true
1003
- }
1004
- );
1040
+ if (e.pointerType !== 'touch') {
1041
+ document.removeEventListener('pointermove', handlePointerMoveBound);
1042
+ this.hideTooltip();
1043
+ document
1044
+ .querySelectorAll('.svgMap-active')
1045
+ .forEach(el => el.classList.remove('svgMap-active'));
1046
+ }
1047
+ }.bind(this)
1048
+ );
1049
+
1050
+ document.addEventListener(
1051
+ 'pointerover',
1052
+ function (e) {
1053
+ if (e.pointerType !== 'touch') return;
1054
+
1055
+ if (
1056
+ e.target.closest('.svgMap-country') ||
1057
+ e.target.closest('.svgMap-tooltip')
1058
+ ) {
1059
+ return;
1060
+ }
1061
+
1062
+ this.hideTooltip();
1063
+ document
1064
+ .querySelectorAll('.svgMap-active')
1065
+ .forEach(el => el.classList.remove('svgMap-active'));
1005
1066
  }.bind(this),
1006
1067
  { passive: true }
1007
1068
  );
@@ -1021,140 +1082,70 @@ export default class svgMap {
1021
1082
  this.options.data.values[countryID]['linkTarget']
1022
1083
  );
1023
1084
  }
1085
+ }
1086
+ }.bind(this)
1087
+ );
1024
1088
 
1025
- let dragged = false;
1026
- countryElement.addEventListener('mousedown', function () {
1027
- dragged = false;
1028
- });
1029
- countryElement.addEventListener('touchstart', function () {
1030
- dragged = false;
1031
- });
1032
- countryElement.addEventListener('mousemove', function () {
1033
- dragged = true;
1034
- });
1035
- countryElement.addEventListener('touchmove', function () {
1036
- dragged = true;
1037
- });
1038
- const clickHandler = function (e) {
1039
- if (dragged) {
1040
- return;
1041
- }
1089
+ let pointerStart = null;
1090
+ let activeCountry = null;
1042
1091
 
1043
- const link = countryElement.getAttribute('data-link');
1044
- const target = countryElement.getAttribute('data-link-target');
1092
+ this.mapImage.addEventListener('pointerdown', e => {
1093
+ // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
1094
+ if (e.button !== 0) return;
1045
1095
 
1046
- if (target) {
1047
- window.open(link, target);
1048
- } else {
1049
- window.location.href = link;
1050
- }
1051
- };
1096
+ pointerStart = { x: e.clientX, y: e.clientY };
1097
+ }, { passive: true });
1052
1098
 
1053
- countryElement.addEventListener('click', clickHandler);
1054
- countryElement.addEventListener('touchend', clickHandler);
1055
- }
1099
+ this.mapImage.addEventListener('pointerup', e => {
1100
+ // Ignore right click (on desktop it allows inspecting the chart elements without opening the URL)
1101
+ if (e.button !== 0) return;
1056
1102
 
1057
- // Hide tooltip when mouse leaves the country area
1058
- countryElement.addEventListener(
1059
- 'mouseleave',
1060
- function () {
1061
- this.hideTooltip();
1062
- countryElement.classList.remove('svgMap-active');
1063
- countryElement.removeEventListener(
1064
- 'mousemove',
1065
- this.tooltipMoveEvent,
1066
- {
1067
- passive: true
1068
- }
1069
- );
1070
- }.bind(this),
1071
- { passive: true }
1072
- );
1103
+ if (!pointerStart) return;
1073
1104
 
1074
- // Hide tooltip when touch ends
1075
- countryElement.addEventListener(
1076
- 'touchend',
1077
- function () {
1078
- // Do not hide the tooltip, so it stays open on mobile
1079
- }.bind(this),
1080
- { passive: true }
1081
- );
1105
+ const dragThreshold = 5; // pixels
1106
+ const moved =
1107
+ Math.abs(pointerStart.x - e.clientX) > dragThreshold ||
1108
+ Math.abs(pointerStart.y - e.clientY) > dragThreshold;
1082
1109
 
1083
- // Show/hide tooltip on click
1084
- countryElement.addEventListener(
1085
- 'click',
1086
- function (e) {
1087
- e.stopPropagation();
1088
- var countryID = countryElement.getAttribute('data-id');
1089
- if (countryElement.getAttribute('data-tooltip-open') === 'true') {
1090
- this.hideTooltip();
1091
- countryElement.setAttribute('data-tooltip-open', 'false');
1092
- } else {
1093
- this.setTooltipContent(this.getTooltipContent(countryID));
1094
- this.showTooltip(e);
1095
- countryElement.setAttribute('data-tooltip-open', 'true');
1096
- }
1097
- }.bind(this),
1098
- { passive: true }
1099
- );
1100
- }.bind(this)
1101
- );
1110
+ pointerStart = null;
1102
1111
 
1103
- // Hide tooltip on touch outside
1104
- document.addEventListener(
1105
- 'touchend',
1106
- function (e) {
1107
- if (
1108
- e.target.closest('.svgMap-country') ||
1109
- e.target.closest('.svgMap-tooltip')
1110
- ) {
1111
- return;
1112
- }
1113
- this.hideTooltip();
1114
- var openTooltips = document.querySelectorAll(
1115
- '[data-tooltip-open="true"]'
1116
- );
1117
- openTooltips.forEach(function (element) {
1118
- element.setAttribute('data-tooltip-open', 'false');
1119
- });
1120
- var activeCountries = document.querySelectorAll('.svgMap-active');
1121
- activeCountries.forEach(function (element) {
1122
- element.classList.remove('svgMap-active');
1123
- });
1124
- }.bind(this),
1125
- { passive: true }
1126
- );
1112
+ if (moved) return;
1127
1113
 
1128
- // Hide tooltip on click outside
1129
- document.addEventListener(
1130
- 'click',
1131
- function (e) {
1132
- if (
1133
- e.target.closest('.svgMap-country') ||
1134
- e.target.closest('.svgMap-tooltip')
1135
- ) {
1136
- return;
1114
+ const countryElement = e.target.closest('.svgMap-country');
1115
+ if (!countryElement) return;
1116
+
1117
+ const countryID = countryElement.getAttribute('data-id');
1118
+ const link = countryElement.getAttribute('data-link');
1119
+ const target = countryElement.getAttribute('data-link-target');
1120
+ if (!link) return;
1121
+
1122
+ const isTouch = e.pointerType === 'touch' || e.pointerType === 'pen';
1123
+
1124
+ if (isTouch) {
1125
+ // Touch: only open if already active
1126
+ if (countryElement.classList.contains('svgMap-active')) {
1127
+ if (target) window.open(link, target);
1128
+ else window.location.href = link;
1129
+ } else {
1130
+ // first tap shows tooltip
1131
+ if (activeCountry) activeCountry.classList.remove('svgMap-active');
1132
+ activeCountry = countryElement;
1133
+ countryElement.classList.add('svgMap-active');
1134
+ this.setTooltipContent(this.getTooltipContent(countryID));
1135
+ this.showTooltip(e);
1137
1136
  }
1138
- this.hideTooltip();
1139
- var openTooltips = document.querySelectorAll(
1140
- '[data-tooltip-open="true"]'
1141
- );
1142
- openTooltips.forEach(function (element) {
1143
- element.setAttribute('data-tooltip-open', 'false');
1144
- });
1145
- var activeCountries = document.querySelectorAll('.svgMap-active');
1146
- activeCountries.forEach(function (element) {
1147
- element.classList.remove('svgMap-active');
1148
- });
1149
- }.bind(this),
1150
- { passive: true }
1151
- );
1137
+ } else {
1138
+ // Desktop: open immediately
1139
+ if (target) window.open(link, target);
1140
+ else window.location.href = link;
1141
+ }
1142
+ });
1152
1143
 
1153
1144
  // Expose instance
1154
1145
  var me = this;
1155
1146
 
1156
1147
  // Init pan zoom
1157
- this.mapPanZoom = svgPanZoom.svgPanZoom(this.mapImage, {
1148
+ this.mapPanZoom = svgPanZoom(this.mapImage, {
1158
1149
  zoomEnabled: this.options.allowInteraction,
1159
1150
  panEnabled: this.options.allowInteraction,
1160
1151
  fit: true,