npm package: @kvndy/history
Description: Deprecated. Undo and redo management for Signals
Author: Kevin Doughty
Latest version: 4.0.4
Deprecated: Deprecated. Please instead use https://www.npmjs.com/package/@kvndy/undo-manager

# History

### Deprecated. Please instead use [https://gitlab.com/kevindoughty/undo-manager](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](https://github.com/kevindoughty/cletustheslackjawedoutlineview).

## Installation

```sh
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`.



```js
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.

```js
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`.

```js
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.

```js
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.

```js
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.

```js
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.

```js
assert.equal(canUndo.value, true);
```

### `canRedo`

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

```js
assert.equal(canRedo.value, true);
```

### `undoDescription`

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

```js
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.

```js
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](https://gitlab.com/kevindoughty/cute-tree)

## License

MIT

## Issues and PRs

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