const _ = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class S {
  /**
   * Plugin dependencies - declare other plugins this one requires.
   *
   * Dependencies are validated when the plugin is attached.
   * Required dependencies throw an error if missing.
   * Optional dependencies log an info message if missing.
   *
   * @example
   * ```typescript
   * static readonly dependencies: PluginDependency[] = [
   *   { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
   *   { name: 'selection', required: false, reason: 'Enables selection-based undo' },
   * ];
   * ```
   */
  static dependencies;
  /**
   * Plugin version - defaults to grid version for built-in plugins.
   * Third-party plugins can override with their own semver.
   */
  version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
  /** 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;
  /**
   * Plugin-level AbortController for event listener cleanup.
   * Created fresh in attach(), aborted in detach().
   * This ensures event listeners are properly cleaned up when plugins are re-attached.
   */
  #e;
  /**
   * 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.
   *
   * @example
   * ```ts
   * attach(grid: GridElement): void {
   *   super.attach(grid);
   *   // Set up document-level listeners with auto-cleanup
   *   document.addEventListener('keydown', this.handleEscape, {
   *     signal: this.disconnectSignal
   *   });
   * }
   * ```
   */
  attach(e) {
    this.#e?.abort(), this.#e = new AbortController(), 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.
   *
   * @example
   * ```ts
   * detach(): void {
   *   // Clean up any state not handled by disconnectSignal
   *   this.selectedRows.clear();
   *   this.cache = null;
   * }
   * ```
   */
  detach() {
    this.#e?.abort(), this.#e = void 0;
  }
  /**
   * Get another plugin instance from the same grid.
   * Use for inter-plugin communication.
   *
   * @example
   * ```ts
   * const selection = this.getPlugin(SelectionPlugin);
   * if (selection) {
   *   const selectedRows = selection.getSelectedRows();
   * }
   * ```
   */
  getPlugin(e) {
    return this.grid?.getPlugin(e);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(e, t) {
    this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
  }
  /**
   * Emit a cancelable custom event from the grid.
   * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
   */
  emitCancelable(e, t) {
    const i = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a re-render and restore focus styling afterward.
   * Use this when a plugin action (like expand/collapse) triggers a render
   * but needs to maintain keyboard navigation focus.
   */
  requestRenderWithFocus() {
    this.grid?.requestRenderWithFocus?.();
  }
  /**
   * 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 grid as an HTMLElement for direct DOM operations.
   * Use sparingly - prefer the typed GridElementRef API when possible.
   *
   * @example
   * ```ts
   * const width = this.gridElement.clientWidth;
   * this.gridElement.classList.add('my-plugin-active');
   * ```
   */
  get gridElement() {
    return this.grid;
  }
  /**
   * Get the render root of the grid for DOM queries.
   * @deprecated Use `gridElement` instead. This getter exists only for backward compatibility.
   *
   * With Shadow DOM removed, the grid element itself is the render root.
   * All new code should use `this.gridElement` for DOM queries.
   *
   * @example
   * // OLD (deprecated)
   * const rows = this.shadowRoot?.querySelector('.rows');
   *
   * // NEW (preferred)
   * const rows = this.gridElement.querySelector('.rows');
   */
  get shadowRoot() {
    return this.gridElement;
  }
  /**
   * 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.#e?.signal ?? this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const e = this.grid?.gridConfig?.icons ?? {};
    return { ..._, ...e };
  }
  // #region Animation Helpers
  /**
   * Check if animations are enabled at the grid level.
   * Respects gridConfig.animation.mode and the CSS variable set by the grid.
   *
   * Plugins should use this to skip animations when:
   * - Animation mode is 'off' or `false`
   * - User prefers reduced motion and mode is 'reduced-motion' (default)
   *
   * @example
   * ```ts
   * private get animationStyle(): 'slide' | 'fade' | false {
   *   if (!this.isAnimationEnabled) return false;
   *   return this.config.animation ?? 'slide';
   * }
   * ```
   */
  get isAnimationEnabled() {
    const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
    if (e === !1 || e === "off") return !1;
    if (e === !0 || e === "on") return !0;
    const t = this.gridElement;
    return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
  }
  /**
   * Get the animation duration in milliseconds from CSS variable.
   * Falls back to 200ms if not set.
   *
   * Plugins can use this for their animation timing to stay consistent
   * with the grid-level animation.duration setting.
   *
   * @example
   * ```ts
   * element.animate(keyframes, { duration: this.animationDuration });
   * ```
   */
  get animationDuration() {
    const e = this.gridElement;
    if (e) {
      const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(t, 10);
      if (!isNaN(i)) return i;
    }
    return 200;
  }
  // #endregion
  /**
   * 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, t) {
    return t !== void 0 ? t : 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, t) {
    typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
const L = "@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px}tbw-grid .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}}";
function k(l) {
  switch (l.type) {
    case "number":
      return (e) => {
        const t = document.createElement("input");
        return t.type = "number", t.value = e.value != null ? String(e.value) : "", t.addEventListener("blur", () => e.commit(t.value === "" ? null : Number(t.value))), t.addEventListener("keydown", (i) => {
          i.key === "Enter" && e.commit(t.value === "" ? null : Number(t.value)), i.key === "Escape" && e.cancel();
        }), t;
      };
    case "boolean":
      return (e) => {
        const t = document.createElement("input");
        return t.type = "checkbox", t.checked = !!e.value, t.addEventListener("change", () => e.commit(t.checked)), t;
      };
    case "date":
      return (e) => {
        const t = document.createElement("input");
        return t.type = "date", e.value instanceof Date && (t.valueAsDate = e.value), t.addEventListener("change", () => e.commit(t.valueAsDate)), t.addEventListener("keydown", (i) => {
          i.key === "Escape" && e.cancel();
        }), t;
      };
    case "select":
    case "typeahead":
      return (e) => {
        const t = document.createElement("select"), i = e.column;
        i.multi && (t.multiple = !0);
        const s = i.options;
        (typeof s == "function" ? s() : s || []).forEach((r) => {
          const a = document.createElement("option");
          a.value = String(r.value), a.textContent = r.label, (i.multi && Array.isArray(e.value) && e.value.includes(r.value) || !i.multi && e.value === r.value) && (a.selected = !0), t.appendChild(a);
        });
        const o = () => {
          if (i.multi) {
            const r = [];
            Array.from(t.selectedOptions).forEach((a) => {
              r.push(a.value);
            }), e.commit(r);
          } else
            e.commit(t.value);
        };
        return t.addEventListener("change", o), t.addEventListener("blur", o), t.addEventListener("keydown", (r) => {
          r.key === "Escape" && e.cancel();
        }), t;
      };
    default:
      return (e) => {
        const t = document.createElement("input");
        return t.type = "text", t.value = e.value != null ? String(e.value) : "", t.addEventListener("blur", () => e.commit(t.value)), t.addEventListener("keydown", (i) => {
          i.key === "Enter" && e.commit(t.value), i.key === "Escape" && e.cancel();
        }), t;
      };
  }
}
const C = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
function w(l) {
  return !(typeof l != "string" || l === "__proto__" || l === "constructor" || l === "prototype");
}
function O(l) {
  return (l.__editingCellCount ?? 0) > 0;
}
function A(l) {
  const e = (l.__editingCellCount ?? 0) + 1;
  l.__editingCellCount = e, l.setAttribute("data-has-editing", "");
}
function q(l) {
  l.__editingCellCount = 0, l.removeAttribute("data-has-editing");
}
function v(l, e) {
  return l instanceof HTMLInputElement ? l.type === "checkbox" ? l.checked : l.type === "number" ? l.value === "" ? null : Number(l.value) : l.type === "date" ? l.valueAsDate : l.value : e?.type === "number" && l.value !== "" ? Number(l.value) : l.value;
}
function T(l, e, t) {
  const i = l.querySelector("input,textarea,select");
  i && (i.addEventListener("blur", () => {
    t(v(i, e));
  }), i instanceof HTMLInputElement && i.type === "checkbox" ? i.addEventListener("change", () => t(i.checked)) : i instanceof HTMLSelectElement && i.addEventListener("change", () => t(v(i, e))));
}
class N extends S {
  name = "editing";
  styles = L;
  get defaultConfig() {
    return {
      editOn: "click"
    };
  }
  // #region Editing State (fully owned by plugin)
  /** Currently active edit row index, or -1 if not editing */
  #e = -1;
  /** Currently active edit column index, or -1 if not editing */
  #r = -1;
  /** Snapshots of row data before editing started */
  #s = /* @__PURE__ */ new Map();
  /** Set of row indices that have been modified */
  #t = /* @__PURE__ */ new Set();
  /** Set of cells currently in edit mode: "rowIndex:colIndex" */
  #n = /* @__PURE__ */ new Set();
  /** Flag to restore focus after next render (used when exiting edit mode) */
  #o = !1;
  // #endregion
  // #region Lifecycle
  attach(e) {
    super.attach(e);
    const t = this.disconnectSignal, i = e;
    i._activeEditRows = -1, i._rowEditSnapshots = /* @__PURE__ */ new Map(), i._changedRowIndices = /* @__PURE__ */ new Set(), Object.defineProperty(e, "changedRows", {
      get: () => this.changedRows,
      configurable: !0
    }), Object.defineProperty(e, "changedRowIndices", {
      get: () => this.changedRowIndices,
      configurable: !0
    }), e.resetChangedRows = (s) => this.resetChangedRows(s), e.beginBulkEdit = (s, n) => {
      n && this.beginCellEdit(s, n);
    }, document.addEventListener(
      "keydown",
      (s) => {
        s.key === "Escape" && this.#e !== -1 && this.#i(this.#e, !0);
      },
      { capture: !0, signal: t }
    ), document.addEventListener(
      "mousedown",
      (s) => {
        if (this.#e === -1) return;
        const n = i.findRenderedRowElement?.(this.#e);
        !n || (s.composedPath && s.composedPath() || []).includes(n) || this.#i(this.#e, !1);
      },
      { signal: t }
    );
  }
  detach() {
    this.#e = -1, this.#r = -1, this.#s.clear(), this.#t.clear(), this.#n.clear(), super.detach();
  }
  // #endregion
  // #region Event Handlers (event distribution)
  /**
   * Handle cell clicks - start editing if configured for click mode.
   * Both click and dblclick events come through this handler.
   * Starts row-based editing (all editable cells in the row get editors).
   */
  onCellClick(e) {
    const t = this.grid, i = this.config.editOn ?? t.effectiveConfig?.editOn;
    if (i === !1 || i === "manual" || i !== "click" && i !== "dblclick") return !1;
    const s = e.originalEvent.type === "dblclick";
    if (i === "click" && s || i === "dblclick" && !s) return !1;
    const { rowIndex: n } = e;
    return t._columns?.some((r) => r.editable) ? (e.originalEvent.stopPropagation(), this.beginBulkEdit(n), !0) : !1;
  }
  /**
   * Handle keyboard events for edit lifecycle.
   */
  onKeyDown(e) {
    const t = this.grid;
    if (e.key === "Escape" && this.#e !== -1)
      return this.#i(this.#e, !0), !0;
    if (e.key === " " || e.key === "Spacebar") {
      const i = t._focusRow, s = t._focusCol;
      if (i >= 0 && s >= 0) {
        const n = t._visibleColumns[s], o = t._rows[i];
        if (n?.editable && n.type === "boolean" && o) {
          const r = n.field;
          if (w(r)) {
            const f = !o[r];
            return this.#a(i, n, f, o), e.preventDefault(), this.requestRender(), !0;
          }
        }
      }
      return !1;
    }
    if (e.key === "Enter" && !e.shiftKey) {
      if (this.#e !== -1)
        return !1;
      const i = this.config.editOn ?? t.effectiveConfig?.editOn;
      if (i === !1 || i === "manual") return !1;
      const s = t._focusRow;
      return s >= 0 && t._columns?.some((o) => o.editable) ? (this.beginBulkEdit(s), !0) : !1;
    }
    return !1;
  }
  // #endregion
  // #region Render Hooks
  /**
   * After render, reapply editors to cells in edit mode.
   * This handles virtualization - when a row scrolls back into view,
   * we need to re-inject the editor.
   */
  afterRender() {
    const e = this.grid;
    if (this.#o && (this.#o = !1, this.#u(e)), this.#n.size !== 0)
      for (const t of this.#n) {
        const [i, s] = t.split(":"), n = parseInt(i, 10), o = parseInt(s, 10), r = e.findRenderedRowElement?.(n);
        if (!r) continue;
        const a = r.querySelector(`.cell[data-col="${o}"]`);
        if (!a || a.classList.contains("editing")) continue;
        const f = e._rows[n], d = e._visibleColumns[o];
        f && d && this.#c(f, n, d, o, a, !0);
      }
  }
  /**
   * On scroll render, reapply editors to recycled cells.
   */
  onScrollRender() {
    this.afterRender();
  }
  // #endregion
  // #region Public API
  /**
   * Get all rows that have been modified.
   */
  get changedRows() {
    const e = this.grid;
    return Array.from(this.#t).map((t) => e._rows[t]);
  }
  /**
   * Get indices of all modified rows.
   */
  get changedRowIndices() {
    return Array.from(this.#t);
  }
  /**
   * Get the currently active edit row index, or -1 if not editing.
   */
  get activeEditRow() {
    return this.#e;
  }
  /**
   * Get the currently active edit column index, or -1 if not editing.
   */
  get activeEditCol() {
    return this.#r;
  }
  /**
   * Check if a specific row is currently being edited.
   */
  isRowEditing(e) {
    return this.#e === e;
  }
  /**
   * Check if a specific cell is currently being edited.
   */
  isCellEditing(e, t) {
    return this.#n.has(`${e}:${t}`);
  }
  /**
   * Check if a specific row has been modified.
   */
  isRowChanged(e) {
    return this.#t.has(e);
  }
  /**
   * Reset all change tracking.
   */
  resetChangedRows(e) {
    const t = this.changedRows, i = this.changedRowIndices;
    this.#t.clear(), this.#l(), e || this.emit("changed-rows-reset", { rows: t, indices: i }), this.grid._rowPool?.forEach((n) => n.classList.remove("changed"));
  }
  /**
   * Programmatically begin editing a cell.
   */
  beginCellEdit(e, t) {
    const i = this.grid, s = i._visibleColumns.findIndex((a) => a.field === t);
    if (s === -1 || !i._visibleColumns[s]?.editable) return;
    const r = i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${s}"]`);
    r && this.#f(e, s, r);
  }
  /**
   * Programmatically begin editing all editable cells in a row.
   */
  beginBulkEdit(e) {
    const t = this.grid;
    if ((this.config.editOn ?? t.effectiveConfig?.editOn) === !1 || !t._columns?.some((r) => r.editable)) return;
    const n = t.findRenderedRowElement?.(e);
    if (!n) return;
    const o = t._rows[e];
    this.#d(e, o), Array.from(n.children).forEach((r, a) => {
      const f = t._visibleColumns[a];
      if (f?.editable) {
        const d = r;
        d.classList.contains("editing") || this.#c(o, e, f, a, d, !0);
      }
    }), setTimeout(() => {
      let r = n.querySelector(`.cell[data-col="${t._focusCol}"]`);
      if (r?.classList.contains("editing") || (r = n.querySelector(".cell.editing")), r?.classList.contains("editing")) {
        const a = r.querySelector(C);
        try {
          a?.focus({ preventScroll: !0 });
        } catch {
        }
      }
    }, 0);
  }
  /**
   * Commit the currently active row edit.
   */
  commitActiveRowEdit() {
    this.#e !== -1 && this.#i(this.#e, !1);
  }
  /**
   * Cancel the currently active row edit.
   */
  cancelActiveRowEdit() {
    this.#e !== -1 && this.#i(this.#e, !0);
  }
  // #endregion
  // #region Internal Methods
  /**
   * Begin editing a single cell.
   */
  #f(e, t, i) {
    const s = this.grid, n = s._rows[e], o = s._visibleColumns[t];
    !n || !o?.editable || i.classList.contains("editing") || (this.#e !== e && this.#d(e, n), this.#r = t, this.#c(n, e, o, t, i, !1));
  }
  /**
   * Sync the internal grid state with the plugin's editing state.
   */
  #l() {
    const e = this.grid;
    e._activeEditRows = this.#e, e._rowEditSnapshots = this.#s, e._changedRowIndices = this.#t;
  }
  /**
   * Snapshot original row data and mark as editing.
   */
  #d(e, t) {
    this.#e !== e && (this.#s.set(e, { ...t }), this.#e = e, this.#l());
  }
  /**
   * Exit editing for a row.
   */
  #i(e, t) {
    if (this.#e !== e) return;
    const i = this.grid, s = this.#s.get(e), n = i._rows[e], o = i.findRenderedRowElement?.(e);
    if (!t && o && n && o.querySelectorAll(".cell.editing").forEach((a) => {
      const f = Number(a.getAttribute("data-col"));
      if (isNaN(f)) return;
      const d = i._visibleColumns[f];
      if (!d) return;
      const b = a.querySelector("input,textarea,select");
      if (b) {
        const c = v(b, d);
        n[d.field] !== c && this.#a(e, d, c, n);
      }
    }), t && s && n)
      Object.keys(s).forEach((r) => {
        n[r] = s[r];
      }), this.#t.delete(e);
    else if (!t) {
      const r = this.#t.has(e);
      this.emit("row-commit", {
        rowIndex: e,
        row: n,
        changed: r,
        changedRows: this.changedRows,
        changedRowIndices: this.changedRowIndices
      });
    }
    this.#s.delete(e), this.#e = -1, this.#r = -1, this.#l();
    for (const r of this.#n)
      r.startsWith(`${e}:`) && this.#n.delete(r);
    o && (o.querySelectorAll(".cell.editing").forEach((r) => {
      r.classList.remove("editing"), q(r.parentElement);
    }), this.requestRender()), this.#o = !0, o || (this.#u(i), this.#o = !1);
  }
  /**
   * Commit a single cell value change.
   */
  #a(e, t, i, s) {
    const n = t.field;
    if (!w(n)) return;
    const o = s[n];
    if (o === i) return;
    const r = !this.#t.has(e);
    if (this.emitCancelable("cell-commit", {
      row: s,
      field: n,
      oldValue: o,
      value: i,
      rowIndex: e,
      changedRows: this.changedRows,
      changedRowIndices: this.changedRowIndices,
      firstTimeForRow: r
    })) return;
    s[n] = i, this.#t.add(e), this.#l();
    const d = this.grid.findRenderedRowElement?.(e);
    d && d.classList.add("changed");
  }
  /**
   * Inject an editor into a cell.
   */
  #c(e, t, i, s, n, o) {
    if (!i.editable || n.classList.contains("editing")) return;
    const r = w(i.field) ? e[i.field] : void 0;
    n.classList.add("editing"), this.#n.add(`${t}:${s}`);
    const a = n.parentElement;
    a && A(a);
    let f = !1;
    const d = (h) => {
      f || this.#e === -1 || this.#a(t, i, h, e);
    }, b = () => {
      f = !0, w(i.field) && (e[i.field] = r);
    }, c = document.createElement("div");
    c.className = "tbw-editor-host", n.innerHTML = "", n.appendChild(c), c.addEventListener("keydown", (h) => {
      h.key === "Enter" && (h.stopPropagation(), h.preventDefault(), f = !0, this.#i(t, !1)), h.key === "Escape" && (h.stopPropagation(), h.preventDefault(), b(), this.#i(t, !0));
    });
    const g = i, E = g.__editorTemplate, u = g.editor || (E ? "template" : k(i)), m = r;
    if (u === "template" && E)
      this.#h(c, g, e, r, d, b, o, t);
    else if (typeof u == "string") {
      const h = document.createElement(u);
      h.value = m, h.addEventListener("change", () => d(h.value)), c.appendChild(h), o || queueMicrotask(() => {
        c.querySelector(C)?.focus({ preventScroll: !0 });
      });
    } else if (typeof u == "function") {
      const h = { row: e, value: m, field: i.field, column: i, commit: d, cancel: b }, p = u(h);
      typeof p == "string" ? (c.innerHTML = p, T(c, i, d)) : p instanceof Node && c.appendChild(p), o || queueMicrotask(() => {
        c.querySelector(C)?.focus({ preventScroll: !0 });
      });
    } else if (u && typeof u == "object") {
      const h = this.grid, p = document.createElement("div");
      p.setAttribute("data-external-editor", ""), p.setAttribute("data-field", i.field), c.appendChild(p);
      const y = { row: e, value: m, field: i.field, column: i, commit: d, cancel: b };
      if (u.mount)
        try {
          u.mount({ placeholder: p, context: y, spec: u });
        } catch (R) {
          console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`, R);
        }
      else
        h.dispatchEvent(
          new CustomEvent("mount-external-editor", { detail: { placeholder: p, spec: u, context: y } })
        );
    }
  }
  /**
   * Render a template-based editor.
   */
  #h(e, t, i, s, n, o, r, a) {
    const f = t.__editorTemplate;
    if (!f) return;
    const d = f.cloneNode(!0), b = t.__compiledEditor;
    b ? d.innerHTML = b({
      row: i,
      value: s,
      field: t.field,
      column: t,
      commit: n,
      cancel: o
    }) : d.querySelectorAll("*").forEach((g) => {
      g.childNodes.length === 1 && g.firstChild?.nodeType === Node.TEXT_NODE && (g.textContent = g.textContent?.replace(/{{\s*value\s*}}/g, s == null ? "" : String(s)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g, (E, u) => {
        if (!w(u)) return "";
        const m = i[u];
        return m == null ? "" : String(m);
      }) || "");
    });
    const c = d.querySelector(
      "input,textarea,select"
    );
    if (c) {
      c instanceof HTMLInputElement && c.type === "checkbox" ? c.checked = !!s : c.value = String(s ?? "");
      let g = !1;
      c.addEventListener("blur", () => {
        g || n(v(c, t));
      }), c.addEventListener("keydown", (E) => {
        const u = E;
        u.key === "Enter" && (u.stopPropagation(), u.preventDefault(), g = !0, n(v(c, t)), this.#i(a, !1)), u.key === "Escape" && (u.stopPropagation(), u.preventDefault(), o(), this.#i(a, !0));
      }), c instanceof HTMLInputElement && c.type === "checkbox" && c.addEventListener("change", () => n(c.checked)), r || setTimeout(() => c.focus({ preventScroll: !0 }), 0);
    }
    e.appendChild(d);
  }
  /**
   * Restore focus to cell after exiting edit mode.
   */
  #u(e) {
    queueMicrotask(() => {
      try {
        const t = e._focusRow, i = e._focusCol, s = e.findRenderedRowElement?.(t);
        if (s) {
          Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(
            (o) => o.classList.remove("cell-focus")
          );
          const n = s.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);
          n && (n.classList.add("cell-focus"), n.setAttribute("aria-selected", "true"), n.hasAttribute("tabindex") || n.setAttribute("tabindex", "-1"), n.focus({ preventScroll: !0 }));
        }
      } catch {
      }
    });
  }
  // #endregion
}
export {
  N as EditingPlugin,
  C as FOCUSABLE_EDITOR_SELECTOR,
  q as clearEditingState,
  k as defaultEditorFor,
  O as hasEditingCells
};
//# sourceMappingURL=index.js.map
