const L = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class P {
  /**
   * 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 o = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(o), o.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 { ...L, ...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(), o = parseInt(t, 10);
      if (!isNaN(o)) return o;
    }
    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 m = {
  sum: (i, e) => i.reduce((t, o) => t + (Number(o[e]) || 0), 0),
  avg: (i, e) => {
    const t = i.reduce((o, r) => o + (Number(r[e]) || 0), 0);
    return i.length ? t / i.length : 0;
  },
  count: (i) => i.length,
  min: (i, e) => Math.min(...i.map((t) => Number(t[e]) || 1 / 0)),
  max: (i, e) => Math.max(...i.map((t) => Number(t[e]) || -1 / 0)),
  first: (i, e) => i[0]?.[e],
  last: (i, e) => i[i.length - 1]?.[e]
}, f = /* @__PURE__ */ new Map(), g = {
  /**
   * Register a custom aggregator function.
   */
  register(i, e) {
    f.set(i, e);
  },
  /**
   * Unregister a custom aggregator function.
   */
  unregister(i) {
    f.delete(i);
  },
  /**
   * Get an aggregator function by reference.
   */
  get(i) {
    if (i !== void 0)
      return typeof i == "function" ? i : f.get(i) ?? m[i];
  },
  /**
   * Run an aggregator on a set of rows.
   */
  run(i, e, t, o) {
    const r = this.get(i);
    return r ? r(e, t, o) : void 0;
  },
  /**
   * Check if an aggregator exists.
   */
  has(i) {
    return f.has(i) || i in m;
  },
  /**
   * List all available aggregator names.
   */
  list() {
    return [...Object.keys(m), ...f.keys()];
  }
}, A = {
  sum: (i) => i.reduce((e, t) => e + t, 0),
  avg: (i) => i.length ? i.reduce((e, t) => e + t, 0) / i.length : 0,
  count: (i) => i.length,
  min: (i) => i.length ? Math.min(...i) : 0,
  max: (i) => i.length ? Math.max(...i) : 0,
  first: (i) => i[0] ?? 0,
  last: (i) => i[i.length - 1] ?? 0
};
function k(i) {
  return A[i] ?? A.sum;
}
g.register.bind(g);
g.unregister.bind(g);
g.get.bind(g);
g.run.bind(g);
g.list.bind(g);
const N = k;
function S(i) {
  const e = [];
  return !i.rowGroupFields?.length && !i.columnGroupFields?.length && e.push("At least one row or column group field is required"), i.valueFields?.length || e.push("At least one value field is required"), e;
}
function C(i, e) {
  return [...i, e].join("|");
}
function K(i, e) {
  const t = e.rowGroupFields ?? [], o = e.columnGroupFields ?? [], r = e.valueFields ?? [], n = I(i, o), a = E(
    i,
    t,
    o,
    n,
    r,
    0,
    // starting depth
    ""
    // parent key prefix
  ), s = z(a, n, r), l = Object.values(s).reduce((d, c) => d + c, 0);
  return {
    rows: a,
    columnKeys: n,
    totals: s,
    grandTotal: l
  };
}
function I(i, e) {
  if (e.length === 0) return ["value"];
  const t = /* @__PURE__ */ new Set();
  for (const o of i) {
    const r = e.map((n) => String(o[n] ?? "")).join("|");
    t.add(r);
  }
  return [...t].sort();
}
function V(i, e) {
  const t = /* @__PURE__ */ new Map();
  for (const o of i) {
    const r = String(o[e] ?? ""), n = t.get(r);
    n ? n.push(o) : t.set(r, [o]);
  }
  return t;
}
function E(i, e, t, o, r, n, a) {
  const s = [];
  if (e.length === 0) {
    const u = _(i, t, o, r), h = R(u);
    return s.push({
      rowKey: a || "all",
      rowLabel: a || "All",
      depth: n,
      values: u,
      total: h,
      isGroup: !1,
      rowCount: i.length
    }), s;
  }
  const l = e[0], d = e.slice(1), c = d.length > 0, p = V(i, l);
  for (const [u, h] of p) {
    const y = a ? `${a}|${u}` : u, x = _(h, t, o, r), G = R(x);
    let F;
    c && (F = E(
      h,
      d,
      t,
      o,
      r,
      n + 1,
      y
    )), s.push({
      rowKey: y,
      rowLabel: u || "(blank)",
      depth: n,
      values: x,
      total: G,
      isGroup: c,
      children: F,
      rowCount: h.length
    });
  }
  return s;
}
function _(i, e, t, o) {
  const r = {};
  for (const n of t)
    for (const a of o) {
      const l = (e.length > 0 ? i.filter((u) => e.map((h) => String(u[h] ?? "")).join("|") === n) : i).map((u) => Number(u[a.field]) || 0), d = N(a.aggFunc), c = l.length > 0 ? d(l) : null, p = C([n], a.field);
      r[p] = c;
    }
  return r;
}
function R(i) {
  let e = 0;
  for (const t of Object.values(i))
    e += t ?? 0;
  return e;
}
function z(i, e, t) {
  const o = {};
  function r(n) {
    for (const a of n)
      if (!a.isGroup || !a.children?.length)
        for (const s of e)
          for (const l of t) {
            const d = C([s], l.field);
            o[d] = (o[d] ?? 0) + (a.values[d] ?? 0);
          }
      else a.children && r(a.children);
  }
  return r(i), o;
}
function D(i, e, t = !0) {
  const o = [];
  function r(n) {
    o.push(n);
    const a = e ? e.has(n.rowKey) : t;
    if (n.children && a)
      for (const s of n.children)
        r(s);
  }
  for (const n of i)
    r(n);
  return o;
}
function q(i) {
  const e = [];
  function t(o) {
    if (o.isGroup && e.push(o.rowKey), o.children)
      for (const r of o.children)
        t(r);
  }
  for (const o of i)
    t(o);
  return e;
}
const H = ["sum", "avg", "count", "min", "max", "first", "last"];
function M(i, e, t, o) {
  const r = new AbortController(), n = { config: e, callbacks: o, signal: r.signal }, a = document.createElement("div");
  return a.className = "tbw-pivot-panel", a.appendChild(b("Options", () => W(t, n))), a.appendChild(b("Row Groups", () => T("rowGroups", n))), a.appendChild(b("Column Groups", () => T("columnGroups", n))), a.appendChild(b("Values", () => Z(n))), a.appendChild(b("Available Fields", () => j(n))), i.appendChild(a), () => {
    r.abort(), a.remove();
  };
}
function b(i, e) {
  const t = document.createElement("div");
  t.className = "tbw-pivot-section";
  const o = document.createElement("div");
  o.className = "tbw-pivot-section-header", o.textContent = i;
  const r = document.createElement("div");
  return r.className = "tbw-pivot-section-content", r.appendChild(e()), t.appendChild(o), t.appendChild(r), t;
}
function T(i, e) {
  const { config: t, callbacks: o, signal: r } = e, n = document.createElement("div");
  n.className = "tbw-pivot-drop-zone", n.setAttribute("data-zone", i);
  const a = i === "rowGroups" ? t.rowGroupFields ?? [] : t.columnGroupFields ?? [];
  if (a.length === 0) {
    const s = document.createElement("div");
    s.className = "tbw-pivot-placeholder", s.textContent = "Drag fields here or click to add", n.appendChild(s);
  } else
    for (const s of a)
      n.appendChild(O(s, i, e));
  return n.addEventListener(
    "dragover",
    (s) => {
      s.preventDefault(), n.classList.add("drag-over");
    },
    { signal: r }
  ), n.addEventListener(
    "dragleave",
    () => {
      n.classList.remove("drag-over");
    },
    { signal: r }
  ), n.addEventListener(
    "drop",
    (s) => {
      s.preventDefault(), n.classList.remove("drag-over");
      const l = s.dataTransfer?.getData("text/plain");
      l && o.onAddFieldToZone(l, i);
    },
    { signal: r }
  ), n;
}
function O(i, e, t) {
  const { callbacks: o, signal: r } = t, n = document.createElement("div");
  n.className = "tbw-pivot-field-chip", n.draggable = !0;
  const a = o.getAvailableFields().find((d) => d.field === i), s = document.createElement("span");
  s.className = "tbw-pivot-chip-label", s.textContent = a?.header ?? i;
  const l = document.createElement("button");
  return l.className = "tbw-pivot-chip-remove", l.innerHTML = "×", l.title = "Remove field", l.addEventListener(
    "click",
    (d) => {
      d.stopPropagation(), o.onRemoveFieldFromZone(i, e);
    },
    { signal: r }
  ), n.appendChild(s), n.appendChild(l), n.addEventListener(
    "dragstart",
    (d) => {
      d.dataTransfer?.setData("text/plain", i), d.dataTransfer?.setData("source-zone", e), n.classList.add("dragging");
    },
    { signal: r }
  ), n.addEventListener(
    "dragend",
    () => {
      n.classList.remove("dragging");
    },
    { signal: r }
  ), n;
}
function Z(i) {
  const { config: e, callbacks: t, signal: o } = i, r = document.createElement("div");
  r.className = "tbw-pivot-drop-zone tbw-pivot-values-zone", r.setAttribute("data-zone", "values");
  const n = e.valueFields ?? [];
  if (n.length === 0) {
    const a = document.createElement("div");
    a.className = "tbw-pivot-placeholder", a.textContent = "Drag numeric fields here for aggregation", r.appendChild(a);
  } else
    for (const a of n)
      r.appendChild($(a, i));
  return r.addEventListener(
    "dragover",
    (a) => {
      a.preventDefault(), r.classList.add("drag-over");
    },
    { signal: o }
  ), r.addEventListener(
    "dragleave",
    () => {
      r.classList.remove("drag-over");
    },
    { signal: o }
  ), r.addEventListener(
    "drop",
    (a) => {
      a.preventDefault(), r.classList.remove("drag-over");
      const s = a.dataTransfer?.getData("text/plain");
      s && t.onAddValueField(s, "sum");
    },
    { signal: o }
  ), r;
}
function $(i, e) {
  const { callbacks: t, signal: o } = e, r = document.createElement("div");
  r.className = "tbw-pivot-field-chip tbw-pivot-value-chip";
  const n = t.getAvailableFields().find((c) => c.field === i.field), a = document.createElement("div");
  a.className = "tbw-pivot-value-label-wrapper";
  const s = document.createElement("span");
  s.className = "tbw-pivot-chip-label", s.textContent = n?.header ?? i.field;
  const l = document.createElement("select");
  l.className = "tbw-pivot-agg-select", l.title = "Aggregation function";
  for (const c of H) {
    const p = document.createElement("option");
    p.value = c, p.textContent = c.toUpperCase(), p.selected = c === i.aggFunc, l.appendChild(p);
  }
  l.addEventListener(
    "change",
    () => {
      t.onUpdateValueAggFunc(i.field, l.value);
    },
    { signal: o }
  );
  const d = document.createElement("button");
  return d.className = "tbw-pivot-chip-remove", d.innerHTML = "×", d.title = "Remove value field", d.addEventListener(
    "click",
    (c) => {
      c.stopPropagation(), t.onRemoveValueField(i.field);
    },
    { signal: o }
  ), a.appendChild(s), a.appendChild(l), r.appendChild(a), r.appendChild(d), r;
}
function j(i) {
  const { config: e, callbacks: t, signal: o } = i, r = document.createElement("div");
  r.className = "tbw-pivot-available-fields";
  const n = t.getAvailableFields(), a = /* @__PURE__ */ new Set([
    ...e.rowGroupFields ?? [],
    ...e.columnGroupFields ?? [],
    ...e.valueFields?.map((l) => l.field) ?? []
  ]), s = n.filter((l) => !a.has(l.field));
  if (s.length === 0) {
    const l = document.createElement("div");
    l.className = "tbw-pivot-placeholder", l.textContent = "All fields are in use", r.appendChild(l);
  } else
    for (const l of s) {
      const d = document.createElement("div");
      d.className = "tbw-pivot-field-chip available", d.textContent = l.header, d.draggable = !0, d.title = `Drag to add "${l.field}" to a zone`, d.addEventListener(
        "dragstart",
        (c) => {
          c.dataTransfer?.setData("text/plain", l.field), d.classList.add("dragging");
        },
        { signal: o }
      ), d.addEventListener(
        "dragend",
        () => {
          d.classList.remove("dragging");
        },
        { signal: o }
      ), r.appendChild(d);
    }
  return r;
}
function W(i, e) {
  const { config: t, callbacks: o, signal: r } = e, n = document.createElement("div");
  return n.className = "tbw-pivot-options", n.appendChild(
    w(
      "Enable Pivot View",
      i,
      (a) => {
        o.onTogglePivot(a);
      },
      r
    )
  ), n.appendChild(
    w(
      "Show Row Totals",
      t.showTotals ?? !0,
      (a) => {
        o.onOptionChange("showTotals", a);
      },
      r
    )
  ), n.appendChild(
    w(
      "Show Grand Total",
      t.showGrandTotal ?? !0,
      (a) => {
        o.onOptionChange("showGrandTotal", a);
      },
      r
    )
  ), n;
}
function w(i, e, t, o) {
  const r = document.createElement("label");
  r.className = "tbw-pivot-checkbox";
  const n = document.createElement("input");
  n.type = "checkbox", n.checked = e, n.addEventListener("change", () => t(n.checked), { signal: o });
  const a = document.createElement("span");
  return a.textContent = i, r.appendChild(n), r.appendChild(a), r;
}
function U(i, e, t) {
  return e.className = "data-grid-row pivot-group-row", e.setAttribute("data-pivot-depth", String(i.__pivotDepth ?? 0)), e.setAttribute("data-pivot-key", String(i.__pivotRowKey ?? "")), e.setAttribute("role", "row"), e.innerHTML = "", t.columns.forEach((o, r) => {
    const n = document.createElement("div");
    if (n.className = "cell", n.setAttribute("data-col", String(r)), n.setAttribute("data-row", String(t.rowIndex)), n.setAttribute("role", "gridcell"), r === 0) {
      const a = Number(i.__pivotIndent) || 0;
      n.style.paddingLeft = `${a}px`;
      const s = String(i.__pivotRowKey), l = document.createElement("button");
      l.type = "button", l.className = "pivot-toggle", l.setAttribute("aria-label", i.__pivotExpanded ? "Collapse group" : "Expand group"), t.setIcon(l, t.resolveIcon(i.__pivotExpanded ? "collapse" : "expand")), l.addEventListener("click", (p) => {
        p.stopPropagation(), t.onToggle(s);
      }), n.appendChild(l);
      const d = document.createElement("span");
      d.className = "pivot-label", d.textContent = String(i.__pivotLabel ?? ""), n.appendChild(d);
      const c = document.createElement("span");
      c.className = "pivot-count", c.textContent = ` (${Number(i.__pivotRowCount) || 0})`, n.appendChild(c);
    } else {
      const a = i[o.field];
      n.textContent = a != null ? String(a) : "";
    }
    e.appendChild(n);
  }), !0;
}
function B(i, e, t, o) {
  return e.className = "data-grid-row pivot-leaf-row", e.setAttribute("data-pivot-depth", String(i.__pivotDepth ?? 0)), e.setAttribute("data-pivot-key", String(i.__pivotRowKey ?? "")), e.innerHTML = "", t.forEach((r, n) => {
    const a = document.createElement("div");
    if (a.className = "cell", a.setAttribute("data-col", String(n)), a.setAttribute("data-row", String(o)), a.setAttribute("role", "gridcell"), n === 0) {
      const s = Number(i.__pivotIndent) || 0;
      a.style.paddingLeft = `${s + 20}px`;
      const l = document.createElement("span");
      l.className = "pivot-label", l.textContent = String(i.__pivotLabel ?? ""), a.appendChild(l);
    } else {
      const s = i[r.field];
      a.textContent = s != null ? String(s) : "";
    }
    e.appendChild(a);
  }), !0;
}
function J(i, e, t) {
  return e.className = "pivot-grand-total-row", e.setAttribute("role", "presentation"), e.innerHTML = "", t.forEach((o, r) => {
    const n = document.createElement("div");
    if (n.className = "cell", n.setAttribute("data-col", String(r)), r === 0) {
      const a = document.createElement("span");
      a.className = "pivot-label", a.textContent = "Grand Total", n.appendChild(a);
    } else {
      const a = i[o.field];
      n.textContent = a != null ? String(a) : "";
    }
    e.appendChild(n);
  }), !0;
}
const Q = '@layer tbw-plugins{.pivot-group-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:600;background:var(--tbw-pivot-group-bg, var(--tbw-color-row-alt));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}.pivot-group-row:hover{background:var(--tbw-pivot-group-hover, var(--tbw-color-row-hover))}.pivot-leaf-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-pivot-leaf-bg, var(--tbw-color-bg));min-height:var(--tbw-row-height);border-bottom:var(--tbw-row-divider)}.pivot-grand-total-row{display:grid;grid-template-columns:var(--tbw-column-template);font-weight:700;background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-height:var(--tbw-row-height);border-top:2px solid var(--tbw-color-border-strong)}.pivot-grand-total-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-grand-total-row>.cell:last-child{border-right:0}.pivot-grand-total-footer{position:sticky;bottom:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-pivot-grand-total-bg, var(--tbw-color-header-bg));min-width:fit-content}.pivot-group-row>.cell,.pivot-leaf-row>.cell{display:flex;align-items:center;padding:var(--tbw-cell-padding);border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;min-width:0}.pivot-group-row>.cell:last-child,.pivot-leaf-row>.cell:last-child{border-right:0}.pivot-toggle{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-right:6px;border:none;background:transparent;cursor:pointer;color:var(--tbw-pivot-toggle-color, var(--tbw-color-fg-muted));border-radius:var(--tbw-border-radius);transition:background .15s,color .15s}.pivot-toggle:hover{background:var(--tbw-pivot-toggle-hover-bg, var(--tbw-color-row-hover));color:var(--tbw-pivot-toggle-hover-color, var(--tbw-color-fg))}.pivot-toggle:focus{outline:var(--tbw-focus-outline);outline-offset:var(--tbw-focus-outline-offset)}.pivot-label{font-weight:inherit}.pivot-count{color:var(--tbw-pivot-count-color, var(--tbw-color-fg-muted));font-size:.9em;font-weight:400}.pivot-total-row{font-weight:700;border-top:2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong))}[data-pivot-depth="1"]{--tbw-pivot-depth: 1}[data-pivot-depth="2"]{--tbw-pivot-depth: 2}[data-pivot-depth="3"]{--tbw-pivot-depth: 3}[data-pivot-depth="4"]{--tbw-pivot-depth: 4}.tbw-pivot-panel{display:flex;flex-direction:column;gap:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));height:100%;overflow-y:auto;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-pivot-section{border:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-section-bg, var(--tbw-color-bg))}.tbw-pivot-section-header{padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));font-weight:600;background:var(--tbw-pivot-header-bg, var(--tbw-color-header-bg));border-bottom:1px solid var(--tbw-pivot-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius) var(--tbw-border-radius) 0 0}.tbw-pivot-section-content{padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-toggle-wrapper{display:flex;align-items:center}.tbw-pivot-toggle-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-toggle-label input{width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);cursor:pointer}.tbw-pivot-drop-zone{min-height:60px;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border:2px dashed var(--tbw-pivot-drop-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);background:var(--tbw-pivot-drop-bg, var(--tbw-color-row-alt));display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);align-content:flex-start;transition:all .15s ease}.tbw-pivot-drop-zone.drag-over{border-color:var(--tbw-color-accent);background:var(--tbw-pivot-drop-active, var(--tbw-focus-background))}.tbw-pivot-placeholder{color:var(--tbw-color-fg-muted);font-style:italic;padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));text-align:center;width:100%}.tbw-pivot-field-chip{display:inline-flex;align-items:center;gap:var(--tbw-spacing-sm, .375rem);padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-pivot-chip-bg, var(--tbw-color-header-bg));border:1px solid var(--tbw-pivot-chip-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius);cursor:grab;font-size:var(--tbw-font-size-xs, .75rem);transition:all .15s ease}.tbw-pivot-field-chip:hover{background:var(--tbw-pivot-chip-hover, var(--tbw-color-row-hover));border-color:var(--tbw-color-accent)}.tbw-pivot-field-chip.available{background:var(--tbw-color-bg)}.tbw-pivot-field-chip.dragging{opacity:.5;cursor:grabbing}.tbw-pivot-chip-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.tbw-pivot-chip-remove{display:flex;align-items:center;justify-content:center;width:var(--tbw-icon-size, 1rem);height:var(--tbw-icon-size, 1rem);padding:0;border:none;background:transparent;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-sm, .875rem);font-weight:700;cursor:pointer;border-radius:50%;transition:all .15s ease}.tbw-pivot-chip-remove:hover{background:var(--tbw-pivot-chip-remove-hover-bg, var(--tbw-color-accent));color:var(--tbw-pivot-chip-remove-hover-fg, var(--tbw-color-accent-fg))}.tbw-pivot-value-chip{padding:var(--tbw-button-padding-sm, var(--tbw-spacing-xs, .25rem) var(--tbw-spacing-md, .5rem))}.tbw-pivot-value-label-wrapper{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;min-width:0}.tbw-pivot-agg-select{padding:var(--tbw-spacing-xs, .125rem) var(--tbw-spacing-xs, .25rem);font-size:var(--tbw-font-size-xs, .6875rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);cursor:pointer}.tbw-pivot-available-fields{display:flex;flex-wrap:wrap;gap:var(--tbw-spacing-sm, .375rem);min-height:40px}.tbw-pivot-options{display:flex;flex-direction:column;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-pivot-checkbox{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));cursor:pointer}.tbw-pivot-checkbox input{width:var(--tbw-icon-size-sm, .875rem);height:var(--tbw-icon-size-sm, .875rem);cursor:pointer}.pivot-group-row.tbw-pivot-slide-in,.pivot-leaf-row.tbw-pivot-slide-in{animation:tbw-pivot-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.pivot-group-row.tbw-pivot-fade-in,.pivot-leaf-row.tbw-pivot-fade-in{animation:tbw-pivot-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-pivot-fade-in{0%{opacity:0}to{opacity:1}}}';
class v extends P {
  name = "pivot";
  styles = Q;
  /** Tool panel ID for shell integration */
  static PANEL_ID = "pivot";
  get defaultConfig() {
    return {
      active: !0,
      showTotals: !0,
      showGrandTotal: !0,
      showToolPanel: !0,
      animation: "slide"
    };
  }
  // #region Internal State
  isActive = !1;
  hasInitialized = !1;
  pivotResult = null;
  fieldHeaderMap = /* @__PURE__ */ new Map();
  expandedKeys = /* @__PURE__ */ new Set();
  defaultExpanded = !0;
  originalColumns = [];
  panelContainer = null;
  grandTotalFooter = null;
  previousVisibleKeys = /* @__PURE__ */ new Set();
  keysToAnimate = /* @__PURE__ */ new Set();
  /**
   * Check if the plugin has valid pivot configuration (at least value fields).
   */
  hasValidPivotConfig() {
    return (this.config.valueFields?.length ?? 0) > 0;
  }
  /**
   * Get expand/collapse animation style from plugin config.
   * Uses base class isAnimationEnabled to respect grid-level settings.
   */
  get animationStyle() {
    return this.isAnimationEnabled ? this.config.animation ?? "slide" : !1;
  }
  // #endregion
  // #region Lifecycle
  detach() {
    this.isActive = !1, this.hasInitialized = !1, this.pivotResult = null, this.fieldHeaderMap.clear(), this.originalColumns = [], this.panelContainer = null, this.cleanupGrandTotalFooter(), this.previousVisibleKeys.clear(), this.keysToAnimate.clear();
  }
  // #endregion
  // #region Shell Integration
  getToolPanel() {
    if ((this.config?.showToolPanel ?? this.userConfig?.showToolPanel ?? !0) !== !1)
      return {
        id: v.PANEL_ID,
        title: "Pivot",
        icon: "⊞",
        tooltip: "Configure pivot table",
        order: 90,
        render: (t) => this.renderPanel(t)
      };
  }
  // #endregion
  // #region Hooks
  processRows(e) {
    if (!this.hasInitialized && this.config.active !== !1 && this.hasValidPivotConfig() && (this.hasInitialized = !0, this.isActive = !0), !this.isActive)
      return [...e];
    const t = S(this.config);
    if (t.length > 0)
      return this.warn(`Config errors: ${t.join(", ")}`), [...e];
    this.buildFieldHeaderMap(), this.defaultExpanded = this.config.defaultExpanded ?? !0, this.expandedKeys.size === 0 && this.defaultExpanded && this.pivotResult && this.expandAllKeys(), this.pivotResult = K(e, this.config), this.expandedKeys.size === 0 && this.defaultExpanded && this.expandAllKeys();
    const o = this.config.indentWidth ?? 20, r = D(
      this.pivotResult.rows,
      this.expandedKeys,
      this.defaultExpanded
    ).map((a) => ({
      __pivotRowKey: a.rowKey,
      __pivotLabel: a.rowLabel,
      __pivotDepth: a.depth,
      __pivotIsGroup: a.isGroup,
      __pivotHasChildren: !!a.children?.length,
      __pivotExpanded: this.expandedKeys.has(a.rowKey),
      __pivotRowCount: a.rowCount ?? 0,
      __pivotIndent: a.depth * o,
      __pivotTotal: a.total,
      ...a.values
    }));
    this.keysToAnimate.clear();
    const n = /* @__PURE__ */ new Set();
    for (const a of r) {
      const s = a.__pivotRowKey;
      n.add(s), !this.previousVisibleKeys.has(s) && a.__pivotDepth > 0 && this.keysToAnimate.add(s);
    }
    return this.previousVisibleKeys = n, r;
  }
  processColumns(e) {
    if (!this.isActive || !this.pivotResult)
      return [...e];
    const t = [], o = (this.config.rowGroupFields ?? []).map((r) => this.fieldHeaderMap.get(r) ?? r).join(" / ");
    t.push({
      field: "__pivotLabel",
      header: o || "Group",
      width: 200
    });
    for (const r of this.pivotResult.columnKeys)
      for (const n of this.config.valueFields ?? []) {
        const a = C([r], n.field), s = n.header || this.fieldHeaderMap.get(n.field) || n.field;
        t.push({
          field: a,
          header: `${r} - ${s} (${n.aggFunc})`,
          width: 120,
          type: "number"
        });
      }
    return this.config.showTotals && t.push({
      field: "__pivotTotal",
      header: "Total",
      width: 100,
      type: "number"
    }), t;
  }
  renderRow(e, t, o) {
    const r = e;
    return r.__pivotRowKey && r.__pivotHasChildren ? U(r, t, {
      columns: this.gridColumns,
      rowIndex: o,
      onToggle: (n) => this.toggle(n),
      resolveIcon: (n) => this.resolveIcon(n),
      setIcon: (n, a) => this.setIcon(n, a)
    }) : r.__pivotRowKey !== void 0 && this.isActive ? B(r, t, this.gridColumns, o) : (this.cleanupPivotStyling(t), !1);
  }
  /**
   * Remove pivot-specific classes, attributes, and inline styles from a row element.
   * Called when pivot mode is disabled to clean up reused DOM elements.
   * Clears innerHTML so the grid's default renderer can rebuild the row.
   */
  cleanupPivotStyling(e) {
    (e.classList.contains("pivot-group-row") || e.classList.contains("pivot-leaf-row") || e.classList.contains("pivot-grand-total-row")) && (e.classList.remove("pivot-group-row", "pivot-leaf-row", "pivot-grand-total-row"), e.classList.add("data-grid-row"), e.removeAttribute("data-pivot-depth"), e.innerHTML = "");
  }
  onKeyDown(e) {
    if (e.key !== " " || !this.isActive) return;
    const t = this.grid._focusRow, o = this.rows[t];
    if (!(!o?.__pivotIsGroup || !o.__pivotHasChildren))
      return e.preventDefault(), this.toggle(o.__pivotRowKey), this.requestRenderWithFocus(), !0;
  }
  afterRender() {
    this.isActive && this.config.showGrandTotal && this.pivotResult ? this.renderGrandTotalFooter() : this.cleanupGrandTotalFooter();
    const e = this.animationStyle;
    if (e === !1 || this.keysToAnimate.size === 0) return;
    const t = this.gridElement?.querySelector(".rows");
    if (!t) return;
    const o = e === "fade" ? "tbw-pivot-fade-in" : "tbw-pivot-slide-in";
    for (const r of t.querySelectorAll(".pivot-group-row, .pivot-leaf-row")) {
      const n = r.dataset.pivotKey;
      n && this.keysToAnimate.has(n) && (r.classList.add(o), r.addEventListener("animationend", () => r.classList.remove(o), { once: !0 }));
    }
    this.keysToAnimate.clear();
  }
  /**
   * Render the grand total row as a sticky footer pinned to the bottom.
   */
  renderGrandTotalFooter() {
    if (!this.pivotResult) return;
    const e = this.gridElement;
    if (!e) return;
    const t = e.querySelector(".tbw-scroll-area") ?? e.querySelector(".tbw-grid-content") ?? e.children[0];
    if (!t) return;
    this.grandTotalFooter || (this.grandTotalFooter = document.createElement("div"), this.grandTotalFooter.className = "pivot-grand-total-footer", t.appendChild(this.grandTotalFooter));
    const o = {
      __pivotRowKey: "__grandTotal",
      __pivotLabel: "Grand Total",
      __pivotIsGrandTotal: !0,
      __pivotTotal: this.pivotResult.grandTotal,
      ...this.pivotResult.totals
    };
    J(o, this.grandTotalFooter, this.gridColumns);
  }
  /**
   * Remove the grand total footer element.
   */
  cleanupGrandTotalFooter() {
    this.grandTotalFooter && (this.grandTotalFooter.remove(), this.grandTotalFooter = null);
  }
  // #endregion
  // #region Expand/Collapse API
  toggle(e) {
    this.expandedKeys.has(e) ? this.expandedKeys.delete(e) : this.expandedKeys.add(e), this.requestRender();
  }
  expand(e) {
    this.expandedKeys.add(e), this.requestRender();
  }
  collapse(e) {
    this.expandedKeys.delete(e), this.requestRender();
  }
  expandAll() {
    this.expandAllKeys(), this.requestRender();
  }
  collapseAll() {
    this.expandedKeys.clear(), this.requestRender();
  }
  /**
   * Add all group keys from the current pivot result to expandedKeys.
   */
  expandAllKeys() {
    if (!this.pivotResult) return;
    const e = q(this.pivotResult.rows);
    for (const t of e)
      this.expandedKeys.add(t);
  }
  isExpanded(e) {
    return this.expandedKeys.has(e);
  }
  // #endregion
  // #region Public API
  enablePivot() {
    this.originalColumns.length === 0 && this.captureOriginalColumns(), this.isActive = !0, this.requestRender();
  }
  disablePivot() {
    this.isActive = !1, this.pivotResult = null, this.requestRender();
  }
  isPivotActive() {
    return this.isActive;
  }
  getPivotResult() {
    return this.pivotResult;
  }
  setRowGroupFields(e) {
    this.config.rowGroupFields = e, this.requestRender();
  }
  setColumnGroupFields(e) {
    this.config.columnGroupFields = e, this.requestRender();
  }
  setValueFields(e) {
    this.config.valueFields = e, this.requestRender();
  }
  refresh() {
    this.pivotResult = null, this.requestRender();
  }
  // #endregion
  // #region Tool Panel API
  /**
   * Show the pivot tool panel.
   * Opens the tool panel and ensures this section is expanded.
   */
  showPanel() {
    this.grid.openToolPanel(), this.grid.expandedToolPanelSections.includes(v.PANEL_ID) || this.grid.toggleToolPanelSection(v.PANEL_ID);
  }
  /**
   * Hide the tool panel.
   */
  hidePanel() {
    this.grid.closeToolPanel();
  }
  /**
   * Toggle the pivot tool panel section.
   */
  togglePanel() {
    this.grid.isToolPanelOpen || this.grid.openToolPanel(), this.grid.toggleToolPanelSection(v.PANEL_ID);
  }
  /**
   * Check if the pivot panel section is currently expanded.
   */
  isPanelVisible() {
    return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(v.PANEL_ID);
  }
  // #endregion
  // #region Private Helpers
  get gridColumns() {
    return this.grid.columns ?? [];
  }
  /**
   * Refresh pivot and update tool panel if active.
   */
  refreshIfActive() {
    this.isActive && this.refresh(), this.refreshPanel();
  }
  buildFieldHeaderMap() {
    const e = this.getAvailableFields();
    this.fieldHeaderMap.clear();
    for (const t of e)
      this.fieldHeaderMap.set(t.field, t.header);
  }
  getAvailableFields() {
    return this.originalColumns.length > 0 ? this.originalColumns : this.captureOriginalColumns();
  }
  captureOriginalColumns() {
    try {
      const e = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];
      return this.originalColumns = e.filter((t) => !t.field.startsWith("__pivot")).map((t) => ({
        field: t.field,
        header: t.header ?? t.field
      })), this.originalColumns;
    } catch {
      return [];
    }
  }
  renderPanel(e) {
    this.panelContainer = e, this.originalColumns.length === 0 && this.captureOriginalColumns();
    const t = {
      onTogglePivot: (o) => {
        o ? this.enablePivot() : this.disablePivot(), this.refreshPanel();
      },
      onAddFieldToZone: (o, r) => this.addFieldToZone(o, r),
      onRemoveFieldFromZone: (o, r) => this.removeFieldFromZone(o, r),
      onAddValueField: (o, r) => this.addValueField(o, r),
      onRemoveValueField: (o) => this.removeValueField(o),
      onUpdateValueAggFunc: (o, r) => this.updateValueAggFunc(o, r),
      onOptionChange: (o, r) => {
        this.config[o] = r, this.isActive && this.refresh();
      },
      getAvailableFields: () => this.getAvailableFields()
    };
    return M(e, this.config, this.isActive, t);
  }
  refreshPanel() {
    this.panelContainer && (this.panelContainer.innerHTML = "", this.renderPanel(this.panelContainer));
  }
  addFieldToZone(e, t) {
    if (t === "rowGroups") {
      const o = this.config.rowGroupFields ?? [];
      o.includes(e) || (this.config.rowGroupFields = [...o, e]);
    } else {
      const o = this.config.columnGroupFields ?? [];
      o.includes(e) || (this.config.columnGroupFields = [...o, e]);
    }
    this.removeFromOtherZones(e, t), this.refreshIfActive();
  }
  removeFieldFromZone(e, t) {
    t === "rowGroups" ? this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((o) => o !== e) : this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((o) => o !== e), this.refreshIfActive();
  }
  removeFromOtherZones(e, t) {
    t !== "rowGroups" && (this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((o) => o !== e)), t !== "columnGroups" && (this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((o) => o !== e)), t !== "values" && (this.config.valueFields = (this.config.valueFields ?? []).filter((o) => o.field !== e));
  }
  addValueField(e, t) {
    const o = this.config.valueFields ?? [];
    o.some((r) => r.field === e) || (this.config.valueFields = [...o, { field: e, aggFunc: t }]), this.removeFromOtherZones(e, "values"), this.refreshIfActive();
  }
  removeValueField(e) {
    this.config.valueFields = (this.config.valueFields ?? []).filter((t) => t.field !== e), this.refreshIfActive();
  }
  updateValueAggFunc(e, t) {
    const o = this.config.valueFields ?? [], r = o.findIndex((n) => n.field === e);
    r >= 0 && (o[r] = { ...o[r], aggFunc: t }, this.config.valueFields = [...o]), this.isActive && this.refresh();
  }
  // #endregion
}
export {
  v as PivotPlugin
};
//# sourceMappingURL=index.js.map
