aberdeen 0.1.2 → 0.2.2

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/dist/aberdeen.js CHANGED
@@ -3,7 +3,6 @@ let queueSet = new Set();
3
3
  let queueOrdered = true;
4
4
  let runQueueDepth = 0;
5
5
  let queueIndex;
6
- let recordingPatch;
7
6
  function queue(runner) {
8
7
  if (queueSet.has(runner))
9
8
  return;
@@ -13,7 +12,7 @@ function queue(runner) {
13
12
  if (!queueArray.length) {
14
13
  setTimeout(runQueue, 0);
15
14
  }
16
- else if (runner.queueOrder < queueArray[queueArray.length - 1].queueOrder) {
15
+ else if (runner._queueOrder < queueArray[queueArray.length - 1]._queueOrder) {
17
16
  queueOrdered = false;
18
17
  }
19
18
  queueArray.push(runner);
@@ -27,7 +26,7 @@ function runQueue() {
27
26
  queueArray.splice(0, queueIndex);
28
27
  queueIndex = 0;
29
28
  // Order queued observers by depth, lowest first.
30
- queueArray.sort((a, b) => a.queueOrder - b.queueOrder);
29
+ queueArray.sort((a, b) => a._queueOrder - b._queueOrder);
31
30
  queueOrdered = true;
32
31
  }
33
32
  // Process the rest of what's currently in the queue.
@@ -35,7 +34,7 @@ function runQueue() {
35
34
  for (; queueIndex < batchEndIndex && queueOrdered; queueIndex++) {
36
35
  let runner = queueArray[queueIndex];
37
36
  queueSet.delete(runner);
38
- runner.queueRun();
37
+ runner._queueRun();
39
38
  }
40
39
  // If new items have been added to the queue while processing the previous
41
40
  // batch, we'll need to run this loop again.
@@ -46,7 +45,6 @@ function runQueue() {
46
45
  runQueueDepth = 0;
47
46
  onCreateEnabled = false;
48
47
  }
49
- let scheduleOrder = 1000;
50
48
  /**
51
49
  * Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
52
50
  *
@@ -65,8 +63,8 @@ let scheduleOrder = 1000;
65
63
  * @param func The function to be executed as a DOM read operation.
66
64
  */
67
65
  export function scheduleDomReader(func) {
68
- let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? ((queueArray[queueIndex].queueOrder + 1) & (~1)) : 1000;
69
- queue({ queueOrder: order, queueRun: func });
66
+ let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex]._queueOrder >= 1000) ? ((queueArray[queueIndex]._queueOrder + 1) & (~1)) : 1000;
67
+ queue({ _queueOrder: order, _queueRun: func });
70
68
  }
71
69
  /**
72
70
  * Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
@@ -86,8 +84,8 @@ export function scheduleDomReader(func) {
86
84
  * @param func The function to be executed as a DOM write operation.
87
85
  */
88
86
  export function scheduleDomWriter(func) {
89
- let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? (queueArray[queueIndex].queueOrder | 1) : 1001;
90
- queue({ queueOrder: order, queueRun: func });
87
+ let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex]._queueOrder >= 1000) ? (queueArray[queueIndex]._queueOrder | 1) : 1001;
88
+ queue({ _queueOrder: order, _queueRun: func });
91
89
  }
92
90
  /**
93
91
  * Given an integer number, a string or an array of these, this function returns a string that can be used
@@ -128,6 +126,7 @@ function numToString(num, neg) {
128
126
  }
129
127
  /*
130
128
  * Scope
129
+ * @internal
131
130
  *
132
131
  * A `Scope` is created with a `render` function that is run initially,
133
132
  * and again when any of the `Store`s that this function reads are changed. Any
@@ -140,54 +139,54 @@ class Scope {
140
139
  constructor(parentElement, precedingSibling, queueOrder) {
141
140
  // The list of clean functions to be called when this scope is cleaned. These can
142
141
  // be for child scopes, subscriptions as well as `clean(..)` hooks.
143
- this.cleaners = [];
142
+ this._cleaners = [];
144
143
  // Set to true after the scope has been cleaned, causing any spurious reruns to
145
144
  // be ignored.
146
- this.isDead = false;
147
- this.parentElement = parentElement;
148
- this.precedingSibling = precedingSibling;
149
- this.queueOrder = queueOrder;
145
+ this._isDead = false;
146
+ this._parentElement = parentElement;
147
+ this._precedingSibling = precedingSibling;
148
+ this._queueOrder = queueOrder;
150
149
  }
151
150
  // Get a reference to the last Node preceding this Scope, or undefined if there is none
152
- findPrecedingNode(stopAt = undefined) {
151
+ _findPrecedingNode(stopAt = undefined) {
153
152
  let cur = this;
154
153
  let pre;
155
- while ((pre = cur.precedingSibling) && pre !== stopAt) {
154
+ while ((pre = cur._precedingSibling) && pre !== stopAt) {
156
155
  if (pre instanceof Node)
157
156
  return pre;
158
- let node = pre.findLastNode();
157
+ let node = pre._findLastNode();
159
158
  if (node)
160
159
  return node;
161
160
  cur = pre;
162
161
  }
163
162
  }
164
163
  // Get a reference to the last Node within this scope and parentElement
165
- findLastNode() {
166
- if (this.lastChild) {
167
- if (this.lastChild instanceof Node)
168
- return this.lastChild;
164
+ _findLastNode() {
165
+ if (this._lastChild) {
166
+ if (this._lastChild instanceof Node)
167
+ return this._lastChild;
169
168
  else
170
- return this.lastChild.findLastNode() || this.lastChild.findPrecedingNode(this.precedingSibling);
169
+ return this._lastChild._findLastNode() || this._lastChild._findPrecedingNode(this._precedingSibling);
171
170
  }
172
171
  }
173
- addNode(node) {
174
- if (!this.parentElement)
172
+ _addNode(node) {
173
+ if (!this._parentElement)
175
174
  throw new ScopeError(true);
176
- let prevNode = this.findLastNode() || this.findPrecedingNode();
177
- this.parentElement.insertBefore(node, prevNode ? prevNode.nextSibling : this.parentElement.firstChild);
178
- this.lastChild = node;
175
+ let prevNode = this._findLastNode() || this._findPrecedingNode();
176
+ this._parentElement.insertBefore(node, prevNode ? prevNode.nextSibling : this._parentElement.firstChild);
177
+ this._lastChild = node;
179
178
  }
180
- remove() {
181
- if (this.parentElement) {
182
- let lastNode = this.findLastNode();
179
+ _remove() {
180
+ if (this._parentElement) {
181
+ let lastNode = this._findLastNode();
183
182
  if (lastNode) {
184
183
  // at least one DOM node to be removed
185
- let nextNode = this.findPrecedingNode();
186
- nextNode = (nextNode ? nextNode.nextSibling : this.parentElement.firstChild);
187
- this.lastChild = undefined;
184
+ let nextNode = this._findPrecedingNode();
185
+ nextNode = (nextNode ? nextNode.nextSibling : this._parentElement.firstChild);
186
+ this._lastChild = undefined;
188
187
  // Keep removing DOM nodes starting at our first node, until we encounter the last node
189
188
  while (true) {
190
- /* istanbul ignore next */
189
+ /* c8 ignore next */
191
190
  if (!nextNode)
192
191
  return internalError(1);
193
192
  const node = nextNode;
@@ -207,7 +206,7 @@ class Scope {
207
206
  // Ignore the deleting element
208
207
  }
209
208
  else {
210
- this.parentElement.removeChild(node);
209
+ this._parentElement.removeChild(node);
211
210
  }
212
211
  if (node === lastNode)
213
212
  break;
@@ -218,37 +217,36 @@ class Scope {
218
217
  this._clean();
219
218
  }
