(function () {
// Disable on touch devices (hover doesn’t make sense there)
if (window.matchMedia?.("(hover: none)").matches) return;
// ========= CONFIG =========
const OPEN_DELAY_MS = 80; // hover intent
const DOM_SETTLE_MS = 220; // no popup DOM changes for this long = “settled”
const MAX_WAIT_MS = 6000; // failsafe
// IMPORTANT: Adjust these selectors to match your popup wrapper if you know it.
// Leave as-is to try common ones.
const POPUP_SELECTORS = [
".mapboxgl-popup",
".voxel-map-popup",
".vx-map-popup",
".ts-map-popup",
".map-popup",
".marker-popup"
];
// Match ONLY requests likely related to Voxel popup/template loading.
// If this is too strict/loose, tweak it based on your Network tab.
function isPopupRequest(url) {
try {
const u = new URL(url, window.location.origin);
if (u.origin !== window.location.origin) return false;
const s = u.href;
return (
s.includes("admin-ajax.php") || // common WP async endpoint
s.includes("/wp-json/") || // REST
s.toLowerCase().includes("voxel") ||// often appears in endpoints
s.includes("vx_") || // common param/prefix patterns
s.includes("vx=")
);
} catch {
return false;
}
}
// ========= POPUP HELPERS =========
function findPopupRoot() {
for (const sel of POPUP_SELECTORS) {
const el = document.querySelector(sel);
if (el) return el;
}
return null;
}
function waitForPopupDomToSettle(timeoutMs = MAX_WAIT_MS) {
return new Promise((resolve) => {
const start = performance.now();
let lastMut = performance.now();
const popup = findPopupRoot();
if (!popup) {
// If we can’t find a wrapper, don’t hang forever
setTimeout(resolve, 200);
return;
}
const obs = new MutationObserver(() => {
lastMut = performance.now();
});
obs.observe(popup, { subtree: true, childList: true, attributes: true, characterData: true });
const tick = () => {
const now = performance.now();
const settled = now - lastMut >= DOM_SETTLE_MS;
const timedOut = now - start >= timeoutMs;
if (settled || timedOut) {
obs.disconnect();
resolve();
return;
}
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
});
}
// ========= NETWORK TRACKER (fetch + XHR) =========
const net = {
inFlight: 0,
waiters: [],
bump(delta) {
this.inFlight += delta;
if (this.inFlight < 0) this.inFlight = 0;
if (this.inFlight === 0) {
const ws = this.waiters.splice(0);
ws.forEach((fn) => fn());
}
},
waitForZero(timeoutMs = MAX_WAIT_MS) {
if (this.inFlight === 0) return Promise.resolve();
return new Promise((resolve) => {
let done = false;
const finish = () => {
if (done) return;
done = true;
resolve();
};
this.waiters.push(finish);
setTimeout(finish, timeoutMs);
});
}
};
// Patch fetch
const _fetch = window.fetch;
window.fetch = function (...args) {
const url = (args[0] && (typeof args[0] === "string" ? args[0] : args[0].url)) || "";
const track = isPopupRequest(url);
if (track) net.bump(1);
return _fetch.apply(this, args).finally(() => {
if (track) net.bump(-1);
});
};
// Patch XHR
const _open = XMLHttpRequest.prototype.open;
const _send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
this.__vx_track = isPopupRequest(url);
return _open.call(this, method, url, ...rest);
};
XMLHttpRequest.prototype.send = function (...args) {
if (this.__vx_track) net.bump(1);
const done = () => {
if (this.__vx_track) {
net.bump(-1);
this.__vx_track = false;
}
this.removeEventListener("loadend", done);
this.removeEventListener("error", done);
this.removeEventListener("abort", done);
};
this.addEventListener("loadend", done);
this.addEventListener("error", done);
this.addEventListener("abort", done);
return _send.apply(this, args);
};
// ========= HOVER QUEUE / LOCK =========
let hoverTimer = null;
let locked = false;
let pendingMarker = null;
let activeMarker = null;
function trueEnter(e, marker) {
return !(e.relatedTarget && marker.contains(e.relatedTarget));
}
async function openMarkerSerial(marker) {
// If something is currently loading, only keep the latest request
if (locked) {
pendingMarker = marker;
return;
}
locked = true;
pendingMarker = null;
// Set active early to avoid re-entrancy
activeMarker = marker;
// Trigger Voxel’s native open path
marker.click();
// Wait for popup-related network to finish + popup DOM to settle
await net.waitForZero(MAX_WAIT_MS);
await waitForPopupDomToSettle(MAX_WAIT_MS);
locked = false;
// If user hovered something else mid-load, open the latest one next
if (pendingMarker && pendingMarker !== marker) {
const next = pendingMarker;
pendingMarker = null;
openMarkerSerial(next);
}
}
document.addEventListener(
"pointerover",
(e) => {
const marker = e.target.closest(".map-marker");
if (!marker) return;
if (!trueEnter(e, marker)) return;
// If hovering same marker, ignore
if (marker === activeMarker) return;
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() => {
openMarkerSerial(marker);
}, OPEN_DELAY_MS);
},
true
);
})();
Rating5.0 / 5
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
There are no results matching your search
{{ item.label }}
{{ item.value ? item.value : currencyFormat( item.amount ) }}
Subtotal
{{ currencyFormat( pricing_summary.total_amount ) }}
There are no results matching your search