@legendapp/state 0.16.1 → 0.17.0-next.0

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/index.d.ts CHANGED
@@ -9,3 +9,4 @@ export { when } from './src/when';
9
9
  export * from './src/observableInterfaces';
10
10
  export { isEmpty } from './src/is';
11
11
  export { lockObservable } from './src/helpers';
12
+ export { ObservablePrimitive } from './src/ObservablePrimitive';
package/index.js CHANGED
@@ -7,6 +7,7 @@ const tracking = {
7
7
  nodes: undefined,
8
8
  listeners: undefined,
9
9
  updates: undefined,
10
+ callbacksMarked: new Set(),
10
11
  };
11
12
  function beginTracking() {
12
13
  // Keep a copy of the previous tracking context so it can be restored
@@ -59,6 +60,22 @@ function checkTracking(node, track) {
59
60
  }
60
61
  }
61
62
  }
63
+ let timeoutSweep;
64
+ function scheduleSweep() {
65
+ if (timeoutSweep) {
66
+ clearTimeout(timeoutSweep);
67
+ }
68
+ timeoutSweep = setTimeout(sweep, 0);
69
+ }
70
+ function sweep() {
71
+ timeoutSweep = undefined;
72
+ if (tracking.callbacksMarked.size)
73
+ console.log('sweeping');
74
+ for (let marked of tracking.callbacksMarked) {
75
+ marked();
76
+ }
77
+ tracking.callbacksMarked.clear();
78
+ }
62
79
 
63
80
  /** @internal */
