@legendapp/state 1.0.0-rc.31 → 1.0.0-rc.32

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
@@ -4,7 +4,7 @@ export { batch, beginBatch, endBatch, afterBatch } from './src/batching';
4
4
  export { computed } from './src/computed';
5
5
  export { event } from './src/event';
6
6
  export { observe } from './src/observe';
7
- export { when } from './src/when';
7
+ export { when, whenReady } from './src/when';
8
8
  export * from './src/observableInterfaces';
9
9
  export { isEmpty, isArray, isBoolean, isFunction, isObject, isPrimitive, isPromise, isString, isSymbol, } from './src/is';
10
10
  import { setAtPath, getNode } from './src/helpers';
package/index.js CHANGED
@@ -51,155 +51,6 @@ function isChildNodeValue(node) {
51
51
  return !!node.parent;
52
52
  }
53
53
 
54
- let timeout;
55
- let numInBatch = 0;
56
- let isRunningBatch = false;
57
- let didDelayEndBatch = false;
58
- let _batch = [];
59
- let _afterBatch = [];
60
- // Use a Map of callbacks for fast lookups to update the BatchItem
61
- let _batchMap = new Map();
62
- function onActionTimeout() {
63
- if (_batch.length > 0) {
64
- if (process.env.NODE_ENV === 'development') {
65
- console.error('Forcibly completing observableBatcher because end() was never called. This may be due to an uncaught error between begin() and end().');
66
- }
67
- endBatch(/*force*/ true);
68
- }
69
- }
70
- function createPreviousHandler(value, changes) {
71
- // Create a function that clones the current state and injects the previous data at the changed path
72
- return function () {
73
- let clone = value ? JSON.parse(JSON.stringify(value)) : {};
74
- for (let i = 0; i < changes.length; i++) {
75
- const { path, prevAtPath } = changes[i];
76
- let o = clone;
77
- if (path.length > 0) {
78
- let i;
79
- for (i = 0; i < path.length - 1; i++) {
80
- o = o[path[i]];
81
- }
82
- o[path[i]] = prevAtPath;
83
- }
84
- else {
85
- clone = prevAtPath;
86
- }
87
- }
88
- return clone;
89
- };
90
- }
91
- function batchNotify(b, immediate) {
92
- const isFunc = isFunction(b);
93
- const cb = isFunc ? b : b.cb;
94
- if (!immediate && numInBatch > 0) {
95
- // Set a timeout to call end() in case end() is never called or there's an uncaught error
96
- if (!timeout) {
97
- timeout = setTimeout(onActionTimeout, 0);
98
- }
99
- const existing = _batchMap.get(cb);
100
- // If this callback already exists, make sure it has the latest value but do not add it
101
- if (existing) {
102
- if (!isFunc) {
103
- const params = existing.params;
104
- params.value = b.params.value;
105
- params.changes.push(...b.params.changes);
106
- params.getPrevious = createPreviousHandler(params.value, params.changes);
107
- }
108
- }
109
- else {
110
- if (!isFunc) {
111
- b.params.getPrevious = createPreviousHandler(b.params.value, b.params.changes);
112
- }
113
- _batch.push(b);
114
- _batchMap.set(cb, isFunc ? true : b);
115
- }
116
- }
117
- else {
118
- // If not batched callback immediately
119
- if (isFunc) {
120
- b();
121
- }
122
- else {
123
- b.params.getPrevious = createPreviousHandler(b.params.value, b.params.changes);
124
- b.cb(b.params);
125
- }
126
- if (numInBatch === 0) {
127
- // Run afterBatch callbacks if this is not batched
128
- const after = _afterBatch;
129
- _afterBatch = [];
130
- for (let i = 0; i < after.length; i++) {
131
- after[i]();
132
- }
133
- }
134
- }
135
- }
136
- function batch(fn, onComplete) {
137
- if (onComplete) {
138
- _afterBatch.push(onComplete);
139
- }
140
- beginBatch();
141
- try {
142
- fn();
143
- }
144
- finally {
145
- endBatch();
146
- }
147
- }
148
- function beginBatch() {
149
- numInBatch++;
150
- }
151
- function endBatch(force) {
152
- numInBatch--;
153
- if (numInBatch <= 0 || force) {
154
- if (isRunningBatch) {
155
- // Don't want to run multiple endBatches recursively, so just note that an endBatch
156
- // was delayed so that the top level endBatch will run endBatch again after it's done.
157
- didDelayEndBatch = true;
158
- }
159
- else {
160
- if (timeout) {
161
- clearTimeout(timeout);
162
- timeout = undefined;
163
- }
164
- numInBatch = 0;
165
- // Save batch locally and reset _batch first because a new batch could begin while looping over callbacks.
166
- // This can happen with observableComputed for example.
167
- const batch = _batch;
168
- const after = _afterBatch;
169
- _batch = [];
170
- _batchMap = new Map();
171
- _afterBatch = [];
172
- isRunningBatch = true;
173
- for (let i = 0; i < batch.length; i++) {
174
- const b = batch[i];
175
- if (isFunction(b)) {
176
- b();
177
- }
178
- else {
179
- const { cb } = b;
180
- cb(b.params);
181
- }
182
- }
183
- for (let i = 0; i < after.length; i++) {
184
- after[i]();
185
- }
186
- isRunningBatch = false;
187
- if (didDelayEndBatch) {
188
- didDelayEndBatch = false;
189
- endBatch(true);
190
- }
191
- }
192
- }
193
- }
194
- function afterBatch(fn) {
195
- if (numInBatch > 0) {
196
- _afterBatch.push(fn);
197
- }
198
- else {
199
- fn();
200
- }
201
- }
202
-
203
54
  let trackCount = 0;
