@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
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { StoreApi } from "zustand";
|
|
10
|
+
import type { Primitive } from "@voidhash/mimic";
|
|
11
|
+
import type { ClientDocument } from "@voidhash/mimic/client";
|
|
10
12
|
import {
|
|
11
13
|
COMMAND_SYMBOL,
|
|
12
14
|
UNDOABLE_COMMAND_SYMBOL,
|
|
@@ -31,6 +33,34 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
|
|
|
31
33
|
maxUndoStackSize: 100,
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Transaction Helper
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a transaction function that routes to draft or document.
|
|
42
|
+
*/
|
|
43
|
+
function buildTransaction<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
|
|
44
|
+
storeApi: StoreApi<TStore>
|
|
45
|
+
): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {
|
|
46
|
+
return (fn) => {
|
|
47
|
+
const state = storeApi.getState();
|
|
48
|
+
const draft = state._commander.activeDraft;
|
|
49
|
+
if (draft) {
|
|
50
|
+
draft.update(fn);
|
|
51
|
+
} else {
|
|
52
|
+
// Access mimic.document from the store
|
|
53
|
+
const mimic = (state as any).mimic;
|
|
54
|
+
if (!mimic?.document) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Commander: No active draft and no mimic document found on the store."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
mimic.document.transaction(fn);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
34
64
|
// =============================================================================
|
|
35
65
|
// Commander Implementation
|
|
36
66
|
// =============================================================================
|
|
@@ -47,8 +77,7 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
|
|
|
47
77
|
* const addItem = commander.action(
|
|
48
78
|
* Schema.Struct({ name: Schema.String }),
|
|
49
79
|
* (ctx, params) => {
|
|
50
|
-
*
|
|
51
|
-
* mimic.document.transaction(root => {
|
|
80
|
+
* ctx.transaction(root => {
|
|
52
81
|
* // add item
|
|
53
82
|
* });
|
|
54
83
|
* }
|
|
@@ -64,9 +93,9 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
|
|
|
64
93
|
* );
|
|
65
94
|
* ```
|
|
66
95
|
*/
|
|
67
|
-
export function createCommander<TStore extends object>(
|
|
96
|
+
export function createCommander<TStore extends object, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
|
|
68
97
|
options: CommanderOptions = {}
|
|
69
|
-
): Commander<TStore & CommanderSlice> {
|
|
98
|
+
): Commander<TStore & CommanderSlice, TSchema> {
|
|
70
99
|
const { maxUndoStackSize } = { ...DEFAULT_OPTIONS, ...options };
|
|
71
100
|
|
|
72
101
|
// Track the store API once middleware is applied
|
|
@@ -75,9 +104,9 @@ export function createCommander<TStore extends object>(
|
|
|
75
104
|
/**
|
|
76
105
|
* Creates the dispatch function for use within command handlers.
|
|
77
106
|
*/
|
|
78
|
-
const createDispatch = (): CommandDispatch<TStore & CommanderSlice> => {
|
|
107
|
+
const createDispatch = (): CommandDispatch<TStore & CommanderSlice, TSchema> => {
|
|
79
108
|
return <TParams, TReturn>(
|
|
80
|
-
command: Command<TStore & CommanderSlice, TParams, TReturn>
|
|
109
|
+
command: Command<TStore & CommanderSlice, TParams, TReturn, TSchema>
|
|
81
110
|
) => {
|
|
82
111
|
return (params: TParams): TReturn => {
|
|
83
112
|
if (!_storeApi) {
|
|
@@ -87,17 +116,21 @@ export function createCommander<TStore extends object>(
|
|
|
87
116
|
}
|
|
88
117
|
|
|
89
118
|
// Create context for the command
|
|
90
|
-
const ctx: CommandContext<TStore & CommanderSlice> = {
|
|
119
|
+
const ctx: CommandContext<TStore & CommanderSlice, TSchema> = {
|
|
91
120
|
getState: () => _storeApi!.getState(),
|
|
92
121
|
setState: (partial) => _storeApi!.setState(partial as any),
|
|
93
122
|
dispatch: createDispatch(),
|
|
123
|
+
transaction: buildTransaction<TStore & CommanderSlice, TSchema>(_storeApi!),
|
|
94
124
|
};
|
|
95
125
|
|
|
96
126
|
// Execute the command
|
|
97
127
|
const result = command.fn(ctx, params);
|
|
98
128
|
|
|
99
|
-
//
|
|
100
|
-
|
|
129
|
+
// Skip undo stack when a draft is active
|
|
130
|
+
const hasDraft = _storeApi!.getState()._commander.activeDraft !== null;
|
|
131
|
+
|
|
132
|
+
// If it's an undoable command and no draft is active, add to undo stack
|
|
133
|
+
if (isUndoableCommand(command) && !hasDraft) {
|
|
101
134
|
const entry: UndoEntry<TParams, TReturn> = {
|
|
102
135
|
command,
|
|
103
136
|
params,
|
|
@@ -106,7 +139,7 @@ export function createCommander<TStore extends object>(
|
|
|
106
139
|
};
|
|
107
140
|
|
|
108
141
|
_storeApi.setState((state: TStore & CommanderSlice) => {
|
|
109
|
-
const { undoStack, redoStack } = state._commander;
|
|
142
|
+
const { undoStack, redoStack: _redoStack } = state._commander;
|
|
110
143
|
|
|
111
144
|
// Add to undo stack, respecting max size
|
|
112
145
|
const newUndoStack = [...undoStack, entry].slice(-maxUndoStackSize);
|
|
@@ -115,6 +148,7 @@ export function createCommander<TStore extends object>(
|
|
|
115
148
|
return {
|
|
116
149
|
...state,
|
|
117
150
|
_commander: {
|
|
151
|
+
...state._commander,
|
|
118
152
|
undoStack: newUndoStack,
|
|
119
153
|
redoStack: [],
|
|
120
154
|
},
|
|
@@ -131,8 +165,8 @@ export function createCommander<TStore extends object>(
|
|
|
131
165
|
* Create a regular command (no undo support).
|
|
132
166
|
*/
|
|
133
167
|
function action<TParams, TReturn = void>(
|
|
134
|
-
fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>
|
|
135
|
-
): Command<TStore & CommanderSlice, TParams, TReturn> {
|
|
168
|
+
fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>
|
|
169
|
+
): Command<TStore & CommanderSlice, TParams, TReturn, TSchema> {
|
|
136
170
|
return {
|
|
137
171
|
[COMMAND_SYMBOL]: true,
|
|
138
172
|
fn,
|
|
@@ -143,9 +177,9 @@ export function createCommander<TStore extends object>(
|
|
|
143
177
|
* Create an undoable command with undo/redo support.
|
|
144
178
|
*/
|
|
145
179
|
function undoableAction<TParams, TReturn>(
|
|
146
|
-
fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>,
|
|
147
|
-
revert: RevertFn<TStore & CommanderSlice, TParams, TReturn>
|
|
148
|
-
): UndoableCommand<TStore & CommanderSlice, TParams, TReturn> {
|
|
180
|
+
fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>,
|
|
181
|
+
revert: RevertFn<TStore & CommanderSlice, TParams, TReturn, TSchema>
|
|
182
|
+
): UndoableCommand<TStore & CommanderSlice, TParams, TReturn, TSchema> {
|
|
149
183
|
return {
|
|
150
184
|
[COMMAND_SYMBOL]: true,
|
|
151
185
|
[UNDOABLE_COMMAND_SYMBOL]: true,
|
|
@@ -181,6 +215,7 @@ export function createCommander<TStore extends object>(
|
|
|
181
215
|
_commander: {
|
|
182
216
|
undoStack: [],
|
|
183
217
|
redoStack: [],
|
|
218
|
+
activeDraft: null,
|
|
184
219
|
},
|
|
185
220
|
};
|
|
186
221
|
};
|
|
@@ -189,23 +224,64 @@ export function createCommander<TStore extends object>(
|
|
|
189
224
|
return {
|
|
190
225
|
action,
|
|
191
226
|
undoableAction,
|
|
192
|
-
middleware: middleware as Commander<TStore & CommanderSlice>["middleware"],
|
|
227
|
+
middleware: middleware as Commander<TStore & CommanderSlice, TSchema>["middleware"],
|
|
193
228
|
};
|
|
194
229
|
}
|
|
195
230
|
|
|
231
|
+
// =============================================================================
|
|
232
|
+
// Draft Helpers
|
|
233
|
+
// =============================================================================
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set the active draft on the commander slice.
|
|
237
|
+
* While a draft is active, transactions route through `draft.update()` and undo is disabled.
|
|
238
|
+
*/
|
|
239
|
+
export function setActiveDraft<TStore extends CommanderSlice>(
|
|
240
|
+
storeApi: StoreApi<TStore>,
|
|
241
|
+
draft: ClientDocument.DraftHandle<any>
|
|
242
|
+
): void {
|
|
243
|
+
storeApi.setState((state: TStore) => ({
|
|
244
|
+
...state,
|
|
245
|
+
_commander: {
|
|
246
|
+
...state._commander,
|
|
247
|
+
activeDraft: draft,
|
|
248
|
+
},
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clear the active draft from the commander slice.
|
|
254
|
+
*/
|
|
255
|
+
export function clearActiveDraft<TStore extends CommanderSlice>(
|
|
256
|
+
storeApi: StoreApi<TStore>
|
|
257
|
+
): void {
|
|
258
|
+
storeApi.setState((state: TStore) => ({
|
|
259
|
+
...state,
|
|
260
|
+
_commander: {
|
|
261
|
+
...state._commander,
|
|
262
|
+
activeDraft: null,
|
|
263
|
+
},
|
|
264
|
+
}));
|
|
265
|
+
}
|
|
266
|
+
|
|
196
267
|
// =============================================================================
|
|
197
268
|
// Undo/Redo Functions
|
|
198
269
|
// =============================================================================
|
|
199
270
|
|
|
200
271
|
/**
|
|
201
272
|
* Perform an undo operation on the store.
|
|
202
|
-
* Returns true if an undo was performed, false if undo stack was empty.
|
|
273
|
+
* Returns true if an undo was performed, false if undo stack was empty or a draft is active.
|
|
203
274
|
*/
|
|
204
275
|
export function performUndo<TStore extends CommanderSlice>(
|
|
205
276
|
storeApi: StoreApi<TStore>
|
|
206
277
|
): boolean {
|
|
207
278
|
const state = storeApi.getState();
|
|
208
|
-
const { undoStack, redoStack } = state._commander;
|
|
279
|
+
const { undoStack, redoStack, activeDraft } = state._commander;
|
|
280
|
+
|
|
281
|
+
// Undo is disabled while a draft is active
|
|
282
|
+
if (activeDraft) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
209
285
|
|
|
210
286
|
// Pop the last entry from undo stack
|
|
211
287
|
const entry = undoStack[undoStack.length - 1];
|
|
@@ -220,6 +296,7 @@ export function performUndo<TStore extends CommanderSlice>(
|
|
|
220
296
|
getState: () => storeApi.getState(),
|
|
221
297
|
setState: (partial) => storeApi.setState(partial as any),
|
|
222
298
|
dispatch: createDispatchForUndo(storeApi),
|
|
299
|
+
transaction: buildTransaction(storeApi),
|
|
223
300
|
};
|
|
224
301
|
|
|
225
302
|
// Execute the revert function
|
|
@@ -229,6 +306,7 @@ export function performUndo<TStore extends CommanderSlice>(
|
|
|
229
306
|
storeApi.setState((state: TStore) => ({
|
|
230
307
|
...state,
|
|
231
308
|
_commander: {
|
|
309
|
+
...state._commander,
|
|
232
310
|
undoStack: newUndoStack,
|
|
233
311
|
redoStack: [...redoStack, entry],
|
|
234
312
|
},
|
|
@@ -239,13 +317,18 @@ export function performUndo<TStore extends CommanderSlice>(
|
|
|
239
317
|
|
|
240
318
|
/**
|
|
241
319
|
* Perform a redo operation on the store.
|
|
242
|
-
* Returns true if a redo was performed, false if redo stack was empty.
|
|
320
|
+
* Returns true if a redo was performed, false if redo stack was empty or a draft is active.
|
|
243
321
|
*/
|
|
244
322
|
export function performRedo<TStore extends CommanderSlice>(
|
|
245
323
|
storeApi: StoreApi<TStore>
|
|
246
324
|
): boolean {
|
|
247
325
|
const state = storeApi.getState();
|
|
248
|
-
const { undoStack, redoStack } = state._commander;
|
|
326
|
+
const { undoStack, redoStack, activeDraft } = state._commander;
|
|
327
|
+
|
|
328
|
+
// Redo is disabled while a draft is active
|
|
329
|
+
if (activeDraft) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
249
332
|
|
|
250
333
|
// Pop the last entry from redo stack
|
|
251
334
|
const entry = redoStack[redoStack.length - 1];
|
|
@@ -260,6 +343,7 @@ export function performRedo<TStore extends CommanderSlice>(
|
|
|
260
343
|
getState: () => storeApi.getState(),
|
|
261
344
|
setState: (partial) => storeApi.setState(partial as any),
|
|
262
345
|
dispatch: createDispatchForUndo(storeApi),
|
|
346
|
+
transaction: buildTransaction(storeApi),
|
|
263
347
|
};
|
|
264
348
|
|
|
265
349
|
// Re-execute the command
|
|
@@ -277,6 +361,7 @@ export function performRedo<TStore extends CommanderSlice>(
|
|
|
277
361
|
storeApi.setState((state: TStore) => ({
|
|
278
362
|
...state,
|
|
279
363
|
_commander: {
|
|
364
|
+
...state._commander,
|
|
280
365
|
undoStack: [...undoStack, newEntry],
|
|
281
366
|
redoStack: newRedoStack,
|
|
282
367
|
},
|
|
@@ -298,6 +383,7 @@ function createDispatchForUndo<TStore>(
|
|
|
298
383
|
getState: () => storeApi.getState(),
|
|
299
384
|
setState: (partial) => storeApi.setState(partial as any),
|
|
300
385
|
dispatch: createDispatchForUndo(storeApi),
|
|
386
|
+
transaction: buildTransaction(storeApi),
|
|
301
387
|
};
|
|
302
388
|
|
|
303
389
|
// Execute without adding to undo stack
|
|
@@ -315,9 +401,9 @@ export function clearUndoHistory<TStore extends CommanderSlice>(
|
|
|
315
401
|
storeApi.setState((state: TStore) => ({
|
|
316
402
|
...state,
|
|
317
403
|
_commander: {
|
|
404
|
+
...state._commander,
|
|
318
405
|
undoStack: [],
|
|
319
406
|
redoStack: [],
|
|
320
407
|
},
|
|
321
408
|
}));
|
|
322
409
|
}
|
|
323
|
-
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import { useCallback, useEffect, useMemo } from "react";
|
|
10
10
|
import { useStore, type StoreApi, type UseBoundStore } from "zustand";
|
|
11
|
-
import {
|
|
11
|
+
import type { Primitive } from "@voidhash/mimic";
|
|
12
|
+
import { performRedo, performUndo, clearUndoHistory, setActiveDraft, clearActiveDraft } from "./commander";
|
|
12
13
|
import {
|
|
13
14
|
isUndoableCommand,
|
|
14
15
|
type Command,
|
|
@@ -26,26 +27,50 @@ import {
|
|
|
26
27
|
* Creates a dispatch function for commands.
|
|
27
28
|
* This is for use outside of React components (e.g., in command handlers).
|
|
28
29
|
*/
|
|
29
|
-
function
|
|
30
|
+
function buildTransactionFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
|
|
31
|
+
storeApi: StoreApi<TStore>
|
|
32
|
+
): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {
|
|
33
|
+
return (fn) => {
|
|
34
|
+
const state = storeApi.getState();
|
|
35
|
+
const draft = state._commander.activeDraft;
|
|
36
|
+
if (draft) {
|
|
37
|
+
draft.update(fn);
|
|
38
|
+
} else {
|
|
39
|
+
const mimic = (state as any).mimic;
|
|
40
|
+
if (!mimic?.document) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Commander: No active draft and no mimic document found on the store."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
mimic.document.transaction(fn);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createDispatchFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
|
|
30
51
|
storeApi: StoreApi<TStore>,
|
|
31
52
|
maxUndoStackSize = 100
|
|
32
|
-
): CommandDispatch<TStore> {
|
|
33
|
-
const dispatch: CommandDispatch<TStore> = <TParams, TReturn>(
|
|
34
|
-
command: Command<TStore, TParams, TReturn>
|
|
53
|
+
): CommandDispatch<TStore, TSchema> {
|
|
54
|
+
const dispatch: CommandDispatch<TStore, TSchema> = <TParams, TReturn>(
|
|
55
|
+
command: Command<TStore, TParams, TReturn, TSchema>
|
|
35
56
|
) => {
|
|
36
57
|
return (params: TParams): TReturn => {
|
|
37
58
|
// Create context for the command
|
|
38
|
-
const ctx: CommandContext<TStore> = {
|
|
59
|
+
const ctx: CommandContext<TStore, TSchema> = {
|
|
39
60
|
getState: () => storeApi.getState(),
|
|
40
61
|
setState: (partial) => storeApi.setState(partial as Partial<TStore>),
|
|
41
62
|
dispatch,
|
|
63
|
+
transaction: buildTransactionFromApi<TStore, TSchema>(storeApi),
|
|
42
64
|
};
|
|
43
65
|
|
|
44
66
|
// Execute the command
|
|
45
67
|
const result = command.fn(ctx, params);
|
|
46
68
|
|
|
47
|
-
//
|
|
48
|
-
|
|
69
|
+
// Skip undo stack when a draft is active
|
|
70
|
+
const hasDraft = storeApi.getState()._commander.activeDraft !== null;
|
|
71
|
+
|
|
72
|
+
// If it's an undoable command and no draft is active, add to undo stack
|
|
73
|
+
if (isUndoableCommand(command) && !hasDraft) {
|
|
49
74
|
storeApi.setState((state: TStore) => {
|
|
50
75
|
const { undoStack } = state._commander;
|
|
51
76
|
|
|
@@ -64,6 +89,7 @@ function createDispatchFromApi<TStore extends CommanderSlice>(
|
|
|
64
89
|
return {
|
|
65
90
|
...state,
|
|
66
91
|
_commander: {
|
|
92
|
+
...state._commander,
|
|
67
93
|
undoStack: newUndoStack,
|
|
68
94
|
redoStack: [],
|
|
69
95
|
},
|
|
@@ -91,9 +117,9 @@ function createDispatchFromApi<TStore extends CommanderSlice>(
|
|
|
91
117
|
* };
|
|
92
118
|
* ```
|
|
93
119
|
*/
|
|
94
|
-
export function useCommander<TStore extends CommanderSlice>(
|
|
120
|
+
export function useCommander<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
|
|
95
121
|
store: UseBoundStore<StoreApi<TStore>>
|
|
96
|
-
): CommandDispatch<TStore> {
|
|
122
|
+
): CommandDispatch<TStore, TSchema> {
|
|
97
123
|
// Get the store API
|
|
98
124
|
const storeApi = useMemo(() => {
|
|
99
125
|
// UseBoundStore has the StoreApi attached
|
|
@@ -102,11 +128,11 @@ export function useCommander<TStore extends CommanderSlice>(
|
|
|
102
128
|
|
|
103
129
|
// Create a stable dispatch function
|
|
104
130
|
const dispatch = useMemo(
|
|
105
|
-
() => createDispatchFromApi<TStore>(storeApi),
|
|
131
|
+
() => createDispatchFromApi<TStore, TSchema>(storeApi),
|
|
106
132
|
[storeApi]
|
|
107
133
|
);
|
|
108
134
|
|
|
109
|
-
return dispatch as CommandDispatch<TStore>;
|
|
135
|
+
return dispatch as CommandDispatch<TStore, TSchema>;
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
// =============================================================================
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { StoreApi, UseBoundStore } from "zustand";
|
|
10
|
+
import type { Primitive } from "@voidhash/mimic";
|
|
11
|
+
import type { ClientDocument } from "@voidhash/mimic/client";
|
|
10
12
|
|
|
11
13
|
// =============================================================================
|
|
12
14
|
// Command Symbol & Type Guard
|
|
@@ -32,7 +34,7 @@ export const UNDOABLE_COMMAND_SYMBOL = Symbol.for(
|
|
|
32
34
|
* Context provided to command functions.
|
|
33
35
|
* Gives access to store state and dispatch capabilities.
|
|
34
36
|
*/
|
|
35
|
-
export interface CommandContext<TStore> {
|
|
37
|
+
export interface CommandContext<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
|
|
36
38
|
/**
|
|
37
39
|
* Get the current store state.
|
|
38
40
|
*/
|
|
@@ -50,7 +52,13 @@ export interface CommandContext<TStore> {
|
|
|
50
52
|
* @example
|
|
51
53
|
* dispatch(otherCommand)({ param: "value" });
|
|
52
54
|
*/
|
|
53
|
-
readonly dispatch: CommandDispatch<TStore>;
|
|
55
|
+
readonly dispatch: CommandDispatch<TStore, TSchema>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run a transaction on the document.
|
|
59
|
+
* Routes to the active draft if one is linked, otherwise to the document directly.
|
|
60
|
+
*/
|
|
61
|
+
readonly transaction: (fn: (root: Primitive.InferProxy<TSchema>) => void) => void;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
// =============================================================================
|
|
@@ -60,8 +68,8 @@ export interface CommandContext<TStore> {
|
|
|
60
68
|
/**
|
|
61
69
|
* The function signature for a command handler.
|
|
62
70
|
*/
|
|
63
|
-
export type CommandFn<TStore, TParams, TReturn> = (
|
|
64
|
-
ctx: CommandContext<TStore>,
|
|
71
|
+
export type CommandFn<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = (
|
|
72
|
+
ctx: CommandContext<TStore, TSchema>,
|
|
65
73
|
params: TParams
|
|
66
74
|
) => TReturn;
|
|
67
75
|
|
|
@@ -69,8 +77,8 @@ export type CommandFn<TStore, TParams, TReturn> = (
|
|
|
69
77
|
* The function signature for an undoable command's revert handler.
|
|
70
78
|
* Receives the original params and the result from the forward execution.
|
|
71
79
|
*/
|
|
72
|
-
export type RevertFn<TStore, TParams, TReturn> = (
|
|
73
|
-
ctx: CommandContext<TStore>,
|
|
80
|
+
export type RevertFn<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = (
|
|
81
|
+
ctx: CommandContext<TStore, TSchema>,
|
|
74
82
|
params: TParams,
|
|
75
83
|
result: TReturn
|
|
76
84
|
) => void;
|
|
@@ -83,19 +91,19 @@ export type RevertFn<TStore, TParams, TReturn> = (
|
|
|
83
91
|
* A command that can be dispatched to modify store state.
|
|
84
92
|
* Regular commands do not support undo/redo.
|
|
85
93
|
*/
|
|
86
|
-
export interface Command<TStore, TParams, TReturn> {
|
|
94
|
+
export interface Command<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
|
|
87
95
|
readonly [COMMAND_SYMBOL]: true;
|
|
88
|
-
readonly fn: CommandFn<TStore, TParams, TReturn>;
|
|
96
|
+
readonly fn: CommandFn<TStore, TParams, TReturn, TSchema>;
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
/**
|
|
92
100
|
* An undoable command that supports undo/redo.
|
|
93
101
|
* Must provide a revert function that knows how to undo the change.
|
|
94
102
|
*/
|
|
95
|
-
export interface UndoableCommand<TStore, TParams, TReturn>
|
|
96
|
-
extends Command<TStore, TParams, TReturn> {
|
|
103
|
+
export interface UndoableCommand<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>
|
|
104
|
+
extends Command<TStore, TParams, TReturn, TSchema> {
|
|
97
105
|
readonly [UNDOABLE_COMMAND_SYMBOL]: true;
|
|
98
|
-
readonly revert: RevertFn<TStore, TParams, TReturn>;
|
|
106
|
+
readonly revert: RevertFn<TStore, TParams, TReturn, TSchema>;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
/**
|
|
@@ -119,8 +127,8 @@ export type AnyUndoableCommand = UndoableCommand<any, any, any>;
|
|
|
119
127
|
* @example
|
|
120
128
|
* const result = dispatch(myCommand)({ param: "value" });
|
|
121
129
|
*/
|
|
122
|
-
export type CommandDispatch<TStore> = <TParams, TReturn>(
|
|
123
|
-
command: Command<TStore, TParams, TReturn>
|
|
130
|
+
export type CommandDispatch<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = <TParams, TReturn>(
|
|
131
|
+
command: Command<TStore, TParams, TReturn, TSchema>
|
|
124
132
|
) => (params: TParams) => TReturn;
|
|
125
133
|
|
|
126
134
|
// =============================================================================
|
|
@@ -151,6 +159,8 @@ export interface CommanderSlice {
|
|
|
151
159
|
readonly undoStack: ReadonlyArray<UndoEntry>;
|
|
152
160
|
/** Stack of commands that can be redone */
|
|
153
161
|
readonly redoStack: ReadonlyArray<UndoEntry>;
|
|
162
|
+
/** Active draft handle, if any. When set, transactions route through the draft and undo is disabled. */
|
|
163
|
+
readonly activeDraft: ClientDocument.DraftHandle<any> | null;
|
|
154
164
|
};
|
|
155
165
|
}
|
|
156
166
|
|
|
@@ -173,7 +183,7 @@ export interface CommanderOptions {
|
|
|
173
183
|
* A commander instance bound to a specific store type.
|
|
174
184
|
* Used to create commands and the middleware.
|
|
175
185
|
*/
|
|
176
|
-
export interface Commander<TStore> {
|
|
186
|
+
export interface Commander<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
|
|
177
187
|
/**
|
|
178
188
|
* Create a regular command (no undo support).
|
|
179
189
|
*
|
|
@@ -193,13 +203,13 @@ export interface Commander<TStore> {
|
|
|
193
203
|
readonly action: {
|
|
194
204
|
// With params (explicit type parameter)
|
|
195
205
|
<TParams, TReturn = void>(
|
|
196
|
-
fn: CommandFn<TStore, TParams, TReturn>
|
|
197
|
-
): Command<TStore, TParams, TReturn>;
|
|
206
|
+
fn: CommandFn<TStore, TParams, TReturn, TSchema>
|
|
207
|
+
): Command<TStore, TParams, TReturn, TSchema>;
|
|
198
208
|
|
|
199
209
|
// Without params (void) - inferred when no type param provided
|
|
200
210
|
<TReturn = void>(
|
|
201
|
-
fn: CommandFn<TStore, void, TReturn>
|
|
202
|
-
): Command<TStore, void, TReturn>;
|
|
211
|
+
fn: CommandFn<TStore, void, TReturn, TSchema>
|
|
212
|
+
): Command<TStore, void, TReturn, TSchema>;
|
|
203
213
|
};
|
|
204
214
|
|
|
205
215
|
/**
|
|
@@ -222,15 +232,15 @@ export interface Commander<TStore> {
|
|
|
222
232
|
readonly undoableAction: {
|
|
223
233
|
// With params (explicit type parameter)
|
|
224
234
|
<TParams, TReturn>(
|
|
225
|
-
fn: CommandFn<TStore, TParams, TReturn>,
|
|
226
|
-
revert: RevertFn<TStore, TParams, TReturn>
|
|
227
|
-
): UndoableCommand<TStore, TParams, TReturn>;
|
|
235
|
+
fn: CommandFn<TStore, TParams, TReturn, TSchema>,
|
|
236
|
+
revert: RevertFn<TStore, TParams, TReturn, TSchema>
|
|
237
|
+
): UndoableCommand<TStore, TParams, TReturn, TSchema>;
|
|
228
238
|
|
|
229
239
|
// Without params (void)
|
|
230
240
|
<TReturn>(
|
|
231
|
-
fn: CommandFn<TStore, void, TReturn>,
|
|
232
|
-
revert: RevertFn<TStore, void, TReturn>
|
|
233
|
-
): UndoableCommand<TStore, void, TReturn>;
|
|
241
|
+
fn: CommandFn<TStore, void, TReturn, TSchema>,
|
|
242
|
+
revert: RevertFn<TStore, void, TReturn, TSchema>
|
|
243
|
+
): UndoableCommand<TStore, void, TReturn, TSchema>;
|
|
234
244
|
};
|
|
235
245
|
|
|
236
246
|
/**
|