# Clipboard Plugin

> Copy and paste grid data with Excel-compatible clipboard support.

The Clipboard plugin brings familiar copy/paste functionality to your grid with full keyboard shortcut support (Ctrl+C, Ctrl+V). It handles single cells, multi-cell selections, and integrates seamlessly with Excel and other spreadsheet applications via tab-delimited output.

:::tip
Works best with [SelectionPlugin](/grid/plugins/selection.md) for copying/pasting selected cells. Without it, copies the entire grid and pastes at row 0, column 0.
:::

## Installation

```ts
import '@toolbox-web/grid/features/clipboard';
```

## Basic Usage

Just enable the feature and you're ready to go—keyboard shortcuts work automatically. Configure options like `includeHeaders` for copying column headers or `delimiter` for custom CSV formats.

#### TypeScript

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

const grid = queryGrid('tbw-grid');
grid.gridConfig = {
  columns: [
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' },
    { field: 'department', header: 'Department' }
  ],
  features: {
    clipboard: {
      includeHeaders: true,
      quoteStrings: true, // Wrap text in quotes for CSV compatibility
    },
  },
};
```

#### React

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

function MyGrid({ data }) {
  return (
    <DataGrid
      rows={data}
      columns={[
        { field: 'name', header: 'Name' },
        { field: 'email', header: 'Email' },
        { field: 'department', header: 'Department' }
      ]}
      selection="range"
      clipboard={{ includeHeaders: true, quoteStrings: true }}
      style={{ height: '400px' }}
    />
  );
}
```

#### Vue

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

const data = [
  { name: 'Alice', email: 'alice@example.com', department: 'Engineering' },
  { name: 'Bob', email: 'bob@example.com', department: 'Marketing' },
];
</script>

<template>
  <TbwGrid
    :rows="data"
    selection="range"
    :clipboard="{ includeHeaders: true, quoteStrings: true }"
  >
    <TbwGridColumn field="name" header="Name" />
    <TbwGridColumn field="email" header="Email" />
    <TbwGridColumn field="department" header="Department" />
  </TbwGrid>
</template>
```

#### Angular

```typescript
// Feature imports - enable the [selection] and [clipboard] inputs
import { GridSelectionDirective } from '@toolbox-web/grid-angular/features/selection';
import { GridClipboardDirective } from '@toolbox-web/grid-angular/features/clipboard';
import { Component } from '@angular/core';
import { Grid } from '@toolbox-web/grid-angular';
import type { ColumnConfig } from '@toolbox-web/grid';

@Component({
  selector: 'app-my-grid',
  imports: [Grid, GridSelectionDirective, GridClipboardDirective],
  template: `
    <tbw-grid
      [rows]="rows"
      [columns]="columns"
      [selection]="'range'"
      [clipboard]="{ includeHeaders: true, quoteStrings: true }"
      style="height: 400px; display: block;">
    </tbw-grid>
  `,
})
export class MyGridComponent {
  rows = [...];

  columns: ColumnConfig[] = [
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' },
    { field: 'department', header: 'Department' }
  ];
}
```

## Demos

### Default Clipboard

```ts
// ClipboardDefaultDemo.astro
import '@toolbox-web/grid';
import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/clipboard';
import '@toolbox-web/grid/features/selection';

