svelte-undo Svelte Themes

Svelte Undo

Low level undo functionality for Svelte

@gira-de/svelte-undo

Provides low-level utility functions to use Svelte Stores in combination with redo/undo capabilities. Relies on immer.js.

Example App

Features

  • UndoStack contains the history of all changes
  • Undo/Redo/Goto reverts the model to a specific state
  • Transactions map multiple changes in one undo step
  • Commit Messages for every undo stack entry
  • immer.js keeps the undo stack small by using patches
  • Snapshots export/import the undo stack to/from JSON
  • Typescript support

Installation

# using npm
npm install -D @gira-de/svelte-undo

# or using pnpm
pnpm install -D @gira-de/svelte-undo

# or using yarn
yarn add -D @gira-de/svelte-undo

Usage examples

Push actions to undo stack

import { undoStack, SetAction } from '@gira-de/svelte-undo';

// create undo stack
const myUndoStack = undoStack('first stack message');
const msgStore = writable('old value');

// create a new action to update the store value
const action = new SetAction(msgStore, 'new value', 'set new value');

// apply the action
action.apply();
console.log($msgStore); // 'new value'

// push action onto the undo stack
myUndoStack.push(action);

// call undo() to revert the changes
myUndoStack.undo();
console.log($msgStore); // 'old value'

Use transactions

Creating actions and manually pushing them to the undo stack might create a lot of boilerplate code. The use of a Transaction simplifies this.

import { undoStack, transactionCtrl } from '@gira-de/svelte-undo';

// create a store as usual
const personStore = writable({ name: 'John', age: '23' });

// create undo stack and a transaction controller
const myUndoStack = undoStack('created');
const myTransactionCtrl = transactionCtrl(myUndoStack);

// apply model changes on the draft state
let personDraft = myTransactionCtrl.draft(personStore);
personDraft['age'] = 24;

// apply all changes made to the drafts
myTransactionCtrl.commit('happy birthday');
console.log($personStore); // { name: 'John', age: '24' }

// call undo() to revert the changes
myUndoStack.undo();
console.log($personStore); // { name: 'John', age: '23' }

Limitations: The transaction controller can only be used with Svelte stores that hold an Object-like value (object, array, map, set).

Save from & load to undo stack

import { undoStack, transactionCtrl } from '@gira-de/svelte-undo';

// push an undo step to the undo stack
const myUndoStack = undoStack('created');
const myTransactionCtrl = transactionCtrl(myUndoStack);
const personStore = writable({ name: 'John', age: '23' });
myTransactionCtrl.draft(personStore)['age'] = 24;
myTransactionCtrl.commit('happy birthday');

// provide a store id for each store used in the undo stack
const stores = {
  person: personStore,
};

// create a snapshot
const undoStackSnapshot = myUndoStack.createSnapshot(stores);

// snapshot can easily be stringified to json
const json = JSON.stringify(undoStackSnapshot);

console.log(json);
// {
//    index: 1,
//    actions: [
//      { type: 'init', msg: 'created' },
//      { type: 'mutate', msg: 'happy birthday', storeId: 'person', data: ... }
//    ]
// }

// later: load the undo stack snapshot
myUndoStack.loadSnapshot(JSON.parse(json), stores);

Documentation

undoStack

The undoStack is basically a Svelte store with various properties and functions. It always contains at least one undo step (action).

Instantiation examples:

const myUndoStack = undoStack('first undo stack message');

const myUndoStack = undoStack({ user: 'xyz', msg: 'project created' });

Properties

  • $myUndoStack.actions
    • a list of all actions that are currently on the undo stack
  • $myUndoStack.selectedAction
    • the current active step whose changes are applied to the model
  • $myUndoStack.canUndo
    • true if the selectedAction is not the first action on the undo stack
    • false otherwise
  • $myUndoStack.canRedo
    • true if the selectedAction is not the last action on the undo stack
    • false otherwise
  • $myUndoStack.index
    • the index of the current selectedAction
    • e.g. is 0 if canUndo is false

Functions

  • myUndoStack.push(action)
    • pushes a new action onto the undo stack and selects the action
    • does not apply the action state, this has to be done manually
    • it's recommended to use the transaction controller instead of pushing actions manually
  • myUndoStack.undo()
    • reverts the state of the selected action and selects the previous action
    • does nothing if there's no previous action on the undo stack
  • myUndoStack.redo()
    • selects the next action and applies its state
    • does nothing if there's no next action on the undo stack
  • myUndoStack.goto(seqNbr)
    • selects the action for the specified sequence number (action.seqNbr) and applies/reverts all actions between
    • has basically the same effect as calling undo/redo in a loop
  • myUndoStack.clear()
    • removes all actions from the undo stack and creates a new init action
    • has the same effect as if a new undo stack has been created
  • myUndoStack.clearRedo()
    • Removes all redoable actions from the stack
    • This is the same as would happen if a new action is pushed while not being at the latest state, just without pushing the action
  • myUndoStack.clearUndo()
    • Deletes all previous undo actions
    • If the current position is the top of the stack, this effectively deletes the whole undo stack
    • If not at the top of the stack, undo actions are still available and undo is possible
  • myUndoStack.createSnapshot(stores)
    • creates and returns a snapshot of the undo stack
    • the snapshot can be easily serialized since it does not contain any store references etc.
  • myUndoStack.loadSnapshot(undoStackSnapshot, stores)
    • clears the undo stack and then loads the specified snapshot
  • myUndoStack.erase(seqNbr?)
    • removes undo/redo capability from actions to reduce the size of the undo stack
    • all actions starting from the specified sequence number are erased
    • starts erasing from the top of the stack if seqNbr is undefined
    • erased actions are still included on the undo stack in form of log entries
    • this function is currently experimental and might not always yield the expected state

transactionCtrl

Instantiation

const myTransactionCtrl = transactionCtrl(myUndoStack);

Functions

  • myTransactionCtrl.draft(store)
    • returns a new draft object for the specified store
    • all changes to the draft object must be followed by a commit, otherwise they will not visible in the store
  • myTransactionCtrl.commit(msg)
    • applies all draft changes to the corresponding stores
    • calling commit without having any draft changes has no effect
  • myTransactionCtrl.rollback()
    • discards all draft changes since the last commit
    • calling commit after a rollback has no effect

Known issues

  • Working with arrays (pushing/removing items) might create very large undo stack entries.

Contributing

Contributions are always welcome! Please have a look at our CONTRIBUTING.md

Top categories

Loading Svelte Themes