# Print Plugin

> Print the grid contents with configurable page settings.

The **Print Plugin** enables printing the full grid content by temporarily disabling virtualization
and applying print-optimized styles. It handles large datasets gracefully with configurable row limits.

## Features

- **Print Layout Mode** - Optimized CSS styles for printing
- **Page Orientation** - Portrait or landscape orientation
- **Row Limits** - Configurable maximum rows with confirmation dialog for large datasets
- **Toolbar Button** - Optional print button in grid header
- **Title & Timestamp** - Optional print header with customizable title and timestamp
- **Column Hiding** - Mark columns as `printHidden` to exclude from print output

## Installation

```typescript
import '@toolbox-web/grid/features/print';
```

## Basic Usage

Use the controls to try different orientations, toggle title/timestamp, isolated printing, and the toolbar button.
Enable "Toolbar button" to add a print icon to the grid header (requires `shell.header` config).

```ts
// PrintBasicDemo.astro
import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';

import '@toolbox-web/grid/features/print';

const container = document.getElementById('print-basic-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container)!;
  const btn = container.querySelector('#print-basic-btn');

  const employeeData = [
    { id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 85000, status: 'Active', email: 'alice@company.com' },
    { id: 2, name: 'Bob Smith', department: 'Marketing', salary: 72000, status: 'Active', email: 'bob@company.com' },
    { id: 3, name: 'Carol Williams', department: 'Engineering', salary: 92000, status: 'Active', email: 'carol@company.com' },
    { id: 4, name: 'David Brown', department: 'Sales', salary: 68000, status: 'On Leave', email: 'david@company.com' },
    { id: 5, name: 'Emma Davis', department: 'HR', salary: 65000, status: 'Active', email: 'emma@company.com' },
    { id: 6, name: 'Frank Miller', department: 'Engineering', salary: 88000, status: 'Active', email: 'frank@company.com' },
    { id: 7, name: 'Grace Wilson', department: 'Marketing', salary: 75000, status: 'Active', email: 'grace@company.com' },
    { id: 8, name: 'Henry Taylor', department: 'Sales', salary: 70000, status: 'Active', email: 'henry@company.com' },
    { id: 9, name: 'Ivy Anderson', department: 'Finance', salary: 82000, status: 'On Leave', email: 'ivy@company.com' },
    { id: 10, name: 'Jack Thomas', department: 'Engineering', salary: 95000, status: 'Active', email: 'jack@company.com' },
  ];

  let opts: Record<string, unknown> = {
    orientation: 'landscape', button: false, includeTitle: true, includeTimestamp: true, isolate: true,
  };

  function applyConfig() {
    grid.gridConfig = {
      fitMode: 'stretch',
      ...(opts.button ? { shell: { header: { title: 'Employee Report' } } } : {}),
      columns: [
        { field: 'id', header: 'ID', width: 60 },
        { field: 'name', header: 'Name', minWidth: 150 },
        { field: 'department', header: 'Department', width: 120 },
        { field: 'email', header: 'Email', minWidth: 200 },
        { field: 'salary', header: 'Salary', width: 100, align: 'right' },
        { field: 'status', header: 'Status', width: 100 },
      ],
      features: {
        print: {
          orientation: opts.orientation as string,
          button: opts.button as boolean,
          title: 'Employee Report',
          includeTitle: opts.includeTitle as boolean,
          includeTimestamp: opts.includeTimestamp as boolean,
        },
      },
    };
    grid.rows = employeeData;
  }

  applyConfig();

  container.addEventListener('control-change', ((e: CustomEvent) => {
    opts = e.detail.allValues;
    applyConfig();
  }) as EventListener);

  btn?.addEventListener('click', () => {
    const plugin = grid.getPluginByName('print');
    plugin?.print({ isolate: opts.isolate as boolean });
  });
}
```

#### TypeScript

```typescript
import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/print';

const grid = queryGrid('tbw-grid');

grid.gridConfig = {
  columns: [
    { field: 'name', header: 'Name' },
    { field: 'department', header: 'Department' },
    { field: 'salary', header: 'Salary' },
  ],
  features: {
    print: {
      title: 'Employee Report',
      includeTitle: true,
      includeTimestamp: true,
    },
  },
};

// Programmatic print
const plugin = grid.getPluginByName('print');
await plugin.print();
```

#### React

```tsx
import '@toolbox-web/grid-react/features/print';
import { DataGrid } from '@toolbox-web/grid-react';
import { useGridPrint } from '@toolbox-web/grid-react/features/print';

function EmployeeGrid({ data }) {
  const { print } = useGridPrint();

  const handlePrint = async () => {
    await print({ title: 'Employee Report' });
  };

  return (
    <div>
      <button onClick={handlePrint}>Print Report</button>
      <DataGrid
        rows={data}
        columns={[
          { field: 'name', header: 'Name' },
          { field: 'department', header: 'Department' },
          { field: 'salary', header: 'Salary' },
        ]}
        print={{
          title: 'Employee Report',
          includeTitle: true,
          includeTimestamp: true,
        }}
        style={{ height: '400px' }}
      />
    </div>
  );
}
```

