Skip to content

Clipboard ​

The grid supports copying and pasting cell ranges with the keyboard. The format is tab separated text, which is compatible with Excel, LibreOffice and Google Sheets.

Select a cell range in the demo and copy it with Ctrl / ⌘ + C, then paste it into the text area below the grid or into a spreadsheet. The other way round, copy the lines of the text area (or cells from Excel), focus a cell and press Ctrl / ⌘ + V. All cells are editable, so pasting and cutting change the data. Ctrl / ⌘ + Z undoes the change.

Source
vue
<template>
  <div class="demo">
    <div class="demo-toolbar">
      <label><input type="checkbox" v-model="copyHeaders" /> Copy headers</label>
      <button @click="api?.undo()">Undo</button>
      <span>Select cells and press Ctrl / ⌘ + C, X or V</span>
    </div>
    <Datagrid
      :columns="columns"
      v-model:rows="rows"
      :options="options"
      @ready="api = $event"
      @cell-value-changed="onChanged"
    />
    <textarea
      v-model="text"
      aria-label="Clipboard text"
      rows="4"
      spellcheck="false"
      style="width: 100%; font-family: var(--vp-font-family-mono); font-size: 12px; padding: 6px 8px; border: 1px solid var(--vp-c-divider); border-radius: 6px"
    ></textarea>
    <div class="demo-toolbar">
      <button @click="api?.pasteText(text)">Paste text at the focused cell</button>
      <span>{{ status }}</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, shallowRef } from "vue";
import {
  Datagrid,
  type CellValueChangedEvent,
  type ColumnConfig,
  type Core,
  type GridOptions,
} from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";

const rows = ref(createEmployees(50));
const api = shallowRef<Core>();
const copyHeaders = ref(false);
const status = ref("");

// tab separated text, like Excel puts it into the clipboard
const exampleText = [
  "Max\tMustermann\tSales\tBerlin\t55000",
  "Erika\tMusterfrau\tHR\tMunich\t61000",
  "John\tDoe\tSupport\tVienna\t48000",
].join("\n");
const text = ref(exampleText);

const columns: Array<ColumnConfig<Employee>> = [
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name" },
  { field: "department", text: "Department" },
  { field: "city", text: "City" },
  { field: "salary", text: "Salary", type: "number" },
  { field: "startDate", text: "Start date", type: "date" },
];

const options = computed<GridOptions<Employee>>(() => ({
  selection: "Cell",
  singleSelect: false,
  clipboard: true,
  copyHeaders: copyHeaders.value,
  undoRedo: true,
  defaultColumn: { editable: true, resizeable: true, flex: 1 },
}));

function onChanged(e: CellValueChangedEvent<Employee>) {
  status.value = `${e.source}: ${e.column.id} ${JSON.stringify(e.oldValue)} → ${JSON.stringify(e.newValue)}`;
}
</script>
tsx
import { useMemo, useState } from "react";
import {
  Datagrid,
  type CellValueChangedEvent,
  type ColumnConfig,
  type Core,
  type GridOptions,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";

// tab separated text, like Excel puts it into the clipboard
const exampleText = [
  "Max\tMustermann\tSales\tBerlin\t55000",
  "Erika\tMusterfrau\tHR\tMunich\t61000",
  "John\tDoe\tSupport\tVienna\t48000",
].join("\n");

const columns: Array<ColumnConfig<Employee>> = [
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name" },
  { field: "department", text: "Department" },
  { field: "city", text: "City" },
  { field: "salary", text: "Salary", type: "number" },
  { field: "startDate", text: "Start date", type: "date" },
];

const textareaStyle = {
  width: "100%",
  fontFamily: "var(--vp-font-family-mono)",
  fontSize: "12px",
  padding: "6px 8px",
  border: "1px solid var(--vp-c-divider)",
  borderRadius: "6px",
};

export default function Clipboard() {
  const [rows, setRows] = useState(() => createEmployees(50));
  const [api, setApi] = useState<Core>();
  const [copyHeaders, setCopyHeaders] = useState(false);
  const [text, setText] = useState(exampleText);
  const [status, setStatus] = useState("");

  const options = useMemo<GridOptions<Employee>>(
    () => ({
      selection: "Cell",
      singleSelect: false,
      clipboard: true,
      copyHeaders,
      undoRedo: true,
      defaultColumn: { editable: true, resizeable: true, flex: 1 },
    }),
    [copyHeaders]
  );

  function onChanged(e: CellValueChangedEvent<Employee>) {
    setStatus(`${e.source}: ${e.column.id} ${JSON.stringify(e.oldValue)} → ${JSON.stringify(e.newValue)}`);
  }

  return (
    <div className="demo">
      <div className="demo-toolbar">
        <label>
          <input type="checkbox" checked={copyHeaders} onChange={(e) => setCopyHeaders(e.target.checked)} /> Copy
          headers
        </label>
        <button onClick={() => api?.undo()}>Undo</button>
        <span>Select cells and press Ctrl / ⌘ + C, X or V</span>
      </div>
      <Datagrid
        columns={columns}
        rows={rows}
        onRowsChange={setRows}
        options={options}
        onReady={setApi}
        onCellValueChanged={onChanged}
      />
      <textarea
        value={text}
        onChange={(e) => setText(e.target.value)}
        aria-label="Clipboard text"
        rows={4}
        spellCheck={false}
        style={textareaStyle}
      />
      <div className="demo-toolbar">
        <button onClick={() => api?.pasteText(text)}>Paste text at the focused cell</button>
        <span>{status}</span>
      </div>
    </div>
  );
}

Copy ​

Ctrl / ⌘ + C copies

  • the selected rows (all visible columns) with row selection
  • the selected cell range with cell selection
  • the focused cell otherwise

Raw values are copied (numbers without formatting, dates as ISO strings), so they can be pasted back. copyHeaders: true includes the column headers.

Paste ​

Ctrl / ⌘ + V pastes the clipboard content starting at the focused cell. Rows and columns are filled to the right and down. If a single value is pasted while a cell range is selected, the value is pasted into all selected cells.

Only editable cells are changed. Values are converted with the valueParser of the column, and the change event is emitted with source: "paste" (@cell-value-changed in Vue, onCellValueChanged in React). A paste can be undone with Ctrl / ⌘ + Z (see undo and redo).

Cut ​

Ctrl / ⌘ + X copies the selection like Ctrl + C and clears the editable cells afterwards (the change event has source: "cut").

Options ​

OptionDefaultDescription
clipboardtrueenables copy and paste
copyHeadersfalseinclude the headers when copying

API ​

ts
await api.copySelection();       // copies and returns the text
api.getSelectionAsText();        // text without copying
api.pasteText("a\tb\nc\td");     // paste at the focused cell
await api.cutSelection();        // copies and clears the editable cells

Released under the ISC License.