@jucie.io/state-history
v1.0.14
Published
History management plugin for @jucie.io/state with undo/redo and change tracking
Maintainers
Readme
@jucio.io/state/history
History management plugin for @jucio.io/state that provides undo/redo functionality with markers, grouping, and commit listeners.
Features
- ⏪ Undo/Redo: Full undo and redo support with automatic change tracking
- 🏷️ Markers: Add descriptive markers to create logical undo/redo boundaries
- 📦 Grouping: Group multiple changes into a single undo/redo step (can span multiple calls; commit when done)
- 🔔 Commit Listeners: React to history commits
- 🎯 Smart Change Consolidation: Automatically merges changes to the same path
- 📏 Configurable History Size: Limit the number of stored changes
Installation
npm install @jucio.io/stateNote: History plugin is included in the main package.
Quick Start
import { createState } from '@jucio.io/state';
import { HistoryManager } from '@jucio.io/state/history';
// Create state and install history plugin
const state = createState({
data: { count: 0 }
});
state.install(HistoryManager);
// Make some changes
state.set(['data', 'count'], 1);
state.set(['data', 'count'], 2);
state.set(['data', 'count'], 3);
console.log(state.get(['data', 'count'])); // 3
// Undo the changes
state.history.undo();
console.log(state.get(['data', 'count'])); // 2
state.history.undo();
console.log(state.get(['data', 'count'])); // 1
// Redo
state.history.redo();
console.log(state.get(['data', 'count'])); // 2
// Check if undo/redo is available
console.log(state.history.canUndo()); // true
console.log(state.history.canRedo()); // trueAPI Reference
Actions
undo(callback?)
Undo the last committed change(s).
state.history.undo(() => {
console.log('Undo completed');
});Returns: boolean - true if undo was successful, false if no changes to undo
redo(callback?)
Redo the next change(s).
state.history.redo(() => {
console.log('Redo completed');
});Returns: boolean - true if redo was successful, false if no changes to redo
canUndo()
Check if undo is available.
if (state.history.canUndo()) {
state.history.undo();
}Returns: boolean
canRedo()
Check if redo is available.
if (state.history.canRedo()) {
state.history.redo();
}Returns: boolean
Grouping
group(callback?)
Start grouping changes into a single undo/redo step. Grouping can span multiple calls; call commit() (or the returned function) when done. Pass a function to run in grouping mode and commit when it returns (quick single-block use).
// Span multiple calls
state.history.group();
state.set(['user', 'name'], 'Alice');
state.set(['user', 'age'], 30);
state.history.commit(); // All changes are now a single undo/redo step
// Or use the returned function
const endGroup = state.history.group();
state.set(['user', 'email'], '[email protected]');
endGroup();
// Quick single-block: pass a callback
state.history.group(() => {
state.set(['user', 'name'], 'Alice');
state.set(['user', 'age'], 30);
});
state.history.undo(); // Undoes both changes at onceReturns: HistoryManager (for chaining) if a callback was provided; otherwise Function to call when done
commit(description?)
Manually commit pending changes and end the current group.
state.history.group();
state.set(['data', 'value'], 1);
state.history.commit('Initial value'); // Commits and ends groupingReturns: HistoryManager instance (for chaining)
Markers
addMarker(description)
Add a descriptive marker to create a logical undo/redo boundary.
state.set(['user', 'name'], 'Alice');
state.history.addMarker('Set user name');
state.set(['user', 'age'], 30);
state.history.addMarker('Set user age');
// Now each undo will stop at the marker
state.history.undo(); // Undoes age change
state.history.undo(); // Undoes name changeParameters:
description(string): Optional description for the marker
Commit Listeners
onCommit(callback)
Listen for history commits.
const unsubscribe = state.history.onCommit((changes) => {
console.log('Changes committed:', changes);
});
state.set(['data', 'value'], 1);
// Console: "Changes committed: [...]"
// Remove listener when done
unsubscribe();Parameters:
callback(Function): Called with an array of changes when committed
Returns: Function - Call to remove the listener
Info
size()
Get the current number of items in the history.
const historySize = state.history.size();
console.log(`History contains ${historySize} items`);Returns: number
Configuration
Configure the history plugin using the configure() method:
import { createState } from '@jucio.io/state';
import { HistoryManager } from '@jucio.io/state/history';
const state = createState({
data: { count: 0 }
});
// Install with custom configuration
state.install(HistoryManager.configure({
maxSize: 200 // Limit to 200 history items (default: 100)
}));Options
maxSize(number): Maximum number of history items to keep. Default:100
Advanced Usage
Grouping with Markers
// Start a complex operation (grouping can span multiple calls)
state.history.group();
state.history.addMarker('Start user registration');
state.set(['user', 'name'], 'Alice');
state.set(['user', 'email'], '[email protected]');
state.set(['user', 'preferences'], { theme: 'dark' });
state.history.commit();
// All changes are now a single undo step with a descriptive markerPause and Resume Recording
// Temporarily pause history recording (internal API)
state.plugins.history.pause();
state.set(['temp', 'data'], 'not recorded');
state.plugins.history.resume();
state.set(['tracked', 'data'], 'recorded'); // This will be recordedReset History
// Clear all history (internal API)
state.plugins.history.reset();How It Works
- Change Tracking: The plugin automatically tracks all state changes
- Consolidation: Multiple changes to the same path are consolidated
- Deferred Commits: Changes are committed asynchronously for performance
- Markers: Markers create logical boundaries for undo/redo operations
- Inversion: Changes are inverted for undo operations
Common Patterns
Form Editing with Undo/Redo
// Track form edits
function handleFieldChange(field, value) {
state.history.group();
state.set(['form', field], value);
state.history.addMarker(`Update ${field}`);
state.history.commit();
}
// Implement undo/redo buttons
function handleUndo() {
if (state.history.canUndo()) {
state.history.undo(() => {
updateUI();
});
}
}Multi-Step Operations
function performComplexOperation() {
state.history.group();
// Step 1: Update user
state.set(['user', 'status'], 'processing');
// Step 2: Create records
state.set(['records'], [{ id: 1, status: 'new' }]);
// Step 3: Update timestamp
state.set(['lastUpdate'], Date.now());
state.history.commit();
state.history.addMarker('Complex operation completed');
}
// All steps undo/redo as one operationLicense
See the root LICENSE file for license information.
Related Packages
- @jucio.io/state - Core state management system
- @jucio.io/state/matcher - Path pattern matching
- @jucio.io/state/on-change - Change listeners
