just-a-walkthrough
Version: 
Framework-agnostic onboarding walkthrough / product tour library with optional React provider & Tailwind/shadcn support.
1,294 lines (1,293 loc) • 44.5 kB
JavaScript
import wt, { useState as A, useEffect as H, useRef as bt, useCallback as K, createContext as vt, useContext as kt } from "react";
var P = { exports: {} }, C = {};
/**
 * @license React
 * react-jsx-runtime.production.js
 *
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
var X;
function Et() {
  if (X) return C;
  X = 1;
  var s = Symbol.for("react.transitional.element"), t = Symbol.for("react.fragment");
  function e(o, c, l) {
    var n = null;
    if (l !== void 0 && (n = "" + l), c.key !== void 0 && (n = "" + c.key), "key" in c) {
      l = {};
      for (var i in c)
        i !== "key" && (l[i] = c[i]);
    } else l = c;
    return c = l.ref, {
      $$typeof: s,
      type: o,
      key: n,
      ref: c !== void 0 ? c : null,
      props: l
    };
  }
  return C.Fragment = t, C.jsx = e, C.jsxs = e, C;
}
var I = {};
/**
 * @license React
 * react-jsx-runtime.development.js
 *
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
var Z;
function _t() {
  return Z || (Z = 1, process.env.NODE_ENV !== "production" && (function() {
    function s(r) {
      if (r == null) return null;
      if (typeof r == "function")
        return r.$$typeof === gt ? null : r.displayName || r.name || null;
      if (typeof r == "string") return r;
      switch (r) {
        case x:
          return "Fragment";
        case L:
          return "Profiler";
        case _:
          return "StrictMode";
        case pt:
          return "Suspense";
        case ht:
          return "SuspenseList";
        case mt:
          return "Activity";
      }
      if (typeof r == "object")
        switch (typeof r.tag == "number" && console.error(
          "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
        ), r.$$typeof) {
          case N:
            return "Portal";
          case ut:
            return (r.displayName || "Context") + ".Provider";
          case ct:
            return (r._context.displayName || "Context") + ".Consumer";
          case dt:
            var h = r.render;
            return r = r.displayName, r || (r = h.displayName || h.name || "", r = r !== "" ? "ForwardRef(" + r + ")" : "ForwardRef"), r;
          case ft:
            return h = r.displayName || null, h !== null ? h : s(r.type) || "Memo";
          case B:
            h = r._payload, r = r._init;
            try {
              return s(r(h));
            } catch {
            }
        }
      return null;
    }
    function t(r) {
      return "" + r;
    }
    function e(r) {
      try {
        t(r);
        var h = !1;
      } catch {
        h = !0;
      }
      if (h) {
        h = console;
        var m = h.error, w = typeof Symbol == "function" && Symbol.toStringTag && r[Symbol.toStringTag] || r.constructor.name || "Object";
        return m.call(
          h,
          "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
          w
        ), t(r);
      }
    }
    function o(r) {
      if (r === x) return "<>";
      if (typeof r == "object" && r !== null && r.$$typeof === B)
        return "<...>";
      try {
        var h = s(r);
        return h ? "<" + h + ">" : "<...>";
      } catch {
        return "<...>";
      }
    }
    function c() {
      var r = M.A;
      return r === null ? null : r.getOwner();
    }
    function l() {
      return Error("react-stack-top-frame");
    }
    function n(r) {
      if (U.call(r, "key")) {
        var h = Object.getOwnPropertyDescriptor(r, "key").get;
        if (h && h.isReactWarning) return !1;
      }
      return r.key !== void 0;
    }
    function i(r, h) {
      function m() {
        Y || (Y = !0, console.error(
          "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
          h
        ));
      }
      m.isReactWarning = !0, Object.defineProperty(r, "key", {
        get: m,
        configurable: !0
      });
    }
    function a() {
      var r = s(this.type);
      return q[r] || (q[r] = !0, console.error(
        "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
      )), r = this.props.ref, r !== void 0 ? r : null;
    }
    function u(r, h, m, w, T, k, D, F) {
      return m = k.ref, r = {
        $$typeof: y,
        type: r,
        key: h,
        props: k,
        _owner: T
      }, (m !== void 0 ? m : null) !== null ? Object.defineProperty(r, "ref", {
        enumerable: !1,
        get: a
      }) : Object.defineProperty(r, "ref", { enumerable: !1, value: null }), r._store = {}, Object.defineProperty(r._store, "validated", {
        configurable: !1,
        enumerable: !1,
        writable: !0,
        value: 0
      }), Object.defineProperty(r, "_debugInfo", {
        configurable: !1,
        enumerable: !1,
        writable: !0,
        value: null
      }), Object.defineProperty(r, "_debugStack", {
        configurable: !1,
        enumerable: !1,
        writable: !0,
        value: D
      }), Object.defineProperty(r, "_debugTask", {
        configurable: !1,
        enumerable: !1,
        writable: !0,
        value: F
      }), Object.freeze && (Object.freeze(r.props), Object.freeze(r)), r;
    }
    function p(r, h, m, w, T, k, D, F) {
      var b = h.children;
      if (b !== void 0)
        if (w)
          if (yt(b)) {
            for (w = 0; w < b.length; w++)
              d(b[w]);
            Object.freeze && Object.freeze(b);
          } else
            console.error(
              "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
            );
        else d(b);
      if (U.call(h, "key")) {
        b = s(r);
        var S = Object.keys(h).filter(function(xt) {
          return xt !== "key";
        });
        w = 0 < S.length ? "{key: someKey, " + S.join(": ..., ") + ": ...}" : "{key: someKey}", G[b + w] || (S = 0 < S.length ? "{" + S.join(": ..., ") + ": ...}" : "{}", console.error(
          `A props object containing a "key" prop is being spread into JSX:
  let props = %s;
  <%s {...props} />
React keys must be passed directly to JSX without using spread:
  let props = %s;
  <%s key={someKey} {...props} />`,
          w,
          b,
          S,
          b
        ), G[b + w] = !0);
      }
      if (b = null, m !== void 0 && (e(m), b = "" + m), n(h) && (e(h.key), b = "" + h.key), "key" in h) {
        m = {};
        for (var W in h)
          W !== "key" && (m[W] = h[W]);
      } else m = h;
      return b && i(
        m,
        typeof r == "function" ? r.displayName || r.name || "Unknown" : r
      ), u(
        r,
        b,
        k,
        T,
        c(),
        m,
        D,
        F
      );
    }
    function d(r) {
      typeof r == "object" && r !== null && r.$$typeof === y && r._store && (r._store.validated = 1);
    }
    var f = wt, y = Symbol.for("react.transitional.element"), N = Symbol.for("react.portal"), x = Symbol.for("react.fragment"), _ = Symbol.for("react.strict_mode"), L = Symbol.for("react.profiler"), ct = Symbol.for("react.consumer"), ut = Symbol.for("react.context"), dt = Symbol.for("react.forward_ref"), pt = Symbol.for("react.suspense"), ht = Symbol.for("react.suspense_list"), ft = Symbol.for("react.memo"), B = Symbol.for("react.lazy"), mt = Symbol.for("react.activity"), gt = Symbol.for("react.client.reference"), M = f.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, U = Object.prototype.hasOwnProperty, yt = Array.isArray, $ = console.createTask ? console.createTask : function() {
      return null;
    };
    f = {
      react_stack_bottom_frame: function(r) {
        return r();
      }
    };
    var Y, q = {}, J = f.react_stack_bottom_frame.bind(
      f,
      l
    )(), V = $(o(l)), G = {};
    I.Fragment = x, I.jsx = function(r, h, m, w, T) {
      var k = 1e4 > M.recentlyCreatedOwnerStacks++;
      return p(
        r,
        h,
        m,
        !1,
        w,
        T,
        k ? Error("react-stack-top-frame") : J,
        k ? $(o(r)) : V
      );
    }, I.jsxs = function(r, h, m, w, T) {
      var k = 1e4 > M.recentlyCreatedOwnerStacks++;
      return p(
        r,
        h,
        m,
        !0,
        w,
        T,
        k ? Error("react-stack-top-frame") : J,
        k ? $(o(r)) : V
      );
    };
  })()), I;
}
var Q;
function Tt() {
  return Q || (Q = 1, process.env.NODE_ENV === "production" ? P.exports = Et() : P.exports = _t()), P.exports;
}
var g = Tt();
let tt = !1;
try {
  typeof process < "u" && process.env?.JUST_A_WALKTHROUGH_DEBUG === "true" && (tt = !0);
} catch {
}
function et() {
  return typeof window < "u" && window.__JUST_A_WALKTHROUGH_DEBUG ? !0 : tt;
}
function v(s, t, e, o) {
  et();
}
new Proxy({}, {
  get(s, t) {
    return (...e) => {
      if (et())
        try {
          console[t]?.("[walkthrough]", ...e);
        } catch {
        }
    };
  }
});
const R = typeof window < "u" && typeof document < "u";
function St(s) {
  try {
    const t = document.implementation.createHTMLDocument("wt"), e = t.createElement("div");
    e.innerHTML = s;
    const o = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEMPLATE", "IFRAME", "OBJECT", "EMBED"]), c = ["href", "src", "xlink:href"], l = /^(https?:|mailto:|tel:|data:image\/(?:png|gif|jpeg|jpg|webp|svg\+xml);)/i, n = t.createTreeWalker(e, NodeFilter.SHOW_ELEMENT), i = [];
    for (; n.nextNode(); ) {
      const a = n.currentNode;
      if (o.has(a.tagName)) {
        i.push(a);
        continue;
      }
      for (const u of Array.from(a.attributes)) {
        const p = u.name.toLowerCase();
        if (p.startsWith("on") || p === "srcdoc") {
          a.removeAttribute(u.name);
          continue;
        }
        if (c.includes(p)) {
          const d = u.value.trim();
          d && !l.test(d) && a.removeAttribute(u.name);
        }
      }
    }
    for (const a of i) a.remove();
    return e.innerHTML;
  } catch {
    const t = document.createElement("div");
    return t.textContent = s, t.innerHTML;
  }
}
class O {
  steps;
  opts;
  index = -1;
  active = !1;
  root;
  overlayParts;
  resizeHandler = () => this.reposition();
  scrollHandler = () => this.reposition();
  keyHandler = (t) => this.onKey(t);
  mutationObserver;
  focusTrapDisposer;
  /**
   * Create a new walkthrough.
   * @param steps Ordered list of steps.
   * @param options Optional configuration.
   */
  constructor(t, e = {}) {
    this.steps = t.map((o) => ({ ...o })), this.opts = {
      backdropOpacity: e.backdropOpacity ?? 0.55,
      zIndex: e.zIndex ?? 9999,
      keyboard: e.keyboard ?? !0,
      allowBodyScroll: e.allowBodyScroll ?? !1,
      // Allow explicit 0 to mean "no waiting / single attempt" but clamp negatives
      stepWaitMs: Math.max(0, e.stepWaitMs ?? 5e3),
      // A poll interval of 0 can create a tight loop under some schedulers; treat 0 as 1ms minimum
      stepPollIntervalMs: Math.max(0, e.stepPollIntervalMs ?? 120),
      onFinish: e.onFinish ?? (() => {
      }),
      onSkip: e.onSkip ?? (() => {
      }),
      onStepChange: e.onStepChange ?? (() => {
      }),
      advanceOnTargetClick: e.advanceOnTargetClick ?? !1,
      advanceOnOverlayClick: e.advanceOnOverlayClick ?? !1,
      scrollIntoView: e.scrollIntoView ?? !0,
      scrollOptions: e.scrollOptions,
      persistProgress: e.persistProgress ?? !1,
      resume: e.resume ?? !!e.persistProgress,
      tourId: e.tourId,
      customTooltip: e.customTooltip,
      disableFocusTrap: e.disableFocusTrap ?? !1,
      theme: e.theme ?? "default",
      tooltipClass: e.tooltipClass,
      ringClass: e.ringClass,
      overlayClass: e.overlayClass,
      alwaysOnTop: e.alwaysOnTop ?? !0
    };
  }
  hasDOM() {
    return typeof window < "u" && typeof document < "u" && !!document.body;
  }
  /**
   * Begin the walkthrough. If persistence+resume is active and stored progress
   * exists, that index will be used instead of `startIndex`.
   * Safe to call multiple times (subsequent calls while active are ignored).
   */
  async start(t = 0) {
    if (this.active) return;
    if (!this.hasDOM()) {
      v("walkthrough", "start-no-dom", this.opts.tourId || "<anon>");
      return;
    }
    if (this.active = !0, this.buildDom(), v("walkthrough", "start", this.opts.tourId || "<anon>", {
      steps: this.steps.length,
      resume: this.opts.resume,
      persist: this.opts.persistProgress
    }), this.opts.allowBodyScroll || (document.body.style.overflow = "hidden"), R && (window.addEventListener("resize", this.resizeHandler, { passive: !0 }), window.addEventListener("scroll", this.scrollHandler, !0)), this.opts.keyboard && document.addEventListener("keydown", this.keyHandler), this.hasDOM() && typeof MutationObserver < "u") {
      this.mutationObserver = new MutationObserver(() => {
        this.hasDOM() && (this.reposition(), this.opts.alwaysOnTop && this.ensureRootOnTop());
      });
      try {
        this.mutationObserver.observe(document.body, {
          attributes: !0,
          childList: !0,
          subtree: !0
        });
      } catch {
      }
    }
    const e = this.loadProgress(), o = this.opts.persistProgress && this.opts.resume && e != null ? e : t;
    await this.go(o);
  }
  /**
   * Jump to an arbitrary 0‑based step index. If out of range, finishes the tour.
   * Intended for internal use & custom navigation UIs.
   */
  async go(t) {
    if (!this.active) return;
    if (t < 0 || t >= this.steps.length) return this.finish();
    if (this.index >= 0) {
      const o = this.steps[this.index];
      o.afterStep && await o.afterStep();
    }
    this.index = t, v("walkthrough", "step", this.opts.tourId || "<anon>", {
      index: this.index,
      selector: this.steps[this.index].selector
    }), this.opts.onStepChange(this.index);
    const e = this.steps[this.index];
    if (e.beforeStep && await e.beforeStep(), e._el = await this.resolveElement(e), !e._el)
      if (v(
        "walkthrough",
        e.required ? "missing-required" : "missing-optional",
        this.opts.tourId || "<anon>",
        { selector: e.selector }
      ), e.required) {
        this.skip(`Required element not found for selector: ${e.selector}`);
        return;
      } else
        return this.go(t + 1);
    v("walkthrough", "resolved", this.opts.tourId || "<anon>", {
      index: this.index,
      selector: e.selector
    }), this.renderStep(e), this.saveProgress();
  }
  /** Advance to next step. */
  next() {
    this.go(this.index + 1);
  }
  /** Go back to previous step. */
  prev() {
    this.go(this.index - 1);
  }
  /**
   * Complete the walkthrough (fires `onFinish`, marks persisted state as completed).
   * Further navigation is disabled until a new instance is started.
   */
  finish() {
    this.active && (this.markCompleted(), this.cleanup(), v("walkthrough", "finish", this.opts.tourId || "<anon>", {
      finalIndex: this.index
    }), this.opts.onFinish());
  }
  /**
   * Abort the walkthrough (fires `onSkip` with a reason token; does NOT mark completed).
   * Useful for user cancellations or required element failures.
   */
  skip(t) {
    this.active && (this.cleanup(), v("walkthrough", "skip", this.opts.tourId || "<anon>", {
      index: this.index
    }), this.opts.onSkip(t));
  }
  /** Hard teardown: remove DOM & listeners without firing callbacks or marking progress. */
  destroy() {
    this.cleanup(), v("walkthrough", "destroy", this.opts.tourId || "<anon>");
  }
  /**
   * Attempt to resolve a DOM element for a step, polling until found or timeout.
   * Fast path: when effective wait is 0 only a single synchronous query is performed.
   */
  async resolveElement(t) {
    const e = Math.max(
      0,
      t.waitMs == null ? this.opts.stepWaitMs : t.waitMs
    );
    if (e === 0)
      return document.querySelector(t.selector) || null;
    const o = Date.now() + e, c = Math.max(1, this.opts.stepPollIntervalMs);
    for (; Date.now() <= o; ) {
      const l = document.querySelector(t.selector);
      if (l) return l;
      await new Promise((n) => setTimeout(n, c));
    }
    return null;
  }
  /** Lazily build overlay DOM (idempotent). */
  buildDom() {
    if (this.root) return;
    const t = document.createElement("div");
    if (t.className = "wt-root", t.setAttribute("data-walkthrough", ""), t.style.position = "fixed", t.style.inset = "0", t.style.zIndex = String(this.opts.zIndex), t.style.pointerEvents = "none", this.opts.theme === "default" && !document.getElementById("__walkthrough_styles")) {
      const p = document.createElement("style");
      p.id = "__walkthrough_styles", p.textContent = this.generateStyles(), document.head.appendChild(p);
    }
    const e = (p) => {
      const d = document.createElement("div");
      return d.className = `wt-part wt-overlay ${p}`, d.style.position = "fixed", this.opts.theme === "default" ? d.style.background = `rgba(0,0,0,${this.opts.backdropOpacity})` : this.opts.theme === "tailwind" && d.classList.add("bg-black/60"), this.opts.overlayClass && d.classList.add(...this.opts.overlayClass.split(/\s+/).filter(Boolean)), d.style.pointerEvents = this.opts.advanceOnOverlayClick ? "auto" : "none", d;
    }, o = e("wt-top"), c = e("wt-left"), l = e("wt-right"), n = e("wt-bottom"), i = document.createElement("div");
    i.className = "wt-ring", i.style.position = "fixed", i.style.pointerEvents = "none", this.opts.theme === "default" ? (i.style.border = "2px solid #6366f1", i.style.borderRadius = "8px", i.style.boxShadow = "0 0 0 4px rgba(99,102,241,0.35), 0 4px 18px rgba(0,0,0,0.4)", i.style.transition = "all 180ms cubic-bezier(.4,0,.2,1)") : this.opts.theme === "tailwind" && i.classList.add(
      "rounded-lg",
      "border-2",
      "border-primary",
      "shadow-[0_0_0_4px_rgba(99,102,241,0.35)]",
      "transition-all"
    ), this.opts.ringClass && i.classList.add(...this.opts.ringClass.split(/\s+/).filter(Boolean));
    const a = document.createElement("div");
    a.className = "wt-tooltip", a.style.position = "fixed", a.style.maxWidth = "340px", a.style.pointerEvents = "auto", a.style.display = "flex", a.style.flexDirection = "column", a.style.gap = "12px", this.opts.theme === "tailwind" ? a.classList.add(
      "rounded-lg",
      "border",
      "p-4",
      "bg-popover",
      "text-popover-foreground",
      "shadow-lg"
    ) : this.opts.theme === "unstyled" && (a.removeAttribute("style"), a.style.position = "fixed", a.style.pointerEvents = "auto"), this.opts.tooltipClass && a.classList.add(
      ...this.opts.tooltipClass.split(/\s+/).filter(Boolean)
    );
    const u = document.createElement("div");
    u.className = "wt-live", u.setAttribute("aria-live", "polite"), u.setAttribute("role", "status"), u.style.position = "absolute", u.style.width = "1px", u.style.height = "1px", u.style.overflow = "hidden", u.style.clip = "rect(1px,1px,1px,1px)", u.style.whiteSpace = "nowrap", u.style.pointerEvents = "none", t.appendChild(u), [o, c, l, n, i, a].forEach(
      (p) => t.appendChild(p)
    ), document.body.appendChild(t), this.root = t, this.overlayParts = { top: o, left: c, right: l, bottom: n, ring: i, tooltip: a, live: u }, this.opts.alwaysOnTop && this.ensureRootOnTop(), this.opts.advanceOnOverlayClick && [o, c, l, n].forEach(
      (p) => p.addEventListener("click", () => this.next())
    );
  }
  /** Ensure root remains the last <body> child (z-order correctness). */
  ensureRootOnTop() {
    if (!this.root) return;
    const t = document.body;
    t.lastElementChild !== this.root && t.appendChild(this.root);
  }
  /** Generate injected stylesheet (default theme only). */
  generateStyles() {
    return `
    .wt-root { font-family: system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
    .wt-tooltip { background: #111827EE; color: #f9fafb; border: 1px solid #374151; border-radius: 10px; padding: 16px 18px; box-shadow: 0 8px 28px -6px rgba(0,0,0,.55); }
    .wt-tooltip h3 { margin: 0 0 4px; font-size: 16px; font-weight: 600; }
    .wt-tooltip .wt-content { font-size: 14px; line-height: 1.4; }
    .wt-tooltip .wt-nav { display:flex; gap:8px; justify-content: flex-end; }
    .wt-tooltip button { all:unset; font:inherit; background:#6366f1; color:#fff; padding:6px 14px; border-radius:6px; cursor:pointer; font-size:13px; font-weight:500; box-shadow:0 2px 4px rgba(0,0,0,.25); }
    .wt-tooltip button:hover { background:#4f46e5; }
    .wt-tooltip button.wt-secondary { background:#374151; }
    .wt-tooltip button.wt-secondary:hover { background:#4b5563; }
    {
      .wt-tooltip { background:#ffffffF2; color:#111827; border-color:#e5e7eb; }
      .wt-tooltip button.wt-secondary { background:#e5e7eb; color:#111827; }
      .wt-tooltip button.wt-secondary:hover { background:#d1d5db; }
    }
    `;
  }
  /** Render highlight + tooltip for a resolved step element. */
  renderStep(t) {
    if (!t._el) return;
    const e = t.padding ?? 8;
    if (this.positionHighlight(t._el, e), this.renderTooltip(t), t.focus)
      try {
        t._el.focus();
      } catch {
      }
    if (this.opts.scrollIntoView && t._el)
      try {
        t._el.scrollIntoView(
          this.opts.scrollOptions || {
            behavior: "smooth",
            block: "center",
            inline: "center"
          }
        );
      } catch {
      }
    if (this.opts.advanceOnTargetClick) {
      const o = () => {
        t._el?.removeEventListener("click", o), this.next();
      };
      t._el.addEventListener("click", o, { once: !0 });
    }
  }
  /** Position the dark overlay panels + highlight ring around the target. */
  positionHighlight(t, e) {
    if (!R || typeof window > "u") return;
    const o = t.getBoundingClientRect(), c = e, l = o.left - c, n = o.top - c, i = o.width + c * 2, a = o.height + c * 2, { top: u, left: p, right: d, bottom: f, ring: y } = this.overlayParts;
    u.style.top = "0px", u.style.left = "0px", u.style.width = "100%", u.style.height = `${Math.max(0, n)}px`, p.style.top = `${n}px`, p.style.left = "0px", p.style.width = `${Math.max(0, l)}px`, p.style.height = `${a}px`, d.style.top = `${n}px`, d.style.left = `${l + i}px`, R && (d.style.width = `${Math.max(0, window.innerWidth - (l + i))}px`), d.style.height = `${a}px`, f.style.top = `${n + a}px`, f.style.left = "0px", f.style.width = "100%", R && (f.style.height = `${Math.max(0, window.innerHeight - (n + a))}px`), y.style.top = `${n}px`, y.style.left = `${l}px`, y.style.width = `${i}px`, y.style.height = `${a}px`, typeof window > "u";
  }
  /** Build & mount tooltip content for the current step. */
  renderTooltip(t) {
    const { tooltip: e, live: o } = this.overlayParts;
    e.innerHTML = "", o.textContent = t.title || "";
    let c = null;
    const l = () => {
      const i = document.createElement("div");
      if (i.className = "wt-nav", this.index > 0) {
        const d = document.createElement("button");
        d.className = "wt-secondary", d.textContent = "Back", d.addEventListener("click", () => this.prev()), i.appendChild(d);
      }
      const a = document.createElement("button");
      a.className = "wt-secondary", a.textContent = "Skip", a.addEventListener("click", () => this.skip("user-skip")), i.appendChild(a);
      const u = document.createElement("button"), p = this.index === this.steps.length - 1;
      return u.textContent = p ? "Done" : "Next", u.addEventListener(
        "click",
        () => p ? this.finish() : this.next()
      ), i.appendChild(u), i;
    };
    if (this.opts.customTooltip) {
      const i = this.opts.customTooltip({
        step: t,
        index: this.index,
        total: this.steps.length,
        api: this,
        defaultNav: l
      });
      e.appendChild(i);
    } else {
      if (t.title) {
        const i = document.createElement("h3");
        i.textContent = t.title, e.appendChild(i);
      }
      if (t.content) {
        const i = document.createElement("div");
        i.className = "wt-content", i.innerHTML = t.allowUnsafeHTML ? t.content : St(t.content), e.appendChild(i);
      }
      e.appendChild(l());
    }
    t._el && this.positionTooltip(t._el, e), this.opts.disableFocusTrap || this.setupFocusTrap(e);
    const n = e.querySelectorAll(
      O.FOCUSABLE_SEL
    );
    n.length ? (c = n[0], setTimeout(() => c?.focus(), 0)) : (e.tabIndex = -1, e.focus());
  }
  static FOCUSABLE_SEL = 'a[href], button:not([disabled]), textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select, [tabindex]:not([tabindex="-1"])';
  /** Simple cyclical focus trap for interactive tooltip controls. */
  setupFocusTrap(t) {
    this.teardownFocusTrap();
    const e = (o) => {
      if (o.key !== "Tab") return;
      const c = Array.from(
        t.querySelectorAll(O.FOCUSABLE_SEL)
      ).filter((i) => !i.hasAttribute("disabled"));
      if (!c.length) {
        o.preventDefault(), t.focus();
        return;
      }
      const l = c[0], n = c[c.length - 1];
      o.shiftKey ? document.activeElement === l && (o.preventDefault(), n.focus()) : document.activeElement === n && (o.preventDefault(), l.focus());
    };
    document.addEventListener("keydown", e, !0), this.focusTrapDisposer = () => document.removeEventListener("keydown", e, !0);
  }
  /** Remove previously installed focus trap handler if present. */
  teardownFocusTrap() {
    this.focusTrapDisposer?.(), this.focusTrapDisposer = void 0;
  }
  /** Compute and set tooltip coordinates (prefers bottom, top, right, left then clamps). */
  positionTooltip(t, e) {
    if (!R || typeof window > "u") return;
    const o = t.getBoundingClientRect(), c = 14, l = e.offsetWidth || 320, n = e.offsetHeight || 140;
    let i = o.bottom + c, a = o.left + (o.width - l) / 2, u = !1;
    const p = (d, f) => d >= 4 && d + l <= window.innerWidth - 4 && f >= 4 && f + n <= window.innerHeight - 4;
    if (p(a, i) && (u = !0), !u) {
      const d = o.top - c - n;
      p(a, d) && (i = d, u = !0);
    }
    if (!u) {
      const d = o.right + c, f = o.top + (o.height - n) / 2;
      p(d, f) && (a = d, i = f, u = !0);
    }
    if (!u) {
      const d = o.left - c - l, f = o.top + (o.height - n) / 2;
      p(d, f) && (a = d, i = f, u = !0);
    }
    u || (i = Math.min(
      Math.max(4, o.bottom + c),
      window.innerHeight - n - 4
    ), a = Math.min(
      Math.max(4, o.left + (o.width - l) / 2),
      window.innerWidth - l - 4
    )), e.style.top = `${i}px`, e.style.left = `${a}px`;
  }
  /** Recompute highlight + tooltip position (on resize/scroll/mutation). */
  reposition() {
    if (!this.active) return;
    const t = this.steps[this.index];
    !t || !t._el || (this.positionHighlight(t._el, t.padding ?? 8), this.positionTooltip(t._el, this.overlayParts.tooltip), typeof window > "u");
  }
  /** Keyboard handler for global navigation / dismissal keys. */
  onKey(t) {
    if (this.active)
      switch (t.key) {
        case "Escape":
          this.skip("esc");
          break;
        case "ArrowRight":
        case "Enter":
          this.next();
          break;
        case "ArrowLeft":
          this.prev();
          break;
      }
  }
  /** Internal teardown used by finish/skip/destroy (difference is which callbacks fire). */
  cleanup() {
    this.active && (this.active = !1, this.opts.allowBodyScroll || (document.body.style.overflow = ""), R && (window.removeEventListener("resize", this.resizeHandler), window.removeEventListener("scroll", this.scrollHandler, !0)), document.removeEventListener("keydown", this.keyHandler), this.teardownFocusTrap(), this.mutationObserver?.disconnect(), this.root?.parentNode && this.root.parentNode.removeChild(this.root), v("walkthrough", "cleanup", this.opts.tourId || "<anon>", {
      index: this.index
    }));
  }
  // Persistence helpers
  /** Build the localStorage key (if persistence enabled). */
  progressKey() {
    return this.opts.tourId ? `__walkthrough:${this.opts.tourId}` : void 0;
  }
  /** Persist current index (not yet completed). */
  saveProgress() {
    if (!(!this.opts.persistProgress || !this.opts.tourId))
      try {
        const t = this.progressKey();
        t && localStorage.setItem(
          t,
          JSON.stringify({
            index: this.index,
            completed: !1,
            ts: Date.now()
          })
        );
      } catch {
      }
  }
  /** Load prior progress index (returns null if completed or unavailable). */
  loadProgress() {
    if (!this.opts.persistProgress || !this.opts.tourId) return null;
    try {
      const t = this.progressKey();
      if (!t) return null;
      const e = localStorage.getItem(t);
      if (!e) return null;
      const o = JSON.parse(e);
      if (o.completed) return null;
      if (typeof o.index == "number") return o.index;
    } catch {
    }
    return null;
  }
  /** Persist completion + final index. */
  markCompleted() {
    if (!(!this.opts.persistProgress || !this.opts.tourId))
      try {
        const t = this.progressKey();
        t && localStorage.setItem(
          t,
          JSON.stringify({
            index: this.index,
            completed: !0,
            ts: Date.now()
          })
        );
      } catch {
      }
  }
  /** Remove any stored progress / completion state for this tour instance. */
  clearProgress() {
    if (this.opts.persistProgress && this.opts.tourId)
      try {
        const t = this.progressKey();
        t && localStorage.removeItem(t);
      } catch {
      }
  }
}
function j(s, t) {
  const e = new O(s, t);
  return e.start(), e;
}
class rt {
  tours;
  currentIndex = -1;
  currentInstance = null;
  constructor(t) {
    this.tours = t;
  }
  start() {
    this.advanceToNext();
  }
  advanceToNext() {
    if (this.currentIndex++, this.currentInstance && (this.currentInstance.destroy(), this.currentInstance = null), this.currentIndex >= this.tours.length) return;
    const t = this.tours[this.currentIndex];
    if (t.options?.persistProgress && t.options.tourId)
      try {
        const o = localStorage.getItem(
          `__walkthrough:${t.options.tourId}`
        );
        if (o && JSON.parse(o)?.completed) {
          this.advanceToNext();
          return;
        }
      } catch {
      }
    const e = new O(t.steps, {
      ...t.options,
      onFinish: () => {
        t.options?.onFinish?.(), this.advanceToNext();
      },
      onSkip: (o) => {
        t.options?.onSkip?.(o);
      }
    });
    this.currentInstance = e, e.start();
  }
  stop() {
    this.currentInstance?.skip("chain-stop");
  }
}
const E = [];
function Rt(s) {
  const t = E.findIndex((e) => e.id === s.id);
  t >= 0 ? E.splice(t, 1, s) : E.push(s), v("orchestrator", "register", s.id, { match: s.match });
}
function Ct(s) {
  s.forEach(Rt);
}
function It() {
  return [...E];
}
function Nt() {
  E.splice(0, E.length);
}
function ot(s) {
  const t = E.filter((e) => Ot(e.match, s));
  return v("orchestrator", "match", s, { count: t.length }), t;
}
function Ot(s, t) {
  return typeof s == "string" ? s.endsWith("*") ? t.startsWith(s.slice(0, -1)) : t === s : s instanceof RegExp ? s.test(t) : s(t);
}
function st(s) {
  return s ? `__walkthrough:${s}` : void 0;
}
function z(s) {
  try {
    const t = st(s);
    if (!t) return !1;
    const e = localStorage.getItem(t);
    return e ? !!JSON.parse(e)?.completed : !1;
  } catch {
    return !1;
  }
}
function nt(s) {
  try {
    const t = st(s);
    t && localStorage.removeItem(t);
  } catch {
  }
}
async function it({
  pathname: s,
  firstOnly: t
}) {
  const e = ot(s).filter(
    (c) => (c.trigger ?? "auto") === "auto"
  ), o = [];
  for (const c of e) {
    const {
      id: l,
      condition: n,
      oncePerSession: i,
      options: a,
      steps: u,
      skipIfCompleted: p = !0
    } = c;
    if (!(i && sessionStorage.getItem(`__wt_session_started:${l}`)) && !(p && a?.persistProgress && (a.tourId || l) && z(a.tourId || l)) && !(n && !await n()) && (j(u, { tourId: a?.tourId || l, ...a }), sessionStorage.setItem(`__wt_session_started:${l}`, "1"), o.push(l), t))
      break;
  }
  return o;
}
async function at(s) {
  const t = ot(s).filter(
    (n) => (n.trigger ?? "auto") === "auto"
  ), e = [];
  for (const n of t) {
    const {
      id: i,
      condition: a,
      oncePerSession: u,
      options: p,
      skipIfCompleted: d = !0
    } = n;
    u && sessionStorage.getItem(`__wt_session_started:${i}`) || d && p?.persistProgress && p.tourId && z(p.tourId) || a && !await a() || e.push(n);
  }
  if (!e.length)
    return { ids: [], chain: null };
  const o = [...e].sort((n, i) => (n.order ?? 0) - (i.order ?? 0)), c = o.map((n) => n.id);
  o.forEach(
    (n) => sessionStorage.setItem(`__wt_session_started:${n.id}`, "1")
  );
  const l = new rt(
    o.map((n) => ({
      id: n.id,
      steps: n.steps,
      options: { tourId: n.id, ...n.options }
    }))
  );
  return l.start(), { ids: c, chain: l };
}
function Lt(s) {
  const t = E.find((e) => e.id === s);
  if (!t) throw new Error(`Tour '${s}' not registered`);
  return j(t.steps, { tourId: t.id, ...t.options });
}
function Pt() {
  E.forEach((s) => {
    (s.options?.tourId || s.id) && nt(s.options?.tourId || s.id);
  });
}
async function At(s) {
  const t = await import(
    /* @vite-ignore */
    s
  ), e = t.tours ?? t.default ?? t.toursDefault;
  return Array.isArray(e) && Ct(e), e;
}
function Mt({
  pathname: s,
  className: t = "",
  style: e,
  chainMatches: o
}) {
  const [c, l] = A(0), [n, i] = A(() => {
    try {
      return localStorage.getItem("__wt_devpanel_collapsed") === "1";
    } catch {
      return !1;
    }
  });
  H(() => {
    try {
      localStorage.setItem("__wt_devpanel_collapsed", n ? "1" : "0");
    } catch {
    }
  }, [n]);
  const a = It(), u = () => l((x) => x + 1), p = s || (typeof window < "u" ? window.location.pathname : "/"), d = async () => {
    o ? await at(p) : await it({ pathname: p }), u();
  }, f = (x) => {
    const _ = a.find((L) => L.id === x);
    _ && j(_.steps, { tourId: _.id, ..._.options });
  }, y = (x) => {
    nt(x), u();
  }, N = () => {
    Pt(), u();
  };
  return n ? /* @__PURE__ */ g.jsxs(
    "button",
    {
      type: "button",
      onClick: () => i(!1),
      className: `walkthrough-dev-panel-collapsed ${t}`,
      style: {
        position: "fixed",
        bottom: 16,
        right: 16,
        zIndex: 999999,
        background: "#111",
        color: "#fff",
        border: "1px solid #333",
        borderRadius: 20,
        padding: "6px 12px",
        fontSize: 12,
        fontFamily: "ui-sans-serif, system-ui, sans-serif",
        cursor: "pointer",
        boxShadow: "0 4px 12px rgba(0,0,0,0.5)",
        ...e
      },
      title: "Expand walkthrough dev panel",
      children: [
        "Tours (",
        a.length,
        ") ▴"
      ]
    }
  ) : /* @__PURE__ */ g.jsxs(
    "div",
    {
      className: "walkthrough-dev-panel " + t,
      style: {
        position: "fixed",
        bottom: 16,
        right: 16,
        width: 320,
        maxHeight: "60vh",
        overflowY: "auto",
        background: "#111",
        color: "#fff",
        fontFamily: "ui-sans-serif, system-ui, sans-serif",
        fontSize: 12,
        border: "1px solid #333",
        borderRadius: 8,
        padding: 12,
        boxShadow: "0 4px 12px rgba(0,0,0,0.5)",
        zIndex: 999999,
        ...e
      },
      "data-refresh": c,
      children: [
        /* @__PURE__ */ g.jsxs(
          "div",
          {
            style: {
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              marginBottom: 8
            },
            children: [
              /* @__PURE__ */ g.jsx("strong", { style: { fontSize: 13 }, children: "Walkthrough Dev Panel" }),
              /* @__PURE__ */ g.jsxs("div", { style: { display: "flex", gap: 6 }, children: [
                /* @__PURE__ */ g.jsx(
                  "button",
                  {
                    type: "button",
                    onClick: () => i(!0),
                    style: {
                      background: "#222",
                      color: "#fff",
                      border: "1px solid #444",
                      borderRadius: 4,
                      padding: "2px 6px",
                      cursor: "pointer"
                    },
                    title: "Minimize",
                    children: "_"
                  }
                ),
                /* @__PURE__ */ g.jsx(
                  "button",
                  {
                    type: "button",
                    onClick: N,
                    style: {
                      background: "#222",
                      color: "#fff",
                      border: "1px solid #444",
                      borderRadius: 4,
                      padding: "2px 6px",
                      cursor: "pointer"
                    },
                    children: "Reset"
                  }
                )
              ] })
            ]
          }
        ),
        /* @__PURE__ */ g.jsxs("div", { style: { display: "flex", gap: 8, marginBottom: 8 }, children: [
          /* @__PURE__ */ g.jsx(
            "button",
            {
              type: "button",
              onClick: d,
              style: {
                flex: 1,
                background: "#2563eb",
                color: "#fff",
                border: "none",
                borderRadius: 4,
                padding: "4px 6px",
                cursor: "pointer"
              },
              children: o ? "Chain Matches" : "Run Matches"
            }
          ),
          /* @__PURE__ */ g.jsx(
            "button",
            {
              type: "button",
              onClick: u,
              style: {
                background: "#222",
                color: "#fff",
                border: "1px solid #444",
                borderRadius: 4,
                padding: "4px 6px",
                cursor: "pointer"
              },
              children: "Refresh"
            }
          )
        ] }),
        a.length === 0 && /* @__PURE__ */ g.jsx("div", { style: { opacity: 0.7 }, children: "No tours registered." }),
        /* @__PURE__ */ g.jsx(
          "ul",
          {
            style: {
              listStyle: "none",
              margin: 0,
              padding: 0,
              display: "flex",
              flexDirection: "column",
              gap: 8
            },
            children: a.map((x) => {
              const _ = z(x.id);
              return /* @__PURE__ */ g.jsxs(
                "li",
                {
                  style: {
                    border: "1px solid #333",
                    borderRadius: 6,
                    padding: 8,
                    background: "#1c1c1c"
                  },
                  children: [
                    /* @__PURE__ */ g.jsxs(
                      "div",
                      {
                        style: {
                          display: "flex",
                          justifyContent: "space-between",
                          alignItems: "center",
                          marginBottom: 4
                        },
                        children: [
                          /* @__PURE__ */ g.jsx("span", { style: { fontWeight: 600 }, children: x.id }),
                          /* @__PURE__ */ g.jsx("span", { style: { fontSize: 10, opacity: 0.7 }, children: _ ? "completed" : "pending" })
                        ]
                      }
                    ),
                    /* @__PURE__ */ g.jsxs("div", { style: { fontSize: 10, opacity: 0.7, marginBottom: 4 }, children: [
                      "match:",
                      " ",
                      typeof x.match == "string" ? x.match : x.match instanceof RegExp ? x.match.toString() : "fn"
                    ] }),
                    /* @__PURE__ */ g.jsxs("div", { style: { display: "flex", gap: 6 }, children: [
                      /* @__PURE__ */ g.jsx(
                        "button",
                        {
                          type: "button",
                          onClick: () => f(x.id),
                          style: {
                            flex: 1,
                            background: "#10b981",
                            color: "#fff",
                            border: "none",
                            borderRadius: 4,
                            padding: "4px 6px",
                            cursor: "pointer"
                          },
                          children: "Start"
                        }
                      ),
                      /* @__PURE__ */ g.jsx(
                        "button",
                        {
                          type: "button",
                          onClick: () => y(x.id),
                          style: {
                            background: "#dc2626",
                            color: "#fff",
                            border: "none",
                            borderRadius: 4,
                            padding: "4px 6px",
                            cursor: "pointer"
                          },
                          children: "Reset"
                        }
                      )
                    ] })
                  ]
                },
                x.id
              );
            })
          }
        )
      ]
    }
  );
}
const lt = vt(
  void 0
);
function $t({
  children: s,
  autoStart: t
}) {
  const [e, o] = A(null), [c, l] = A(null), n = !!e, i = bt(null), a = K(
    (p, d) => {
      i.current?.destroy();
      const f = j(p, {
        ...d || {},
        onStepChange: (y) => {
          l(y), d?.onStepChange?.(y);
        },
        onFinish: () => {
          d?.onFinish?.(), i.current = null, o(null), l(null);
        },
        onSkip: (y) => {
          d?.onSkip?.(y), i.current = null, o(null), l(null);
        }
      });
      return i.current = f, o(f), f;
    },
    []
  ), u = K((p) => {
    const d = new rt(
      p.map((f) => ({
        ...f,
        options: {
          ...f.options || {},
          onStepChange: (y) => {
            l(y), f.options?.onStepChange?.(y);
          },
          onFinish: () => {
            f.options?.onFinish?.();
          },
          onSkip: (y) => {
            f.options?.onSkip?.(y);
          }
        }
      }))
    );
    return d.start(), d;
  }, []);
  return H(() => {
    t && a(t.steps, t.options);
  }, []), /* @__PURE__ */ g.jsx(
    lt.Provider,
    {
      value: { start: a, chain: u, active: n, currentIndex: c, instance: i.current },
      children: s
    }
  );
}
function Dt() {
  const s = kt(lt);
  if (!s) throw new Error("useWalkthrough must be used within WalkthroughProvider");
  return s;
}
function Ft({
  pathname: s,
  firstOnly: t,
  chain: e,
  dynamicModule: o,
  onStartIds: c
}) {
  return H(() => {
    let l = !1;
    return (async () => {
      if (o)
        try {
          await At(o);
        } catch (n) {
          v("react", "dyn-load-fail", o, { error: n.message });
        }
      if (e) {
        const { ids: n } = await at(s);
        !l && n.length && c && c(n);
      } else {
        const n = await it({ pathname: s, firstOnly: t });
        !l && n.length && c && c(n);
      }
    })(), () => {
      l = !0;
    };
  }, [s, t, e, o, c]), null;
}
export {
  Ft as RouteOrchestrator,
  O as Walkthrough,
  rt as WalkthroughChain,
  Mt as WalkthroughDevPanel,
  $t as WalkthroughProvider,
  at as chainAutoMatches,
  nt as clearTourProgress,
  Nt as clearTours,
  ot as findMatchingTours,
  z as isTourCompleted,
  It as listTours,
  At as loadTours,
  Rt as registerTour,
  Ct as registerTours,
  Pt as resetAllTourProgress,
  it as startAutoMatches,
  Lt as startTourById,
  j as startWalkthrough,
  Dt as useWalkthrough
};
//# sourceMappingURL=react-walkthrough.esm.js.map