220
219
  _clean() {
221
- this.isDead = true;
222
- for (let cleaner of this.cleaners) {
220
+ this._isDead = true;
221
+ for (let cleaner of this._cleaners) {
223
222
  cleaner._clean(this);
224
223
  }
225
- this.cleaners.length = 0;
224
+ this._cleaners.length = 0;
226
225
  }
227
- onChange(index, newData, oldData) {
226
+ _onChange(index, newData, oldData) {
228
227
  queue(this);
229
228
  }
230
229
  }
231
230
  class SimpleScope extends Scope {
232
231
  constructor(parentElement, precedingSibling, queueOrder, renderer) {
233
232
  super(parentElement, precedingSibling, queueOrder);
234
- this.renderer = renderer;
233
+ this._renderer = renderer;
235
234
  }
236
- queueRun() {
237
- /* istanbul ignore next */
238
- if (currentScope) {
235
+ _queueRun() {
236
+ /* c8 ignore next */
237
+ if (currentScope)
239
238
  internalError(2);
240
- }
241
- if (this.isDead)
239
+ if (this._isDead)
242
240
  return;
243
- this.remove();
244
- this.isDead = false;
245
- this.update();
241
+ this._remove();
242
+ this._isDead = false;
243
+ this._update();
246
244
  }
247
- update() {
245
+ _update() {
248
246
  let savedScope = currentScope;
249
247
  currentScope = this;
250
248
  try {
251
- this.renderer();
249
+ this._renderer();
252
250
  }
253
251
  catch (e) {
254
252
  // Throw the error async, so the rest of the rendering can continue
@@ -257,16 +255,47 @@ class SimpleScope extends Scope {
257
255
  currentScope = savedScope;
258
256
  }
259
257
  }
258
+ let immediateQueue = new Set();
259
+ class ImmediateScope extends SimpleScope {
260
+ _onChange(index, newData, oldData) {
261
+ immediateQueue.add(this);
262
+ }
263
+ }
264
+ let immediateQueuerRunning = false;
265
+ function runImmediateQueue() {
266
+ if (immediateQueuerRunning)
267
+ return;
268
+ for (let count = 0; immediateQueue.size; count++) {
269
+ if (count > 42) {
270
+ immediateQueue.clear();
271
+ throw new Error("Too many recursive updates from immediate-mode observes");
272
+ }
273
+ immediateQueuerRunning = true;
274
+ let copy = immediateQueue;
275
+ immediateQueue = new Set();
276
+ let savedScope = currentScope;
277
+ currentScope = undefined;
278
+ try {
279
+ for (const scope of copy) {
280
+ scope._queueRun();
281
+ }
282
+ }
283
+ finally {
284
+ currentScope = savedScope;
285
+ immediateQueuerRunning = false;
286
+ }
287
+ }
288
+ }
260
289
  class IsEmptyObserver {
261
290
  constructor(scope, collection, triggerCount) {
262
291
  this.scope = scope;
263
292
  this.collection = collection;
264
293
  this.triggerCount = triggerCount;
265
- this.count = collection.getCount();
266
- collection.addObserver(ANY_INDEX, this);
267
- scope.cleaners.push(this);
294
+ this.count = collection._getCount();
295
+ collection._addObserver(ANY_INDEX, this);
296
+ scope._cleaners.push(this);
268
297
  }
269
- onChange(index, newData, oldData) {
298
+ _onChange(index, newData, oldData) {
270
299
  if (newData === undefined) {
271
300
  // oldData is guaranteed not to be undefined
272
301
  if (this.triggerCount || !--this.count)
@@ -278,106 +307,105 @@ class IsEmptyObserver {
278
307
  }
279
308
  }
280
309
  _clean() {
281
- this.collection.removeObserver(ANY_INDEX, this);
310
+ this.collection._removeObserver(ANY_INDEX, this);
282
311
  }
283
312
  }
313
+ /** @internal */
284
314
  class OnEachScope extends Scope {
285
315
  constructor(parentElement, precedingSibling, queueOrder, collection, renderer, makeSortKey) {
286
316
  super(parentElement, precedingSibling, queueOrder);
287
317
  /** The ordered list of currently item scopes */
288
- this.byPosition = [];
318
+ this._byPosition = [];
289
319
  /** The item scopes in a Map by index */
290
- this.byIndex = new Map();
320
+ this._byIndex = new Map();
291
321
  /** Indexes that have been created/removed and need to be handled in the next `queueRun` */
292
- this.newIndexes = new Set();
293
- this.removedIndexes = new Set();
294
- this.collection = collection;
295
- this.renderer = renderer;
296
- this.makeSortKey = makeSortKey;
322
+ this._newIndexes = new Set();
323
+ this._removedIndexes = new Set();
324
+ this._collection = collection;
325
+ this._renderer = renderer;
326
+ this._makeSortKey = makeSortKey;
297
327
  }
298
328
  // toString(): string {
299
329
  // return `OnEachScope(collection=${this.collection})`
300
330
  // }
301
- onChange(index, newData, oldData) {
331
+ _onChange(index, newData, oldData) {
302
332
  if (oldData === undefined) {
303
- if (this.removedIndexes.has(index)) {
304
- this.removedIndexes.delete(index);
333
+ if (this._removedIndexes.has(index)) {
334
+ this._removedIndexes.delete(index);
305
335
  }
306
336
  else {
307
- this.newIndexes.add(index);
337
+ this._newIndexes.add(index);
308
338
  queue(this);
309
339
  }
310
340
  }
311
341
  else if (newData === undefined) {
312
- if (this.newIndexes.has(index)) {
313
- this.newIndexes.delete(index);
342
+ if (this._newIndexes.has(index)) {
343
+ this._newIndexes.delete(index);
314
344
  }
315
345
  else {
316
- this.removedIndexes.add(index);
346
+ this._removedIndexes.add(index);
317
347
  queue(this);
318
348
  }
319
349
  }
320
350
  }
321
- queueRun() {
322
- if (this.isDead)
351
+ _queueRun() {
352
+ if (this._isDead)
323
353
  return;
324
- let indexes = this.removedIndexes;
325
- this.removedIndexes = new Set();
354
+ let indexes = this._removedIndexes;
355
+ this._removedIndexes = new Set();
326
356
  indexes.forEach(index => {
327
- this.removeChild(index);
357
+ this._removeChild(index);
328
358
  });
329
- indexes = this.newIndexes;
330
- this.newIndexes = new Set();
359
+ indexes = this._newIndexes;
360
+ this._newIndexes = new Set();
331
361
  indexes.forEach(index => {
332
- this.addChild(index);
362
+ this._addChild(index);
333
363
  });
334
364
  }
335
365
  _clean() {
336
366
  super._clean();
337
- this.collection.observers.delete(this);
338
- for (const [index, scope] of this.byIndex) {
367
+ this._collection._observers.delete(this);
368
+ for (const [index, scope] of this._byIndex) {
339
369
  scope._clean();
340
370
  }
341
371
  // Help garbage collection:
342
- this.byPosition.length = 0;
343
- this.byIndex.clear();
372
+ this._byPosition.length = 0;
373
+ this._byIndex.clear();
344
374
  }
345
- renderInitial() {
346
- /* istanbul ignore next */
347
- if (!currentScope) {
375
+ _renderInitial() {
376
+ /* c8 ignore next */
377
+ if (!currentScope)
348
378
  return internalError(3);
349
- }
350
379
  let parentScope = currentScope;
351
- this.collection.iterateIndexes(this);
380
+ this._collection._iterateIndexes(this);
352
381
  currentScope = parentScope;
353
382
  }
354
- addChild(itemIndex) {
355
- let scope = new OnEachItemScope(this.parentElement, undefined, this.queueOrder + 1, this, itemIndex);
356
- this.byIndex.set(itemIndex, scope);
357
- scope.update();
383
+ _addChild(itemIndex) {
384
+ let scope = new OnEachItemScope(this._parentElement, undefined, this._queueOrder + 1, this, itemIndex);
385
+ this._byIndex.set(itemIndex, scope);
386
+ scope._update();
358
387
  // We're not adding a cleaner here, as we'll be calling them from our _clean function
359
388
  }
360
- removeChild(itemIndex) {
361
- let scope = this.byIndex.get(itemIndex);
362
- /* istanbul ignore next */
363
- if (!scope) {
389
+ _removeChild(itemIndex) {
390
+ let scope = this._byIndex.get(itemIndex);
391
+ /* c8 ignore next */
392
+ if (!scope)
364
393
  return internalError(6);
365
- }
366
- scope.remove();
367
- this.byIndex.delete(itemIndex);
368
- this.removeFromPosition(scope);
394
+ scope._remove();
395
+ this._byIndex.delete(itemIndex);
396
+ this._removeFromPosition(scope);
369
397
  }
370
- findPosition(sortStr) {
398
+ _findPosition(sortStr) {
371
399
  // In case of duplicate `sortStr`s, this will return the first match.
372
- let items = this.byPosition;
400
+ let items = this._byPosition;
373
401
  let min = 0, max = items.length;
374
402
  // Fast-path for elements that are already ordered (as is the case when working with arrays ordered by index)
375
- if (!max || sortStr > items[max - 1].sortStr)
403
+ if (!max || sortStr > items[max - 1]._sortStr)
376
404
  return max;
377
405
  // Binary search for the insert position
378
406
  while (min < max) {
379
407
  let mid = (min + max) >> 1;
380
- if (items[mid].sortStr < sortStr) {
408
+ if (items[mid]._sortStr < sortStr) {
381
409
  min = mid + 1;
382
410
  }
383
411
  else {
@@ -386,101 +414,100 @@ class OnEachScope extends Scope {
386
414
  }
387
415
  return min;
388
416
  }
389
- insertAtPosition(child) {
390
- let pos = this.findPosition(child.sortStr);
391
- this.byPosition.splice(pos, 0, child);
417
+ _insertAtPosition(child) {
418
+ let pos = this._findPosition(child._sortStr);
419
+ this._byPosition.splice(pos, 0, child);
392
420
  // Based on the position in the list, set the precedingSibling for the new Scope
393
421
  // and for the next sibling.
394
- let nextSibling = this.byPosition[pos + 1];
422
+ let nextSibling = this._byPosition[pos + 1];
395
423
  if (nextSibling) {
396
- child.precedingSibling = nextSibling.precedingSibling;
397
- nextSibling.precedingSibling = child;
424
+ child._precedingSibling = nextSibling._precedingSibling;
425
+ nextSibling._precedingSibling = child;
398
426
  }
399
427
  else {
400
- child.precedingSibling = this.lastChild || this.precedingSibling;
401
- this.lastChild = child;
428
+ child._precedingSibling = this._lastChild || this._precedingSibling;
429
+ this._lastChild = child;
402
430
  }
403
431
  }
404
- removeFromPosition(child) {
405
- if (child.sortStr === '')
432
+ _removeFromPosition(child) {
433
+ if (child._sortStr === '')
406
434
  return;
407
- let pos = this.findPosition(child.sortStr);
435
+ let pos = this._findPosition(child._sortStr);
408
436
  while (true) {
409
- if (this.byPosition[pos] === child) {
437
+ if (this._byPosition[pos] === child) {
410
438
  // Yep, this is the right scope
411
- this.byPosition.splice(pos, 1);
412
- if (pos < this.byPosition.length) {
413
- let nextSibling = this.byPosition[pos];
414
- /* istanbul ignore next */
439
+ this._byPosition.splice(pos, 1);
440
+ if (pos < this._byPosition.length) {
441
+ let nextSibling = this._byPosition[pos];
442
+ /* c8 ignore next */
415
443
  if (!nextSibling)
416
444
  return internalError(8);
417
- /* istanbul ignore next */
418
- if (nextSibling.precedingSibling !== child)
445
+ /* c8 ignore next */
446
+ if (nextSibling._precedingSibling !== child)
419
447
  return internalError(13);
420
- nextSibling.precedingSibling = child.precedingSibling;
448
+ nextSibling._precedingSibling = child._precedingSibling;
421
449
  }
422
450
  else {
423
- /* istanbul ignore next */
424
- if (child !== this.lastChild)
451
+ /* c8 ignore next */
452
+ if (child !== this._lastChild)
425
453
  return internalError(12);
426
- this.lastChild = child.precedingSibling === this.precedingSibling ? undefined : child.precedingSibling;
454
+ this._lastChild = child._precedingSibling === this._precedingSibling ? undefined : child._precedingSibling;
427
455
  }
428
456
  return;
429
457
  }
430
458
  // There may be another Scope with the same sortStr
431
- /* istanbul ignore next */
432
- if (++pos >= this.byPosition.length || this.byPosition[pos].sortStr !== child.sortStr) {
459
+ /* c8 ignore next */
460
+ if (++pos >= this._byPosition.length || this._byPosition[pos]._sortStr !== child._sortStr)
433
461
  return internalError(5);
434
- }
435
462
  }
436
463
  }
437
464
  }
465
+ /** @internal */
438
466
  class OnEachItemScope extends Scope {
439
467
  constructor(parentElement, precedingSibling, queueOrder, parent, itemIndex) {
440
468
  super(parentElement, precedingSibling, queueOrder);
441
- this.sortStr = "";
442
- this.parent = parent;
443
- this.itemIndex = itemIndex;
469
+ this._sortStr = "";
470
+ this._parent = parent;
471
+ this._itemIndex = itemIndex;
444
472
  }
445
473
  // toString(): string {
446
474
  // return `OnEachItemScope(itemIndex=${this.itemIndex} parentElement=${this.parentElement} parent=${this.parent} precedingSibling=${this.precedingSibling} lastChild=${this.lastChild})`
447
475
  // }
448
- queueRun() {
449
- /* istanbul ignore next */
450
- if (currentScope) {
476
+ _queueRun() {
477
+ /* c8 ignore next */
478
+ if (currentScope)
451
479
  internalError(4);
452
- }
453
- if (this.isDead)
480
+ if (this._isDead)
454
481
  return;
455
- this.remove();
456
- this.isDead = false;
457
- this.update();
482
+ this._remove();
483
+ this._isDead = false;
484
+ this._update();
458
485
  }
459
- update() {
486
+ _update() {
460
487
  // Have the makeSortKey function return an ordering int/string/array.
461
488
  // Since makeSortKey may get() the Store, we'll need to set currentScope first.
462
489
  let savedScope = currentScope;
463
490
  currentScope = this;
464
- let itemStore = new Store(this.parent.collection, this.itemIndex);
491
+ let itemStore = new Store(this._parent._collection, this._itemIndex);
465
492
  let sortKey;
466
493
  try {
467
- sortKey = this.parent.makeSortKey(itemStore);
494
+ sortKey = this._parent._makeSortKey(itemStore);
468
495
  }
469
496
  catch (e) {
470
497
  handleError(e);
471
498
  }
472
- let oldSortStr = this.sortStr;
499
+ let oldSortStr = this._sortStr;
473
500
  let newSortStr = sortKey == null ? '' : sortKeyToString(sortKey);
474
501
  if (oldSortStr !== '' && oldSortStr !== newSortStr) {
475
- this.parent.removeFromPosition(this);
502
+ this._parent._removeFromPosition(this);
476
503
  }
477
- this.sortStr = newSortStr;
504
+ this._sortStr = newSortStr;
478
505
  if (newSortStr !== '') {
479
506
  if (newSortStr !== oldSortStr) {
480
- this.parent.insertAtPosition(this);
507
+ this._parent._insertAtPosition(this);
481
508
  }
482
509
  try {
483
- this.parent.renderer(itemStore);
510
+ this._parent._renderer(itemStore);
484
511
  }
485
512
  catch (e) {
486
513
  handleError(e);
@@ -498,49 +525,45 @@ let currentScope;
498
525
  * A special Node observer index to subscribe to any value in the map changing.
499
526
  */
500
527
  const ANY_INDEX = {};
501
- class ObsCollection {
528
+ /** @internal */
529
+ export class ObsCollection {
502
530
  constructor() {
503
- this.observers = new Map();
531
+ this._observers = new Map();
504
532
  }
505
533
  // toString(): string {
506
534
  // return JSON.stringify(peek(() => this.getRecursive(3)))
507
535
  // }
508
- addObserver(index, observer) {
536
+ _addObserver(index, observer) {
509
537
  observer = observer;
510
- let obsSet = this.observers.get(index);
538
+ let obsSet = this._observers.get(index);
511
539
  if (obsSet) {
512
540
  if (obsSet.has(observer))
513
541
  return false;
514
542
  obsSet.add(observer);
515
543
  }
516
544
  else {
517
- this.observers.set(index, new Set([observer]));
545
+ this._observers.set(index, new Set([observer]));
518
546
  }
519
547
  return true;
520
548
  }
521
- removeObserver(index, observer) {
522
- let obsSet = this.observers.get(index);
549
+ _removeObserver(index, observer) {
550
+ let obsSet = this._observers.get(index);
523
551
  obsSet.delete(observer);
524
552
  }
525
553
  emitChange(index, newData, oldData) {
526
- if (recordingPatch) {
527
- addToPatch(recordingPatch, this, index, newData, oldData);
528
- }
529
- else {
530
- let obsSet = this.observers.get(index);
531
- if (obsSet)
532
- obsSet.forEach(observer => observer.onChange(index, newData, oldData));
533
- obsSet = this.observers.get(ANY_INDEX);
534
- if (obsSet)
535
- obsSet.forEach(observer => observer.onChange(index, newData, oldData));
536
- }
554
+ let obsSet = this._observers.get(index);
555
+ if (obsSet)
556
+ obsSet.forEach(observer => observer._onChange(index, newData, oldData));
557
+ obsSet = this._observers.get(ANY_INDEX);
558
+ if (obsSet)
559
+ obsSet.forEach(observer => observer._onChange(index, newData, oldData));
537
560
  }
538
561
  _clean(observer) {
539
- this.removeObserver(ANY_INDEX, observer);
562
+ this._removeObserver(ANY_INDEX, observer);
540
563
  }
541
- setIndex(index, newValue, deleteMissing) {
564
+ _setIndex(index, newValue, deleteMissing) {
542
565
  const curData = this.rawGet(index);
543
- if (!(curData instanceof ObsCollection) || newValue instanceof Store || !curData.merge(newValue, deleteMissing)) {
566
+ if (!(curData instanceof ObsCollection) || newValue instanceof Store || !curData._merge(newValue, deleteMissing)) {
544
567
  let newData = valueToData(newValue);
545
568
  if (newData !== curData) {
546
569
  this.rawSet(index, newData);
@@ -549,67 +572,71 @@ class ObsCollection {
549
572
  }
550
573
  }
551
574
  }
575
+ /** @internal */
552
576
  class ObsArray extends ObsCollection {
553
577
  constructor() {
554
578
  super(...arguments);
555
- this.data = [];
579
+ this._data = [];
556
580
  }
557
- getType() {
581
+ _getType() {
558
582
  return "array";
559
583
  }
560
- getRecursive(depth) {
584
+ _getRecursive(depth) {
561
585
  if (currentScope) {
562
- if (this.addObserver(ANY_INDEX, currentScope)) {
563
- currentScope.cleaners.push(this);
586
+ if (this._addObserver(ANY_INDEX, currentScope)) {
587
+ currentScope._cleaners.push(this);
564
588
  }
565
589
  }
566
590
  let result = [];
567
- for (let i = 0; i < this.data.length; i++) {
568
- let v = this.data[i];
569
- result.push(v instanceof ObsCollection ? (depth ? v.getRecursive(depth - 1) : new Store(this, i)) : v);
591
+ for (let i = 0; i < this._data.length; i++) {
592
+ let v = this._data[i];
593
+ result.push(v instanceof ObsCollection ? (depth ? v._getRecursive(depth - 1) : new Store(this, i)) : v);
570
594
  }
571
595
  return result;
572
596
  }
573
597
  rawGet(index) {
574
- return this.data[index];
598
+ return this._data[index];
575
599
  }
576
600
  rawSet(index, newData) {
577
601
  if (index !== (0 | index) || index < 0 || index > 999999) {
578
602
  throw new Error(`Invalid array index ${JSON.stringify(index)}`);
579
603
  }
580
- this.data[index] = newData;
604
+ this._data[index] = newData;
581
605
  // Remove trailing `undefined`s
582
- while (this.data.length > 0 && this.data[this.data.length - 1] === undefined) {
583
- this.data.pop();
606
+ while (this._data.length > 0 && this._data[this._data.length - 1] === undefined) {
607
+ this._data.pop();
584
608
  }
585
609
  }
586
- merge(newValue, deleteMissing) {
610
+ _merge(newValue, deleteMissing) {
587
611
  if (!(newValue instanceof Array)) {
588
612
  return false;
589
613
  }
590
614
  // newValue is an array
591
615
  for (let i = 0; i < newValue.length; i++) {
592
- this.setIndex(i, newValue[i], deleteMissing);
593
- }
594
- if (deleteMissing && this.data.length > newValue.length) {
595
- for (let i = newValue.length; i < this.data.length; i++) {
596
- let old = this.data[i];
616
+ this._setIndex(i, newValue[i], deleteMissing);
617
+ }
618
+ // Overwriting just the first elements of an array and leaving the rest of
619
+ // the old data in place is just weird and unexpected, so we'll always use
620
+ // 'replace' behavior for arrays.
621
+ if ( /*deleteMissing &&*/this._data.length > newValue.length) {
622
+ for (let i = newValue.length; i < this._data.length; i++) {
623
+ let old = this._data[i];
597
624
  if (old !== undefined) {
598
625
  this.emitChange(i, undefined, old);
599
626
  }
600
627
  }
601
- this.data.length = newValue.length;
628
+ this._data.length = newValue.length;
602
629
  }
603
630
  return true;
604
631
  }
605
- iterateIndexes(scope) {
606
- for (let i = 0; i < this.data.length; i++) {
607
- if (this.data[i] !== undefined) {
608
- scope.addChild(i);
632
+ _iterateIndexes(scope) {
633
+ for (let i = 0; i < this._data.length; i++) {
634
+ if (this._data[i] !== undefined) {
635
+ scope._addChild(i);
609
636
  }
610
637
  }
611
638
  }
612
- normalizeIndex(index) {
639
+ _normalizeIndex(index) {
613
640
  if (typeof index === 'number')
614
641
  return index;
615
642
  if (typeof index === 'string') {
@@ -621,27 +648,28 @@ class ObsArray extends ObsCollection {
621
648
  }
622
649
  throw new Error(`Invalid array index ${JSON.stringify(index)}`);
623
650
  }
624
- getCount() {
625
- return this.data.length;
651
+ _getCount() {
652
+ return this._data.length;
626
653
  }
627
654
  }
655
+ /** @internal */
628
656
  class ObsMap extends ObsCollection {
629
657
  constructor() {
630
658
  super(...arguments);
631
659
  this.data = new Map();
632
660
  }
633
- getType() {
661
+ _getType() {
634
662
  return "map";
635
663
  }
636
- getRecursive(depth) {
664
+ _getRecursive(depth) {
637
665
  if (currentScope) {
638
- if (this.addObserver(ANY_INDEX, currentScope)) {
639
- currentScope.cleaners.push(this);
666
+ if (this._addObserver(ANY_INDEX, currentScope)) {
667
+ currentScope._cleaners.push(this);
640
668
  }
641
669
  }
642
670
  let result = new Map();
643
671
  this.data.forEach((v, k) => {
644
- result.set(k, (v instanceof ObsCollection) ? (depth ? v.getRecursive(depth - 1) : new Store(this, k)) : v);
672
+ result.set(k, (v instanceof ObsCollection) ? (depth ? v._getRecursive(depth - 1) : new Store(this, k)) : v);
645
673
  });
646
674
  return result;
647
675
  }
@@ -656,67 +684,68 @@ class ObsMap extends ObsCollection {
656
684
  this.data.set(index, newData);
657
685
  }
658
686
  }
659
- merge(newValue, deleteMissing) {
687
+ _merge(newValue, deleteMissing) {
660
688
  if (!(newValue instanceof Map)) {
661
689
  return false;
662
690
  }
663
691
  // Walk the pairs of the new value map
664
692
  newValue.forEach((v, k) => {
665
- this.setIndex(k, v, deleteMissing);
693
+ this._setIndex(k, v, deleteMissing);
666
694
  });
667
695
  if (deleteMissing) {
668
696
  this.data.forEach((v, k) => {
669
697
  if (!newValue.has(k))
670
- this.setIndex(k, undefined, false);
698
+ this._setIndex(k, undefined, false);
671
699
  });
672
700
  }
673
701
  return true;
674
702
  }
675
- iterateIndexes(scope) {
703
+ _iterateIndexes(scope) {
676
704
  this.data.forEach((_, itemIndex) => {
677
- scope.addChild(itemIndex);
705
+ scope._addChild(itemIndex);
678
706
  });
679
707
  }
680
- normalizeIndex(index) {
708
+ _normalizeIndex(index) {
681
709
  return index;
682
710
  }
683
- getCount() {
711
+ _getCount() {
684
712
  return this.data.size;
685
713
  }
686
714
  }
715
+ /** @internal */
687
716
  class ObsObject extends ObsMap {
688
- getType() {
717
+ _getType() {
689
718
  return "object";
690
719
  }
691
- getRecursive(depth) {
720
+ _getRecursive(depth) {
692
721
  if (currentScope) {
693
- if (this.addObserver(ANY_INDEX, currentScope)) {
694
- currentScope.cleaners.push(this);
722
+ if (this._addObserver(ANY_INDEX, currentScope)) {
723
+ currentScope._cleaners.push(this);
695
724
  }
696
725
  }
697
726
  let result = {};
698
727
  this.data.forEach((v, k) => {
699
- result[k] = (v instanceof ObsCollection) ? (depth ? v.getRecursive(depth - 1) : new Store(this, k)) : v;
728
+ result[k] = (v instanceof ObsCollection) ? (depth ? v._getRecursive(depth - 1) : new Store(this, k)) : v;
700
729
  });
701
730
  return result;
702
731
  }
703
- merge(newValue, deleteMissing) {
732
+ _merge(newValue, deleteMissing) {
704
733
  if (!newValue || newValue.constructor !== Object) {
705
734
  return false;
706
735
  }
707
736
  // Walk the pairs of the new value object
708
737
  for (let k in newValue) {
709
- this.setIndex(k, newValue[k], deleteMissing);
738
+ this._setIndex(k, newValue[k], deleteMissing);
710
739
  }
711
740
  if (deleteMissing) {
712
741
  this.data.forEach((v, k) => {
713
742
  if (!newValue.hasOwnProperty(k))
714
- this.setIndex(k, undefined, false);
743
+ this._setIndex(k, undefined, false);
715
744
  });
716
745
  }
717
746
  return true;
718
747
  }
719
- normalizeIndex(index) {
748
+ _normalizeIndex(index) {
720
749
  let type = typeof index;
721
750
  if (type === 'string')
722
751
  return index;
@@ -724,7 +753,7 @@ class ObsObject extends ObsMap {
724
753
  return '' + index;
725
754
  throw new Error(`Invalid object index ${JSON.stringify(index)}`);
726
755
  }
727
- getCount() {
756
+ _getCount() {
728
757
  let cnt = 0;
729
758
  for (let key of this.data)
730
759
  cnt++;
@@ -742,18 +771,18 @@ class ObsObject extends ObsMap {
742
771
  export class Store {
743
772
  constructor(value = undefined, index = undefined) {
744
773
  if (index === undefined) {
745
- this.collection = new ObsArray();
746
- this.idx = 0;
774
+ this._collection = new ObsArray();
775
+ this._idx = 0;
747
776
  if (value !== undefined) {
748
- this.collection.rawSet(0, valueToData(value));
777
+ this._collection.rawSet(0, valueToData(value));
749
778
  }
750
779
  }
751
780
  else {
752
781
  if (!(value instanceof ObsCollection)) {
753
782
  throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");
754
783
  }
755
- this.collection = value;
756
- this.idx = index;
784
+ this._collection = value;
785
+ this._idx = index;
757
786
  }
758
787
  }
759
788
  /**
@@ -771,11 +800,11 @@ export class Store {
771
800
  * ```
772
801
  */
773
802
  index() {
774
- return this.idx;
803
+ return this._idx;
775
804
  }
776
805
  /** @internal */
777
806
  _clean(scope) {
778
- this.collection.removeObserver(this.idx, scope);
807
+ this._collection._removeObserver(this._idx, scope);
779
808
  }
780
809
  /**
781
810
  * @returns Resolves `path` and then retrieves the value that is there, subscribing
@@ -871,12 +900,12 @@ export class Store {
871
900
  let store = opts.path && opts.path.length ? this.ref(...opts.path) : this;
872
901
  let value = store._observe();
873
902
  if (opts.type && (value !== undefined || opts.defaultValue === undefined)) {
874
- let type = (value instanceof ObsCollection) ? value.getType() : (value === null ? "null" : typeof value);
903
+ let type = (value instanceof ObsCollection) ? value._getType() : (value === null ? "null" : typeof value);
875
904
  if (type !== opts.type)
876
905
  throw new TypeError(`Expecting ${opts.type} but got ${type}`);
877
906
  }
878
907
  if (value instanceof ObsCollection) {
879
- return value.getRecursive(opts.depth == null ? -1 : opts.depth - 1);
908
+ return value._getRecursive(opts.depth == null ? -1 : opts.depth - 1);
880
909
  }
881
910
  return value === undefined ? opts.defaultValue : value;
882
911
  }
@@ -896,7 +925,7 @@ export class Store {
896
925
  return !observer.count;
897
926
  }
898
927
  else {
899
- return !value.getCount();
928
+ return !value._getCount();
900
929
  }
901
930
  }
902
931
  else if (value === undefined) {
@@ -922,7 +951,7 @@ export class Store {
922
951
  return observer.count;
923
952
  }
924
953
  else {
925
- return value.getCount();
954
+ return value._getCount();
926
955
  }
927
956
  }
928
957
  else if (value === undefined) {
@@ -944,7 +973,7 @@ export class Store {
944
973
  getType(...path) {
945
974
  let store = this.ref(...path);
946
975
  let value = store._observe();
947
- return (value instanceof ObsCollection) ? value.getType() : (value === null ? "null" : typeof value);
976
+ return (value instanceof ObsCollection) ? value._getType() : (value === null ? "null" : typeof value);
948
977
  }
949
978
  /**
950
979
  * Sets the value to the last given argument. Any earlier argument are a Store-path that is first
@@ -978,7 +1007,8 @@ export class Store {
978
1007
  set(...pathAndValue) {
979
1008
  let newValue = pathAndValue.pop();
980
1009
  let store = this.makeRef(...pathAndValue);
981
- store.collection.setIndex(store.idx, newValue, true);
1010
+ store._collection._setIndex(store._idx, newValue, true);
1011
+ runImmediateQueue();
982
1012
  }
983
1013
  /**
984
1014
  * Sets the `Store` to the given `mergeValue`, but without deleting any pre-existing
@@ -995,7 +1025,8 @@ export class Store {
995
1025
  merge(...pathAndValue) {
996
1026
  let mergeValue = pathAndValue.pop();
997
1027
  let store = this.makeRef(...pathAndValue);
998
- store.collection.setIndex(store.idx, mergeValue, false);
1028
+ store._collection._setIndex(store._idx, mergeValue, false);
1029
+ runImmediateQueue();
999
1030
  }
1000
1031
  /**
1001
1032
  * Sets the value for the store to `undefined`, which causes it to be omitted from the map (or array, if it's at the end)
@@ -1015,7 +1046,8 @@ export class Store {
1015
1046
  */
1016
1047
  delete(...path) {
1017
1048
  let store = this.makeRef(...path);
1018
- store.collection.setIndex(store.idx, undefined, true);
1049
+ store._collection._setIndex(store._idx, undefined, true);
1050
+ runImmediateQueue();
1019
1051
  }
1020
1052
  /**
1021
1053
  * Pushes a value to the end of the Array that is at the specified path in the store.
@@ -1037,18 +1069,19 @@ export class Store {
1037
1069
  push(...pathAndValue) {
1038
1070
  let newValue = pathAndValue.pop();
1039
1071
  let store = this.makeRef(...pathAndValue);
1040
- let obsArray = store.collection.rawGet(store.idx);
1072
+ let obsArray = store._collection.rawGet(store._idx);
1041
1073
  if (obsArray === undefined) {
1042
1074
  obsArray = new ObsArray();
1043
- store.collection.setIndex(store.idx, obsArray, true);
1075
+ store._collection._setIndex(store._idx, obsArray, true);
1044
1076
  }
1045
1077
  else if (!(obsArray instanceof ObsArray)) {
1046
1078
  throw new Error(`push() is only allowed for an array or undefined (which would become an array)`);
1047
1079
  }
1048
1080
  let newData = valueToData(newValue);
1049
- let pos = obsArray.data.length;
1050
- obsArray.data.push(newData);
1081
+ let pos = obsArray._data.length;
1082
+ obsArray._data.push(newData);
1051
1083
  obsArray.emitChange(pos, newData, undefined);
1084
+ runImmediateQueue();
1052
1085
  return pos;
1053
1086
  }
1054
1087
  /**
@@ -1072,7 +1105,7 @@ export class Store {
1072
1105
  for (let i = 0; i < path.length; i++) {
1073
1106
  let value = store._observe();
1074
1107
  if (value instanceof ObsCollection) {
1075
- store = new Store(value, value.normalizeIndex(path[i]));
1108
+ store = new Store(value, value._normalizeIndex(path[i]));
1076
1109
  }
1077
1110
  else {
1078
1111
  if (value !== undefined)
@@ -1105,26 +1138,27 @@ export class Store {
1105
1138
  makeRef(...path) {
1106
1139
  let store = this;
1107
1140
  for (let i = 0; i < path.length; i++) {
1108
- let value = store.collection.rawGet(store.idx);
1141
+ let value = store._collection.rawGet(store._idx);
1109
1142
  if (!(value instanceof ObsCollection)) {
1110
1143
  if (value !== undefined)
1111
1144
  throw new Error(`Value ${JSON.stringify(value)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(path)})`);
1112
1145
  value = new ObsObject();
1113
- store.collection.rawSet(store.idx, value);
1114
- store.collection.emitChange(store.idx, value, undefined);
1146
+ store._collection.rawSet(store._idx, value);
1147
+ store._collection.emitChange(store._idx, value, undefined);
1115
1148
  }
1116
- store = new Store(value, value.normalizeIndex(path[i]));
1149
+ store = new Store(value, value._normalizeIndex(path[i]));
1117
1150
  }
1151
+ runImmediateQueue();
1118
1152
  return store;
1119
1153
  }
1120
1154
  /** @internal */
1121
1155
  _observe() {
1122
1156
  if (currentScope) {
1123
- if (this.collection.addObserver(this.idx, currentScope)) {
1124
- currentScope.cleaners.push(this);
1157
+ if (this._collection._addObserver(this._idx, currentScope)) {
1158
+ currentScope._cleaners.push(this);
1125
1159
  }
1126
1160
  }
1127
- return this.collection.rawGet(this.idx);
1161
+ return this._collection.rawGet(this._idx);
1128
1162
  }
1129
1163
  /**
1130
1164
  * Iterate the specified collection (Array, Map or object), running the given code block for each item.
@@ -1151,11 +1185,11 @@ export class Store {
1151
1185
  let val = store._observe();
1152
1186
  if (val instanceof ObsCollection) {
1153
1187
  // Subscribe to changes using the specialized OnEachScope
1154
- let onEachScope = new OnEachScope(currentScope.parentElement, currentScope.lastChild || currentScope.precedingSibling, currentScope.queueOrder + 1, val, renderer, makeSortKey);
1155
- val.addObserver(ANY_INDEX, onEachScope);
1156
- currentScope.cleaners.push(onEachScope);
1157
- currentScope.lastChild = onEachScope;
1158
- onEachScope.renderInitial();
1188
+ let onEachScope = new OnEachScope(currentScope._parentElement, currentScope._lastChild || currentScope._precedingSibling, currentScope._queueOrder + 1, val, renderer, makeSortKey);
1189
+ val._addObserver(ANY_INDEX, onEachScope);
1190
+ currentScope._cleaners.push(onEachScope);
1191
+ currentScope._lastChild = onEachScope;
1192
+ onEachScope._renderInitial();
1159
1193
  }
1160
1194
  else if (val !== undefined) {
1161
1195
  throw new Error(`onEach() attempted on a value that is neither a collection nor undefined`);
@@ -1240,7 +1274,7 @@ export class Store {
1240
1274
  * does not exist.
1241
1275
  */
1242
1276
  isDetached() { return false; }
1243
- /*
1277
+ /**
1244
1278
  * Dump a live view of the `Store` tree as HTML text, `ul` and `li` nodes at
1245
1279
  * the current mount position. Meant for debugging purposes.
1246
1280
  */
@@ -1306,22 +1340,22 @@ export function node(tag = "", ...rest) {
1306
1340
  el.className = classes.replaceAll('.', ' ');
1307
1341
  }
1308
1342
  }
1309
- currentScope.addNode(el);
1343
+ currentScope._addNode(el);
1310
1344
  for (let item of rest) {
1311
1345
  let type = typeof item;
1312
1346
  if (type === 'function') {
1313
- let scope = new SimpleScope(el, undefined, currentScope.queueOrder + 1, item);
1347
+ let scope = new SimpleScope(el, undefined, currentScope._queueOrder + 1, item);
1314
1348
  if (onCreateEnabled) {
1315
1349
  onCreateEnabled = false;
1316
- scope.update();
1350
+ scope._update();
1317
1351
  onCreateEnabled = true;
1318
1352
  }
1319
1353
  else {
1320
- scope.update();
1354
+ scope._update();
1321
1355
  }
1322
1356
  // Add it to our list of cleaners. Even if `scope` currently has
1323
1357
  // no cleaners, it may get them in a future refresh.
1324
- currentScope.cleaners.push(scope);
1358
+ currentScope._cleaners.push(scope);
1325
1359
  }
1326
1360
  else if (type === 'string' || type === 'number') {
1327
1361
  el.textContent = item;
@@ -1344,12 +1378,12 @@ export function node(tag = "", ...rest) {
1344
1378
  * @param html - The HTML string. For example `"<section><h2>Test</h2><p>Info..</p></section>"`.
1345
1379
  */
1346
1380
  export function html(html) {
1347
- if (!currentScope || !currentScope.parentElement)
1381
+ if (!currentScope || !currentScope._parentElement)
1348
1382
  throw new ScopeError(true);
1349
- let tmpParent = document.createElement(currentScope.parentElement.tagName);
1383
+ let tmpParent = document.createElement(currentScope._parentElement.tagName);
1350
1384
  tmpParent.innerHTML = '' + html;
1351
1385
  while (tmpParent.firstChild) {
1352
- currentScope.addNode(tmpParent.firstChild);
1386
+ currentScope._addNode(tmpParent.firstChild);
1353
1387
  }
1354
1388
  }
1355
1389
  function bindInput(el, store) {
@@ -1397,18 +1431,18 @@ export function text(text) {
1397
1431
  throw new ScopeError(true);
1398
1432
  if (text == null)
1399
1433
  return;
1400
- currentScope.addNode(document.createTextNode(text));
1434
+ currentScope._addNode(document.createTextNode(text));
1401
1435
  }
1402
1436
  export function prop(name, value = undefined) {
1403
- if (!currentScope || !currentScope.parentElement)
1437
+ if (!currentScope || !currentScope._parentElement)
1404
1438
  throw new ScopeError(true);
1405
1439
  if (typeof name === 'object') {
1406
1440
  for (let k in name) {
1407
- applyProp(currentScope.parentElement, k, name[k]);
1441
+ applyProp(currentScope._parentElement, k, name[k]);
1408
1442
  }
1409
1443
  }
1410
1444
  else {
1411
- applyProp(currentScope.parentElement, name, value);
1445
+ applyProp(currentScope._parentElement, name, value);
1412
1446
  }
1413
1447
  }
1414
1448
  /**
@@ -1419,9 +1453,9 @@ export function prop(name, value = undefined) {
1419
1453
  * terribly surprising. Be careful within the parent element of onEach() though.
1420
1454
  */
1421
1455
  export function getParentElement() {
1422
- if (!currentScope || !currentScope.parentElement)
1456
+ if (!currentScope || !currentScope._parentElement)
1423
1457
  throw new ScopeError(true);
1424
- return currentScope.parentElement;
1458
+ return currentScope._parentElement;
1425
1459
  }
1426
1460
  /**
1427
1461
  * Register a function that is to be executed right before the current reactive scope
@@ -1431,7 +1465,7 @@ export function getParentElement() {
1431
1465
  export function clean(clean) {
1432
1466
  if (!currentScope)
1433
1467
  throw new ScopeError(false);
1434
- currentScope.cleaners.push({ _clean: clean });
1468
+ currentScope._cleaners.push({ _clean: clean });
1435
1469
  }
1436
1470
  /**
1437
1471
  * Reactively run a function, meaning the function will rerun when any `Store` that was read
@@ -1440,6 +1474,7 @@ export function clean(clean) {
1440
1474
  * no cause the outer function to rerun.
1441
1475
  *
1442
1476
  * @param func - The function to be (repeatedly) executed.
1477
+ * @returns The mount id (usable for `unmount`) if this is a top-level observe.
1443
1478
  * @example
1444
1479
  * ```
1445
1480
  * let number = new Store(0)
@@ -1455,13 +1490,26 @@ export function clean(clean) {
1455
1490
  * })
1456
1491
  */
1457
1492
  export function observe(func) {
1458
- mount(undefined, func);
1493
+ return _mount(undefined, func, SimpleScope);
1494
+ }
1495
+ /**
1496
+ * Like `observe`, but instead of deferring running the observer function until
1497
+ * a setTimeout 0, run it immediately and synchronously when a change to one of
1498
+ * the observed `Store`s is made. Use this sparingly, as this prevents Aberdeen
1499
+ * from doing the usual batching and smart ordering of observers, leading to
1500
+ * performance problems and observing of 'weird' partial states.
1501
+ * @param func The function to be (repeatedly) executed.
1502
+ * @returns The mount id (usable for `unmount`) if this is a top-level observe.
1503
+ */
1504
+ export function immediateObserve(func) {
1505
+ return _mount(undefined, func, ImmediateScope);
1459
1506
  }
1460
1507
  /**
1461
1508
  * Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
1462
1509
 
1463
1510
  * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
1464
1511
  * @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
1512
+ * @returns The mount id (usable for `unmount`) if this is a top-level mount.
1465
1513
  *
1466
1514
  * @example
1467
1515
  * ```
@@ -1498,20 +1546,47 @@ export function observe(func) {
1498
1546
  * ```
1499
1547
  */
1500
1548
  export function mount(parentElement, func) {
1549
+ return _mount(parentElement, func, SimpleScope);
1550
+ }
1551
+ let maxTopScopeId = 0;
1552
+ const topScopes = new Map();
1553
+ function _mount(parentElement, func, MountScope) {
1501
1554
  let scope;
1502
1555
  if (parentElement || !currentScope) {
1503
- scope = new SimpleScope(parentElement, undefined, 0, func);
1556
+ scope = new MountScope(parentElement, undefined, 0, func);
1504
1557
  }
1505
1558
  else {
1506
- scope = new SimpleScope(currentScope.parentElement, currentScope.lastChild || currentScope.precedingSibling, currentScope.queueOrder + 1, func);
1507
- currentScope.lastChild = scope;
1559
+ scope = new MountScope(currentScope._parentElement, currentScope._lastChild || currentScope._precedingSibling, currentScope._queueOrder + 1, func);
1560
+ currentScope._lastChild = scope;
1508
1561
  }
1509
1562
  // Do the initial run
1510
- scope.update();
1563
+ scope._update();
1511
1564
  // Add it to our list of cleaners. Even if `scope` currently has
1512
1565
  // no cleaners, it may get them in a future refresh.
1513
1566
  if (currentScope) {
1514
- currentScope.cleaners.push(scope);
1567
+ currentScope._cleaners.push(scope);
1568
+ }
1569
+ else {
1570
+ topScopes.set(++maxTopScopeId, scope);
1571
+ return maxTopScopeId;
1572
+ }
1573
+ }
1574
+ /**
1575
+ * Unmount one specific or all top-level mounts or observes, meaning those that were created outside of the scope
1576
+ * of any other mount or observe.
1577
+ * @param id Optional mount number (as returned by `mount`, `observe` or `immediateObserve`). If `undefined`, unmount all.
1578
+ */
1579
+ export function unmount(id) {
1580
+ if (id == null) {
1581
+ for (let scope of topScopes.values())
1582
+ scope._remove();
1583
+ topScopes.clear();
1584
+ }
1585
+ else {
1586
+ let scope = topScopes.get(id);
1587
+ if (!scope)
1588
+ throw new Error("No such mount " + id);
1589
+ scope._remove();
1515
1590
  }
1516
1591
  }
1517
1592
  /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
@@ -1641,11 +1716,12 @@ function valueToData(value) {
1641
1716
  function defaultMakeSortKey(store) {
1642
1717
  return store.index();
1643
1718
  }
1644
- /* istanbul ignore next */
1719
+ /* c8 ignore start */
1645
1720
  function internalError(code) {
1646
1721
  let error = new Error("Aberdeen internal error " + code);
1647
1722
  setTimeout(() => { throw error; }, 0);
1648
1723
  }
1724
+ /* c8 ignore end */
1649
1725
  function handleError(e) {
1650
1726
  // Throw the error async, so the rest of the rendering can continue
1651
1727
  setTimeout(() => { throw e; }, 0);
@@ -1655,188 +1731,26 @@ class ScopeError extends Error {
1655
1731
  super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
1656
1732
  }
1657
1733
  }
1658
- const FADE_TIME = 400;
1659
- const GROW_SHRINK_TRANSITION = `margin ${FADE_TIME}ms ease-out, transform ${FADE_TIME}ms ease-out`;
1660
- function getGrowShrinkProps(el) {
1661
- const parentStyle = el.parentElement ? getComputedStyle(el.parentElement) : {};
1662
- const isHorizontal = parentStyle.display === 'flex' && (parentStyle.flexDirection || '').startsWith('row');
1663
- return isHorizontal ?
1664
- { marginLeft: `-${el.offsetWidth / 2}px`, marginRight: `-${el.offsetWidth / 2}px`, transform: "scaleX(0)" } :
1665
- { marginBottom: `-${el.offsetHeight / 2}px`, marginTop: `-${el.offsetHeight / 2}px`, transform: "scaleY(0)" };
1666
- }
1667
- /** Do a grow transition for the given element. This is meant to be used as a
1668
- * handler for the `create` property.
1669
- *
1670
- * @param el The element to transition.
1671
- *
1672
- * The transition doesn't look great for table elements, and may have problems
1673
- * for other specific cases as well.
1674
- */
1675
- export function grow(el) {
1676
- // This timeout is to await all other elements having been added to the Dom
1677
- scheduleDomReader(() => {
1678
- // Make the element size 0 using transforms and negative margins.
1679
- // This causes a browser layout, as we're querying el.offset<>.
1680
- let props = getGrowShrinkProps(el);
1681
- // The timeout is in order to batch all reads and then all writes when there
1682
- // are multiple simultaneous grow transitions.
1683
- scheduleDomWriter(() => {
1684
- Object.assign(el.style, props);
1685
- // This timeout is to combine multiple transitions into a single browser layout
1686
- scheduleDomReader(() => {
1687
- // Make sure the layouting has been performed, to cause transitions to trigger
1688
- el.offsetHeight;
1689
- scheduleDomWriter(() => {
1690
- // Do the transitions
1691
- el.style.transition = GROW_SHRINK_TRANSITION;
1692
- for (let prop in props)
1693
- el.style[prop] = "";
1694
- setTimeout(() => {
1695
- // Reset the element to a clean state
1696
- el.style.transition = "";
1697
- }, FADE_TIME);
1698
- });
1699
- });
1700
- });
1701
- });
1702
- }
1703
- /** Do a shrink transition for the given element, and remove it from the DOM
1704
- * afterwards. This is meant to be used as a handler for the `destroy` property.
1705
- *
1706
- * @param el The element to transition and remove.
1707
- *
1708
- * The transition doesn't look great for table elements, and may have problems
1709
- * for other specific cases as well.
1710
- */
1711
- export function shrink(el) {
1712
- scheduleDomReader(() => {
1713
- const props = getGrowShrinkProps(el);
1714
- // The timeout is in order to batch all reads and then all writes when there
1715
- // are multiple simultaneous shrink transitions.
1716
- scheduleDomWriter(() => {
1717
- el.style.transition = GROW_SHRINK_TRANSITION;
1718
- Object.assign(el.style, props);
1719
- setTimeout(() => el.remove(), FADE_TIME);
1720
- });
1721
- });
1722
- }
1723
- function recordPatch(func) {
1724
- if (recordingPatch)
1725
- throw new Error(`already recording a patch`);
1726
- recordingPatch = new Map();
1734
+ /** @internal */
1735
+ export function withEmitHandler(handler, func) {
1736
+ const oldEmitHandler = ObsCollection.prototype.emitChange;
1737
+ ObsCollection.prototype.emitChange = handler;
1727
1738
  try {
1728
1739
  func();
1729
1740
  }
1730
- catch (e) {
1731
- recordingPatch = undefined;
1732
- throw e;
1733
- }
1734
- const result = recordingPatch;
1735
- recordingPatch = undefined;
1736
- return result;
1737
- }
1738
- function addToPatch(patch, collection, index, newData, oldData) {
1739
- let collectionMap = patch.get(collection);
1740
- if (!collectionMap) {
1741
- collectionMap = new Map();
1742
- patch.set(collection, collectionMap);
1743
- }
1744
- let prev = collectionMap.get(index);
1745
- if (prev)
1746
- oldData = prev[1];
1747
- if (newData === oldData)
1748
- collectionMap.delete(index);
1749
- else
1750
- collectionMap.set(index, [newData, oldData]);
1751
- }
1752
- function emitPatch(patch) {
1753
- for (let [collection, collectionMap] of patch) {
1754
- for (let [index, [newData, oldData]] of collectionMap) {
1755
- collection.emitChange(index, newData, oldData);
1756
- }
1757
- }
1758
- }
1759
- function mergePatch(target, source, reverse = false) {
1760
- for (let [collection, collectionMap] of source) {
1761
- for (let [index, [newData, oldData]] of collectionMap) {
1762
- addToPatch(target, collection, index, reverse ? oldData : newData, reverse ? newData : oldData);
1763
- }
1764
- }
1765
- }
1766
- function silentlyApplyPatch(patch, force = false) {
1767
- for (let [collection, collectionMap] of patch) {
1768
- for (let [index, [newData, oldData]] of collectionMap) {
1769
- let actualData = collection.rawGet(index);
1770
- if (actualData !== oldData) {
1771
- if (force)
1772
- handleError(new Error(`Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`));
1773
- else
1774
- return false;
1775
- }
1776
- }
1777
- }
1778
- for (let [collection, collectionMap] of patch) {
1779
- for (let [index, [newData, oldData]] of collectionMap) {
1780
- collection.rawSet(index, newData);
1781
- }
1741
+ finally {
1742
+ ObsCollection.prototype.emitChange = oldEmitHandler;
1782
1743
  }
1783
- return true;
1784
1744
  }
1785
- const appliedPredictions = [];
1786
1745
  /**
1787
- * Run the provided function, while treating all changes to Observables as predictions,
1788
- * meaning they will be reverted when changes come back from the server (or some other
1789
- * async source).
1790
- * @param predictFunc The function to run. It will generally modify some Observables
1791
- * to immediately reflect state (as closely as possible) that we expect the server
1792
- * to communicate back to us later on.
1793
- * @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
1746
+ * Run a function, while *not* causing reactive effects for any changes it makes to `Store`s.
1747
+ * @param func The function to be executed once immediately.
1794
1748
  */
1795
- export function applyPrediction(predictFunc) {
1796
- let patch = recordPatch(predictFunc);
1797
- appliedPredictions.push(patch);
1798
- emitPatch(patch);
1799
- return patch;
1800
- }
1801
- /**
1802
- * Temporarily revert all outstanding predictions, optionally run the provided function
1803
- * (which will generally make authoritative changes to the data based on a server response),
1804
- * and then attempt to reapply the predictions on top of the new canonical state, dropping
1805
- * any predictions that can no longer be applied cleanly (the data has been modified) or
1806
- * that were specified in `dropPredictions`.
1807
- *
1808
- * All of this is done such that redraws are only triggered if the overall effect is an
1809
- * actual change to an `Observable`.
1810
- * @param canonFunc The function to run without any predictions applied. This will typically
1811
- * make authoritative changes to the data, based on a server response.
1812
- * @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
1813
- * to undo. Typically, when a server response for a certain request is being handled,
1814
- * you'd want to drop the prediction that was done for that request.
1815
- */
1816
- export function applyCanon(canonFunc, dropPredictions = []) {
1817
- let resultPatch = new Map();
1818
- for (let prediction of appliedPredictions)
1819
- mergePatch(resultPatch, prediction, true);
1820
- silentlyApplyPatch(resultPatch, true);
1821
- for (let prediction of dropPredictions) {
1822
- let pos = appliedPredictions.indexOf(prediction);
1823
- if (pos >= 0)
1824
- appliedPredictions.splice(pos, 1);
1825
- }
1826
- if (canonFunc)
1827
- mergePatch(resultPatch, recordPatch(canonFunc));
1828
- for (let idx = 0; idx < appliedPredictions.length; idx++) {
1829
- if (silentlyApplyPatch(appliedPredictions[idx])) {
1830
- mergePatch(resultPatch, appliedPredictions[idx]);
1831
- }
1832
- else {
1833
- appliedPredictions.splice(idx, 1);
1834
- idx--;
1835
- }
1836
- }
1837
- emitPatch(resultPatch);
1749
+ export function inhibitEffects(func) {
1750
+ withEmitHandler(() => { }, func);
1838
1751
  }
1839
1752
  // @ts-ignore
1840
- // istanbul ignore next
1753
+ // c8 ignore next
1841
1754
  if (!String.prototype.replaceAll)
1842
1755
  String.prototype.replaceAll = function (from, to) { return this.split(from).join(to); };
1756
+ //# sourceMappingURL=aberdeen.js.map