#### Vue

```html
<script setup>
import '@toolbox-web/grid-vue/features/print';
import { TbwGrid, TbwGridColumn, useGrid } from '@toolbox-web/grid-vue';

const data = [
  { name: 'Alice', department: 'Engineering', salary: 95000 },
  { name: 'Bob', department: 'Marketing', salary: 75000 },
];

const { gridElement } = useGrid();

const handlePrint = async () => {
  await gridElement.value?.getPluginByName('print')?.print();
};
</script>

<template>
  <div>
    <button @click="handlePrint">Print Report</button>
    <TbwGrid :rows="data" :print="{ title: 'Employee Report', includeTitle: true, includeTimestamp: true }" style="height: 400px">
      <TbwGridColumn field="name" header="Name" />
      <TbwGridColumn field="department" header="Department" />
      <TbwGridColumn field="salary" header="Salary" />
    </TbwGrid>
  </div>
</template>
```

#### Angular

```typescript
// Feature import - enables the [print] input
import { GridPrintDirective } from '@toolbox-web/grid-angular/features/print';
import { Component } from '@angular/core';
import { Grid, injectGrid } from '@toolbox-web/grid-angular';
import type { ColumnConfig } from '@toolbox-web/grid';

@Component({
  selector: 'app-employee-grid',
  imports: [Grid, GridPrintDirective],
  template: `
    <button (click)="handlePrint()">Print Report</button>
    <tbw-grid
      [rows]="rows"
      [columns]="columns"
      [print]="{ title: 'Employee Report', includeTitle: true, includeTimestamp: true }"
      style="height: 400px; display: block;">
    </tbw-grid>
  `,
})
export class EmployeeGridComponent {
  grid = injectGrid();
  rows = [...];

  columns: ColumnConfig[] = [
    { field: 'name', header: 'Name' },
    { field: 'department', header: 'Department' },
    { field: 'salary', header: 'Salary' },
  ];

  async handlePrint() {
    const plugin = this.grid.element()?.getPluginByName('print');
    await plugin?.print();
  }
}
```

## Configuration Options

See [`PrintConfig`](./Interfaces/PrintConfig/) for the full list of options and defaults.

## Hiding Columns in Print

Use the `printHidden` column property to exclude specific columns from print output.
This is useful for hiding action buttons, interactive elements, or columns that aren't
relevant on paper.

```typescript
import { queryGrid } from '@toolbox-web/grid';

const grid = queryGrid('tbw-grid');

grid.gridConfig = {
  columns: [
    { field: 'id', header: 'ID' },
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' },
    { field: 'actions', header: 'Actions', printHidden: true }, // Hidden when printing
  ],
  features: { print: true },
};
```

The `printHidden` property:
- Temporarily hides columns when `print()` is called
- Automatically restores column visibility after printing
- Preserves the original hidden state (if a column was already hidden, it stays hidden)
- Works independently of the VisibilityPlugin

### Utility (system) columns

