Clipboard Plugin
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.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/clipboard';Basic Usage
Section titled “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.
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 }, },};import '@toolbox-web/grid-react/features/selection';import '@toolbox-web/grid-react/features/clipboard';import { DataGrid } from '@toolbox-web/grid-react';import type { GridConfig } from '@toolbox-web/grid-react';
const gridConfig: GridConfig = { columns: [ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'department', header: 'Department' } ], features: { selection: 'range', clipboard: { includeHeaders: true, quoteStrings: true }, },};
function MyGrid({ data }) { return <DataGrid rows={data} gridConfig={gridConfig} style={{ height: '400px' }} />;}<script setup>import '@toolbox-web/grid-vue/features/selection';import '@toolbox-web/grid-vue/features/clipboard';import { TbwGrid } from '@toolbox-web/grid-vue';import type { GridConfig } from '@toolbox-web/grid-vue';
const data = [ { name: 'Alice', email: 'alice@example.com', department: 'Engineering' }, { name: 'Bob', email: 'bob@example.com', department: 'Marketing' },];
const gridConfig: GridConfig = { columns: [ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'department', header: 'Department' }, ], features: { selection: 'range', clipboard: { includeHeaders: true, quoteStrings: true }, },};</script>
<template> <TbwGrid :rows="data" :grid-config="gridConfig" style="height: 400px" /></template>import '@toolbox-web/grid-angular/features/selection';import '@toolbox-web/grid-angular/features/clipboard';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { GridConfig } from '@toolbox-web/grid-angular';
@Component({ selector: 'app-my-grid', imports: [Grid], template: ` <tbw-grid [rows]="rows" [gridConfig]="gridConfig" style="height: 400px; display: block;"> </tbw-grid> `,})export class MyGridComponent { rows = [...];
gridConfig: GridConfig = { columns: [ { field: 'name', header: 'Name' }, { field: 'email', header: 'Email' }, { field: 'department', header: 'Department' } ], features: { selection: 'range', clipboard: { includeHeaders: true, quoteStrings: true }, }, };}Default Clipboard
Section titled “Default Clipboard”<tbw-grid style="height: 350px;"></tbw-grid>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 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', editable: true }, { field: 'email', header: 'Email', editable: true }, { field: 'department', header: 'Department', editable: true },];
const grid = queryGrid('tbw-grid');
function rebuild(includeHeaders = false, quoteStrings = false) { grid.gridConfig = { columns, features: { selection: 'range', clipboard: { includeHeaders, quoteStrings }, editing: true, }, }; grid.rows = sampleData;}
rebuild();Select cells and use Ctrl+C to copy, Ctrl+V to paste.
Copy & Paste (Auto-Paste)
Section titled “Copy & Paste (Auto-Paste)”Or paste tab/comma-separated data from Excel or Sheets!
Also try to select an area to paste in. Pasting will be restricted to that area only.
<tbw-grid style="height: 350px;"></tbw-grid>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 grid = queryGrid('tbw-grid');
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
Section titled “Custom Paste Handler”<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/clipboard';import '@toolbox-web/grid/features/selection';
// Sample data for clipboard demosconst 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\tworldfoo\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'); 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)];
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
Section titled “Single Cell Mode”Click a cell and press Ctrl+C to copy its value. No range selection — only the focused cell is copied.
<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';
import '@toolbox-web/grid/features/clipboard';
const grid = queryGrid('tbw-grid');
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 Ctrl+C to copy just that cell’s value.
Configuration Options
Section titled “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 |
escapeFormulas | boolean | true | Prefix values starting with =, +, -, @, tab or CR with ' so a spreadsheet treats them as text. Since 3.5.0 |
fillSelection | boolean | false | Tile the clipboard source to fill a larger multi-cell selection (see Fill Selection) |
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 |
Per-Column Paste Guard/Transform (column.onPaste)
Section titled “Per-Column Paste Guard/Transform (column.onPaste)”Use column.onPaste for cell-level paste rules without replacing the whole paste pipeline:
falserejects pastes into that column,(ctx) => falserejects a specific cell,(ctx) => ({ value })transforms the incoming value,- omitted /
trueaccepts as-is.
columns: [ { field: 'id', onPaste: false }, { field: 'price', onPaste: ({ value, sourceField }) => { // Reject cross-column pastes into this field. if (sourceField && sourceField !== 'price') return false; const n = Number(String(value).replace(/[^0-9.]/g, '')); return Number.isFinite(n) ? { value: n } : false; }, },];Cells rejected by onPaste emit one paste-rejected event per paste with a list of rejected cells.
Paste Behavior
Section titled “Paste Behavior”The clipboard plugin respects both selection bounds and the editable column property when pasting:
Editable Columns
Section titled “Editable Columns”Paste only works on columns marked editable: true. Non-editable columns are skipped during paste, preserving column alignment:
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.
Dirty Tracking, Undo & Validation
Section titled “Dirty Tracking, Undo & Validation”When your grid has a getRowId and the EditingPlugin loaded, paste routes each cell change through the same commit pipeline as an interactive edit. That means a paste automatically:
- marks rows dirty (surfaces in
isDirty()/getDirtyRows()whendirtyTracking: true), - is undoable with the UndoRedoPlugin (Ctrl+Z reverts the whole paste),
- emits
cell-commitper cell — a handler that callspreventDefault()vetoes that value, so you can coerce or reject pasted input, - triggers cascades and other editing side effects.
grid.addEventListener('cell-commit', (e) => { // Reject a pasted value that fails validation. if (e.detail.field === 'age' && Number.isNaN(Number(e.detail.value))) { e.preventDefault(); }});Copied Headers
Section titled “Copied Headers”When includeHeaders: true, copied text carries a header row so it round-trips cleanly to spreadsheets and text editors. Pasting back into cells drops that header row automatically — only values land in cells, never the header label. The header is stripped when the pasted first row consists entirely of this grid’s column labels, so it is removed even for a cross-column paste (copy one column, paste into another), while external pastes without a matching header row are left intact.
Structured Values (Same-Grid & Cross-Window Paste)
Section titled “Structured Values (Same-Grid & Cross-Window Paste)”Copy is WYSIWYG for external targets — processCell (or the column format) turns each cell into text, so pasting into a spreadsheet or editor looks right. At the same time the grid writes a structured payload: it keeps the raw cell values in memory and embeds them in a text/html representation on the clipboard.
When you paste back into a grid — the same grid, another grid, or another browser window/tab — those raw values are restored, so an object-valued cell (e.g. { id, name }, arrays) round-trips losslessly instead of degrading to its display text. The same-grid path uses the in-memory snapshot (preserving exact value types such as Date); cross-grid / cross-window uses the text/html payload. Each pasted cell is cloned, so tiling one source across many cells (fill-selection) never shares a reference, and array→cells pastes (equal or unequal counts, expansion, and clipping) behave exactly like a text paste.
For an external paste into an object column (e.g. from Excel), only text is available; the grid can’t reconstruct an arbitrary object shape from a string, so your app decides how to handle it (e.g. reject via validation, or map the text to a lookup).
Selection Bounds
Section titled “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 active range | Paste starts at the selection anchor/focused cell when available; otherwise 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.
Fill Selection
Section titled “Fill Selection”With fillSelection: true, pasting a source that is smaller than a bounded multi-cell selection tiles (repeats) the source to fill the whole selection. This is handy for applying one value across many cells without copying it repeatedly.
new ClipboardPlugin({ fillSelection: true });| Copied source | Selection | Result |
|---|---|---|
| 1 cell | 4 cells | value repeated in all 4 cells |
v1, v2 | 4 cells | v1, v2, v1, v2 |
| 2×2 block | 4×4 range | the block tiled four times |
Fill only applies to a bounded (multi-cell) selection and never grows the grid. Non-editable columns are still skipped. When fillSelection is false (the default), paste writes only the source extent.
Keyboard Shortcuts
Section titled “Keyboard Shortcuts”| Shortcut | Action |
|---|---|
Ctrl+C / Cmd+C | Copy selected cells |
Ctrl+V / Cmd+V | Paste into selected cells |
Programmatic API
Section titled “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
Section titled “Basic Copy”const plugin = grid.getPluginByName('clipboard');
// Copy current selection to clipboardawait plugin.copy();
// Copy with headers includedawait plugin.copy({ includeHeaders: true });Copy with Column/Row Control (CopyOptions)
Section titled “Copy with Column/Row Control (CopyOptions)”The copy() method accepts CopyOptions to precisely control what gets copied—independently of the current selection:
const plugin = grid.getPluginByName('clipboard');
// Copy specific columns from specific rowsawait plugin.copy({ rowIndices: [0, 3, 7], // Non-contiguous rows supported columns: ['name', 'email'], // Only these columns includeHeaders: true,});
// Copy specific rows with all visible columnsawait plugin.copyRows([0, 5]);
// Copy specific rows with column filterawait plugin.copyRows([0, 5], { columns: ['name', 'department'] });Preview Without Copying
Section titled “Preview Without Copying”Use getSelectionAsText() to get the formatted text without writing to the clipboard—useful for preview dialogs:
const text = plugin.getSelectionAsText({ columns: ['name', 'email', 'department'], includeHeaders: true,});// "Name\tEmail\tDepartment\nAlice\talice@example.com\tEngineering\n..."CopyOptions Reference
Section titled “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 from clipboardawait plugin.paste();
// Get last copied infoconst lastCopied = plugin.getLastCopied();// { text: '...', timestamp: 1234567890 }Excel Compatibility
Section titled “Excel Compatibility”The default tab delimiter ensures copied data pastes correctly into Excel:
features: { clipboard: { delimiter: '\t', // Tab for Excel includeHeaders: true, // Include column names },},Events
Section titled “Events”Select cells and use Ctrl+C / Ctrl+V to copy and paste.
| Event | Detail | Description |
|---|---|---|
copy | { text, rowCount, columnCount } | Fired after cells are copied |
paste | { rows, text, target, fields, rawRows?, sourceFields?, fillSelection? } | Fired before data is applied (default handler or custom pasteHandler can consume it) |
paste-rejected | { rejected: PasteRejectedCell[] } | Fired when one or more cells are rejected by column onPaste |
Styling
Section titled “Styling”The clipboard plugin doesn’t add visible UI elements. Selection styling is handled by the SelectionPlugin.