1108 lines
31 KiB
JavaScript
1108 lines
31 KiB
JavaScript
(function ($, hotspots, undefined) {
|
|
"use strict";
|
|
|
|
var Leaflet = L.noConflict();
|
|
|
|
var ua = window.navigator.userAgent,
|
|
isiOS = !!ua.match(/iPad/i) || !!ua.match(/iPhone/i),
|
|
isWebkit = !!ua.match(/WebKit/i),
|
|
isWebkitiOS = isiOS && isWebkit,
|
|
isMobileSafari = isiOS && isWebkit && !ua.match(/CriOS/i),
|
|
getBrowserVersion = function () {
|
|
var temp,
|
|
M =
|
|
ua.match(
|
|
/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i,
|
|
) || [];
|
|
|
|
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"];
|
|
if ((temp = ua.match(/version\/(\d+)/i)) != null) {
|
|
M.splice(1, 1, temp[1]);
|
|
}
|
|
return parseInt(M[1]);
|
|
};
|
|
|
|
// Store all the leaflets on the page in an array for access later
|
|
var leaflets = [];
|
|
|
|
// Store all image maps on the page in an object for access later
|
|
var imageMaps = {};
|
|
|
|
// Store all the more info area hotspots in an object for access later
|
|
hotspots.infoSpots = {};
|
|
|
|
// Intersection Observer to detect when hidden hotspots become visible
|
|
var hotspotVisibilityObserver = null;
|
|
|
|
const layoutsThatShowTitleOnFocus = [
|
|
"left",
|
|
"right",
|
|
"bottom",
|
|
"top",
|
|
"lightbox",
|
|
];
|
|
|
|
var setupHotspotVisibilityObserver = function () {
|
|
if (!window.IntersectionObserver) {
|
|
return; // Browser doesn't support Intersection Observer
|
|
}
|
|
|
|
hotspotVisibilityObserver = new IntersectionObserver(
|
|
function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (entry.isIntersecting) {
|
|
// Hotspot container has become visible
|
|
var container = entry.target;
|
|
var containerJQuery = $(container);
|
|
|
|
// Check if this container was previously hidden
|
|
if (containerJQuery.data("was-hidden")) {
|
|
console.log("Hotspot container became visible:", container);
|
|
|
|
hotspots.init(); // Reinitialize hotspots
|
|
containerJQuery.removeData("was-hidden");
|
|
}
|
|
} else {
|
|
// Hotspot container has become hidden
|
|
var container = entry.target;
|
|
var containerJQuery = $(container);
|
|
|
|
containerJQuery.data("was-hidden", true);
|
|
}
|
|
});
|
|
},
|
|
{
|
|
threshold: 0.1,
|
|
rootMargin: "10px",
|
|
},
|
|
);
|
|
};
|
|
|
|
var observeHotspotContainer = function (container) {
|
|
if (hotspotVisibilityObserver && container.length) {
|
|
var containerElement = container.get(0);
|
|
var rect = containerElement.getBoundingClientRect();
|
|
var isInitiallyHidden =
|
|
rect.width === 0 ||
|
|
rect.height === 0 ||
|
|
window.getComputedStyle(containerElement).display === "none" ||
|
|
window.getComputedStyle(containerElement).visibility === "hidden";
|
|
|
|
if (isInitiallyHidden) {
|
|
container.data("was-hidden", true);
|
|
}
|
|
|
|
hotspotVisibilityObserver.observe(containerElement);
|
|
}
|
|
};
|
|
|
|
var mapSetup = function () {
|
|
$(".da-error").hide();
|
|
$(".hotspot-info").addClass("da-hidden");
|
|
|
|
setupHotspotVisibilityObserver();
|
|
|
|
var images = $("img.hotspots-image, picture.hotspots-image img"); // Select images as well as images inside picture elements
|
|
|
|
images.each(function () {
|
|
var img = $(this);
|
|
|
|
var tester = new Image();
|
|
|
|
tester.onload = function () {
|
|
markLoaded(img);
|
|
moreInfoSetup(img);
|
|
leafletSetup(img);
|
|
var container = img.parents(".hotspots-container");
|
|
observeHotspotContainer(container);
|
|
};
|
|
tester.src = img.attr("src");
|
|
});
|
|
|
|
if (isMobileSafari) {
|
|
window.onpageshow = function (event) {
|
|
if (event.persisted) {
|
|
window.location.reload();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
var markLoaded = function (img) {
|
|
img.data("status", "loaded");
|
|
var container = img.parents(".hotspots-container").addClass("loaded");
|
|
img.attr("aria-hidden", true);
|
|
};
|
|
|
|
var stopAudioVideo = function (el) {
|
|
var iframe = el.querySelector("iframe[src*=youtube]");
|
|
var video = el.querySelector("video");
|
|
var audio = el.querySelector("audio");
|
|
if (iframe) {
|
|
var iframeSrc = iframe.src;
|
|
iframe.src = iframeSrc;
|
|
}
|
|
if (video) {
|
|
video.pause();
|
|
}
|
|
if (audio) {
|
|
audio.pause();
|
|
}
|
|
};
|
|
|
|
var moreInfoSetup = function (img) {
|
|
var container = img.parents(".hotspots-container");
|
|
|
|
if (container.data("layout") == "tooltip") {
|
|
return;
|
|
}
|
|
|
|
if (container.data("layout") == "lightbox") {
|
|
lightboxSetup(container, img);
|
|
return;
|
|
}
|
|
|
|
infoboxSetup(container, img);
|
|
};
|
|
|
|
var infoboxSetup = function (container, img) {
|
|
var initial = container.find(".hotspot-initial");
|
|
var content = container.find(".hotspots-placeholder");
|
|
initial.addClass("visible");
|
|
container.on("active.responsilight inactive.responsilight", function (e) {
|
|
var data = $(e.target).data("areaData");
|
|
var info;
|
|
if (e.type === "active") {
|
|
info = $(data.href);
|
|
} else {
|
|
info = initial;
|
|
}
|
|
|
|
var visibleContent = content.children(".visible");
|
|
stopAudioVideo(visibleContent.get(0));
|
|
visibleContent.removeClass("visible");
|
|
info.removeClass("da-hidden").addClass("visible").appendTo(content);
|
|
|
|
// Check for an embedded video player
|
|
var video = info.find(".wp-video");
|
|
if (video.length) {
|
|
if (!info.data("video-resized")) {
|
|
info.data("video-resized", true);
|
|
window.dispatchEvent(new Event("resize"));
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
var lightboxSetup = function (container, img) {
|
|
container.on("active.responsilight", function (e) {
|
|
var data = $(e.target).data("areaData"),
|
|
info = $(data.href),
|
|
container = info.parent(),
|
|
target = $(e.target),
|
|
currentLightbox = $(".featherlight");
|
|
|
|
if (e.type === "active" && currentLightbox.length === 0) {
|
|
$.featherlight('<div class="hotspot-info"></div>', {
|
|
closeLabel: drawattentionData.closeLabel,
|
|
closeSpeed: 250,
|
|
closeOnEsc: false,
|
|
afterContent: function () {
|
|
var content = $(".featherlight-inner"),
|
|
lb = $(".featherlight-content"),
|
|
mapNo = img.data("id"),
|
|
lightboxContainer = $(".featherlight");
|
|
|
|
info.appendTo(content).show();
|
|
lb.addClass("lightbox-" + mapNo);
|
|
|
|
// Apply accessibility attributes
|
|
var hotspotTitle =
|
|
info.find(".hotspot-title").text() || data.title || "";
|
|
lightboxContainer.attr({
|
|
tabindex: "-1",
|
|
role: "dialog",
|
|
"aria-modal": "true",
|
|
"aria-label": hotspotTitle,
|
|
});
|
|
|
|
// Fix accessibility issue - links not focusable by keyboard
|
|
var untabbables = info.find("a[tabindex=-1]");
|
|
untabbables.attr("tabindex", 0);
|
|
|
|
setTimeout(function () {
|
|
var img = content.find("img"),
|
|
contentHeight = content.get(0).scrollHeight,
|
|
imgHeight = img.height(),
|
|
lbHeight = lb.outerHeight();
|
|
|
|
if (contentHeight > lbHeight) {
|
|
var diff = contentHeight - lbHeight + 50,
|
|
newHeight = imgHeight - diff,
|
|
minHeight = $(window).innerHeight() / 2;
|
|
|
|
newHeight = newHeight < minHeight ? minHeight : newHeight;
|
|
|
|
var naturalHeight = img.prop("naturalHeight");
|
|
|
|
if (newHeight < naturalHeight) {
|
|
img.css({ width: "auto" });
|
|
img.animate(
|
|
{
|
|
height: newHeight,
|
|
},
|
|
200,
|
|
);
|
|
}
|
|
}
|
|
}, 100);
|
|
|
|
lightboxAnchorLinks(content, target);
|
|
lightboxTrapFocus();
|
|
window.dispatchEvent(new Event("resize")); // Trigger window resize event for [video] shortcode JS
|
|
},
|
|
afterOpen: function () {
|
|
$("body").on("keyup", documentEsc);
|
|
},
|
|
afterClose: function () {
|
|
target.removeClass("hotspot-active");
|
|
$("body").off("keyup", documentEsc);
|
|
},
|
|
beforeClose: function () {
|
|
stopAudioVideo(document.querySelector(".featherlight-content"));
|
|
setTimeout(function () {
|
|
info.hide().appendTo(container);
|
|
}, 500); // delay hiding content and moving it back outside the lightbox
|
|
},
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
var documentEsc = function (e) {
|
|
if (e.keyCode === 27) {
|
|
$.featherlight.current().close();
|
|
}
|
|
};
|
|
|
|
var lightboxAnchorLinks = function (content, hotspot) {
|
|
var links = content.find('.hotspot-content a[href^="#"]');
|
|
links.on("click", function (e) {
|
|
e.preventDefault();
|
|
var targetEl = $(e.target.hash);
|
|
var current = $.featherlight.current();
|
|
if (!current) return;
|
|
current.afterClose = function () {
|
|
hotspot.removeClass("hotspot-active");
|
|
$("html").removeClass("with-featherlight");
|
|
$("html, body").animate(
|
|
{
|
|
scrollTop: targetEl.offset().top,
|
|
},
|
|
500,
|
|
);
|
|
};
|
|
current.close();
|
|
});
|
|
};
|
|
|
|
var lightboxTrapFocus = function () {
|
|
var lightbox = $(".featherlight");
|
|
var focusableElements = lightbox.find(
|
|
'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
);
|
|
|
|
if (focusableElements.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var firstFocusable = focusableElements.first();
|
|
var lastFocusable = focusableElements.last();
|
|
|
|
// Set initial focus to the lightbox container
|
|
lightbox.focus();
|
|
|
|
lightbox.on("keydown.focustrap", function (e) {
|
|
if (e.key !== "Tab" && e.keyCode !== 9) {
|
|
return;
|
|
}
|
|
|
|
if (e.shiftKey) {
|
|
if (
|
|
document.activeElement === firstFocusable.get(0) ||
|
|
document.activeElement === lightbox.get(0)
|
|
) {
|
|
e.preventDefault();
|
|
lastFocusable.focus();
|
|
}
|
|
} else {
|
|
if (document.activeElement === lastFocusable.get(0)) {
|
|
e.preventDefault();
|
|
firstFocusable.focus();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Clean up when lightbox closes
|
|
lightbox.on("closed.featherlight", function () {
|
|
lightbox.off("keydown.focustrap");
|
|
});
|
|
};
|
|
|
|
var showTooltip = function (shape, areaData) {
|
|
var container = $(shape._map._container);
|
|
var content = $(areaData.href).html();
|
|
|
|
var tip = Leaflet.responsivePopup({
|
|
autoPan: false,
|
|
closeButton: areaData.trigger == "click",
|
|
hasTip: container.width() > 840,
|
|
maxHeight: container.height() * 0.9,
|
|
offset: new Leaflet.Point(0, 0),
|
|
});
|
|
|
|
tip.setContent(content);
|
|
shape.bindPopup(tip);
|
|
|
|
if (areaData.trigger === "click") {
|
|
shape.on("click", function (e) {
|
|
if (shape._path.classList.contains("hotspot-active")) {
|
|
shape.closePopup();
|
|
} else {
|
|
shape.openPopup();
|
|
}
|
|
});
|
|
} else {
|
|
shape.on("mouseover", function () {
|
|
shape.openPopup();
|
|
});
|
|
shape.on("mouseout", function () {
|
|
shape.closePopup();
|
|
});
|
|
}
|
|
|
|
container.on("click", function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
$(document).on("click", function (e) {
|
|
shape.closePopup();
|
|
shape._path.classList.remove("hotspot-active");
|
|
});
|
|
};
|
|
|
|
var leafletSetup = function (img) {
|
|
var id = img.data("id");
|
|
var container = $(
|
|
'<div id="hotspots-map-container-' +
|
|
id +
|
|
'" class="hotspots-map-container"></div>',
|
|
);
|
|
var imgWidth = img.width();
|
|
var imgHeight = img.height();
|
|
|
|
container.css({
|
|
width: imgWidth + "px",
|
|
height: imgHeight + "px",
|
|
});
|
|
img.after(container);
|
|
|
|
// Check to be sure the container is on the page
|
|
// To prevent errors when loaded in Elementor popup
|
|
var containerTest = $("#hotspots-map-container-" + id);
|
|
if (!containerTest.length) {
|
|
return;
|
|
}
|
|
|
|
var map = Leaflet.map("hotspots-map-container-" + id, {
|
|
attributionControl: false,
|
|
boxZoom: false,
|
|
crs: Leaflet.CRS.Simple,
|
|
doubleClickZoom: false,
|
|
dragging: false,
|
|
keyboard: false,
|
|
minZoom: -20,
|
|
scrollWheelZoom: false,
|
|
// tap: !isWebkitiOS,
|
|
tap: true,
|
|
touchZoom: false,
|
|
zoomControl: false,
|
|
zoomSnap: 0,
|
|
});
|
|
|
|
var domImg = img.get(0);
|
|
var natHeight = domImg.naturalHeight;
|
|
var natWidth = domImg.naturalWidth;
|
|
img.data("natW", natWidth);
|
|
img.data("natH", natHeight);
|
|
var bounds = [
|
|
[0, 0],
|
|
[natHeight, natWidth],
|
|
];
|
|
var imageAlt = img.attr("alt") || img.data("image-title") || "";
|
|
var imageLayer = Leaflet.imageOverlay(img.attr("src"), bounds, {
|
|
alt: imageAlt,
|
|
}).addTo(map);
|
|
map.fitBounds(bounds);
|
|
|
|
leaflets.push({
|
|
map: map,
|
|
img: img,
|
|
});
|
|
|
|
drawSpots(img, map);
|
|
};
|
|
|
|
var showContextMenu = function (shape, areaData, e) {
|
|
var hash = areaData.href;
|
|
var container = $(shape._map._container);
|
|
|
|
if (!hash) {
|
|
return;
|
|
}
|
|
|
|
$(".da-address-wrapper").remove();
|
|
|
|
var windowAddress = window.location.href.split("#")[0];
|
|
var shapeAddress = windowAddress + hash;
|
|
var div = $('<div class="da-address-wrapper"></div>')
|
|
.css({
|
|
left: e.originalEvent.pageX + "px",
|
|
top: e.originalEvent.pageY + "px",
|
|
})
|
|
.append("<p>" + shapeAddress + "</p>")
|
|
.append('<span class="da-address-close">×</span>')
|
|
.appendTo(document.body);
|
|
|
|
div.find(".da-address-close").on("click", function () {
|
|
div.remove();
|
|
});
|
|
};
|
|
|
|
var drawSpots = function (img, map) {
|
|
var id = img.data("id");
|
|
|
|
/* If the image has a usemap attribute, detach the map and store it in the imageMaps object */
|
|
if (img[0].hasAttribute("usemap")) {
|
|
var mapName = img.attr("usemap").replace("#", "");
|
|
var imageMap = $('map[name="' + mapName + '"]');
|
|
imageMaps[id] = imageMap.detach();
|
|
img.removeAttr("usemap");
|
|
} else {
|
|
/* Else we've already removed the image map, so we just need to get it */
|
|
var imageMap = imageMaps[id];
|
|
}
|
|
|
|
var areas = imageMap.find("area");
|
|
var container = img.parents(".hotspots-container");
|
|
|
|
areas.each(function () {
|
|
var area = $(this);
|
|
var shape = area.attr("shape");
|
|
var coords = area.attr("coords").split(",");
|
|
var areaData = {
|
|
style: area.data("color-scheme")
|
|
? area.data("color-scheme")
|
|
: "default",
|
|
title: area.attr("title"),
|
|
href: area.attr("href"),
|
|
spot: area.data("id"),
|
|
target: area.attr("target"),
|
|
action: area.data("action"),
|
|
layout: container.data("layout"),
|
|
trigger: container.data("trigger"),
|
|
};
|
|
switch (shape) {
|
|
case "circle":
|
|
renderCircle(coords, map, img, areaData);
|
|
break;
|
|
case "rect":
|
|
renderPoly(coords, map, img, areaData);
|
|
break;
|
|
case "polygon":
|
|
renderPoly(coords, map, img, areaData);
|
|
break;
|
|
case "poly": // Leaving this here for backward compatibility
|
|
renderPoly(coords, map, img, areaData);
|
|
break;
|
|
}
|
|
});
|
|
// A11y fixes after all the spots are drawn
|
|
a11yFixes(img, map);
|
|
|
|
// Link to area after all the spots are drawn
|
|
hotspots.linkToArea();
|
|
};
|
|
|
|
var renderCircle = function (coords, map, img, areaData) {
|
|
var x = coords[0];
|
|
var y = img.data("natH") - coords[1];
|
|
var rad = coords[2];
|
|
var circle = Leaflet.circle([y, x], {
|
|
radius: rad,
|
|
className: "hotspot-" + areaData.style,
|
|
title: areaData.title,
|
|
});
|
|
|
|
circle.on("add", function (e) {
|
|
var $circle = $(e.target.getElement());
|
|
$circle.data("areaData", areaData);
|
|
$circle.attr("tabindex", "0");
|
|
$circle.attr("aria-label", areaData.title);
|
|
$circle.attr("name", areaData.title);
|
|
});
|
|
|
|
circle.addTo(map);
|
|
|
|
// If this is a more info hotspot, add it to the infoSpots object
|
|
if (areaData.href.charAt(0) === "#") {
|
|
var spotName = areaData.href.replace("#", "");
|
|
hotspots.infoSpots[spotName] = circle;
|
|
}
|
|
|
|
shapeEvents(circle, areaData);
|
|
};
|
|
|
|
var renderPoly = function (coords, map, img, areaData) {
|
|
var xCoords = [];
|
|
var yCoords = [];
|
|
for (var i = 0; i < coords.length; i++) {
|
|
if (i % 2 == 0) {
|
|
xCoords.push(coords[i]);
|
|
} else {
|
|
yCoords.push(coords[i]);
|
|
}
|
|
}
|
|
|
|
var polyCoords = yCoords.map(function (coord, index) {
|
|
return [img.data("natH") - coord, xCoords[index]];
|
|
});
|
|
|
|
var poly = Leaflet.polygon(polyCoords, {
|
|
className: "hotspot-" + areaData.style,
|
|
title: areaData.title,
|
|
});
|
|
|
|
// Attach the area data to the path as soon as it's added to the map, a11y attributes
|
|
poly.on("add", function (e) {
|
|
var $poly = $(e.target.getElement());
|
|
$poly.data("areaData", areaData);
|
|
$poly.attr("tabindex", "0");
|
|
$poly.attr("aria-label", areaData.title);
|
|
$poly.attr("name", areaData.title);
|
|
});
|
|
|
|
poly.addTo(map);
|
|
|
|
// If this is a more info hotspot, add it to the infoSpots object
|
|
if (areaData.href.charAt(0) === "#") {
|
|
var spotName = areaData.href.replace("#", "");
|
|
hotspots.infoSpots[spotName] = poly;
|
|
}
|
|
|
|
shapeEvents(poly, areaData);
|
|
};
|
|
|
|
var shapeOver = function (shape, areaData, e) {
|
|
var $shape = $(e.target.getElement());
|
|
$shape.trigger("over.responsilight");
|
|
if (
|
|
areaData.trigger === "hover" &&
|
|
(e.type !== "touchstart") & (e.type !== "keypress")
|
|
) {
|
|
$shape.addClass("hotspot-active");
|
|
$shape.trigger("active.responsilight");
|
|
}
|
|
};
|
|
|
|
var shapeOut = function (shape, areaData, e) {
|
|
var $shape = $(e.target.getElement());
|
|
$shape.trigger("out.responsilight");
|
|
if (areaData.trigger === "hover" && e.type !== "keypress") {
|
|
$shape.removeClass("hotspot-active");
|
|
$shape.trigger("inactive.responsilight");
|
|
// If mouse user, blur hover spots when mouse moves out
|
|
if (e.type === "mouseout") {
|
|
$shape.trigger("blur");
|
|
}
|
|
}
|
|
};
|
|
|
|
var shapeClick = function (shape, areaData, e) {
|
|
var $shape = $(e.target.getElement());
|
|
$shape.trigger("areaClick.responsilight");
|
|
if (
|
|
areaData.trigger === "hover" &&
|
|
e.type !== "touchstart" &&
|
|
e.type !== "keypress" &&
|
|
!isMobileSafari
|
|
) {
|
|
return;
|
|
}
|
|
$shape.toggleClass("hotspot-active");
|
|
if ($shape.hasClass("hotspot-active")) {
|
|
$shape.trigger("active.responsilight");
|
|
} else {
|
|
$shape.trigger("inactive.responsilight");
|
|
}
|
|
var oldActive = $shape.siblings(".hotspot-active");
|
|
if (oldActive.length) {
|
|
oldActive.removeClass("hotspot-active");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Very Custom logic to just handle displaying the popup on focus
|
|
* Why we had to do it this way:
|
|
* shape.on('focus') doesn't fire similar to the other events
|
|
* openPopup() would steal the focus and it's hard to then rely on focusout on shape to remove the popup, cause the focusout would fire right after calling openPopup()
|
|
* closePopup() would also programmatically control/alter the tab behavior and that is another problem
|
|
*
|
|
*/
|
|
var maybeDisplayPopupOnFocus = function (shape, areaData, nativeEvent) {
|
|
if (areaData.trigger !== "hover") return;
|
|
if (areaData.layout !== "tooltip") return;
|
|
|
|
var $shape = $(nativeEvent.target);
|
|
if ($shape.hasClass("hotspot-active")) {
|
|
return;
|
|
}
|
|
|
|
$shape.addClass("hotspot-active");
|
|
$shape.trigger("over.responsilight");
|
|
shape.openPopup();
|
|
|
|
setTimeout(() => {
|
|
const activeElement = document.activeElement;
|
|
if (!activeElement.classList.contains("leaflet-rrose-content")) {
|
|
return;
|
|
}
|
|
$shape.focus();
|
|
|
|
const closePopupAfterFocusIsLost = (e) => {
|
|
shape.closePopup();
|
|
$shape.removeClass("hotspot-active");
|
|
$shape.trigger("out.responsilight");
|
|
|
|
$shape.off("focusout", closePopupAfterFocusIsLost);
|
|
};
|
|
$shape.on("focusout", closePopupAfterFocusIsLost);
|
|
}, 0);
|
|
};
|
|
|
|
/**
|
|
* Left, right, top, bottom & lightbox layout + click trigger
|
|
* Display Title on focus
|
|
*/
|
|
var maybeDisplayTitleOnFocusForLayout = function (
|
|
shape,
|
|
areaData,
|
|
nativeEvent,
|
|
) {
|
|
if (nativeEvent.type !== "focus") return;
|
|
if (areaData.trigger !== "click") return;
|
|
if (!layoutsThatShowTitleOnFocus.includes(areaData.layout)) return;
|
|
|
|
const target = nativeEvent.target;
|
|
target.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
|
|
target.addEventListener("focusout", function handleFocusOut(e) {
|
|
target.dispatchEvent(new MouseEvent("mouseout", { bubbles: true }));
|
|
target.removeEventListener("focusout", handleFocusOut);
|
|
});
|
|
};
|
|
|
|
var shapeEvents = function (shape, areaData) {
|
|
// Handle URL spots
|
|
if (areaData.action == "url") {
|
|
if (areaData.title) {
|
|
shape.bindTooltip(areaData.title);
|
|
}
|
|
shape.on("click", function (e) {
|
|
if (areaData.target == "_new" && !isMobileSafari) {
|
|
// new window
|
|
window.open(areaData.href, "_blank");
|
|
} else {
|
|
// same window
|
|
var first = areaData.href.charAt(0);
|
|
var targetElem;
|
|
try {
|
|
targetElem = first === "#" ? $(areaData.href) : null;
|
|
} catch (error) {
|
|
targetElem = null;
|
|
}
|
|
if (targetElem && targetElem.length) {
|
|
// hash link to existing target
|
|
$("html, body").animate(
|
|
{
|
|
scrollTop: targetElem.offset().top - 50,
|
|
},
|
|
750,
|
|
function () {
|
|
// callback after scrolling
|
|
history.pushState({}, "", areaData.href);
|
|
},
|
|
);
|
|
} else {
|
|
window.location = areaData.href;
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Show right-click context menu for logged-in admins only
|
|
if (drawattentionData.isLoggedIn && drawattentionData.isAdmin) {
|
|
shape.on("contextmenu", function (e) {
|
|
showContextMenu(shape, areaData, e);
|
|
});
|
|
}
|
|
|
|
// Handle tooltip spots
|
|
if (areaData.layout === "tooltip") {
|
|
showTooltip(shape, areaData);
|
|
}
|
|
|
|
// Add styled tooltip to all non-hover areas
|
|
if (
|
|
areaData.action === "url" ||
|
|
(areaData.trigger === "click" && areaData.layout !== "tooltip")
|
|
) {
|
|
if (areaData.title) {
|
|
shape.bindTooltip(areaData.title);
|
|
}
|
|
}
|
|
|
|
// Handle all other spots
|
|
var moved = false;
|
|
|
|
shape.on(
|
|
"touchstart touchmove touchend click mouseover mouseout keypress focus blur",
|
|
function (e) {
|
|
switch (e.type) {
|
|
case "touchstart":
|
|
moved = false;
|
|
break;
|
|
case "touchmove":
|
|
moved = true;
|
|
break;
|
|
case "touchend":
|
|
if (moved) {
|
|
return;
|
|
}
|
|
shapeOver(shape, areaData, e);
|
|
shapeClick(shape, areaData, e);
|
|
break;
|
|
case "click":
|
|
shapeClick(shape, areaData, e);
|
|
break;
|
|
case "keypress":
|
|
var key = e.originalEvent.which;
|
|
if (key == 13 || key == 32) {
|
|
e.originalEvent.preventDefault();
|
|
shapeClick(shape, areaData, e);
|
|
}
|
|
break;
|
|
case "mouseover":
|
|
if (!isMobileSafari || (isMobileSafari && getBrowserVersion < 13)) {
|
|
shapeOver(shape, areaData, e);
|
|
}
|
|
if (isMobileSafari && !(getBrowserVersion() >= 12)) {
|
|
shapeClick(shape, areaData, e);
|
|
}
|
|
break;
|
|
case "focus":
|
|
shapeOver(shape, areaData, e);
|
|
break;
|
|
case "blur":
|
|
shapeOut(shape, areaData, e);
|
|
break;
|
|
case "mouseout":
|
|
shapeOut(shape, areaData, e);
|
|
break;
|
|
}
|
|
},
|
|
);
|
|
|
|
$(shape._path).on("focus", function (nativeEvent) {
|
|
maybeDisplayPopupOnFocus(shape, areaData, nativeEvent);
|
|
maybeDisplayTitleOnFocusForLayout(shape, areaData, nativeEvent);
|
|
});
|
|
};
|
|
|
|
var a11yFixes = function (img, map) {
|
|
let svg = img
|
|
.siblings(".leaflet-container")
|
|
.find(".leaflet-overlay-pane svg");
|
|
let id = img.data("id");
|
|
|
|
// Add title and description to SVG
|
|
svg.prepend(
|
|
'<desc id="img-desc-' +
|
|
id +
|
|
'">' +
|
|
img.data("image-description") +
|
|
"</desc>",
|
|
);
|
|
svg.prepend(
|
|
'<title id="img-title-' +
|
|
id +
|
|
'">' +
|
|
img.data("image-title") +
|
|
"</title>",
|
|
);
|
|
|
|
// Use title and desc to describe the svg
|
|
svg.attr("aria-labelledby", "img-title-" + id);
|
|
svg.attr("aria-describedby", "img-desc-" + id);
|
|
|
|
// Make svg screen reader traversable
|
|
svg.attr("role", "group");
|
|
|
|
// Create a traversable list
|
|
var group = svg.find("g").attr("role", "list");
|
|
group.children().attr("role", "listitem");
|
|
|
|
// Handle tooltips for the map
|
|
map.on("popupopen", function (popup) {
|
|
// shift focus to the popup when it opens
|
|
$(popup.popup._container)
|
|
.find(".leaflet-rrose-content")
|
|
.attr("tabindex", "-1")
|
|
.focus();
|
|
|
|
// move the close button to the end of the popup content so screen readers reach it
|
|
// after the main popup content, not before
|
|
var close = $(popup.popup._container)
|
|
.find(".leaflet-rrose-close-button")
|
|
.detach();
|
|
close.attr("aria-label", "Close item");
|
|
$(popup.popup._container).append(close);
|
|
|
|
$(document).on("keydown", function (e) {
|
|
if (e.which == 27) {
|
|
map.closePopup();
|
|
}
|
|
});
|
|
});
|
|
|
|
// return focus to the icon we started from before opening the pop up
|
|
map.on("popupclose", function (popup) {
|
|
$(document).off("keydown");
|
|
$(popup.popup._source._path).focus();
|
|
});
|
|
};
|
|
|
|
hotspots.linkToArea = function () {
|
|
// Called after the shapes are drawn
|
|
var hash = window.location.hash;
|
|
if (!hash) return;
|
|
|
|
var spotName = hash.replace("#", "");
|
|
if (!hotspots.infoSpots.hasOwnProperty(spotName)) return;
|
|
|
|
Object.keys(hotspots.infoSpots).forEach(function (key) {
|
|
hotspots.infoSpots[key].closeTooltip();
|
|
});
|
|
|
|
const spot = hotspots.infoSpots[spotName];
|
|
|
|
if (!spot._path.classList.contains("hotspot-active")) {
|
|
spot.fire("click");
|
|
}
|
|
|
|
scrollIntoViewIfNeeded(spot._map?.getContainer());
|
|
};
|
|
|
|
hotspots.setup = function () {
|
|
mapSetup();
|
|
};
|
|
|
|
hotspots.resizeTimer = null;
|
|
hotspots.resizing = false;
|
|
|
|
hotspots.init = function () {
|
|
// For backward compatibility - resets the size of the leaflet on demand
|
|
leaflets.forEach(function (item) {
|
|
var isLoaded = item.img.data("status") === "loaded";
|
|
|
|
if (!isLoaded) {
|
|
return;
|
|
}
|
|
|
|
item.img.next(".hotspots-map-container").css({
|
|
width: item.img.width() + "px",
|
|
height: item.img.height() + "px",
|
|
});
|
|
item.map.invalidateSize(true);
|
|
item.map.fitBounds([
|
|
[0, 0],
|
|
[item.img.data("natH"), item.img.data("natW")],
|
|
]);
|
|
});
|
|
};
|
|
|
|
hotspots.compatibilityFixes = function () {
|
|
if (window.Foundation) {
|
|
/* Fix for Foundation firing tag change event indiscriminately when some items are clicked on the page */
|
|
$(window).on("change.zf.tabs", function (e) {
|
|
if (e.target.tagName !== "INPUT") {
|
|
hotspots.init();
|
|
}
|
|
});
|
|
}
|
|
|
|
$(window).on("pageloaded load", function () {
|
|
/* Listen for pageloaded and load events on the window */
|
|
hotspots.init();
|
|
});
|
|
|
|
$(window).on("et_hashchange", function () {
|
|
/* Listen for Divi hashchange */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 1000);
|
|
});
|
|
|
|
$(".ult_tabs").on("click", ".ult_tab a", function () {
|
|
/* Ultimate tabs */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 2000);
|
|
});
|
|
|
|
$(
|
|
"a[data-vc-accordion], .et_pb_tabs .et_pb_tabs_controls li, .et_pb_toggle_title",
|
|
).on("click", function () {
|
|
/* Divi tabs, accordion, toggle */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 1000);
|
|
});
|
|
$(".vc_tta-tabs-container").on("click", ".vc_tta-tab", function () {
|
|
/* Visual composer tabs */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 1000);
|
|
});
|
|
|
|
$(".ui-tabs-anchor, .nav-tabs > li").on("click", function () {
|
|
/* UI Tabs */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 750);
|
|
});
|
|
|
|
$(".fl-accordion-button").on("click", function () {
|
|
/* Beaver Builder accordion */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 500);
|
|
});
|
|
|
|
$(".responsive-tabs").on(
|
|
"click",
|
|
".responsive-tabs__list__item",
|
|
function () {
|
|
/* Responsive tabs */
|
|
hotspots.init();
|
|
},
|
|
);
|
|
|
|
$(".elementor-tabs").on("click", ".elementor-tab-title", function () {
|
|
/* Elementor tabs */
|
|
hotspots.init();
|
|
});
|
|
|
|
$(".fl-tabs").on("click", "a.fl-tabs-label", function () {
|
|
/* Beaver Builder tabs */
|
|
hotspots.init();
|
|
});
|
|
|
|
$(".uabb-adv-accordion-button .uabb-tabs").on("click", function () {
|
|
/* Ultimate Beaver Builder addons accordion and tabs */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 250);
|
|
});
|
|
$(".w-tabs-item").on("click", function () {
|
|
/* Custom Woocommerce tabs */
|
|
setTimeout(function () {
|
|
hotspots.init();
|
|
}, 1000);
|
|
});
|
|
};
|
|
})(jQuery, (window.hotspots = window.hotspots || {}));
|
|
|
|
jQuery(function () {
|
|
hotspots.setup();
|
|
hotspots.compatibilityFixes();
|
|
});
|
|
|
|
jQuery(document).on("elementor/popup/show", function () {
|
|
hotspots.setup();
|
|
hotspots.compatibilityFixes();
|
|
});
|
|
|
|
jQuery(window).on("resize orientationchange", function (e) {
|
|
var $window = jQuery(this);
|
|
if (!hotspots.resizing) {
|
|
$window.trigger("resizeStart.responsilight");
|
|
hotspots.resizing = true;
|
|
}
|
|
clearTimeout(hotspots.resizeTimer);
|
|
hotspots.resizeTimer = setTimeout(function () {
|
|
hotspots.init();
|
|
$window.trigger("resizeComplete.responsilight");
|
|
hotspots.resizing = false;
|
|
}, 250);
|
|
});
|
|
|
|
jQuery(window).on("hashchange", function () {
|
|
hotspots.linkToArea();
|
|
});
|
|
|
|
window.onerror = function (errorMsg, url, lineNumber) {
|
|
// This should be a fun little experiement!
|
|
var errorBox = jQuery(".da-error").show();
|
|
if (errorBox.length) {
|
|
var contents = errorBox.html();
|
|
errorBox.html(
|
|
contents +
|
|
"<br/><br/><strong>Error:</strong> " +
|
|
errorMsg +
|
|
"<br/>Line " +
|
|
lineNumber +
|
|
": " +
|
|
url,
|
|
);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/* Fix for Elementor bug - duplicating DA images when popup opens */
|
|
jQuery(document).on("elementor/popup/show", () => {
|
|
setTimeout(function () {
|
|
var imageContainers = jQuery(".hotspots-image-container");
|
|
imageContainers.each(function () {
|
|
var extraMapContainers = jQuery(this)
|
|
.find(".hotspots-map-container:not(:first)")
|
|
.remove(); // find all but the first one and remove them
|
|
});
|
|
}, 250);
|
|
});
|
|
|
|
function scrollIntoViewIfNeeded(el, options = {}) {
|
|
if (!el) return;
|
|
const rect = el.getBoundingClientRect();
|
|
|
|
const fullyVisible =
|
|
rect.top >= 0 &&
|
|
rect.left >= 0 &&
|
|
rect.bottom <=
|
|
(window.innerHeight || document.documentElement.clientHeight) &&
|
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth);
|
|
|
|
if (!fullyVisible) {
|
|
el.scrollIntoView(options);
|
|
}
|
|
}
|