use-track-history
Version:
React hook to track state history with undo/redo functionality
156 lines (146 loc) • 6.08 kB
JavaScript
;
var react = require('react');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* React hook for tracking state history with undo/redo functionality
*
* @param defaultValue - Initial value to store in history
* @param options - Configuration options for history behavior
* @returns Object containing current value, update/reset functions, and history controls
*
* @example
* ```tsx
* const { value, update, reset, history } = useTrackHistory('Initial text');
*
* // Update value
* update('New text');
*
* // Undo/redo
* history.undo();
* history.redo();
*
* // Check if undo/redo is available
* console.log(history.canUndo); // boolean
* console.log(history.canRedo); // boolean
*
* // Reset history
* reset('Start over');
* ```
*/
function useTrackHistory(defaultValue, options) {
if (options === void 0) { options = {}; }
var _a = react.useState([defaultValue]), history = _a[0], setHistory = _a[1];
var _b = react.useState(0), historyPointer = _b[0], setHistoryPointer = _b[1];
// Store the latest state of history and pointer for synchronous access
var stateRef = react.useRef({
history: [defaultValue],
pointer: 0
});
// Keep ref in sync with state
stateRef.current.history = history;
stateRef.current.pointer = historyPointer;
// Store options in a ref so they don't cause rerenders when changed
var optionsRef = react.useRef(options);
optionsRef.current = options;
// Current value is always at the current pointer position
var value = history[historyPointer];
// Reset history with an optional new value
var reset = react.useCallback(function (newValue) {
setHistory([newValue]);
setHistoryPointer(0);
// Update ref immediately for sync access
stateRef.current = {
history: [newValue],
pointer: 0
};
}, []);
// Add a new value to history
var update = react.useCallback(function (newValue) {
// Get current state from ref for consistent access within the callback
var _a = stateRef.current, currentHistory = _a.history, currentPointer = _a.pointer;
// If we're not at the end of history, remove all future states
var newHistory = currentHistory.slice(0, currentPointer + 1);
// Add the new state to history
var updatedHistory = __spreadArray(__spreadArray([], newHistory, true), [newValue], false);
// Apply maxHistorySize limit if specified
var maxHistorySize = optionsRef.current.maxHistorySize;
var finalHistory = updatedHistory;
var newPointer = currentPointer + 1;
if (maxHistorySize && updatedHistory.length > maxHistorySize) {
// Trim history and adjust pointer
finalHistory = updatedHistory.slice(-maxHistorySize);
newPointer = finalHistory.length - 1;
}
// Update state
setHistory(finalHistory);
setHistoryPointer(newPointer);
// Update ref immediately for sync access
stateRef.current = {
history: finalHistory,
pointer: newPointer
};
}, []);
// Move back in history
var undo = react.useCallback(function () {
// Get current state from ref for consistent access
var currentPointer = stateRef.current.pointer;
if (currentPointer > 0) {
var newPointer = currentPointer - 1;
setHistoryPointer(newPointer);
// Update ref immediately for sync access
stateRef.current.pointer = newPointer;
}
}, []);
// Move forward in history
var redo = react.useCallback(function () {
// Get current state from ref for consistent access
var _a = stateRef.current, currentHistory = _a.history, currentPointer = _a.pointer;
if (currentPointer < currentHistory.length - 1) {
var newPointer = currentPointer + 1;
setHistoryPointer(newPointer);
// Update ref immediately for sync access
stateRef.current.pointer = newPointer;
}
}, []);
// Memoize the history object to prevent unnecessary rerenders
var historyObject = react.useMemo(function () { return ({
undo: undo,
redo: redo,
canUndo: historyPointer > 0,
canRedo: historyPointer < history.length - 1,
length: history.length,
}); }, [undo, redo, historyPointer, history.length]);
return {
value: value,
update: update,
reset: reset,
history: historyObject,
};
}
exports.useTrackHistory = useTrackHistory;
//# sourceMappingURL=index.js.map