Appearance
Context menu
contextMenu: true shows a menu with clipboard, undo / redo and export commands when a cell is right clicked. The menu can be extended with your own items or replaced completely.
vue
<template>
<Datagrid :columns="columns" :rows="rows" :options="options" @update:rows="rows = $event" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
const rows = ref(createEmployees(200));
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70, editable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
selection: "Cell",
singleSelect: false,
contextMenu: true,
defaultColumn: { editable: true, flex: 1 },
};
</script>tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
const columns: Array<ColumnConfig<Employee>> = [
{ field: "id", text: "ID", type: "number", width: 70, editable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Employee> = {
rowId: (data) => data.id,
selection: "Cell",
singleSelect: false,
contextMenu: true,
defaultColumn: { editable: true, flex: 1 },
};
export default function ContextMenuDefault() {
const [rows, setRows] = useState(() => createEmployees(200));
return <Datagrid columns={columns} rows={rows} options={options} onRowsChange={setRows} />;
}Behaviour
- A right click on a cell that is not part of the selection selects the cell and focuses it, like in a spreadsheet. A right click inside the selected range keeps the selection, so the commands work on the whole range.
- The menu is closed with Escape, a click outside, scrolling or after an item was clicked.
- There is no grid menu for pinned rows, the total row and the header (for the header see the column menu).
- Items which can't be used are disabled: cut, paste and clear if no selected cell is editable, undo / redo if there is nothing to undo / redo, paste if the browser does not allow reading the clipboard.
Default items
With contextMenu: true the menu contains depending on the options:
| Items | Shown when |
|---|---|
copy, copyWithHeaders, cut, paste | clipboard: true (default) |
undo, redo | undoRedo: true (default) |
expandAll, collapseAll | rows are grouped or tree data is shown |
exportCsv | always |
api.getDefaultContextMenuItems() returns this list for the current options.
Built-in items
| Id | Action |
|---|---|
copy | copy the selected cells (Ctrl + C) |
copyWithHeaders | copy the selected cells including the column headers |
cut | copy and clear the selected cells (Ctrl + X) |
paste | paste the clipboard at the focused cell (Ctrl + V) |
clear | clear the selected cells (Delete) |
undo, redo | undo / redo the last change (Ctrl + Z / Y) |
exportCsv | download the rows as CSV (export) |
autosizeColumns | fit all columns to their content |
resetColumns | restore the initial column widths, order, pinning and visibility |
expandAll, collapseAll | expand / collapse all groups or tree rows |
separator | a line between items |
Separators at the beginning, at the end and directly after another separator are removed, so it is safe to combine lists. The labels are localized (keys menu_copy, menu_cut, …).
Custom items
contextMenu can be
- an array of built-in ids and custom items – the menu is the same for all cells
- a function
(params, defaultItems) => items– called every time the menu opens, e.g. to add items to the defaults or to show items depending on the clicked cell
A custom item is an object:
| Property | Description |
|---|---|
label | text of the item |
action(params) | called when the item is clicked |
icon | optional: an emoji / text, or css classes of a grid icon, e.g. "ut-icon filter-icon" |
shortcut | optional: hint shown on the right side (only displayed, it doesn't register a key) |
disabled | optional: shows the item disabled |
params is { cell, api }: cell is the right clicked cell { item, column } (the row is cell.item, its data object cell.item.data), api the grid api.
ts
contextMenu: [
"copy",
"paste",
"separator",
{ label: "Open", icon: "📂", action: ({ cell }) => open(cell!.item.data) },
],vue
<template>
<div class="demo">
<Datagrid :columns="columns" :rows="rows" :options="options" @update:rows="rows = $event" />
<div class="demo-log">{{ log }}</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, getText, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";
type Person = Employee & { highlighted?: boolean };
const rows = ref<Array<Person>>(createEmployees(200));
const log = ref("Right click a cell");
function toggleHighlight(person: Person) {
rows.value = rows.value.map((row) => (row.id === person.id ? { ...row, highlighted: !row.highlighted } : row));
}
function deleteRow(person: Person) {
rows.value = rows.value.filter((row) => row.id !== person.id);
log.value = `Deleted ${person.firstName} ${person.lastName}`;
}
const columns: Array<ColumnConfig<Person>> = [
{ field: "id", text: "ID", type: "number", width: 70, editable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
const options: GridOptions<Person> = {
rowId: (data) => data.id,
rowClass: (row) => (row.data?.highlighted ? "row-highlighted" : undefined),
selection: "Cell",
singleSelect: false,
defaultColumn: { editable: true, flex: 1 },
// called whenever the menu is opened
contextMenu: ({ cell }, defaultItems) => {
const person = cell?.item.data;
return [
{
label: person?.highlighted ? "Remove highlight" : "Highlight row",
icon: "★",
disabled: !person,
action: () => toggleHighlight(person!),
},
{
label: "Delete row",
icon: "ut-icon remove-icon",
disabled: !person,
action: () => deleteRow(person!),
},
{
label: "Show cell value",
disabled: !cell,
action: ({ cell }) => (log.value = `${cell!.column.text}: ${getText(cell!.column, cell!.item)}`),
},
"separator",
...defaultItems, // copy, cut, paste, undo, redo, export
"separator",
"clear",
"autosizeColumns",
];
},
};
</script>
<style>
.row-highlighted .ut-cell {
background-color: rgba(250, 204, 21, 0.25);
}
</style>tsx
import { useMemo, useState } from "react";
import { Datagrid, getText, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";
type Person = Employee & { highlighted?: boolean };
const columns: Array<ColumnConfig<Person>> = [
{ field: "id", text: "ID", type: "number", width: 70, editable: false },
{ field: "firstName", text: "First name" },
{ field: "lastName", text: "Last name" },
{ field: "department", text: "Department" },
{ field: "salary", text: "Salary", type: "number" },
];
export default function ContextMenuCustom() {
const [rows, setRows] = useState<Array<Person>>(() => createEmployees(200));
const [log, setLog] = useState("Right click a cell");
const options = useMemo((): GridOptions<Person> => {
function toggleHighlight(person: Person) {
setRows((rows) => rows.map((row) => (row.id === person.id ? { ...row, highlighted: !row.highlighted } : row)));
}
function deleteRow(person: Person) {
setRows((rows) => rows.filter((row) => row.id !== person.id));
setLog(`Deleted ${person.firstName} ${person.lastName}`);
}
return {
rowId: (data) => data.id,
rowClass: (row) => (row.data?.highlighted ? "row-highlighted" : undefined),
selection: "Cell",
singleSelect: false,
defaultColumn: { editable: true, flex: 1 },
// called whenever the menu is opened
contextMenu: ({ cell }, defaultItems) => {
const person = cell?.item.data;
return [
{
label: person?.highlighted ? "Remove highlight" : "Highlight row",
icon: "★",
disabled: !person,
action: () => toggleHighlight(person!),
},
{
label: "Delete row",
icon: "ut-icon remove-icon",
disabled: !person,
action: () => deleteRow(person!),
},
{
label: "Show cell value",
disabled: !cell,
action: ({ cell }) => setLog(`${cell!.column.text}: ${getText(cell!.column, cell!.item)}`),
},
"separator",
...defaultItems, // copy, cut, paste, undo, redo, export
"separator",
"clear",
"autosizeColumns",
];
},
};
}, []);
return (
<div className="demo">
<Datagrid columns={columns} rows={rows} options={options} onRowsChange={setRows} />
<div className="demo-log">{log}</div>
<style>{`
.row-highlighted .ut-cell {
background-color: rgba(250, 204, 21, 0.25);
}
`}</style>
</div>
);
}Your own menu
The cell-contextmenu event is emitted before the grid menu opens. Call event.preventDefault() in the handler to show your own menu component instead (the grid menu and the browser menu are not shown). See the example in Events & state.
vue
<Datagrid @cell-contextmenu="({ event, row, column }) => { event.preventDefault(); openMyMenu(event, row); }" ... />tsx
<Datagrid
onCellContextMenu={({ event, row }) => {
event.preventDefault();
openMyMenu(event, row);
}}
/>The event is emitted with and without the contextMenu option. Without the option, the right click neither selects the cell nor prevents the browser menu.
API
ts
api.getDefaultContextMenuItems(); // ids of the default menu for the current options
api.getContextMenuItems(cell); // resolved items for a cell: { id, label, icon, shortcut, disabled, action } or { separator: true }getContextMenuItems can be used to render the grid menu items in your own menu component.
Styling
| Class | Element |
|---|---|
.ut-context-menu | the popup |
.ut-menu__item | an item (a button, :disabled when disabled) |
.ut-menu__icon, .ut-menu__label, .ut-menu__shortcut | parts of an item |
.ut-menu__separator | separator |