204
55
  const trackingQueue = [];
205
56
  const tracking = {
@@ -331,6 +182,206 @@ function findIDKey(obj, node) {
331
182
  return idKey;
332
183
  }
333
184
 
185
+ let timeout;
186
+ let numInBatch = 0;
187
+ let isRunningBatch = false;
188
+ let didDelayEndBatch = false;
189
+ let _afterBatch = [];
190
+ let _batchMap = new Map();
191
+ function onActionTimeout() {
192
+ if (_batchMap.size > 0) {
193
+ if (process.env.NODE_ENV === 'development') {
194
+ console.error('Forcibly completing observableBatcher because end() was never called. This may be due to an uncaught error between begin() and end().');
195
+ }
196
+ endBatch(/*force*/ true);
197
+ }
198
+ }
199
+ function createPreviousHandlerInner(value, changes) {
200
+ // Clones the current state and inject the previous data at the changed path
201
+ let clone = value ? JSON.parse(JSON.stringify(value)) : {};
202
+ for (let i = 0; i < changes.length; i++) {
203
+ const { path, prevAtPath } = changes[i];
204
+ let o = clone;
205
+ if (path.length > 0) {
206
+ let i;
207
+ for (i = 0; i < path.length - 1; i++) {
208
+ o = o[path[i]];
209
+ }
210
+ o[path[i]] = prevAtPath;
211
+ }
212
+ else {
213
+ clone = prevAtPath;
214
+ }
215
+ }
216
+ return clone;
217
+ }
218
+ function createPreviousHandler(value, changes) {
219
+ // Create a function that generates the previous state
220
+ // We don't want to always do this because cloning is expensive
221
+ // so it's better to run on demand.
222
+ return function () {
223
+ return createPreviousHandlerInner(value, changes);
224
+ };
225
+ }
226
+ function notify(node, value, prev, level, whenOptimizedOnlyIf) {
227
+ // Run immediate listeners if there are any
228
+ const changesInBatch = new Map();
229
+ computeChangesRecursive(changesInBatch, node, value, [], [], value, prev,
230
+ /*immediate*/ true, level, whenOptimizedOnlyIf);
231
+ batchNotifyChanges(changesInBatch, /*immediate*/ true);
232
+ // Update the current batch
233
+ const existing = _batchMap.get(node);
234
+ if (existing) {
235
+ existing.value = value;
236
+ // TODO: level, whenOptimizedOnlyIf
237
+ }
238
+ else {
239
+ _batchMap.set(node, { value, prev, level, whenOptimizedOnlyIf });
240
+ }
241
+ // If not in a batch run it immediately
242
+ if (numInBatch <= 0) {
243
+ runBatch();
244
+ }
245
+ }
246
+ function computeChangesAtNode(changesInBatch, node, value, path, pathTypes, valueAtPath, prevAtPath, immediate, level, whenOptimizedOnlyIf) {
247
+ // If there are listeners at this node compute the changes that need to be run
248
+ if (immediate ? node.listenersImmediate : node.listeners) {
249
+ const change = {
250
+ path,
251
+ pathTypes,
252
+ valueAtPath,
253
+ prevAtPath,
254
+ };
255
+ const changeInBatch = changesInBatch.get(node);
256
+ // If the node itself has been changed then we can ignore all the child changes
257
+ if (changeInBatch && path.length > 0) {
258
+ changeInBatch.changes.push(change);
259
+ }
260
+ else {
261
+ changesInBatch.set(node, {
262
+ level,
263
+ value,
264
+ whenOptimizedOnlyIf,
265
+ changes: [change],
266
+ });
267
+ }
268
+ }
269
+ }
270
+ function computeChangesRecursive(changesInBatch, node, value, path, pathTypes, valueAtPath, prevAtPath, immediate, level, whenOptimizedOnlyIf) {
271
+ // Do the compute at this node
272
+ computeChangesAtNode(changesInBatch, node, value, path, pathTypes, valueAtPath, prevAtPath, immediate, level, whenOptimizedOnlyIf);
273
+ // If not root notify up through parents
274
+ if (node.parent) {
275
+ const parent = node.parent;
276
+ if (parent) {
277
+ const parentValue = getNodeValue(parent);
278
+ computeChangesRecursive(changesInBatch, parent, parentValue, [node.key].concat(path), [(isArray(value) ? 'array' : 'object')].concat(pathTypes), valueAtPath, prevAtPath, immediate, level + 1, whenOptimizedOnlyIf);
279
+ }
280
+ }
281
+ }
282
+ function batchNotifyChanges(changesInBatch, immediate) {
283
+ // For each change in the batch, notify all of the listeners
284
+ changesInBatch.forEach(({ changes, level, value, whenOptimizedOnlyIf }, node) => {
285
+ const listeners = immediate ? node.listenersImmediate : node.listeners;
286
+ if (listeners) {
287
+ let listenerParams;
288
+ // Need to convert to an array here instead of using a for...of loop because listeners can change while iterating
289
+ const arr = Array.from(listeners);
290
+ for (let i = 0; i < arr.length; i++) {
291
+ const listenerFn = arr[i];
292
+ const { track, noArgs, listener } = listenerFn;
293
+ const ok = track === true || track === 'shallow'
294
+ ? level <= 0
295
+ : track === 'optimize'
296
+ ? whenOptimizedOnlyIf && level <= 0
297
+ : true;
298
+ // Notify if listener is not shallow or if this is the first level
299
+ if (ok) {
300
+ // Create listenerParams if not already created
301
+ if (!noArgs && !listenerParams) {
302
+ listenerParams = {
303
+ value,
304
+ getPrevious: createPreviousHandler(value, changes),
305
+ changes,
306
+ };
307
+ }
308
+ listener(listenerParams);
309
+ }
310
+ }
311
+ }
312
+ });
313
+ }
314
+ function runBatch() {
315
+ const map = _batchMap;
316
+ _batchMap = new Map();
317
+ const changesInBatch = new Map();
318
+ // First compute all of the changes at each node. It's important to do this first before
319
+ // running all the notifications because createPreviousHandler depends on knowing
320
+ // all of the changes happening at the node.
321
+ map.forEach(({ value, prev, level, whenOptimizedOnlyIf }, node) => {
322
+ computeChangesRecursive(changesInBatch, node, value, [], [], value, prev, false, level, whenOptimizedOnlyIf);
323
+ });
324
+ // Once all changes are computed, notify all listeners for each node with the computed changes.
325
+ batchNotifyChanges(changesInBatch, false);
326
+ }
327
+ function batch(fn, onComplete) {
328
+ if (onComplete) {
329
+ _afterBatch.push(onComplete);
330
+ }
331
+ beginBatch();
332
+ try {
333
+ fn();
334
+ }
335
+ finally {
336
+ endBatch();
337
+ }
338
+ }
339
+ function beginBatch() {
340
+ numInBatch++;
341
+ if (!timeout) {
342
+ timeout = setTimeout(onActionTimeout, 0);
343
+ }
344
+ }
345
+ function endBatch(force) {
346
+ numInBatch--;
347
+ if (numInBatch <= 0 || force) {
348
+ if (isRunningBatch) {
349
+ // Don't want to run multiple endBatches recursively, so just note that an endBatch
350
+ // was delayed so that the top level endBatch will run endBatch again after it's done.
351
+ didDelayEndBatch = true;
352
+ }
353
+ else {
354
+ if (timeout) {
355
+ clearTimeout(timeout);
356
+ timeout = undefined;
357
+ }
358
+ numInBatch = 0;
359
+ // Save batch locally and reset _batch first because a new batch could begin while looping over callbacks.
360
+ // This can happen with observableComputed for example.
361
+ const after = _afterBatch;
362
+ _afterBatch = [];
363
+ isRunningBatch = true;
364
+ runBatch();
365
+ isRunningBatch = false;
366
+ if (didDelayEndBatch) {
367
+ didDelayEndBatch = false;
368
+ endBatch(true);
369
+ }
370
+ for (let i = 0; i < after.length; i++) {
371
+ after[i]();
372
+ }
373
+ }
374
+ }
375
+ }
376
+ function afterBatch(fn) {
377
+ if (numInBatch > 0) {
378
+ _afterBatch.push(fn);
379
+ }
380
+ else {
381
+ fn();
382
+ }
383
+ }
384
+
334
385
  function isObservable(obs) {
335
386
  return obs && !!obs[symbolIsObservable];
336
387
  }
@@ -480,69 +531,23 @@ function isObservableValueReady(value) {
480
531
  return !!value && ((!isObject(value) && !isArray(value)) || !isEmpty(value));
481
532
  }
482
533
 
483
- function doNotify(node, value, path, pathTypes, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
484
- const listeners = node.listeners;
485
- if (listeners) {
486
- let listenerParams;
487
- // Need to convert to an array here instead of using a for...of loop because listeners can change while iterating
488
- const arr = Array.from(listeners);
489
- for (let i = 0; i < arr.length; i++) {
490
- const listenerFn = arr[i];
491
- const { track, noArgs, immediate, listener } = listenerFn;
492
- const ok = track === true || track === 'shallow'
493
- ? level <= 0
494
- : track === 'optimize'
495
- ? whenOptimizedOnlyIf && level <= 0
496
- : true;
497
- // Notify if listener is not shallow or if this is the first level
498
- if (ok) {
499
- if (!noArgs && !listenerParams) {
500
- listenerParams = {
501
- value,
502
- changes: [
503
- {
504
- path,
505
- pathTypes,
506
- valueAtPath,
507
- prevAtPath,
508
- },
509
- ],
510
- };
511
- }
512
- batchNotify(noArgs ? listener : { cb: listener, params: listenerParams }, immediate);
513
- }
514
- }
515
- }
516
- }
517
- function _notifyParents(node, value, path, pathTypes, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf) {
518
- // Do the notify
519
- doNotify(node, value, path, pathTypes, valueAtPath, prevAtPath, level, whenOptimizedOnlyIf);
520
- // If not root notify up through parents
521
- if (node.parent) {
522
- const parent = node.parent;
523
- if (parent) {
524
- const parentValue = getNodeValue(parent);
525
- _notifyParents(parent, parentValue, [node.key].concat(path), [(isArray(value) ? 'array' : 'object')].concat(pathTypes), valueAtPath, prevAtPath, level + 1, whenOptimizedOnlyIf);
526
- }
527
- }
528
- }
529
- function notify(node, value, prev, level, whenOptimizedOnlyIf) {
530
- // Notify self and up through parents
531
- _notifyParents(node, value, [], [], value, prev, level, whenOptimizedOnlyIf);
532
- }
533
-
534
534
  function onChange(node, callback, options, noArgs) {
535
- let listeners = node.listeners;
535
+ const { trackingType, initial, immediate } = options || {};
536
+ let listeners = immediate ? node.listenersImmediate : node.listeners;
536
537
  if (!listeners) {
537
- node.listeners = listeners = new Set();
538
+ listeners = new Set();
539
+ if (immediate) {
540
+ node.listenersImmediate = listeners;
541
+ }
542
+ else {
543
+ node.listeners = listeners;
544
+ }
538
545
  }
539
546
  checkActivate(node);
540
- const { trackingType, initial, immediate } = options || {};
541
547
  const listener = {
542
548
  listener: callback,
543
549
  track: trackingType,
544
550
  noArgs,
545
- immediate: immediate,
546
551
  };
547
552
  listeners.add(listener);
548
553
  let parent = node.parent;
@@ -610,7 +615,7 @@ ObservablePrimitiveClass.prototype.set = function (value) {
610
615
  const root = this._node.root;
611
616
  const prev = root._;
612
617
  root._ = value;
613
- doNotify(this._node, value, [], [], value, prev, 0);
618
+ notify(this._node, value, prev, 0);
614
619
  return this;
615
620
  };
616
621
  ObservablePrimitiveClass.prototype.toggle = function () {
@@ -623,6 +628,10 @@ ObservablePrimitiveClass.prototype.toggle = function () {
623
628
  }
624
629
  return !value;
625
630
  };
631
+ ObservablePrimitiveClass.prototype.delete = function () {
632
+ this.set(undefined);
633
+ return this;
634
+ };
626
635
  // Listener
627
636
  ObservablePrimitiveClass.prototype.onChange = function (cb, options, noArgs) {
628
637
  return onChange(this._node, cb, options, noArgs);
@@ -685,8 +694,8 @@ function updateNodes(parent, obj, prevValue) {
685
694
  if ((isObject(obj) && obj[symbolOpaque]) || (isObject(prevValue) && prevValue[symbolOpaque])) {
686
695
  const isDiff = obj !== prevValue;
687
696
  if (isDiff) {
688
- if (parent.listeners) {
689
- doNotify(parent, obj, [], [], obj, prevValue, 0);
697
+ if (parent.listeners || parent.listenersImmediate) {
698
+ notify(parent, obj, prevValue, 0);
690
699
  }
691
700
  }
692
701
  return isDiff;
@@ -747,8 +756,8 @@ function updateNodes(parent, obj, prevValue) {
747
756
  if (!isPrimitive(prev)) {
748
757
  updateNodes(child, undefined, prev);
749
758
  }
750
- if (child.listeners) {
751
- doNotify(child, undefined, [], [], undefined, prev, 0);
759
+ if (child.listeners || child.listenersImmediate) {
760
+ notify(child, undefined, prev, 0);
752
761
  }
753
762
  }
754
763
  }
@@ -815,8 +824,8 @@ function updateNodes(parent, obj, prevValue) {
815
824
  // Or if the position changed in an array whose length did not change
816
825
  // But do not notify child if the parent is an array with changing length -
817
826
  // the array's listener will cover it
818
- if (child.listeners) {
819
- doNotify(child, value, [], [], value, prev, 0, !isArrDiff);
827
+ if (child.listeners || child.listenersImmediate) {
828
+ notify(child, value, prev, 0, !isArrDiff);
820
829
  }
821
830
  }
822
831
  }
@@ -1183,10 +1192,12 @@ function observe(selectorOrRun, reactionOrOptions, options) {
1183
1192
  e.onCleanupReaction();
1184
1193
  e.onCleanupReaction = undefined;
1185
1194
  }
1195
+ // Call the reaction if there is one and the value changed
1186
1196
  if (reaction && (e.num > 0 || !selectorOrRun[symbolIsEvent]) && e.previous !== e.value) {
1187
1197
  reaction(e);
1188
- e.previous = e.value;
1189
1198
  }
1199
+ // Update the previous value
1200
+ e.previous = e.value;
1190
1201
  // Increment the counter
1191
1202
  e.num++;
1192
1203
  endBatch();
@@ -1254,12 +1265,12 @@ function event() {
1254
1265
  };
1255
1266
  }
1256
1267
 
1257
- function when(predicate, effect) {
1268
+ function _when(predicate, effect, checkReady) {
1258
1269
  let value;
1259
1270
  // Create a wrapping fn that calls the effect if predicate returns true
1260
1271
  function run(e) {
1261
1272
  const ret = computeSelector(predicate);
1262
- if (isObservableValueReady(ret)) {
1273
+ if (checkReady ? isObservableValueReady(ret) : ret) {
1263
1274
  value = ret;
1264
1275
  // If value is truthy then run the effect
1265
1276
  effect === null || effect === void 0 ? void 0 : effect(ret);
@@ -1267,14 +1278,15 @@ function when(predicate, effect) {
1267
1278
  e.cancel = true;
1268
1279
  }
1269
1280
  }
1270
- // Create an effect for the fn
1281
+ // Run in an observe
1271
1282
  observe(run);
1272
- // If first run resulted in a truthy value just return it
1283
+ // If first run resulted in a truthy value just return it.
1273
1284
  // It will have set e.cancel so no need to dispose
1274
1285
  if (value !== undefined) {
1275
1286
  return Promise.resolve(value);
1276
1287
  }
1277
1288
  else {
1289
+ // Wrap it in a promise
1278
1290
  const promise = new Promise((resolve) => {
1279
1291
  if (effect) {
1280
1292
  const originalEffect = effect;
@@ -1290,6 +1302,12 @@ function when(predicate, effect) {
1290
1302
  return promise;
1291
1303
  }
1292
1304
  }
1305
+ function when(predicate, effect) {
1306
+ return _when(predicate, effect, false);
1307
+ }
1308
+ function whenReady(predicate, effect) {
1309
+ return _when(predicate, effect, true);
1310
+ }
1293
1311
 
1294
1312
  const internal = {
1295
1313
  getNode,
@@ -1344,4 +1362,5 @@ exports.symbolIsObservable = symbolIsObservable;
1344
1362
  exports.tracking = tracking;
1345
1363
  exports.updateTracking = updateTracking;
1346
1364
  exports.when = when;
1365
+ exports.whenReady = whenReady;
1347
1366
  //# sourceMappingURL=index.js.map