| 1 |
/** |
| 2 |
* WPSubsModal — reusable centered dialog over a dimmed backdrop. |
| 3 |
* |
| 4 |
* Behaviour only; the visual comes from the .wpsubs-modal CSS. A modal is a |
| 5 |
* `.wpsubs-modal[hidden]` element with an id. Any control opens it via |
| 6 |
* `data-wpsubs-modal-open="<modal-id>"`; any control inside closes it via |
| 7 |
* `data-wpsubs-modal-close` (the backdrop and the header/footer buttons use it). |
| 8 |
* Escape also closes. No per-page wiring needed. |
| 9 |
* |
| 10 |
* A modal with `data-wpsubs-modal-autoopen` opens automatically on load (e.g. the |
| 11 |
* Pro-upgrade preview modals). Opening locks body scroll; it's restored when the |
| 12 |
* last open modal closes. |
| 13 |
* |
| 14 |
* Usage: |
| 15 |
* PHP: wpsubs_render_modal( $args ) — renders the markup |
| 16 |
* JS: auto-inits; WPSubsModal.open(id) / .close(id) available programmatically |
| 17 |
* |
| 18 |
* Events fired on the modal element (bubbles): |
| 19 |
* wpsubs:modal:open / wpsubs:modal:close |
| 20 |
*/ |
| 21 |
(function () { |
| 22 |
"use strict"; |
| 23 |
|
| 24 |
/** |
| 25 |
* @param {string|HTMLElement} target Modal id or element. |
| 26 |
* @return {HTMLElement|null} |
| 27 |
*/ |
| 28 |
function resolve(target) { |
| 29 |
if (target instanceof HTMLElement) return target; |
| 30 |
return document.getElementById(String(target)); |
| 31 |
} |
| 32 |
|
| 33 |
function open(target) { |
| 34 |
var modal = resolve(target); |
| 35 |
if (!modal) return; |
| 36 |
modal.hidden = false; |
| 37 |
document.body.style.overflow = "hidden"; |
| 38 |
modal.dispatchEvent(new CustomEvent("wpsubs:modal:open", { bubbles: true })); |
| 39 |
var focusable = modal.querySelector("input, textarea, select, button:not([data-wpsubs-modal-close])"); |
| 40 |
if (focusable) focusable.focus(); |
| 41 |
} |
| 42 |
|
| 43 |
function close(target) { |
| 44 |
var modal = resolve(target); |
| 45 |
if (!modal) return; |
| 46 |
modal.hidden = true; |
| 47 |
// Restore body scroll only when no modal remains open. |
| 48 |
if (!document.querySelector(".wpsubs-modal:not([hidden])")) { |
| 49 |
document.body.style.overflow = ""; |
| 50 |
} |
| 51 |
modal.dispatchEvent(new CustomEvent("wpsubs:modal:close", { bubbles: true })); |
| 52 |
} |
| 53 |
|
| 54 |
function closeAll() { |
| 55 |
document.querySelectorAll(".wpsubs-modal:not([hidden])").forEach(function (m) { |
| 56 |
close(m); |
| 57 |
}); |
| 58 |
} |
| 59 |
|
| 60 |
// Delegated open/close — works for markup added after load too. |
| 61 |
document.addEventListener("click", function (e) { |
| 62 |
var opener = e.target.closest("[data-wpsubs-modal-open]"); |
| 63 |
if (opener) { |
| 64 |
e.preventDefault(); |
| 65 |
open(opener.getAttribute("data-wpsubs-modal-open")); |
| 66 |
return; |
| 67 |
} |
| 68 |
var closer = e.target.closest("[data-wpsubs-modal-close]"); |
| 69 |
if (closer) { |
| 70 |
e.preventDefault(); |
| 71 |
var modal = closer.closest(".wpsubs-modal"); |
| 72 |
if (modal) close(modal); |
| 73 |
} |
| 74 |
}); |
| 75 |
|
| 76 |
document.addEventListener("keydown", function (e) { |
| 77 |
if (e.key === "Escape") closeAll(); |
| 78 |
}); |
| 79 |
|
| 80 |
/** |
| 81 |
* Open any modal flagged to auto-open on load. |
| 82 |
* |
| 83 |
* @param {Document|HTMLElement} [root] |
| 84 |
*/ |
| 85 |
function init(root) { |
| 86 |
(root || document).querySelectorAll(".wpsubs-modal[data-wpsubs-modal-autoopen]").forEach(function (modal) { |
| 87 |
open(modal); |
| 88 |
}); |
| 89 |
} |
| 90 |
|
| 91 |
if (document.readyState === "loading") { |
| 92 |
document.addEventListener("DOMContentLoaded", function () { |
| 93 |
init(); |
| 94 |
}); |
| 95 |
} else { |
| 96 |
init(); |
| 97 |
} |
| 98 |
|
| 99 |
// Public API |
| 100 |
window.WPSubsModal = { open: open, close: close, init: init }; |
| 101 |
})(); |
| 102 |
|