const container = document.getElementById('clipboard-default-demo');
if (container) {
  const sampleData = [
    { id: 1, name: 'Alice', email: 'alice@example.com', department: 'Engineering' },
    { id: 2, name: 'Bob', email: 'bob@example.com', department: 'Marketing' },
    { id: 3, name: 'Carol', email: 'carol@example.com', department: 'Engineering' },
    { id: 4, name: 'Dan', email: 'dan@example.com', department: 'Sales' },
  ];
  const columns = [
    { field: 'id', header: 'ID', type: 'number' },
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' },
    { field: 'department', header: 'Department' },
  ];

  const grid = queryGrid('tbw-grid', container)!;

  function rebuild(includeHeaders = false, quoteStrings = false) {
    grid.gridConfig = {
      columns,
      features: {
        selection: 'range',
        clipboard: { includeHeaders, quoteStrings },
      },
    };
    grid.rows = sampleData;
  }

  rebuild();

  container.addEventListener('control-change', ((e: CustomEvent) => {
    const v = e.detail.allValues;
    rebuild(v.includeHeaders as boolean, v.quoteStrings as boolean);
  }) as EventListener);
}
```

Select cells and use Ctrl+C to copy, Ctrl+V to paste.

### Copy & Paste (Auto-Paste)

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

import '@toolbox-web/grid/features/clipboard';
import '@toolbox-web/grid/features/editing';
import '@toolbox-web/grid/features/selection';

const container = document.getElementById('clipboard-copy-paste-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container);

  grid.gridConfig = {
    columns: [
      { field: 'col1', header: 'Column 1', editable: true },
      { field: 'col2', header: 'Column 2', editable: true },
      { field: 'col3', header: 'Column 3', editable: true },
    ],
    features: {
      selection: 'range',
      clipboard: true,
      editing: true,
    },
  };

  grid.rows = [
    { col1: 'A1', col2: 'B1', col3: 'C1' },
    { col1: 'A2', col2: 'B2', col3: 'C2' },
    { col1: 'A3', col2: 'B3', col3: 'C3' },
    { col1: 'A4', col2: 'B4', col3: 'C4' },
  ];
}
```

By default, the ClipboardPlugin handles paste operations automatically—no event handling needed.
Just add the plugin and paste works out of the box.

### Custom Paste Handler

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

import '@toolbox-web/grid/features/clipboard';
import '@toolbox-web/grid/features/selection';

