Grid Clipboard

Clipboard

Cmd+C copy with TSV defaults, per-column format, grid-level onCopy override.

Cmd/Ctrl+C copies the current selection as TSV. The synthetic row-select column is filtered out of the output, so a copy from a checkbox-enabled grid produces the same text as the same selection from an unchecked grid.

Default TSV format

The default serializer emits tab-separated cells, newline-separated rows, with a blank line between blocks for multi-range selections. Default cell coercion:

  • null / undefined""
  • Datevalue.toISOString()
  • string / number / boolean / bigintString(value)
  • plain object → JSON.stringify(value) (best-effort fallback)

This matches what Excel and Sheets accept on paste, so a copy out of a Pretable grid pastes cleanly into a spreadsheet.

Per-column format

For domain-specific formatting (a Date that should copy as YYYY-MM-DD instead of full ISO, a number with grouping separators, a status enum that should copy as a label), supply format on the column:

tsx
const columns: PretableColumn<Event>[] = [ { id: "timestamp", header: "Time", value: (r) => r.timestamp, format: ({ value }) => value instanceof Date ? value.toISOString().slice(0, 10) : String(value), }, // ... ];

format({ value, row, column }) is called per cell before the default coercion runs. Return a string. The same format function drives display rendering (in a follow-up phase) and copy serialization, so a single definition keeps the on-screen text and the clipboard text in sync. If you don't supply one, the default coercion above applies.

Grid-level onCopy override

For full control — a custom delimiter, a JSON payload, an HTML clipboard payload alongside the TSV — pass onCopy on the surface:

tsx
<PretableSurface ariaLabel="Inspection grid" columns={columns} rows={rows} getRowId={(row) => row.id} onCopy={({ ranges, snapshot }) => ({ text: serializeAsTsv({ ranges, snapshot }), html: serializeAsHtmlTable({ ranges, snapshot }), })} />

onCopy returns:

  • string — written to the clipboard as text/plain.
  • { text, html? } — when html is present, the surface writes both text/plain and text/html via the Clipboard API. Excel and Sheets prefer text/html when present.
  • null — skip the clipboard write entirely (suppress copy in this mode).

The serializeRangesAsTsv helper from @pretable/react is the same function the default uses; call it directly when you only want to wrap the TSV output.

copyWithHeaders

When copyWithHeaders is true, each block in the output is prefixed with a row of column headers, separated from the body by a blank line:

tsx
<PretableSurface copyWithHeaders /* ... */ />

Use this when consumers paste into a spreadsheet that needs labeled columns, or into a doc where the data wants a built-in legend. Off by default — most copies want the body only.

Multi-range serialization

A discontiguous selection (Cmd/Ctrl+click, multiple drags) serializes as one block per range, blocks separated by a blank line:

text
A1\tB1\nA2\tB2 D5\tE5\nD6\tE6

Each block is its own TSV grid; range order matches the order the ranges were added. With copyWithHeaders, the header row is repeated at the top of each block.

aria-live announcements

The surface renders an off-screen aria-live="polite" region that announces copy and select-all events for assistive technology. Defaults are English; pass a messages?: PretableSurfaceMessages prop to override:

tsx
<PretableSurface messages={{ selectAllAnnouncement: ({ rowCount, columnCount, isAll }) => isAll ? "Tutto selezionato" : `${rowCount} righe × ${columnCount} colonne selezionate`, copyAnnouncement: ({ rowCount, columnCount }) => `Copiato ${rowCount} × ${columnCount}`, copyFailedAnnouncement: () => "Copia non riuscita", }} />
EventDefault announcement
Cmd/Ctrl+A (or header-checkbox select-all)"All rows selected" (or {n} rows × {m} columns selected for partial coverage)
Cmd/Ctrl+C success{n} rows × {m} columns copied
Cmd/Ctrl+C failure (clipboard rejects)"Copy failed"

Announcements are debounced (~500ms) to avoid screen-reader thrashing on held shift+arrow extends. Programmatic mutations via state.selection do not announce — only user-triggered events do.

Paste (deferred)

Paste lands in a follow-up phase. The shape will mirror this one symmetrically:

  • parseFromCopy?(text, row): unknown per column — inverse of format.
  • onPaste?({ text, html, target }) at the surface — full override, returning the rows to apply.

Until paste ships, the browser's default paste applies (which means nothing happens in the grid).

See also

  • Selection — what gets serialized when you copy.
  • Keyboard — the Cmd/Ctrl+C binding lives here too.
  • API referenceCopyPayload, SerializeRangesArgs, PretableSurfaceMessages types.