@voidhash/mimic-react 1.0.0-beta.15 → 1.0.0-beta.16
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
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @voidhash/mimic-react@1.0.0-beta.
|
|
2
|
+
> @voidhash/mimic-react@1.0.0-beta.16 build /home/runner/work/mimic/mimic/packages/mimic-react
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m tsdown [2mv0.18.2[22m powered by rolldown [2mv1.0.0-beta.55[22m
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32mzustand/middleware.d.cts[39m [2m2.13 kB[22m [2m│ gzip: 0.85 kB[22m
|
|
39
39
|
[34mℹ[39m [33m[CJS][39m [2mdist/[22m[32mzustand/useDraft.d.cts[39m [2m1.54 kB[22m [2m│ gzip: 0.66 kB[22m
|
|
40
40
|
[34mℹ[39m [33m[CJS][39m 15 files, total: 28.71 kB
|
|
41
|
-
[32m✔[39m Build complete in [
|
|
41
|
+
[32m✔[39m Build complete in [32m3487ms[39m
|
|
42
42
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mzustand-commander/index.mjs[22m [2m 0.53 kB[22m [2m│ gzip: 0.21 kB[22m
|
|
43
43
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mzustand/index.mjs[22m [2m 0.11 kB[22m [2m│ gzip: 0.09 kB[22m
|
|
44
44
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[1mindex.mjs[22m [2m 0.01 kB[22m [2m│ gzip: 0.03 kB[22m
|
|
45
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/commander.mjs.map [2m15.
|
|
45
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/commander.mjs.map [2m15.09 kB[22m [2m│ gzip: 3.78 kB[22m
|
|
46
46
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/types.mjs.map [2m11.30 kB[22m [2m│ gzip: 2.83 kB[22m
|
|
47
|
-
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/hooks.mjs.map [2m10.
|
|
47
|
+
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/hooks.mjs.map [2m10.71 kB[22m [2m│ gzip: 3.28 kB[22m
|
|
48
48
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand/middleware.mjs.map [2m 6.89 kB[22m [2m│ gzip: 2.11 kB[22m
|
|
49
49
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand-commander/commander.mjs [2m 6.83 kB[22m [2m│ gzip: 1.88 kB[22m
|
|
50
50
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22mzustand/useDraft.mjs.map [2m 4.64 kB[22m [2m│ gzip: 1.59 kB[22m
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32mzustand-commander/commander.d.mts[39m [2m 2.28 kB[22m [2m│ gzip: 0.80 kB[22m
|
|
73
73
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32mzustand/middleware.d.mts[39m [2m 2.13 kB[22m [2m│ gzip: 0.85 kB[22m
|
|
74
74
|
[34mℹ[39m [34m[ESM][39m [2mdist/[22m[32mzustand/useDraft.d.mts[39m [2m 1.54 kB[22m [2m│ gzip: 0.66 kB[22m
|
|
75
|
-
[34mℹ[39m [34m[ESM][39m 33 files, total: 97.
|
|
76
|
-
[32m✔[39m Build complete in [
|
|
75
|
+
[34mℹ[39m [34m[ESM][39m 33 files, total: 97.85 kB
|
|
76
|
+
[32m✔[39m Build complete in [32m3508ms[39m
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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 as (root: unknown) => void);\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 extends CommanderSlice>(\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":";;;;AA+BA,MAAMA,kBAA8C,EAClD,kBAAkB,KACnB;;;;AASD,SAAS,iBACP,UAC6D;AAC7D,SAAQ,OAAO;EACb,MAAM,QAAQ,SAAS,UAAU;EACjC,MAAM,QAAQ,MAAM,WAAW;AAC/B,MAAI,MACF,OAAM,OAAO,GAA8B;OACtC;GAEL,MAAM,QAAS,MAAc;AAC7B,OAAI,gDAAC,MAAO,UACV,OAAM,IAAI,MACR,uEACD;AAEH,SAAM,SAAS,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCpC,SAAgB,gBACd,UAA4B,EAAE,EACe;CAC7C,MAAM,EAAE,uDAA0B,kBAAoB;CAGtD,IAAIC,YAAsD;;;;CAK1D,MAAM,uBAA0E;AAC9E,UACE,YACG;AACH,WAAQ,WAA6B;AACnC,QAAI,CAAC,UACH,OAAM,IAAI,MACR,+EACD;IAIH,MAAMC,MAAwD;KAC5D,gBAAgB,UAAW,UAAU;KACrC,WAAW,YAAY,UAAW,SAAS,QAAe;KAC1D,UAAU,gBAAgB;KAC1B,aAAa,iBAAmD,UAAW;KAC5E;IAGD,MAAM,SAAS,QAAQ,GAAG,KAAK,OAAO;IAGtC,MAAM,WAAW,UAAW,UAAU,CAAC,WAAW,gBAAgB;AAGlE,QAAI,kBAAkB,QAAQ,IAAI,CAAC,UAAU;KAC3C,MAAMC,QAAqC;MACzC;MACA;MACA;MACA,WAAW,KAAK,KAAK;MACtB;AAED,eAAU,UAAU,UAAmC;MACrD,MAAM,EAAE,WAAW,WAAW,eAAe,MAAM;MAGnD,MAAM,eAAe,CAAC,GAAG,WAAW,MAAM,CAAC,MAAM,CAAC,iBAAiB;AAGnE,+CACK,cACH,8CACK,MAAM;OACT,WAAW;OACX,WAAW,EAAE;;OAGjB;;AAGJ,WAAO;;;;;;;CAQb,SAAS,OACP,IAC6D;AAC7D,SAAO;IACJ,iBAAiB;GAClB;GACD;;;;;CAMH,SAAS,eACP,IACA,QACqE;AACrE,SAAO;IACJ,iBAAiB;IACjB,0BAA0B;GAC3B;GACA;GACD;;;;;CAMH,MAAM,cACJ,WAKG;AACH,UACE,KACA,KACA,QACuB;AAEvB,eAAY;AAMZ,4CAHkB,OAAO,KAAK,KAAK,IAAI,SAKrC,YAAY;IACV,WAAW,EAAE;IACb,WAAW,EAAE;IACb,aAAa;IACd;;;AAKP,QAAO;EACL;EACA;EACY;EACb;;;;;;AAWH,SAAgB,eACd,UACA,OACM;AACN,UAAS,UAAU,4CACd,cACH,8CACK,MAAM,mBACT,aAAa,YAEd;;;;;AAML,SAAgB,iBACd,UACM;AACN,UAAS,UAAU,4CACd,cACH,8CACK,MAAM,mBACT,aAAa,WAEd;;;;;;AAWL,SAAgB,YACd,UACS;CAET,MAAM,EAAE,WAAW,WAAW,gBADhB,SAAS,UAAU,CACmB;AAGpD,KAAI,YACF,QAAO;CAIT,MAAM,QAAQ,UAAU,UAAU,SAAS;AAC3C,KAAI,CAAC,MACH,QAAO;CAGT,MAAM,eAAe,UAAU,MAAM,GAAG,GAAG;CAG3C,MAAMC,MAA8B;EAClC,gBAAgB,SAAS,UAAU;EACnC,WAAW,YAAY,SAAS,SAAS,QAAe;EACxD,UAAU,sBAAsB,SAAS;EACzC,aAAa,iBAAiB,SAAS;EACxC;AAGD,OAAM,QAAQ,OAAO,KAAK,MAAM,QAAQ,MAAM,OAAO;AAGrD,UAAS,UAAU,4CACd,cACH,8CACK,MAAM;EACT,WAAW;EACX,WAAW,CAAC,GAAG,WAAW,MAAM;OAEjC;AAEH,QAAO;;;;;;AAOT,SAAgB,YACd,UACS;CAET,MAAM,EAAE,WAAW,WAAW,gBADhB,SAAS,UAAU,CACmB;AAGpD,KAAI,YACF,QAAO;CAIT,MAAM,QAAQ,UAAU,UAAU,SAAS;AAC3C,KAAI,CAAC,MACH,QAAO;CAGT,MAAM,eAAe,UAAU,MAAM,GAAG,GAAG;CAG3C,MAAMA,MAA8B;EAClC,gBAAgB,SAAS,UAAU;EACnC,WAAW,YAAY,SAAS,SAAS,QAAe;EACxD,UAAU,sBAAsB,SAAS;EACzC,aAAa,iBAAiB,SAAS;EACxC;CAGD,MAAM,SAAS,MAAM,QAAQ,GAAG,KAAK,MAAM,OAAO;CAGlD,MAAMC,WAAsB;EAC1B,SAAS,MAAM;EACf,QAAQ,MAAM;EACd;EACA,WAAW,KAAK,KAAK;EACtB;AAGD,UAAS,UAAU,4CACd,cACH,8CACK,MAAM;EACT,WAAW,CAAC,GAAG,WAAW,SAAS;EACnC,WAAW;OAEZ;AAEH,QAAO;;;;;;AAOT,SAAS,sBACP,UACyB;AACzB,SAA0B,YAA+C;AACvE,UAAQ,WAA6B;GACnC,MAAMD,MAA8B;IAClC,gBAAgB,SAAS,UAAU;IACnC,WAAW,YAAY,SAAS,SAAS,QAAe;IACxD,UAAU,sBAAsB,SAAS;IACzC,aAAa,iBAAiB,SAAS;IACxC;AAGD,UAAO,QAAQ,GAAG,KAAK,OAAO;;;;;;;AAQpC,SAAgB,iBACd,UACM;AACN,UAAS,UAAU,4CACd,cACH,8CACK,MAAM;EACT,WAAW,EAAE;EACb,WAAW,EAAE;OAEd"}
|
|
@@ -1 +1 @@
|
|
|
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"}
|
|
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 as (root: unknown) => void);\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,GAA8B;OACtC;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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidhash/mimic-react",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
"typescript": "5.8.3",
|
|
38
38
|
"vite-tsconfig-paths": "^5.1.4",
|
|
39
39
|
"vitest": "^3.2.4",
|
|
40
|
-
"@voidhash/tsconfig": "1.0.0-beta.
|
|
40
|
+
"@voidhash/tsconfig": "1.0.0-beta.16"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"effect": "^3.16.0",
|
|
44
44
|
"react": "^18.0.0 || ^19.0.0",
|
|
45
|
-
"@voidhash/mimic": "1.0.0-beta.
|
|
45
|
+
"@voidhash/mimic": "1.0.0-beta.16"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsdown",
|
|
@@ -47,7 +47,7 @@ function buildTransaction<TStore extends CommanderSlice, TSchema extends Primiti
|
|
|
47
47
|
const state = storeApi.getState();
|
|
48
48
|
const draft = state._commander.activeDraft;
|
|
49
49
|
if (draft) {
|
|
50
|
-
draft.update(fn);
|
|
50
|
+
draft.update(fn as (root: unknown) => void);
|
|
51
51
|
} else {
|
|
52
52
|
// Access mimic.document from the store
|
|
53
53
|
const mimic = (state as any).mimic;
|
|
@@ -374,7 +374,7 @@ export function performRedo<TStore extends CommanderSlice>(
|
|
|
374
374
|
* Creates a dispatch function for use during undo/redo operations.
|
|
375
375
|
* This dispatch does NOT add to undo stack (to avoid infinite loops).
|
|
376
376
|
*/
|
|
377
|
-
function createDispatchForUndo<TStore>(
|
|
377
|
+
function createDispatchForUndo<TStore extends CommanderSlice>(
|
|
378
378
|
storeApi: StoreApi<TStore>
|
|
379
379
|
): CommandDispatch<TStore> {
|
|
380
380
|
return <TParams, TReturn>(command: Command<TStore, TParams, TReturn>) => {
|
|
@@ -34,7 +34,7 @@ function buildTransactionFromApi<TStore extends CommanderSlice, TSchema extends
|
|
|
34
34
|
const state = storeApi.getState();
|
|
35
35
|
const draft = state._commander.activeDraft;
|
|
36
36
|
if (draft) {
|
|
37
|
-
draft.update(fn);
|
|
37
|
+
draft.update(fn as (root: unknown) => void);
|
|
38
38
|
} else {
|
|
39
39
|
const mimic = (state as any).mimic;
|
|
40
40
|
if (!mimic?.document) {
|