JSFiddle - React, Tailwind, and code Playground
HTML
<div id="controls">
<button id="undo-btn" disabled>Undo</button>
<button id="redo-btn" disabled>Redo</button>
<button id="clear-btn" disabled>Clear History</button>
<button id="squash-btn" disabled>Squash unique sequence</button>
</div>
<div id="app">
<label>Add text:</label>
<input type="text" id="text" required><button id="save-btn">Save</button>
</div>
<div id="history"></div>
CSS
#controls,
#history,
#app { padding: 1em; }
.remove { font-size: smaller; }
JavaScript
//
// scroll down to line 230 onwards to see example code
//
const StateRewind = function (options) {
let history = [],
changeIndex = -1,
onChangeCallback,
defaultForwardBackwardCallback;
// default options
options = Object.assign({
log: false,
initialState: undefined,
}, options || {});
// super simple logging
const log = function log(...data) {
if (options.log) {
console.info('%c[state]', 'color: limegreen; font-weight: bold;', ...data);
}
};
// we expose functions later to squash state history down based on a defined comparison function
// this is done by reducing the data to non matching, and having only the latest matching value be found
const squashReducer = function (compare, modify, startIndex) {
return function (accumulator, currentValue, index) {
if (typeof startIndex != "undefined") {
index += startIndex;
}
let previousValue = accumulator[index - 1];
if (index && compare(previousValue.change, currentValue.change)) {
if (index <= changeIndex) { // adjust the change index to match the meet the new order
changeIndex--;
}
if (typeof modify == 'function') {
currentValue.change = modify(previousValue.change, currentValue.change);
}
log('squashing index', index, ' from: ', previousValue, ' to:', currentValue);
accumulator[index - 1] = currentValue;
startIndex = (startIndex || 0) - 1;
return accumulator;
}
return accumulator.concat([currentValue]);
}
};
// runs any provided callback after any state change (set,exec,undo,redo)
const onChangeHandler = function () {
if (typeof onChangeCallback == 'function') {
onChangeCallback();
}
};
...