Skip to content

Copying grid data

View as Markdown

Every framework table grid, TGrid, and report grid has a right-click copy menu. Standalone BaseGrid surfaces get the same menu when they wrap the rendered levels in GridCopyContextMenu.

The menu has two commands:

  • Copy copies the active cell or selected cell range as CSV text.
  • Copy with headers adds a header row before the CSV values.

The selection is the cell-grid selection. A single active cell copies one cell. A range copies the rectangular range between the selection anchor and head. Row selection is separate and stays available for row operations such as delete, export, bulk edit, or opening a side panel.

A plain grid column contributes one clipboard column. The value comes from the row data at the column id, and Copy with headers uses that same column id as the header.

const accountColumn: ColumnSchema = {
id: "account",
name: "Account",
renderCell: ({ value }) => String(value ?? ""),
};

Copying that column with headers produces:

account
Cash

The display name is still only a grid label. Clipboard headers are data headers, so they should stay stable when the visible label changes.

Some visible columns show a label but store another value. select, foreignKey, and lookupValue presets all have this shape. Spreadsheet users usually need the label. Integration and reconciliation workflows often need the stored value.

For those presets, one selected visible column contributes two clipboard columns by default: the stored value and the label.

const accountColumn = columnPreset.foreignKey({
id: "account_id",
name: "Account",
valueLookup: accountLookup,
searchLookup: accountSearch,
});

Copying one selected account_id cell with headers produces:

account_id,account_id_label
acct_123,Cash

The same rule applies to select columns:

const statusColumn = columnPreset.select({
id: "status",
name: "Status",
options: [
{ value: "open", label: "Open" },
{ value: "closed", label: "Closed" },
],
});
status,status_label
open,Open

The raw output column reads the stored value. The label output column uses the preset’s label source, with the preset value formatter as the fallback when a lookup label is not available.

Start with the default behavior. It gives stable data headers for plain columns and raw-plus-label output for labeled values.

Customize column copy behavior when the rendered cell is not the clipboard contract you need. Common cases are unit conversion, combining several row fields behind one visible column, copying an integration code instead of a badge, or omitting the label column for a workflow that only accepts raw ids.

ColumnPreset helpers also accept copy. This is useful when you want preset display and editing behavior, but a different clipboard value.

const statusColumn = columnPreset.select({
id: "status",
name: "Status",
options: [
{ value: "open", label: "Open" },
{ value: "closed", label: "Closed" },
],
copy: () => [
{
header: "workflow_status",
valueAt: (row) => row.columns.status,
},
],
});

For select, foreignKey, and lookupValue, a supplied copy replaces the raw-plus-label default for that column.

BaseGrid columns use ColumnSchema.copy directly.

const personColumn: ColumnSchema = {
id: "person_id",
name: "Person",
renderCell: ({ row }) => String(row.columns.person_name ?? ""),
copy: () => [
{
header: "person_id",
valueAt: (row) => row.columns.person_id,
},
{
header: "person_name",
valueAt: (row) => row.columns.person_name,
},
],
};

Standalone BaseGrid screens also need the context menu wrapper:

<GridRuntimeProvider runtime={runtime}>
<GridCopyContextMenu>
<GridLevel path={runtime.root.path} />
</GridCopyContextMenu>
</GridRuntimeProvider>

Without a copy function, BaseGrid falls back to one clipboard column using column.id and row.columns[column.id].

Copy omits the header row. Copy with headers writes the headers returned by the selected columns’ copy behavior.

Headers should be stable data names, not display labels. The default header is the grid column id. Labeled-value presets use <column_id>_label for the label column. If two contributed clipboard columns use the same header, the grid keeps the first one and appends suffixes such as _2 and _3 to later duplicates.

Column copy behavior may be async. The grid waits for each selected column’s copy behavior before it materializes the CSV rows, which lets lookup-backed columns load labels before writing to the clipboard.

Typecheck the example and exercise its visible loading, ready, interaction, and failure states. Use only public @sapporta/grid export paths. Continue with Grid Reference.