UNPKG

4.43 kBJavaScriptView Raw
1import { observe, isObservableMap, isObservableObject, isObservableArray, values, entries, } from "mobx";
2function buildPath(entry) {
3 if (!entry)
4 return "ROOT";
5 var res = [];
6 while (entry.parent) {
7 res.push(entry.path);
8 entry = entry.parent;
9 }
10 return res.reverse().join("/");
11}
12function isRecursivelyObservable(thing) {
13 return isObservableObject(thing) || isObservableArray(thing) || isObservableMap(thing);
14}
15/**
16 * Given an object, deeply observes the given object.
17 * It is like `observe` from mobx, but applied recursively, including all future children.
18 *
19 * Note that the given object cannot ever contain cycles and should be a tree.
20 *
21 * As benefit: path and root will be provided in the callback, so the signature of the listener is
22 * (change, path, root) => void
23 *
24 * The returned disposer can be invoked to clean up the listener
25 *
26 * deepObserve cannot be used on computed values.
27 *
28 * @example
29 * const disposer = deepObserve(target, (change, path) => {
30 * console.dir(change)
31 * })
32 */
33export function deepObserve(target, listener) {
34 var entrySet = new WeakMap();
35 function genericListener(change) {
36 var entry = entrySet.get(change.object);
37 processChange(change, entry);
38 listener(change, buildPath(entry), target);
39 }
40 function processChange(change, parent) {
41 switch (change.type) {
42 // Object changes
43 case "add": // also for map
44 observeRecursively(change.newValue, parent, change.name);
45 break;
46 case "update": // also for array and map
47 unobserveRecursively(change.oldValue);
48 observeRecursively(change.newValue, parent, change.name || "" + change.index);
49 break;
50 case "remove": // object
51 case "delete": // map
52 unobserveRecursively(change.oldValue);
53 break;
54 // Array changes
55 case "splice":
56 change.removed.map(unobserveRecursively);
57 change.added.forEach(function (value, idx) {
58 return observeRecursively(value, parent, "" + (change.index + idx));
59 });
60 // update paths
61 for (var i = change.index + change.addedCount; i < change.object.length; i++) {
62 if (isRecursivelyObservable(change.object[i])) {
63 var entry = entrySet.get(change.object[i]);
64 if (entry)
65 entry.path = "" + i;
66 }
67 }
68 break;
69 }
70 }
71 function observeRecursively(thing, parent, path) {
72 if (isRecursivelyObservable(thing)) {
73 var entry = entrySet.get(thing);
74 if (entry) {
75 if (entry.parent !== parent || entry.path !== path)
76 // MWE: this constraint is artificial, and this tool could be made to work with cycles,
77 // but it increases administration complexity, has tricky edge cases and the meaning of 'path'
78 // would become less clear. So doesn't seem to be needed for now
79 throw new Error("The same observable object cannot appear twice in the same tree," +
80 (" trying to assign it to '" + buildPath(parent) + "/" + path + "',") +
81 (" but it already exists at '" + buildPath(entry.parent) + "/" + entry.path + "'"));
82 }
83 else {
84 var entry_1 = {
85 parent: parent,
86 path: path,
87 dispose: observe(thing, genericListener),
88 };
89 entrySet.set(thing, entry_1);
90 entries(thing).forEach(function (_a) {
91 var key = _a[0], value = _a[1];
92 return observeRecursively(value, entry_1, key);
93 });
94 }
95 }
96 }
97 function unobserveRecursively(thing) {
98 if (isRecursivelyObservable(thing)) {
99 var entry = entrySet.get(thing);
100 if (!entry)
101 return;
102 entrySet.delete(thing);
103 entry.dispose();
104 values(thing).forEach(unobserveRecursively);
105 }
106 }
107 observeRecursively(target, undefined, "");
108 return function () {
109 unobserveRecursively(target);
110 };
111}