const v = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰"
};
class w {
  /**
   * 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 r = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
    return this.grid?.dispatchEvent?.(r), r.defaultPrevented;
  }
  /**
   * Request a re-render of the grid.
   */
  requestRender() {
    this.grid?.requestRender?.();
  }
  /**
   * Request a re-render and restore focus styling afterward.
   * Use this when a plugin action (like expand/collapse) triggers a render
   * but needs to maintain keyboard navigation focus.
   */
  requestRenderWithFocus() {
    this.grid?.requestRenderWithFocus?.();
  }
  /**
   * Request a lightweight style update without rebuilding DOM.
   * Use this instead of requestRender() when only CSS classes need updating.
   */
  requestAfterRender() {
    this.grid?.requestAfterRender?.();
  }
  /**
   * Get the current rows from the grid.
   */
  get rows() {
    return this.grid?.rows ?? [];
  }
  /**
   * Get the original unfiltered/unprocessed rows from the grid.
   * Use this when you need all source data regardless of active filters.
   */
  get sourceRows() {
    return this.grid?.sourceRows ?? [];
  }
  /**
   * Get the current columns from the grid.
   */
  get columns() {
    return this.grid?.columns ?? [];
  }
  /**
   * Get only visible columns from the grid (excludes hidden).
   * Use this for rendering that needs to match the grid template.
   */
  get visibleColumns() {
    return this.grid?._visibleColumns ?? [];
  }
  /**
   * Get the grid as an HTMLElement for direct DOM operations.
   * Use sparingly - prefer the typed GridElementRef API when possible.
   *
   * @example
   * ```ts
   * const width = this.gridElement.clientWidth;
   * this.gridElement.classList.add('my-plugin-active');
   * ```
   */
  get gridElement() {
    return this.grid;
  }
  /**
   * Get the 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 { ...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(), r = parseInt(t, 10);
      if (!isNaN(r)) return r;
    }
    return 200;
  }
  // #endregion
  /**
   * Resolve an icon value to string or HTMLElement.
   * Checks plugin config first, then grid-level icons, then defaults.
   *
   * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
   * @param pluginOverride - Optional plugin-level override
   * @returns The resolved icon value
   */
  resolveIcon(e, t) {
    return t !== void 0 ? t : this.gridIcons[e];
  }
  /**
   * Set an icon value on an element.
   * Handles both string (text/HTML) and HTMLElement values.
   *
   * @param element - The element to set the icon on
   * @param icon - The icon value (string or HTMLElement)
   */
  setIcon(e, t) {
    typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
  }
  /**
   * Log a warning message.
   */
  warn(e) {
    console.warn(`[tbw-grid:${this.name}] ${e}`);
  }
  // #endregion
}
const C = '@layer tbw-plugins{.tbw-visibility-content{display:flex;flex-direction:column;height:100%}.tbw-visibility-list{flex:1;overflow-y:auto;padding:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem))}.tbw-visibility-row{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-menu-item-padding, .375rem .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);border-radius:var(--tbw-border-radius, .25rem);position:relative}.tbw-visibility-row:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}.tbw-visibility-row input[type=checkbox]{cursor:pointer}.tbw-visibility-row.locked span{color:var(--tbw-color-fg-muted, #888)}.tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted, #888);font-size:var(--tbw-font-size-2xs, .625rem);letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-row.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg, #1f2937)}.tbw-visibility-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;cursor:pointer}.tbw-visibility-row.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-row.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-row.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6))}.tbw-visibility-show-all{margin:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding, .5rem .75rem);border:1px solid var(--tbw-visibility-border, var(--tbw-color-border, #e5e7eb));border-radius:var(--tbw-border-radius, .25rem);background:var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg, #f9fafb));color:var(--tbw-color-fg, #1f2937);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-visibility-show-all:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6))}}';
function m(b) {
  const e = b.meta ?? {};
  return e.lockPosition !== !0 && e.suppressMovable !== !0;
}
class g extends w {
  /**
   * Plugin dependencies - VisibilityPlugin optionally uses ReorderPlugin for drag-drop reordering.
   *
   * When ReorderPlugin is present, columns in the visibility panel become draggable.
   */
  static dependencies = [
    { name: "reorder", required: !1, reason: "Enables drag-to-reorder columns in visibility panel" }
  ];
  name = "visibility";
  /** Tool panel ID for shell integration */
  static PANEL_ID = "columns";
  styles = C;
  get defaultConfig() {
    return {
      allowHideAll: !1
    };
  }
  // #region Internal State
  columnListElement = null;
  // Drag state for reorder integration
  isDragging = !1;
  draggedField = null;
  draggedIndex = null;
  dropIndex = null;
  /** Clear drag-related classes from all rows in a list. */
  clearDragClasses(e) {
    e.querySelectorAll(".tbw-visibility-row").forEach((t) => {
      t.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
    });
  }
  // #endregion
  // #region Lifecycle
  detach() {
    this.columnListElement = null, this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
  }
  // #endregion
  // #region Shell Integration
  /**
   * Register the column visibility tool panel with the shell.
   */
  getToolPanel() {
    return {
      id: g.PANEL_ID,
      title: "Columns",
      icon: "☰",
      tooltip: "Column visibility",
      order: 100,
      // High order so it appears last
      render: (e) => this.renderPanelContent(e)
    };
  }
  // #endregion
  // #region Public API
  /**
   * Show the visibility sidebar panel.
   * Opens the tool panel and ensures this section is expanded.
   */
  show() {
    this.grid.openToolPanel(), this.grid.expandedToolPanelSections.includes(g.PANEL_ID) || this.grid.toggleToolPanelSection(g.PANEL_ID);
  }
  /**
   * Hide the visibility sidebar panel.
   */
  hide() {
    this.grid.closeToolPanel();
  }
  /**
   * Toggle the visibility sidebar panel section.
   */
  toggle() {
    this.grid.isToolPanelOpen || this.grid.openToolPanel(), this.grid.toggleToolPanelSection(g.PANEL_ID);
  }
  /**
   * Check if a specific column is visible.
   * Delegates to grid.isColumnVisible().
   * @param field - The field name to check
   * @returns True if the column is visible
   */
  isColumnVisible(e) {
    return this.grid.isColumnVisible(e);
  }
  /**
   * Set visibility for a specific column.
   * Delegates to grid.setColumnVisible().
   * @param field - The field name of the column
   * @param visible - Whether the column should be visible
   */
  setColumnVisible(e, t) {
    this.grid.setColumnVisible(e, t);
  }
  /**
   * Get list of all visible column fields.
   * @returns Array of visible field names
   */
  getVisibleColumns() {
    return this.grid.getAllColumns().filter((e) => e.visible).map((e) => e.field);
  }
  /**
   * Get list of all hidden column fields.
   * @returns Array of hidden field names
   */
  getHiddenColumns() {
    return this.grid.getAllColumns().filter((e) => !e.visible).map((e) => e.field);
  }
  /**
   * Show all columns.
   * Delegates to grid.showAllColumns().
   */
  showAll() {
    this.grid.showAllColumns();
  }
  /**
   * Toggle visibility for a specific column.
   * Delegates to grid.toggleColumnVisibility().
   * @param field - The field name of the column
   */
  toggleColumn(e) {
    this.grid.toggleColumnVisibility(e);
  }
  /**
   * Show a specific column.
   * Delegates to grid.setColumnVisible().
   * @param field - The field name of the column to show
   */
  showColumn(e) {
    this.setColumnVisible(e, !0);
  }
  /**
   * Hide a specific column.
   * Delegates to grid.setColumnVisible().
   * @param field - The field name of the column to hide
   */
  hideColumn(e) {
    this.setColumnVisible(e, !1);
  }
  /**
   * Get all columns with their visibility status.
   * Useful for building visibility UI.
   * @returns Array of column info with visibility status
   */
  getAllColumns() {
    return this.grid.getAllColumns();
  }
  /**
   * Check if the sidebar panel is currently open.
   * @returns True if the panel section is expanded
   */
  isPanelVisible() {
    return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(g.PANEL_ID);
  }
  // #endregion
  // #region Private Methods
  /**
   * Render the panel content into the shell's tool panel container.
   * Returns a cleanup function.
   */
  renderPanelContent(e) {
    const t = document.createElement("div");
    t.className = "tbw-visibility-content";
    const r = document.createElement("div");
    r.className = "tbw-visibility-list", t.appendChild(r);
    const s = document.createElement("button");
    return s.className = "tbw-visibility-show-all", s.textContent = "Show All", s.addEventListener("click", () => {
      this.grid.showAllColumns(), this.rebuildToggles(r);
    }), t.appendChild(s), this.columnListElement = r, this.rebuildToggles(r), e.appendChild(t), () => {
      this.columnListElement = null, t.remove();
    };
  }
  /**
   * Check if a reorder plugin is present (by name to avoid static import).
   */
  hasReorderPlugin() {
    const e = this.grid?.getPluginByName?.("reorder");
    return !!(e && typeof e.moveColumn == "function");
  }
  /**
   * Build the column toggle checkboxes.
   * When a reorder plugin is present, adds drag handles for reordering.
   */
  rebuildToggles(e) {
    const t = this.hasReorderPlugin();
    e.innerHTML = "";
    const r = this.grid.getAllColumns().filter((s) => !s.utility);
    for (let s = 0; s < r.length; s++) {
      const i = r[s], d = i.header || i.field, n = document.createElement("div");
      n.className = i.lockVisible ? "tbw-visibility-row locked" : "tbw-visibility-row", n.setAttribute("data-field", i.field), n.setAttribute("data-index", String(s)), t && m(i) && (n.draggable = !0, n.classList.add("reorderable"), this.setupDragListeners(n, i.field, s, e));
      const l = document.createElement("label");
      l.className = "tbw-visibility-label";
      const o = document.createElement("input");
      o.type = "checkbox", o.checked = i.visible, o.disabled = i.lockVisible ?? !1, o.addEventListener("change", () => {
        this.grid.toggleColumnVisibility(i.field), setTimeout(() => this.rebuildToggles(e), 0);
      });
      const a = document.createElement("span");
      if (a.textContent = d, l.appendChild(o), l.appendChild(a), t && m(i)) {
        const c = document.createElement("span");
        c.className = "tbw-visibility-handle", this.setIcon(c, this.resolveIcon("dragHandle")), c.title = "Drag to reorder", n.appendChild(c);
      }
      n.appendChild(l), e.appendChild(n);
    }
  }
  /**
   * Set up drag-and-drop event listeners for a row.
   * On drop, emits a 'column-reorder-request' event for other plugins to handle.
   */
  setupDragListeners(e, t, r, s) {
    e.addEventListener("dragstart", (i) => {
      this.isDragging = !0, this.draggedField = t, this.draggedIndex = r, i.dataTransfer && (i.dataTransfer.effectAllowed = "move", i.dataTransfer.setData("text/plain", t)), e.classList.add("dragging");
    }), e.addEventListener("dragend", () => {
      this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, this.clearDragClasses(s);
    }), e.addEventListener("dragover", (i) => {
      if (i.preventDefault(), !this.isDragging || this.draggedField === t) return;
      const d = e.getBoundingClientRect(), n = d.top + d.height / 2;
      this.dropIndex = i.clientY < n ? r : r + 1, s.querySelectorAll(".tbw-visibility-row").forEach((l) => {
        l !== e && l.classList.remove("drop-target", "drop-before", "drop-after");
      }), e.classList.add("drop-target"), e.classList.toggle("drop-before", i.clientY < n), e.classList.toggle("drop-after", i.clientY >= n);
    }), e.addEventListener("dragleave", () => {
      e.classList.remove("drop-target", "drop-before", "drop-after");
    }), e.addEventListener("drop", (i) => {
      i.preventDefault();
      const d = this.draggedField, n = this.draggedIndex, l = this.dropIndex;
      if (!this.isDragging || d === null || n === null || l === null)
        return;
      const o = l > n ? l - 1 : l;
      if (o !== n) {
        const a = this.grid.getAllColumns(), h = a.filter((u) => !u.utility)[o]?.field, f = h ? a.findIndex((u) => u.field === h) : a.length, p = {
          field: d,
          fromIndex: n,
          // Not used by ReorderPlugin, just for info
          toIndex: f
        };
        this.emit("column-reorder-request", p), setTimeout(() => {
          this.rebuildToggles(s);
        }, 0);
      }
    });
  }
  // #endregion
}
export {
  g as VisibilityPlugin
};
//# sourceMappingURL=index.js.map
