UNPKG

use-track-history

Version:

React hook to track state history with undo/redo functionality

156 lines (146 loc) 6.08 kB
'use strict'; 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