const x = '<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>', R = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰",
  filter: x,
  filterActive: x,
  print: "🖨️"
};
class C {
  /**
   * 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 manifest - declares owned properties, config rules, and hook priorities.
   *
   * This is read by the configuration validator to:
   * - Validate that required plugins are loaded when their properties are used
   * - Execute configRules to detect invalid/conflicting settings
   * - Order hook execution based on priority
   *
   * @example
   * ```typescript
   * static override readonly manifest: PluginManifest<MyConfig> = {
   *   ownedProperties: [
   *     { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
   *   ],
   *   configRules: [
   *     { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
   *   ],
   * };
   * ```
   */
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  static manifest;
  /**
   * 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 r = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(r), r.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 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 { ...R, ...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(), r = parseInt(t, 10);
      if (!isNaN(r)) return r;
    }
    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 E = "__tbw_expander";
function A(n) {
  return n.field === E;
}
const y = {
  sum: (n, e) => n.reduce((t, r) => t + (Number(r[e]) || 0), 0),
  avg: (n, e) => {
    const t = n.reduce((r, s) => r + (Number(s[e]) || 0), 0);
    return n.length ? t / n.length : 0;
  },
  count: (n) => n.length,
  min: (n, e) => Math.min(...n.map((t) => Number(t[e]) || 1 / 0)),
  max: (n, e) => Math.max(...n.map((t) => Number(t[e]) || -1 / 0)),
  first: (n, e) => n[0]?.[e],
  last: (n, e) => n[n.length - 1]?.[e]
}, b = /* @__PURE__ */ new Map(), m = {
  /**
   * Register a custom aggregator function.
   */
  register(n, e) {
    b.set(n, e);
  },
  /**
   * Unregister a custom aggregator function.
   */
  unregister(n) {
    b.delete(n);
  },
  /**
   * Get an aggregator function by reference.
   */
  get(n) {
    if (n !== void 0)
      return typeof n == "function" ? n : b.get(n) ?? y[n];
  },
  /**
   * Run an aggregator on a set of rows.
   */
  run(n, e, t, r) {
    const s = this.get(n);
    return s ? s(e, t, r) : void 0;
  },
  /**
   * Check if an aggregator exists.
   */
  has(n) {
    return b.has(n) || n in y;
  },
  /**
   * List all available aggregator names.
   */
  list() {
    return [...Object.keys(y), ...b.keys()];
  }
};
m.register.bind(m);
m.unregister.bind(m);
m.get.bind(m);
const _ = m.run.bind(m);
m.list.bind(m);
function v({ rows: n, config: e, expanded: t, initialExpanded: r }) {
  const s = e.groupOn;
  if (typeof s != "function")
    return [];
  const o = { key: "__root__", value: null, depth: -1, rows: [], children: /* @__PURE__ */ new Map() };
  if (n.forEach((d) => {
    let u = s(d);
    u == null || u === !1 ? u = ["__ungrouped__"] : Array.isArray(u) || (u = [u]);
    let a = o;
    u.forEach((w, p) => {
      const c = w == null ? "∅" : String(w), h = a.key === "__root__" ? c : a.key + "||" + c;
      let f = a.children.get(c);
      f || (f = { key: h, value: w, depth: p, rows: [], children: /* @__PURE__ */ new Map(), parent: a }, a.children.set(c, f)), a = f;
    }), a.rows.push(d);
  }), o.children.size === 1 && o.children.has("__ungrouped__") && o.children.get("__ungrouped__").rows.length === n.length)
    return [];
  const g = /* @__PURE__ */ new Set([...t, ...r ?? []]), i = [], l = (d) => {
    if (d === o) {
      d.children.forEach((a) => l(a));
      return;
    }
    const u = g.has(d.key);
    i.push({
      kind: "group",
      key: d.key,
      value: d.value,
      depth: d.depth,
      rows: d.rows,
      expanded: u
    }), u && (d.children.size ? d.children.forEach((a) => l(a)) : d.rows.forEach((a) => i.push({ kind: "data", row: a, rowIndex: n.indexOf(a) })));
  };
  return l(o), i;
}
function S(n, e) {
  const t = new Set(n);
  return t.has(e) ? t.delete(e) : t.add(e), t;
}
function K(n) {
  const e = /* @__PURE__ */ new Set();
  for (const t of n)
    t.kind === "group" && e.add(t.key);
  return e;
}
function k() {
  return /* @__PURE__ */ new Set();
}
function G(n, e) {
  if (n === !0)
    return new Set(e);
  if (n === !1 || n == null)
    return /* @__PURE__ */ new Set();
  if (typeof n == "number") {
    const t = e[n];
    return t ? /* @__PURE__ */ new Set([t]) : /* @__PURE__ */ new Set();
  }
  return typeof n == "string" ? /* @__PURE__ */ new Set([n]) : Array.isArray(n) ? new Set(n) : /* @__PURE__ */ new Set();
}
function T(n) {
  return n.filter((e) => e.kind === "group").map((e) => e.key);
}
function I(n) {
  return n.kind !== "group" ? 0 : n.rows.length;
}
const N = "@layer tbw-plugins{.group-row{display:grid;grid-template-columns:var(--tbw-column-template);background:var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));font-weight:500;border-bottom:var(--tbw-row-divider);min-height:var(--tbw-row-height)}.group-row .cell{display:flex;align-items:center;padding:var(--tbw-cell-padding, .125rem .5rem)}.group-row:hover{background:var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover))}.group-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-toggle-size, 1.25rem);height:var(--tbw-toggle-size, 1.25rem);margin-right:.25rem;background:none;border:0;font:inherit}.group-toggle:hover{background:var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));border-radius:var(--tbw-border-radius, .125rem)}.group-label{display:inline-flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.group-count{color:var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));font-size:var(--tbw-font-size-xs, .85em);font-weight:400}.group-aggregates{display:inline-flex;align-items:center;gap:var(--tbw-spacing-lg, 1rem);margin-left:var(--tbw-spacing-lg, 1rem);font-weight:400;font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-grouping-rows-aggregate-color, var(--tbw-color-fg-muted))}.group-aggregate{white-space:nowrap}.group-row{padding-left:calc(var(--tbw-group-depth, 0) * var(--tbw-group-indent-width, 1.25em))}.data-grid-row.tbw-group-slide-in{animation:tbw-group-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}.data-grid-row.tbw-group-fade-in{animation:tbw-group-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-group-fade-in{0%{opacity:0}to{opacity:1}}}";
class D extends C {
  /**
   * Plugin manifest - declares configuration validation rules.
   * @internal
   */
  static manifest = {
    configRules: [
      {
        id: "groupingRows/accordion-defaultExpanded",
        severity: "warn",
        message: `"accordion: true" and "defaultExpanded" (non-false) are used together.
  → In accordion mode, only one group can be open at a time.
  → Using defaultExpanded with multiple groups will collapse to one on first toggle.
  → Consider using "defaultExpanded: false" or a single group key/index with accordion mode.`,
        check: (e) => e.accordion === !0 && e.defaultExpanded !== !1 && e.defaultExpanded !== void 0 && typeof e.defaultExpanded != "number" && typeof e.defaultExpanded != "string" && // Warn if true or array with multiple items
        (e.defaultExpanded === !0 || Array.isArray(e.defaultExpanded) && e.defaultExpanded.length > 1)
      }
    ]
  };
  /** @internal */
  name = "groupingRows";
  /** @internal */
  styles = N;
  /** @internal */
  get defaultConfig() {
    return {
      defaultExpanded: !1,
      showRowCount: !0,
      indentWidth: 20,
      aggregators: {},
      animation: "slide",
      accordion: !1
    };
  }
  // #region Internal State
  expandedKeys = /* @__PURE__ */ new Set();
  flattenedRows = [];
  isActive = !1;
  previousVisibleKeys = /* @__PURE__ */ new Set();
  keysToAnimate = /* @__PURE__ */ new Set();
  /** Track if initial defaultExpanded has been applied */
  hasAppliedDefaultExpanded = !1;
  // #endregion
  // #region Animation
  /**
   * 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
  /** @internal */
  detach() {
    this.expandedKeys.clear(), this.flattenedRows = [], this.isActive = !1, this.previousVisibleKeys.clear(), this.keysToAnimate.clear(), this.hasAppliedDefaultExpanded = !1;
  }
  // #endregion
  // #region Hooks
  /**
   * Auto-detect grouping configuration from grid config.
   * Called by plugin system to determine if plugin should activate.
   */
  static detect(e, t) {
    return typeof t?.groupOn == "function" || typeof t?.enableRowGrouping == "boolean";
  }
  /** @internal */
  processRows(e) {
    const t = this.config;
    if (typeof t.groupOn != "function")
      return this.isActive = !1, this.flattenedRows = [], [...e];
    const r = v({
      rows: [...e],
      config: t,
      expanded: /* @__PURE__ */ new Set()
      // Empty to get all root groups
    });
    if (r.length === 0)
      return this.isActive = !1, this.flattenedRows = [], [...e];
    let s;
    if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && t.defaultExpanded !== !1) {
      const i = T(r);
      s = G(t.defaultExpanded ?? !1, i), s.size > 0 && (this.expandedKeys = new Set(s), this.hasAppliedDefaultExpanded = !0);
    }
    const o = v({
      rows: [...e],
      config: t,
      expanded: this.expandedKeys,
      initialExpanded: s
    });
    this.isActive = !0, this.flattenedRows = o, this.keysToAnimate.clear();
    const g = /* @__PURE__ */ new Set();
    return o.forEach((i, l) => {
      if (i.kind === "data") {
        const d = `data-${l}`;
        g.add(d), this.previousVisibleKeys.has(d) || this.keysToAnimate.add(d);
      }
    }), this.previousVisibleKeys = g, o.map((i) => i.kind === "group" ? {
      __isGroupRow: !0,
      __groupKey: i.key,
      __groupValue: i.value,
      __groupDepth: i.depth,
      __groupRows: i.rows,
      __groupExpanded: i.expanded,
      __groupRowCount: I(i)
    } : i.row);
  }
  /** @internal */
  onCellClick(e) {
    const t = e.row;
    if (t?.__isGroupRow && e.originalEvent.target?.closest(".group-toggle"))
      return this.toggle(t.__groupKey), !0;
  }
  /** @internal */
  onKeyDown(e) {
    if (e.key !== " ") return;
    const t = this.grid._focusRow, r = this.rows[t];
    if (r?.__isGroupRow)
      return e.preventDefault(), this.toggle(r.__groupKey), this.requestRenderWithFocus(), !0;
  }
  /**
   * Render a row. Returns true if we handled the row (group row), false otherwise.
   * @internal
   */
  renderRow(e, t, r) {
    if (!e?.__isGroupRow)
      return !1;
    const s = this.config;
    if (s.groupRowRenderer) {
      const i = () => {
        this.toggle(e.__groupKey);
      }, l = s.groupRowRenderer({
        key: e.__groupKey,
        value: e.__groupValue,
        depth: e.__groupDepth,
        rows: e.__groupRows,
        expanded: e.__groupExpanded,
        toggleExpand: i
      });
      if (l)
        return t.className = "data-grid-row group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), typeof l == "string" ? t.innerHTML = l : (t.innerHTML = "", t.appendChild(l)), !0;
    }
    const o = () => {
      this.toggle(e.__groupKey);
    };
    return t.className = "data-grid-row group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), t.setAttribute("role", "row"), t.setAttribute("aria-expanded", String(e.__groupExpanded)), t.style.setProperty("--tbw-group-depth", String(e.__groupDepth || 0)), s.indentWidth !== void 0 && t.style.setProperty("--tbw-group-indent-width", `${s.indentWidth}px`), t.style.height = "", t.innerHTML = "", s.fullWidth !== !1 ? this.renderFullWidthGroupRow(e, t, o) : this.renderPerColumnGroupRow(e, t, o), !0;
  }
  /** @internal */
  afterRender() {
    const e = this.animationStyle;
    if (e === !1 || this.keysToAnimate.size === 0) return;
    const t = this.gridElement?.querySelector(".rows");
    if (!t) return;
    const r = e === "fade" ? "tbw-group-fade-in" : "tbw-group-slide-in";
    for (const s of t.querySelectorAll(".data-grid-row:not(.group-row)")) {
      const o = s.querySelector(".cell[data-row]"), g = o ? parseInt(o.getAttribute("data-row") ?? "-1", 10) : -1, l = this.flattenedRows[g]?.kind === "data" ? `data-${g}` : void 0;
      l && this.keysToAnimate.has(l) && (s.classList.add(r), s.addEventListener("animationend", () => s.classList.remove(r), { once: !0 }));
    }
    this.keysToAnimate.clear();
  }
  // #endregion
  // #region Private Rendering Helpers
  /**
   * Create a toggle button for expanding/collapsing a group.
   */
  createToggleButton(e, t) {
    const r = document.createElement("button");
    return r.type = "button", r.className = `group-toggle${e ? " expanded" : ""}`, r.setAttribute("aria-label", e ? "Collapse group" : "Expand group"), this.setIcon(r, this.resolveIcon(e ? "collapse" : "expand")), r.addEventListener("click", (s) => {
      s.stopPropagation(), t();
    }), r;
  }
  /**
   * Get the formatted label text for a group.
   */
  getGroupLabelText(e, t, r) {
    const s = this.config;
    return s.formatLabel ? s.formatLabel(e, t, r) : String(e);
  }
  renderFullWidthGroupRow(e, t, r) {
    const s = this.config, o = s.aggregators ?? {}, g = e.__groupRows ?? [], i = document.createElement("div");
    i.className = "cell group-full", i.style.gridColumn = "1 / -1", i.setAttribute("role", "gridcell"), i.setAttribute("data-col", "0"), i.appendChild(this.createToggleButton(e.__groupExpanded, r));
    const l = document.createElement("span");
    if (l.className = "group-label", l.textContent = this.getGroupLabelText(e.__groupValue, e.__groupDepth || 0, e.__groupKey), i.appendChild(l), s.showRowCount !== !1) {
      const u = document.createElement("span");
      u.className = "group-count", u.textContent = `(${e.__groupRowCount ?? e.__groupRows?.length ?? 0})`, i.appendChild(u);
    }
    const d = Object.entries(o);
    if (d.length > 0) {
      const u = document.createElement("span");
      u.className = "group-aggregates";
      for (const [a, w] of d) {
        const p = this.columns.find((h) => h.field === a), c = _(w, g, a, p);
        if (c != null) {
          const h = document.createElement("span");
          h.className = "group-aggregate", h.setAttribute("data-field", a);
          const f = p?.header ?? a;
          h.textContent = `${f}: ${c}`, u.appendChild(h);
        }
      }
      u.children.length > 0 && i.appendChild(u);
    }
    t.appendChild(i);
  }
  renderPerColumnGroupRow(e, t, r) {
    const s = this.config, o = s.aggregators ?? {}, g = this.columns, i = e.__groupRows ?? [], d = this.gridElement?.querySelector(".body")?.style.gridTemplateColumns || "";
    d && (t.style.display = "grid", t.style.gridTemplateColumns = d);
    let u = !1;
    g.forEach((a, w) => {
      const p = document.createElement("div");
      if (p.className = "cell group-cell", p.setAttribute("data-col", String(w)), p.setAttribute("role", "gridcell"), A(a)) {
        p.setAttribute("data-field", a.field), t.appendChild(p);
        return;
      }
      if (u) {
        const c = o[a.field];
        if (c) {
          const h = _(c, i, a.field, a);
          p.textContent = h != null ? String(h) : "";
        } else
          p.textContent = "";
      } else {
        u = !0, p.appendChild(this.createToggleButton(e.__groupExpanded, r));
        const c = document.createElement("span"), h = o[a.field];
        if (h) {
          const f = _(h, i, a.field, a);
          c.textContent = f != null ? String(f) : String(e.__groupValue);
        } else
          c.textContent = this.getGroupLabelText(e.__groupValue, e.__groupDepth || 0, e.__groupKey);
        if (p.appendChild(c), s.showRowCount !== !1) {
          const f = document.createElement("span");
          f.className = "group-count", f.textContent = ` (${i.length})`, p.appendChild(f);
        }
      }
      t.appendChild(p);
    });
  }
  // #endregion
  // #region Public API
  /**
   * Expand all groups.
   */
  expandAll() {
    this.expandedKeys = K(this.flattenedRows), this.requestRender();
  }
  /**
   * Collapse all groups.
   */
  collapseAll() {
    this.expandedKeys = k(), this.requestRender();
  }
  /**
   * Toggle expansion of a specific group.
   * In accordion mode, expanding a group will collapse all sibling groups.
   * @param key - The group key to toggle
   */
  toggle(e) {
    const t = !this.expandedKeys.has(e), r = this.config, s = this.flattenedRows.find((o) => o.kind === "group" && o.key === e);
    if (r.accordion && t && s) {
      const o = /* @__PURE__ */ new Set();
      for (const g of this.expandedKeys)
        if (e.startsWith(g + "||") || g.startsWith(e + "||"))
          e.startsWith(g + "||") && o.add(g);
        else {
          const i = this.flattenedRows.find((l) => l.kind === "group" && l.key === g);
          i && i.depth !== s.depth && o.add(g);
        }
      o.add(e), this.expandedKeys = o;
    } else
      this.expandedKeys = S(this.expandedKeys, e);
    this.emit("group-toggle", {
      key: e,
      expanded: this.expandedKeys.has(e),
      value: s?.value,
      depth: s?.depth ?? 0
    }), this.requestRender();
  }
  /**
   * Check if a specific group is expanded.
   * @param key - The group key to check
   * @returns Whether the group is expanded
   */
  isExpanded(e) {
    return this.expandedKeys.has(e);
  }
  /**
   * Expand a specific group.
   * @param key - The group key to expand
   */
  expand(e) {
    this.expandedKeys.has(e) || (this.expandedKeys = /* @__PURE__ */ new Set([...this.expandedKeys, e]), this.requestRender());
  }
  /**
   * Collapse a specific group.
   * @param key - The group key to collapse
   */
  collapse(e) {
    if (this.expandedKeys.has(e)) {
      const t = new Set(this.expandedKeys);
      t.delete(e), this.expandedKeys = t, this.requestRender();
    }
  }
  /**
   * Get the current group state.
   * @returns Group state information
   */
  getGroupState() {
    const e = this.flattenedRows.filter((t) => t.kind === "group");
    return {
      isActive: this.isActive,
      expandedCount: this.expandedKeys.size,
      totalGroups: e.length,
      expandedKeys: [...this.expandedKeys]
    };
  }
  /**
   * Get the total count of visible rows (including group headers).
   * @returns Number of visible rows
   */
  getRowCount() {
    return this.flattenedRows.length;
  }
  /**
   * Refresh the grouped row model.
   * Call this after modifying groupOn or other config options.
   */
  refreshGroups() {
    this.requestRender();
  }
  /**
   * Get current expanded group keys.
   * @returns Array of expanded group keys
   */
  getExpandedGroups() {
    return [...this.expandedKeys];
  }
  /**
   * Get the flattened row model.
   * @returns Array of render rows (groups + data rows)
   */
  getFlattenedRows() {
    return this.flattenedRows;
  }
  /**
   * Check if grouping is currently active.
   * @returns Whether grouping is active
   */
  isGroupingActive() {
    return this.isActive;
  }
  /**
   * Set the groupOn function dynamically.
   * @param fn - The groupOn function or undefined to disable
   */
  setGroupOn(e) {
    this.config.groupOn = e, this.requestRender();
  }
  // #endregion
}
export {
  D as GroupingRowsPlugin
};
//# sourceMappingURL=index.js.map