const container = document.getElementById('clipboard-custom-paste-handler-demo');
if (container) {
  // Sample data for clipboard demos
  const sampleData = [
    { id: 1, name: 'Alice', email: 'alice@example.com', department: 'Engineering' },
    { id: 2, name: 'Bob', email: 'bob@example.com', department: 'Marketing' },
    { id: 3, name: 'Carol', email: 'carol@example.com', department: 'Engineering' },
    { id: 4, name: 'Dan', email: 'dan@example.com', department: 'Sales' },
  ];
  const columns = [
    { field: 'id', header: 'ID', type: 'number' },
    { field: 'name', header: 'Name' },
    { field: 'email', header: 'Email' },
    { field: 'department', header: 'Department' },
  ];

  // Sample data to copy
      const sampleDataBox = document.createElement('div');
      sampleDataBox.style.cssText = `
        padding: 12px;
        background: var(--demo-sample-bg);
        border-bottom: 1px solid var(--demo-sample-border);
        font-family: monospace;
        font-size: 13px;
        white-space: pre;
        user-select: all;
        cursor: text;
      `;
      sampleDataBox.textContent = `hello\tworld
  foo\tbar`;
      sampleDataBox.title = 'Paste this into the grid - values will be uppercased!';

      // Instructions banner
      const banner = document.createElement('div');
      banner.style.cssText = `
        padding: 12px;
        background: var(--demo-info-bg);
        border-bottom: 1px solid var(--demo-info-border);
        font-size: 14px;
        color: var(--demo-info-fg);
      `;
      banner.innerHTML = `
        <strong>Custom Handler:</strong> Pasted values are automatically UPPERCASED.
        <br>This demo also prevents adding new rows when pasting beyond the grid.
      `;

      // Grid
      const grid = queryGrid('tbw-grid', container);
      grid.style.cssText = 'flex: 1;';

      // Custom paste handler that uppercases values and prevents adding rows
      const customPasteHandler = (detail, gridEl) => {
        if (!detail.target) return;

        const { rows: pastedRows, target, fields } = detail;
        const columns = gridEl.effectiveConfig.columns ?? [];
        const allFields = columns.map((c) => c.field);
        const currentRows = [...(gridEl.rows as Record<string, string>[])];

        pastedRows.forEach((rowData, rowOffset) => {
          const targetRowIndex = target.row + rowOffset;

          // This handler allows adding rows (unlike the code sample which doesn't)
          while (targetRowIndex >= currentRows.length) {
            const emptyRow = {};
            allFields.forEach((f) => (emptyRow[f] = ''));
            currentRows.push(emptyRow);
          }

          currentRows[targetRowIndex] = { ...currentRows[targetRowIndex] };
          rowData.forEach((value, colOffset) => {
            const field = fields[colOffset];
            if (field) {
              // Transform: uppercase all values
              currentRows[targetRowIndex][field] = value.toUpperCase();
            }
          });
        });

        gridEl.rows = currentRows;
      };

      grid.gridConfig = {
        columns: [
          { field: 'col1', header: 'Column 1' },
          { field: 'col2', header: 'Column 2' },
          { field: 'col3', header: 'Column 3' },
        ],
        features: {
          selection: 'range',
          clipboard: { pasteHandler: customPasteHandler },
        },
      };

      grid.rows = [
        { col1: 'A1', col2: 'B1', col3: 'C1' },
        { col1: 'A2', col2: 'B2', col3: 'C2' },
        { col1: 'A3', col2: 'B3', col3: 'C3' },
      ];
}
```

For advanced use cases, provide a custom `pasteHandler` to validate, transform, or integrate with state management.

### Single Cell Mode

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

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

const container = document.getElementById('clipboard-single-cell-demo');
if (container) {
  const grid = queryGrid('tbw-grid', container);

  grid.gridConfig = {
    columns: [
      { field: 'id', header: 'ID', type: 'number', width: 60 },
      { field: 'name', header: 'Name' },
      { field: 'department', header: 'Department' },
      { field: 'email', header: 'Email' },
      { field: 'salary', header: 'Salary', type: 'number', align: 'right' },
    ],
    features: { clipboard: true },
  };

  grid.rows = [
    { id: 1, name: 'Alice Johnson', department: 'Engineering', email: 'alice@company.com', salary: 85000 },
    { id: 2, name: 'Bob Smith', department: 'Marketing', email: 'bob@company.com', salary: 72000 },
    { id: 3, name: 'Carol Williams', department: 'Engineering', email: 'carol@company.com', salary: 92000 },
    { id: 4, name: 'David Brown', department: 'Sales', email: 'david@company.com', salary: 68000 },
    { id: 5, name: 'Emma Davis', department: 'HR', email: 'emma@company.com', salary: 65000 },
    { id: 6, name: 'Frank Miller', department: 'Engineering', email: 'frank@company.com', salary: 88000 },
  ];
}
```

Without a SelectionPlugin, the clipboard operates on a single focused cell — press <kbd>Ctrl+C</kbd> to copy just that cell's value.

## Configuration Options

| Option           | Type                                  | Default              | Description                                                                                              |
| ---------------- | ------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------- |
| `includeHeaders` | `boolean`                             | `false`              | Include column headers in copied data                                                                    |
| `delimiter`      | `string`                              | `'\t'`               | Column delimiter (tab for Excel compatibility)                                                           |
| `newline`        | `string`                              | `'\n'`               | Row delimiter                                                                                            |
| `quoteStrings`   | `boolean`                             | `false`              | Wrap string values in quotes                                                                             |
| `processCell`    | `(value, field, row) => string`       | -                    | Custom cell value processor for copy operations                                                          |
| `pasteHandler`   | `PasteHandler \| null`                | `defaultPasteHandler`| Custom paste handler. Set to `null` to disable auto-paste and handle the `paste` event manually         |

## Paste Behavior

The clipboard plugin respects both selection bounds and the `editable` column property when pasting:

### Editable Columns

**Paste only works on columns marked `editable: true`.** Non-editable columns are skipped during paste, preserving column alignment:

```ts
columns: [
  { field: 'id', header: 'ID' },                    // NOT editable - paste skipped
  { field: 'name', header: 'Name', editable: true }, // Paste allowed
  { field: 'email', header: 'Email', editable: true }, // Paste allowed
]
```

If you paste `"1\t2\t3"` into this grid, only the `name` and `email` columns receive values (`2` and `3`). The `id` column remains unchanged.

