const x = /{{\s*([^}]+)\s*}}/g, u = "__DG_EMPTY__", R = /^[\w$. '?+\-*/%:()!<>=,&|]+$/, E = /__(proto|defineGetter|defineSetter)|constructor|window|globalThis|global|process|Function|import|eval|Reflect|Proxy|Error|arguments|document|location|cookie|localStorage|sessionStorage|indexedDB|fetch|XMLHttpRequest|WebSocket|Worker|SharedWorker|ServiceWorker|opener|parent|top|frames|self|this\b/, v = /* @__PURE__ */ new Set([
  "script",
  "iframe",
  "object",
  "embed",
  "form",
  "input",
  "button",
  "textarea",
  "select",
  "link",
  "meta",
  "base",
  "style",
  "template",
  "slot",
  "portal",
  "frame",
  "frameset",
  "applet",
  "noscript",
  "noembed",
  "plaintext",
  "xmp",
  "listing"
]), g = /^on\w+$/i, y = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "data", "srcdoc", "xlink:href", "poster", "srcset"]), C = /^\s*(javascript|vbscript|data|blob):/i;
function _(n) {
  if (!n || typeof n != "string") return "";
  if (n.indexOf("<") === -1) return n;
  const e = document.createElement("template");
  return e.innerHTML = n, A(e.content), e.innerHTML;
}
function A(n) {
  const e = [], t = n.querySelectorAll("*");
  for (const i of t) {
    const s = i.tagName.toLowerCase();
    if (v.has(s)) {
      e.push(i);
      continue;
    }
    if ((s === "svg" || i.namespaceURI === "http://www.w3.org/2000/svg") && Array.from(i.attributes).some(
      (r) => g.test(r.name) || r.name === "href" || r.name === "xlink:href"
    )) {
      e.push(i);
      continue;
    }
    const l = [];
    for (const o of i.attributes) {
      const r = o.name.toLowerCase();
      if (g.test(r)) {
        l.push(o.name);
        continue;
      }
      if (y.has(r) && C.test(o.value)) {
        l.push(o.name);
        continue;
      }
      if (r === "style" && /expression\s*\(|javascript:|behavior\s*:/i.test(o.value)) {
        l.push(o.name);
        continue;
      }
    }
    l.forEach((o) => i.removeAttribute(o));
  }
  e.forEach((i) => i.remove());
}
function D(n, e) {
  if (!n || n.indexOf("{{") === -1) return n;
  const t = [], i = n.replace(x, (r, a) => {
    const d = S(a, e);
    return t.push({ expr: a.trim(), result: d }), d;
  }), s = L(i), l = t.length && t.every((r) => r.result === "" || r.result === u);
  return /Reflect\.|\bProxy\b|ownKeys\(/.test(n) || l ? "" : s;
}
function S(n, e) {
  if (n = (n || "").trim(), !n || /\b(Reflect|Proxy|ownKeys)\b/.test(n)) return u;
  if (n === "value") return e.value == null ? u : String(e.value);
  if (n.startsWith("row.") && !/[()?]/.test(n) && !n.includes(":")) {
    const i = n.slice(4), s = e.row ? e.row[i] : void 0;
    return s == null ? u : String(s);
  }
  if (n.length > 80 || !R.test(n) || E.test(n)) return u;
  const t = n.match(/\./g);
  if (t && t.length > 1) return u;
  try {
    const s = new Function("value", "row", `return (${n});`)(e.value, e.row), l = s == null ? "" : String(s);
    return /Reflect|Proxy|ownKeys/.test(l) ? u : l || u;
  } catch {
    return u;
  }
}
function L(n) {
  return n && n.replace(new RegExp(u, "g"), "").replace(/Reflect\.[^<>{}\s]+/g, "").replace(/\bProxy\b/g, "").replace(/ownKeys\([^)]*\)/g, "");
}
const I = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class T {
  /**
   * 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 { ...I, ...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 p = "__tbw_expander", H = 32;
function m(n) {
  return n.field === p;
}
function k(n) {
  return n.find(m);
}
function O(n) {
  return {
    field: p,
    header: "",
    // No header text - visually merges with next column
    width: H,
    resizable: !1,
    sortable: !1,
    filterable: !1,
    // No filter button for expander column
    meta: {
      lockPosition: !0,
      suppressMovable: !0,
      expanderColumn: !0,
      expanderPlugin: n,
      utility: !0
      // Marks this as a utility column (excluded from selection, clipboard, etc.)
    }
  };
}
function h(n, e) {
  const t = new Set(n);
  return t.has(e) ? t.delete(e) : t.add(e), t;
}
function N(n, e) {
  const t = new Set(n);
  return t.add(e), t;
}
function q(n, e) {
  const t = new Set(n);
  return t.delete(e), t;
}
function P(n, e) {
  return n.has(e);
}
function M(n, e, t, i) {
  const s = document.createElement("div");
  s.className = "master-detail-row", s.setAttribute("data-detail-for", String(e)), s.setAttribute("role", "row");
  const l = document.createElement("div");
  l.className = "master-detail-cell", l.setAttribute("role", "cell"), l.style.gridColumn = `1 / ${i + 1}`;
  const o = t(n, e);
  return typeof o == "string" ? l.innerHTML = o : o instanceof HTMLElement && l.appendChild(o), s.appendChild(l), s;
}
const F = "@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";
class w extends T {
  name = "masterDetail";
  styles = F;
  get defaultConfig() {
    return {
      detailHeight: "auto",
      expandOnRowClick: !1,
      collapseOnClickOutside: !1,
      showExpandColumn: !0,
      animation: "slide"
      // Plugin's own default
    };
  }
  // #region Light DOM Parsing
  /**
   * Called when plugin is attached to the grid.
   * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.
   */
  attach(e) {
    super.attach(e), this.parseLightDomDetail();
  }
  /**
   * Parse `<tbw-grid-detail>` elements from the grid's light DOM.
   *
   * Allows declarative configuration:
   * ```html
   * <tbw-grid [rows]="data">
   *   <tbw-grid-detail>
   *     <div class="detail-content">
   *       <p>Name: {{ row.name }}</p>
   *       <p>Email: {{ row.email }}</p>
   *     </div>
   *   </tbw-grid-detail>
   * </tbw-grid>
   * ```
   *
   * Attributes:
   * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')
   * - `show-expand-column`: 'true' | 'false' (default: 'true')
   * - `expand-on-row-click`: 'true' | 'false' (default: 'false')
   * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')
   * - `height`: number (pixels) or 'auto' (default: 'auto')
   */
  parseLightDomDetail() {
    const e = this.grid;
    if (!e || typeof e.querySelector != "function") return;
    const t = e.querySelector("tbw-grid-detail");
    if (!t) return;
    const i = e;
    if (i.__frameworkAdapter?.parseDetailElement) {
      const f = i.__frameworkAdapter.parseDetailElement(t);
      if (f) {
        this.config = { ...this.config, detailRenderer: f };
        return;
      }
    }
    const s = t.getAttribute("animation"), l = t.getAttribute("show-expand-column"), o = t.getAttribute("expand-on-row-click"), r = t.getAttribute("collapse-on-click-outside"), a = t.getAttribute("height"), d = {};
    s !== null && (d.animation = s === "false" ? !1 : s), l !== null && (d.showExpandColumn = l !== "false"), o !== null && (d.expandOnRowClick = o === "true"), r !== null && (d.collapseOnClickOutside = r === "true"), a !== null && (d.detailHeight = a === "auto" ? "auto" : parseInt(a, 10));
    const c = t.innerHTML.trim();
    c && !this.config.detailRenderer && (d.detailRenderer = (f, G) => {
      const b = D(c, { value: f, row: f });
      return _(b);
    }), Object.keys(d).length > 0 && (this.config = { ...this.config, ...d });
  }
  // #endregion
  // #region Animation Helpers
  /**
   * 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;
  }
  /**
   * Apply expand animation to a detail element.
   */
  animateExpand(e) {
    !this.isAnimationEnabled || this.animationStyle === !1 || (e.classList.add("tbw-expanding"), e.addEventListener(
      "animationend",
      () => {
        e.classList.remove("tbw-expanding");
      },
      { once: !0 }
    ));
  }
  /**
   * Apply collapse animation to a detail element and remove after animation.
   */
  animateCollapse(e, t) {
    if (!this.isAnimationEnabled || this.animationStyle === !1) {
      t();
      return;
    }
    e.classList.add("tbw-collapsing");
    const i = () => {
      e.classList.remove("tbw-collapsing"), t();
    };
    e.addEventListener("animationend", i, { once: !0 }), setTimeout(i, this.animationDuration + 50);
  }
  // #endregion
  // #region Internal State
  expandedRows = /* @__PURE__ */ new Set();
  detailElements = /* @__PURE__ */ new Map();
  /** Default height for detail rows when not configured */
  static DEFAULT_DETAIL_HEIGHT = 150;
  /**
   * Get the estimated height for a detail row.
   */
  getDetailHeight(e) {
    const t = this.detailElements.get(e);
    return t ? t.offsetHeight : typeof this.config?.detailHeight == "number" ? this.config.detailHeight : w.DEFAULT_DETAIL_HEIGHT;
  }
  /**
   * Toggle a row's detail and emit event.
   */
  toggleAndEmit(e, t) {
    this.expandedRows = h(this.expandedRows, e), this.emit("detail-expand", {
      rowIndex: t,
      row: e,
      expanded: this.expandedRows.has(e)
    }), this.requestRender();
  }
  // #endregion
  // #region Lifecycle
  detach() {
    this.expandedRows.clear(), this.detailElements.clear();
  }
  // #endregion
  // #region Hooks
  processColumns(e) {
    if (!this.config.detailRenderer || this.config.showExpandColumn === !1)
      return [...e];
    const t = [...e];
    if (k(t))
      return t;
    const s = O(this.name);
    return s.viewRenderer = (l) => {
      const { row: o } = l, r = this.expandedRows.has(o), a = document.createElement("span");
      a.className = "master-detail-expander expander-cell";
      const d = document.createElement("span");
      return d.className = `master-detail-toggle${r ? " expanded" : ""}`, this.setIcon(d, this.resolveIcon(r ? "collapse" : "expand")), d.setAttribute("role", "button"), d.setAttribute("tabindex", "0"), d.setAttribute("aria-expanded", String(r)), d.setAttribute("aria-label", r ? "Collapse details" : "Expand details"), a.appendChild(d), a;
    }, [s, ...t];
  }
  onRowClick(e) {
    if (!(!this.config.expandOnRowClick || !this.config.detailRenderer))
      return this.toggleAndEmit(e.row, e.rowIndex), !1;
  }
  onCellClick(e) {
    if (e.originalEvent?.target?.classList.contains("master-detail-toggle"))
      return this.toggleAndEmit(e.row, e.rowIndex), !0;
    this.expandedRows.size > 0 && queueMicrotask(() => this.#e());
  }
  onKeyDown(e) {
    if (e.key !== " ") return;
    const t = this.grid._focusCol, i = this.grid._focusRow, s = this.columns[t];
    if (!s || !m(s)) return;
    const l = this.rows[i];
    if (l)
      return e.preventDefault(), this.toggleAndEmit(l, i), this.requestRenderWithFocus(), !0;
  }
  afterRender() {
    this.#e();
  }
  /**
   * Called on scroll to sync detail elements with visible rows.
   * Removes details for rows that scrolled out of view and reattaches for visible rows.
   */
  onScrollRender() {
    !this.config.detailRenderer || this.expandedRows.size === 0 || this.#e();
  }
  /**
   * Full sync of detail rows - cleans up stale elements and creates new ones.
   * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.
   */
  #e() {
    if (!this.config.detailRenderer) return;
    const e = this.gridElement?.querySelector(".rows");
    if (!e) return;
    const t = /* @__PURE__ */ new Map(), i = e.querySelectorAll(".data-grid-row"), s = this.columns.length;
    for (const o of i) {
      const r = o.querySelector(".cell[data-row]"), a = r ? parseInt(r.getAttribute("data-row") ?? "-1", 10) : -1;
      a >= 0 && t.set(a, o);
    }
    const l = e.querySelectorAll(".master-detail-row");
    for (const o of l) {
      const r = parseInt(o.getAttribute("data-detail-for") ?? "-1", 10), a = r >= 0 ? this.rows[r] : void 0, d = a && this.expandedRows.has(a), c = t.has(r);
      (!d || !c) && (o.remove(), a && this.detailElements.delete(a));
    }
    for (const [o, r] of t) {
      const a = this.rows[o];
      if (!a || !this.expandedRows.has(a)) continue;
      const d = this.detailElements.get(a);
      if (d) {
        d.previousElementSibling !== r && r.after(d);
        continue;
      }
      const c = M(a, o, this.config.detailRenderer, s);
      typeof this.config.detailHeight == "number" && (c.style.height = `${this.config.detailHeight}px`), r.after(c), this.detailElements.set(a, c), this.animateExpand(c);
    }
  }
  /**
   * Return total extra height from all expanded detail rows.
   * Used by grid virtualization to adjust scrollbar height.
   */
  getExtraHeight() {
    let e = 0;
    for (const t of this.expandedRows)
      e += this.getDetailHeight(t);
    return e;
  }
  /**
   * Return extra height that appears before a given row index.
   * This is the sum of heights of all expanded details whose parent row is before the given index.
   */
  getExtraHeightBefore(e) {
    let t = 0;
    for (const i of this.expandedRows) {
      const s = this.rows.indexOf(i);
      s >= 0 && s < e && (t += this.getDetailHeight(i));
    }
    return t;
  }
  /**
   * Adjust the virtualization start index to keep expanded row visible while its detail is visible.
   * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.
   */
  adjustVirtualStart(e, t, i) {
    if (this.expandedRows.size === 0) return e;
    const s = [];
    for (const r of this.expandedRows) {
      const a = this.rows.indexOf(r);
      a >= 0 && s.push({ index: a, row: r });
    }
    s.sort((r, a) => r.index - a.index);
    let l = e, o = 0;
    for (const { index: r, row: a } of s) {
      const d = r * i + o, c = this.getDetailHeight(a), f = d + i + c;
      o += c, !(r >= e) && f > t && r < l && (l = r);
    }
    return l;
  }
  // #endregion
  // #region Public API
  /**
   * Expand the detail row at the given index.
   * @param rowIndex - Index of the row to expand
   */
  expand(e) {
    const t = this.rows[e];
    t && (this.expandedRows = N(this.expandedRows, t), this.requestRender());
  }
  /**
   * Collapse the detail row at the given index.
   * @param rowIndex - Index of the row to collapse
   */
  collapse(e) {
    const t = this.rows[e];
    t && (this.expandedRows = q(this.expandedRows, t), this.requestRender());
  }
  /**
   * Toggle the detail row at the given index.
   * @param rowIndex - Index of the row to toggle
   */
  toggle(e) {
    const t = this.rows[e];
    t && (this.expandedRows = h(this.expandedRows, t), this.requestRender());
  }
  /**
   * Check if the detail row at the given index is expanded.
   * @param rowIndex - Index of the row to check
   * @returns Whether the detail row is expanded
   */
  isExpanded(e) {
    const t = this.rows[e];
    return t ? P(this.expandedRows, t) : !1;
  }
  /**
   * Expand all detail rows.
   */
  expandAll() {
    for (const e of this.rows)
      this.expandedRows.add(e);
    this.requestRender();
  }
  /**
   * Collapse all detail rows.
   */
  collapseAll() {
    this.expandedRows.clear(), this.requestRender();
  }
  /**
   * Get the indices of all expanded rows.
   * @returns Array of row indices that are expanded
   */
  getExpandedRows() {
    const e = [];
    for (const t of this.expandedRows) {
      const i = this.rows.indexOf(t);
      i >= 0 && e.push(i);
    }
    return e;
  }
  /**
   * Get the detail element for a specific row.
   * @param rowIndex - Index of the row
   * @returns The detail HTMLElement or undefined
   */
  getDetailElement(e) {
    const t = this.rows[e];
    return t ? this.detailElements.get(t) : void 0;
  }
  /**
   * Re-parse light DOM to refresh the detail renderer.
   * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).
   *
   * This allows frameworks to register templates asynchronously and then
   * update the plugin's detailRenderer.
   */
  refreshDetailRenderer() {
    const e = this.config.detailRenderer;
    if (this.config = { ...this.config, detailRenderer: void 0 }, this.parseLightDomDetail(), !this.config.detailRenderer && e && (this.config = { ...this.config, detailRenderer: e }), this.config.detailRenderer) {
      const t = this.grid;
      typeof t.refreshColumns == "function" ? t.refreshColumns() : this.requestRender();
    }
  }
  // #endregion
}
export {
  w as MasterDetailPlugin
};
//# sourceMappingURL=index.js.map
