UNPKG

mobx-bonsai

Version:

A fast lightweight alternative to MobX-State-Tree + Y.js two-way binding

102 lines (95 loc) 3.4 kB
import { action, isObservableArray, isObservableObject, remove, set } from "mobx" import { failure } from "../error/failure" import { assertIsNode } from "../node/node" import { reconcileData } from "../node/reconcileData" import { resolvePath } from "../node/tree/resolvePath" import type { UndoableChange } from "./types" /** * Applies a single change in forward or reverse mode. * Used by UndoManager during undo/redo operations. * * @param rootNode - The tracked root node * @param change - The change to apply * @param mode - Whether to apply the change forward (redo) or reverse (undo) */ export const applyChange = action( (rootNode: object, change: UndoableChange, mode: "forward" | "reverse"): void => { // Resolve the target object/array using the path const result = resolvePath(rootNode, change.path) if (!result.resolved) { throw failure(`cannot resolve path: ${change.path.join(".")}`) } const target = result.value as any // Ensure target is a node assertIsNode(target, "target") // Validate target type matches operation type if (change.operation.startsWith("object-")) { if (!isObservableObject(target)) { throw failure( `cannot apply ${change.operation} to non-object target at path: ${change.path.join(".")}` ) } } else if (change.operation.startsWith("array-")) { if (!isObservableArray(target)) { throw failure( `cannot apply ${change.operation} to non-array target at path: ${change.path.join(".")}` ) } } if (mode === "reverse") { // Apply changes in reverse (undo) switch (change.operation) { case "object-add": remove(target, change.propertyName) break case "object-remove": set(target, change.propertyName, reconcileData(undefined, change.oldValue, target)) break case "object-update": set( target, change.propertyName, reconcileData(target[change.propertyName], change.oldValue, target) ) break case "array-splice": target.splice( change.index, change.added.length, ...change.removed.map((val: any) => reconcileData(undefined, val, target)) ) break case "array-update": set(target, change.index, reconcileData(target[change.index], change.oldValue, target)) break } } else { // Apply changes forward (redo) switch (change.operation) { case "object-add": set(target, change.propertyName, reconcileData(undefined, change.newValue, target)) break case "object-remove": remove(target, change.propertyName) break case "object-update": set( target, change.propertyName, reconcileData(target[change.propertyName], change.newValue, target) ) break case "array-splice": target.splice( change.index, change.removed.length, ...change.added.map((val: any) => reconcileData(undefined, val, target)) ) break case "array-update": set(target, change.index, reconcileData(target[change.index], change.newValue, target)) break } } } )