function y(r) {
  if (!r) return -1;
  const e = r.getAttribute("data-row");
  if (e) return parseInt(e, 10);
  const t = r.closest(".data-grid-row");
  if (!t) return -1;
  const s = t.parentElement;
  if (!s) return -1;
  const i = s.querySelectorAll(":scope > .data-grid-row");
  for (let l = 0; l < i.length; l++)
    if (i[l] === t) return l;
  return -1;
}
function m(r) {
  r && r.querySelectorAll(".cell-focus").forEach((e) => e.classList.remove("cell-focus"));
}
const R = '<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>', S = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰",
  filter: R,
  filterActive: R,
  print: "🖨️"
};
class v {
  /**
   * 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(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.#t?.abort(), this.#t = 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.#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(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 s = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(s), s.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 e = this.grid?.gridConfig?.icons ?? {};
    return { ...S, ...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(), s = parseInt(t, 10);
      if (!isNaN(s)) return s;
    }
    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 g(r) {
  return r.meta?.utility === !0;
}
function u(r) {
  return {
    startRow: Math.min(r.startRow, r.endRow),
    startCol: Math.min(r.startCol, r.endCol),
    endRow: Math.max(r.startRow, r.endRow),
    endCol: Math.max(r.startCol, r.endCol)
  };
}
function E(r) {
  const e = u(r);
  return {
    from: { row: e.startRow, col: e.startCol },
    to: { row: e.endRow, col: e.endCol }
  };
}
function p(r) {
  return r.map(E);
}
function I(r, e, t) {
  const s = u(t);
  return r >= s.startRow && r <= s.endRow && e >= s.startCol && e <= s.endCol;
}
function C(r, e, t) {
  return t.some((s) => I(r, e, s));
}
function x(r) {
  const e = [], t = u(r);
  for (let s = t.startRow; s <= t.endRow; s++)
    for (let i = t.startCol; i <= t.endCol; i++)
      e.push({ row: s, col: i });
  return e;
}
function q(r) {
  const e = /* @__PURE__ */ new Map();
  for (const t of r)
    for (const s of x(t))
      e.set(`${s.row},${s.col}`, s);
  return [...e.values()];
}
function b(r, e) {
  return {
    startRow: r.row,
    startCol: r.col,
    endRow: e.row,
    endCol: e.col
  };
}
function w(r, e) {
  const t = u(r), s = u(e);
  return t.startRow === s.startRow && t.startCol === s.startCol && t.endRow === s.endRow && t.endCol === s.endCol;
}
const k = "@layer tbw-plugins{tbw-grid.selecting .data-grid-row>.cell{-webkit-user-select:none;user-select:none}tbw-grid[data-has-focus] .data-grid-row.row-focus{background-color:var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%))}tbw-grid[data-selection-mode=row] .cell-focus{outline:none}tbw-grid .data-grid-row>.cell.selected{background-color:var(--tbw-range-selection-bg)}tbw-grid .data-grid-row>.cell.selected.top{border-top:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.bottom{border-bottom:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.first{border-left:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row>.cell.selected.last{border-right:2px solid var(--tbw-range-border-color)}tbw-grid .data-grid-row[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row[data-selectable=false].row-focus{background-color:var(--tbw-color-row-alt)}tbw-grid .data-grid-row>.cell[data-selectable=false]{cursor:not-allowed;opacity:.6}tbw-grid .data-grid-row>.cell[data-selectable=false].selected{background-color:var(--tbw-selection-warning-bg, rgba(from var(--tbw-color-error) r g b / 50%))}tbw-grid .tbw-selection-summary{font-size:var(--tbw-font-size-sm, .8125rem);color:var(--tbw-color-fg-muted);white-space:nowrap}}";
function K(r, e, t) {
  if (r === "cell" && e.selectedCell)
    return {
      mode: r,
      ranges: [
        {
          from: { row: e.selectedCell.row, col: e.selectedCell.col },
          to: { row: e.selectedCell.row, col: e.selectedCell.col }
        }
      ]
    };
  if (r === "row" && e.selected.size > 0) {
    const s = [...e.selected].map((i) => ({
      from: { row: i, col: 0 },
      to: { row: i, col: t - 1 }
    }));
    return { mode: r, ranges: s };
  }
  return r === "range" && e.ranges.length > 0 ? { mode: r, ranges: p(e.ranges) } : { mode: r, ranges: [] };
}
class D extends v {
  /**
   * Plugin manifest - declares configuration validation rules.
   * @internal
   */
  static manifest = {
    configRules: [
      {
        id: "selection/range-dblclick",
        severity: "warn",
        message: `"triggerOn: 'dblclick'" has no effect when mode is "range".
  → Range selection uses drag interaction (mousedown → mousemove), not click events.
  → The "triggerOn" option only affects "cell" and "row" selection modes.`,
        check: (e) => e.mode === "range" && e.triggerOn === "dblclick"
      }
    ]
  };
  /** @internal */
  name = "selection";
  /** @internal */
  styles = k;
  /** @internal */
  get defaultConfig() {
    return {
      mode: "cell",
      triggerOn: "click",
      enabled: !0
    };
  }
  // #region Internal State
  /** Row selection state (row mode) */
  selected = /* @__PURE__ */ new Set();
  lastSelected = null;
  anchor = null;
  /** Range selection state (range mode) */
  ranges = [];
  activeRange = null;
  cellAnchor = null;
  isDragging = !1;
  /** Pending keyboard navigation update (processed in afterRender) */
  pendingKeyboardUpdate = null;
  /** Cell selection state (cell mode) */
  selectedCell = null;
  // #endregion
  // #region Private Helpers - Selection Enabled Check
  /**
   * Check if selection is enabled at the grid level.
   * Grid-wide `selectable: false` or plugin's `enabled: false` disables all selection.
   */
  isSelectionEnabled() {
    return this.config.enabled === !1 ? !1 : this.grid.effectiveConfig?.selectable !== !1;
  }
  // #endregion
  // #region Private Helpers - Selectability
  /**
   * Check if a row/cell is selectable.
   * Returns true if selectable, false if not.
   */
  checkSelectable(e, t) {
    const { isSelectable: s } = this.config;
    if (!s) return !0;
    const i = this.rows[e];
    if (!i) return !1;
    const l = t !== void 0 ? this.columns[t] : void 0;
    return s(i, e, l, t);
  }
  /**
   * Check if an entire row is selectable (for row mode).
   */
  isRowSelectable(e) {
    return this.checkSelectable(e);
  }
  /**
   * Check if a cell is selectable (for cell/range modes).
   */
  isCellSelectable(e, t) {
    return this.checkSelectable(e, t);
  }
  // #endregion
  // #region Lifecycle
  /** @internal */
  detach() {
    this.selected.clear(), this.ranges = [], this.activeRange = null, this.cellAnchor = null, this.isDragging = !1, this.selectedCell = null, this.pendingKeyboardUpdate = null;
  }
  // #endregion
  // #region Event Handlers
  /** @internal */
  onCellClick(e) {
    if (!this.isSelectionEnabled()) return !1;
    const { rowIndex: t, colIndex: s, originalEvent: i } = e, { mode: l, triggerOn: n = "click" } = this.config;
    if (i.type !== n)
      return !1;
    const o = this.columns[s], d = o && g(o);
    if (l === "cell") {
      if (d || !this.isCellSelectable(t, s))
        return !1;
      const a = this.selectedCell;
      return a && a.row === t && a.col === s || (this.selectedCell = { row: t, col: s }, this.emit("selection-change", this.#e()), this.requestAfterRender()), !1;
    }
    if (l === "row")
      return !this.isRowSelectable(t) || this.selected.size === 1 && this.selected.has(t) || (this.selected.clear(), this.selected.add(t), this.lastSelected = t, this.emit("selection-change", this.#e()), this.requestAfterRender()), !1;
    if (l === "range") {
      if (d || !this.isCellSelectable(t, s))
        return !1;
      const a = i.shiftKey, h = i.ctrlKey || i.metaKey;
      if (a && this.cellAnchor) {
        const c = b(this.cellAnchor, { row: t, col: s }), f = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;
        if (f && w(f, c))
          return !1;
        h ? this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = c : this.ranges.push(c) : this.ranges = [c], this.activeRange = c;
      } else if (h) {
        const c = {
          startRow: t,
          startCol: s,
          endRow: t,
          endCol: s
        };
        this.ranges.push(c), this.activeRange = c, this.cellAnchor = { row: t, col: s };
      } else {
        const c = {
          startRow: t,
          startCol: s,
          endRow: t,
          endCol: s
        };
        if (this.ranges.length === 1 && w(this.ranges[0], c))
          return !1;
        this.ranges = [c], this.activeRange = c, this.cellAnchor = { row: t, col: s };
      }
      return this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
    }
    return !1;
  }
  /** @internal */
  onKeyDown(e) {
    if (!this.isSelectionEnabled()) return !1;
    const { mode: t } = this.config, i = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"].includes(e.key);
    if (e.key === "Escape")
      return t === "cell" ? this.selectedCell = null : t === "row" ? (this.selected.clear(), this.anchor = null) : t === "range" && (this.ranges = [], this.activeRange = null, this.cellAnchor = null), this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
    if (t === "cell" && i)
      return queueMicrotask(() => {
        const l = this.grid._focusRow, n = this.grid._focusCol;
        this.isCellSelectable(l, n) ? this.selectedCell = { row: l, col: n } : this.selectedCell = null, this.emit("selection-change", this.#e()), this.requestAfterRender();
      }), !1;
    if (t === "row" && (e.key === "ArrowUp" || e.key === "ArrowDown"))
      return queueMicrotask(() => {
        const l = this.grid._focusRow;
        this.isRowSelectable(l) ? (this.selected.clear(), this.selected.add(l), this.lastSelected = l) : this.selected.clear(), this.emit("selection-change", this.#e()), this.requestAfterRender();
      }), !1;
    if (t === "range" && i) {
      const l = e.key === "Tab", n = e.shiftKey && !l;
      return n && !this.cellAnchor && (this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol }), this.pendingKeyboardUpdate = { shiftKey: n }, queueMicrotask(() => this.requestAfterRender()), !1;
    }
    if (t === "range" && e.key === "a" && (e.ctrlKey || e.metaKey)) {
      const l = this.rows.length, n = this.columns.length;
      if (l > 0 && n > 0) {
        e.preventDefault(), e.stopPropagation();
        const o = {
          startRow: 0,
          startCol: 0,
          endRow: l - 1,
          endCol: n - 1
        };
        return this.ranges = [o], this.activeRange = o, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
      }
    }
    return !1;
  }
  /** @internal */
  onCellMouseDown(e) {
    if (!this.isSelectionEnabled() || this.config.mode !== "range" || e.rowIndex === void 0 || e.colIndex === void 0 || e.rowIndex < 0) return;
    const t = this.columns[e.colIndex];
    if (t && g(t) || !this.isCellSelectable(e.rowIndex, e.colIndex) || e.originalEvent.shiftKey && this.cellAnchor)
      return;
    this.isDragging = !0;
    const s = e.rowIndex, i = e.colIndex, l = e.originalEvent.ctrlKey || e.originalEvent.metaKey, n = {
      startRow: s,
      startCol: i,
      endRow: s,
      endCol: i
    };
    return !l && this.ranges.length === 1 && w(this.ranges[0], n) ? (this.cellAnchor = { row: s, col: i }, !0) : (this.cellAnchor = { row: s, col: i }, l || (this.ranges = []), this.ranges.push(n), this.activeRange = n, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0);
  }
  /** @internal */
  onCellMouseMove(e) {
    if (!this.isSelectionEnabled() || this.config.mode !== "range" || !this.isDragging || !this.cellAnchor || e.rowIndex === void 0 || e.colIndex === void 0 || e.rowIndex < 0) return;
    let t = e.colIndex;
    const s = this.columns[t];
    if (s && g(s)) {
      const n = this.columns.findIndex((o) => !g(o));
      n >= 0 && (t = n);
    }
    const i = b(this.cellAnchor, { row: e.rowIndex, col: t }), l = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;
    return l && w(l, i) || (this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = i : this.ranges.push(i), this.activeRange = i, this.emit("selection-change", this.#e()), this.requestAfterRender()), !0;
  }
  /** @internal */
  onCellMouseUp(e) {
    if (this.isSelectionEnabled() && this.config.mode === "range" && this.isDragging)
      return this.isDragging = !1, !0;
  }
  /**
   * Apply selection classes to visible cells/rows.
   * Shared by afterRender and onScrollRender.
   */
  #t() {
    const e = this.gridElement;
    if (!e) return;
    const { mode: t } = this.config, s = !!this.config.isSelectable;
    e.querySelectorAll(".cell").forEach((n) => {
      n.classList.remove("selected", "top", "bottom", "first", "last"), s && n.removeAttribute("data-selectable");
    });
    const l = e.querySelectorAll(".data-grid-row");
    if (l.forEach((n) => {
      n.classList.remove("selected", "row-focus"), s && n.removeAttribute("data-selectable");
    }), t === "row" && (m(e), l.forEach((n) => {
      const o = n.querySelector(".cell[data-row]"), d = y(o);
      d >= 0 && (s && !this.isRowSelectable(d) && n.setAttribute("data-selectable", "false"), this.selected.has(d) && n.classList.add("selected", "row-focus"));
    })), (t === "cell" || t === "range") && s && e.querySelectorAll(".cell[data-row][data-col]").forEach((o) => {
      const d = parseInt(o.getAttribute("data-row") ?? "-1", 10), a = parseInt(o.getAttribute("data-col") ?? "-1", 10);
      d >= 0 && a >= 0 && (this.isCellSelectable(d, a) || o.setAttribute("data-selectable", "false"));
    }), t === "range" && this.ranges.length > 0) {
      m(e);
      const n = this.activeRange ? u(this.activeRange) : null, o = this.columns.findIndex((a) => !g(a));
      this.columns.length - 1, e.querySelectorAll(".cell[data-row][data-col]").forEach((a) => {
        const h = parseInt(a.getAttribute("data-row") ?? "-1", 10), c = parseInt(a.getAttribute("data-col") ?? "-1", 10);
        if (h >= 0 && c >= 0) {
          const f = this.columns[c];
          if (f && g(f))
            return;
          if (C(h, c, this.ranges) && (a.classList.add("selected"), n)) {
            h === n.startRow && a.classList.add("top"), h === n.endRow && a.classList.add("bottom");
            const A = Math.max(n.startCol, o);
            c === A && a.classList.add("first"), c === n.endCol && a.classList.add("last");
          }
        }
      });
    }
  }
  /** @internal */
  afterRender() {
    if (!this.isSelectionEnabled()) return;
    const e = this.gridElement;
    if (!e) return;
    const t = e.children[0], { mode: s } = this.config;
    if (this.pendingKeyboardUpdate && s === "range") {
      const { shiftKey: i } = this.pendingKeyboardUpdate;
      this.pendingKeyboardUpdate = null;
      const l = this.grid._focusRow, n = this.grid._focusCol;
      if (i && this.cellAnchor) {
        const o = b(this.cellAnchor, { row: l, col: n });
        this.ranges = [o], this.activeRange = o;
      } else i || (this.ranges = [], this.activeRange = null, this.cellAnchor = { row: l, col: n });
      this.emit("selection-change", this.#e());
    }
    this.grid.setAttribute("data-selection-mode", s), t && t.classList.toggle("selecting", this.isDragging), this.#t();
  }
  /**
   * Called after scroll-triggered row rendering.
   * Reapplies selection classes to recycled DOM elements.
   * @internal
   */
  onScrollRender() {
    this.isSelectionEnabled() && this.#t();
  }
  // #endregion
  // #region Public API
  /**
   * Get the current selection as a unified result.
   * Works for all selection modes and always returns ranges.
   *
   * @example
   * ```ts
   * const selection = plugin.getSelection();
   * if (selection.ranges.length > 0) {
   *   const { from, to } = selection.ranges[0];
   *   // For cell mode: from === to (single cell)
   *   // For row mode: from.col = 0, to.col = lastCol (full row)
   *   // For range mode: rectangular selection
   * }
   * ```
   */
  getSelection() {
    return {
      mode: this.config.mode,
      ranges: this.#e().ranges,
      anchor: this.cellAnchor
    };
  }
  /**
   * Get all selected cells across all ranges.
   */
  getSelectedCells() {
    return q(this.ranges);
  }
  /**
   * Check if a specific cell is in range selection.
   */
  isCellSelected(e, t) {
    return C(e, t, this.ranges);
  }
  /**
   * Clear all selection.
   */
  clearSelection() {
    this.selectedCell = null, this.selected.clear(), this.anchor = null, this.ranges = [], this.activeRange = null, this.cellAnchor = null, this.emit("selection-change", { mode: this.config.mode, ranges: [] }), this.requestAfterRender();
  }
  /**
   * Set selected ranges programmatically.
   */
  setRanges(e) {
    this.ranges = e.map((t) => ({
      startRow: t.from.row,
      startCol: t.from.col,
      endRow: t.to.row,
      endCol: t.to.col
    })), this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null, this.emit("selection-change", {
      mode: this.config.mode,
      ranges: p(this.ranges)
    }), this.requestAfterRender();
  }
  // #endregion
  // #region Private Helpers
  #e() {
    return K(
      this.config.mode,
      {
        selectedCell: this.selectedCell,
        selected: this.selected,
        ranges: this.ranges
      },
      this.columns.length
    );
  }
  // #endregion
}
export {
  D as SelectionPlugin
};
//# sourceMappingURL=index.js.map
