const m = '<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>', v = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰",
  filter: m,
  filterActive: m,
  print: "🖨️"
};
class A {
  /**
   * 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 n = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(n), n.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 { ...v, ...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(), n = parseInt(t, 10);
      if (!isNaN(n)) return n;
    }
    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
}
function R(o, e, t) {
  return o.id !== void 0 ? String(o.id) : t ? `${t}-${e}` : String(e);
}
function y(o, e) {
  const t = new Set(o);
  return t.has(e) ? t.delete(e) : t.add(e), t;
}
function w(o, e, t = null, n = 0) {
  const i = e.childrenField ?? "children", s = /* @__PURE__ */ new Set();
  for (let r = 0; r < o.length; r++) {
    const l = o[r], a = R(l, r, t), d = l[i];
    if (Array.isArray(d) && d.length > 0) {
      s.add(a);
      const h = w(d, e, a, n + 1);
      for (const u of h) s.add(u);
    }
  }
  return s;
}
function E() {
  return /* @__PURE__ */ new Set();
}
function _(o, e, t, n = null, i = 0) {
  const s = t.childrenField ?? "children";
  for (let r = 0; r < o.length; r++) {
    const l = o[r], a = R(l, r, n);
    if (a === e)
      return [a];
    const d = l[s];
    if (Array.isArray(d) && d.length > 0) {
      const h = _(d, e, t, a, i + 1);
      if (h)
        return [a, ...h];
    }
  }
  return null;
}
function C(o, e, t, n) {
  const i = _(o, e, t);
  if (!i) return n;
  const s = new Set(n);
  for (let r = 0; r < i.length - 1; r++)
    s.add(i[r]);
  return s;
}
function x(o, e = "children") {
  if (!Array.isArray(o) || o.length === 0) return !1;
  for (const t of o) {
    if (!t) continue;
    const n = t[e];
    if (Array.isArray(n) && n.length > 0)
      return !0;
  }
  return !1;
}
function k(o) {
  if (!Array.isArray(o) || o.length === 0) return null;
  const e = ["children", "items", "nodes", "subRows", "nested"];
  for (const t of o)
    if (!(!t || typeof t != "object"))
      for (const n of e) {
        const i = t[n];
        if (Array.isArray(i) && i.length > 0)
          return n;
      }
  return null;
}
const T = "@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:flex-start}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 .tree-cell-wrapper{display:inline-flex;align-items:center;padding-left:calc(var(--tbw-tree-depth, 0) * var(--tbw-tree-indent-width, var(--tbw-tree-toggle-size, 1.25em)))}tbw-grid .tree-expander{display:flex;align-items:center;justify-content:flex-start;width:100%;height:100%;box-sizing:border-box}tbw-grid .tree-toggle{cursor:pointer;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center;width:var(--tbw-tree-toggle-size, 1.25em);height:var(--tbw-tree-toggle-size, 1.25em);flex-shrink:0}tbw-grid .tree-toggle:hover{color:var(--tbw-tree-accent, var(--tbw-color-accent))}tbw-grid .tree-spacer{width:var(--tbw-tree-toggle-size, 1.25em);display:inline-block;flex-shrink:0}tbw-grid .data-grid-row.tbw-tree-slide-in{animation:tbw-tree-slide-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .data-grid-row.tbw-tree-fade-in{animation:tbw-tree-fade-in var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-tree-slide-in{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes tbw-tree-fade-in{0%{opacity:0}to{opacity:1}}}";
class F extends A {
  /** @internal */
  name = "tree";
  /** @internal */
  styles = T;
  /** @internal */
  get defaultConfig() {
    return {
      childrenField: "children",
      autoDetect: !0,
      defaultExpanded: !1,
      indentWidth: 20,
      showExpandIcons: !0,
      animation: "slide"
    };
  }
  // #region State
  expandedKeys = /* @__PURE__ */ new Set();
  initialExpansionDone = !1;
  flattenedRows = [];
  rowKeyMap = /* @__PURE__ */ new Map();
  previousVisibleKeys = /* @__PURE__ */ new Set();
  keysToAnimate = /* @__PURE__ */ new Set();
  sortState = null;
  /** @internal */
  detach() {
    this.expandedKeys.clear(), this.initialExpansionDone = !1, this.flattenedRows = [], this.rowKeyMap.clear(), this.previousVisibleKeys.clear(), this.keysToAnimate.clear(), this.sortState = null;
  }
  // #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 Auto-Detection
  detect(e) {
    if (!this.config.autoDetect) return !1;
    const t = e, n = this.config.childrenField ?? k(t) ?? "children";
    return x(t, n);
  }
  // #endregion
  // #region Data Processing
  /** @internal */
  processRows(e) {
    const t = this.config.childrenField ?? "children", n = e;
    if (!x(n, t))
      return this.flattenedRows = [], this.rowKeyMap.clear(), this.previousVisibleKeys.clear(), [...e];
    let i = this.withStableKeys(n);
    this.sortState && (i = this.sortTree(i, this.sortState.field, this.sortState.direction)), this.config.defaultExpanded && !this.initialExpansionDone && (this.expandedKeys = w(i, this.config), this.initialExpansionDone = !0), this.flattenedRows = this.flattenTree(i, this.expandedKeys), this.rowKeyMap.clear(), this.keysToAnimate.clear();
    const s = /* @__PURE__ */ new Set();
    for (const r of this.flattenedRows)
      this.rowKeyMap.set(r.key, r), s.add(r.key), !this.previousVisibleKeys.has(r.key) && r.depth > 0 && this.keysToAnimate.add(r.key);
    return this.previousVisibleKeys = s, this.flattenedRows.map((r) => ({
      ...r.data,
      __treeKey: r.key,
      __treeDepth: r.depth,
      __treeHasChildren: r.hasChildren,
      __treeExpanded: r.isExpanded
    }));
  }
  /** Assign stable keys to rows (preserves key across sort operations) */
  withStableKeys(e, t = null) {
    const n = this.config.childrenField ?? "children";
    return e.map((i, s) => {
      const r = i.__stableKey, l = i.id !== void 0 ? String(i.id) : r ?? (t ? `${t}-${s}` : String(s)), a = i[n], d = Array.isArray(a) && a.length > 0;
      return {
        ...i,
        __stableKey: l,
        ...d ? { [n]: this.withStableKeys(a, l) } : {}
      };
    });
  }
  /** Flatten tree using stable keys */
  flattenTree(e, t, n = 0) {
    const i = this.config.childrenField ?? "children", s = [];
    for (const r of e) {
      const a = r.__stableKey ?? String(r.id ?? "?"), d = r[i], h = Array.isArray(d) && d.length > 0, u = t.has(a);
      s.push({
        key: a,
        data: r,
        depth: n,
        hasChildren: h,
        isExpanded: u,
        parentKey: n > 0 && a.substring(0, a.lastIndexOf("-")) || null
      }), h && u && s.push(...this.flattenTree(d, t, n + 1));
    }
    return s;
  }
  /** Sort tree recursively, keeping children with parents */
  sortTree(e, t, n) {
    const i = this.config.childrenField ?? "children";
    return [...e].sort((r, l) => {
      const a = r[t], d = l[t];
      return a == null && d == null ? 0 : a == null ? -1 : d == null ? 1 : a > d ? n : a < d ? -n : 0;
    }).map((r) => {
      const l = r[i];
      return Array.isArray(l) && l.length > 0 ? { ...r, [i]: this.sortTree(l, t, n) } : r;
    });
  }
  /** @internal */
  processColumns(e) {
    if (this.flattenedRows.length === 0) return [...e];
    const t = [...e];
    if (t.length === 0) return t;
    const n = t[0], i = n.viewRenderer, s = () => this.config, r = this.setIcon.bind(this), l = this.resolveIcon.bind(this), a = (d) => {
      const { row: h, value: u } = d, { showExpandIcons: K = !0, indentWidth: b } = s(), g = h, S = g.__treeDepth ?? 0, f = document.createElement("span");
      if (f.className = "tree-cell-wrapper", f.style.setProperty("--tbw-tree-depth", String(S)), b !== void 0 && f.style.setProperty("--tbw-tree-indent-width", `${b}px`), K)
        if (g.__treeHasChildren) {
          const c = document.createElement("span");
          c.className = `tree-toggle${g.__treeExpanded ? " expanded" : ""}`, r(c, l(g.__treeExpanded ? "collapse" : "expand")), c.setAttribute("data-tree-key", String(g.__treeKey ?? "")), f.appendChild(c);
        } else {
          const c = document.createElement("span");
          c.className = "tree-spacer", f.appendChild(c);
        }
      const p = document.createElement("span");
      if (p.className = "tree-content", i) {
        const c = i(d);
        c instanceof Node ? p.appendChild(c) : typeof c == "string" && (p.innerHTML = c);
      } else
        p.textContent = u != null ? String(u) : "";
      return f.appendChild(p), f;
    };
    return t[0] = { ...n, viewRenderer: a }, t;
  }
  // #endregion
  // #region Event Handlers
  /** @internal */
  onCellClick(e) {
    const t = e.originalEvent?.target;
    if (!t?.classList.contains("tree-toggle")) return !1;
    const n = t.getAttribute("data-tree-key");
    if (!n) return !1;
    const i = this.rowKeyMap.get(n);
    return i ? (this.expandedKeys = y(this.expandedKeys, n), this.emit("tree-expand", {
      key: n,
      row: i.data,
      expanded: this.expandedKeys.has(n),
      depth: i.depth
    }), this.requestRender(), !0) : !1;
  }
  /** @internal */
  onKeyDown(e) {
    if (e.key !== " ") return;
    const t = this.grid._focusRow, n = this.flattenedRows[t];
    if (n?.hasChildren)
      return e.preventDefault(), this.expandedKeys = y(this.expandedKeys, n.key), this.emit("tree-expand", {
        key: n.key,
        row: n.data,
        expanded: this.expandedKeys.has(n.key),
        depth: n.depth
      }), this.requestRenderWithFocus(), !0;
  }
  /** @internal */
  onHeaderClick(e) {
    if (this.flattenedRows.length === 0 || !e.column.sortable) return !1;
    const { field: t } = e.column;
    !this.sortState || this.sortState.field !== t ? this.sortState = { field: t, direction: 1 } : this.sortState.direction === 1 ? this.sortState = { field: t, direction: -1 } : this.sortState = null;
    const n = this.grid;
    return n._sortState !== void 0 && (n._sortState = this.sortState ? { ...this.sortState } : null), this.emit("sort-change", { field: t, direction: this.sortState?.direction ?? 0 }), this.requestRender(), !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 n = e === "fade" ? "tbw-tree-fade-in" : "tbw-tree-slide-in";
    for (const i of t.querySelectorAll(".data-grid-row")) {
      const s = i.querySelector(".cell[data-row]"), r = s ? parseInt(s.getAttribute("data-row") ?? "-1", 10) : -1, l = this.flattenedRows[r]?.key;
      l && this.keysToAnimate.has(l) && (i.classList.add(n), i.addEventListener("animationend", () => i.classList.remove(n), { once: !0 }));
    }
    this.keysToAnimate.clear();
  }
  // #endregion
  // #region Public API
  expand(e) {
    this.expandedKeys.add(e), this.requestRender();
  }
  collapse(e) {
    this.expandedKeys.delete(e), this.requestRender();
  }
  toggle(e) {
    this.expandedKeys = y(this.expandedKeys, e), this.requestRender();
  }
  expandAll() {
    this.expandedKeys = w(this.rows, this.config), this.requestRender();
  }
  collapseAll() {
    this.expandedKeys = E(), this.requestRender();
  }
  isExpanded(e) {
    return this.expandedKeys.has(e);
  }
  getExpandedKeys() {
    return [...this.expandedKeys];
  }
  getFlattenedRows() {
    return [...this.flattenedRows];
  }
  getRowByKey(e) {
    return this.rowKeyMap.get(e)?.data;
  }
  expandToKey(e) {
    this.expandedKeys = C(this.rows, e, this.config, this.expandedKeys), this.requestRender();
  }
  // #endregion
}
export {
  F as TreePlugin
};
//# sourceMappingURL=index.js.map
