# subscription/2.3.0/assets/js/admin-components/modal.js

Subscriptions for WooCommerce with Stripe Recurring Payments, version 2.3.0. 102 lines.

- Page: https://pluginprobe.com/plugins/subscription/2.3.0/code/assets/js/admin-components/modal.js
- Raw: https://pluginprobe.com/plugins/subscription/2.3.0/raw/assets/js/admin-components/modal.js
- Modified: 2026-09-14T12:17:12+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/subscription/2.3.0/code/assets/js/admin-components/modal.js#L10-L20`.

```javascript
/**
 * WPSubsModal — reusable centered dialog over a dimmed backdrop.
 *
 * Behaviour only; the visual comes from the .wpsubs-modal CSS. A modal is a
 * `.wpsubs-modal[hidden]` element with an id. Any control opens it via
 * `data-wpsubs-modal-open="<modal-id>"`; any control inside closes it via
 * `data-wpsubs-modal-close` (the backdrop and the header/footer buttons use it).
 * Escape also closes. No per-page wiring needed.
 *
 * A modal with `data-wpsubs-modal-autoopen` opens automatically on load (e.g. the
 * Pro-upgrade preview modals). Opening locks body scroll; it's restored when the
 * last open modal closes.
 *
 * Usage:
 *   PHP: wpsubs_render_modal( $args )  — renders the markup
 *   JS:  auto-inits; WPSubsModal.open(id) / .close(id) available programmatically
 *
 * Events fired on the modal element (bubbles):
 *   wpsubs:modal:open / wpsubs:modal:close
 */
(function () {
  "use strict";

  /**
   * @param {string|HTMLElement} target Modal id or element.
   * @return {HTMLElement|null}
   */
  function resolve(target) {
    if (target instanceof HTMLElement) return target;
    return document.getElementById(String(target));
  }

  function open(target) {
    var modal = resolve(target);
    if (!modal) return;
    modal.hidden = false;
    document.body.style.overflow = "hidden";
    modal.dispatchEvent(new CustomEvent("wpsubs:modal:open", { bubbles: true }));
    var focusable = modal.querySelector("input, textarea, select, button:not([data-wpsubs-modal-close])");
    if (focusable) focusable.focus();
  }

  function close(target) {
    var modal = resolve(target);
    if (!modal) return;
    modal.hidden = true;
    // Restore body scroll only when no modal remains open.
    if (!document.querySelector(".wpsubs-modal:not([hidden])")) {
      document.body.style.overflow = "";
    }
    modal.dispatchEvent(new CustomEvent("wpsubs:modal:close", { bubbles: true }));
  }

  function closeAll() {
    document.querySelectorAll(".wpsubs-modal:not([hidden])").forEach(function (m) {
      close(m);
    });
  }

  // Delegated open/close — works for markup added after load too.
  document.addEventListener("click", function (e) {
    var opener = e.target.closest("[data-wpsubs-modal-open]");
    if (opener) {
      e.preventDefault();
      open(opener.getAttribute("data-wpsubs-modal-open"));
      return;
    }
    var closer = e.target.closest("[data-wpsubs-modal-close]");
    if (closer) {
      e.preventDefault();
      var modal = closer.closest(".wpsubs-modal");
      if (modal) close(modal);
    }
  });

  document.addEventListener("keydown", function (e) {
    if (e.key === "Escape") closeAll();
  });

  /**
   * Open any modal flagged to auto-open on load.
   *
   * @param {Document|HTMLElement} [root]
   */
  function init(root) {
    (root || document).querySelectorAll(".wpsubs-modal[data-wpsubs-modal-autoopen]").forEach(function (modal) {
      open(modal);
    });
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", function () {
      init();
    });
  } else {
    init();
  }

  // Public API
  window.WPSubsModal = { open: open, close: close, init: init };
})();

```
