const y = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class C {
  /** Plugin version - override in subclass if needed */
  version = "1.0.0";
  /** CSS styles to inject into the grid's shadow DOM */
  styles;
  /** Custom cell renderers keyed by type name */
  cellRenderers;
  /** Custom header renderers keyed by type name */
  headerRenderers;
  /** Custom cell editors keyed by type name */
  cellEditors;
  /** The grid instance this plugin is attached to */
  grid;
  /** Plugin configuration - merged with defaults in attach() */
  config;
  /** User-provided configuration from constructor */
  userConfig;
  /**
   * Default configuration - subclasses should override this getter.
   * Note: This must be a getter (not property initializer) for proper inheritance
   * since property initializers run after parent constructor.
   */
  get defaultConfig() {
    return {};
  }
  constructor(e = {}) {
    this.userConfig = e;
  }
  /**
   * Called when the plugin is attached to a grid.
   * Override to set up event listeners, initialize state, etc.
   */
  attach(e) {
    this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
  }
  /**
   * Called when the plugin is detached from a grid.
   * Override to clean up event listeners, timers, etc.
   */
  detach() {
  }
  /**
   * Get another plugin instance from the same grid.
   * Use for inter-plugin communication.
   */
  getPlugin(e) {
    return this.grid?.getPlugin(e);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(e, n) {
    this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: n, bubbles: !0 }));
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a lightweight style update without rebuilding DOM.
   * Use this instead of requestRender() when only CSS classes need updating.
   */
  requestAfterRender() {
    this.grid?.requestAfterRender?.();
  }
  /**
   * Get the current rows from the grid.
   */
  get rows() {
    return this.grid?.rows ?? [];
  }
  /**
   * Get the original unfiltered/unprocessed rows from the grid.
   * Use this when you need all source data regardless of active filters.
   */
  get sourceRows() {
    return this.grid?.sourceRows ?? [];
  }
  /**
   * Get the current columns from the grid.
   */
  get columns() {
    return this.grid?.columns ?? [];
  }
  /**
   * Get only visible columns from the grid (excludes hidden).
   * Use this for rendering that needs to match the grid template.
   */
  get visibleColumns() {
    return this.grid?.visibleColumns ?? [];
  }
  /**
   * Get the shadow root of the grid.
   */
  get shadowRoot() {
    return this.grid?.shadowRoot ?? null;
  }
  /**
   * Get the disconnect signal for event listener cleanup.
   * This signal is aborted when the grid disconnects from the DOM.
   * Use this when adding event listeners that should be cleaned up automatically.
   *
   * Best for:
   * - Document/window-level listeners added in attach()
   * - Listeners on the grid element itself
   * - Any listener that should persist across renders
   *
   * Not needed for:
   * - Listeners on elements created in afterRender() (removed with element)
   *
   * @example
   * element.addEventListener('click', handler, { signal: this.disconnectSignal });
   * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
   */
  get disconnectSignal() {
    return this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const e = this.grid?.gridConfig?.icons ?? {};
    return { ...y, ...e };
  }
  /**
   * Resolve an icon value to string or HTMLElement.
   * Checks plugin config first, then grid-level icons, then defaults.
   *
   * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
   * @param pluginOverride - Optional plugin-level override
   * @returns The resolved icon value
   */
  resolveIcon(e, n) {
    return n !== void 0 ? n : this.gridIcons[e];
  }
  /**
   * Set an icon value on an element.
   * Handles both string (text/HTML) and HTMLElement values.
   *
   * @param element - The element to set the icon on
   * @param icon - The icon value (string or HTMLElement)
   */
  setIcon(e, n) {
    typeof n == "string" ? e.innerHTML = n : n instanceof HTMLElement && (e.innerHTML = "", e.appendChild(n.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
const I = ".tbw-context-menu{position:fixed;background:light-dark(#f5f5f5,#2a2a2a);color:light-dark(#222,#eee);border:1px solid light-dark(#d0d0d4,#454545);border-radius:4px;box-shadow:0 2px 10px #00000026;min-width:160px;padding:4px 0;z-index:10000;font-size:13px;font-family:system-ui,sans-serif}.tbw-context-menu-item{display:flex;align-items:center;padding:6px 12px;cursor:pointer;gap:8px}.tbw-context-menu-item:hover:not(.disabled){background:light-dark(#e8e8e8,#3a3a3a)}.tbw-context-menu-item.disabled{opacity:.5;cursor:default}.tbw-context-menu-item.danger{color:light-dark(#c00,#f66)}.tbw-context-menu-icon{width:16px;text-align:center}.tbw-context-menu-label{flex:1}.tbw-context-menu-shortcut{color:light-dark(#888,#888);font-size:11px}.tbw-context-menu-arrow{font-size:10px;color:light-dark(#888,#888)}.tbw-context-menu-separator{height:1px;background:light-dark(#d0d0d4,#454545);margin:4px 0}";
function x(s, e) {
  return (typeof s == "function" ? s(e) : s).filter((i) => !(i.hidden === !0 || typeof i.hidden == "function" && i.hidden(e)));
}
function M(s, e) {
  return s.disabled === !0 ? !0 : typeof s.disabled == "function" ? s.disabled(e) : !1;
}
function w(s, e, n, i = y.submenuArrow) {
  const r = document.createElement("div");
  r.className = "tbw-context-menu", r.setAttribute("role", "menu");
  for (const o of s) {
    if (o.separator) {
      const l = document.createElement("div");
      l.className = "tbw-context-menu-separator", l.setAttribute("role", "separator"), r.appendChild(l);
      continue;
    }
    const t = document.createElement("div");
    t.className = "tbw-context-menu-item", o.cssClass && t.classList.add(o.cssClass), t.setAttribute("role", "menuitem"), t.setAttribute("data-id", o.id);
    const c = M(o, e);
    if (c && (t.classList.add("disabled"), t.setAttribute("aria-disabled", "true")), o.icon) {
      const l = document.createElement("span");
      l.className = "tbw-context-menu-icon", l.innerHTML = o.icon, t.appendChild(l);
    }
    const d = document.createElement("span");
    if (d.className = "tbw-context-menu-label", d.textContent = o.name, t.appendChild(d), o.shortcut) {
      const l = document.createElement("span");
      l.className = "tbw-context-menu-shortcut", l.textContent = o.shortcut, t.appendChild(l);
    }
    if (o.subMenu?.length) {
      const l = document.createElement("span");
      l.className = "tbw-context-menu-arrow", typeof i == "string" ? l.innerHTML = i : i instanceof HTMLElement && l.appendChild(i.cloneNode(!0)), t.appendChild(l), t.addEventListener("mouseenter", () => {
        if (t.querySelector(".tbw-context-menu") || !o.subMenu) return;
        const h = x(o.subMenu, e), a = w(h, e, n, i);
        a.classList.add("tbw-context-submenu"), a.style.position = "absolute", a.style.left = "100%", a.style.top = "0", t.style.position = "relative", t.appendChild(a);
      }), t.addEventListener("mouseleave", () => {
        const u = t.querySelector(".tbw-context-menu");
        u && u.remove();
      });
    }
    !c && o.action && !o.subMenu && t.addEventListener("click", (l) => {
      l.stopPropagation(), n(o);
    }), r.appendChild(t);
  }
  return r;
}
function E(s, e, n) {
  s.style.position = "fixed", s.style.left = `${e}px`, s.style.top = `${n}px`, s.style.visibility = "hidden", s.style.zIndex = "10000";
  const i = s.getBoundingClientRect(), r = window.innerWidth, o = window.innerHeight;
  let t = e, c = n;
  e + i.width > r && (t = e - i.width), n + i.height > o && (c = n - i.height), t = Math.max(0, t), c = Math.max(0, c), s.style.left = `${t}px`, s.style.top = `${c}px`, s.style.visibility = "visible";
}
let f = null, p = null, m = null, g = 0;
const b = [
  {
    id: "copy",
    name: "Copy",
    shortcut: "Ctrl+C",
    action: (s) => {
      s.grid?.plugins?.clipboard?.copy?.();
    }
  },
  { separator: !0, id: "sep1", name: "" },
  {
    id: "export-csv",
    name: "Export CSV",
    action: (s) => {
      s.grid?.plugins?.export?.exportCsv?.();
    }
  }
];
class H extends C {
  name = "contextMenu";
  version = "1.0.0";
  get defaultConfig() {
    return {
      items: b
    };
  }
  // #region Internal State
  isOpen = !1;
  position = { x: 0, y: 0 };
  params = null;
  menuElement = null;
  // #endregion
  // #region Lifecycle
  attach(e) {
    super.attach(e), this.installGlobalHandlers(), g++;
  }
  detach() {
    this.menuElement && (this.menuElement.remove(), this.menuElement = null), this.isOpen = !1, this.params = null, this.uninstallGlobalHandlers();
  }
  // #endregion
  // #region Private Methods
  installGlobalHandlers() {
    !m && typeof document < "u" && (m = document.createElement("style"), m.id = "tbw-context-menu-styles", m.textContent = I, document.head.appendChild(m)), f || (f = () => {
      document.querySelectorAll(".tbw-context-menu").forEach((n) => n.remove());
    }, document.addEventListener("click", f)), p || (p = (e) => {
      e.key === "Escape" && document.querySelectorAll(".tbw-context-menu").forEach((i) => i.remove());
    }, document.addEventListener("keydown", p));
  }
  /**
   * Clean up global handlers when the last instance detaches.
   * Uses reference counting to ensure handlers persist while any grid uses the plugin.
   */
  uninstallGlobalHandlers() {
    g--, !(g > 0) && (f && (document.removeEventListener("click", f), f = null), p && (document.removeEventListener("keydown", p), p = null), m && (m.remove(), m = null));
  }
  // #endregion
  // #region Hooks
  afterRender() {
    const e = this.shadowRoot;
    if (!e) return;
    const n = e.children[0];
    n && n.getAttribute("data-context-menu-bound") !== "true" && (n.setAttribute("data-context-menu-bound", "true"), n.addEventListener("contextmenu", (i) => {
      const r = i;
      r.preventDefault();
      const o = r.target, t = o.closest("[data-row][data-col]"), c = o.closest(".header-cell");
      let d;
      if (t) {
        const u = parseInt(t.getAttribute("data-row") ?? "-1", 10), h = parseInt(t.getAttribute("data-col") ?? "-1", 10), a = this.columns[h], v = this.rows[u];
        d = {
          row: v,
          rowIndex: u,
          column: a,
          columnIndex: h,
          field: a?.field ?? "",
          value: v?.[a?.field] ?? null,
          isHeader: !1,
          event: r
        };
      } else if (c) {
        const u = parseInt(c.getAttribute("data-col") ?? "-1", 10), h = this.columns[u];
        d = {
          row: null,
          rowIndex: -1,
          column: h,
          columnIndex: u,
          field: h?.field ?? "",
          value: null,
          isHeader: !0,
          event: r
        };
      } else
        return;
      this.params = d, this.position = { x: r.clientX, y: r.clientY };
      const l = x(this.config.items ?? b, d);
      l.length && (this.menuElement && this.menuElement.remove(), this.menuElement = w(
        l,
        d,
        (u) => {
          u.action && u.action(d), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
        },
        this.gridIcons.submenuArrow
      ), document.body.appendChild(this.menuElement), E(this.menuElement, r.clientX, r.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: d, items: l }));
    }));
  }
  // #endregion
  // #region Public API
  /**
   * Programmatically show the context menu at the specified position.
   * @param x - X coordinate
   * @param y - Y coordinate
   * @param params - Partial context menu parameters
   */
  showMenu(e, n, i) {
    const r = {
      row: i.row ?? null,
      rowIndex: i.rowIndex ?? -1,
      column: i.column ?? null,
      columnIndex: i.columnIndex ?? -1,
      field: i.field ?? "",
      value: i.value ?? null,
      isHeader: i.isHeader ?? !1,
      event: i.event ?? new MouseEvent("contextmenu")
    }, o = x(this.config.items ?? b, r);
    this.menuElement && this.menuElement.remove(), this.menuElement = w(
      o,
      r,
      (t) => {
        t.action && t.action(r), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
      },
      this.gridIcons.submenuArrow
    ), document.body.appendChild(this.menuElement), E(this.menuElement, e, n), this.isOpen = !0;
  }
  /**
   * Hide the context menu.
   */
  hideMenu() {
    this.menuElement && (this.menuElement.remove(), this.menuElement = null, this.isOpen = !1);
  }
  /**
   * Check if the context menu is currently open.
   * @returns Whether the menu is open
   */
  isMenuOpen() {
    return this.isOpen;
  }
  // #endregion
  // Styles are injected globally via installGlobalHandlers() since menu renders in document.body
}
export {
  H as ContextMenuPlugin
};
//# sourceMappingURL=index.js.map
