react-form-rewind 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 react-form-rewind contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # react-form-rewind
2
+
3
+ [![npm version](https://img.shields.io/npm/v/react-form-rewind.svg)](https://www.npmjs.com/package/react-form-rewind)
4
+ [![npm downloads](https://img.shields.io/npm/dm/react-form-rewind.svg)](https://www.npmjs.com/package/react-form-rewind)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/react-form-rewind)](https://bundlephobia.com/package/react-form-rewind)
6
+ [![license](https://img.shields.io/npm/l/react-form-rewind.svg)](LICENSE)
7
+ [![react](https://img.shields.io/badge/react-%3E%3D18.0.0-blue.svg)](https://reactjs.org)
8
+ [![typescript](https://img.shields.io/badge/typescript-5.3%2B-3178c6.svg)](https://www.typescriptlang.org/)
9
+
10
+ Zero-dependency, tree-shakable React state engine with auto-saved history stacks, time-traveling undo/redo, keyboard shortcuts, and draft persistence.
11
+
12
+ ---
13
+
14
+ ## Why?
15
+
16
+ | Problem | Solution |
17
+ |---------|----------|
18
+ | No native Ctrl+Z / Ctrl+Y in React forms | Built-in keyboard shortcuts with history tracking |
19
+ | User progress lost on tab reload | Auto-save drafts to `localStorage` with schema versioning |
20
+ | Manual debouncers for history snapshots | Automated keystroke coalescing into logical snapshots |
21
+ | Heavy form libraries add validation bloat | Focused solely on history and state persistence |
22
+
23
+ ## Quick Start
24
+
25
+ ```bash
26
+ npm install react-form-rewind
27
+ ```
28
+
29
+ ```tsx
30
+ import { useFormHistory } from "react-form-rewind";
31
+
32
+ function MyForm() {
33
+ const { state, setState, undo, redo, canUndo, canRedo } = useFormHistory(
34
+ { name: "", email: "" },
35
+ { persist: { key: "my-form-draft" } }
36
+ );
37
+
38
+ return (
39
+ <form>
40
+ <input
41
+ value={state.name}
42
+ onChange={(e) => setState({ ...state, name: e.target.value })}
43
+ />
44
+ <input
45
+ value={state.email}
46
+ onChange={(e) => setState({ ...state, email: e.target.value })}
47
+ />
48
+ <button type="button" onClick={undo} disabled={!canUndo}>
49
+ Undo
50
+ </button>
51
+ <button type="button" onClick={redo} disabled={!canRedo}>
52
+ Redo
53
+ </button>
54
+ </form>
55
+ );
56
+ }
57
+ ```
58
+
59
+ Press **Ctrl+Z** to undo, **Ctrl+Shift+Z** or **Ctrl+Y** to redo.
60
+
61
+ ---
62
+
63
+ ## API Reference
64
+
65
+ ### `useFormHistory<T>(initialState, options?)`
66
+
67
+ The core hook that manages a history-backed state stack.
68
+
69
+ **Returns:**
70
+
71
+ | Property | Type | Description |
72
+ |----------|------|-------------|
73
+ | `state` | `T` | Current present state |
74
+ | `setState` | `(value: T \| ((prev: T) => T), label?: string) => void` | Update state (pushes to history) |
75
+ | `undo` | `() => void` | Revert to previous state |
76
+ | `redo` | `() => void` | Re-apply undone state |
77
+ | `canUndo` | `boolean` | Whether undo is available |
78
+ | `canRedo` | `boolean` | Whether redo is available |
79
+ | `clearHistory` | `() => void` | Reset history, keep current state |
80
+ | `clearDraft` | `() => void` | Clear persisted draft from storage |
81
+ | `snapshot` | `(label?: string) => void` | Force-commit current state to history |
82
+ | `past` | `HistoryEntry<T>[]` | Past history entries |
83
+ | `future` | `HistoryEntry<T>[]` | Future (undone) entries |
84
+
85
+ **Options:**
86
+
87
+ | Option | Type | Default | Description |
88
+ |--------|------|---------|-------------|
89
+ | `maxHistory` | `number` | `100` | Maximum past entries to retain |
90
+ | `debounceMs` | `number` | `300` | Debounce window for rapid state changes |
91
+ | `persist` | `boolean \| PersistOptions` | `false` | Enable draft persistence |
92
+ | `onUndo` | `(state: T) => void` | — | Callback after undo |
93
+ | `onRedo` | `(state: T) => void` | — | Callback after redo |
94
+ | `onSnapshot` | `(entry: HistoryEntry<T>) => void` | — | Callback when a snapshot is committed |
95
+
96
+ ### `PersistOptions`
97
+
98
+ | Property | Type | Default | Description |
99
+ |----------|------|---------|-------------|
100
+ | `key` | `string` | — | `localStorage` key for draft storage |
101
+ | `debounceMs` | `number` | `500` | Debounce for auto-save writes |
102
+ | `version` | `number` | `1` | Schema version (mismatches discard draft) |
103
+
104
+ ---
105
+
106
+ ## Features
107
+
108
+ ### Keyboard Shortcuts
109
+
110
+ Shortcuts are enabled by default. Press **Ctrl+Z** to undo, **Ctrl+Shift+Z** or **Ctrl+Y** to redo. On macOS, **Ctrl** maps to **Cmd** automatically.
111
+
112
+ ### Snapshot Debouncing
113
+
114
+ Rapid keystrokes (typing "hello" quickly) are coalesced into a single history entry instead of one per keystroke. The debounce window defaults to 300ms.
115
+
116
+ ### Draft Persistence
117
+
118
+ Enable with `persist: { key: "my-form" }`. Drafts are auto-saved to `localStorage` and restored on mount. Schema versioning prevents stale drafts from hydrating incorrectly.
119
+
120
+ ---
121
+
122
+ ## Tree-Shaking
123
+
124
+ `react-form-rewind` uses pure ES module exports with `sideEffects: false` in `package.json`. Bundlers like Webpack, Rollup, and esbuild will only include code you actually import.
125
+
126
+ ```ts
127
+ // Only the hook is bundled — no extra code
128
+ import { useFormHistory } from "react-form-rewind";
129
+ ```
130
+
131
+ ---
132
+
133
+ ## TypeScript
134
+
135
+ Full type definitions are included. All generics are inferred from your initial state:
136
+
137
+ ```ts
138
+ const { state } = useFormHistory({ count: 0 });
139
+ // state is typed as { count: number }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Browser Support
145
+
146
+ - Chrome 80+
147
+ - Firefox 78+
148
+ - Safari 14+
149
+ - Edge 80+
150
+
151
+ Requires `React 18+` and native `Array`, `localStorage`, and `addEventListener` APIs.
152
+
153
+ ---
154
+
155
+ ## License
156
+
157
+ [MIT](LICENSE)
@@ -0,0 +1,38 @@
1
+ interface HistoryEntry<T> {
2
+ state: T;
3
+ timestamp: number;
4
+ label?: string;
5
+ }
6
+ interface HistoryStack<T> {
7
+ past: HistoryEntry<T>[];
8
+ present: T;
9
+ future: HistoryEntry<T>[];
10
+ }
11
+ interface UseFormHistoryOptions<T> {
12
+ maxHistory?: number;
13
+ debounceMs?: number;
14
+ persist?: boolean | PersistOptions;
15
+ onUndo?: (state: T) => void;
16
+ onRedo?: (state: T) => void;
17
+ onSnapshot?: (entry: HistoryEntry<T>) => void;
18
+ }
19
+ interface PersistOptions {
20
+ key: string;
21
+ debounceMs?: number;
22
+ version?: number;
23
+ }
24
+ interface UseFormHistoryReturn<T> {
25
+ state: T;
26
+ setState: (value: T | ((prev: T) => T), label?: string) => void;
27
+ undo: () => void;
28
+ redo: () => void;
29
+ canUndo: boolean;
30
+ canRedo: boolean;
31
+ clearHistory: () => void;
32
+ clearDraft: () => void;
33
+ snapshot: (label?: string) => void;
34
+ past: HistoryEntry<T>[];
35
+ future: HistoryEntry<T>[];
36
+ }
37
+
38
+ export type { HistoryEntry, HistoryStack, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn };
@@ -0,0 +1,38 @@
1
+ interface HistoryEntry<T> {
2
+ state: T;
3
+ timestamp: number;
4
+ label?: string;
5
+ }
6
+ interface HistoryStack<T> {
7
+ past: HistoryEntry<T>[];
8
+ present: T;
9
+ future: HistoryEntry<T>[];
10
+ }
11
+ interface UseFormHistoryOptions<T> {
12
+ maxHistory?: number;
13
+ debounceMs?: number;
14
+ persist?: boolean | PersistOptions;
15
+ onUndo?: (state: T) => void;
16
+ onRedo?: (state: T) => void;
17
+ onSnapshot?: (entry: HistoryEntry<T>) => void;
18
+ }
19
+ interface PersistOptions {
20
+ key: string;
21
+ debounceMs?: number;
22
+ version?: number;
23
+ }
24
+ interface UseFormHistoryReturn<T> {
25
+ state: T;
26
+ setState: (value: T | ((prev: T) => T), label?: string) => void;
27
+ undo: () => void;
28
+ redo: () => void;
29
+ canUndo: boolean;
30
+ canRedo: boolean;
31
+ clearHistory: () => void;
32
+ clearDraft: () => void;
33
+ snapshot: (label?: string) => void;
34
+ past: HistoryEntry<T>[];
35
+ future: HistoryEntry<T>[];
36
+ }
37
+
38
+ export type { HistoryEntry, HistoryStack, PersistOptions, UseFormHistoryOptions, UseFormHistoryReturn };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ //# sourceMappingURL=index.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=index.mjs.map
3
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.mjs"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "react-form-rewind",
3
+ "version": "0.1.0",
4
+ "description": "Zero-dependency React state engine with auto-saved history stacks, time-traveling undo/redo, keyboard shortcuts, and draft persistence for forms.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "LICENSE"
23
+ ],
24
+ "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "lint": "tsc --noEmit",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=18.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.2.0",
36
+ "react": "^18.2.0",
37
+ "react-dom": "^18.2.0",
38
+ "tsup": "^8.0.0",
39
+ "typescript": "^5.3.0"
40
+ },
41
+ "keywords": [
42
+ "react",
43
+ "form",
44
+ "history",
45
+ "undo",
46
+ "redo",
47
+ "time-travel",
48
+ "state",
49
+ "persistence",
50
+ "draft",
51
+ "localStorage",
52
+ "keyboard-shortcuts",
53
+ "hooks"
54
+ ],
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "https://github.com/your-username/react-form-rewind.git"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/your-username/react-form-rewind/issues"
61
+ },
62
+ "homepage": "https://github.com/your-username/react-form-rewind#readme",
63
+ "license": "MIT"
64
+ }