Undo and redo
Ctrl+Z undoes what was done to a table in the order it was done: cell edits, records added or deleted, fields added, changed or deleted. Undoing a deleted task puts it back in its place and its project lists it again. The arrows in the tab bar say what the next undo and redo will do, and each table keeps its history while you look at another. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example from the SvGrid gallery (Base). See the SvGrid documentation for the full API.
About this example
Undo and redo in an SvGrid Base, in Svelte 5. Ctrl+Z undoes cell edits, records added or deleted and fields added, changed or deleted, in the order they were made, and Ctrl+Shift+Z or Ctrl+Y redoes them. A deleted record comes back in its place with its links, and the tab bar's arrows say what the next undo will do.
Ctrl+Z undoes what was done to a table, in the order it was done: a cell edit, a paste, a record added or deleted, a field added, changed or deleted. Ctrl+Shift+Z or Ctrl+Y redoes it. The Undo and Redo buttons at the end of the tab bar say what the next press will do ("Undo delete 2 records").
Undoing a deleted record puts it back where it was and brings its links back: delete a task and its project stops listing it; undo, and the project lists it again. Each table keeps its own history while you look at another one.
Imports, features and API used
Imports: @svgrid/grid, @svgrid/enterprise/base, ../shared/base-people
Columns: task (Task), project (Project), status (Status), owner (Owner), hours (Estimate (h)), status, name (Project), lead (Lead), tasks (Tasks), hours (Hours), hours
Frequently asked questions
What can be undone?
Cell edits, pastes and fills, records added or deleted, records imported from a file, and fields added, changed or deleted. View settings such as a filter or a sort are not part of the history; a view keeps them as you leave them.
Does undoing a delete bring back the links?
Yes. Deleting a task removes it from its project's list of tasks; undo puts the task back where it was and the project lists it again, because the step restores the records of every table the delete touched.
What happens to the history when I switch tables?
Each table keeps its own history while another table is shown. Go back to the table and Ctrl+Z carries on from where you left it.
Which license does it need?
SvGrid Base is part of the Suite edition of @svgrid/enterprise. Without a key it runs with a watermark, so it can be evaluated as is.
Related documentation
Related articles
- A Svelte Data Grid with SvelteKit and Supabase - Wire SvGrid to a Supabase Postgres backend with server-side pagination, sorting, and filtering - keeping credentials on the server and queries fast with proper indexing.
- A Live Svelte Data Grid with Firebase Firestore - Wire Firestore onSnapshot to SvGrid in Svelte 5 - reactive listeners, query-driven sort and filter, cursor pagination, and the subtle cleanup detail most implementations get wrong.
- A Svelte Data Grid with PocketBase - Wire SvGrid to PocketBase for server-side paging, sorting, and filtering via getList, plus live row updates through PocketBase realtime subscriptions.
Source code (705-base-undo-redo.svelte)
<script lang="ts">
/**
* 705. SvGrid Base: undo and redo
* -------------------------------
* Ctrl+Z undoes what was done to a table, in the order it was done: a cell
* edit, a paste, a record added or deleted, a field added, changed or
* deleted. Ctrl+Shift+Z or Ctrl+Y redoes it. The Undo and Redo buttons at
* the end of the tab bar say what the next press will do ("Undo delete 2
* records").
*
* Undoing a deleted record puts it back where it was and brings its links
* back: delete a task and its project stops listing it; undo, and the
* project lists it again. Each table keeps its own history while you look
* at another one.
*/
import { SvToaster } from '@svgrid/grid'
import { SvBase, enableBase, type BaseTable } from '@svgrid/enterprise/base'
import { usePeople } from '../shared/base-people'
enableBase()
usePeople()
const STATUS = [
{ value: 'Todo', color: '#94a3b8' },
{ value: 'In progress', color: '#2d7ff9' },
{ value: 'Blocked', color: '#f82b60' },
{ value: 'Done', color: '#20c933' },
]
const TASKS: Array<[string, string, string, string, number]> = [
['Write the launch brief', 'p1', 'Done', 'u1', 3],
['Design the pricing page', 'p1', 'In progress', 'u3', 8],
['Record the product video', 'p1', 'Todo', 'u5', 6],
['Migrate invoices to the new schema', 'p2', 'Blocked', 'u2', 13],
['Backfill customer ids', 'p2', 'In progress', 'u4', 5],
['Load test the export job', 'p2', 'Todo', 'u2', 4],
['Interview three design partners', 'p3', 'Done', 'u6', 6],
['Summarize the interviews', 'p3', 'Todo', 'u6', 2],
]
const tasks = TASKS.map(([task, project, status, owner, hours], i) => ({ id: `t${i + 1}`, task, project: [project], status, owner, hours }))
const projects = [
{ id: 'p1', name: 'Website launch', lead: 'u1' },
{ id: 'p2', name: 'Billing migration', lead: 'u2' },
{ id: 'p3', name: 'Customer research', lead: 'u6' },
].map((p) => ({ ...p, tasks: tasks.filter((t) => t.project[0] === p.id).map((t) => t.id) }))
let tables = $state.raw<BaseTable[]>([
{
id: 'undo-tasks',
name: 'Tasks',
primaryField: 'task',
data: tasks,
columns: [
{ field: 'task', header: 'Task', width: 280, fieldType: 'text' },
{ field: 'project', header: 'Project', width: 190, fieldType: 'link', fieldProps: { table: 'undo-projects', multiple: false, inverse: 'tasks' } },
{ field: 'status', header: 'Status', width: 130, fieldType: 'singleSelect', fieldProps: { options: STATUS } },
{ field: 'owner', header: 'Owner', width: 160, fieldType: 'user' },
{ field: 'hours', header: 'Estimate (h)', width: 120, fieldType: 'number', fieldProps: { format: 'integer' } },
],
views: [{ id: 'all', name: 'All tasks', type: 'grid', color: { mode: 'field', field: 'status' } }],
props: { rowAdder: { label: 'Add task', defaults: () => ({ status: 'Todo' }) } },
},
{
id: 'undo-projects',
name: 'Projects',
primaryField: 'name',
data: projects,
columns: [
{ field: 'name', header: 'Project', width: 220, fieldType: 'text' },
{ field: 'lead', header: 'Lead', width: 160, fieldType: 'user' },
{ field: 'tasks', header: 'Tasks', width: 360, fieldType: 'link', fieldProps: { table: 'undo-tasks', multiple: true, inverse: 'project' } },
{ field: 'hours', header: 'Hours', width: 110, fieldType: 'rollup', fieldProps: { linkField: 'tasks', field: 'hours', aggregation: 'SUM' } },
],
views: [{ id: 'all', name: 'All projects', type: 'grid' }],
},
])
</script>
<section class="base-demo">
<p class="base-demo-tip">
Edit a few cells, delete a task (right-click a row, Delete record), delete a field from its header menu, then press
<kbd>Ctrl</kbd>+<kbd>Z</kbd> until everything is back. The arrows at the top right show what the next undo and redo will do.
</p>
<SvBase
bind:tables
editable
sortable
showRowNumbers
expandRows
rowNumberWidth={64}
showRowSelection={false}
rowHeight={40}
containerHeight="100%"
selectionBar={['selectAll', 'editFields', 'deleteRows']}
columnAdder
fieldMenu
/>
<SvToaster />
</section>
<style>
.base-demo { display: flex; flex-direction: column; flex: 1; min-height: 480px; height: 100%; gap: 10px; }
.base-demo-tip { margin: 0; font-size: 13px; line-height: 1.5; color: var(--sg-muted, #64748b); }
.base-demo-tip kbd {
padding: 1px 5px; font: inherit; font-size: 12px; border-radius: 4px;
border: 1px solid var(--sg-border, #e2e8f0); background: color-mix(in srgb, var(--sg-fg, #0f172a) 7%, transparent); color: var(--sg-fg, #0f172a);
}
</style>More Base examples
- Project tracker - A project tracker, ready to use: Tasks and Milestones as two linked tables. Tasks have status, tags, assignee, dates, estimate, progress, budget and files; each milestone lists its tasks with their progress and budget. Views for all tasks, the board by status, due dates on a calendar, a gallery and a request form. Automations close finished tasks, start work from a button and flag overdue ones.
- Content calendar - An editorial content calendar, ready to use: Posts and Campaigns as two linked tables. Posts have stage, channels, author, publish date, cover and brief. Views for the plan, the pipeline as a Kanban, the publishing calendar, a wall of covers and a pitch form.
- Sales CRM - A sales CRM, ready to use: Deals, Accounts and Contacts as three linked tables. Link a deal to an account and the account lists it. Views for all deals, the pipeline as a Kanban, the closing calendar, open deals by owner and a new-lead form.
- Product roadmap - A product roadmap, ready to use: Features and customer Feedback as two linked tables. Features have quarter, status, teams, lead, impact, effort and votes. Views for the roadmap grouped by quarter, a status board, a timeline, the work in progress now and a request form.
- Hiring tracker - A hiring tracker, ready to use: Candidates and Roles as two linked tables. Candidates have photo, role, stage, recruiter, rating, interview date and resume; each role lists its candidates. Views for the pipeline as a Kanban, all candidates, a gallery of faces, interviews and an application form.