This package has been deprecated

Author message:

Deprecated. Please instead use https://www.npmjs.com/package/@kvndy/undo-manager

@kvndy/history
TypeScript icon, indicating that this package has built-in type declarations

4.0.4 • Public • Published

History

Deprecated. Please instead use https://gitlab.com/kevindoughty/undo-manager

This package provides undo and redo management using Preact Signals. Its innovation is in having effectively two separate data models. Changes to one class, the Undoable, automatically register with an undo stack. Changes to a second class, the Preservable, do not themselves result in a new addition to the undo stack, but as their name would suggest are preserved when a change to an Undoable is made.

Undoables are meant to provide navigation of user data through history in a document-based architecture for web-based apps. Preservables are meant to restore presentational state and visual appearance. In other words, they take the user to where they were when they made the change. The canonical example is preserving expanded/collapsed state of a tree view with disclosure triangles.

An aspect of Preservables which may not be immediately intuitive is their value as restored by navigating the undo stack depends on the direction traveled. The value of a Preservable when reaching a certain state of an Undoable via undo may be different when reaching the very same state of an Undoable via redo. This is accomplished by capturing the value of all Preservables both before and after a change to an Undoable.

When undoing, the user typically wants appearances to be as they were right before they made a change. When redoing, the user typically wants appearances to be as they were right after a change. If two consecutive undos drastically change the layout of an app making a change difficult to spot, when immediately followed by a redo appearances would align with user expectation.

If presentational state were captured only after a change to an Undoable, a single undo might drastically change appearances, but a redo after that would drastically change appearances again. It would be impossible to see the location of any changes being made because navigation would take them elsewhere every time.

This project uses a technique first developed for Objective-C located at https://github.com/kevindoughty/cletustheslackjawedoutlineview.

Installation

npm install @kvndy/history

API

The single export is the History object constructor which exposes primitives to be used for managing the undo stack.

new History(undoLocalizer, redoLocalizer, maxCount)

Creates a new History object for managing an undo stack. Its first two parameters are both optional functions meant to generate strings to be used as tooltips or menu items describing what specific change an undo or redo would produce. Each function is in turn passed a single argument of the developer’s choosing.

undoLocalizer is a function that generates the undoDescription for a given change to an Undoable. It has one parameter, the changeDescription from an Undoable or group, and should return a string or null.

redoLocalizer is a function that generates the redoDescription for a given change to an Undoable. It has one parameter, the changeDescription from an Undoable or group, and should return a string or null.

maxCount is a positive integer that determines the size of the undo stack. The default is Infinity.

const undoLocalizer = (changeDescription) => {
	return "Undo " + changeDescription;
}
const redoLocalizer = (changeDescription) => {
	return "Redo " + changeDescription;
}
const { undoable, preservable, group, undo, redo, canUndo, canRedo, undoDescription, redoDescription } = new History(undoLocalizer, redoLocalizer);

undoable(initialValue, changeDescription, coalescing)

Creates an Undoable object which is meant to be used in place of a Signal. It privately maintains a Signal to hold its value and provide timely UI updates. It exposes a similar API as a Signal, with value getter and setter accessors.

initialValue is passed along to its Signal upon creation.

changeDescription is an optional object which is passed to the undoLocalizer and redoLocalizer functions to generate an undoDescription and redoDescription. Pass null or undefined to bypass for no description. There is an alternative method for more dynamic descriptions using group.

coalescing is an optional object with a default value of false but is not limited to booleans. When true, multiple successive changes to an Undoable only register as a single change. When an object, referential equality determines if changes can also coalesce with a group using the same object.

const setting = undoable(0, "change setting", true);
setting.value = 1; // registers for undo

preservable(initialValue, interrupting)

Creates a Preservable object which is also meant to be used in place of a Signal, privately maintains one of its own, and exposes a similar API as a Signal through value getter and setter accessors.

initialValue is passed along to its Signal upon creation.

interrupting is an optional boolean that specifies if changes inhibit undoable coalescing when not called from within an enclosing group.

const appearance = preservable(0, true);
appearance.value = 2; // does not register for undo
setting.value = 3; // previous appearance value of 2 is captured as both its before state and after state
appearance.value = 4; // not captured as the after state of the previous change to setting

group(callback, changeDescription, coalescing)

Makes use of the Signals batch function which permits multiple signal writes into one update. A change to a Preservable is considered made after any change to an Undoable regardless of call order. The changeDescription and coalescing key from the outer group are used.

callback is the function which gets passed to a Signals batch call.

changeDescription is an optional object similar to the second parameter of undoable and is not limited to strings. The undoLocalizer or redoLocalizer functions can be written to handle an array or other object for more precise and dynamic descriptions of a change. Pass null or undefined to bypass for no description.

coalescing is an optional object similar to the third parameter of undoable and is not limited to booleans. If true, the changeDescription is used as a unique key to determine if changes should be coalesced. Otherwise if the argument is not null, undefined, or false it is used as the unique key.

group( () => {
	setting.value = 5; // registers for undo
	appearance.value = 6; // properly registers as the after change value
}, "change setting and more", true); // does not coalesce with previous change
group( () => {
	setting.value = 7; // registers for undo
	appearance.value = 8; // properly registers as the after change value
}, "change setting and more", true); // does coalesce with previous change

It is a commonly held belief that parameters should not come after a function, but rather before for readability. Not adhering to this was a concious choice as the second and third parameter are optional.

undo()

Navigates to the previous state.

undo();
assert.equal(setting.value, 3); // both grouped changes were coalesced and now undone
assert.equal(appearance.value, 4); // this was the value before those changes were made
undo();
assert.equal(setting.value, 1);
assert.equal(appearance.value, 2);

redo()

Navigates to the next state.

redo();
assert.equal(setting.value, 3);
assert.equal(appearance.value, 2); // as commented above, value did not change

canUndo

A Signals computed whose value getter returns a boolean that provides if undo is possible.

assert.equal(canUndo.value, true);

canRedo

A Signals computed whose value getter returns a boolean that provides if redo is possible.

assert.equal(canRedo.value, true);

undoDescription

A Signals computed whose value getter returns the result of the undoLocalizer function passed to the History constructor.

assert.equal(undoDescription.value, "Undo change setting");

redoDescription

A Signals computed whose value getter returns the result of the redoLocalizer function passed to the History constructor.

assert.equal(redoDescription.value, "Redo change setting and more");

Example

A non-virtual, non-animated tree view that preserves selection and expanded/collapsed state:

https://gitlab.com/kevindoughty/cute-tree

License

MIT

Issues and PRs

Welcome, especially for tooling, bundling, or Typescript/JSDoc definitions.

Package Sidebar

Install

npm i @kvndy/history

Weekly Downloads

1

Version

4.0.4

License

MIT

Unpacked Size

209 kB

Total Files

22

Last publish

Collaborators

  • kvndy