const b = '<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>', A = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰",
  filter: b,
  filterActive: b,
  print: "🖨️"
};
class R {
  /**
   * 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.
   */
  #t;
  /**
   * 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(t = {}) {
    this.userConfig = t;
  }
  /**
   * 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(t) {
    this.#t?.abort(), this.#t = new AbortController(), this.grid = t, 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.#t?.abort(), this.#t = 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(t) {
    return this.grid?.getPlugin(t);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(t, e) {
    this.grid?.dispatchEvent?.(new CustomEvent(t, { detail: e, bubbles: !0 }));
  }
  /**
   * Emit a cancelable custom event from the grid.
   * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
   */
  emitCancelable(t, e) {
    const o = new CustomEvent(t, { detail: e, 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 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.#t?.signal ?? this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const t = this.grid?.gridConfig?.icons ?? {};
    return { ...A, ...t };
  }
  // #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 t = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
    if (t === !1 || t === "off") return !1;
    if (t === !0 || t === "on") return !0;
    const e = this.gridElement;
    return e ? getComputedStyle(e).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 t = this.gridElement;
    if (t) {
      const e = getComputedStyle(t).getPropertyValue("--tbw-animation-duration").trim(), o = parseInt(e, 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(t, e) {
    return e !== void 0 ? e : this.gridIcons[t];
  }
  /**
   * 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(t, e) {
    typeof e == "string" ? t.innerHTML = e : e instanceof HTMLElement && (t.innerHTML = "", t.appendChild(e.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(t) {
    console.warn(`[tbw-grid:${this.name}] ${t}`);
  }
  // #endregion
}
const p = {
  sum: (n, t) => n.reduce((e, o) => e + (Number(o[t]) || 0), 0),
  avg: (n, t) => {
    const e = n.reduce((o, r) => o + (Number(r[t]) || 0), 0);
    return n.length ? e / n.length : 0;
  },
  count: (n) => n.length,
  min: (n, t) => Math.min(...n.map((e) => Number(e[t]) || 1 / 0)),
  max: (n, t) => Math.max(...n.map((e) => Number(e[t]) || -1 / 0)),
  first: (n, t) => n[0]?.[t],
  last: (n, t) => n[n.length - 1]?.[t]
}, f = /* @__PURE__ */ new Map(), d = {
  /**
   * Register a custom aggregator function.
   */
  register(n, t) {
    f.set(n, t);
  },
  /**
   * Unregister a custom aggregator function.
   */
  unregister(n) {
    f.delete(n);
  },
  /**
   * Get an aggregator function by reference.
   */
  get(n) {
    if (n !== void 0)
      return typeof n == "function" ? n : f.get(n) ?? p[n];
  },
  /**
   * Run an aggregator on a set of rows.
   */
  run(n, t, e, o) {
    const r = this.get(n);
    return r ? r(t, e, o) : void 0;
  },
  /**
   * Check if an aggregator exists.
   */
  has(n) {
    return f.has(n) || n in p;
  },
  /**
   * List all available aggregator names.
   */
  list() {
    return [...Object.keys(p), ...f.keys()];
  }
};
d.register.bind(d);
d.unregister.bind(d);
const w = d.get.bind(d);
d.run.bind(d);
d.list.bind(d);
function y(n) {
  return typeof n == "object" && n !== null && "aggFunc" in n;
}
function m(n, t) {
  const e = document.createElement("div");
  e.className = "tbw-pinned-rows", e.setAttribute("role", "presentation"), e.setAttribute("aria-live", "polite");
  const o = document.createElement("div");
  o.className = "tbw-pinned-rows-left";
  const r = document.createElement("div");
  r.className = "tbw-pinned-rows-center";
  const s = document.createElement("div");
  if (s.className = "tbw-pinned-rows-right", n.showRowCount !== !1) {
    const i = document.createElement("span");
    i.className = "tbw-status-panel tbw-status-panel-row-count", i.textContent = `Total: ${t.totalRows} rows`, o.appendChild(i);
  }
  if (n.showFilteredCount && t.filteredRows !== t.totalRows) {
    const i = document.createElement("span");
    i.className = "tbw-status-panel tbw-status-panel-filtered-count", i.textContent = `Filtered: ${t.filteredRows}`, o.appendChild(i);
  }
  if (n.showSelectedCount && t.selectedRows > 0) {
    const i = document.createElement("span");
    i.className = "tbw-status-panel tbw-status-panel-selected-count", i.textContent = `Selected: ${t.selectedRows}`, s.appendChild(i);
  }
  if (n.customPanels)
    for (const i of n.customPanels) {
      const a = S(i, t);
      switch (i.position) {
        case "left":
          o.appendChild(a);
          break;
        case "center":
          r.appendChild(a);
          break;
        case "right":
          s.appendChild(a);
          break;
      }
    }
  return e.appendChild(o), e.appendChild(r), e.appendChild(s), e;
}
function C(n) {
  const t = document.createElement("div");
  return t.className = `tbw-aggregation-rows tbw-aggregation-rows-${n}`, t.setAttribute("role", "presentation"), t;
}
function v(n, t, e, o) {
  n.innerHTML = "";
  for (const r of t) {
    const s = document.createElement("div");
    if (s.className = "tbw-aggregation-row", s.setAttribute("role", "presentation"), r.id && s.setAttribute("data-aggregation-id", r.id), r.fullWidth) {
      const i = document.createElement("div");
      i.className = "tbw-aggregation-cell tbw-aggregation-cell-full", i.style.gridColumn = "1 / -1", i.textContent = r.label || "", s.appendChild(i);
    } else
      for (const i of e) {
        const a = document.createElement("div");
        a.className = "tbw-aggregation-cell", a.setAttribute("data-field", i.field);
        let l, u;
        const h = r.aggregators?.[i.field];
        if (h)
          if (y(h)) {
            const g = w(h.aggFunc);
            g && (l = g(o, i.field, i)), u = h.formatter;
          } else {
            const g = w(h);
            g && (l = g(o, i.field, i));
          }
        else if (r.cells && Object.prototype.hasOwnProperty.call(r.cells, i.field)) {
          const g = r.cells[i.field];
          typeof g == "function" ? l = g(o, i.field, i) : l = g;
        }
        l != null ? a.textContent = u ? u(l, i.field, i) : String(l) : a.textContent = "", s.appendChild(a);
      }
    n.appendChild(s);
  }
}
function S(n, t) {
  const e = document.createElement("div");
  e.className = "tbw-status-panel tbw-status-panel-custom", e.id = `status-panel-${n.id}`;
  const o = n.render(t);
  return typeof o == "string" ? e.innerHTML = o : e.appendChild(o), e;
}
function E(n, t, e, o, r) {
  return {
    totalRows: n.length,
    filteredRows: r?.cachedResult?.length ?? n.length,
    selectedRows: o?.selected?.size ?? 0,
    columns: t,
    rows: n,
    grid: e
  };
}
const B = "@layer tbw-plugins{.tbw-footer{flex-shrink:0;z-index:var(--tbw-z-layer-pinned-rows, 20);background:var(--tbw-color-panel-bg)}.tbw-pinned-rows{display:flex;align-items:center;justify-content:space-between;padding:var(--tbw-button-padding, var(--tbw-spacing-md, .5rem) var(--tbw-spacing-lg, .75rem));background:var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));border-top:1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75rem);color:var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));min-height:32px;box-sizing:border-box;min-width:fit-content}.tbw-pinned-rows-left,.tbw-pinned-rows-center,.tbw-pinned-rows-right{display:flex;align-items:center;gap:var(--tbw-spacing-xl, 1rem)}.tbw-pinned-rows-left{justify-content:flex-start}.tbw-pinned-rows-center{justify-content:center;flex:1}.tbw-pinned-rows-right{justify-content:flex-end}.tbw-status-panel{white-space:nowrap}.tbw-aggregation-rows{min-width:fit-content;background:var(--tbw-aggregation-bg, var(--tbw-color-header-bg))}.tbw-aggregation-rows-top{border-bottom:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-rows-bottom{border-top:1px solid var(--tbw-aggregation-border, var(--tbw-color-border))}.tbw-aggregation-row{display:grid;grid-template-columns:var(--tbw-column-template);font-size:var(--tbw-aggregation-font-size, .8em);font-weight:var(--tbw-aggregation-font-weight, 600)}.tbw-aggregation-cell{padding:var(--tbw-cell-padding, .125rem .5rem);min-height:var(--tbw-row-height, 1.75rem);display:block;align-items:center;border-right:1px solid var(--tbw-color-border-cell);overflow:hidden;text-overflow:ellipsis;white-space:var(--tbw-cell-white-space, nowrap)}.tbw-aggregation-cell:last-child{border-right:0}.tbw-aggregation-cell-full{grid-column:1 / -1;border-right:0}}";
class x extends R {
  /** @internal */
  name = "pinnedRows";
  /** @internal */
  styles = B;
  /** @internal */
  get defaultConfig() {
    return {
      position: "bottom",
      showRowCount: !0,
      showSelectedCount: !0,
      showFilteredCount: !0
    };
  }
  // #region Internal State
  infoBarElement = null;
  topAggregationContainer = null;
  bottomAggregationContainer = null;
  footerWrapper = null;
  // #endregion
  // #region Lifecycle
  /** @internal */
  detach() {
    this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null), this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null);
  }
  // #endregion
  // #region Hooks
  /** @internal */
  afterRender() {
    const t = this.gridElement;
    if (!t) return;
    const e = t.querySelector(".tbw-scroll-area") ?? t.querySelector(".tbw-grid-content") ?? t.children[0];
    if (!e) return;
    this.footerWrapper && !e.contains(this.footerWrapper) && (this.footerWrapper = null, this.bottomAggregationContainer = null, this.infoBarElement = null), this.topAggregationContainer && !e.contains(this.topAggregationContainer) && (this.topAggregationContainer = null), this.infoBarElement && !e.contains(this.infoBarElement) && (this.infoBarElement = null);
    const o = this.getSelectionState(), r = this.getFilterState(), s = E(
      this.rows,
      this.columns,
      this.grid,
      o,
      r
    ), i = this.config.aggregationRows || [], a = i.filter((c) => c.position === "top"), l = i.filter((c) => c.position !== "top");
    if (a.length > 0) {
      if (!this.topAggregationContainer) {
        this.topAggregationContainer = C("top");
        const c = t.querySelector(".header");
        c && c.nextSibling ? e.insertBefore(this.topAggregationContainer, c.nextSibling) : e.appendChild(this.topAggregationContainer);
      }
      v(
        this.topAggregationContainer,
        a,
        this.visibleColumns,
        this.rows
      );
    } else this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null);
    const u = this.config.showRowCount !== !1 || this.config.showSelectedCount && s.selectedRows > 0 || this.config.showFilteredCount && s.filteredRows !== s.totalRows || this.config.customPanels && this.config.customPanels.length > 0, h = u && this.config.position !== "top", g = l.length > 0 || h;
    if (u && this.config.position === "top")
      if (!this.infoBarElement)
        this.infoBarElement = m(this.config, s), e.insertBefore(this.infoBarElement, e.firstChild);
      else {
        const c = m(this.config, s);
        this.infoBarElement.replaceWith(c), this.infoBarElement = c;
      }
    else this.config.position === "top" && this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null);
    g ? (this.footerWrapper || (this.footerWrapper = document.createElement("div"), this.footerWrapper.className = "tbw-footer", e.appendChild(this.footerWrapper)), this.footerWrapper.innerHTML = "", l.length > 0 && (this.bottomAggregationContainer || (this.bottomAggregationContainer = C("bottom")), this.footerWrapper.appendChild(this.bottomAggregationContainer), v(
      this.bottomAggregationContainer,
      l,
      this.visibleColumns,
      this.rows
    )), h && (this.infoBarElement = m(this.config, s), this.footerWrapper.appendChild(this.infoBarElement))) : this.cleanupFooter();
  }
  // #endregion
  // #region Private Methods
  cleanup() {
    this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null), this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null);
  }
  cleanupFooter() {
    this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.infoBarElement && this.config.position !== "top" && (this.infoBarElement.remove(), this.infoBarElement = null);
  }
  getSelectionState() {
    try {
      return this.grid?.getPluginState?.("selection") ?? null;
    } catch {
      return null;
    }
  }
  getFilterState() {
    try {
      return this.grid?.getPluginState?.("filtering") ?? null;
    } catch {
      return null;
    }
  }
  // #endregion
  // #region Public API
  /**
   * Refresh the status bar to reflect current grid state.
   */
  refresh() {
    this.requestRender();
  }
  /**
   * Get the current status bar context.
   * @returns The context with row counts and other info
   */
  getContext() {
    const t = this.getSelectionState(), e = this.getFilterState();
    return E(
      this.rows,
      this.columns,
      this.grid,
      t,
      e
    );
  }
  /**
   * Add a custom panel to the info bar.
   * @param panel - The panel configuration to add
   */
  addPanel(t) {
    this.config.customPanels || (this.config.customPanels = []), this.config.customPanels.push(t), this.requestRender();
  }
  /**
   * Remove a custom panel by ID.
   * @param id - The panel ID to remove
   */
  removePanel(t) {
    this.config.customPanels && (this.config.customPanels = this.config.customPanels.filter((e) => e.id !== t), this.requestRender());
  }
  /**
   * Add an aggregation row.
   * @param row - The aggregation row configuration
   */
  addAggregationRow(t) {
    this.config.aggregationRows || (this.config.aggregationRows = []), this.config.aggregationRows.push(t), this.requestRender();
  }
  /**
   * Remove an aggregation row by ID.
   * @param id - The aggregation row ID to remove
   */
  removeAggregationRow(t) {
    this.config.aggregationRows && (this.config.aggregationRows = this.config.aggregationRows.filter((e) => e.id !== t), this.requestRender());
  }
  // #endregion
}
export {
  x as PinnedRowsPlugin
};
//# sourceMappingURL=index.js.map
