@voidhash/mimic-react 1.0.0-beta.13 → 1.0.0-beta.15
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/.turbo/turbo-build.log +36 -36
- package/dist/zustand/middleware.d.cts +1 -1
- package/dist/zustand/middleware.d.mts +1 -1
- package/dist/zustand/types.d.cts +1 -1
- package/dist/zustand/types.d.mts +1 -1
- package/dist/zustand/useDraft.cjs +14 -11
- package/dist/zustand/useDraft.d.cts +6 -5
- package/dist/zustand/useDraft.d.cts.map +1 -1
- package/dist/zustand/useDraft.d.mts +6 -5
- package/dist/zustand/useDraft.d.mts.map +1 -1
- package/dist/zustand/useDraft.mjs +14 -11
- package/dist/zustand/useDraft.mjs.map +1 -1
- package/dist/zustand-commander/commander.cjs +59 -22
- package/dist/zustand-commander/commander.d.cts +16 -6
- package/dist/zustand-commander/commander.d.cts.map +1 -1
- package/dist/zustand-commander/commander.d.mts +16 -6
- package/dist/zustand-commander/commander.d.mts.map +1 -1
- package/dist/zustand-commander/commander.mjs +57 -22
- package/dist/zustand-commander/commander.mjs.map +1 -1
- package/dist/zustand-commander/hooks.cjs +18 -4
- package/dist/zustand-commander/hooks.d.cts +2 -1
- package/dist/zustand-commander/hooks.d.cts.map +1 -1
- package/dist/zustand-commander/hooks.d.mts +2 -1
- package/dist/zustand-commander/hooks.d.mts.map +1 -1
- package/dist/zustand-commander/hooks.mjs +18 -4
- package/dist/zustand-commander/hooks.mjs.map +1 -1
- package/dist/zustand-commander/index.cjs +2 -0
- package/dist/zustand-commander/index.d.cts +2 -2
- package/dist/zustand-commander/index.d.mts +2 -2
- package/dist/zustand-commander/index.mjs +2 -2
- package/dist/zustand-commander/types.d.cts +23 -14
- package/dist/zustand-commander/types.d.cts.map +1 -1
- package/dist/zustand-commander/types.d.mts +23 -14
- package/dist/zustand-commander/types.d.mts.map +1 -1
- package/dist/zustand-commander/types.mjs.map +1 -1
- package/package.json +3 -3
- package/src/zustand/useDraft.ts +15 -19
- package/src/zustand-commander/commander.ts +107 -21
- package/src/zustand-commander/hooks.ts +38 -12
- package/src/zustand-commander/index.ts +2 -0
- package/src/zustand-commander/types.ts +34 -24
- package/tests/zustand-commander/commander.test.ts +321 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.cts","names":[],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"commander.d.cts","names":[],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AA8OA;;;;;;AAgBA;;;;;AAoBA;;;;;AA+CA;;;;;AA4EA;;;AACY,iBA/SI,eA+SJ,CAAA,eAAA,MAAA,EAAA,gBA/S2D,SAAA,CAAU,YA+SrE,GA/SoF,SAAA,CAAU,YA+S9F,CAAA,CAAA,OAAA,CAAA,EA9SD,gBA8SC,CAAA,EA7ST,SA6SS,CA7SC,MA6SD,GA7SU,cA6SV,EA7S0B,OA6S1B,CAAA;;;;;iBAhKI,8BAA8B,0BAClC,SAAS,gBACZ,cAAA,CAAe;;;;iBAcR,gCAAgC,0BACpC,SAAS;;;;;iBAmBL,2BAA2B,0BAC/B,SAAS;;;;;iBA8CL,2BAA2B,0BAC/B,SAAS;;;;iBA2EL,gCAAgC,0BACpC,SAAS"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Commander, CommanderOptions, CommanderSlice } from "./types.mjs";
|
|
2
2
|
import { StoreApi } from "zustand";
|
|
3
|
+
import { Primitive } from "@voidhash/mimic";
|
|
4
|
+
import { ClientDocument } from "@voidhash/mimic/client";
|
|
3
5
|
|
|
4
6
|
//#region src/zustand-commander/commander.d.ts
|
|
5
7
|
|
|
@@ -15,8 +17,7 @@ import { StoreApi } from "zustand";
|
|
|
15
17
|
* const addItem = commander.action(
|
|
16
18
|
* Schema.Struct({ name: Schema.String }),
|
|
17
19
|
* (ctx, params) => {
|
|
18
|
-
*
|
|
19
|
-
* mimic.document.transaction(root => {
|
|
20
|
+
* ctx.transaction(root => {
|
|
20
21
|
* // add item
|
|
21
22
|
* });
|
|
22
23
|
* }
|
|
@@ -32,15 +33,24 @@ import { StoreApi } from "zustand";
|
|
|
32
33
|
* );
|
|
33
34
|
* ```
|
|
34
35
|
*/
|
|
35
|
-
declare function createCommander<TStore extends object>(options?: CommanderOptions): Commander<TStore & CommanderSlice>;
|
|
36
|
+
declare function createCommander<TStore extends object, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(options?: CommanderOptions): Commander<TStore & CommanderSlice, TSchema>;
|
|
37
|
+
/**
|
|
38
|
+
* Set the active draft on the commander slice.
|
|
39
|
+
* While a draft is active, transactions route through `draft.update()` and undo is disabled.
|
|
40
|
+
*/
|
|
41
|
+
declare function setActiveDraft<TStore extends CommanderSlice>(storeApi: StoreApi<TStore>, draft: ClientDocument.DraftHandle<any>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Clear the active draft from the commander slice.
|
|
44
|
+
*/
|
|
45
|
+
declare function clearActiveDraft<TStore extends CommanderSlice>(storeApi: StoreApi<TStore>): void;
|
|
36
46
|
/**
|
|
37
47
|
* Perform an undo operation on the store.
|
|
38
|
-
* Returns true if an undo was performed, false if undo stack was empty.
|
|
48
|
+
* Returns true if an undo was performed, false if undo stack was empty or a draft is active.
|
|
39
49
|
*/
|
|
40
50
|
declare function performUndo<TStore extends CommanderSlice>(storeApi: StoreApi<TStore>): boolean;
|
|
41
51
|
/**
|
|
42
52
|
* Perform a redo operation on the store.
|
|
43
|
-
* Returns true if a redo was performed, false if redo stack was empty.
|
|
53
|
+
* Returns true if a redo was performed, false if redo stack was empty or a draft is active.
|
|
44
54
|
*/
|
|
45
55
|
declare function performRedo<TStore extends CommanderSlice>(storeApi: StoreApi<TStore>): boolean;
|
|
46
56
|
/**
|
|
@@ -48,5 +58,5 @@ declare function performRedo<TStore extends CommanderSlice>(storeApi: StoreApi<T
|
|
|
48
58
|
*/
|
|
49
59
|
declare function clearUndoHistory<TStore extends CommanderSlice>(storeApi: StoreApi<TStore>): void;
|
|
50
60
|
//#endregion
|
|
51
|
-
export { clearUndoHistory, createCommander, performRedo, performUndo };
|
|
61
|
+
export { clearActiveDraft, clearUndoHistory, createCommander, performRedo, performUndo, setActiveDraft };
|
|
52
62
|
//# sourceMappingURL=commander.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.d.mts","names":[],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"commander.d.mts","names":[],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AA8OA;;;;;;AAgBA;;;;;AAoBA;;;;;AA+CA;;;;;AA4EA;;;AACY,iBA/SI,eA+SJ,CAAA,eAAA,MAAA,EAAA,gBA/S2D,SAAA,CAAU,YA+SrE,GA/SoF,SAAA,CAAU,YA+S9F,CAAA,CAAA,OAAA,CAAA,EA9SD,gBA8SC,CAAA,EA7ST,SA6SS,CA7SC,MA6SD,GA7SU,cA6SV,EA7S0B,OA6S1B,CAAA;;;;;iBAhKI,8BAA8B,0BAClC,SAAS,gBACZ,cAAA,CAAe;;;;iBAcR,gCAAgC,0BACpC,SAAS;;;;;iBAmBL,2BAA2B,0BAC/B,SAAS;;;;;iBA8CL,2BAA2B,0BAC/B,SAAS;;;;iBA2EL,gCAAgC,0BACpC,SAAS"}
|
|
@@ -4,6 +4,21 @@ import { COMMAND_SYMBOL, UNDOABLE_COMMAND_SYMBOL, isUndoableCommand } from "./ty
|
|
|
4
4
|
//#region src/zustand-commander/commander.ts
|
|
5
5
|
const DEFAULT_OPTIONS = { maxUndoStackSize: 100 };
|
|
6
6
|
/**
|
|
7
|
+
* Build a transaction function that routes to draft or document.
|
|
8
|
+
*/
|
|
9
|
+
function buildTransaction(storeApi) {
|
|
10
|
+
return (fn) => {
|
|
11
|
+
const state = storeApi.getState();
|
|
12
|
+
const draft = state._commander.activeDraft;
|
|
13
|
+
if (draft) draft.update(fn);
|
|
14
|
+
else {
|
|
15
|
+
const mimic = state.mimic;
|
|
16
|
+
if (!(mimic === null || mimic === void 0 ? void 0 : mimic.document)) throw new Error("Commander: No active draft and no mimic document found on the store.");
|
|
17
|
+
mimic.document.transaction(fn);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
7
22
|
* Creates a commander instance bound to a specific store type.
|
|
8
23
|
*
|
|
9
24
|
* @example
|
|
@@ -15,8 +30,7 @@ const DEFAULT_OPTIONS = { maxUndoStackSize: 100 };
|
|
|
15
30
|
* const addItem = commander.action(
|
|
16
31
|
* Schema.Struct({ name: Schema.String }),
|
|
17
32
|
* (ctx, params) => {
|
|
18
|
-
*
|
|
19
|
-
* mimic.document.transaction(root => {
|
|
33
|
+
* ctx.transaction(root => {
|
|
20
34
|
* // add item
|
|
21
35
|
* });
|
|
22
36
|
* }
|
|
@@ -45,10 +59,12 @@ function createCommander(options = {}) {
|
|
|
45
59
|
const ctx = {
|
|
46
60
|
getState: () => _storeApi.getState(),
|
|
47
61
|
setState: (partial) => _storeApi.setState(partial),
|
|
48
|
-
dispatch: createDispatch()
|
|
62
|
+
dispatch: createDispatch(),
|
|
63
|
+
transaction: buildTransaction(_storeApi)
|
|
49
64
|
};
|
|
50
65
|
const result = command.fn(ctx, params);
|
|
51
|
-
|
|
66
|
+
const hasDraft = _storeApi.getState()._commander.activeDraft !== null;
|
|
67
|
+
if (isUndoableCommand(command) && !hasDraft) {
|
|
52
68
|
const entry = {
|
|
53
69
|
command,
|
|
54
70
|
params,
|
|
@@ -56,12 +72,12 @@ function createCommander(options = {}) {
|
|
|
56
72
|
timestamp: Date.now()
|
|
57
73
|
};
|
|
58
74
|
_storeApi.setState((state) => {
|
|
59
|
-
const { undoStack, redoStack } = state._commander;
|
|
75
|
+
const { undoStack, redoStack: _redoStack } = state._commander;
|
|
60
76
|
const newUndoStack = [...undoStack, entry].slice(-maxUndoStackSize);
|
|
61
|
-
return _objectSpread2(_objectSpread2({}, state), {}, { _commander: {
|
|
77
|
+
return _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, {
|
|
62
78
|
undoStack: newUndoStack,
|
|
63
79
|
redoStack: []
|
|
64
|
-
} });
|
|
80
|
+
}) });
|
|
65
81
|
});
|
|
66
82
|
}
|
|
67
83
|
return result;
|
|
@@ -96,7 +112,8 @@ function createCommander(options = {}) {
|
|
|
96
112
|
_storeApi = api;
|
|
97
113
|
return _objectSpread2(_objectSpread2({}, config(set, get, api)), {}, { _commander: {
|
|
98
114
|
undoStack: [],
|
|
99
|
-
redoStack: []
|
|
115
|
+
redoStack: [],
|
|
116
|
+
activeDraft: null
|
|
100
117
|
} });
|
|
101
118
|
};
|
|
102
119
|
};
|
|
@@ -107,39 +124,56 @@ function createCommander(options = {}) {
|
|
|
107
124
|
};
|
|
108
125
|
}
|
|
109
126
|
/**
|
|
127
|
+
* Set the active draft on the commander slice.
|
|
128
|
+
* While a draft is active, transactions route through `draft.update()` and undo is disabled.
|
|
129
|
+
*/
|
|
130
|
+
function setActiveDraft(storeApi, draft) {
|
|
131
|
+
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, { activeDraft: draft }) }));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Clear the active draft from the commander slice.
|
|
135
|
+
*/
|
|
136
|
+
function clearActiveDraft(storeApi) {
|
|
137
|
+
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, { activeDraft: null }) }));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
110
140
|
* Perform an undo operation on the store.
|
|
111
|
-
* Returns true if an undo was performed, false if undo stack was empty.
|
|
141
|
+
* Returns true if an undo was performed, false if undo stack was empty or a draft is active.
|
|
112
142
|
*/
|
|
113
143
|
function performUndo(storeApi) {
|
|
114
|
-
const { undoStack, redoStack } = storeApi.getState()._commander;
|
|
144
|
+
const { undoStack, redoStack, activeDraft } = storeApi.getState()._commander;
|
|
145
|
+
if (activeDraft) return false;
|
|
115
146
|
const entry = undoStack[undoStack.length - 1];
|
|
116
147
|
if (!entry) return false;
|
|
117
148
|
const newUndoStack = undoStack.slice(0, -1);
|
|
118
149
|
const ctx = {
|
|
119
150
|
getState: () => storeApi.getState(),
|
|
120
151
|
setState: (partial) => storeApi.setState(partial),
|
|
121
|
-
dispatch: createDispatchForUndo(storeApi)
|
|
152
|
+
dispatch: createDispatchForUndo(storeApi),
|
|
153
|
+
transaction: buildTransaction(storeApi)
|
|
122
154
|
};
|
|
123
155
|
entry.command.revert(ctx, entry.params, entry.result);
|
|
124
|
-
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: {
|
|
156
|
+
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, {
|
|
125
157
|
undoStack: newUndoStack,
|
|
126
158
|
redoStack: [...redoStack, entry]
|
|
127
|
-
} }));
|
|
159
|
+
}) }));
|
|
128
160
|
return true;
|
|
129
161
|
}
|
|
130
162
|
/**
|
|
131
163
|
* Perform a redo operation on the store.
|
|
132
|
-
* Returns true if a redo was performed, false if redo stack was empty.
|
|
164
|
+
* Returns true if a redo was performed, false if redo stack was empty or a draft is active.
|
|
133
165
|
*/
|
|
134
166
|
function performRedo(storeApi) {
|
|
135
|
-
const { undoStack, redoStack } = storeApi.getState()._commander;
|
|
167
|
+
const { undoStack, redoStack, activeDraft } = storeApi.getState()._commander;
|
|
168
|
+
if (activeDraft) return false;
|
|
136
169
|
const entry = redoStack[redoStack.length - 1];
|
|
137
170
|
if (!entry) return false;
|
|
138
171
|
const newRedoStack = redoStack.slice(0, -1);
|
|
139
172
|
const ctx = {
|
|
140
173
|
getState: () => storeApi.getState(),
|
|
141
174
|
setState: (partial) => storeApi.setState(partial),
|
|
142
|
-
dispatch: createDispatchForUndo(storeApi)
|
|
175
|
+
dispatch: createDispatchForUndo(storeApi),
|
|
176
|
+
transaction: buildTransaction(storeApi)
|
|
143
177
|
};
|
|
144
178
|
const result = entry.command.fn(ctx, entry.params);
|
|
145
179
|
const newEntry = {
|
|
@@ -148,10 +182,10 @@ function performRedo(storeApi) {
|
|
|
148
182
|
result,
|
|
149
183
|
timestamp: Date.now()
|
|
150
184
|
};
|
|
151
|
-
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: {
|
|
185
|
+
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, {
|
|
152
186
|
undoStack: [...undoStack, newEntry],
|
|
153
187
|
redoStack: newRedoStack
|
|
154
|
-
} }));
|
|
188
|
+
}) }));
|
|
155
189
|
return true;
|
|
156
190
|
}
|
|
157
191
|
/**
|
|
@@ -164,7 +198,8 @@ function createDispatchForUndo(storeApi) {
|
|
|
164
198
|
const ctx = {
|
|
165
199
|
getState: () => storeApi.getState(),
|
|
166
200
|
setState: (partial) => storeApi.setState(partial),
|
|
167
|
-
dispatch: createDispatchForUndo(storeApi)
|
|
201
|
+
dispatch: createDispatchForUndo(storeApi),
|
|
202
|
+
transaction: buildTransaction(storeApi)
|
|
168
203
|
};
|
|
169
204
|
return command.fn(ctx, params);
|
|
170
205
|
};
|
|
@@ -174,12 +209,12 @@ function createDispatchForUndo(storeApi) {
|
|
|
174
209
|
* Clear the undo and redo stacks.
|
|
175
210
|
*/
|
|
176
211
|
function clearUndoHistory(storeApi) {
|
|
177
|
-
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: {
|
|
212
|
+
storeApi.setState((state) => _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, {
|
|
178
213
|
undoStack: [],
|
|
179
214
|
redoStack: []
|
|
180
|
-
} }));
|
|
215
|
+
}) }));
|
|
181
216
|
}
|
|
182
217
|
|
|
183
218
|
//#endregion
|
|
184
|
-
export { clearUndoHistory, createCommander, performRedo, performUndo };
|
|
219
|
+
export { clearActiveDraft, clearUndoHistory, createCommander, performRedo, performUndo, setActiveDraft };
|
|
185
220
|
//# sourceMappingURL=commander.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commander.mjs","names":["DEFAULT_OPTIONS: Required<CommanderOptions>","_storeApi: StoreApi<TStore & CommanderSlice> | null","ctx: CommandContext<TStore & CommanderSlice>","entry: UndoEntry<TParams, TReturn>","ctx: CommandContext<TStore>","newEntry: UndoEntry"],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":["/**\n * @voidhash/mimic-react/zustand-commander\n *\n * Commander creation and command definition.\n *\n * @since 0.0.1\n */\n\nimport type { StoreApi } from \"zustand\";\nimport {\n COMMAND_SYMBOL,\n UNDOABLE_COMMAND_SYMBOL,\n isUndoableCommand,\n type Command,\n type Commander,\n type CommanderOptions,\n type CommanderSlice,\n type CommandContext,\n type CommandDispatch,\n type CommandFn,\n type RevertFn,\n type UndoableCommand,\n type UndoEntry,\n} from \"./types\";\n\n// =============================================================================\n// Default Options\n// =============================================================================\n\nconst DEFAULT_OPTIONS: Required<CommanderOptions> = {\n maxUndoStackSize: 100,\n};\n\n// =============================================================================\n// Commander Implementation\n// =============================================================================\n\n/**\n * Creates a commander instance bound to a specific store type.\n *\n * @example\n * ```ts\n * // Create commander for your store type\n * const commander = createCommander<StoreState>();\n *\n * // Define commands\n * const addItem = commander.action(\n * Schema.Struct({ name: Schema.String }),\n * (ctx, params) => {\n * const { mimic } = ctx.getState();\n * mimic.document.transaction(root => {\n * // add item\n * });\n * }\n * );\n *\n * // Create store with middleware\n * const useStore = create(\n * commander.middleware(\n * mimic(document, (set, get) => ({\n * // your state\n * }))\n * )\n * );\n * ```\n */\nexport function createCommander<TStore extends object>(\n options: CommanderOptions = {}\n): Commander<TStore & CommanderSlice> {\n const { maxUndoStackSize } = { ...DEFAULT_OPTIONS, ...options };\n\n // Track the store API once middleware is applied\n let _storeApi: StoreApi<TStore & CommanderSlice> | null = null;\n\n /**\n * Creates the dispatch function for use within command handlers.\n */\n const createDispatch = (): CommandDispatch<TStore & CommanderSlice> => {\n return <TParams, TReturn>(\n command: Command<TStore & CommanderSlice, TParams, TReturn>\n ) => {\n return (params: TParams): TReturn => {\n if (!_storeApi) {\n throw new Error(\n \"Commander: Store not initialized. Make sure to use the commander middleware.\"\n );\n }\n\n // Create context for the command\n const ctx: CommandContext<TStore & CommanderSlice> = {\n getState: () => _storeApi!.getState(),\n setState: (partial) => _storeApi!.setState(partial as any),\n dispatch: createDispatch(),\n };\n\n // Execute the command\n const result = command.fn(ctx, params);\n\n // If it's an undoable command, add to undo stack\n if (isUndoableCommand(command)) {\n const entry: UndoEntry<TParams, TReturn> = {\n command,\n params,\n result,\n timestamp: Date.now(),\n };\n\n _storeApi.setState((state: TStore & CommanderSlice) => {\n const { undoStack, redoStack } = state._commander;\n\n // Add to undo stack, respecting max size\n const newUndoStack = [...undoStack, entry].slice(-maxUndoStackSize);\n\n // Clear redo stack when a new command is executed\n return {\n ...state,\n _commander: {\n undoStack: newUndoStack,\n redoStack: [],\n },\n };\n });\n }\n\n return result;\n };\n };\n };\n\n /**\n * Create a regular command (no undo support).\n */\n function action<TParams, TReturn = void>(\n fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>\n ): Command<TStore & CommanderSlice, TParams, TReturn> {\n return {\n [COMMAND_SYMBOL]: true,\n fn,\n };\n }\n\n /**\n * Create an undoable command with undo/redo support.\n */\n function undoableAction<TParams, TReturn>(\n fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>,\n revert: RevertFn<TStore & CommanderSlice, TParams, TReturn>\n ): UndoableCommand<TStore & CommanderSlice, TParams, TReturn> {\n return {\n [COMMAND_SYMBOL]: true,\n [UNDOABLE_COMMAND_SYMBOL]: true,\n fn,\n revert,\n };\n }\n\n /**\n * Zustand middleware that adds commander functionality.\n */\n const middleware = <T extends object>(\n config: (\n set: StoreApi<T & CommanderSlice>[\"setState\"],\n get: StoreApi<T & CommanderSlice>[\"getState\"],\n api: StoreApi<T & CommanderSlice>\n ) => T\n ) => {\n return (\n set: StoreApi<T & CommanderSlice>[\"setState\"],\n get: StoreApi<T & CommanderSlice>[\"getState\"],\n api: StoreApi<T & CommanderSlice>\n ): T & CommanderSlice => {\n // Store the API reference for dispatch\n _storeApi = api as unknown as StoreApi<TStore & CommanderSlice>;\n\n // Get user's state\n const userState = config(set, get, api);\n\n // Add commander slice\n return {\n ...userState,\n _commander: {\n undoStack: [],\n redoStack: [],\n },\n };\n };\n };\n\n return {\n action,\n undoableAction,\n middleware: middleware as Commander<TStore & CommanderSlice>[\"middleware\"],\n };\n}\n\n// =============================================================================\n// Undo/Redo Functions\n// =============================================================================\n\n/**\n * Perform an undo operation on the store.\n * Returns true if an undo was performed, false if undo stack was empty.\n */\nexport function performUndo<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): boolean {\n const state = storeApi.getState();\n const { undoStack, redoStack } = state._commander;\n\n // Pop the last entry from undo stack\n const entry = undoStack[undoStack.length - 1];\n if (!entry) {\n return false;\n }\n\n const newUndoStack = undoStack.slice(0, -1);\n\n // Create context for the revert function\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n };\n\n // Execute the revert function\n entry.command.revert(ctx, entry.params, entry.result);\n\n // Move entry to redo stack\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n undoStack: newUndoStack,\n redoStack: [...redoStack, entry],\n },\n }));\n\n return true;\n}\n\n/**\n * Perform a redo operation on the store.\n * Returns true if a redo was performed, false if redo stack was empty.\n */\nexport function performRedo<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): boolean {\n const state = storeApi.getState();\n const { undoStack, redoStack } = state._commander;\n\n // Pop the last entry from redo stack\n const entry = redoStack[redoStack.length - 1];\n if (!entry) {\n return false;\n }\n\n const newRedoStack = redoStack.slice(0, -1);\n\n // Create context for re-executing the command\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n };\n\n // Re-execute the command\n const result = entry.command.fn(ctx, entry.params);\n\n // Create new entry with potentially new result\n const newEntry: UndoEntry = {\n command: entry.command,\n params: entry.params,\n result,\n timestamp: Date.now(),\n };\n\n // Move entry back to undo stack\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n undoStack: [...undoStack, newEntry],\n redoStack: newRedoStack,\n },\n }));\n\n return true;\n}\n\n/**\n * Creates a dispatch function for use during undo/redo operations.\n * This dispatch does NOT add to undo stack (to avoid infinite loops).\n */\nfunction createDispatchForUndo<TStore>(\n storeApi: StoreApi<TStore>\n): CommandDispatch<TStore> {\n return <TParams, TReturn>(command: Command<TStore, TParams, TReturn>) => {\n return (params: TParams): TReturn => {\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n };\n\n // Execute without adding to undo stack\n return command.fn(ctx, params);\n };\n };\n}\n\n/**\n * Clear the undo and redo stacks.\n */\nexport function clearUndoHistory<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): void {\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n undoStack: [],\n redoStack: [],\n },\n }));\n}\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"commander.mjs","names":["DEFAULT_OPTIONS: Required<CommanderOptions>","_storeApi: StoreApi<TStore & CommanderSlice> | null","ctx: CommandContext<TStore & CommanderSlice, TSchema>","entry: UndoEntry<TParams, TReturn>","ctx: CommandContext<TStore>","newEntry: UndoEntry"],"sources":["../../src/zustand-commander/commander.ts"],"sourcesContent":["/**\n * @voidhash/mimic-react/zustand-commander\n *\n * Commander creation and command definition.\n *\n * @since 0.0.1\n */\n\nimport type { StoreApi } from \"zustand\";\nimport type { Primitive } from \"@voidhash/mimic\";\nimport type { ClientDocument } from \"@voidhash/mimic/client\";\nimport {\n COMMAND_SYMBOL,\n UNDOABLE_COMMAND_SYMBOL,\n isUndoableCommand,\n type Command,\n type Commander,\n type CommanderOptions,\n type CommanderSlice,\n type CommandContext,\n type CommandDispatch,\n type CommandFn,\n type RevertFn,\n type UndoableCommand,\n type UndoEntry,\n} from \"./types\";\n\n// =============================================================================\n// Default Options\n// =============================================================================\n\nconst DEFAULT_OPTIONS: Required<CommanderOptions> = {\n maxUndoStackSize: 100,\n};\n\n// =============================================================================\n// Transaction Helper\n// =============================================================================\n\n/**\n * Build a transaction function that routes to draft or document.\n */\nfunction buildTransaction<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(\n storeApi: StoreApi<TStore>\n): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {\n return (fn) => {\n const state = storeApi.getState();\n const draft = state._commander.activeDraft;\n if (draft) {\n draft.update(fn);\n } else {\n // Access mimic.document from the store\n const mimic = (state as any).mimic;\n if (!mimic?.document) {\n throw new Error(\n \"Commander: No active draft and no mimic document found on the store.\"\n );\n }\n mimic.document.transaction(fn);\n }\n };\n}\n\n// =============================================================================\n// Commander Implementation\n// =============================================================================\n\n/**\n * Creates a commander instance bound to a specific store type.\n *\n * @example\n * ```ts\n * // Create commander for your store type\n * const commander = createCommander<StoreState>();\n *\n * // Define commands\n * const addItem = commander.action(\n * Schema.Struct({ name: Schema.String }),\n * (ctx, params) => {\n * ctx.transaction(root => {\n * // add item\n * });\n * }\n * );\n *\n * // Create store with middleware\n * const useStore = create(\n * commander.middleware(\n * mimic(document, (set, get) => ({\n * // your state\n * }))\n * )\n * );\n * ```\n */\nexport function createCommander<TStore extends object, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(\n options: CommanderOptions = {}\n): Commander<TStore & CommanderSlice, TSchema> {\n const { maxUndoStackSize } = { ...DEFAULT_OPTIONS, ...options };\n\n // Track the store API once middleware is applied\n let _storeApi: StoreApi<TStore & CommanderSlice> | null = null;\n\n /**\n * Creates the dispatch function for use within command handlers.\n */\n const createDispatch = (): CommandDispatch<TStore & CommanderSlice, TSchema> => {\n return <TParams, TReturn>(\n command: Command<TStore & CommanderSlice, TParams, TReturn, TSchema>\n ) => {\n return (params: TParams): TReturn => {\n if (!_storeApi) {\n throw new Error(\n \"Commander: Store not initialized. Make sure to use the commander middleware.\"\n );\n }\n\n // Create context for the command\n const ctx: CommandContext<TStore & CommanderSlice, TSchema> = {\n getState: () => _storeApi!.getState(),\n setState: (partial) => _storeApi!.setState(partial as any),\n dispatch: createDispatch(),\n transaction: buildTransaction<TStore & CommanderSlice, TSchema>(_storeApi!),\n };\n\n // Execute the command\n const result = command.fn(ctx, params);\n\n // Skip undo stack when a draft is active\n const hasDraft = _storeApi!.getState()._commander.activeDraft !== null;\n\n // If it's an undoable command and no draft is active, add to undo stack\n if (isUndoableCommand(command) && !hasDraft) {\n const entry: UndoEntry<TParams, TReturn> = {\n command,\n params,\n result,\n timestamp: Date.now(),\n };\n\n _storeApi.setState((state: TStore & CommanderSlice) => {\n const { undoStack, redoStack: _redoStack } = state._commander;\n\n // Add to undo stack, respecting max size\n const newUndoStack = [...undoStack, entry].slice(-maxUndoStackSize);\n\n // Clear redo stack when a new command is executed\n return {\n ...state,\n _commander: {\n ...state._commander,\n undoStack: newUndoStack,\n redoStack: [],\n },\n };\n });\n }\n\n return result;\n };\n };\n };\n\n /**\n * Create a regular command (no undo support).\n */\n function action<TParams, TReturn = void>(\n fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>\n ): Command<TStore & CommanderSlice, TParams, TReturn, TSchema> {\n return {\n [COMMAND_SYMBOL]: true,\n fn,\n };\n }\n\n /**\n * Create an undoable command with undo/redo support.\n */\n function undoableAction<TParams, TReturn>(\n fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>,\n revert: RevertFn<TStore & CommanderSlice, TParams, TReturn, TSchema>\n ): UndoableCommand<TStore & CommanderSlice, TParams, TReturn, TSchema> {\n return {\n [COMMAND_SYMBOL]: true,\n [UNDOABLE_COMMAND_SYMBOL]: true,\n fn,\n revert,\n };\n }\n\n /**\n * Zustand middleware that adds commander functionality.\n */\n const middleware = <T extends object>(\n config: (\n set: StoreApi<T & CommanderSlice>[\"setState\"],\n get: StoreApi<T & CommanderSlice>[\"getState\"],\n api: StoreApi<T & CommanderSlice>\n ) => T\n ) => {\n return (\n set: StoreApi<T & CommanderSlice>[\"setState\"],\n get: StoreApi<T & CommanderSlice>[\"getState\"],\n api: StoreApi<T & CommanderSlice>\n ): T & CommanderSlice => {\n // Store the API reference for dispatch\n _storeApi = api as unknown as StoreApi<TStore & CommanderSlice>;\n\n // Get user's state\n const userState = config(set, get, api);\n\n // Add commander slice\n return {\n ...userState,\n _commander: {\n undoStack: [],\n redoStack: [],\n activeDraft: null,\n },\n };\n };\n };\n\n return {\n action,\n undoableAction,\n middleware: middleware as Commander<TStore & CommanderSlice, TSchema>[\"middleware\"],\n };\n}\n\n// =============================================================================\n// Draft Helpers\n// =============================================================================\n\n/**\n * Set the active draft on the commander slice.\n * While a draft is active, transactions route through `draft.update()` and undo is disabled.\n */\nexport function setActiveDraft<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>,\n draft: ClientDocument.DraftHandle<any>\n): void {\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n ...state._commander,\n activeDraft: draft,\n },\n }));\n}\n\n/**\n * Clear the active draft from the commander slice.\n */\nexport function clearActiveDraft<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): void {\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n ...state._commander,\n activeDraft: null,\n },\n }));\n}\n\n// =============================================================================\n// Undo/Redo Functions\n// =============================================================================\n\n/**\n * Perform an undo operation on the store.\n * Returns true if an undo was performed, false if undo stack was empty or a draft is active.\n */\nexport function performUndo<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): boolean {\n const state = storeApi.getState();\n const { undoStack, redoStack, activeDraft } = state._commander;\n\n // Undo is disabled while a draft is active\n if (activeDraft) {\n return false;\n }\n\n // Pop the last entry from undo stack\n const entry = undoStack[undoStack.length - 1];\n if (!entry) {\n return false;\n }\n\n const newUndoStack = undoStack.slice(0, -1);\n\n // Create context for the revert function\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n transaction: buildTransaction(storeApi),\n };\n\n // Execute the revert function\n entry.command.revert(ctx, entry.params, entry.result);\n\n // Move entry to redo stack\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n ...state._commander,\n undoStack: newUndoStack,\n redoStack: [...redoStack, entry],\n },\n }));\n\n return true;\n}\n\n/**\n * Perform a redo operation on the store.\n * Returns true if a redo was performed, false if redo stack was empty or a draft is active.\n */\nexport function performRedo<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): boolean {\n const state = storeApi.getState();\n const { undoStack, redoStack, activeDraft } = state._commander;\n\n // Redo is disabled while a draft is active\n if (activeDraft) {\n return false;\n }\n\n // Pop the last entry from redo stack\n const entry = redoStack[redoStack.length - 1];\n if (!entry) {\n return false;\n }\n\n const newRedoStack = redoStack.slice(0, -1);\n\n // Create context for re-executing the command\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n transaction: buildTransaction(storeApi),\n };\n\n // Re-execute the command\n const result = entry.command.fn(ctx, entry.params);\n\n // Create new entry with potentially new result\n const newEntry: UndoEntry = {\n command: entry.command,\n params: entry.params,\n result,\n timestamp: Date.now(),\n };\n\n // Move entry back to undo stack\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n ...state._commander,\n undoStack: [...undoStack, newEntry],\n redoStack: newRedoStack,\n },\n }));\n\n return true;\n}\n\n/**\n * Creates a dispatch function for use during undo/redo operations.\n * This dispatch does NOT add to undo stack (to avoid infinite loops).\n */\nfunction createDispatchForUndo<TStore>(\n storeApi: StoreApi<TStore>\n): CommandDispatch<TStore> {\n return <TParams, TReturn>(command: Command<TStore, TParams, TReturn>) => {\n return (params: TParams): TReturn => {\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as any),\n dispatch: createDispatchForUndo(storeApi),\n transaction: buildTransaction(storeApi),\n };\n\n // Execute without adding to undo stack\n return command.fn(ctx, params);\n };\n };\n}\n\n/**\n * Clear the undo and redo stacks.\n */\nexport function clearUndoHistory<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>\n): void {\n storeApi.setState((state: TStore) => ({\n ...state,\n _commander: {\n ...state._commander,\n undoStack: [],\n redoStack: [],\n },\n }));\n}\n"],"mappings":"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"}
|
|
@@ -16,16 +16,30 @@ let zustand = require("zustand");
|
|
|
16
16
|
* Creates a dispatch function for commands.
|
|
17
17
|
* This is for use outside of React components (e.g., in command handlers).
|
|
18
18
|
*/
|
|
19
|
+
function buildTransactionFromApi(storeApi) {
|
|
20
|
+
return (fn) => {
|
|
21
|
+
const state = storeApi.getState();
|
|
22
|
+
const draft = state._commander.activeDraft;
|
|
23
|
+
if (draft) draft.update(fn);
|
|
24
|
+
else {
|
|
25
|
+
const mimic = state.mimic;
|
|
26
|
+
if (!(mimic === null || mimic === void 0 ? void 0 : mimic.document)) throw new Error("Commander: No active draft and no mimic document found on the store.");
|
|
27
|
+
mimic.document.transaction(fn);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
function createDispatchFromApi(storeApi, maxUndoStackSize = 100) {
|
|
20
32
|
const dispatch = (command) => {
|
|
21
33
|
return (params) => {
|
|
22
34
|
const ctx = {
|
|
23
35
|
getState: () => storeApi.getState(),
|
|
24
36
|
setState: (partial) => storeApi.setState(partial),
|
|
25
|
-
dispatch
|
|
37
|
+
dispatch,
|
|
38
|
+
transaction: buildTransactionFromApi(storeApi)
|
|
26
39
|
};
|
|
27
40
|
const result = command.fn(ctx, params);
|
|
28
|
-
|
|
41
|
+
const hasDraft = storeApi.getState()._commander.activeDraft !== null;
|
|
42
|
+
if (require_types.isUndoableCommand(command) && !hasDraft) storeApi.setState((state) => {
|
|
29
43
|
const { undoStack } = state._commander;
|
|
30
44
|
const newUndoStack = [...undoStack, {
|
|
31
45
|
command,
|
|
@@ -33,10 +47,10 @@ function createDispatchFromApi(storeApi, maxUndoStackSize = 100) {
|
|
|
33
47
|
result,
|
|
34
48
|
timestamp: Date.now()
|
|
35
49
|
}].slice(-maxUndoStackSize);
|
|
36
|
-
return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, state), {}, { _commander: {
|
|
50
|
+
return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, state), {}, { _commander: require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, state._commander), {}, {
|
|
37
51
|
undoStack: newUndoStack,
|
|
38
52
|
redoStack: []
|
|
39
|
-
} });
|
|
53
|
+
}) });
|
|
40
54
|
});
|
|
41
55
|
return result;
|
|
42
56
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CommandDispatch, CommanderSlice } from "./types.cjs";
|
|
2
2
|
import { StoreApi, UseBoundStore } from "zustand";
|
|
3
|
+
import { Primitive } from "@voidhash/mimic";
|
|
3
4
|
|
|
4
5
|
//#region src/zustand-commander/hooks.d.ts
|
|
5
6
|
|
|
@@ -16,7 +17,7 @@ import { StoreApi, UseBoundStore } from "zustand";
|
|
|
16
17
|
* };
|
|
17
18
|
* ```
|
|
18
19
|
*/
|
|
19
|
-
declare function useCommander<TStore extends CommanderSlice>(store: UseBoundStore<StoreApi<TStore>>): CommandDispatch<TStore>;
|
|
20
|
+
declare function useCommander<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(store: UseBoundStore<StoreApi<TStore>>): CommandDispatch<TStore, TSchema>;
|
|
20
21
|
/**
|
|
21
22
|
* State and actions for undo/redo functionality.
|
|
22
23
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.cts","names":[],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.cts","names":[],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAgJA;AAiCA;;;;;;AAEgB,iBA5DA,YA4DA,CAAA,eA5D4B,cA4D5B,EAAA,gBA5D4D,SAAA,CAAU,YA4DtE,GA5DqF,SAAA,CAAU,YA4D/F,CAAA,CAAA,KAAA,EA3DP,aA2DO,CA3DO,QA2DP,CA3DgB,MA2DhB,CAAA,CAAA,CAAA,EA1Db,eA0Da,CA1DG,MA0DH,EA1DW,OA0DX,CAAA;AA+ChB;AAiBA;;AACgC,UApGf,aAAA,CAoGe;EAAT;EAAd,SAAA,OAAA,EAAA,OAAA;EACE;EAA+B,SAAA,OAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBApE1B,2BAA2B,uBAClC,cAAc,SAAS,WAC7B;;;;UA+Cc,0BAAA;;;;;;;;;;;;;;;;iBAiBD,mCAAmC,uBAC1C,cAAc,SAAS,oBACrB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CommandDispatch, CommanderSlice } from "./types.mjs";
|
|
2
2
|
import { StoreApi, UseBoundStore } from "zustand";
|
|
3
|
+
import { Primitive } from "@voidhash/mimic";
|
|
3
4
|
|
|
4
5
|
//#region src/zustand-commander/hooks.d.ts
|
|
5
6
|
|
|
@@ -16,7 +17,7 @@ import { StoreApi, UseBoundStore } from "zustand";
|
|
|
16
17
|
* };
|
|
17
18
|
* ```
|
|
18
19
|
*/
|
|
19
|
-
declare function useCommander<TStore extends CommanderSlice>(store: UseBoundStore<StoreApi<TStore>>): CommandDispatch<TStore>;
|
|
20
|
+
declare function useCommander<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(store: UseBoundStore<StoreApi<TStore>>): CommandDispatch<TStore, TSchema>;
|
|
20
21
|
/**
|
|
21
22
|
* State and actions for undo/redo functionality.
|
|
22
23
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.mts","names":[],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hooks.d.mts","names":[],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAgJA;AAiCA;;;;;;AAEgB,iBA5DA,YA4DA,CAAA,eA5D4B,cA4D5B,EAAA,gBA5D4D,SAAA,CAAU,YA4DtE,GA5DqF,SAAA,CAAU,YA4D/F,CAAA,CAAA,KAAA,EA3DP,aA2DO,CA3DO,QA2DP,CA3DgB,MA2DhB,CAAA,CAAA,CAAA,EA1Db,eA0Da,CA1DG,MA0DH,EA1DW,OA0DX,CAAA;AA+ChB;AAiBA;;AACgC,UApGf,aAAA,CAoGe;EAAT;EAAd,SAAA,OAAA,EAAA,OAAA;EACE;EAA+B,SAAA,OAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBApE1B,2BAA2B,uBAClC,cAAc,SAAS,WAC7B;;;;UA+Cc,0BAAA;;;;;;;;;;;;;;;;iBAiBD,mCAAmC,uBAC1C,cAAc,SAAS,oBACrB"}
|
|
@@ -16,16 +16,30 @@ import { useStore } from "zustand";
|
|
|
16
16
|
* Creates a dispatch function for commands.
|
|
17
17
|
* This is for use outside of React components (e.g., in command handlers).
|
|
18
18
|
*/
|
|
19
|
+
function buildTransactionFromApi(storeApi) {
|
|
20
|
+
return (fn) => {
|
|
21
|
+
const state = storeApi.getState();
|
|
22
|
+
const draft = state._commander.activeDraft;
|
|
23
|
+
if (draft) draft.update(fn);
|
|
24
|
+
else {
|
|
25
|
+
const mimic = state.mimic;
|
|
26
|
+
if (!(mimic === null || mimic === void 0 ? void 0 : mimic.document)) throw new Error("Commander: No active draft and no mimic document found on the store.");
|
|
27
|
+
mimic.document.transaction(fn);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
function createDispatchFromApi(storeApi, maxUndoStackSize = 100) {
|
|
20
32
|
const dispatch = (command) => {
|
|
21
33
|
return (params) => {
|
|
22
34
|
const ctx = {
|
|
23
35
|
getState: () => storeApi.getState(),
|
|
24
36
|
setState: (partial) => storeApi.setState(partial),
|
|
25
|
-
dispatch
|
|
37
|
+
dispatch,
|
|
38
|
+
transaction: buildTransactionFromApi(storeApi)
|
|
26
39
|
};
|
|
27
40
|
const result = command.fn(ctx, params);
|
|
28
|
-
|
|
41
|
+
const hasDraft = storeApi.getState()._commander.activeDraft !== null;
|
|
42
|
+
if (isUndoableCommand(command) && !hasDraft) storeApi.setState((state) => {
|
|
29
43
|
const { undoStack } = state._commander;
|
|
30
44
|
const newUndoStack = [...undoStack, {
|
|
31
45
|
command,
|
|
@@ -33,10 +47,10 @@ function createDispatchFromApi(storeApi, maxUndoStackSize = 100) {
|
|
|
33
47
|
result,
|
|
34
48
|
timestamp: Date.now()
|
|
35
49
|
}].slice(-maxUndoStackSize);
|
|
36
|
-
return _objectSpread2(_objectSpread2({}, state), {}, { _commander: {
|
|
50
|
+
return _objectSpread2(_objectSpread2({}, state), {}, { _commander: _objectSpread2(_objectSpread2({}, state._commander), {}, {
|
|
37
51
|
undoStack: newUndoStack,
|
|
38
52
|
redoStack: []
|
|
39
|
-
} });
|
|
53
|
+
}) });
|
|
40
54
|
});
|
|
41
55
|
return result;
|
|
42
56
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.mjs","names":["dispatch: CommandDispatch<TStore>","ctx: CommandContext<TStore>"],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":["/**\n * @voidhash/mimic-react/zustand-commander\n *\n * React hooks for zustand-commander.\n *\n * @since 0.0.1\n */\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useStore, type StoreApi, type UseBoundStore } from \"zustand\";\nimport { performRedo, performUndo, clearUndoHistory } from \"./commander\";\nimport {\n isUndoableCommand,\n type Command,\n type CommandContext,\n type CommandDispatch,\n type CommanderSlice,\n type ExtractState,\n} from \"./types\";\n\n// =============================================================================\n// useCommander Hook\n// =============================================================================\n\n/**\n * Creates a dispatch function for commands.\n * This is for use outside of React components (e.g., in command handlers).\n */\nfunction createDispatchFromApi<TStore extends CommanderSlice>(\n storeApi: StoreApi<TStore>,\n maxUndoStackSize = 100\n): CommandDispatch<TStore> {\n const dispatch: CommandDispatch<TStore> = <TParams, TReturn>(\n command: Command<TStore, TParams, TReturn>\n ) => {\n return (params: TParams): TReturn => {\n // Create context for the command\n const ctx: CommandContext<TStore> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as Partial<TStore>),\n dispatch,\n };\n\n // Execute the command\n const result = command.fn(ctx, params);\n\n // If it's an undoable command, add to undo stack\n if (isUndoableCommand(command)) {\n storeApi.setState((state: TStore) => {\n const { undoStack } = state._commander;\n\n // Add to undo stack, respecting max size\n const newUndoStack = [\n ...undoStack,\n {\n command,\n params,\n result,\n timestamp: Date.now(),\n },\n ].slice(-maxUndoStackSize);\n\n // Clear redo stack when a new command is executed\n return {\n ...state,\n _commander: {\n undoStack: newUndoStack,\n redoStack: [],\n },\n } as TStore;\n });\n }\n\n return result;\n };\n };\n\n return dispatch;\n}\n\n/**\n * React hook to get a dispatch function for commands.\n * The dispatch function executes commands and manages undo/redo state.\n *\n * @example\n * ```tsx\n * const dispatch = useCommander(useStore);\n *\n * const handleClick = () => {\n * dispatch(addCard)({ columnId: \"col-1\", title: \"New Card\" });\n * };\n * ```\n */\nexport function useCommander<TStore extends CommanderSlice>(\n store: UseBoundStore<StoreApi<TStore>>\n): CommandDispatch<TStore> {\n // Get the store API\n const storeApi = useMemo(() => {\n // UseBoundStore has the StoreApi attached\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Create a stable dispatch function\n const dispatch = useMemo(\n () => createDispatchFromApi<TStore>(storeApi),\n [storeApi]\n );\n\n return dispatch as CommandDispatch<TStore>;\n}\n\n// =============================================================================\n// useUndoRedo Hook\n// =============================================================================\n\n/**\n * State and actions for undo/redo functionality.\n */\nexport interface UndoRedoState {\n /** Whether there are actions that can be undone */\n readonly canUndo: boolean;\n /** Whether there are actions that can be redone */\n readonly canRedo: boolean;\n /** Number of items in the undo stack */\n readonly undoCount: number;\n /** Number of items in the redo stack */\n readonly redoCount: number;\n /** Undo the last action */\n readonly undo: () => boolean;\n /** Redo the last undone action */\n readonly redo: () => boolean;\n /** Clear the undo/redo history */\n readonly clear: () => void;\n}\n\n/**\n * React hook for undo/redo functionality.\n * Provides state (canUndo, canRedo) and actions (undo, redo, clear).\n *\n * @example\n * ```tsx\n * const { canUndo, canRedo, undo, redo } = useUndoRedo(useStore);\n *\n * return (\n * <>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * </>\n * );\n * ```\n */\nexport function useUndoRedo<TStore extends CommanderSlice>(\n store: UseBoundStore<StoreApi<TStore>>\n): UndoRedoState {\n // Get the store API\n const storeApi = useMemo(() => {\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Subscribe to commander state\n const commanderState = useStore(\n store,\n (state: TStore) => state._commander\n );\n\n const canUndo = commanderState.undoStack.length > 0;\n const canRedo = commanderState.redoStack.length > 0;\n const undoCount = commanderState.undoStack.length;\n const redoCount = commanderState.redoStack.length;\n\n const undo = useCallback(() => {\n return performUndo(storeApi);\n }, [storeApi]);\n\n const redo = useCallback(() => {\n return performRedo(storeApi);\n }, [storeApi]);\n\n const clear = useCallback(() => {\n clearUndoHistory(storeApi);\n }, [storeApi]);\n\n return {\n canUndo,\n canRedo,\n undoCount,\n redoCount,\n undo,\n redo,\n clear,\n };\n}\n\n// =============================================================================\n// Keyboard Shortcut Hook\n// =============================================================================\n\n/**\n * Options for the keyboard shortcut hook.\n */\nexport interface UseUndoRedoKeyboardOptions {\n /** Enable Ctrl/Cmd+Z for undo (default: true) */\n readonly enableUndo?: boolean;\n /** Enable Ctrl/Cmd+Shift+Z or Ctrl+Y for redo (default: true) */\n readonly enableRedo?: boolean;\n}\n\n/**\n * React hook that adds keyboard shortcuts for undo/redo.\n * Listens for Ctrl/Cmd+Z (undo) and Ctrl/Cmd+Shift+Z or Ctrl+Y (redo).\n *\n * @example\n * ```tsx\n * // In your app component\n * useUndoRedoKeyboard(useStore);\n * ```\n */\nexport function useUndoRedoKeyboard<TStore extends CommanderSlice>(\n store: UseBoundStore<StoreApi<TStore>>,\n options: UseUndoRedoKeyboardOptions = {}\n): void {\n const { enableUndo = true, enableRedo = true } = options;\n\n const storeApi = useMemo(() => {\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Set up keyboard listener\n useEffect(() => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac = navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0;\n const modKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (!modKey) return;\n\n // Undo: Ctrl/Cmd + Z (without Shift)\n if (enableUndo && event.key === \"z\" && !event.shiftKey) {\n event.preventDefault();\n performUndo(storeApi);\n return;\n }\n\n // Redo: Ctrl/Cmd + Shift + Z or Ctrl + Y\n if (enableRedo) {\n if ((event.key === \"z\" && event.shiftKey) || event.key === \"y\") {\n event.preventDefault();\n performRedo(storeApi);\n }\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [storeApi, enableUndo, enableRedo]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4BA,SAAS,sBACP,UACA,mBAAmB,KACM;CACzB,MAAMA,YACJ,YACG;AACH,UAAQ,WAA6B;GAEnC,MAAMC,MAA8B;IAClC,gBAAgB,SAAS,UAAU;IACnC,WAAW,YAAY,SAAS,SAAS,QAA2B;IACpE;IACD;GAGD,MAAM,SAAS,QAAQ,GAAG,KAAK,OAAO;AAGtC,OAAI,kBAAkB,QAAQ,CAC5B,UAAS,UAAU,UAAkB;IACnC,MAAM,EAAE,cAAc,MAAM;IAG5B,MAAM,eAAe,CACnB,GAAG,WACH;KACE;KACA;KACA;KACA,WAAW,KAAK,KAAK;KACtB,CACF,CAAC,MAAM,CAAC,iBAAiB;AAG1B,6CACK,cACH,YAAY;KACV,WAAW;KACX,WAAW,EAAE;KACd;KAEH;AAGJ,UAAO;;;AAIX,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,aACd,OACyB;CAEzB,MAAM,WAAW,cAAc;AAE7B,SAAO;IACN,CAAC,MAAM,CAAC;AAQX,QALiB,cACT,sBAA8B,SAAS,EAC7C,CAAC,SAAS,CACX;;;;;;;;;;;;;;;;;;AA6CH,SAAgB,YACd,OACe;CAEf,MAAM,WAAW,cAAc;AAC7B,SAAO;IACN,CAAC,MAAM,CAAC;CAGX,MAAM,iBAAiB,SACrB,QACC,UAAkB,MAAM,WAC1B;AAmBD,QAAO;EACL,SAlBc,eAAe,UAAU,SAAS;EAmBhD,SAlBc,eAAe,UAAU,SAAS;EAmBhD,WAlBgB,eAAe,UAAU;EAmBzC,WAlBgB,eAAe,UAAU;EAmBzC,MAjBW,kBAAkB;AAC7B,UAAO,YAAY,SAAS;KAC3B,CAAC,SAAS,CAAC;EAgBZ,MAdW,kBAAkB;AAC7B,UAAO,YAAY,SAAS;KAC3B,CAAC,SAAS,CAAC;EAaZ,OAXY,kBAAkB;AAC9B,oBAAiB,SAAS;KACzB,CAAC,SAAS,CAAC;EAUb;;;;;;;;;;;;AA2BH,SAAgB,oBACd,OACA,UAAsC,EAAE,EAClC;CACN,MAAM,EAAE,aAAa,MAAM,aAAa,SAAS;CAEjD,MAAM,WAAW,cAAc;AAC7B,SAAO;IACN,CAAC,MAAM,CAAC;AAGX,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;EAGF,MAAM,iBAAiB,UAAyB;AAI9C,OAAI,EAHU,UAAU,SAAS,aAAa,CAAC,QAAQ,MAAM,IAAI,IAC1C,MAAM,UAAU,MAAM,SAEhC;AAGb,OAAI,cAAc,MAAM,QAAQ,OAAO,CAAC,MAAM,UAAU;AACtD,UAAM,gBAAgB;AACtB,gBAAY,SAAS;AACrB;;AAIF,OAAI,YACF;QAAK,MAAM,QAAQ,OAAO,MAAM,YAAa,MAAM,QAAQ,KAAK;AAC9D,WAAM,gBAAgB;AACtB,iBAAY,SAAS;;;;AAK3B,SAAO,iBAAiB,WAAW,cAAc;AACjD,eAAa,OAAO,oBAAoB,WAAW,cAAc;IAChE;EAAC;EAAU;EAAY;EAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"hooks.mjs","names":["dispatch: CommandDispatch<TStore, TSchema>","ctx: CommandContext<TStore, TSchema>"],"sources":["../../src/zustand-commander/hooks.ts"],"sourcesContent":["/**\n * @voidhash/mimic-react/zustand-commander\n *\n * React hooks for zustand-commander.\n *\n * @since 0.0.1\n */\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useStore, type StoreApi, type UseBoundStore } from \"zustand\";\nimport type { Primitive } from \"@voidhash/mimic\";\nimport { performRedo, performUndo, clearUndoHistory, setActiveDraft, clearActiveDraft } from \"./commander\";\nimport {\n isUndoableCommand,\n type Command,\n type CommandContext,\n type CommandDispatch,\n type CommanderSlice,\n type ExtractState,\n} from \"./types\";\n\n// =============================================================================\n// useCommander Hook\n// =============================================================================\n\n/**\n * Creates a dispatch function for commands.\n * This is for use outside of React components (e.g., in command handlers).\n */\nfunction buildTransactionFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(\n storeApi: StoreApi<TStore>\n): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {\n return (fn) => {\n const state = storeApi.getState();\n const draft = state._commander.activeDraft;\n if (draft) {\n draft.update(fn);\n } else {\n const mimic = (state as any).mimic;\n if (!mimic?.document) {\n throw new Error(\n \"Commander: No active draft and no mimic document found on the store.\"\n );\n }\n mimic.document.transaction(fn);\n }\n };\n}\n\nfunction createDispatchFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(\n storeApi: StoreApi<TStore>,\n maxUndoStackSize = 100\n): CommandDispatch<TStore, TSchema> {\n const dispatch: CommandDispatch<TStore, TSchema> = <TParams, TReturn>(\n command: Command<TStore, TParams, TReturn, TSchema>\n ) => {\n return (params: TParams): TReturn => {\n // Create context for the command\n const ctx: CommandContext<TStore, TSchema> = {\n getState: () => storeApi.getState(),\n setState: (partial) => storeApi.setState(partial as Partial<TStore>),\n dispatch,\n transaction: buildTransactionFromApi<TStore, TSchema>(storeApi),\n };\n\n // Execute the command\n const result = command.fn(ctx, params);\n\n // Skip undo stack when a draft is active\n const hasDraft = storeApi.getState()._commander.activeDraft !== null;\n\n // If it's an undoable command and no draft is active, add to undo stack\n if (isUndoableCommand(command) && !hasDraft) {\n storeApi.setState((state: TStore) => {\n const { undoStack } = state._commander;\n\n // Add to undo stack, respecting max size\n const newUndoStack = [\n ...undoStack,\n {\n command,\n params,\n result,\n timestamp: Date.now(),\n },\n ].slice(-maxUndoStackSize);\n\n // Clear redo stack when a new command is executed\n return {\n ...state,\n _commander: {\n ...state._commander,\n undoStack: newUndoStack,\n redoStack: [],\n },\n } as TStore;\n });\n }\n\n return result;\n };\n };\n\n return dispatch;\n}\n\n/**\n * React hook to get a dispatch function for commands.\n * The dispatch function executes commands and manages undo/redo state.\n *\n * @example\n * ```tsx\n * const dispatch = useCommander(useStore);\n *\n * const handleClick = () => {\n * dispatch(addCard)({ columnId: \"col-1\", title: \"New Card\" });\n * };\n * ```\n */\nexport function useCommander<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(\n store: UseBoundStore<StoreApi<TStore>>\n): CommandDispatch<TStore, TSchema> {\n // Get the store API\n const storeApi = useMemo(() => {\n // UseBoundStore has the StoreApi attached\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Create a stable dispatch function\n const dispatch = useMemo(\n () => createDispatchFromApi<TStore, TSchema>(storeApi),\n [storeApi]\n );\n\n return dispatch as CommandDispatch<TStore, TSchema>;\n}\n\n// =============================================================================\n// useUndoRedo Hook\n// =============================================================================\n\n/**\n * State and actions for undo/redo functionality.\n */\nexport interface UndoRedoState {\n /** Whether there are actions that can be undone */\n readonly canUndo: boolean;\n /** Whether there are actions that can be redone */\n readonly canRedo: boolean;\n /** Number of items in the undo stack */\n readonly undoCount: number;\n /** Number of items in the redo stack */\n readonly redoCount: number;\n /** Undo the last action */\n readonly undo: () => boolean;\n /** Redo the last undone action */\n readonly redo: () => boolean;\n /** Clear the undo/redo history */\n readonly clear: () => void;\n}\n\n/**\n * React hook for undo/redo functionality.\n * Provides state (canUndo, canRedo) and actions (undo, redo, clear).\n *\n * @example\n * ```tsx\n * const { canUndo, canRedo, undo, redo } = useUndoRedo(useStore);\n *\n * return (\n * <>\n * <button onClick={undo} disabled={!canUndo}>Undo</button>\n * <button onClick={redo} disabled={!canRedo}>Redo</button>\n * </>\n * );\n * ```\n */\nexport function useUndoRedo<TStore extends CommanderSlice>(\n store: UseBoundStore<StoreApi<TStore>>\n): UndoRedoState {\n // Get the store API\n const storeApi = useMemo(() => {\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Subscribe to commander state\n const commanderState = useStore(\n store,\n (state: TStore) => state._commander\n );\n\n const canUndo = commanderState.undoStack.length > 0;\n const canRedo = commanderState.redoStack.length > 0;\n const undoCount = commanderState.undoStack.length;\n const redoCount = commanderState.redoStack.length;\n\n const undo = useCallback(() => {\n return performUndo(storeApi);\n }, [storeApi]);\n\n const redo = useCallback(() => {\n return performRedo(storeApi);\n }, [storeApi]);\n\n const clear = useCallback(() => {\n clearUndoHistory(storeApi);\n }, [storeApi]);\n\n return {\n canUndo,\n canRedo,\n undoCount,\n redoCount,\n undo,\n redo,\n clear,\n };\n}\n\n// =============================================================================\n// Keyboard Shortcut Hook\n// =============================================================================\n\n/**\n * Options for the keyboard shortcut hook.\n */\nexport interface UseUndoRedoKeyboardOptions {\n /** Enable Ctrl/Cmd+Z for undo (default: true) */\n readonly enableUndo?: boolean;\n /** Enable Ctrl/Cmd+Shift+Z or Ctrl+Y for redo (default: true) */\n readonly enableRedo?: boolean;\n}\n\n/**\n * React hook that adds keyboard shortcuts for undo/redo.\n * Listens for Ctrl/Cmd+Z (undo) and Ctrl/Cmd+Shift+Z or Ctrl+Y (redo).\n *\n * @example\n * ```tsx\n * // In your app component\n * useUndoRedoKeyboard(useStore);\n * ```\n */\nexport function useUndoRedoKeyboard<TStore extends CommanderSlice>(\n store: UseBoundStore<StoreApi<TStore>>,\n options: UseUndoRedoKeyboardOptions = {}\n): void {\n const { enableUndo = true, enableRedo = true } = options;\n\n const storeApi = useMemo(() => {\n return store as unknown as StoreApi<TStore>;\n }, [store]);\n\n // Set up keyboard listener\n useEffect(() => {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac = navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0;\n const modKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (!modKey) return;\n\n // Undo: Ctrl/Cmd + Z (without Shift)\n if (enableUndo && event.key === \"z\" && !event.shiftKey) {\n event.preventDefault();\n performUndo(storeApi);\n return;\n }\n\n // Redo: Ctrl/Cmd + Shift + Z or Ctrl + Y\n if (enableRedo) {\n if ((event.key === \"z\" && event.shiftKey) || event.key === \"y\") {\n event.preventDefault();\n performRedo(storeApi);\n }\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [storeApi, enableUndo, enableRedo]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAS,wBACP,UAC6D;AAC7D,SAAQ,OAAO;EACb,MAAM,QAAQ,SAAS,UAAU;EACjC,MAAM,QAAQ,MAAM,WAAW;AAC/B,MAAI,MACF,OAAM,OAAO,GAAG;OACX;GACL,MAAM,QAAS,MAAc;AAC7B,OAAI,gDAAC,MAAO,UACV,OAAM,IAAI,MACR,uEACD;AAEH,SAAM,SAAS,YAAY,GAAG;;;;AAKpC,SAAS,sBACP,UACA,mBAAmB,KACe;CAClC,MAAMA,YACJ,YACG;AACH,UAAQ,WAA6B;GAEnC,MAAMC,MAAuC;IAC3C,gBAAgB,SAAS,UAAU;IACnC,WAAW,YAAY,SAAS,SAAS,QAA2B;IACpE;IACA,aAAa,wBAAyC,SAAS;IAChE;GAGD,MAAM,SAAS,QAAQ,GAAG,KAAK,OAAO;GAGtC,MAAM,WAAW,SAAS,UAAU,CAAC,WAAW,gBAAgB;AAGhE,OAAI,kBAAkB,QAAQ,IAAI,CAAC,SACjC,UAAS,UAAU,UAAkB;IACnC,MAAM,EAAE,cAAc,MAAM;IAG5B,MAAM,eAAe,CACnB,GAAG,WACH;KACE;KACA;KACA;KACA,WAAW,KAAK,KAAK;KACtB,CACF,CAAC,MAAM,CAAC,iBAAiB;AAG1B,6CACK,cACH,8CACK,MAAM;KACT,WAAW;KACX,WAAW,EAAE;;KAGjB;AAGJ,UAAO;;;AAIX,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,aACd,OACkC;CAElC,MAAM,WAAW,cAAc;AAE7B,SAAO;IACN,CAAC,MAAM,CAAC;AAQX,QALiB,cACT,sBAAuC,SAAS,EACtD,CAAC,SAAS,CACX;;;;;;;;;;;;;;;;;;AA6CH,SAAgB,YACd,OACe;CAEf,MAAM,WAAW,cAAc;AAC7B,SAAO;IACN,CAAC,MAAM,CAAC;CAGX,MAAM,iBAAiB,SACrB,QACC,UAAkB,MAAM,WAC1B;AAmBD,QAAO;EACL,SAlBc,eAAe,UAAU,SAAS;EAmBhD,SAlBc,eAAe,UAAU,SAAS;EAmBhD,WAlBgB,eAAe,UAAU;EAmBzC,WAlBgB,eAAe,UAAU;EAmBzC,MAjBW,kBAAkB;AAC7B,UAAO,YAAY,SAAS;KAC3B,CAAC,SAAS,CAAC;EAgBZ,MAdW,kBAAkB;AAC7B,UAAO,YAAY,SAAS;KAC3B,CAAC,SAAS,CAAC;EAaZ,OAXY,kBAAkB;AAC9B,oBAAiB,SAAS;KACzB,CAAC,SAAS,CAAC;EAUb;;;;;;;;;;;;AA2BH,SAAgB,oBACd,OACA,UAAsC,EAAE,EAClC;CACN,MAAM,EAAE,aAAa,MAAM,aAAa,SAAS;CAEjD,MAAM,WAAW,cAAc;AAC7B,SAAO;IACN,CAAC,MAAM,CAAC;AAGX,iBAAgB;AACd,MAAI,OAAO,WAAW,YACpB;EAGF,MAAM,iBAAiB,UAAyB;AAI9C,OAAI,EAHU,UAAU,SAAS,aAAa,CAAC,QAAQ,MAAM,IAAI,IAC1C,MAAM,UAAU,MAAM,SAEhC;AAGb,OAAI,cAAc,MAAM,QAAQ,OAAO,CAAC,MAAM,UAAU;AACtD,UAAM,gBAAgB;AACtB,gBAAY,SAAS;AACrB;;AAIF,OAAI,YACF;QAAK,MAAM,QAAQ,OAAO,MAAM,YAAa,MAAM,QAAQ,KAAK;AAC9D,WAAM,gBAAgB;AACtB,iBAAY,SAAS;;;;AAK3B,SAAO,iBAAiB,WAAW,cAAc;AACjD,eAAa,OAAO,oBAAoB,WAAW,cAAc;IAChE;EAAC;EAAU;EAAY;EAAW,CAAC"}
|
|
@@ -4,12 +4,14 @@ const require_hooks = require('./hooks.cjs');
|
|
|
4
4
|
|
|
5
5
|
exports.COMMAND_SYMBOL = require_types.COMMAND_SYMBOL;
|
|
6
6
|
exports.UNDOABLE_COMMAND_SYMBOL = require_types.UNDOABLE_COMMAND_SYMBOL;
|
|
7
|
+
exports.clearActiveDraft = require_commander.clearActiveDraft;
|
|
7
8
|
exports.clearUndoHistory = require_commander.clearUndoHistory;
|
|
8
9
|
exports.createCommander = require_commander.createCommander;
|
|
9
10
|
exports.isCommand = require_types.isCommand;
|
|
10
11
|
exports.isUndoableCommand = require_types.isUndoableCommand;
|
|
11
12
|
exports.performRedo = require_commander.performRedo;
|
|
12
13
|
exports.performUndo = require_commander.performUndo;
|
|
14
|
+
exports.setActiveDraft = require_commander.setActiveDraft;
|
|
13
15
|
exports.useCommander = require_hooks.useCommander;
|
|
14
16
|
exports.useUndoRedo = require_hooks.useUndoRedo;
|
|
15
17
|
exports.useUndoRedoKeyboard = require_hooks.useUndoRedoKeyboard;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AnyCommand, AnyUndoableCommand, COMMAND_SYMBOL, Command, CommandContext, CommandDispatch, CommandFn, CommandParams, CommandReturn, CommandStore, Commander, CommanderMiddleware, CommanderOptions, CommanderSlice, ExtractState, RevertFn, UNDOABLE_COMMAND_SYMBOL, UndoEntry, UndoableCommand, isCommand, isUndoableCommand } from "./types.cjs";
|
|
2
|
-
import { clearUndoHistory, createCommander, performRedo, performUndo } from "./commander.cjs";
|
|
2
|
+
import { clearActiveDraft, clearUndoHistory, createCommander, performRedo, performUndo, setActiveDraft } from "./commander.cjs";
|
|
3
3
|
import { UndoRedoState, UseUndoRedoKeyboardOptions, useCommander, useUndoRedo, useUndoRedoKeyboard } from "./hooks.cjs";
|
|
4
|
-
export { type AnyCommand, type AnyUndoableCommand, COMMAND_SYMBOL, type Command, type CommandContext, type CommandDispatch, type CommandFn, type CommandParams, type CommandReturn, type CommandStore, type Commander, type CommanderMiddleware, type CommanderOptions, type CommanderSlice, type ExtractState, type RevertFn, UNDOABLE_COMMAND_SYMBOL, type UndoEntry, type UndoRedoState, type UndoableCommand, type UseUndoRedoKeyboardOptions, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, useCommander, useUndoRedo, useUndoRedoKeyboard };
|
|
4
|
+
export { type AnyCommand, type AnyUndoableCommand, COMMAND_SYMBOL, type Command, type CommandContext, type CommandDispatch, type CommandFn, type CommandParams, type CommandReturn, type CommandStore, type Commander, type CommanderMiddleware, type CommanderOptions, type CommanderSlice, type ExtractState, type RevertFn, UNDOABLE_COMMAND_SYMBOL, type UndoEntry, type UndoRedoState, type UndoableCommand, type UseUndoRedoKeyboardOptions, clearActiveDraft, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, setActiveDraft, useCommander, useUndoRedo, useUndoRedoKeyboard };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AnyCommand, AnyUndoableCommand, COMMAND_SYMBOL, Command, CommandContext, CommandDispatch, CommandFn, CommandParams, CommandReturn, CommandStore, Commander, CommanderMiddleware, CommanderOptions, CommanderSlice, ExtractState, RevertFn, UNDOABLE_COMMAND_SYMBOL, UndoEntry, UndoableCommand, isCommand, isUndoableCommand } from "./types.mjs";
|
|
2
|
-
import { clearUndoHistory, createCommander, performRedo, performUndo } from "./commander.mjs";
|
|
2
|
+
import { clearActiveDraft, clearUndoHistory, createCommander, performRedo, performUndo, setActiveDraft } from "./commander.mjs";
|
|
3
3
|
import { UndoRedoState, UseUndoRedoKeyboardOptions, useCommander, useUndoRedo, useUndoRedoKeyboard } from "./hooks.mjs";
|
|
4
|
-
export { type AnyCommand, type AnyUndoableCommand, COMMAND_SYMBOL, type Command, type CommandContext, type CommandDispatch, type CommandFn, type CommandParams, type CommandReturn, type CommandStore, type Commander, type CommanderMiddleware, type CommanderOptions, type CommanderSlice, type ExtractState, type RevertFn, UNDOABLE_COMMAND_SYMBOL, type UndoEntry, type UndoRedoState, type UndoableCommand, type UseUndoRedoKeyboardOptions, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, useCommander, useUndoRedo, useUndoRedoKeyboard };
|
|
4
|
+
export { type AnyCommand, type AnyUndoableCommand, COMMAND_SYMBOL, type Command, type CommandContext, type CommandDispatch, type CommandFn, type CommandParams, type CommandReturn, type CommandStore, type Commander, type CommanderMiddleware, type CommanderOptions, type CommanderSlice, type ExtractState, type RevertFn, UNDOABLE_COMMAND_SYMBOL, type UndoEntry, type UndoRedoState, type UndoableCommand, type UseUndoRedoKeyboardOptions, clearActiveDraft, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, setActiveDraft, useCommander, useUndoRedo, useUndoRedoKeyboard };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COMMAND_SYMBOL, UNDOABLE_COMMAND_SYMBOL, isCommand, isUndoableCommand } from "./types.mjs";
|
|
2
|
-
import { clearUndoHistory, createCommander, performRedo, performUndo } from "./commander.mjs";
|
|
2
|
+
import { clearActiveDraft, clearUndoHistory, createCommander, performRedo, performUndo, setActiveDraft } from "./commander.mjs";
|
|
3
3
|
import { useCommander, useUndoRedo, useUndoRedoKeyboard } from "./hooks.mjs";
|
|
4
4
|
|
|
5
|
-
export { COMMAND_SYMBOL, UNDOABLE_COMMAND_SYMBOL, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, useCommander, useUndoRedo, useUndoRedoKeyboard };
|
|
5
|
+
export { COMMAND_SYMBOL, UNDOABLE_COMMAND_SYMBOL, clearActiveDraft, clearUndoHistory, createCommander, isCommand, isUndoableCommand, performRedo, performUndo, setActiveDraft, useCommander, useUndoRedo, useUndoRedoKeyboard };
|