@patch-kit/history 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A self-contained undo/redo system for using the Command Pattern.
|
|
4
4
|
|
|
5
|
+
**Interactive demo:** [Default](https://xkeexe.github.io/patchkit/?path=/story/history--default)
|
|
6
|
+
|
|
5
7
|
---
|
|
6
8
|
|
|
7
9
|
## Overview
|
|
8
10
|
|
|
9
11
|
Each user action is recorded as a command with `undo()` and `redo()` functions, enabling full history traversal. The factory pattern ensures each history instance is independently scoped and fully typed.
|
|
10
12
|
|
|
13
|
+
Example usage
|
|
14
|
+
|
|
11
15
|
---
|
|
12
16
|
|
|
13
17
|
## Setup
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
19
19
|
};
|
|
20
20
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
21
|
|
|
22
|
-
// index.tsx
|
|
22
|
+
// src/index.tsx
|
|
23
23
|
var index_exports = {};
|
|
24
24
|
__export(index_exports, {
|
|
25
25
|
createHistory: () => createHistory
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.tsx"],"sourcesContent":["\"use client\";\n\nimport React, {\n createContext,\n useContext,\n useReducer,\n useCallback,\n useMemo,\n} from \"react\";\nimport { Command } from \"./types\";\n\ninterface HistoryContextType<T> {\n addHistory: (command: Command<T>, immediate?: boolean) => void;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n resetHistory: () => void;\n}\n\n// State and action types for the reducer\ninterface HistoryState {\n commands: Command<any>[];\n step: number;\n}\n\ntype HistoryAction =\n | { type: \"ADD\"; command: Command<any>; limit: number }\n | { type: \"UNDO\" }\n | { type: \"REDO\" }\n | { type: \"RESET\" };\n\nconst initialState: HistoryState = {\n commands: [],\n step: 0,\n};\n\n/**\n * Pure reducer function that handles all history state transitions atomically.\n * Side effects (undo/redo) are handled outside the reducer to avoid\n * state updates during render.\n */\nfunction historyReducer(state: HistoryState, action: HistoryAction): HistoryState {\n switch (action.type) {\n case \"ADD\": {\n try {\n const newHistory = state.commands.slice(0, state.step);\n newHistory.push(action.command);\n\n if (newHistory.length > action.limit) {\n newHistory.shift();\n }\n\n return { commands: newHistory, step: newHistory.length };\n } catch (error) {\n console.error(`Failed to add command ${action.command.name}: ${error}`);\n return state;\n }\n }\n case \"UNDO\": {\n if (state.step <= 0) return state;\n return { ...state, step: state.step - 1 };\n }\n case \"REDO\": {\n if (state.step >= state.commands.length) return state;\n return { ...state, step: state.step + 1 };\n }\n case \"RESET\":\n return initialState;\n default:\n return state;\n }\n}\n\ninterface HistoryProviderProps<T> {\n children: React.ReactNode;\n limit?: number;\n onUndo?: (value: T) => void;\n onRedo?: (value: T) => void;\n}\n\n/**\n * Manages a history of commands to provide undo and redo functionality.\n *\n * Implements the Command design pattern's history tracking. It stores\n * a list of executed commands in a buffer with a limit.\n * When a new command is added after an `undo` operation, any existing `redo`\n * history is cleared. If the history limit is exceeded, the oldest command is\n * discarded.\n */\nexport function createHistory<T = any>() {\n const Context = createContext<HistoryContextType<T> | null>(null);\n\n function HistoryProvider({\n children,\n limit = 64,\n onUndo,\n onRedo,\n }: HistoryProviderProps<T>) {\n const [state, dispatch] = useReducer(historyReducer, initialState);\n\n const canUndo = state.step > 0;\n const canRedo = state.step < state.commands.length;\n\n const addHistory = useCallback(\n (command: Command<T>, immediate = false) => {\n if (immediate) command.redo();\n dispatch({ type: \"ADD\", command, limit });\n },\n [limit]\n );\n\n const undo = useCallback(() => {\n const { commands, step } = state;\n if (step <= 0) return;\n\n const command = commands[step - 1];\n const value = command.undo();\n onUndo?.(value as T);\n dispatch({ type: \"UNDO\" });\n }, [state, onUndo]);\n\n const redo = useCallback(() => {\n const { commands, step } = state;\n if (step >= commands.length) return;\n\n const command = commands[step];\n const value = command.redo();\n onRedo?.(value as T);\n dispatch({ type: \"REDO\" });\n }, [state, onRedo]);\n\n const resetHistory = useCallback(() => {\n dispatch({ type: \"RESET\" });\n }, []);\n\n const value = useMemo(\n () => ({\n addHistory,\n undo,\n redo,\n canUndo,\n canRedo,\n resetHistory,\n }),\n [addHistory, undo, redo, canUndo, canRedo, resetHistory]\n );\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useHistory() {\n const context = useContext(Context);\n if (!context) throw new Error(\"useHistory must be used within a HistoryProvider\");\n return context;\n }\n\n return { HistoryProvider, useHistory };\n}"],"mappings":"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","names":["value"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport React, {\n createContext,\n useContext,\n useReducer,\n useCallback,\n useMemo,\n} from \"react\";\nimport { Command } from \"./types\";\n\ninterface HistoryContextType<T> {\n addHistory: (command: Command<T>, immediate?: boolean) => void;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n resetHistory: () => void;\n}\n\n// State and action types for the reducer\ninterface HistoryState {\n commands: Command<any>[];\n step: number;\n}\n\ntype HistoryAction =\n | { type: \"ADD\"; command: Command<any>; limit: number }\n | { type: \"UNDO\" }\n | { type: \"REDO\" }\n | { type: \"RESET\" };\n\nconst initialState: HistoryState = {\n commands: [],\n step: 0,\n};\n\n/**\n * Pure reducer function that handles all history state transitions atomically.\n * Side effects (undo/redo) are handled outside the reducer to avoid\n * state updates during render.\n */\nfunction historyReducer(state: HistoryState, action: HistoryAction): HistoryState {\n switch (action.type) {\n case \"ADD\": {\n try {\n const newHistory = state.commands.slice(0, state.step);\n newHistory.push(action.command);\n\n if (newHistory.length > action.limit) {\n newHistory.shift();\n }\n\n return { commands: newHistory, step: newHistory.length };\n } catch (error) {\n console.error(`Failed to add command ${action.command.name}: ${error}`);\n return state;\n }\n }\n case \"UNDO\": {\n if (state.step <= 0) return state;\n return { ...state, step: state.step - 1 };\n }\n case \"REDO\": {\n if (state.step >= state.commands.length) return state;\n return { ...state, step: state.step + 1 };\n }\n case \"RESET\":\n return initialState;\n default:\n return state;\n }\n}\n\ninterface HistoryProviderProps<T> {\n children: React.ReactNode;\n limit?: number;\n onUndo?: (value: T) => void;\n onRedo?: (value: T) => void;\n}\n\n/**\n * Manages a history of commands to provide undo and redo functionality.\n *\n * Implements the Command design pattern's history tracking. It stores\n * a list of executed commands in a buffer with a limit.\n * When a new command is added after an `undo` operation, any existing `redo`\n * history is cleared. If the history limit is exceeded, the oldest command is\n * discarded.\n */\nexport function createHistory<T = any>() {\n const Context = createContext<HistoryContextType<T> | null>(null);\n\n function HistoryProvider({\n children,\n limit = 64,\n onUndo,\n onRedo,\n }: HistoryProviderProps<T>) {\n const [state, dispatch] = useReducer(historyReducer, initialState);\n\n const canUndo = state.step > 0;\n const canRedo = state.step < state.commands.length;\n\n const addHistory = useCallback(\n (command: Command<T>, immediate = false) => {\n if (immediate) command.redo();\n dispatch({ type: \"ADD\", command, limit });\n },\n [limit]\n );\n\n const undo = useCallback(() => {\n const { commands, step } = state;\n if (step <= 0) return;\n\n const command = commands[step - 1];\n const value = command.undo();\n onUndo?.(value as T);\n dispatch({ type: \"UNDO\" });\n }, [state, onUndo]);\n\n const redo = useCallback(() => {\n const { commands, step } = state;\n if (step >= commands.length) return;\n\n const command = commands[step];\n const value = command.redo();\n onRedo?.(value as T);\n dispatch({ type: \"REDO\" });\n }, [state, onRedo]);\n\n const resetHistory = useCallback(() => {\n dispatch({ type: \"RESET\" });\n }, []);\n\n const value = useMemo(\n () => ({\n addHistory,\n undo,\n redo,\n canUndo,\n canRedo,\n resetHistory,\n }),\n [addHistory, undo, redo, canUndo, canRedo, resetHistory]\n );\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useHistory() {\n const context = useContext(Context);\n if (!context) throw new Error(\"useHistory must be used within a HistoryProvider\");\n return context;\n }\n\n return { HistoryProvider, useHistory };\n}"],"mappings":"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","names":["value"]}
|
package/dist/index.mjs
CHANGED
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.tsx"],"sourcesContent":["\"use client\";\n\nimport React, {\n createContext,\n useContext,\n useReducer,\n useCallback,\n useMemo,\n} from \"react\";\nimport { Command } from \"./types\";\n\ninterface HistoryContextType<T> {\n addHistory: (command: Command<T>, immediate?: boolean) => void;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n resetHistory: () => void;\n}\n\n// State and action types for the reducer\ninterface HistoryState {\n commands: Command<any>[];\n step: number;\n}\n\ntype HistoryAction =\n | { type: \"ADD\"; command: Command<any>; limit: number }\n | { type: \"UNDO\" }\n | { type: \"REDO\" }\n | { type: \"RESET\" };\n\nconst initialState: HistoryState = {\n commands: [],\n step: 0,\n};\n\n/**\n * Pure reducer function that handles all history state transitions atomically.\n * Side effects (undo/redo) are handled outside the reducer to avoid\n * state updates during render.\n */\nfunction historyReducer(state: HistoryState, action: HistoryAction): HistoryState {\n switch (action.type) {\n case \"ADD\": {\n try {\n const newHistory = state.commands.slice(0, state.step);\n newHistory.push(action.command);\n\n if (newHistory.length > action.limit) {\n newHistory.shift();\n }\n\n return { commands: newHistory, step: newHistory.length };\n } catch (error) {\n console.error(`Failed to add command ${action.command.name}: ${error}`);\n return state;\n }\n }\n case \"UNDO\": {\n if (state.step <= 0) return state;\n return { ...state, step: state.step - 1 };\n }\n case \"REDO\": {\n if (state.step >= state.commands.length) return state;\n return { ...state, step: state.step + 1 };\n }\n case \"RESET\":\n return initialState;\n default:\n return state;\n }\n}\n\ninterface HistoryProviderProps<T> {\n children: React.ReactNode;\n limit?: number;\n onUndo?: (value: T) => void;\n onRedo?: (value: T) => void;\n}\n\n/**\n * Manages a history of commands to provide undo and redo functionality.\n *\n * Implements the Command design pattern's history tracking. It stores\n * a list of executed commands in a buffer with a limit.\n * When a new command is added after an `undo` operation, any existing `redo`\n * history is cleared. If the history limit is exceeded, the oldest command is\n * discarded.\n */\nexport function createHistory<T = any>() {\n const Context = createContext<HistoryContextType<T> | null>(null);\n\n function HistoryProvider({\n children,\n limit = 64,\n onUndo,\n onRedo,\n }: HistoryProviderProps<T>) {\n const [state, dispatch] = useReducer(historyReducer, initialState);\n\n const canUndo = state.step > 0;\n const canRedo = state.step < state.commands.length;\n\n const addHistory = useCallback(\n (command: Command<T>, immediate = false) => {\n if (immediate) command.redo();\n dispatch({ type: \"ADD\", command, limit });\n },\n [limit]\n );\n\n const undo = useCallback(() => {\n const { commands, step } = state;\n if (step <= 0) return;\n\n const command = commands[step - 1];\n const value = command.undo();\n onUndo?.(value as T);\n dispatch({ type: \"UNDO\" });\n }, [state, onUndo]);\n\n const redo = useCallback(() => {\n const { commands, step } = state;\n if (step >= commands.length) return;\n\n const command = commands[step];\n const value = command.redo();\n onRedo?.(value as T);\n dispatch({ type: \"REDO\" });\n }, [state, onRedo]);\n\n const resetHistory = useCallback(() => {\n dispatch({ type: \"RESET\" });\n }, []);\n\n const value = useMemo(\n () => ({\n addHistory,\n undo,\n redo,\n canUndo,\n canRedo,\n resetHistory,\n }),\n [addHistory, undo, redo, canUndo, canRedo, resetHistory]\n );\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useHistory() {\n const context = useContext(Context);\n if (!context) throw new Error(\"useHistory must be used within a HistoryProvider\");\n return context;\n }\n\n return { HistoryProvider, useHistory };\n}"],"mappings":";;;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA4II;AApHX,IAAM,eAA6B;AAAA,EACjC,UAAU,CAAC;AAAA,EACX,MAAM;AACR;AAOA,SAAS,eAAe,OAAqB,QAAqC;AAChF,UAAQ,OAAO,MAAM;AAAA,IACnB,KAAK,OAAO;AACV,UAAI;AACF,cAAM,aAAa,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI;AACrD,mBAAW,KAAK,OAAO,OAAO;AAE9B,YAAI,WAAW,SAAS,OAAO,OAAO;AACpC,qBAAW,MAAM;AAAA,QACnB;AAEA,eAAO,EAAE,UAAU,YAAY,MAAM,WAAW,OAAO;AAAA,MACzD,SAAS,OAAO;AACd,gBAAQ,MAAM,yBAAyB,OAAO,QAAQ,IAAI,KAAK,KAAK,EAAE;AACtE,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,MAAM,QAAQ,EAAG,QAAO;AAC5B,aAAO,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,EAAE;AAAA,IAC1C;AAAA,IACA,KAAK,QAAQ;AACX,UAAI,MAAM,QAAQ,MAAM,SAAS,OAAQ,QAAO;AAChD,aAAO,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,EAAE;AAAA,IAC1C;AAAA,IACA,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAkBO,SAAS,gBAAyB;AACvC,QAAM,UAAU,cAA4C,IAAI;AAEhE,WAAS,gBAAgB;AAAA,IACvB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF,GAA4B;AAC1B,UAAM,CAAC,OAAO,QAAQ,IAAI,WAAW,gBAAgB,YAAY;AAEjE,UAAM,UAAU,MAAM,OAAO;AAC7B,UAAM,UAAU,MAAM,OAAO,MAAM,SAAS;AAE5C,UAAM,aAAa;AAAA,MACjB,CAAC,SAAqB,YAAY,UAAU;AAC1C,YAAI,UAAW,SAAQ,KAAK;AAC5B,iBAAS,EAAE,MAAM,OAAO,SAAS,MAAM,CAAC;AAAA,MAC1C;AAAA,MACA,CAAC,KAAK;AAAA,IACR;AAEA,UAAM,OAAO,YAAY,MAAM;AAC7B,YAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,UAAI,QAAQ,EAAG;AAEf,YAAM,UAAU,SAAS,OAAO,CAAC;AACjC,YAAMA,SAAQ,QAAQ,KAAK;AAC3B,eAASA,MAAU;AACnB,eAAS,EAAE,MAAM,OAAO,CAAC;AAAA,IAC3B,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,UAAM,OAAO,YAAY,MAAM;AAC7B,YAAM,EAAE,UAAU,KAAK,IAAI;AAC3B,UAAI,QAAQ,SAAS,OAAQ;AAE7B,YAAM,UAAU,SAAS,IAAI;AAC7B,YAAMA,SAAQ,QAAQ,KAAK;AAC3B,eAASA,MAAU;AACnB,eAAS,EAAE,MAAM,OAAO,CAAC;AAAA,IAC3B,GAAG,CAAC,OAAO,MAAM,CAAC;AAElB,UAAM,eAAe,YAAY,MAAM;AACrC,eAAS,EAAE,MAAM,QAAQ,CAAC;AAAA,IAC5B,GAAG,CAAC,CAAC;AAEL,UAAM,QAAQ;AAAA,MACZ,OAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,CAAC,YAAY,MAAM,MAAM,SAAS,SAAS,YAAY;AAAA,IACzD;AAEA,WAAO,oBAAC,QAAQ,UAAR,EAAiB,OAAe,UAAS;AAAA,EACnD;AAEA,WAAS,aAAa;AACpB,UAAM,UAAU,WAAW,OAAO;AAClC,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,kDAAkD;AAChF,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,iBAAiB,WAAW;AACvC;","names":["value"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport React, {\n createContext,\n useContext,\n useReducer,\n useCallback,\n useMemo,\n} from \"react\";\nimport { Command } from \"./types\";\n\ninterface HistoryContextType<T> {\n addHistory: (command: Command<T>, immediate?: boolean) => void;\n undo: () => void;\n redo: () => void;\n canUndo: boolean;\n canRedo: boolean;\n resetHistory: () => void;\n}\n\n// State and action types for the reducer\ninterface HistoryState {\n commands: Command<any>[];\n step: number;\n}\n\ntype HistoryAction =\n | { type: \"ADD\"; command: Command<any>; limit: number }\n | { type: \"UNDO\" }\n | { type: \"REDO\" }\n | { type: \"RESET\" };\n\nconst initialState: HistoryState = {\n commands: [],\n step: 0,\n};\n\n/**\n * Pure reducer function that handles all history state transitions atomically.\n * Side effects (undo/redo) are handled outside the reducer to avoid\n * state updates during render.\n */\nfunction historyReducer(state: HistoryState, action: HistoryAction): HistoryState {\n switch (action.type) {\n case \"ADD\": {\n try {\n const newHistory = state.commands.slice(0, state.step);\n newHistory.push(action.command);\n\n if (newHistory.length > action.limit) {\n newHistory.shift();\n }\n\n return { commands: newHistory, step: newHistory.length };\n } catch (error) {\n console.error(`Failed to add command ${action.command.name}: ${error}`);\n return state;\n }\n }\n case \"UNDO\": {\n if (state.step <= 0) return state;\n return { ...state, step: state.step - 1 };\n }\n case \"REDO\": {\n if (state.step >= state.commands.length) return state;\n return { ...state, step: state.step + 1 };\n }\n case \"RESET\":\n return initialState;\n default:\n return state;\n }\n}\n\ninterface HistoryProviderProps<T> {\n children: React.ReactNode;\n limit?: number;\n onUndo?: (value: T) => void;\n onRedo?: (value: T) => void;\n}\n\n/**\n * Manages a history of commands to provide undo and redo functionality.\n *\n * Implements the Command design pattern's history tracking. It stores\n * a list of executed commands in a buffer with a limit.\n * When a new command is added after an `undo` operation, any existing `redo`\n * history is cleared. If the history limit is exceeded, the oldest command is\n * discarded.\n */\nexport function createHistory<T = any>() {\n const Context = createContext<HistoryContextType<T> | null>(null);\n\n function HistoryProvider({\n children,\n limit = 64,\n onUndo,\n onRedo,\n }: HistoryProviderProps<T>) {\n const [state, dispatch] = useReducer(historyReducer, initialState);\n\n const canUndo = state.step > 0;\n const canRedo = state.step < state.commands.length;\n\n const addHistory = useCallback(\n (command: Command<T>, immediate = false) => {\n if (immediate) command.redo();\n dispatch({ type: \"ADD\", command, limit });\n },\n [limit]\n );\n\n const undo = useCallback(() => {\n const { commands, step } = state;\n if (step <= 0) return;\n\n const command = commands[step - 1];\n const value = command.undo();\n onUndo?.(value as T);\n dispatch({ type: \"UNDO\" });\n }, [state, onUndo]);\n\n const redo = useCallback(() => {\n const { commands, step } = state;\n if (step >= commands.length) return;\n\n const command = commands[step];\n const value = command.redo();\n onRedo?.(value as T);\n dispatch({ type: \"REDO\" });\n }, [state, onRedo]);\n\n const resetHistory = useCallback(() => {\n dispatch({ type: \"RESET\" });\n }, []);\n\n const value = useMemo(\n () => ({\n addHistory,\n undo,\n redo,\n canUndo,\n canRedo,\n resetHistory,\n }),\n [addHistory, undo, redo, canUndo, canRedo, resetHistory]\n );\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n }\n\n function useHistory() {\n const context = useContext(Context);\n if (!context) throw new Error(\"useHistory must be used within a HistoryProvider\");\n return context;\n }\n\n return { HistoryProvider, useHistory };\n}"],"mappings":"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","names":["value"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patch-kit/history",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "History Manager for React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,12 @@
|
|
|
18
18
|
"require": "./dist/index.cjs"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"files": [
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
22
27
|
"scripts": {
|
|
23
28
|
"build": "tsup",
|
|
24
29
|
"dev": "tsup --watch",
|