aberdeen 0.0.16 → 0.1.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/README.md CHANGED
@@ -115,4 +115,16 @@ mount(document.body, () => {
115
115
 
116
116
  ## Reference documentation
117
117
 
118
- https://vanviegen.github.io/aberdeen/
118
+ https://vanviegen.github.io/aberdeen/modules.html
119
+
120
+
121
+ ## Roadmap
122
+
123
+ - [x] Support for (dis)appear transitions.
124
+ - [x] A better alternative for scheduleTask.
125
+ - [ ] A simple router.
126
+ - [ ] Architecture document.
127
+ - [x] Optimistic client-side predictions.
128
+ - [ ] SVG support.
129
+ - [ ] More user friendly documentation generator.
130
+ - [ ] Performance profiling and tuning regarding lists.
@@ -2,6 +2,43 @@ interface QueueRunner {
2
2
  queueOrder: number;
3
3
  queueRun(): void;
4
4
  }
5
+ type Patch = Map<ObsCollection, Map<any, [any, any]>>;
6
+ /**
7
+ * Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
8
+ *
9
+ * This function is used to batch DOM read operations together, avoiding unnecessary
10
+ * layout recalculations and improving browser performance. A DOM read operation should
11
+ * only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
12
+ *
13
+ * By batching DOM reads separately from DOM writes, this prevents the browser from
14
+ * interleaving layout reads and writes, which can force additional layout recalculations.
15
+ * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
16
+ * intermediate DOM states during updates.
17
+ *
18
+ * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
19
+ * operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
20
+ *
21
+ * @param func The function to be executed as a DOM read operation.
22
+ */
23
+ export declare function scheduleDomReader(func: () => void): void;
24
+ /**
25
+ * Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
26
+ *
27
+ * This function is used to batch DOM write operations together, avoiding unnecessary
28
+ * layout recalculations and improving browser performance. A DOM write operation should
29
+ * only *write* to the DOM, such as modifying element properties or applying styles.
30
+ *
31
+ * By batching DOM writes separately from DOM reads, this prevents the browser from
32
+ * interleaving layout reads and writes, which can force additional layout recalculations.
33
+ * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
34
+ * intermediate DOM states during updates.
35
+ *
36
+ * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
37
+ * operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
38
+ *
39
+ * @param func The function to be executed as a DOM write operation.
40
+ */
41
+ export declare function scheduleDomWriter(func: () => void): void;
5
42
  type SortKeyType = number | string | Array<number | string>;
6
43
  interface Observer {
7
44
  onChange(index: any, newData: DatumType, oldData: DatumType): void;
@@ -16,7 +53,7 @@ declare abstract class Scope implements QueueRunner, Observer {
16
53
  }>;
17
54
  isDead: boolean;
18
55
  constructor(parentElement: Element | undefined, precedingSibling: Node | Scope | undefined, queueOrder: number);
19
- findPrecedingNode(): Node | undefined;
56
+ findPrecedingNode(stopAt?: Scope | Node | undefined): Node | undefined;
20
57
  findLastNode(): Node | undefined;
21
58
  addNode(node: Node): void;
22
59
  remove(): void;
@@ -102,7 +139,7 @@ declare class ObsMap extends ObsCollection {
102
139
  *
103
140
  * Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
104
141
  * `Array`, `object` and `Map`. The latter three will always have `Store` objects as
105
- * values, creating a tree of `Store`s.
142
+ * values, creating a tree of `Store`-objects.
106
143
  */
107
144
  export declare class Store {
108
145
  private collection;
@@ -110,7 +147,7 @@ export declare class Store {
110
147
  /**
111
148
  * Create a new store with the given `value` as its value. Defaults to `undefined` if no value is given.
112
149
  * When the value is a plain JavaScript object, an `Array` or a `Map`, it will be stored as a tree of
113
- * `Store`s. (Calling [[`get`]] on the store will recreate the original data strucure, though.)
150
+ * `Store`s. (Calling {@link Store.get} on the store will recreate the original data strucure, though.)
114
151
  *
115
152
  * @example
116
153
  * ```
@@ -153,46 +190,46 @@ export declare class Store {
153
190
  */
154
191
  get(...path: any[]): any;
155
192
  /**
156
- * Like [[`get`]], but doesn't subscribe to changes.
193
+ * Like {@link Store.get}, but doesn't subscribe to changes.
157
194
  */
158
195
  peek(...path: any[]): any;
159
196
  /**
160
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `number`.
161
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
197
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
198
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
162
199
  */
163
200
  getNumber(...path: any[]): number;
164
201
  /**
165
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `string`.
166
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
202
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
203
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
167
204
  */
168
205
  getString(...path: any[]): string;
169
206
  /**
170
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `boolean`.
171
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
207
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
208
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
172
209
  */
173
210
  getBoolean(...path: any[]): boolean;
174
211
  /**
175
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `function`.
176
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
212
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
213
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
177
214
  */
178
215
  getFunction(...path: any[]): (Function);
179
216
  /**
180
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `array`.
181
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
217
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
218
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
182
219
  */
183
220
  getArray(...path: any[]): any[];
184
221
  /**
185
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `object`.
186
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
222
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
223
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
187
224
  */
188
225
  getObject(...path: any[]): object;
189
226
  /**
190
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `map`.
191
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
227
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
228
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
192
229
  */
193
230
  getMap(...path: any[]): Map<any, any>;
194
231
  /**
195
- * Like [[`get`]], but the first parameter is the default value (returned when the Store
232
+ * Like {@link Store.get}, but the first parameter is the default value (returned when the Store
196
233
  * contains `undefined`). This default value is also used to determine the expected type,
197
234
  * and to throw otherwise.
198
235
  *
@@ -206,7 +243,7 @@ export declare class Store {
206
243
  */
207
244
  getOr<T>(defaultValue: T, ...path: any[]): T;
208
245
  /** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
209
- * form of the [[`get`]] and [[`peek`]] methods.
246
+ * form of the {@link Store.get} and {@link Store.peek} methods.
210
247
  *
211
248
  * @returns The resulting value, or `undefined` if the `path` does not exist.
212
249
  */
@@ -263,15 +300,15 @@ export declare class Store {
263
300
  getType(...path: any[]): string;
264
301
  /**
265
302
  * Sets the value to the last given argument. Any earlier argument are a Store-path that is first
266
- * resolved/created using [[`makeRef`]].
303
+ * resolved/created using {@link Store.makeRef}.
267
304
  *
268
305
  * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
269
306
  * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
270
307
  * be created, so that changes made through one `Store` will be reflected through the other. Be
271
308
  * carefull not to create loops in your `Store` tree that way, as that would cause any future
272
- * call to [[`get`]] to throw a `RangeError` (Maximum call stack size exceeded.)
309
+ * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
273
310
  *
274
- * If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
311
+ * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
275
312
  *
276
313
  *
277
314
  * @example
@@ -340,7 +377,7 @@ export declare class Store {
340
377
  */
341
378
  push(...pathAndValue: any[]): number;
342
379
  /**
343
- * [[`peek`]] the current value, pass it through `func`, and [[`set`]] the resulting
380
+ * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
344
381
  * value.
345
382
  * @param func The function transforming the value.
346
383
  */
@@ -350,7 +387,7 @@ export declare class Store {
350
387
  * subscribing to every level.
351
388
  * In case `undefined` is encountered while resolving the path, a newly
352
389
  * created `Store` containing `undefined` is returned. In that case, the
353
- * `Store`'s [[`isDetached`]] method will return `true`.
390
+ * `Store`'s {@link Store.isDetached} method will return `true`.
354
391
  * In case something other than a collection is encountered, an error is thrown.
355
392
  */
356
393
  ref(...path: any[]): Store;
@@ -375,12 +412,12 @@ export declare class Store {
375
412
  * ```
376
413
  */
377
414
  makeRef(...path: any[]): Store;
378
- /** @Internal */
415
+ /** @internal */
379
416
  _observe(): DatumType;
380
417
  /**
381
418
  * Iterate the specified collection (Array, Map or object), running the given code block for each item.
382
419
  * When items are added to the collection at some later point, the code block will be ran for them as well.
383
- * When an item is removed, the [[`clean`]] handlers left by its code block are executed.
420
+ * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
384
421
  *
385
422
  *
386
423
  *
@@ -420,7 +457,7 @@ export declare class Store {
420
457
  */
421
458
  multiMap(func: (store: Store) => any): Store;
422
459
  /**
423
- * @returns Returns `true` when the `Store` was created by [[`ref`]]ing a path that
460
+ * @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
424
461
  * does not exist.
425
462
  */
426
463
  isDetached(): boolean;
@@ -431,7 +468,7 @@ export declare class Store {
431
468
  * @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
432
469
  * @param rest - The other arguments are flexible and interpreted based on their types:
433
470
  * - `string`: Used as textContent for the element.
434
- * - `object`: Used as attributes, properties or event listeners for the element. See [[`prop`]] on how the distinction is made.
471
+ * - `object`: Used as attributes, properties or event listeners for the element. See {@link Store.prop} on how the distinction is made and to read about a couple of special keys.
435
472
  * - `function`: The render function used to draw the scope of the element. This function gets its own `Scope`, so that if any `Store` it reads changes, it will redraw by itself.
436
473
  * - `Store`: Presuming `tag` is `"input"`, `"textarea"` or `"select"`, create a two-way binding between this `Store` value and the input element. The initial value of the input will be set to the initial value of the `Store`, or the other way around if the `Store` holds `undefined`. After that, the `Store` will be updated when the input changes and vice versa.
437
474
  * @example
@@ -457,8 +494,63 @@ export declare function text(text: string): void;
457
494
  * without recreating the element itself. Also, code can be more readable this way.
458
495
  * Note that when a nested `observe()` is used, properties set this way do NOT
459
496
  * automatically revert to their previous values.
497
+ *
498
+ * Here's how properties are handled:
499
+ * - If `name` is `"create"`, `value` should be either a function that gets
500
+ * called with the element as its only argument immediately after creation,
501
+ * or a string being the name of a CSS class that gets added immediately
502
+ * after element creation, and removed shortly afterwards. This allows for
503
+ * reveal animations. However, this is intentionally *not* done
504
+ * for elements that are created as part of a larger (re)draw, to prevent
505
+ * all elements from individually animating on page creation.
506
+ * - If `name` is `"destroy"`, `value` should be a function that gets called
507
+ * with the element as its only argument, *instead of* the element being
508
+ * removed from the DOM (which the function will presumably need to do
509
+ * eventually). This can be used for a conceal animation.
510
+ * As a convenience, it's also possible to provide a string instead of
511
+ * a function, which will be added to the element as a CSS class, allowing
512
+ * for simple transitions. In this case, the DOM element in removed 2 seconds
513
+ * later (currently not configurable).
514
+ * Similar to `"create"` (and in this case doing anything else would make little
515
+ * sense), this only happens when the element being is the top-level element
516
+ * being removed from the DOM.
517
+ * - If `value` is a function, it is registered as an event handler for the
518
+ * `name` event.
519
+ * - If `name` is `"class"` or `"className"` and the `value` is an
520
+ * object, all keys of the object are either added or removed from `classList`,
521
+ * depending on whether `value` is true-like or false-like.
522
+ * - If `value` is a boolean *or* `name` is `"value"`, `"className"` or
523
+ * `"selectedIndex"`, it is set as a DOM element *property*.
524
+ * - If `name` is `"text"`, the `value` is set as the element's `textContent`.
525
+ * - If `name` is `"style"` and `value` is an object, each of its
526
+ * key/value pairs are assigned to the element's `.style`.
527
+ * - In other cases, the `value` is set as the `name` HTML *attribute*.
528
+ *
529
+ * @example
530
+ * ```
531
+ * node('input', () => {
532
+ * prop('type', 'password')
533
+ * prop('readOnly', true)
534
+ * prop('class', 'my-class')
535
+ * prop('class', {
536
+ * 'my-disabled-class': false,
537
+ * 'my-enabled-class': true,
538
+ * })
539
+ * prop({
540
+ * class: 'my-class',
541
+ * text: 'Here is something to read...',
542
+ * style: {
543
+ * backgroundColor: 'red',
544
+ * fontWeight: 'bold',
545
+ * },
546
+ * create: aberdeen.fadeIn,
547
+ * destroy: 'my-fade-out-class',
548
+ * click: myClickHandler,
549
+ * })
550
+ * })
551
+ * ```
460
552
  */
461
- export declare function prop(prop: string, value: any): void;
553
+ export declare function prop(name: string, value: any): void;
462
554
  export declare function prop(props: object): void;
463
555
  /**
464
556
  * Return the browser Element that `node()`s would be rendered to at this point.
@@ -497,7 +589,7 @@ export declare function clean(clean: (scope: Scope) => void): void;
497
589
  */
498
590
  export declare function observe(func: () => void): void;
499
591
  /**
500
- * Like [[`observe`]], but allow the function to create DOM elements using [[`node`]].
592
+ * Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
501
593
 
502
594
  * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
503
595
  * @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
@@ -512,7 +604,7 @@ export declare function observe(func: () => void): void;
512
604
  * })
513
605
  * ```
514
606
  *
515
- * An example nesting [[`observe`]] within `mount`:
607
+ * An example nesting {@link Store.observe} within `mount`:
516
608
  * ```
517
609
  * let selected = new Store(0)
518
610
  * let colors = new Store(new Map())
@@ -537,7 +629,7 @@ export declare function observe(func: () => void): void;
537
629
  * ```
538
630
  */
539
631
  export declare function mount(parentElement: Element | undefined, func: () => void): void;
540
- /** Runs the given function, while not subscribing the current scope when reading [[`Store`]] values.
632
+ /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
541
633
  *
542
634
  * @param func Function to be executed immediately.
543
635
  * @returns Whatever `func()` returns.
@@ -559,4 +651,48 @@ export declare function mount(parentElement: Element | undefined, func: () => vo
559
651
  * for `count()` however.
560
652
  */
561
653
  export declare function peek<T>(func: () => T): T;
654
+ /** Do a grow transition for the given element. This is meant to be used as a
655
+ * handler for the `create` property.
656
+ *
657
+ * @param el The element to transition.
658
+ *
659
+ * The transition doesn't look great for table elements, and may have problems
660
+ * for other specific cases as well.
661
+ */
662
+ export declare function grow(el: HTMLElement): void;
663
+ /** Do a shrink transition for the given element, and remove it from the DOM
664
+ * afterwards. This is meant to be used as a handler for the `destroy` property.
665
+ *
666
+ * @param el The element to transition and remove.
667
+ *
668
+ * The transition doesn't look great for table elements, and may have problems
669
+ * for other specific cases as well.
670
+ */
671
+ export declare function shrink(el: HTMLElement): void;
672
+ /**
673
+ * Run the provided function, while treating all changes to Observables as predictions,
674
+ * meaning they will be reverted when changes come back from the server (or some other
675
+ * async source).
676
+ * @param predictFunc The function to run. It will generally modify some Observables
677
+ * to immediately reflect state (as closely as possible) that we expect the server
678
+ * to communicate back to us later on.
679
+ * @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
680
+ */
681
+ export declare function applyPrediction(predictFunc: () => void): Patch;
682
+ /**
683
+ * Temporarily revert all outstanding predictions, optionally run the provided function
684
+ * (which will generally make authoritative changes to the data based on a server response),
685
+ * and then attempt to reapply the predictions on top of the new canonical state, dropping
686
+ * any predictions that can no longer be applied cleanly (the data has been modified) or
687
+ * that were specified in `dropPredictions`.
688
+ *
689
+ * All of this is done such that redraws are only triggered if the overall effect is an
690
+ * actual change to an `Observable`.
691
+ * @param canonFunc The function to run without any predictions applied. This will typically
692
+ * make authoritative changes to the data, based on a server response.
693
+ * @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
694
+ * to undo. Typically, when a server response for a certain request is being handled,
695
+ * you'd want to drop the prediction that was done for that request.
696
+ */
697
+ export declare function applyCanon(canonFunc?: (() => void), dropPredictions?: Array<Patch>): void;
562
698
  export {};
package/dist/aberdeen.js CHANGED
@@ -2,6 +2,8 @@ let queueArray = [];
2
2
  let queueSet = new Set();
3
3
  let queueOrdered = true;
4
4
  let runQueueDepth = 0;
5
+ let queueIndex;
6
+ let recordingPatch;
5
7
  function queue(runner) {
6
8
  if (queueSet.has(runner))
7
9
  return;
@@ -18,24 +20,74 @@ function queue(runner) {
18
20
  queueSet.add(runner);
19
21
  }
20
22
  function runQueue() {
21
- for (let index = 0; index < queueArray.length;) {
23
+ onCreateEnabled = true;
24
+ for (queueIndex = 0; queueIndex < queueArray.length;) {
25
+ // Sort queue if new unordered items have been added since last time.
22
26
  if (!queueOrdered) {
23
- queueArray.splice(0, index);
24
- index = 0;
25
- // Order queued observers by depth, lowest first
27
+ queueArray.splice(0, queueIndex);
28
+ queueIndex = 0;
29
+ // Order queued observers by depth, lowest first.
26
30
  queueArray.sort((a, b) => a.queueOrder - b.queueOrder);
27
31
  queueOrdered = true;
28
32
  }
33
+ // Process the rest of what's currently in the queue.
29
34
  let batchEndIndex = queueArray.length;
30
- for (; index < batchEndIndex && queueOrdered; index++) {
31
- let runner = queueArray[index];
35
+ for (; queueIndex < batchEndIndex && queueOrdered; queueIndex++) {
36
+ let runner = queueArray[queueIndex];
32
37
  queueSet.delete(runner);
33
38
  runner.queueRun();
34
39
  }
40
+ // If new items have been added to the queue while processing the previous
41
+ // batch, we'll need to run this loop again.
35
42
  runQueueDepth++;
36
43
  }
37
44
  queueArray.length = 0;
45
+ queueIndex = undefined;
38
46
  runQueueDepth = 0;
47
+ onCreateEnabled = false;
48
+ }
49
+ let scheduleOrder = 1000;
50
+ /**
51
+ * Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
52
+ *
53
+ * This function is used to batch DOM read operations together, avoiding unnecessary
54
+ * layout recalculations and improving browser performance. A DOM read operation should
55
+ * only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
56
+ *
57
+ * By batching DOM reads separately from DOM writes, this prevents the browser from
58
+ * interleaving layout reads and writes, which can force additional layout recalculations.
59
+ * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
60
+ * intermediate DOM states during updates.
61
+ *
62
+ * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
63
+ * operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
64
+ *
65
+ * @param func The function to be executed as a DOM read operation.
66
+ */
67
+ 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 });
70
+ }
71
+ /**
72
+ * Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
73
+ *
74
+ * This function is used to batch DOM write operations together, avoiding unnecessary
75
+ * layout recalculations and improving browser performance. A DOM write operation should
76
+ * only *write* to the DOM, such as modifying element properties or applying styles.
77
+ *
78
+ * By batching DOM writes separately from DOM reads, this prevents the browser from
79
+ * interleaving layout reads and writes, which can force additional layout recalculations.
80
+ * This helps reduce visual glitches and flashes by ensuring the browser doesn't render
81
+ * intermediate DOM states during updates.
82
+ *
83
+ * Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
84
+ * operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
85
+ *
86
+ * @param func The function to be executed as a DOM write operation.
87
+ */
88
+ 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 });
39
91
  }
40
92
  /**
41
93
  * Given an integer number, a string or an array of these, this function returns a string that can be used
@@ -96,24 +148,27 @@ class Scope {
96
148
  this.precedingSibling = precedingSibling;
97
149
  this.queueOrder = queueOrder;
98
150
  }
99
- // Get a reference to the last Node preceding
100
- findPrecedingNode() {
101
- let pre = this.precedingSibling;
102
- while (pre) {
151
+ // Get a reference to the last Node preceding this Scope, or undefined if there is none
152
+ findPrecedingNode(stopAt = undefined) {
153
+ let cur = this;
154
+ let pre;
155
+ while ((pre = cur.precedingSibling) && pre !== stopAt) {
103
156
  if (pre instanceof Node)
104
157
  return pre;
105
158
  let node = pre.findLastNode();
106
159
  if (node)
107
160
  return node;
108
- pre = pre.precedingSibling;
161
+ cur = pre;
109
162
  }
110
163
  }
111
164
  // Get a reference to the last Node within this scope and parentElement
112
165
  findLastNode() {
113
- if (this.lastChild instanceof Node)
114
- return this.lastChild;
115
- if (this.lastChild instanceof Scope)
116
- return this.lastChild.findLastNode() || this.lastChild.findPrecedingNode();
166
+ if (this.lastChild) {
167
+ if (this.lastChild instanceof Node)
168
+ return this.lastChild;
169
+ else
170
+ return this.lastChild.findLastNode() || this.lastChild.findPrecedingNode(this.precedingSibling);
171
+ }
117
172
  }
118
173
  addNode(node) {
119
174
  if (!this.parentElement)
@@ -127,20 +182,37 @@ class Scope {
127
182
  let lastNode = this.findLastNode();
128
183
  if (lastNode) {
129
184
  // at least one DOM node to be removed
130
- let precedingNode = this.findPrecedingNode();
131
- // Keep removing DOM nodes starting at our last node, until we encounter the preceding node
132
- // (which can be undefined)
133
- while (lastNode !== precedingNode) {
185
+ let nextNode = this.findPrecedingNode();
186
+ nextNode = (nextNode ? nextNode.nextSibling : this.parentElement.firstChild);
187
+ this.lastChild = undefined;
188
+ // Keep removing DOM nodes starting at our first node, until we encounter the last node
189
+ while (true) {
134
190
  /* istanbul ignore next */
135
- if (!lastNode) {
191
+ if (!nextNode)
136
192
  return internalError(1);
193
+ const node = nextNode;
194
+ nextNode = node.nextSibling || undefined;
195
+ let onDestroy = onDestroyMap.get(node);
196
+ if (onDestroy && node instanceof Element) {
197
+ if (onDestroy !== true) {
198
+ if (typeof onDestroy === 'function') {
199
+ onDestroy(node);
200
+ }
201
+ else {
202
+ destroyWithClass(node, onDestroy);
203
+ }
204
+ // This causes the element to be ignored from this function from now on:
205
+ onDestroyMap.set(node, true);
206
+ }
207
+ // Ignore the deleting element
208
+ }
209
+ else {
210
+ this.parentElement.removeChild(node);
137
211
  }
138
- let nextLastNode = lastNode.previousSibling || undefined;
139
- this.parentElement.removeChild(lastNode);
140
- lastNode = nextLastNode;
212
+ if (node === lastNode)
213
+ break;
141
214
  }
142
215
  }
143
- this.lastChild = undefined;
144
216
  }
145
217
  // run cleaners
146
218
  this._clean();
@@ -223,6 +295,9 @@ class OnEachScope extends Scope {
223
295
  this.renderer = renderer;
224
296
  this.makeSortKey = makeSortKey;
225
297
  }
298
+ // toString(): string {
299
+ // return `OnEachScope(collection=${this.collection})`
300
+ // }
226
301
  onChange(index, newData, oldData) {
227
302
  if (oldData === undefined) {
228
303
  if (this.removedIndexes.has(index)) {
@@ -288,9 +363,9 @@ class OnEachScope extends Scope {
288
363
  if (!scope) {
289
364
  return internalError(6);
290
365
  }
366
+ scope.remove();
291
367
  this.byIndex.delete(itemIndex);
292
368
  this.removeFromPosition(scope);
293
- scope.remove();
294
369
  }
295
370
  findPosition(sortStr) {
296
371
  // In case of duplicate `sortStr`s, this will return the first match.
@@ -315,12 +390,14 @@ class OnEachScope extends Scope {
315
390
  let pos = this.findPosition(child.sortStr);
316
391
  this.byPosition.splice(pos, 0, child);
317
392
  // Based on the position in the list, set the precedingSibling for the new Scope
318
- child.precedingSibling = pos > 0 ? this.byPosition[pos - 1] : this.precedingSibling;
319
- // Now set the precedingSibling for the subsequent item to this new Scope
320
- if (pos + 1 < this.byPosition.length) {
321
- this.byPosition[pos + 1].precedingSibling = child;
393
+ // and for the next sibling.
394
+ let nextSibling = this.byPosition[pos + 1];
395
+ if (nextSibling) {
396
+ child.precedingSibling = nextSibling.precedingSibling;
397
+ nextSibling.precedingSibling = child;
322
398
  }
323
399
  else {
400
+ child.precedingSibling = this.lastChild || this.precedingSibling;
324
401
  this.lastChild = child;
325
402
  }
326
403
  }
@@ -333,10 +410,20 @@ class OnEachScope extends Scope {
333
410
  // Yep, this is the right scope
334
411
  this.byPosition.splice(pos, 1);
335
412
  if (pos < this.byPosition.length) {
336
- this.byPosition[pos].precedingSibling = pos > 0 ? this.byPosition[pos - 1] : this.precedingSibling;
413
+ let nextSibling = this.byPosition[pos];
414
+ /* istanbul ignore next */
415
+ if (!nextSibling)
416
+ return internalError(8);
417
+ /* istanbul ignore next */
418
+ if (nextSibling.precedingSibling !== child)
419
+ return internalError(13);
420
+ nextSibling.precedingSibling = child.precedingSibling;
337
421
  }
338
422
  else {
339
- this.lastChild = this.byPosition.length ? this.byPosition[this.byPosition.length - 1] : undefined;
423
+ /* istanbul ignore next */
424
+ if (child !== this.lastChild)
425
+ return internalError(12);
426
+ this.lastChild = child.precedingSibling === this.precedingSibling ? undefined : child.precedingSibling;
340
427
  }
341
428
  return;
342
429
  }
@@ -355,6 +442,9 @@ class OnEachItemScope extends Scope {
355
442
  this.parent = parent;
356
443
  this.itemIndex = itemIndex;
357
444
  }
445
+ // toString(): string {
446
+ // return `OnEachItemScope(itemIndex=${this.itemIndex} parentElement=${this.parentElement} parent=${this.parent} precedingSibling=${this.precedingSibling} lastChild=${this.lastChild})`
447
+ // }
358
448
  queueRun() {
359
449
  /* istanbul ignore next */
360
450
  if (currentScope) {
@@ -412,6 +502,9 @@ class ObsCollection {
412
502
  constructor() {
413
503
  this.observers = new Map();
414
504
  }
505
+ // toString(): string {
506
+ // return JSON.stringify(peek(() => this.getRecursive(3)))
507
+ // }
415
508
  addObserver(index, observer) {
416
509
  observer = observer;
417
510
  let obsSet = this.observers.get(index);
@@ -430,12 +523,17 @@ class ObsCollection {
430
523
  obsSet.delete(observer);
431
524
  }
432
525
  emitChange(index, newData, oldData) {
433
- let obsSet = this.observers.get(index);
434
- if (obsSet)
435
- obsSet.forEach(observer => observer.onChange(index, newData, oldData));
436
- obsSet = this.observers.get(ANY_INDEX);
437
- if (obsSet)
438
- obsSet.forEach(observer => observer.onChange(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
+ }
439
537
  }
440
538
  _clean(observer) {
441
539
  this.removeObserver(ANY_INDEX, observer);
@@ -639,7 +737,7 @@ class ObsObject extends ObsMap {
639
737
  *
640
738
  * Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
641
739
  * `Array`, `object` and `Map`. The latter three will always have `Store` objects as
642
- * values, creating a tree of `Store`s.
740
+ * values, creating a tree of `Store`-objects.
643
741
  */
644
742
  export class Store {
645
743
  constructor(value = undefined, index = undefined) {
@@ -694,48 +792,48 @@ export class Store {
694
792
  return this.query({ path });
695
793
  }
696
794
  /**
697
- * Like [[`get`]], but doesn't subscribe to changes.
795
+ * Like {@link Store.get}, but doesn't subscribe to changes.
698
796
  */
699
797
  peek(...path) {
700
798
  return this.query({ path, peek: true });
701
799
  }
702
800
  /**
703
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `number`.
704
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
801
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
802
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
705
803
  */
706
804
  getNumber(...path) { return this.query({ path, type: 'number' }); }
707
805
  /**
708
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `string`.
709
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
806
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
807
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
710
808
  */
711
809
  getString(...path) { return this.query({ path, type: 'string' }); }
712
810
  /**
713
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `boolean`.
714
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
811
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
812
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
715
813
  */
716
814
  getBoolean(...path) { return this.query({ path, type: 'boolean' }); }
717
815
  /**
718
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `function`.
719
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
816
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
817
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
720
818
  */
721
819
  getFunction(...path) { return this.query({ path, type: 'function' }); }
722
820
  /**
723
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `array`.
724
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
821
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
822
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
725
823
  */
726
824
  getArray(...path) { return this.query({ path, type: 'array' }); }
727
825
  /**
728
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `object`.
729
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
826
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
827
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
730
828
  */
731
829
  getObject(...path) { return this.query({ path, type: 'object' }); }
732
830
  /**
733
- * @returns Like [[`get`]], but throws a `TypeError` if the resulting value is not of type `map`.
734
- * Using this instead of just [[`get`]] is especially useful from within TypeScript.
831
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
832
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
735
833
  */
736
834
  getMap(...path) { return this.query({ path, type: 'map' }); }
737
835
  /**
738
- * Like [[`get`]], but the first parameter is the default value (returned when the Store
836
+ * Like {@link Store.get}, but the first parameter is the default value (returned when the Store
739
837
  * contains `undefined`). This default value is also used to determine the expected type,
740
838
  * and to throw otherwise.
741
839
  *
@@ -758,7 +856,7 @@ export class Store {
758
856
  return this.query({ type, defaultValue, path });
759
857
  }
760
858
  /** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
761
- * form of the [[`get`]] and [[`peek`]] methods.
859
+ * form of the {@link Store.get} and {@link Store.peek} methods.
762
860
  *
763
861
  * @returns The resulting value, or `undefined` if the `path` does not exist.
764
862
  */
@@ -850,15 +948,15 @@ export class Store {
850
948
  }
851
949
  /**
852
950
  * Sets the value to the last given argument. Any earlier argument are a Store-path that is first
853
- * resolved/created using [[`makeRef`]].
951
+ * resolved/created using {@link Store.makeRef}.
854
952
  *
855
953
  * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
856
954
  * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
857
955
  * be created, so that changes made through one `Store` will be reflected through the other. Be
858
956
  * carefull not to create loops in your `Store` tree that way, as that would cause any future
859
- * call to [[`get`]] to throw a `RangeError` (Maximum call stack size exceeded.)
957
+ * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
860
958
  *
861
- * If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
959
+ * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
862
960
  *
863
961
  *
864
962
  * @example
@@ -954,7 +1052,7 @@ export class Store {
954
1052
  return pos;
955
1053
  }
956
1054
  /**
957
- * [[`peek`]] the current value, pass it through `func`, and [[`set`]] the resulting
1055
+ * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
958
1056
  * value.
959
1057
  * @param func The function transforming the value.
960
1058
  */
@@ -966,7 +1064,7 @@ export class Store {
966
1064
  * subscribing to every level.
967
1065
  * In case `undefined` is encountered while resolving the path, a newly
968
1066
  * created `Store` containing `undefined` is returned. In that case, the
969
- * `Store`'s [[`isDetached`]] method will return `true`.
1067
+ * `Store`'s {@link Store.isDetached} method will return `true`.
970
1068
  * In case something other than a collection is encountered, an error is thrown.
971
1069
  */
972
1070
  ref(...path) {
@@ -1019,7 +1117,7 @@ export class Store {
1019
1117
  }
1020
1118
  return store;
1021
1119
  }
1022
- /** @Internal */
1120
+ /** @internal */
1023
1121
  _observe() {
1024
1122
  if (currentScope) {
1025
1123
  if (this.collection.addObserver(this.idx, currentScope)) {
@@ -1031,7 +1129,7 @@ export class Store {
1031
1129
  /**
1032
1130
  * Iterate the specified collection (Array, Map or object), running the given code block for each item.
1033
1131
  * When items are added to the collection at some later point, the code block will be ran for them as well.
1034
- * When an item is removed, the [[`clean`]] handlers left by its code block are executed.
1132
+ * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
1035
1133
  *
1036
1134
  *
1037
1135
  *
@@ -1122,7 +1220,7 @@ export class Store {
1122
1220
  result.forEach((value, key) => {
1123
1221
  out.set(key, value);
1124
1222
  });
1125
- keys = Array.from(result.keys());
1223
+ keys = [...result.keys()];
1126
1224
  }
1127
1225
  else {
1128
1226
  return;
@@ -1138,7 +1236,7 @@ export class Store {
1138
1236
  return out;
1139
1237
  }
1140
1238
  /**
1141
- * @returns Returns `true` when the `Store` was created by [[`ref`]]ing a path that
1239
+ * @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
1142
1240
  * does not exist.
1143
1241
  */
1144
1242
  isDetached() { return false; }
@@ -1167,12 +1265,18 @@ export class Store {
1167
1265
  class DetachedStore extends Store {
1168
1266
  isDetached() { return true; }
1169
1267
  }
1268
+ let onCreateEnabled = false;
1269
+ let onDestroyMap = new WeakMap();
1270
+ function destroyWithClass(element, cls) {
1271
+ element.classList.add(cls);
1272
+ setTimeout(() => element.remove(), 2000);
1273
+ }
1170
1274
  /**
1171
1275
  * Create a new DOM element, and insert it into the DOM at the position held by the current scope.
1172
1276
  * @param tag - The tag of the element to be created and optionally dot-separated class names. For example: `h1` or `p.intro.has_avatar`.
1173
1277
  * @param rest - The other arguments are flexible and interpreted based on their types:
1174
1278
  * - `string`: Used as textContent for the element.
1175
- * - `object`: Used as attributes, properties or event listeners for the element. See [[`prop`]] on how the distinction is made.
1279
+ * - `object`: Used as attributes, properties or event listeners for the element. See {@link Store.prop} on how the distinction is made and to read about a couple of special keys.
1176
1280
  * - `function`: The render function used to draw the scope of the element. This function gets its own `Scope`, so that if any `Store` it reads changes, it will redraw by itself.
1177
1281
  * - `Store`: Presuming `tag` is `"input"`, `"textarea"` or `"select"`, create a two-way binding between this `Store` value and the input element. The initial value of the input will be set to the initial value of the `Store`, or the other way around if the `Store` holds `undefined`. After that, the `Store` will be updated when the input changes and vice versa.
1178
1282
  * @example
@@ -1207,7 +1311,14 @@ export function node(tag = "", ...rest) {
1207
1311
  let type = typeof item;
1208
1312
  if (type === 'function') {
1209
1313
  let scope = new SimpleScope(el, undefined, currentScope.queueOrder + 1, item);
1210
- scope.update();
1314
+ if (onCreateEnabled) {
1315
+ onCreateEnabled = false;
1316
+ scope.update();
1317
+ onCreateEnabled = true;
1318
+ }
1319
+ else {
1320
+ scope.update();
1321
+ }
1211
1322
  // Add it to our list of cleaners. Even if `scope` currently has
1212
1323
  // no cleaners, it may get them in a future refresh.
1213
1324
  currentScope.cleaners.push(scope);
@@ -1262,13 +1373,13 @@ function bindInput(el, store) {
1262
1373
  };
1263
1374
  }
1264
1375
  else {
1376
+ onInputChange = () => store.set(type === 'number' || type === 'range' ? (el.value === '' ? null : +el.value) : el.value);
1265
1377
  if (value === undefined)
1266
- store.set(el.value);
1378
+ onInputChange();
1267
1379
  onStoreChange = value => {
1268
1380
  if (el.value !== value)
1269
1381
  el.value = value;
1270
1382
  };
1271
- onInputChange = () => store.set(el.value);
1272
1383
  }
1273
1384
  observe(() => {
1274
1385
  onStoreChange(store.get());
@@ -1288,16 +1399,16 @@ export function text(text) {
1288
1399
  return;
1289
1400
  currentScope.addNode(document.createTextNode(text));
1290
1401
  }
1291
- export function prop(prop, value = undefined) {
1402
+ export function prop(name, value = undefined) {
1292
1403
  if (!currentScope || !currentScope.parentElement)
1293
1404
  throw new ScopeError(true);
1294
- if (typeof prop === 'object') {
1295
- for (let k in prop) {
1296
- applyProp(currentScope.parentElement, k, prop[k]);
1405
+ if (typeof name === 'object') {
1406
+ for (let k in name) {
1407
+ applyProp(currentScope.parentElement, k, name[k]);
1297
1408
  }
1298
1409
  }
1299
1410
  else {
1300
- applyProp(currentScope.parentElement, prop, value);
1411
+ applyProp(currentScope.parentElement, name, value);
1301
1412
  }
1302
1413
  }
1303
1414
  /**
@@ -1347,7 +1458,7 @@ export function observe(func) {
1347
1458
  mount(undefined, func);
1348
1459
  }
1349
1460
  /**
1350
- * Like [[`observe`]], but allow the function to create DOM elements using [[`node`]].
1461
+ * Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
1351
1462
 
1352
1463
  * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
1353
1464
  * @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
@@ -1362,7 +1473,7 @@ export function observe(func) {
1362
1473
  * })
1363
1474
  * ```
1364
1475
  *
1365
- * An example nesting [[`observe`]] within `mount`:
1476
+ * An example nesting {@link Store.observe} within `mount`:
1366
1477
  * ```
1367
1478
  * let selected = new Store(0)
1368
1479
  * let colors = new Store(new Map())
@@ -1403,7 +1514,7 @@ export function mount(parentElement, func) {
1403
1514
  currentScope.cleaners.push(scope);
1404
1515
  }
1405
1516
  }
1406
- /** Runs the given function, while not subscribing the current scope when reading [[`Store`]] values.
1517
+ /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
1407
1518
  *
1408
1519
  * @param func Function to be executed immediately.
1409
1520
  * @returns Whatever `func()` returns.
@@ -1438,33 +1549,47 @@ export function peek(func) {
1438
1549
  * Helper functions
1439
1550
  */
1440
1551
  function applyProp(el, prop, value) {
1441
- if ((prop === 'class' || prop === 'className') && typeof value === 'object') {
1442
- // Allow setting classes using an object where the keys are the names and
1443
- // the values are booleans stating whether to set or remove.
1444
- for (let name in value) {
1445
- if (value[name])
1446
- el.classList.add(name);
1447
- else
1448
- el.classList.remove(name);
1552
+ if (prop === 'create') {
1553
+ if (onCreateEnabled) {
1554
+ if (typeof value === 'function') {
1555
+ value(el);
1556
+ }
1557
+ else {
1558
+ el.classList.add(value);
1559
+ setTimeout(function () { el.classList.remove(value); }, 0);
1560
+ }
1449
1561
  }
1450
1562
  }
1451
- else if (prop === 'value' || prop === 'className' || prop === 'selectedIndex' || value === true || value === false) {
1452
- // All boolean values and a few specific keys should be set as a property
1453
- el[prop] = value;
1563
+ else if (prop === 'destroy') {
1564
+ onDestroyMap.set(el, value);
1454
1565
  }
1455
1566
  else if (typeof value === 'function') {
1456
1567
  // Set an event listener; remove it again on clean.
1457
1568
  el.addEventListener(prop, value);
1458
1569
  clean(() => el.removeEventListener(prop, value));
1459
1570
  }
1460
- else if (prop === 'style' && typeof value === 'object') {
1461
- // `style` can receive an object
1462
- Object.assign(el.style, value);
1571
+ else if (prop === 'value' || prop === 'className' || prop === 'selectedIndex' || value === true || value === false) {
1572
+ // All boolean values and a few specific keys should be set as a property
1573
+ el[prop] = value;
1463
1574
  }
1464
1575
  else if (prop === 'text') {
1465
1576
  // `text` is set as textContent
1466
1577
  el.textContent = value;
1467
1578
  }
1579
+ else if ((prop === 'class' || prop === 'className') && typeof value === 'object') {
1580
+ // Allow setting classes using an object where the keys are the names and
1581
+ // the values are booleans stating whether to set or remove.
1582
+ for (let name in value) {
1583
+ if (value[name])
1584
+ el.classList.add(name);
1585
+ else
1586
+ el.classList.remove(name);
1587
+ }
1588
+ }
1589
+ else if (prop === 'style' && typeof value === 'object') {
1590
+ // `style` can receive an object
1591
+ Object.assign(el.style, value);
1592
+ }
1468
1593
  else {
1469
1594
  // Everything else is an HTML attribute
1470
1595
  el.setAttribute(prop, value);
@@ -1530,6 +1655,182 @@ class ScopeError extends Error {
1530
1655
  super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
1531
1656
  }
1532
1657
  }
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();
1727
+ try {
1728
+ func();
1729
+ }
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 == null) {
1741
+ collectionMap = new Map();
1742
+ patch.set(collection, collectionMap);
1743
+ }
1744
+ let prev = collectionMap.get(index);
1745
+ collectionMap.set(index, [newData, prev == null ? oldData : prev[1]]);
1746
+ }
1747
+ function emitPatch(patch) {
1748
+ for (let [collection, collectionMap] of patch) {
1749
+ for (let [index, [newData, oldData]] of collectionMap) {
1750
+ collection.emitChange(index, newData, oldData);
1751
+ }
1752
+ }
1753
+ }
1754
+ function mergePatch(target, source, reverse = false) {
1755
+ for (let [collection, collectionMap] of source) {
1756
+ for (let [index, [newData, oldData]] of collectionMap) {
1757
+ addToPatch(target, collection, index, reverse ? oldData : newData, reverse ? newData : oldData);
1758
+ }
1759
+ }
1760
+ }
1761
+ function silentlyApplyPatch(patch, force = false) {
1762
+ for (let [collection, collectionMap] of patch) {
1763
+ for (let [index, [newData, oldData]] of collectionMap) {
1764
+ let actualData = collection.rawGet(index);
1765
+ if (actualData !== oldData) {
1766
+ if (force)
1767
+ handleError(new Error(`Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`));
1768
+ else
1769
+ return false;
1770
+ }
1771
+ }
1772
+ }
1773
+ for (let [collection, collectionMap] of patch) {
1774
+ for (let [index, [newData, oldData]] of collectionMap) {
1775
+ collection.rawSet(index, newData);
1776
+ }
1777
+ }
1778
+ return true;
1779
+ }
1780
+ const appliedPredictions = [];
1781
+ /**
1782
+ * Run the provided function, while treating all changes to Observables as predictions,
1783
+ * meaning they will be reverted when changes come back from the server (or some other
1784
+ * async source).
1785
+ * @param predictFunc The function to run. It will generally modify some Observables
1786
+ * to immediately reflect state (as closely as possible) that we expect the server
1787
+ * to communicate back to us later on.
1788
+ * @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
1789
+ */
1790
+ export function applyPrediction(predictFunc) {
1791
+ let patch = recordPatch(predictFunc);
1792
+ appliedPredictions.push(patch);
1793
+ emitPatch(patch);
1794
+ return patch;
1795
+ }
1796
+ /**
1797
+ * Temporarily revert all outstanding predictions, optionally run the provided function
1798
+ * (which will generally make authoritative changes to the data based on a server response),
1799
+ * and then attempt to reapply the predictions on top of the new canonical state, dropping
1800
+ * any predictions that can no longer be applied cleanly (the data has been modified) or
1801
+ * that were specified in `dropPredictions`.
1802
+ *
1803
+ * All of this is done such that redraws are only triggered if the overall effect is an
1804
+ * actual change to an `Observable`.
1805
+ * @param canonFunc The function to run without any predictions applied. This will typically
1806
+ * make authoritative changes to the data, based on a server response.
1807
+ * @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
1808
+ * to undo. Typically, when a server response for a certain request is being handled,
1809
+ * you'd want to drop the prediction that was done for that request.
1810
+ */
1811
+ export function applyCanon(canonFunc, dropPredictions = []) {
1812
+ let resultPatch = new Map();
1813
+ for (let prediction of appliedPredictions)
1814
+ mergePatch(resultPatch, prediction, true);
1815
+ silentlyApplyPatch(resultPatch, true);
1816
+ for (let prediction of dropPredictions) {
1817
+ let pos = appliedPredictions.indexOf(prediction);
1818
+ if (pos >= 0)
1819
+ appliedPredictions.splice(pos, 1);
1820
+ }
1821
+ if (canonFunc)
1822
+ mergePatch(resultPatch, recordPatch(canonFunc));
1823
+ for (let idx = 0; idx < appliedPredictions.length; idx++) {
1824
+ if (silentlyApplyPatch(appliedPredictions[idx])) {
1825
+ mergePatch(resultPatch, appliedPredictions[idx]);
1826
+ }
1827
+ else {
1828
+ appliedPredictions.splice(idx, 1);
1829
+ idx--;
1830
+ }
1831
+ }
1832
+ emitPatch(resultPatch);
1833
+ }
1533
1834
  // @ts-ignore
1534
1835
  // istanbul ignore next
1535
1836
  if (!String.prototype.replaceAll)
@@ -1 +1 @@
1
- let e,t=[],i=new Set,n=!0,r=0;function s(e){if(!i.has(e)){if(r>42)throw new Error("Too many recursive updates from observes");t.length?e.queueOrder<t[t.length-1].queueOrder&&(n=!1):setTimeout(o,0),t.push(e),i.add(e)}}function o(){for(let e=0;e<t.length;){n||(t.splice(0,e),e=0,t.sort((e,t)=>e.queueOrder-t.queueOrder),n=!0);let s=t.length;for(;e<s&&n;e++){let n=t[e];i.delete(n),n.queueRun()}r++}t.length=0,r=0}function a(e){if("string"==typeof e)return e+"";{let t=function(e,t){let i="";for(;e>0;)i+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return i}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class l{constructor(e,t,i){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=i}findPrecedingNode(){let e=this.precedingSibling;for(;e;){if(e instanceof Node)return e;let t=e.findLastNode();if(t)return t;e=e.precedingSibling}}findLastNode(){return this.lastChild instanceof Node?this.lastChild:this.lastChild instanceof l?this.lastChild.findLastNode()||this.lastChild.findPrecedingNode():void 0}addNode(e){if(!this.parentElement)throw new O(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(;e!==t;){if(!e)return E(1);let t=e.previousSibling||void 0;this.parentElement.removeChild(e),e=t}}this.lastChild=void 0}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,i){s(this)}}class h extends l{constructor(e,t,i,n){super(e,t,i),this.renderer=n}queueRun(){e&&E(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;try{this.renderer()}catch(e){C(e)}e=t}}class d{constructor(e,t,i){this.scope=e,this.collection=t,this.triggerCount=i,this.count=t.getCount(),t.addObserver(f,this),e.cleaners.push(this)}onChange(e,t,i){void 0===t?!this.triggerCount&&--this.count||s(this.scope):void 0===i&&(!this.triggerCount&&this.count++||s(this.scope))}_clean(){this.collection.removeObserver(f,this)}}class c extends l{constructor(e,t,i,n,r,s){super(e,t,i),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=n,this.renderer=r,this.makeSortKey=s}onChange(e,t,i){void 0===i?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),s(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),s(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!e)return E(3);let t=e;this.collection.iterateIndexes(this),e=t}addChild(e){let t=new u(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return E(6);this.byIndex.delete(e),this.removeFromPosition(t),t.remove()}findPosition(e){let t=this.byPosition,i=0,n=t.length;if(!n||e>t[n-1].sortStr)return n;for(;i<n;){let r=i+n>>1;t[r].sortStr<e?i=r+1:n=r}return i}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e),e.precedingSibling=t>0?this.byPosition[t-1]:this.precedingSibling,t+1<this.byPosition.length?this.byPosition[t+1].precedingSibling=e:this.lastChild=e}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e)return this.byPosition.splice(t,1),void(t<this.byPosition.length?this.byPosition[t].precedingSibling=t>0?this.byPosition[t-1]:this.precedingSibling:this.lastChild=this.byPosition.length?this.byPosition[this.byPosition.length-1]:void 0);if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return E(5)}}}class u extends l{constructor(e,t,i,n,r){super(e,t,i),this.sortStr="",this.parent=n,this.itemIndex=r}queueRun(){e&&E(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;let i,n=new Store(this.parent.collection,this.itemIndex);try{i=this.parent.makeSortKey(n)}catch(e){C(e)}let r=this.sortStr,s=null==i?"":(o=i)instanceof Array?o.map(a).join(""):a(o);var o;if(""!==r&&r!==s&&this.parent.removeFromPosition(this),this.sortStr=s,""!==s){s!==r&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){C(e)}}e=t}}const f={};class p{constructor(){this.observers=new Map}addObserver(e,t){t=t;let i=this.observers.get(e);if(i){if(i.has(t))return!1;i.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,t,i){let n=this.observers.get(e);n&&n.forEach(n=>n.onChange(e,t,i)),n=this.observers.get(f),n&&n.forEach(n=>n.onChange(e,t,i))}_clean(e){this.removeObserver(f,e)}setIndex(e,t,i){const n=this.rawGet(e);if(!(n instanceof p)||t instanceof Store||!n.merge(t,i)){let i=x(t);i!==n&&(this.rawSet(e,i),this.emitChange(e,i,n))}}}class g extends p{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=[];for(let e=0;e<this.data.length;e++){let n=this.data[e];i.push(n instanceof p?t?n.getRecursive(t-1):new Store(this,e):n)}return i}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let i=0;i<e.length;i++)this.setIndex(i,e[i],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class v extends p{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=new Map;return this.data.forEach((e,n)=>{i.set(n,e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e)}),i}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,i)=>{this.setIndex(i,e,t)}),t&&this.data.forEach((t,i)=>{e.has(i)||this.setIndex(i,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,i)=>{e.addChild(i)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class y extends v{getType(){return"object"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i={};return this.data.forEach((e,n)=>{i[n]=e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e}),i}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let i in e)this.setIndex(i,e[i],t);return t&&this.data.forEach((t,i)=>{e.hasOwnProperty(i)||this.setIndex(i,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new g,this.idx=0,void 0!==e&&this.collection.rawSet(0,x(e));else{if(!(e instanceof p))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let i=typeof e;return"object"===i&&(e instanceof Map?i="map":e instanceof Array&&(i="array")),this.query({type:i,defaultValue:e,path:t})}query(t){if(t.peek&&e){let i=e;e=void 0;let n=this.query(t);return e=i,n}let i=(t.path&&t.path.length?this.ref(...t.path):this)._observe();if(t.type&&(void 0!==i||void 0===t.defaultValue)){let e=i instanceof p?i.getType():null===i?"null":typeof i;if(e!==t.type)throw new TypeError(`Expecting ${t.type} but got ${e}`)}return i instanceof p?i.getRecursive(null==t.depth?-1:t.depth-1):void 0===i?t.defaultValue:i}isEmpty(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return!new d(e,i,!1).count}return!i.getCount()}if(void 0===i)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(i))}count(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return new d(e,i,!0).count}return i.getCount()}if(void 0===i)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(i))}getType(...e){let t=this.ref(...e)._observe();return t instanceof p?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!0)}merge(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),i=this.makeRef(...e),n=i.collection.rawGet(i.idx);if(void 0===n)n=new g,i.collection.setIndex(i.idx,n,!0);else if(!(n instanceof g))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=x(t),s=n.data.length;return n.data.push(r),n.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let i=0;i<e.length;i++){let n=t._observe();if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);return new m}t=new Store(n,n.normalizeIndex(e[i]))}return t}makeRef(...e){let t=this;for(let i=0;i<e.length;i++){let n=t.collection.rawGet(t.idx);if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);n=new y,t.collection.rawSet(t.idx,n),t.collection.emitChange(t.idx,n,void 0)}t=new Store(n,n.normalizeIndex(e[i]))}return t}_observe(){return e&&this.collection.addObserver(this.idx,e)&&e.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...t){let i=S,n=t.pop();if("function"!=typeof t[t.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(i=n),n=t.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!e)throw new O(!1);let r=this.ref(...t)._observe();if(r instanceof p){let t=new c(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,r,n,i);r.addObserver(f,t),e.cleaners.push(t),e.lastChild=t,t.renderInitial()}else if(void 0!==r)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(i=>{let n=e(i);if(void 0!==n){let e=i.index();t.set(e,n),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(i=>{let n,r=e(i);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);n=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,i)=>{t.set(i,e)}),n=Array.from(r.keys())}n.length&&clean(()=>{for(let e of n)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class m extends Store{isDetached(){return!0}}export function node(t="",...i){if(!e)throw new O(!0);let n;if(t instanceof Element)n=t;else{let e,i=t.indexOf(".");i>=0&&(e=t.substr(i+1),t=t.substr(0,i)),n=document.createElement(t||"div"),e&&(n.className=e.replaceAll("."," "))}e.addNode(n);for(let t of i){let i=typeof t;if("function"===i){let i=new h(n,void 0,e.queueOrder+1,t);i.update(),e.cleaners.push(i)}else if("string"===i||"number"===i)n.textContent=t;else if("object"===i&&t&&t.constructor===Object)for(let e in t)b(n,e,t[e]);else if(t instanceof Store)w(n,t);else if(null!=t)throw new Error("Unexpected argument "+JSON.stringify(t))}}export function html(t){if(!e||!e.parentElement)throw new O(!0);let i=document.createElement(e.parentElement.tagName);for(i.innerHTML=""+t;i.firstChild;)e.addNode(i.firstChild)}function w(e,t){let i,n,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),i=t=>e.checked=t,n=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),i=t=>e.checked=t===e.value,n=()=>{e.checked&&t.set(e.value)}):(void 0===s&&t.set(e.value),i=t=>{e.value!==t&&(e.value=t)},n=()=>t.set(e.value)),observe(()=>{i(t.get())}),e.addEventListener("input",n),clean(()=>{e.removeEventListener("input",n)})}export function text(t){if(!e)throw new O(!0);null!=t&&e.addNode(document.createTextNode(t))}export function prop(t,i){if(!e||!e.parentElement)throw new O(!0);if("object"==typeof t)for(let i in t)b(e.parentElement,i,t[i]);else b(e.parentElement,t,i)}export function getParentElement(){if(!e||!e.parentElement)throw new O(!0);return e.parentElement}export function clean(t){if(!e)throw new O(!1);e.cleaners.push({_clean:t})}export function observe(e){mount(void 0,e)}export function mount(t,i){let n;t||!e?n=new h(t,void 0,0,i):(n=new h(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,i),e.lastChild=n),n.update(),e&&e.cleaners.push(n)}export function peek(t){let i=e;e=void 0;try{return t()}finally{e=i}}function b(e,t,i){if("class"!==t&&"className"!==t||"object"!=typeof i)"value"===t||"className"===t||"selectedIndex"===t||!0===i||!1===i?e[t]=i:"function"==typeof i?(e.addEventListener(t,i),clean(()=>e.removeEventListener(t,i))):"style"===t&&"object"==typeof i?Object.assign(e.style,i):"text"===t?e.textContent=i:e.setAttribute(t,i);else for(let t in i)i[t]?e.classList.add(t):e.classList.remove(t)}function x(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new v;return e.forEach((e,i)=>{let n=x(e);void 0!==n&&t.rawSet(i,n)}),t}if(e instanceof Array){let t=new g;for(let i=0;i<e.length;i++){let n=x(e[i]);void 0!==n&&t.rawSet(i,n)}return t}if(e.constructor===Object){let t=new y;for(let i in e){let n=x(e[i]);void 0!==n&&t.rawSet(i,n)}return t}return e}return e}function S(e){return e.index()}function E(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function C(e){setTimeout(()=>{throw e},0)}class O extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
1
+ let e,t,n=[],i=new Set,r=!0,s=0;function o(e){if(!i.has(e)){if(s>42)throw new Error("Too many recursive updates from observes");n.length?e.queueOrder<n[n.length-1].queueOrder&&(r=!1):setTimeout(l,0),n.push(e),i.add(e)}}function l(){for(b=!0,e=0;e<n.length;){r||(n.splice(0,e),e=0,n.sort((e,t)=>e.queueOrder-t.queueOrder),r=!0);let t=n.length;for(;e<t&&r;e++){let t=n[e];i.delete(t),t.queueRun()}s++}n.length=0,e=void 0,s=0,b=!1}let a;export function scheduleDomReader(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?n[e].queueOrder+1&-2:1e3,queueRun:t})}export function scheduleDomWriter(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?1|n[e].queueOrder:1001,queueRun:t})}function h(e){if("string"==typeof e)return e+"";{let t=function(e,t){let n="";for(;e>0;)n+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return n}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class d{constructor(e,t,n){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=n}findPrecedingNode(e){let t,n=this;for(;(t=n.precedingSibling)&&t!==e;){if(t instanceof Node)return t;let e=t.findLastNode();if(e)return e;n=t}}findLastNode(){if(this.lastChild)return this.lastChild instanceof Node?this.lastChild:this.lastChild.findLastNode()||this.lastChild.findPrecedingNode(this.precedingSibling)}addNode(e){if(!this.parentElement)throw new k(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(t=t?t.nextSibling:this.parentElement.firstChild,this.lastChild=void 0;;){if(!t)return N(1);const n=t;t=n.nextSibling||void 0;let i=S.get(n);if(i&&n instanceof Element?!0!==i&&("function"==typeof i?i(n):E(n,i),S.set(n,!0)):this.parentElement.removeChild(n),n===e)break}}}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,n){o(this)}}class u extends d{constructor(e,t,n,i){super(e,t,n),this.renderer=i}queueRun(){a&&N(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;try{this.renderer()}catch(e){P(e)}a=e}}class c{constructor(e,t,n){this.scope=e,this.collection=t,this.triggerCount=n,this.count=t.getCount(),t.addObserver(g,this),e.cleaners.push(this)}onChange(e,t,n){void 0===t?!this.triggerCount&&--this.count||o(this.scope):void 0===n&&(!this.triggerCount&&this.count++||o(this.scope))}_clean(){this.collection.removeObserver(g,this)}}class f extends d{constructor(e,t,n,i,r,s){super(e,t,n),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=i,this.renderer=r,this.makeSortKey=s}onChange(e,t,n){void 0===n?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),o(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),o(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!a)return N(3);let e=a;this.collection.iterateIndexes(this),a=e}addChild(e){let t=new p(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return N(6);t.remove(),this.byIndex.delete(e),this.removeFromPosition(t)}findPosition(e){let t=this.byPosition,n=0,i=t.length;if(!i||e>t[i-1].sortStr)return i;for(;n<i;){let r=n+i>>1;t[r].sortStr<e?n=r+1:i=r}return n}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e);let n=this.byPosition[t+1];n?(e.precedingSibling=n.precedingSibling,n.precedingSibling=e):(e.precedingSibling=this.lastChild||this.precedingSibling,this.lastChild=e)}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e){if(this.byPosition.splice(t,1),t<this.byPosition.length){let n=this.byPosition[t];if(!n)return N(8);if(n.precedingSibling!==e)return N(13);n.precedingSibling=e.precedingSibling}else{if(e!==this.lastChild)return N(12);this.lastChild=e.precedingSibling===this.precedingSibling?void 0:e.precedingSibling}return}if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return N(5)}}}class p extends d{constructor(e,t,n,i,r){super(e,t,n),this.sortStr="",this.parent=i,this.itemIndex=r}queueRun(){a&&N(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;let t,n=new Store(this.parent.collection,this.itemIndex);try{t=this.parent.makeSortKey(n)}catch(e){P(e)}let i=this.sortStr,r=null==t?"":(s=t)instanceof Array?s.map(h).join(""):h(s);var s;if(""!==i&&i!==r&&this.parent.removeFromPosition(this),this.sortStr=r,""!==r){r!==i&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){P(e)}}a=e}}const g={};class m{constructor(){this.observers=new Map}addObserver(e,t){t=t;let n=this.observers.get(e);if(n){if(n.has(t))return!1;n.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,n,i){if(t)M(t,this,e,n,i);else{let t=this.observers.get(e);t&&t.forEach(t=>t.onChange(e,n,i)),t=this.observers.get(g),t&&t.forEach(t=>t.onChange(e,n,i))}}_clean(e){this.removeObserver(g,e)}setIndex(e,t,n){const i=this.rawGet(e);if(!(i instanceof m)||t instanceof Store||!i.merge(t,n)){let n=I(t);n!==i&&(this.rawSet(e,n),this.emitChange(e,n,i))}}}class v extends m{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=[];for(let n=0;n<this.data.length;n++){let i=this.data[n];t.push(i instanceof m?e?i.getRecursive(e-1):new Store(this,n):i)}return t}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let n=0;n<e.length;n++)this.setIndex(n,e[n],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class y extends m{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=new Map;return this.data.forEach((n,i)=>{t.set(i,n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n)}),t}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,n)=>{this.setIndex(n,e,t)}),t&&this.data.forEach((t,n)=>{e.has(n)||this.setIndex(n,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,n)=>{e.addChild(n)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class w extends y{getType(){return"object"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t={};return this.data.forEach((n,i)=>{t[i]=n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n}),t}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let n in e)this.setIndex(n,e[n],t);return t&&this.data.forEach((t,n)=>{e.hasOwnProperty(n)||this.setIndex(n,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new v,this.idx=0,void 0!==e&&this.collection.rawSet(0,I(e));else{if(!(e instanceof m))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let n=typeof e;return"object"===n&&(e instanceof Map?n="map":e instanceof Array&&(n="array")),this.query({type:n,defaultValue:e,path:t})}query(e){if(e.peek&&a){let t=a;a=void 0;let n=this.query(e);return a=t,n}let t=(e.path&&e.path.length?this.ref(...e.path):this)._observe();if(e.type&&(void 0!==t||void 0===e.defaultValue)){let n=t instanceof m?t.getType():null===t?"null":typeof t;if(n!==e.type)throw new TypeError(`Expecting ${e.type} but got ${n}`)}return t instanceof m?t.getRecursive(null==e.depth?-1:e.depth-1):void 0===t?e.defaultValue:t}isEmpty(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return!new c(a,t,!1).count}return!t.getCount()}if(void 0===t)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(t))}count(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return new c(a,t,!0).count}return t.getCount()}if(void 0===t)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(t))}getType(...e){let t=this.ref(...e)._observe();return t instanceof m?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!0)}merge(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),n=this.makeRef(...e),i=n.collection.rawGet(n.idx);if(void 0===i)i=new v,n.collection.setIndex(n.idx,i,!0);else if(!(i instanceof v))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=I(t),s=i.data.length;return i.data.push(r),i.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let n=0;n<e.length;n++){let i=t._observe();if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);return new x}t=new Store(i,i.normalizeIndex(e[n]))}return t}makeRef(...e){let t=this;for(let n=0;n<e.length;n++){let i=t.collection.rawGet(t.idx);if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);i=new w,t.collection.rawSet(t.idx,i),t.collection.emitChange(t.idx,i,void 0)}t=new Store(i,i.normalizeIndex(e[n]))}return t}_observe(){return a&&this.collection.addObserver(this.idx,a)&&a.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...e){let t=q,n=e.pop();if("function"!=typeof e[e.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(t=n),n=e.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!a)throw new k(!1);let i=this.ref(...e)._observe();if(i instanceof m){let e=new f(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,i,n,t);i.addObserver(g,e),a.cleaners.push(e),a.lastChild=e,e.renderInitial()}else if(void 0!==i)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(n=>{let i=e(n);if(void 0!==i){let e=n.index();t.set(e,i),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(n=>{let i,r=e(n);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);i=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,n)=>{t.set(n,e)}),i=[...r.keys()]}i.length&&clean(()=>{for(let e of i)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class x extends Store{isDetached(){return!0}}let b=!1,S=new WeakMap;function E(e,t){e.classList.add(t),setTimeout(()=>e.remove(),2e3)}export function node(e="",...t){if(!a)throw new k(!0);let n;if(e instanceof Element)n=e;else{let t,i=e.indexOf(".");i>=0&&(t=e.substr(i+1),e=e.substr(0,i)),n=document.createElement(e||"div"),t&&(n.className=t.replaceAll("."," "))}a.addNode(n);for(let e of t){let t=typeof e;if("function"===t){let t=new u(n,void 0,a.queueOrder+1,e);b?(b=!1,t.update(),b=!0):t.update(),a.cleaners.push(t)}else if("string"===t||"number"===t)n.textContent=e;else if("object"===t&&e&&e.constructor===Object)for(let t in e)O(n,t,e[t]);else if(e instanceof Store)C(n,e);else if(null!=e)throw new Error("Unexpected argument "+JSON.stringify(e))}}export function html(e){if(!a||!a.parentElement)throw new k(!0);let t=document.createElement(a.parentElement.tagName);for(t.innerHTML=""+e;t.firstChild;)a.addNode(t.firstChild)}function C(e,t){let n,i,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),n=t=>e.checked=t,i=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),n=t=>e.checked=t===e.value,i=()=>{e.checked&&t.set(e.value)}):(i=()=>t.set("number"===r||"range"===r?""===e.value?null:+e.value:e.value),void 0===s&&i(),n=t=>{e.value!==t&&(e.value=t)}),observe(()=>{n(t.get())}),e.addEventListener("input",i),clean(()=>{e.removeEventListener("input",i)})}export function text(e){if(!a)throw new k(!0);null!=e&&a.addNode(document.createTextNode(e))}export function prop(e,t){if(!a||!a.parentElement)throw new k(!0);if("object"==typeof e)for(let t in e)O(a.parentElement,t,e[t]);else O(a.parentElement,e,t)}export function getParentElement(){if(!a||!a.parentElement)throw new k(!0);return a.parentElement}export function clean(e){if(!a)throw new k(!1);a.cleaners.push({_clean:e})}export function observe(e){mount(void 0,e)}export function mount(e,t){let n;e||!a?n=new u(e,void 0,0,t):(n=new u(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,t),a.lastChild=n),n.update(),a&&a.cleaners.push(n)}export function peek(e){let t=a;a=void 0;try{return e()}finally{a=t}}function O(e,t,n){if("create"===t)b&&("function"==typeof n?n(e):(e.classList.add(n),setTimeout((function(){e.classList.remove(n)}),0)));else if("destroy"===t)S.set(e,n);else if("function"==typeof n)e.addEventListener(t,n),clean(()=>e.removeEventListener(t,n));else if("value"===t||"className"===t||"selectedIndex"===t||!0===n||!1===n)e[t]=n;else if("text"===t)e.textContent=n;else if("class"!==t&&"className"!==t||"object"!=typeof n)"style"===t&&"object"==typeof n?Object.assign(e.style,n):e.setAttribute(t,n);else for(let t in n)n[t]?e.classList.add(t):e.classList.remove(t)}function I(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new y;return e.forEach((e,n)=>{let i=I(e);void 0!==i&&t.rawSet(n,i)}),t}if(e instanceof Array){let t=new v;for(let n=0;n<e.length;n++){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}if(e.constructor===Object){let t=new w;for(let n in e){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}return e}return e}function q(e){return e.index()}function N(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function P(e){setTimeout(()=>{throw e},0)}class k extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}function R(e){const t=e.parentElement?getComputedStyle(e.parentElement):{};return"flex"===t.display&&(t.flexDirection||"").startsWith("row")?{marginLeft:`-${e.offsetWidth/2}px`,marginRight:`-${e.offsetWidth/2}px`,transform:"scaleX(0)"}:{marginBottom:`-${e.offsetHeight/2}px`,marginTop:`-${e.offsetHeight/2}px`,transform:"scaleY(0)"}}export function grow(e){scheduleDomReader(()=>{let t=R(e);scheduleDomWriter(()=>{Object.assign(e.style,t),scheduleDomReader(()=>{e.offsetHeight,scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out";for(let n in t)e.style[n]="";setTimeout(()=>{e.style.transition=""},400)})})})})}export function shrink(e){scheduleDomReader(()=>{const t=R(e);scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out",Object.assign(e.style,t),setTimeout(()=>e.remove(),400)})})}function j(e){if(t)throw new Error("already recording a patch");t=new Map;try{e()}catch(e){throw t=void 0,e}const n=t;return t=void 0,n}function M(e,t,n,i,r){let s=e.get(t);null==s&&(s=new Map,e.set(t,s));let o=s.get(n);s.set(n,[i,null==o?r:o[1]])}function T(e){for(let[t,n]of e)for(let[e,[i,r]]of n)t.emitChange(e,i,r)}function D(e,t,n=!1){for(let[i,r]of t)for(let[t,[s,o]]of r)M(e,i,t,n?o:s,n?s:o)}function $(e,t=!1){for(let[n,i]of e)for(let[e,[r,s]]of i){let i=n.rawGet(e);if(i!==s){if(!t)return!1;P(new Error(`Applying invalid patch: data ${i} is unequal to expected old data ${s} for index ${e}`))}}for(let[t,n]of e)for(let[e,[i,r]]of n)t.rawSet(e,i);return!0}const _=[];export function applyPrediction(e){let t=j(e);return _.push(t),T(t),t}export function applyCanon(e,t=[]){let n=new Map;for(let e of _)D(n,e,!0);$(n,!0);for(let e of t){let t=_.indexOf(e);t>=0&&_.splice(t,1)}e&&D(n,j(e));for(let e=0;e<_.length;e++)$(_[e])?D(n,_[e]):(_.splice(e,1),e--);T(n)}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "aberdeen",
3
- "version": "0.0.16",
3
+ "version": "0.1.0",
4
4
  "description": "A TypeScript/JavaScript library for quickly building performant declarative user interfaces without the use of a virtual DOM.",
5
5
  "main": "dist/aberdeen.js",
6
6
  "types": "dist/aberdeen.d.js",
7
7
  "scripts": {
8
8
  "build": "$npm_execpath run build-dist && $npm_execpath run build-docs",
9
- "build-dist": "tsc && terser --module --compress --mangle -- dist/aberdeen.js > dist/aberdeen.min.js",
9
+ "build-dist": "rm -f dist/aberdeen.js && tsc && chmod a-w dist/aberdeen.js && terser --module --compress --mangle -- dist/aberdeen.js > dist/aberdeen.min.js",
10
10
  "build-docs": "typedoc --excludePrivate --excludeInternal src/aberdeen.ts",
11
- "build-test": "[ tests/build/aberdeen.js -nt src/aberdeen.ts ] || tsc -t ES2015 -m commonjs --outdir tests/build src/aberdeen.ts",
11
+ "build-test": "[ tests/build/aberdeen.js -nt src/aberdeen.ts ] || ( rm -f tests/build/aberdeen.js && tsc -t ES2015 -m commonjs --outdir tests/build src/aberdeen.ts && chmod a-w tests/build/aberdeen.js )",
12
12
  "coverage": "$npm_execpath run build-test && nyc mocha --file tests/_init.js tests/[^_]*.js",
13
13
  "test": "$npm_execpath run build-test && mocha --file tests/_init.js tests/[^_]*.js",
14
14
  "prepack": "$npm_execpath run test && $npm_execpath run build"