64
81
  function isArray(obj) {
@@ -92,6 +109,15 @@ function isBoolean(obj) {
92
109
  function isEmpty(obj) {
93
110
  return obj && Object.keys(obj).length === 0;
94
111
  }
112
+ const mapPrimitives = new Map([
113
+ ['boolean', true],
114
+ ['string', true],
115
+ ['number', true],
116
+ ]);
117
+ /** @internal */
118
+ function isActualPrimitive(arg) {
119
+ return mapPrimitives.has(typeof arg);
120
+ }
95
121
 
96
122
  const symbolDateModified = Symbol('dateModified');
97
123
  const symbolIsObservable = Symbol('isObservable');
@@ -160,48 +186,32 @@ function getChildNode(node, key) {
160
186
  return child;
161
187
  }
162
188
 
163
- function isObservable(obs) {
164
- return obs && !!obs[symbolIsObservable];
165
- }
166
- function getNode(obs) {
167
- return obs[symbolGetNode];
168
- }
169
- function lockObservable(obs, value) {
170
- var _a;
171
- const root = (_a = getNode(obs)) === null || _a === void 0 ? void 0 : _a.root;
172
- if (root) {
173
- root.locked = value;
174
- }
175
- }
176
- function mergeIntoObservable(target, ...sources) {
177
- var _a, _b;
178
- if (!sources.length)
179
- return target;
180
- const source = sources.shift();
181
- const needsSet = isObservable(target);
182
- if (isObject(target) && isObject(source)) {
183
- if (source[symbolDateModified]) {
184
- if (needsSet) {
185
- target.set(symbolDateModified, source[symbolDateModified]);
186
- }
187
- else {
188
- target[symbolDateModified] = source[symbolDateModified];
189
- }
190
- }
191
- const value = ((_b = (_a = target).get) === null || _b === void 0 ? void 0 : _b.call(_a)) || target;
192
- for (const key in source) {
193
- if (isObject(source[key])) {
194
- if (!value[key] || !isObject(value[key])) {
195
- needsSet ? target.set(key, {}) : (target[key] = {});
196
- }
197
- mergeIntoObservable(target[key], source[key]);
198
- }
199
- else {
200
- needsSet ? target.set(key, source[key]) : (target[key] = source[key]);
201
- }
202
- }
203
- }
204
- return mergeIntoObservable(target, ...sources);
189
+ /******************************************************************************
190
+ Copyright (c) Microsoft Corporation.
191
+
192
+ Permission to use, copy, modify, and/or distribute this software for any
193
+ purpose with or without fee is hereby granted.
194
+
195
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
196
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
197
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
198
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
199
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
200
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
201
+ PERFORMANCE OF THIS SOFTWARE.
202
+ ***************************************************************************** */
203
+
204
+ function __classPrivateFieldGet(receiver, state, kind, f) {
205
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
206
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
207
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
208
+ }
209
+
210
+ function __classPrivateFieldSet(receiver, state, value, kind, f) {
211
+ if (kind === "m") throw new TypeError("Private method is not writable");
212
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
213
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
214
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
205
215
  }
206
216
 
207
217
  let timeout;
@@ -269,14 +279,185 @@ function endBatch(force) {
269
279
  }
270
280
  }
271
281
 
272
- function onChange(node, callback, track, noArgs) {
282
+ function createPreviousHandler(value, path, prevAtPath) {
283
+ // Create a function that clones the current state and injects the previous data at the changed path
284
+ return function () {
285
+ let clone = value ? JSON.parse(JSON.stringify(value)) : path.length > 0 ? {} : value;
286
+ let o = clone;
287
+ if (path.length > 0) {
288
+ let i;
289
+ for (i = 0; i < path.length - 1; i++) {
290
+ o = o[path[i]];
291
+ }
292
+ o[path[i]] = prevAtPath;
293
+ }
294
+ else {
295
+ clone = prevAtPath;
296
+ }
297
+ return clone;
298
+ };
299
+ }
300
+ function doNotify(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
301
+ const listeners = node.listeners;
302
+ if (listeners) {
303
+ let getPrevious;
304
+ for (let listenerFn of listeners) {
305
+ const { track, noArgs } = listenerFn;
306
+ const ok = track === exports.Tracking.shallow
307
+ ? level <= 0
308
+ : track === exports.Tracking.optimized
309
+ ? whenOptimizedOnlyIf && level <= 0
310
+ : true;
311
+ // Notify if listener is not shallow or if this is the first level
312
+ if (ok) {
313
+ // Create a function to get the previous data. Computing a clone of previous data can be expensive if doing
314
+ // it often, so leave it up to the caller.
315
+ if (!noArgs && !getPrevious) {
316
+ getPrevious = createPreviousHandler(value, path, prevAtPath);
317
+ }
318
+ batchNotify(noArgs
319
+ ? listenerFn.listener
320
+ : {
321
+ cb: listenerFn.listener,
322
+ value,
323
+ getPrevious,
324
+ path,
325
+ valueAtPath,
326
+ prevAtPath,
327
+ node,
328
+ });
329
+ }
330
+ }
331
+ }
332
+ }
333
+ function _notifyParents(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
334
+ // Do the notify
335
+ doNotify(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf);
336
+ // If not root notify up through parents
337
+ if (node.parent) {
338
+ const parent = node.parent;
339
+ if (parent) {
340
+ const parentValue = getNodeValue(parent);
341
+ _notifyParents(parent, parentValue, [node.key].concat(path), valueAtPath, prevAtPath, level + 1, whenOptimizedOnlyIf);
342
+ }
343
+ }
344
+ }
345
+ function notify(node, value, prev, level, whenOptimizedOnlyIf) {
346
+ // Notify self and up through parents
347
+ _notifyParents(node, value, [], value, prev, level, whenOptimizedOnlyIf);
348
+ }
349
+
350
+ function onChange(node, callback, track, noArgs, markAndSweep) {
273
351
  let listeners = node.listeners;
274
352
  if (!listeners) {
275
353
  node.listeners = listeners = new Set();
276
354
  }
277
- const listener = { listener: callback, track: track, noArgs };
355
+ let dispose;
356
+ let listener;
357
+ let c = callback;
358
+ if (markAndSweep) {
359
+ c = function () {
360
+ tracking.callbacksMarked.add(dispose);
361
+ callback();
362
+ };
363
+ }
364
+ listener = { listener: c, track: track, noArgs };
278
365
  listeners.add(listener);
279
- return () => listeners.delete(listener);
366
+ return (dispose = () => {
367
+ if (markAndSweep) {
368
+ tracking.callbacksMarked.delete(dispose);
369
+ }
370
+ listeners.delete(listener);
371
+ });
372
+ }
373
+
374
+ var _ObservablePrimitive_node;
375
+ class ObservablePrimitive {
376
+ constructor(node) {
377
+ _ObservablePrimitive_node.set(this, void 0);
378
+ __classPrivateFieldSet(this, _ObservablePrimitive_node, node, "f");
379
+ this.set = this.set.bind(this);
380
+ }
381
+ get value() {
382
+ const node = __classPrivateFieldGet(this, _ObservablePrimitive_node, "f");
383
+ updateTracking(node);
384
+ return node.root._;
385
+ }
386
+ set value(value) {
387
+ if (__classPrivateFieldGet(this, _ObservablePrimitive_node, "f").root.locked) {
388
+ throw new Error(process.env.NODE_ENV === 'development'
389
+ ? '[legend-state] Cannot modify an observable while it is locked. Please make sure that you unlock the observable before making changes.'
390
+ : '[legend-state] Modified locked observable');
391
+ }
392
+ const prev = __classPrivateFieldGet(this, _ObservablePrimitive_node, "f").root._;
393
+ __classPrivateFieldGet(this, _ObservablePrimitive_node, "f").root._ = value;
394
+ doNotify(__classPrivateFieldGet(this, _ObservablePrimitive_node, "f"), value, [], value, prev, 0);
395
+ }
396
+ get() {
397
+ return this.value;
398
+ }
399
+ set(value) {
400
+ if (isFunction(value)) {
401
+ value = value(__classPrivateFieldGet(this, _ObservablePrimitive_node, "f").root._);
402
+ }
403
+ this.value = value;
404
+ return this;
405
+ }
406
+ onChange(cb, track, noArgs, markAndSweep) {
407
+ return onChange(__classPrivateFieldGet(this, _ObservablePrimitive_node, "f"), cb, track, noArgs, markAndSweep);
408
+ }
409
+ obs() {
410
+ return this;
411
+ }
412
+ /** @internal */
413
+ getNode() {
414
+ return __classPrivateFieldGet(this, _ObservablePrimitive_node, "f");
415
+ }
416
+ }
417
+ _ObservablePrimitive_node = new WeakMap();
418
+
419
+ function isObservable(obs) {
420
+ return obs && (obs instanceof ObservablePrimitive || !!obs[symbolIsObservable]);
421
+ }
422
+ function getNode(obs) {
423
+ return obs instanceof ObservablePrimitive ? obs.getNode() : obs[symbolGetNode];
424
+ }
425
+ function lockObservable(obs, value) {
426
+ var _a;
427
+ const root = (_a = getNode(obs)) === null || _a === void 0 ? void 0 : _a.root;
428
+ if (root) {
429
+ root.locked = value;
430
+ }
431
+ }
432
+ function mergeIntoObservable(target, ...sources) {
433
+ var _a, _b;
434
+ if (!sources.length)
435
+ return target;
436
+ const source = sources.shift();
437
+ const needsSet = isObservable(target);
438
+ if (isObject(target) && isObject(source)) {
439
+ if (source[symbolDateModified]) {
440
+ if (needsSet) {
441
+ target.set(symbolDateModified, source[symbolDateModified]);
442
+ }
443
+ else {
444
+ target[symbolDateModified] = source[symbolDateModified];
445
+ }
446
+ }
447
+ const value = ((_b = (_a = target).get) === null || _b === void 0 ? void 0 : _b.call(_a)) || target;
448
+ for (const key in source) {
449
+ if (isObject(source[key])) {
450
+ if (!value[key] || !isObject(value[key])) {
451
+ needsSet ? target.set(key, {}) : (target[key] = {});
452
+ }
453
+ mergeIntoObservable(target[key], source[key]);
454
+ }
455
+ else {
456
+ needsSet ? target.set(key, source[key]) : (target[key] = source[key]);
457
+ }
458
+ }
459
+ }
460
+ return mergeIntoObservable(target, ...sources);
280
461
  }
281
462
 
282
463
  let lastAccessedNode;
@@ -305,21 +486,22 @@ const objectFns = new Map([
305
486
  ['delete', deleteFn],
306
487
  ]);
307
488
  // Override primitives
308
- const wrapFn = (fn) => function (...args) {
489
+ const wrapFn = (name, fn) => function (...args) {
309
490
  if (lastAccessedNode && lastAccessedPrimitive) {
310
491
  const node = getChildNode(lastAccessedNode, lastAccessedPrimitive);
311
492
  if (getNodeValue(node) === this) {
312
493
  return fn(node, ...args);
313
494
  }
314
495
  else if (process.env.NODE_ENV === 'development') {
315
- console.error(`[legend-state] Error calling ${fn} on a primitive with value ${this}. Please ensure that if you are saving references to observable functions on primitive values that you use obs() first, like obs.primitive.obs().set.`);
496
+ console.error(`[legend-state] Error calling ${name} on a primitive with value [${this}]. Please ensure that if you are saving references to observable functions on primitive values that you use obs() first, like obs.primitive.obs().set.`);
497
+ debugger;
316
498
  }
317
499
  }
318
500
  };
319
501
  const toOverride = [Number, Boolean, String];
320
502
  for (let [key, fn] of objectFns) {
321
503
  for (let i = 0; i < toOverride.length; i++) {
322
- toOverride[i].prototype[key] = wrapFn(fn);
504
+ toOverride[i].prototype[key] = wrapFn(key, fn);
323
505
  }
324
506
  }
325
507
  function collectionSetter(node, target, prop, ...args) {
@@ -377,7 +559,7 @@ function updateNodes(parent, obj, prevValue) {
377
559
  updateNodes(child, undefined, prev);
378
560
  }
379
561
  if (child.listeners) {
380
- _notify(child, undefined, [], undefined, prev, 0);
562
+ doNotify(child, undefined, [], undefined, prev, 0);
381
563
  }
382
564
  }
383
565
  }
@@ -431,7 +613,7 @@ function updateNodes(parent, obj, prevValue) {
431
613
  // But do not notify child if the parent is an array with changing length -
432
614
  // the array's listener will cover it
433
615
  if (child.listeners) {
434
- _notify(child, value, [], value, prev, 0, !isArrDiff);
616
+ doNotify(child, value, [], value, prev, 0, !isArrDiff);
435
617
  }
436
618
  }
437
619
  }
@@ -491,7 +673,7 @@ const proxyHandler = {
491
673
  };
492
674
  }
493
675
  let value = getNodeValue(node);
494
- if (!node.parent && isPrimitive(value) && p === 'value') {
676
+ if (!node.parent && isPrimitive(value) && node.root._ === value && p === 'value') {
495
677
  updateTracking(node);
496
678
  return value;
497
679
  }
@@ -686,73 +868,6 @@ function setProp(node, key, newValue, level) {
686
868
  inSetFn = false;
687
869
  return isRoot ? getProxy(node) : getProxy(node, key);
688
870
  }
689
- function createPreviousHandler(value, path, prevAtPath) {
690
- // Create a function that clones the current state and injects the previous data at the changed path
691
- return function () {
692
- let clone = value ? JSON.parse(JSON.stringify(value)) : path.length > 0 ? {} : value;
693
- let o = clone;
694
- if (path.length > 0) {
695
- let i;
696
- for (i = 0; i < path.length - 1; i++) {
697
- o = o[path[i]];
698
- }
699
- o[path[i]] = prevAtPath;
700
- }
701
- else {
702
- clone = prevAtPath;
703
- }
704
- return clone;
705
- };
706
- }
707
- function _notify(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
708
- const listeners = node.listeners;
709
- if (listeners) {
710
- let getPrevious;
711
- for (let listenerFn of listeners) {
712
- const { track, noArgs } = listenerFn;
713
- const ok = track === exports.Tracking.shallow
714
- ? level <= 0
715
- : track === exports.Tracking.optimized
716
- ? whenOptimizedOnlyIf && level <= 0
717
- : true;
718
- // Notify if listener is not shallow or if this is the first level
719
- if (ok) {
720
- // Create a function to get the previous data. Computing a clone of previous data can be expensive if doing
721
- // it often, so leave it up to the caller.
722
- if (!getPrevious) {
723
- getPrevious = createPreviousHandler(value, path, prevAtPath);
724
- }
725
- batchNotify(noArgs
726
- ? listenerFn.listener
727
- : {
728
- cb: listenerFn.listener,
729
- value,
730
- getPrevious,
731
- path,
732
- valueAtPath,
733
- prevAtPath,
734
- node,
735
- });
736
- }
737
- }
738
- }
739
- }
740
- function _notifyParents(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
741
- // Do the notify
742
- _notify(node, value, path, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf);
743
- // If not root notify up through parents
744
- if (node.parent) {
745
- const parent = node.parent;
746
- if (parent) {
747
- const parentValue = getNodeValue(parent);
748
- _notifyParents(parent, parentValue, [node.key].concat(path), valueAtPath, prevAtPath, level + 1, whenOptimizedOnlyIf);
749
- }
750
- }
751
- }
752
- function notify(node, value, prev, level, whenOptimizedOnlyIf) {
753
- // Notify self and up through parents
754
- _notifyParents(node, value, [], value, prev, level, whenOptimizedOnlyIf);
755
- }
756
871
  function assign(node, value) {
757
872
  const proxy = getProxy(node);
758
873
  beginBatch();
@@ -788,25 +903,31 @@ function observable(value, safe) {
788
903
  id: nextNodeID.current++,
789
904
  root: obs,
790
905
  };
791
- const proxy = getProxy(node);
792
- if (promise) {
793
- promise.catch((error) => {
794
- proxy.set({ error });
795
- });
796
- promise.then((value) => {
797
- proxy.set(value);
798
- });
906
+ if (isActualPrimitive(value)) {
907
+ // @ts-ignore
908
+ return new ObservablePrimitive(node);
909
+ }
910
+ else {
911
+ const proxy = getProxy(node);
912
+ if (promise) {
913
+ promise.catch((error) => {
914
+ proxy.set({ error });
915
+ });
916
+ promise.then((value) => {
917
+ proxy.set(value);
918
+ });
919
+ }
920
+ return proxy;
799
921
  }
800
- return proxy;
801
922
  }
802
923
 
803
- function setupTracking(nodes, update, noArgs) {
924
+ function setupTracking(nodes, update, noArgs, markAndSweep) {
804
925
  let listeners = [];
805
926
  // Listen to tracked nodes
806
927
  if (nodes) {
807
928
  for (let tracked of nodes.values()) {
808
929
  const { node, track } = tracked;
809
- listeners.push(onChange(node, update, track, noArgs));
930
+ listeners.push(onChange(node, update, track, noArgs, markAndSweep));
810
931
  }
811
932
  }
812
933
  return () => {
@@ -824,7 +945,7 @@ function observe(run) {
824
945
  // Wrap it in a function so it doesn't pass all the arguments to run()
825
946
  let update = function () {
826
947
  var _a;
827
- if (cleanup) {
948
+ if (cleanup && isFunction(cleanup)) {
828
949
  cleanup();
829
950
  cleanup = undefined;
830
951
  }
@@ -849,7 +970,7 @@ function observe(run) {
849
970
  update = tracking.updates(update);
850
971
  }
851
972
  }
852
- const ret = setupTracking(tracking.nodes, update);
973
+ const ret = setupTracking(tracking.nodes, update, /*noArgs*/ true);
853
974
  endTracking(trackingPrev);
854
975
  return ret;
855
976
  }
@@ -861,11 +982,14 @@ function computed(compute) {
861
982
  const val = compute();
862
983
  if (obs) {
863
984
  // Update the computed value
985
+ lockObservable(obs, false);
864
986
  obs.set(val);
987
+ lockObservable(obs, true);
865
988
  }
866
989
  else {
867
990
  // Create the observable on the first run
868
991
  obs = observable(val);
992
+ lockObservable(obs, true);
869
993
  }
870
994
  };
871
995
  observe(fn);
@@ -881,8 +1005,10 @@ function event() {
881
1005
  // Notify increments the value so that the observable changes
882
1006
  obs.value++;
883
1007
  },
884
- on: obs.onChange,
885
- get: obs.get,
1008
+ on: function (cb) {
1009
+ return obs.onChange(cb);
1010
+ },
1011
+ get: () => obs.get(),
886
1012
  };
887
1013
  }
888
1014
 
@@ -913,6 +1039,7 @@ function when(predicate, effect) {
913
1039
  return promise || cleanup;
914
1040
  }
915
1041
 
1042
+ exports.ObservablePrimitive = ObservablePrimitive;
916
1043
  exports.batch = batch;
917
1044
  exports.beginBatch = beginBatch;
918
1045
  exports.beginTracking = beginTracking;
@@ -934,7 +1061,9 @@ exports.mergeIntoObservable = mergeIntoObservable;
934
1061
  exports.observable = observable;
935
1062
  exports.observe = observe;
936
1063
  exports.onChange = onChange;
1064
+ exports.scheduleSweep = scheduleSweep;
937
1065
  exports.setupTracking = setupTracking;
1066
+ exports.sweep = sweep;
938
1067
  exports.symbolDateModified = symbolDateModified;
939
1068
  exports.symbolIsObservable = symbolIsObservable;
940
1069
  exports.symbolUndef = symbolUndef;