changelog

1 | 3:22 pm December 21, 2025 | 2 Items
  • Bug | Changelog Item 1 Title

changelog items 1 Text Editor

  • Feature | Changelog Item 2 Title

changelog items 2 Text Editor

  • Bug | Text 2

Text Editor 2

Live Example

[own_shortcode1] (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 ); })();

PHP

(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
  );
})();

Load map popup card on hover instead of click

Published on December 21, 2025

Rating5.0 / 5

The Mission

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

  • Subtotal

    {{ currencyFormat( pricing_summary.total_amount ) }}