function b(l) {
  l && l.querySelectorAll(".cell-focus").forEach((e) => e.classList.remove("cell-focus"));
}
const v = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])', C = document.createElement("template");
C.innerHTML = '<div class="cell" role="gridcell" part="cell"></div>';
const E = document.createElement("template");
E.innerHTML = '<div class="data-grid-row" role="row" part="row"></div>';
function w(l, e) {
  if (l._virtualization?.enabled) {
    const { rowHeight: a, container: r, viewportEl: s } = l._virtualization, o = r, c = s?.clientHeight ?? o?.clientHeight ?? 0;
    if (o && c > 0) {
      const u = l._focusRow * a;
      u < o.scrollTop ? o.scrollTop = u : u + a > o.scrollTop + c && (o.scrollTop = u - c + a);
    }
  }
  const t = l._activeEditRows !== void 0 && l._activeEditRows !== -1;
  t || l.refreshVirtualWindow(!1), b(l._bodyEl), Array.from(l._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach((a) => {
    a.setAttribute("aria-selected", "false");
  });
  const i = l._focusRow, n = l._virtualization.start ?? 0, d = l._virtualization.end ?? l._rows.length;
  if (i >= n && i < d) {
    const a = l._bodyEl.querySelectorAll(".data-grid-row")[i - n];
    let r = a?.children[l._focusCol];
    if ((!r || !r.classList?.contains("cell")) && (r = a?.querySelector(`.cell[data-col="${l._focusCol}"]`) ?? a?.querySelector(".cell[data-col]")), r) {
      r.classList.add("cell-focus"), r.setAttribute("aria-selected", "true");
      const s = l.querySelector(".tbw-scroll-area");
      if (s && r && !t) {
        const o = l._getHorizontalScrollOffsets?.(a ?? void 0, r) ?? { left: 0, right: 0 };
        if (!o.skipScroll) {
          const c = r.getBoundingClientRect(), u = s.getBoundingClientRect(), f = c.left - u.left + s.scrollLeft, g = f + c.width, h = s.scrollLeft + o.left, m = s.scrollLeft + s.clientWidth - o.right;
          f < h ? s.scrollLeft = f - o.left : g > m && (s.scrollLeft = g - s.clientWidth + o.right);
        }
      }
      if (l._activeEditRows !== void 0 && l._activeEditRows !== -1 && r.classList.contains("editing")) {
        const o = r.querySelector(v);
        if (o && document.activeElement !== o)
          try {
            o.focus({ preventScroll: !0 });
          } catch {
          }
      } else if (!r.contains(document.activeElement)) {
        r.hasAttribute("tabindex") || r.setAttribute("tabindex", "-1");
        try {
          r.focus({ preventScroll: !0 });
        } catch {
        }
      }
    }
  }
}
const y = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
}, L = {
  /** Ask if a column can be moved. Context: ColumnConfig. Response: boolean | undefined */
  CAN_MOVE_COLUMN: "canMoveColumn"
};
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 version - defaults to grid version for built-in plugins.
   * Third-party plugins can override with their own semver.
   */
  version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
  /** CSS styles to inject into the grid's shadow DOM */
  styles;
  /** Custom cell renderers keyed by type name */
  cellRenderers;
  /** Custom header renderers keyed by type name */
  headerRenderers;
  /** Custom cell editors keyed by type name */
  cellEditors;
  /** The grid instance this plugin is attached to */
  grid;
  /** Plugin configuration - merged with defaults in attach() */
  config;
  /** User-provided configuration from constructor */
  userConfig;
  /**
   * Plugin-level AbortController for event listener cleanup.
   * Created fresh in attach(), aborted in detach().
   * This ensures event listeners are properly cleaned up when plugins are re-attached.
   */
  #e;
  /**
   * Default configuration - subclasses should override this getter.
   * Note: This must be a getter (not property initializer) for proper inheritance
   * since property initializers run after parent constructor.
   */
  get defaultConfig() {
    return {};
  }
  constructor(e = {}) {
    this.userConfig = e;
  }
  /**
   * Called when the plugin is attached to a grid.
   * Override to set up event listeners, initialize state, etc.
   *
   * @example
   * ```ts
   * attach(grid: GridElement): void {
   *   super.attach(grid);
   *   // Set up document-level listeners with auto-cleanup
   *   document.addEventListener('keydown', this.handleEscape, {
   *     signal: this.disconnectSignal
   *   });
   * }
   * ```
   */
  attach(e) {
    this.#e?.abort(), this.#e = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
  }
  /**
   * Called when the plugin is detached from a grid.
   * Override to clean up event listeners, timers, etc.
   *
   * @example
   * ```ts
   * detach(): void {
   *   // Clean up any state not handled by disconnectSignal
   *   this.selectedRows.clear();
   *   this.cache = null;
   * }
   * ```
   */
  detach() {
    this.#e?.abort(), this.#e = void 0;
  }
  /**
   * Get another plugin instance from the same grid.
   * Use for inter-plugin communication.
   *
   * @example
   * ```ts
   * const selection = this.getPlugin(SelectionPlugin);
   * if (selection) {
   *   const selectedRows = selection.getSelectedRows();
   * }
   * ```
   */
  getPlugin(e) {
    return this.grid?.getPlugin(e);
  }
  /**
   * Emit a custom event from the grid.
   */
  emit(e, t) {
    this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
  }
  /**
   * Emit a cancelable custom event from the grid.
   * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
   */
  emitCancelable(e, t) {
    const i = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a re-render and restore focus styling afterward.
   * Use this when a plugin action (like expand/collapse) triggers a render
   * but needs to maintain keyboard navigation focus.
   */
  requestRenderWithFocus() {
    this.grid?.requestRenderWithFocus?.();
  }
  /**
   * Request a lightweight style update without rebuilding DOM.
   * Use this instead of requestRender() when only CSS classes need updating.
   */
  requestAfterRender() {
    this.grid?.requestAfterRender?.();
  }
  /**
   * Get the current rows from the grid.
   */
  get rows() {
    return this.grid?.rows ?? [];
  }
  /**
   * Get the original unfiltered/unprocessed rows from the grid.
   * Use this when you need all source data regardless of active filters.
   */
  get sourceRows() {
    return this.grid?.sourceRows ?? [];
  }
  /**
   * Get the current columns from the grid.
   */
  get columns() {
    return this.grid?.columns ?? [];
  }
  /**
   * Get only visible columns from the grid (excludes hidden).
   * Use this for rendering that needs to match the grid template.
   */
  get visibleColumns() {
    return this.grid?._visibleColumns ?? [];
  }
  /**
   * Get the grid as an HTMLElement for direct DOM operations.
   * Use sparingly - prefer the typed GridElementRef API when possible.
   *
   * @example
   * ```ts
   * const width = this.gridElement.clientWidth;
   * this.gridElement.classList.add('my-plugin-active');
   * ```
   */
  get gridElement() {
    return this.grid;
  }
  /**
   * Get the render root of the grid for DOM queries.
   * @deprecated Use `gridElement` instead. This getter exists only for backward compatibility.
   *
   * With Shadow DOM removed, the grid element itself is the render root.
   * All new code should use `this.gridElement` for DOM queries.
   *
   * @example
   * // OLD (deprecated)
   * const rows = this.shadowRoot?.querySelector('.rows');
   *
   * // NEW (preferred)
   * const rows = this.gridElement.querySelector('.rows');
   */
  get shadowRoot() {
    return this.gridElement;
  }
  /**
   * Get the disconnect signal for event listener cleanup.
   * This signal is aborted when the grid disconnects from the DOM.
   * Use this when adding event listeners that should be cleaned up automatically.
   *
   * Best for:
   * - Document/window-level listeners added in attach()
   * - Listeners on the grid element itself
   * - Any listener that should persist across renders
   *
   * Not needed for:
   * - Listeners on elements created in afterRender() (removed with element)
   *
   * @example
   * element.addEventListener('click', handler, { signal: this.disconnectSignal });
   * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
   */
  get disconnectSignal() {
    return this.#e?.signal ?? this.grid?.disconnectSignal;
  }
  /**
   * Get the grid-level icons configuration.
   * Returns merged icons (user config + defaults).
   */
  get gridIcons() {
    const e = this.grid?.gridConfig?.icons ?? {};
    return { ...y, ...e };
  }
  // #region Animation Helpers
  /**
   * Check if animations are enabled at the grid level.
   * Respects gridConfig.animation.mode and the CSS variable set by the grid.
   *
   * Plugins should use this to skip animations when:
   * - Animation mode is 'off' or `false`
   * - User prefers reduced motion and mode is 'reduced-motion' (default)
   *
   * @example
   * ```ts
   * private get animationStyle(): 'slide' | 'fade' | false {
   *   if (!this.isAnimationEnabled) return false;
   *   return this.config.animation ?? 'slide';
   * }
   * ```
   */
  get isAnimationEnabled() {
    const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
    if (e === !1 || e === "off") return !1;
    if (e === !0 || e === "on") return !0;
    const t = this.gridElement;
    return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
  }
  /**
   * Get the animation duration in milliseconds from CSS variable.
   * Falls back to 200ms if not set.
   *
   * Plugins can use this for their animation timing to stay consistent
   * with the grid-level animation.duration setting.
   *
   * @example
   * ```ts
   * element.animate(keyframes, { duration: this.animationDuration });
   * ```
   */
  get animationDuration() {
    const e = this.gridElement;
    if (e) {
      const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(t, 10);
      if (!isNaN(i)) return i;
    }
    return 200;
  }
  // #endregion
  /**
   * Resolve an icon value to string or HTMLElement.
   * Checks plugin config first, then grid-level icons, then defaults.
   *
   * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
   * @param pluginOverride - Optional plugin-level override
   * @returns The resolved icon value
   */
  resolveIcon(e, t) {
    return t !== void 0 ? t : this.gridIcons[e];
  }
  /**
   * Set an icon value on an element.
   * Handles both string (text/HTML) and HTMLElement values.
   *
   * @param element - The element to set the icon on
   * @param icon - The icon value (string or HTMLElement)
   */
  setIcon(e, t) {
    typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
function O(l) {
  const e = l.meta ?? {};
  return e.lockPosition !== !0 && e.suppressMovable !== !0;
}
function p(l, e, t) {
  if (e === t || e < 0 || e >= l.length || t < 0 || t > l.length) return l;
  const i = [...l], [n] = i.splice(e, 1);
  return i.splice(t, 0, n), i;
}
const _ = '@layer tbw-plugins{.header-row>.cell[draggable=true]{cursor:grab;position:relative}.header-row>.cell.dragging{opacity:.5;cursor:grabbing}.header-row>.cell.drop-before:before{content:"";position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.header-row>.cell.drop-after:after{content:"";position:absolute;right:0;top:0;bottom:0;width:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent));z-index:1}.cell.flip-animating{transition:transform var(--tbw-animation-duration, .2s) ease-out;will-change:transform;z-index:1}@keyframes reorder-fade-in{0%{opacity:0}to{opacity:1}}.cell.fade-animating{animation:reorder-fade-in var(--tbw-animation-duration, .2s) ease-out backwards}}';
class R extends A {
  name = "reorder";
  styles = _;
  get defaultConfig() {
    return {
      animation: "flip"
      // Plugin's own default
    };
  }
  /**
   * Resolve animation type from plugin config.
   * Uses base class isAnimationEnabled to respect grid-level settings.
   */
  get animationType() {
    return this.isAnimationEnabled ? this.config.animation !== void 0 ? this.config.animation : this.config.viewTransition === !1 ? !1 : (this.config.viewTransition === !0, "flip") : !1;
  }
  /**
   * Get animation duration, allowing plugin config override.
   * Uses base class animationDuration for default.
   */
  get animationDuration() {
    return this.config.animationDuration !== void 0 ? this.config.animationDuration : super.animationDuration;
  }
  // #region Internal State
  isDragging = !1;
  draggedField = null;
  draggedIndex = null;
  dropIndex = null;
  /**
   * Check if a column can be moved, considering both column config and plugin queries.
   */
  canMoveColumnWithPlugins(e) {
    return !e || !O(e) ? !1 : !this.grid.queryPlugins({
      type: L.CAN_MOVE_COLUMN,
      context: e
    }).includes(!1);
  }
  /**
   * Clear all drag-related classes from header cells.
   */
  clearDragClasses() {
    this.gridElement?.querySelectorAll(".header-row > .cell").forEach((e) => {
      e.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
    });
  }
  // #endregion
  // #region Lifecycle
  attach(e) {
    super.attach(e), e.addEventListener(
      "column-reorder-request",
      (t) => {
        const i = t.detail;
        i?.field && typeof i.toIndex == "number" && this.moveColumn(i.field, i.toIndex);
      },
      { signal: this.disconnectSignal }
    );
  }
  detach() {
    this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
  }
  // #endregion
  // #region Hooks
  afterRender() {
    const e = this.gridElement;
    if (!e) return;
    e.querySelectorAll(".header-row > .cell").forEach((i) => {
      const n = i, d = n.getAttribute("data-field");
      if (!d) return;
      const a = this.columns.find((r) => r.field === d);
      if (!this.canMoveColumnWithPlugins(a)) {
        n.draggable = !1;
        return;
      }
      n.draggable = !0, !n.getAttribute("data-dragstart-bound") && (n.setAttribute("data-dragstart-bound", "true"), n.addEventListener("dragstart", (r) => {
        const o = this.getColumnOrder().indexOf(d);
        this.isDragging = !0, this.draggedField = d, this.draggedIndex = o, r.dataTransfer && (r.dataTransfer.effectAllowed = "move", r.dataTransfer.setData("text/plain", d)), n.classList.add("dragging");
      }), n.addEventListener("dragend", () => {
        this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, this.clearDragClasses();
      }), n.addEventListener("dragover", (r) => {
        if (r.preventDefault(), !this.isDragging || this.draggedField === d) return;
        const s = n.getBoundingClientRect(), o = s.left + s.width / 2, u = this.getColumnOrder().indexOf(d);
        this.dropIndex = r.clientX < o ? u : u + 1, n.classList.add("drop-target"), n.classList.toggle("drop-before", r.clientX < o), n.classList.toggle("drop-after", r.clientX >= o);
      }), n.addEventListener("dragleave", () => {
        n.classList.remove("drop-target", "drop-before", "drop-after");
      }), n.addEventListener("drop", (r) => {
        r.preventDefault();
        const s = this.draggedField, o = this.draggedIndex, c = this.dropIndex;
        if (!this.isDragging || s === null || o === null || c === null)
          return;
        const u = c > o ? c - 1 : c, f = this.getColumnOrder(), g = p(f, o, u), h = {
          field: s,
          fromIndex: o,
          toIndex: u,
          columnOrder: g
        };
        this.emitCancelable("column-move", h) || this.updateColumnOrder(g);
      }));
    });
  }
  /**
   * Handle Alt+Arrow keyboard shortcuts for column reordering.
   */
  onKeyDown(e) {
    if (!e.altKey || e.key !== "ArrowLeft" && e.key !== "ArrowRight")
      return;
    const t = this.grid, i = t._focusCol, n = t._visibleColumns;
    if (i < 0 || i >= n.length) return;
    const d = n[i];
    if (!this.canMoveColumnWithPlugins(d)) return;
    const a = this.getColumnOrder(), r = a.indexOf(d.field);
    if (r === -1) return;
    const s = e.key === "ArrowLeft" ? r - 1 : r + 1;
    if (s < 0 || s >= a.length) return;
    const o = n.find((c) => c.field === a[s]);
    if (this.canMoveColumnWithPlugins(o))
      return this.moveColumn(d.field, s), t._focusCol = s, w(this.grid), e.preventDefault(), e.stopPropagation(), !0;
  }
  // #endregion
  // #region Public API
  /**
   * Get the current column order from the grid.
   * @returns Array of field names in display order
   */
  getColumnOrder() {
    return this.grid.getColumnOrder();
  }
  /**
   * Move a column to a new position.
   * @param field - The field name of the column to move
   * @param toIndex - The target index
   */
  moveColumn(e, t) {
    const i = this.getColumnOrder(), n = i.indexOf(e);
    if (n === -1) return;
    const d = p(i, n, t);
    this.emitCancelable("column-move", {
      field: e,
      fromIndex: n,
      toIndex: t,
      columnOrder: d
    }) || this.updateColumnOrder(d);
  }
  /**
   * Set a specific column order.
   * @param order - Array of field names in desired order
   */
  setColumnOrder(e) {
    this.updateColumnOrder(e);
  }
  /**
   * Reset column order to the original configuration order.
   */
  resetColumnOrder() {
    const e = this.columns.map((t) => t.field);
    this.updateColumnOrder(e);
  }
  // #endregion
  // #region View Transition
  /**
   * Capture header cell positions before reorder.
   */
  captureHeaderPositions() {
    const e = /* @__PURE__ */ new Map();
    return this.gridElement?.querySelectorAll(".header-row > .cell[data-field]").forEach((t) => {
      const i = t.getAttribute("data-field");
      i && e.set(i, t.getBoundingClientRect().left);
    }), e;
  }
  /**
   * Apply FLIP animation for column reorder.
   * Uses CSS transitions - JS sets initial transform and toggles class.
   * @param oldPositions - Header positions captured before DOM change
   */
  animateFLIP(e) {
    const t = this.gridElement;
    if (!t || e.size === 0) return;
    const i = /* @__PURE__ */ new Map();
    if (t.querySelectorAll(".header-row > .cell[data-field]").forEach((a) => {
      const r = a.getAttribute("data-field");
      if (!r) return;
      const s = e.get(r);
      if (s === void 0) return;
      const o = s - a.getBoundingClientRect().left;
      Math.abs(o) > 1 && i.set(r, o);
    }), i.size === 0) return;
    const n = [];
    if (t.querySelectorAll(".cell[data-field]").forEach((a) => {
      const r = i.get(a.getAttribute("data-field") ?? "");
      if (r !== void 0) {
        const s = a;
        s.style.transform = `translateX(${r}px)`, n.push(s);
      }
    }), n.length === 0) return;
    this.gridElement.offsetHeight;
    const d = this.animationDuration;
    requestAnimationFrame(() => {
      n.forEach((a) => {
        a.classList.add("flip-animating"), a.style.transform = "";
      }), setTimeout(() => {
        n.forEach((a) => {
          a.style.transform = "", a.classList.remove("flip-animating");
        });
      }, d + 50);
    });
  }
  /**
   * Apply crossfade animation for moved columns.
   * Uses CSS keyframes - JS just toggles classes.
   */
  animateFade(e) {
    const t = this.gridElement;
    if (!t) {
      e();
      return;
    }
    const i = this.captureHeaderPositions();
    e();
    const n = /* @__PURE__ */ new Set();
    if (t.querySelectorAll(".header-row > .cell[data-field]").forEach((r) => {
      const s = r.getAttribute("data-field");
      if (!s) return;
      const o = i.get(s);
      if (o === void 0) return;
      const c = r.getBoundingClientRect().left;
      Math.abs(o - c) > 1 && n.add(s);
    }), n.size === 0) return;
    const d = [];
    if (t.querySelectorAll(".cell[data-field]").forEach((r) => {
      const s = r.getAttribute("data-field");
      if (s && n.has(s)) {
        const o = r;
        o.classList.add("fade-animating"), d.push(o);
      }
    }), d.length === 0) return;
    const a = this.animationDuration;
    setTimeout(() => {
      d.forEach((r) => r.classList.remove("fade-animating"));
    }, a + 50);
  }
  /**
   * Update column order with configured animation.
   */
  updateColumnOrder(e) {
    const t = this.animationType;
    if (t === "flip" && this.gridElement) {
      const i = this.captureHeaderPositions();
      this.grid.setColumnOrder(e), requestAnimationFrame(() => {
        this.gridElement.offsetHeight, this.animateFLIP(i);
      });
    } else t === "fade" ? this.animateFade(() => this.grid.setColumnOrder(e)) : this.grid.setColumnOrder(e);
    this.grid.requestStateChange?.();
  }
  // #endregion
}
export {
  R as ReorderPlugin
};
//# sourceMappingURL=index.js.map