:::tip
This works independently of the EditingPlugin. You can use `editable: true` to control paste behavior even without enabling inline editing.
:::

### Selection Bounds

| Selection Type  | Paste Behavior                                                                 |
| --------------- | ------------------------------------------------------------------------------ |
| Single cell     | Paste expands freely from that cell, adding rows if needed                     |
| Range selection | Paste is clipped to fit within the selected range                              |
| Row selection   | Paste is clipped to the selected rows (all columns within those rows)          |
| No selection    | Paste starts at row 0, column 0                                                |

**Example:** If you have a 2×2 range selected and paste 3×3 data, only the 2×2 portion that fits will be applied.

## Keyboard Shortcuts

| Shortcut           | Action                    |
| ------------------ | ------------------------- |
| `Ctrl+C` / `Cmd+C` | Copy selected cells       |
| `Ctrl+V` / `Cmd+V` | Paste into selected cells |

## Programmatic API

The ClipboardPlugin exposes methods for programmatic copy operations with fine-grained control over which columns and rows to include. This is ideal for workflows where users select rows in the grid, then choose columns via a dialog before copying.

### Basic Copy

```ts
const plugin = grid.getPluginByName('clipboard');

// Copy current selection to clipboard
await plugin.copy();

// Copy with headers included
await plugin.copy({ includeHeaders: true });
```

### Copy with Column/Row Control (`CopyOptions`)

The `copy()` method accepts `CopyOptions` to precisely control what gets copied—independently of the current selection:

```ts
const plugin = grid.getPluginByName('clipboard');

// Copy specific columns from specific rows
await plugin.copy({
  rowIndices: [0, 3, 7],         // Non-contiguous rows supported
  columns: ['name', 'email'],    // Only these columns
  includeHeaders: true,
});

// Copy specific rows with all visible columns
await plugin.copyRows([0, 5]);

// Copy specific rows with column filter
await plugin.copyRows([0, 5], { columns: ['name', 'department'] });
```

### Preview Without Copying

Use `getSelectionAsText()` to get the formatted text without writing to the clipboard—useful for preview dialogs:

```ts
const text = plugin.getSelectionAsText({
  columns: ['name', 'email', 'department'],
  includeHeaders: true,
});
// "Name\tEmail\tDepartment\nAlice\talice@example.com\tEngineering\n..."
```

### `CopyOptions` Reference

| Option           | Type                            | Default       | Description                                        |
| ---------------- | ------------------------------- | ------------- | -------------------------------------------------- |
| `columns`        | `string[]`                      | -             | Specific column fields to include                  |
| `rowIndices`     | `number[]`                      | -             | Specific row indices to copy (non-contiguous OK)   |
| `includeHeaders` | `boolean`                       | config value  | Include column headers in copied text              |
| `delimiter`      | `string`                        | config value  | Column delimiter override                          |
| `newline`        | `string`                        | config value  | Row delimiter override                             |
| `processCell`    | `(value, field, row) => string` | config value  | Custom cell value processor for this operation     |

### Paste

```ts
// Paste from clipboard
await plugin.paste();

// Get last copied info
const lastCopied = plugin.getLastCopied();
// { text: '...', timestamp: 1234567890 }
```

## Excel Compatibility

The default tab delimiter ensures copied data pastes correctly into Excel:

```ts
features: {
  clipboard: {
    delimiter: '\t', // Tab for Excel
    includeHeaders: true, // Include column names
  },
},
```

## Events

| Event   | Detail                                     | Description                   |
| ------- | ------------------------------------------ | ----------------------------- |
| `copy`  | `{ text, rowCount, columnCount }`          | Fired after cells are copied  |
| `paste` | `{ rows, text, target, fields }`           | Fired after data is pasted    |

## Styling

The clipboard plugin doesn't add visible UI elements. Selection styling is handled by the SelectionPlugin.

## See Also

- [Selection](/grid/plugins/selection.md) — Cell and row selection
- [Editing](/grid/plugins/editing.md) — Inline cell editing
- [Export](/grid/plugins/export.md) — Export to CSV/JSON
