@typescript-package/history
Version:
A TypeScript package for tracking the history of values.
727 lines (715 loc) • 18.2 kB
JavaScript
/**
* @description
* @export
* @class RedoHistoryPeek
* @template Type
*/
class RedoHistoryPeek {
history;
/**
* Creates an instance of `RedoHistoryPeek` child class.
* @constructor
* @param {History<Type>} history
*/
constructor(history) {
this.history = history;
}
/**
* @description
* @public
* @param {number} [index=0]
* @returns {(Type | undefined)}
*/
index(index = 0) {
return this.history.redoHistory.peek(index);
}
/**
* @description
* @public
* @returns {(Type | undefined)}
*/
last() {
return this.history.redoHistory.peekLast();
}
/**
* @description
* @public
* @returns {(Type | undefined)}
*/
next() {
return this.history.redoHistory.peekNext();
}
}
/**
* @description The class
* @export
* @class UndoHistoryPeek
* @template Type
*/
class UndoHistoryPeek {
history;
/**
* Creates an instance of `UndoHistoryPeek` child class.
* @constructor
* @param {History<Type>} history
*/
constructor(history) {
this.history = history;
}
/**
* @description
* @public
* @param {number} [index=this.history.undoHistory.length - 1]
* @returns {(Type | undefined)}
*/
index(index = this.history.undoHistory.length - 1) {
return this.history.undoHistory.peek(index);
}
/**
* @description
* @public
* @returns {(Type | undefined)}
*/
last() {
return this.history.undoHistory.peekLast();
}
/**
* @description
* @public
* @returns {(Type | undefined)}
*/
next() {
return this.history.undoHistory.peekNext();
}
}
/**
* @description
* @export
* @class HistoryPeek
* @template Type
*/
class HistoryPeek {
/**
* @description
* @public
* @readonly
* @type {*}
*/
get redo() {
return this.#redo;
}
/**
* @description
* @public
* @readonly
* @type {*}
*/
get undo() {
return this.#undo;
}
/**
* @description
* @readonly
* @type {*}
*/
#undo;
/**
* @description
* @readonly
* @type {*}
*/
#redo;
/**
* Creates an instance of `HistoryPeek`.
* @constructor
* @param {History<Type>} history
*/
constructor(history) {
this.#redo = new RedoHistoryPeek(history);
this.#undo = new UndoHistoryPeek(history);
}
}
var _a;
/**
* @description The core class for history append and prepend.
* @export
* @abstract
* @class HistoryCore
* @template Type
* @template {number} [Size=number]
*/
class HistoryCore {
/**
* @description A private property for history of `WeakMap` type.
* @static
* @readonly
* @type {WeakMap}
*/
static #history = new WeakMap();
/**
* @description Returns the `string` tag representation of the `HistoryCore` class when used in `Object.prototype.toString.call(instance)`.
* @public
* @readonly
* @type {string}
*/
get [Symbol.toStringTag]() {
return _a.name;
}
/**
* @description Gets the history.
* @protected
* @readonly
* @type {Type[]}
*/
get history() {
return _a.#history.get(this);
}
/**
* @description The length of the history.
* @public
* @readonly
* @type {number}
*/
get length() {
return this.history.length;
}
/**
* @description The maximum size of the history.
* @public
* @readonly
* @type {Size}
*/
get size() {
return this.#size;
}
/**
* @description The maximum size of the history.
* @type {number}
*/
#size;
/**
* Creates an instance of `HistoryCore` child class.
* @constructor
* @param {Size} size
*/
constructor(size) {
this.#initialize();
this.#size = size;
}
/**
* @description Clears the history.
* @public
* @returns {this} The current instance.
*/
clear() {
const history = this.history;
history.length = 0;
return this;
}
/**
* @description Destroys the history of this instance.
* @public
* @returns {this} The current instance.
*/
destroy() {
_a.#history.delete(this);
return this;
}
/**
* @description Gets the history.
* @public
* @returns {Readonly<Type[]>}
*/
get() {
return this.history;
}
/**
* @description Returns the value at the specified index in the history.
* @public
* @param {number} index The position in the history (0-based index).
* @returns {Type | undefined} The value at the specified position.
*/
peek(index) {
return this.history[index];
}
/**
* @description Initializes the history by setting an empty `Array`.
* @private
* @returns {this} The current instance.
*/
#initialize() {
_a.#history.set(this, []);
return this;
}
}
_a = HistoryCore;
// Abstract.
/**
* @description Class extends the `HistoryCore` class to maintain a history of values in a prepend manner.
* This means that new entries are added to the beginning of the history, and older entries are shifted out as the history size exceeds its limit.
* @export
* @abstract
* @class HistoryPrepend
* @template Type
* @template {number} [Size=number]
*/
class HistoryPrepend extends HistoryCore {
/**
* @description The default value of maximum history size.
* @public
* @static
* @type {number}
*/
static size = 10;
/**
* @description Returns the `string` tag representation of the `HistoryPrepend` class when used in `Object.prototype.toString.call(instance)`.
* @public
* @readonly
* @type {string}
*/
get [Symbol.toStringTag]() {
return HistoryPrepend.name;
}
/**
* Creates an instance of `HistoryPrepend` child class.
* @constructor
* @param {Size} [size=HistoryPrepend.size as Size]
*/
constructor(size = HistoryPrepend.size) {
super(size);
}
/**
* @description Adds the value to the history in a backward manner.
* @public
* @param {Type} value The value to store.
* @returns {this} The current instance.
*/
add(value) {
const history = super.history;
history.length >= super.size && history.pop();
history.unshift(value);
return this;
}
/**
* @description Returns the last value that would be redone without modifying history.
* @public
* @returns {Type | undefined} The next redo value.
*/
peekLast() {
return super.history[super.history.length - 1];
}
/**
* @description Returns the next value that would be redone without modifying history.
* @public
* @returns {Type | undefined} The next redo value.
*/
peekNext() {
return super.history[0];
}
/**
* @description Takes the first value.
* @public
* @returns {(Type | undefined)}
*/
take() {
return super.history.shift();
}
}
// Abstract.
/**
* @description Manages the redo history with prepend mechanism.
* @export
* @class RedoHistory
* @template [Type=any] The type of elements stored in the history
* @template {number} [Size=number] The maximum size of the history.
* @extends {HistoryPrepend<Type, Size>}
*/
class RedoHistory extends HistoryPrepend {
}
;
// Abstract.
/**
* @description Class extends the `HistoryCore` class to maintain a history of values in a append manner.
* This means that new entries are added to the end of the history, and as the history exceeds its size limit, entries from the beginning are removed.
* - LIFO(Last in, First out).
* @export
* @abstract
* @class HistoryAppend
* @template Type
* @template {number} [Size=number]
*/
class HistoryAppend extends HistoryCore {
/**
* @description The default value of maximum history size.
* @public
* @static
* @type {number}
*/
static size = 10;
/**
* @description Returns the `string` tag representation of the `HistoryAppend` class when used in `Object.prototype.toString.call(instance)`.
* @public
* @readonly
* @type {string}
*/
get [Symbol.toStringTag]() {
return HistoryAppend.name;
}
/**
* Creates an instance of `HistoryAppend` child class.
* @constructor
* @param {Size} [size=HistoryAppend.size as Size]
*/
constructor(size = HistoryAppend.size) {
super(size);
}
/**
* @description Adds the value to the history.
* @public
* @param {Type} value The value to store.
* @returns {this} The current instance.
*/
add(value) {
const history = super.history;
history.length >= super.size && history.shift();
history.push(value);
return this;
}
/**
* @description Returns the last value that would be use to redo or undo without modifying history.
* @public
* @returns {Type | undefined} The next redo value.
*/
peekLast() {
return super.history[0];
}
/**
* @description Returns the next value that would be use to redo or undo without modifying history.
* @public
* @returns {Type | undefined} The next redo value.
*/
peekNext() {
return super.history[super.history.length - 1];
}
/**
* @description Takes the last value.
* @public
* @returns {(Type | undefined)}
*/
take() {
return super.history.pop();
}
}
// Abstract.
/**
* @description Manages the undo history with append mechanism.
* @export
* @class UndoHistory
* @template [Type=any] The type of elements stored in the history
* @template {number} [Size=number] The maximum size of the history.
* @extends {HistoryAppend<Type, Size>}
*/
class UndoHistory extends HistoryAppend {
}
;
// Abstract.
/**
* @description The class to manage the value changes.
* @export
* @abstract
* @class History
* @template Type
* @template {number} [Size=number]
*/
class History {
/**
* @description The max size for undo history.
* @public
* @static
* @type {number}
*/
static size = Infinity;
/**
* @description Returns the `string` tag representation of the `History` class when used in `Object.prototype.toString.call(instance)`.
* @public
* @readonly
* @type {string}
*/
get [Symbol.toStringTag]() {
return History.name;
}
/**
* @description Gets the current value stored in history.
* @public
* @readonly
* @type {Type}
*/
get current() {
return this.#current.value;
}
/**
* @description
* @public
* @readonly
* @type {HistoryPeek<Type>}
*/
get peek() {
return this.#peek;
}
/**
* @description Returns the redo history.
* @public
* @readonly
* @type {RedoHistory<any, number>}
*/
get redoHistory() {
return this.#redo;
}
/**
* @description Returns the undo history.
* @public
* @readonly
* @type {UndoHistory<any, number>}
*/
get undoHistory() {
return this.#undo;
}
/**
* @description A private field to store the current value.
* @type {{ value?: Type }}
*/
#current = {};
/**
* @description
* @type {boolean}
*/
#hasSetBeenCalled = false;
/**
* @description Privately stored callback function invoked on redo.
* @type {(value: Type) => void}
*/
#onRedoCallback;
/**
* @description Privately stored callback function invoked on undo.
* @type {(value: Type) => void}
*/
#onUndoCallback;
/**
* @description
* @type {HistoryPeek}
*/
#peek;
/**
* @description The class to manage the redo history.
* @type {RedoHistory}
*/
#redo;
/**
* @description The class to manage the undo history.
* @type {UndoHistory}
*/
#undo;
/**
* Creates an instance of `History` child class.
* @constructor
* @param {{value?: Type, size?: Size}} param0
* @param {Type} param0.value
* @param {Size} param0.size
*/
constructor({ value, size } = {}) {
this.#peek = new HistoryPeek(this);
this.#redo = new RedoHistory(Infinity);
this.#undo = new UndoHistory(size || History.size);
Object.hasOwn(arguments[0] || {}, 'value') && ((this.#hasSetBeenCalled = true), (this.#current = { value }));
}
/**
* @description Clears the `undo` and `redo` history, removes the current value, and resets the `hasSetBeenCalled`.
* @public
* @returns {this} The current instance.
*/
clear() {
delete this.#current.value;
this.#hasSetBeenCalled = false;
this.#redo.clear();
this.#undo.clear();
return this;
}
/**
* @description Destroys the history of this instance.
* @public
* @returns {this} The current instance.
*/
destroy() {
this.clear();
this.#undo.destroy();
this.#redo.destroy();
return this;
}
/**
* @description Gets the undo and redo history.
* @public
* @returns {{ undo: Readonly<Type[]>; redo: Readonly<Type[]> }}
*/
get() {
return {
undo: this.#undo.get(),
redo: this.#redo.get(),
};
}
/**
* @description The instance method returns read-only redo history.
* @public
* @template Type
* @returns {(Readonly<Type[]> | undefined)}
*/
getRedo() {
return this.#redo.get();
}
/**
* @description The instance method returns read-only undo history.
* @public
* @template Type
* @returns {(Readonly<Type[]> | undefined)}
*/
getUndo() {
return this.#undo.get();
}
/**
* @description Checks whether the current value is set.
* @public
* @returns {boolean}
*/
hasCurrent() {
return Object.hasOwn(this.#current, 'value');
}
/**
* @description Sets the callback function invoked on redo.
* @public
* @param {(value: Type) => void} callbackFn The callback function to invoke.
* @returns {this}
*/
onRedo(callbackFn) {
this.#onRedoCallback = callbackFn;
return this;
}
/**
* @description Sets the callback function invoked on undo.
* @public
* @param {(value: Type) => void} callbackFn The callback function to invoke.
* @returns {this}
*/
onUndo(callbackFn) {
this.#onUndoCallback = callbackFn;
return this;
}
/**
* @description Returns the specified by index value from redo history.
* @public
* @param {number} [index=0]
* @returns {(Type | undefined)}
*/
peekRedo(index = 0) {
return this.#redo.peek(index);
}
/**
* @description Returns the specified by index value from undo history.
* @public
* @param {number} [index=this.#undo.length - 1]
* @returns {(Type | undefined)}
*/
peekUndo(index = this.#undo.length - 1) {
return this.#undo.peek(index);
}
/**
* @description Returns the last value that would be redone without modifying history.
* @public
* @returns {Type | undefined} The last redo value.
*/
peekLastRedo() {
return this.#redo.peekLast();
}
/**
* @description Returns the last value that would be undone without modifying history.
* @public
* @returns {Type | undefined} The last undo value.
*/
peekLastUndo() {
return this.#undo.peekLast();
}
/**
* @description Returns the next value that would be redone without modifying history.
* @public
* @returns {Type | undefined} The next redo value.
*/
peekNextRedo() {
return this.#redo.peekNext();
}
/**
* @description Returns the next value that would be undone without modifying history.
* @public
* @returns {Type | undefined} The next undo value.
*/
peekNextUndo() {
return this.#undo.peekNext();
}
/**
* @description Pick the undo or redo history.
* @public
* @param {('undo' | 'redo')} type
* @returns {Readonly<Type[]>}
*/
pick(type) {
return (type === 'undo' ? this.#undo : this.#redo).get();
}
/**
* @description Redoes the last undone action.
* @public
* @returns {this} The current instance.
*/
redo() {
const redo = this.#redo;
if (redo.get()?.length) {
const value = redo.take();
this.#undo.add(this.#current.value);
this.#current = { value };
this.#onRedoCallback?.(value);
}
return this;
}
/**
* @description Sets a new value and updates the undo history.
* @public
* @param {Type} value
* @returns {this} The current instance.
*/
set(value) {
this.#hasSetBeenCalled === true && this.#undo.add(this.#current.value);
this.#current = { value };
this.#redo.clear();
this.#hasSetBeenCalled === false && (this.#hasSetBeenCalled = true);
return this;
}
/**
* @description Undoes the last action and moves it to redo history.
* @public
* @returns {this} The current instance.
*/
undo() {
const undo = this.#undo;
if (undo.get()?.length) {
const value = undo.take();
this.#redo.add(this.#current.value);
this.#current = { value };
this.#onUndoCallback?.(value);
}
return this;
}
}
/*
* Public API Surface of history
*/
/**
* Generated bundle index. Do not edit.
*/
export { History, HistoryAppend, HistoryCore, HistoryPrepend };
//# sourceMappingURL=typescript-package-history.mjs.map