const h = "tbw-print-isolation-style";
function p(r, t) {
  const e = document.createElement("style");
  return e.id = h, e.textContent = `
    /* Print isolation: hide everything except the target grid */
    @media print {
      /* Hide all body children by default */
      body > *:not(#${r}) {
        display: none !important;
      }

      /* But show the grid and ensure it's not hidden by ancestor rules */
      #${r} {
        display: block !important;
        position: static !important;
        visibility: visible !important;
        opacity: 1 !important;
        overflow: visible !important;
        height: auto !important;
        width: 100% !important;
        max-height: none !important;
        margin: 0 !important;
        padding: 0 !important;
        transform: none !important;
      }

      /* If grid is nested, we need to show its ancestors too */
      #${r},
      #${r} * {
        visibility: visible !important;
      }

      /* Walk up the DOM and show all ancestors of the grid */
      body *:has(> #${r}),
      body *:has(#${r}) {
        display: block !important;
        visibility: visible !important;
        opacity: 1 !important;
        overflow: visible !important;
        height: auto !important;
        position: static !important;
        transform: none !important;
        background: transparent !important;
        border: none !important;
        padding: 0 !important;
        margin: 0 !important;
      }

      /* Hide siblings of ancestors (everything that's not in the path to the grid) */
      body *:has(#${r}) > *:not(:has(#${r})):not(#${r}) {
        display: none !important;
      }

      /* Page settings */
      @page {
        size: ${t};
        margin: 1cm;
      }

      /* Ensure proper print styling */
      body {
        margin: 0 !important;
        padding: 0 !important;
        background: white !important;
        color-scheme: light !important;
      }
    }

    /* Screen: also apply isolation for print preview */
    @media screen {
      /* When this stylesheet is active, we're about to print */
      /* No screen-specific rules needed - isolation only applies to print */
    }
  `, e;
}
async function u(r, t = {}) {
  const { orientation: e = "landscape" } = t, i = r.id;
  document.querySelectorAll(`#${CSS.escape(i)}`).length > 1 && console.warn(
    `[tbw-grid:print] Multiple elements found with id="${i}". Print isolation may not work correctly. Ensure each grid has a unique ID.`
  ), document.getElementById(h)?.remove();
  const s = p(i, e);
  return document.head.appendChild(s), new Promise((a) => {
    const l = () => {
      window.removeEventListener("afterprint", l), document.getElementById(h)?.remove(), a();
    };
    window.addEventListener("afterprint", l), window.print(), setTimeout(() => {
      window.removeEventListener("afterprint", l), document.getElementById(h)?.remove(), a();
    }, 5e3);
  });
}
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>', g = {
  expand: "▶",
  collapse: "▼",
  sortAsc: "▲",
  sortDesc: "▼",
  sortNone: "⇅",
  submenuArrow: "▶",
  dragHandle: "⋮⋮",
  toolPanel: "☰",
  filter: m,
  filterActive: m,
  print: "🖨️"
};
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 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 i = new CustomEvent(t, { detail: e, 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 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 { ...g, ...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(), i = parseInt(e, 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(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 b = ".tbw-print-header,.tbw-print-footer{display:none}@media print{tbw-grid{overflow:visible!important;height:auto!important;border:none!important;border-radius:0!important;color-scheme:light only;-webkit-print-color-adjust:exact;print-color-adjust:exact}tbw-grid .tbw-grid-content{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .tbw-scroll-area{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .rows-body{overflow:visible!important;height:auto!important;max-height:none!important}tbw-grid .rows-container,tbw-grid .rows-viewport,tbw-grid .rows{overflow:visible!important;height:auto!important;max-height:none!important;transform:none!important}tbw-grid .rows-viewport .rows{position:static!important}tbw-grid .resize-handle,tbw-grid [part=sort-indicator],tbw-grid .tbw-filter-btn,tbw-grid .tool-panel,tbw-grid .tool-panel-content,tbw-grid .tbw-shell-header,tbw-grid .shell-toolbar,tbw-grid .tool-panel-toggle,tbw-grid [data-print-hide],tbw-grid .expander-cell,tbw-grid .tree-toggle,tbw-grid .context-menu,tbw-grid .faux-vscroll{display:none!important}tbw-grid .tbw-print-header{display:flex;justify-content:space-between;align-items:baseline;padding:var(--tbw-spacing-md, .5em) 0;margin-bottom:var(--tbw-spacing-md, .5em);border-bottom:2px solid var(--tbw-print-border, var(--tbw-color-border-strong));font-family:inherit}.tbw-print-header-title{font-size:1.25em;font-weight:700}.tbw-print-header-timestamp{font-size:var(--tbw-font-size-sm, .875em);color:var(--tbw-print-muted, var(--tbw-color-fg-muted))}tbw-grid .tbw-print-footer{display:block;margin-top:var(--tbw-spacing-md, .5em);padding-top:var(--tbw-spacing-md, .5em);border-top:1px solid var(--tbw-print-border, var(--tbw-color-border));font-size:var(--tbw-font-size-xs, .75em);color:var(--tbw-print-muted, var(--tbw-color-fg-muted));text-align:right}tbw-grid .data-grid-row{break-inside:avoid;page-break-inside:avoid}tbw-grid .cell{border:1px solid var(--tbw-print-cell-border, var(--tbw-color-border))!important}tbw-grid .header-row,tbw-grid .data-grid-row{padding-right:1px}tbw-grid .data-grid-row:hover,tbw-grid .cell:hover{background:inherit!important}@page{margin:1cm}@page{tbw-grid.print-landscape{size:landscape}}@page{tbw-grid.print-portrait{size:portrait}}}", f = {
  button: !1,
  orientation: "landscape",
  warnThreshold: 500,
  maxRows: 0,
  includeTitle: !0,
  includeTimestamp: !0,
  title: "",
  isolate: !1
};
class v extends w {
  /** @internal */
  name = "print";
  /** @internal */
  version = "1.0.0";
  /** CSS styles for print mode */
  styles = b;
  /** Current print state */
  #t = !1;
  /** Saved column visibility state */
  #r = null;
  /** Saved virtualization state */
  #n = null;
  /** Saved rows when maxRows limit is applied */
  #o = null;
  /** Print header element */
  #e = null;
  /** Print footer element */
  #i = null;
  /** Applied scale factor (legacy, used for cleanup) */
  #a = null;
  /**
   * Get the grid typed as PrintGridRef for internal access.
   */
  get #s() {
    return this.grid;
  }
  /**
   * Check if print is currently in progress
   */
  isPrinting() {
    return this.#t;
  }
  /**
   * Trigger the browser print dialog
   *
   * This method:
   * 1. Validates row count against maxRows limit
   * 2. Disables virtualization to render all rows
   * 3. Applies print-specific CSS classes
   * 4. Opens the browser print dialog (or isolated window if `isolate: true`)
   * 5. Restores normal state after printing
   *
   * @param params - Optional parameters to override config for this print
   * @param params.isolate - If true, prints in an isolated window containing only the grid
   * @returns Promise that resolves when print dialog closes
   */
  async print(t) {
    if (this.#t) {
      console.warn("[PrintPlugin] Print already in progress");
      return;
    }
    const e = this.gridElement;
    if (!e) {
      console.warn("[PrintPlugin] Grid not available");
      return;
    }
    const i = { ...f, ...this.config, ...t }, s = this.rows.length;
    let a = s, l = !1;
    if (i.warnThreshold > 0 && s > i.warnThreshold) {
      const d = i.maxRows > 0 ? `

Note: Output will be limited to ${i.maxRows.toLocaleString()} rows.` : "";
      if (!confirm(
        `This grid has ${s.toLocaleString()} rows. Printing large datasets may cause performance issues or browser slowdowns.${d}

Click OK to continue, or Cancel to abort.`
      ))
        return;
    }
    i.maxRows > 0 && s > i.maxRows && (a = i.maxRows, l = !0), this.#t = !0;
    const c = performance.now();
    this.emit("print-start", {
      rowCount: a,
      limitApplied: l,
      originalRowCount: s
    });
    try {
      const d = this.#s;
      this.#n = {
        bypassThreshold: d._virtualization?.bypassThreshold ?? 24
      }, this.#p(), l && (this.#o = this.sourceRows, this.grid.rows = this.sourceRows.slice(0, a), await new Promise((n) => setTimeout(n, 50))), (i.includeTitle || i.includeTimestamp) && this.#d(i), await this.#h(), await new Promise((n) => requestAnimationFrame(n)), await new Promise((n) => requestAnimationFrame(n)), e.classList.add(`print-${i.orientation}`), await new Promise((n) => requestAnimationFrame(n)), await new Promise((n) => requestAnimationFrame(n)), i.isolate ? await this.#m(i) : await this.#c(), this.emit("print-complete", {
        success: !0,
        rowCount: a,
        duration: Math.round(performance.now() - c)
      });
    } catch (d) {
      console.error("[PrintPlugin] Print failed:", d), this.emit("print-complete", {
        success: !1,
        rowCount: 0,
        duration: Math.round(performance.now() - c)
      });
    } finally {
      this.#g(), this.#t = !1;
    }
  }
  /**
   * Add print header with title and timestamp
   */
  #d(t) {
    const e = this.gridElement;
    if (e) {
      if (this.#e = document.createElement("div"), this.#e.className = "tbw-print-header", t.includeTitle) {
        const i = t.title || this.grid.effectiveConfig?.shell?.header?.title || "Grid Data", o = document.createElement("div");
        o.className = "tbw-print-header-title", o.textContent = i, this.#e.appendChild(o);
      }
      if (t.includeTimestamp) {
        const i = document.createElement("div");
        i.className = "tbw-print-header-timestamp", i.textContent = `Printed: ${(/* @__PURE__ */ new Date()).toLocaleString()}`, this.#e.appendChild(i);
      }
      e.insertBefore(this.#e, e.firstChild), this.#i = document.createElement("div"), this.#i.className = "tbw-print-footer", this.#i.textContent = `Page generated from ${window.location.hostname}`, e.appendChild(this.#i);
    }
  }
  /**
   * Disable virtualization to render all rows
   */
  async #h() {
    const t = this.#s;
    if (!t._virtualization) return;
    const e = this.rows.length;
    t._virtualization.bypassThreshold = e + 100, t.refreshVirtualWindow(!0), await new Promise((i) => setTimeout(i, 100));
  }
  /**
   * Trigger the browser print dialog
   */
  async #c() {
    return new Promise((t) => {
      const e = () => {
        window.removeEventListener("afterprint", e), t();
      };
      window.addEventListener("afterprint", e), window.print(), setTimeout(() => {
        typeof window < "u" && window.removeEventListener("afterprint", e), t();
      }, 1e3);
    });
  }
  /**
   * Print in isolation by hiding all other page content.
   * This excludes navigation, sidebars, etc. while keeping the grid in place.
   */
  async #m(t) {
    const e = this.gridElement;
    e && await u(e, {
      orientation: t.orientation
    });
  }
  /**
   * Hide columns marked with printHidden: true
   */
  #p() {
    const t = this.columns;
    if (t) {
      this.#r = /* @__PURE__ */ new Map();
      for (const e of t)
        e.printHidden && e.field && (this.#r.set(e.field, !e.hidden), this.grid.setColumnVisible(e.field, !1));
    }
  }
  /**
   * Restore columns that were hidden for printing
   */
  #u() {
    if (this.#r) {
      for (const [t, e] of this.#r)
        this.grid.setColumnVisible(t, e);
      this.#r = null;
    }
  }
  /**
   * Cleanup after printing
   */
  #g() {
    const t = this.gridElement;
    if (!t) return;
    this.#u(), t.classList.remove("print-portrait", "print-landscape"), this.#a !== null && (t.style.transform = "", t.style.transformOrigin = "", t.style.width = "", this.#a = null), this.#e && (this.#e.remove(), this.#e = null), this.#i && (this.#i.remove(), this.#i = null);
    const e = this.#s;
    this.#n && e._virtualization && (e._virtualization.bypassThreshold = this.#n.bypassThreshold, e.refreshVirtualWindow(!0), this.#n = null), this.#o !== null && (this.grid.rows = this.#o, this.#o = null);
  }
  /**
   * Register toolbar button if configured
   * @internal
   */
  afterRender() {
    this.config?.button && !this.#l && (this.#w(), this.#l = !0);
  }
  /** Track if toolbar button is registered */
  #l = !1;
  /**
   * Register print button in toolbar
   */
  #w() {
    this.#s.registerToolbarContent?.({
      id: "print-button",
      order: 900,
      // High order to appear at the end
      render: (e) => {
        const i = document.createElement("button");
        i.className = "tbw-toolbar-btn tbw-print-btn", i.title = "Print grid", i.type = "button";
        const o = this.resolveIcon("print") || "🖨️";
        this.setIcon(i, o), i.addEventListener(
          "click",
          () => {
            this.print();
          },
          { signal: this.disconnectSignal }
        ), e.appendChild(i);
      }
    });
  }
}
export {
  v as PrintPlugin,
  u as printGridIsolated
};
//# sourceMappingURL=index.js.map