Columns marked with [`utility: true`](/grid/core.md#system-columns) — including the
selection checkbox, expander, drag handle, and any system column you author yourself —
are hidden from print **by default**, no `printHidden` needed.

To force a utility column to appear in the printout, set `printHidden: false` explicitly:

```typescript
{ field: '__id', header: '#', utility: true, printHidden: false }
```

```ts
// PrintHiddenColumnsDemo.astro
import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';

import '@toolbox-web/grid/features/print';

const container = document.getElementById('print-hidden-columns-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container);
  const btn = container.querySelector('#print-hidden-btn');

  const employeeData = [
    { id: 1, name: 'Alice Johnson', department: 'Engineering', salary: 85000, email: 'alice@company.com' },
    { id: 2, name: 'Bob Smith', department: 'Marketing', salary: 72000, email: 'bob@company.com' },
    { id: 3, name: 'Carol Williams', department: 'Engineering', salary: 92000, email: 'carol@company.com' },
    { id: 4, name: 'David Brown', department: 'Sales', salary: 68000, email: 'david@company.com' },
    { id: 5, name: 'Emma Davis', department: 'HR', salary: 65000, email: 'emma@company.com' },
    { id: 6, name: 'Frank Miller', department: 'Engineering', salary: 88000, email: 'frank@company.com' },
    { id: 7, name: 'Grace Wilson', department: 'Marketing', salary: 75000, email: 'grace@company.com' },
    { id: 8, name: 'Henry Taylor', department: 'Sales', salary: 70000, email: 'henry@company.com' },
  ];

  grid.gridConfig = {
    fitMode: 'stretch',
    columns: [
      { field: 'id', header: 'ID', width: 60 },
      { field: 'name', header: 'Name', minWidth: 150 },
      { field: 'department', header: 'Department', width: 120 },
      { field: 'email', header: 'Email', minWidth: 200 },
      { field: 'salary', header: 'Salary', width: 100, align: 'right' },
      {
        field: 'actions',
        header: 'Actions',
        width: 100,
        printHidden: true,
        renderer: () => {
          const btn = document.createElement('button');
          btn.textContent = 'Edit';
          btn.style.cssText = 'padding: 4px 8px; cursor: pointer;';
          return btn;
        },
      },
    ],
    features: {
      print: {
        title: 'Employee Report (Filtered)',
        includeTitle: true,
        includeTimestamp: true,
      },
    },
  };
  grid.rows = employeeData;

  btn?.addEventListener('click', () => {
    const plugin = grid.getPluginByName('print');
    plugin?.print({ isolate: true });
  });
}
```

## Row Limits

Two separate options control large dataset behavior:

- **`warnThreshold`** - Shows a confirmation dialog when row count exceeds this value,
  letting users consent to a potentially slow print operation
- **`maxRows`** - Hard-limits the printed rows to this number (excess rows are not rendered)

```ts
// PrintRowLimitDemo.astro
import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';

import '@toolbox-web/grid/features/print';

const container = document.getElementById('print-row-limit-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container);
  const btn = container.querySelector('#print-limit-btn');

  const largeDataset = Array.from({ length: 1000 }, (_, i) => ({
    id: i + 1,
    name: `Employee ${i + 1}`,
    department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Finance'][i % 5],
    salary: 50000 + Math.floor(Math.random() * 50000),
  }));

  grid.gridConfig = {
    columns: [
      { field: 'id', header: 'ID', width: 60 },
      { field: 'name', header: 'Name', width: 180 },
      { field: 'department', header: 'Department', width: 120 },
      { field: 'salary', header: 'Salary', width: 100, align: 'right' },
    ],
    features: {
      print: {
        warnThreshold: 500,
        maxRows: 100,
        title: 'Large Dataset Print Test',
      },
    },
  };
  grid.rows = largeDataset;

  btn?.addEventListener('click', () => {
    const plugin = grid.getPluginByName('print');
    plugin?.print({ isolate: true });
  });
}
```

```typescript
// Warn at 500+ rows, but print all if confirmed
features: { print: { warnThreshold: 500 } }

// Hard limit to 100 rows (no warning, just limits)
features: { print: { maxRows: 100, warnThreshold: 0 } }

// Warn at 500+ rows AND hard limit to 1000
features: { print: { warnThreshold: 500, maxRows: 1000 } }
```

The confirmation dialog shows:
- Total row count
- Note about hard limit (if `maxRows` is set)
- Option to proceed or cancel

## Events

The plugin emits events during the print lifecycle:

```ts
// PrintPrintEventsDemo.astro
import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';

import '@toolbox-web/grid/features/print';

const container = document.getElementById('print-events-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container);
  const btn = container.querySelector('#print-events-btn');
  const log = container.querySelector('#print-event-log');

  const employeeData = [
    { id: 1, name: 'Alice Johnson', department: 'Engineering' },
    { id: 2, name: 'Bob Smith', department: 'Marketing' },
    { id: 3, name: 'Carol Williams', department: 'Engineering' },
    { id: 4, name: 'David Brown', department: 'Sales' },
    { id: 5, name: 'Emma Davis', department: 'HR' },
  ];

  grid.gridConfig = {
    columns: [
      { field: 'name', header: 'Name' },
      { field: 'department', header: 'Department' },
    ],
    features: {
      print: {
        title: 'Event Demo',
        warnThreshold: 0,
      },
    },
  };
  grid.rows = employeeData;

  grid.on('print-start', ({ rowCount }) => {
    if (!log) return;
    const msg = document.createElement('div');
    msg.textContent = `[print-start] Preparing ${rowCount} rows...`;
    log.appendChild(msg);
  });

  grid.on('print-complete', ({ duration }) => {
    if (!log) return;
    const msg = document.createElement('div');
    msg.textContent = `[print-complete] Dialog closed after ${duration}ms`;
    log.appendChild(msg);
  });

  btn?.addEventListener('click', () => {
    const plugin = grid.getPluginByName('print');
    plugin?.print({ isolate: true });
  });
}
```

### `print-start`

Fired when print preparation begins:

```typescript
grid.on('print-start', ({ rowCount, limitApplied, originalRowCount }) => {
  console.log('Preparing rows:', rowCount);
  console.log('Limit applied:', limitApplied);
  if (limitApplied) {
    console.log('Original count:', originalRowCount);
  }
});
```

### `print-complete`

Fired when the print dialog closes:

```typescript
grid.on('print-complete', ({ duration, rowCount }) => {
  console.log('Dialog open for:', duration, 'ms');
  console.log('Rows prepared:', rowCount);
});
```

:::note
Browsers don't expose whether the user clicked "Print" or "Cancel".
The `success` field only indicates whether the plugin encountered an error
preparing the print dialog - it does NOT indicate the user's choice.
:::

## Programmatic API

### `print(params?)`

Initiates the print process. Returns a Promise that resolves when printing completes.

```typescript
const plugin = grid.getPluginByName('print');

// Basic print (prints entire page)
await plugin.print();

// With runtime overrides
await plugin.print({
  title: 'Custom Report Title',
  orientation: 'portrait',
});

// Isolated print - opens new window with only the grid
await plugin.print({ isolate: true });
```

### Print Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `orientation` | `'portrait' \| 'landscape'` | Override page orientation |
| `title` | `string` | Override print title |
| `maxRows` | `number` | Override maximum rows |
| `isolate` | `boolean` | Print with page isolation (hides all non-grid content) |

### Isolated Printing

When `isolate: true` is passed, the plugin uses CSS to hide **everything** on the page
except the grid during printing. This is useful when:

- The page has navigation, sidebars, or headers that shouldn't appear in print
- You want to print only the grid without surrounding UI
- The grid is embedded in an application framework like Storybook, Angular, etc.

```typescript
// Print just the grid, hiding all other page content
await plugin.print({ isolate: true });
```

**How it works:**
1. A temporary `@media print` stylesheet is injected
2. The stylesheet hides all elements except the grid (identified by its unique ID)
3. After printing, the stylesheet is removed
4. The grid stays in place with virtualization disabled, so ALL rows are printed

### `isPrinting()`

Returns `true` if a print operation is currently in progress.

```typescript
if (plugin.isPrinting()) {
  console.log('Print already in progress');
}
```

## Print CSS Classes

The plugin applies these CSS classes during printing:

| Class | Description |
|-------|-------------|
| `.print-portrait` | Applied when orientation is portrait |
| `.print-landscape` | Applied when orientation is landscape |
| `.tbw-print-header` | Print header container |
| `.tbw-print-header-title` | Title element |
| `.tbw-print-header-timestamp` | Timestamp element |

## Custom Print Styles

Override print styles using CSS:

```css
@media print {
  tbw-grid.print-landscape {
    /* Custom print styles */
    font-size: 10pt;
  }

  tbw-grid.print-landscape .dg-cell {
    border-color: #000;
  }
}
```

## How It Works

1. **Disable Virtualization** - Temporarily renders all rows (up to `maxRows`)
2. **Apply Print Styles** - Adds print-mode CSS class and orientation class
3. **Add Print Header** - Inserts title and timestamp (if configured)
4. **Trigger Print** - Calls `window.print()`
5. **Cleanup** - Restores virtualization and removes temporary elements

## Browser Support

The Print Plugin uses standard browser printing APIs (`window.print()`, `@media print`)
and works in all modern browsers:

- Chrome / Edge (Chromium)
- Firefox
- Safari

## Best Practices

1. **Set reasonable `maxRows`** - Consider limiting to ~5000 rows to prevent browser hangs
2. **Use landscape for wide grids** - More columns fit horizontally
3. **Test print preview** - Use browser's print preview to verify layout
4. **Custom titles** - Provide meaningful titles for printed reports
5. **Use `printHidden` for interactive columns** - Hide action buttons, checkboxes, etc.
6. **Use `isolate: true`** - When other page content interferes with print layout

## Standalone Utility Function

For simple use cases where you don't need the full plugin, use the standalone
`printGridIsolated` function:

```typescript
import { queryGrid } from '@toolbox-web/grid';
import { printGridIsolated } from '@toolbox-web/grid/plugins/print';

const grid = queryGrid('tbw-grid');
await printGridIsolated(grid, {
  orientation: 'landscape',
});
```

This function injects a temporary CSS stylesheet that hides everything except
the target grid during printing. The grid must have an `id` attribute (DataGridElement
auto-generates one if not explicitly set).

**Note:** Unlike the PrintPlugin, this function does NOT disable virtualization.
If you need to print all rows, use the full PrintPlugin with `isolate: true`.

## See Also

- **[Export](/grid/plugins/export.md)** — Export to CSV/JSON
- **[Selection](/grid/plugins/selection.md)** — Row/cell selection
