aberdeen 0.0.16 → 0.0.17

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,4 @@ 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
@@ -2,6 +2,21 @@ interface QueueRunner {
2
2
  queueOrder: number;
3
3
  queueRun(): void;
4
4
  }
5
+ /**
6
+ * Schedule a function to be executed by Aberdeen's internal task queue. This
7
+ * can be useful to batch together DOM layout read operations and DOM write
8
+ * operations, so that we're not forcing the browser to do more layout calculations
9
+ * than needed. Also, unlike setTimeout or requestAnimationFrame, this doesn't
10
+ * give the browser the chance to render partial DOM states to the screen (which
11
+ * would be seen as glitches/flashes).
12
+ * @param func The function to be called soon.
13
+ * @param order Higher mean later. Defaults to 0, which would still be *after* all
14
+ * node/observe redraws have been handled.
15
+ *
16
+ * **EXPERIMENTAL:** There's a good chance this function will be replaced by
17
+ * something that explicitly addresses DOM layout reads and DOM writes.
18
+ */
19
+ export declare function scheduleTask(func: () => void, order?: number): void;
5
20
  type SortKeyType = number | string | Array<number | string>;
6
21
  interface Observer {
7
22
  onChange(index: any, newData: DatumType, oldData: DatumType): void;
@@ -16,7 +31,7 @@ declare abstract class Scope implements QueueRunner, Observer {
16
31
  }>;
17
32
  isDead: boolean;
18
33
  constructor(parentElement: Element | undefined, precedingSibling: Node | Scope | undefined, queueOrder: number);
19
- findPrecedingNode(): Node | undefined;
34
+ findPrecedingNode(stopAt?: Scope | Node | undefined): Node | undefined;
20
35
  findLastNode(): Node | undefined;
21
36
  addNode(node: Node): void;
22
37
  remove(): void;
@@ -102,7 +117,7 @@ declare class ObsMap extends ObsCollection {
102
117
  *
103
118
  * Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
104
119
  * `Array`, `object` and `Map`. The latter three will always have `Store` objects as
105
- * values, creating a tree of `Store`s.
120
+ * values, creating a tree of `Store`-objects.
106
121
  */
107
122
  export declare class Store {
108
123
  private collection;
@@ -110,7 +125,7 @@ export declare class Store {
110
125
  /**
111
126
  * Create a new store with the given `value` as its value. Defaults to `undefined` if no value is given.
112
127
  * 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.)
128
+ * `Store`s. (Calling {@link Store.get} on the store will recreate the original data strucure, though.)
114
129
  *
115
130
  * @example
116
131
  * ```
@@ -153,46 +168,46 @@ export declare class Store {
153
168
  */
154
169
  get(...path: any[]): any;
155
170
  /**
156
- * Like [[`get`]], but doesn't subscribe to changes.
171
+ * Like {@link Store.get}, but doesn't subscribe to changes.
157
172
  */
158
173
  peek(...path: any[]): any;
159
174
  /**
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.
175
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
176
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
162
177
  */
163
178
  getNumber(...path: any[]): number;
164
179
  /**
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.
180
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
181
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
167
182
  */
168
183
  getString(...path: any[]): string;
169
184
  /**
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.
185
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
186
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
172
187
  */
173
188
  getBoolean(...path: any[]): boolean;
174
189
  /**
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.
190
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
191
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
177
192
  */
178
193
  getFunction(...path: any[]): (Function);
179
194
  /**
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.
195
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
196
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
182
197
  */
183
198
  getArray(...path: any[]): any[];
184
199
  /**
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.
200
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
201
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
187
202
  */
188
203
  getObject(...path: any[]): object;
189
204
  /**
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.
205
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
206
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
192
207
  */
193
208
  getMap(...path: any[]): Map<any, any>;
194
209
  /**
195
- * Like [[`get`]], but the first parameter is the default value (returned when the Store
210
+ * Like {@link Store.get}, but the first parameter is the default value (returned when the Store
196
211
  * contains `undefined`). This default value is also used to determine the expected type,
197
212
  * and to throw otherwise.
198
213
  *
@@ -206,7 +221,7 @@ export declare class Store {
206
221
  */
207
222
  getOr<T>(defaultValue: T, ...path: any[]): T;
208
223
  /** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
209
- * form of the [[`get`]] and [[`peek`]] methods.
224
+ * form of the {@link Store.get} and {@link Store.peek} methods.
210
225
  *
211
226
  * @returns The resulting value, or `undefined` if the `path` does not exist.
212
227
  */
@@ -263,15 +278,15 @@ export declare class Store {
263
278
  getType(...path: any[]): string;
264
279
  /**
265
280
  * Sets the value to the last given argument. Any earlier argument are a Store-path that is first
266
- * resolved/created using [[`makeRef`]].
281
+ * resolved/created using {@link Store.makeRef}.
267
282
  *
268
283
  * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
269
284
  * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
270
285
  * be created, so that changes made through one `Store` will be reflected through the other. Be
271
286
  * 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.)
287
+ * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
273
288
  *
274
- * If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
289
+ * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
275
290
  *
276
291
  *
277
292
  * @example
@@ -340,7 +355,7 @@ export declare class Store {
340
355
  */
341
356
  push(...pathAndValue: any[]): number;
342
357
  /**
343
- * [[`peek`]] the current value, pass it through `func`, and [[`set`]] the resulting
358
+ * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
344
359
  * value.
345
360
  * @param func The function transforming the value.
346
361
  */
@@ -350,7 +365,7 @@ export declare class Store {
350
365
  * subscribing to every level.
351
366
  * In case `undefined` is encountered while resolving the path, a newly
352
367
  * created `Store` containing `undefined` is returned. In that case, the
353
- * `Store`'s [[`isDetached`]] method will return `true`.
368
+ * `Store`'s {@link Store.isDetached} method will return `true`.
354
369
  * In case something other than a collection is encountered, an error is thrown.
355
370
  */
356
371
  ref(...path: any[]): Store;
@@ -375,12 +390,12 @@ export declare class Store {
375
390
  * ```
376
391
  */
377
392
  makeRef(...path: any[]): Store;
378
- /** @Internal */
393
+ /** @internal */
379
394
  _observe(): DatumType;
380
395
  /**
381
396
  * Iterate the specified collection (Array, Map or object), running the given code block for each item.
382
397
  * 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.
398
+ * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
384
399
  *
385
400
  *
386
401
  *
@@ -420,7 +435,7 @@ export declare class Store {
420
435
  */
421
436
  multiMap(func: (store: Store) => any): Store;
422
437
  /**
423
- * @returns Returns `true` when the `Store` was created by [[`ref`]]ing a path that
438
+ * @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
424
439
  * does not exist.
425
440
  */
426
441
  isDetached(): boolean;
@@ -431,7 +446,7 @@ export declare class Store {
431
446
  * @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
447
  * @param rest - The other arguments are flexible and interpreted based on their types:
433
448
  * - `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.
449
+ * - `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
450
  * - `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
451
  * - `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
452
  * @example
@@ -457,8 +472,63 @@ export declare function text(text: string): void;
457
472
  * without recreating the element itself. Also, code can be more readable this way.
458
473
  * Note that when a nested `observe()` is used, properties set this way do NOT
459
474
  * automatically revert to their previous values.
475
+ *
476
+ * Here's how properties are handled:
477
+ * - If `name` is `"create"`, `value` should be either a function that gets
478
+ * called with the element as its only argument immediately after creation,
479
+ * or a string being the name of a CSS class that gets added immediately
480
+ * after element creation, and removed shortly afterwards. This allows for
481
+ * reveal animations. However, this is intentionally *not* done
482
+ * for elements that are created as part of a larger (re)draw, to prevent
483
+ * all elements from individually animating on page creation.
484
+ * - If `name` is `"destroy"`, `value` should be a function that gets called
485
+ * with the element as its only argument, *instead of* the element being
486
+ * removed from the DOM (which the function will presumably need to do
487
+ * eventually). This can be used for a conceal animation.
488
+ * As a convenience, it's also possible to provide a string instead of
489
+ * a function, which will be added to the element as a CSS class, allowing
490
+ * for simple transitions. In this case, the DOM element in removed 2 seconds
491
+ * later (currently not configurable).
492
+ * Similar to `"create"` (and in this case doing anything else would make little
493
+ * sense), this only happens when the element being is the top-level element
494
+ * being removed from the DOM.
495
+ * - If `value` is a function, it is registered as an event handler for the
496
+ * `name` event.
497
+ * - If `name` is `"class"` or `"className"` and the `value` is an
498
+ * object, all keys of the object are either added or removed from `classList`,
499
+ * depending on whether `value` is true-like or false-like.
500
+ * - If `value` is a boolean *or* `name` is `"value"`, `"className"` or
501
+ * `"selectedIndex"`, it is set as a DOM element *property*.
502
+ * - If `name` is `"text"`, the `value` is set as the element's `textContent`.
503
+ * - If `name` is `"style"` and `value` is an object, each of its
504
+ * key/value pairs are assigned to the element's `.style`.
505
+ * - In other cases, the `value` is set as the `name` HTML *attribute*.
506
+ *
507
+ * @example
508
+ * ```
509
+ * node('input', () => {
510
+ * prop('type', 'password')
511
+ * prop('readOnly', true)
512
+ * prop('class', 'my-class')
513
+ * prop('class', {
514
+ * 'my-disabled-class': false,
515
+ * 'my-enabled-class': true,
516
+ * })
517
+ * prop({
518
+ * class: 'my-class',
519
+ * text: 'Here is something to read...',
520
+ * style: {
521
+ * backgroundColor: 'red',
522
+ * fontWeight: 'bold',
523
+ * },
524
+ * create: aberdeen.fadeIn,
525
+ * destroy: 'my-fade-out-class',
526
+ * click: myClickHandler,
527
+ * })
528
+ * })
529
+ * ```
460
530
  */
461
- export declare function prop(prop: string, value: any): void;
531
+ export declare function prop(name: string, value: any): void;
462
532
  export declare function prop(props: object): void;
463
533
  /**
464
534
  * Return the browser Element that `node()`s would be rendered to at this point.
@@ -497,7 +567,7 @@ export declare function clean(clean: (scope: Scope) => void): void;
497
567
  */
498
568
  export declare function observe(func: () => void): void;
499
569
  /**
500
- * Like [[`observe`]], but allow the function to create DOM elements using [[`node`]].
570
+ * Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
501
571
 
502
572
  * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
503
573
  * @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
@@ -512,7 +582,7 @@ export declare function observe(func: () => void): void;
512
582
  * })
513
583
  * ```
514
584
  *
515
- * An example nesting [[`observe`]] within `mount`:
585
+ * An example nesting {@link Store.observe} within `mount`:
516
586
  * ```
517
587
  * let selected = new Store(0)
518
588
  * let colors = new Store(new Map())
@@ -537,7 +607,7 @@ export declare function observe(func: () => void): void;
537
607
  * ```
538
608
  */
539
609
  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.
610
+ /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
541
611
  *
542
612
  * @param func Function to be executed immediately.
543
613
  * @returns Whatever `func()` returns.
@@ -559,4 +629,22 @@ export declare function mount(parentElement: Element | undefined, func: () => vo
559
629
  * for `count()` however.
560
630
  */
561
631
  export declare function peek<T>(func: () => T): T;
632
+ /** Do a grow transition for the given element. This is meant to be used as a
633
+ * handler for the `create` property.
634
+ *
635
+ * @param el The element to transition.
636
+ *
637
+ * The transition doesn't look great for table elements, and may have problems
638
+ * for other specific cases as well.
639
+ */
640
+ export declare function grow(el: HTMLElement): void;
641
+ /** Do a shrink transition for the given element, and remove it from the DOM
642
+ * afterwards. This is meant to be used as a handler for the `destroy` property.
643
+ *
644
+ * @param el The element to transition and remove.
645
+ *
646
+ * The transition doesn't look great for table elements, and may have problems
647
+ * for other specific cases as well.
648
+ */
649
+ export declare function shrink(el: HTMLElement): void;
562
650
  export {};
package/dist/aberdeen.js CHANGED
@@ -18,6 +18,7 @@ function queue(runner) {
18
18
  queueSet.add(runner);
19
19
  }
20
20
  function runQueue() {
21
+ onCreateEnabled = true;
21
22
  for (let index = 0; index < queueArray.length;) {
22
23
  if (!queueOrdered) {
23
24
  queueArray.splice(0, index);
@@ -36,6 +37,24 @@ function runQueue() {
36
37
  }
37
38
  queueArray.length = 0;
38
39
  runQueueDepth = 0;
40
+ onCreateEnabled = false;
41
+ }
42
+ /**
43
+ * Schedule a function to be executed by Aberdeen's internal task queue. This
44
+ * can be useful to batch together DOM layout read operations and DOM write
45
+ * operations, so that we're not forcing the browser to do more layout calculations
46
+ * than needed. Also, unlike setTimeout or requestAnimationFrame, this doesn't
47
+ * give the browser the chance to render partial DOM states to the screen (which
48
+ * would be seen as glitches/flashes).
49
+ * @param func The function to be called soon.
50
+ * @param order Higher mean later. Defaults to 0, which would still be *after* all
51
+ * node/observe redraws have been handled.
52
+ *
53
+ * **EXPERIMENTAL:** There's a good chance this function will be replaced by
54
+ * something that explicitly addresses DOM layout reads and DOM writes.
55
+ */
56
+ export function scheduleTask(func, order = 0) {
57
+ queue({ queueOrder: 1000 + order, queueRun: func });
39
58
  }
40
59
  /**
41
60
  * Given an integer number, a string or an array of these, this function returns a string that can be used
@@ -96,24 +115,27 @@ class Scope {
96
115
  this.precedingSibling = precedingSibling;
97
116
  this.queueOrder = queueOrder;
98
117
  }
99
- // Get a reference to the last Node preceding
100
- findPrecedingNode() {
101
- let pre = this.precedingSibling;
102
- while (pre) {
118
+ // Get a reference to the last Node preceding this Scope, or undefined if there is none
119
+ findPrecedingNode(stopAt = undefined) {
120
+ let cur = this;
121
+ let pre;
122
+ while ((pre = cur.precedingSibling) && pre !== stopAt) {
103
123
  if (pre instanceof Node)
104
124
  return pre;
105
125
  let node = pre.findLastNode();
106
126
  if (node)
107
127
  return node;
108
- pre = pre.precedingSibling;
128
+ cur = pre;
109
129
  }
110
130
  }
111
131
  // Get a reference to the last Node within this scope and parentElement
112
132
  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();
133
+ if (this.lastChild) {
134
+ if (this.lastChild instanceof Node)
135
+ return this.lastChild;
136
+ else
137
+ return this.lastChild.findLastNode() || this.lastChild.findPrecedingNode(this.precedingSibling);
138
+ }
117
139
  }
118
140
  addNode(node) {
119
141
  if (!this.parentElement)
@@ -127,20 +149,37 @@ class Scope {
127
149
  let lastNode = this.findLastNode();
128
150
  if (lastNode) {
129
151
  // 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) {
152
+ let nextNode = this.findPrecedingNode();
153
+ nextNode = (nextNode ? nextNode.nextSibling : this.parentElement.firstChild);
154
+ this.lastChild = undefined;
155
+ // Keep removing DOM nodes starting at our first node, until we encounter the last node
156
+ while (true) {
134
157
  /* istanbul ignore next */
135
- if (!lastNode) {
158
+ if (!nextNode)
136
159
  return internalError(1);
160
+ const node = nextNode;
161
+ nextNode = node.nextSibling || undefined;
162
+ let onDestroy = onDestroyMap.get(node);
163
+ if (onDestroy && node instanceof Element) {
164
+ if (onDestroy !== true) {
165
+ if (typeof onDestroy === 'function') {
166
+ onDestroy(node);
167
+ }
168
+ else {
169
+ destroyWithClass(node, onDestroy);
170
+ }
171
+ // This causes the element to be ignored from this function from now on:
172
+ onDestroyMap.set(node, true);
173
+ }
174
+ // Ignore the deleting element
137
175
  }
138
- let nextLastNode = lastNode.previousSibling || undefined;
139
- this.parentElement.removeChild(lastNode);
140
- lastNode = nextLastNode;
176
+ else {
177
+ this.parentElement.removeChild(node);
178
+ }
179
+ if (node === lastNode)
180
+ break;
141
181
  }
142
182
  }
143
- this.lastChild = undefined;
144
183
  }
145
184
  // run cleaners
146
185
  this._clean();
@@ -223,6 +262,9 @@ class OnEachScope extends Scope {
223
262
  this.renderer = renderer;
224
263
  this.makeSortKey = makeSortKey;
225
264
  }
265
+ // toString(): string {
266
+ // return `OnEachScope(collection=${this.collection})`
267
+ // }
226
268
  onChange(index, newData, oldData) {
227
269
  if (oldData === undefined) {
228
270
  if (this.removedIndexes.has(index)) {
@@ -288,9 +330,9 @@ class OnEachScope extends Scope {
288
330
  if (!scope) {
289
331
  return internalError(6);
290
332
  }
333
+ scope.remove();
291
334
  this.byIndex.delete(itemIndex);
292
335
  this.removeFromPosition(scope);
293
- scope.remove();
294
336
  }
295
337
  findPosition(sortStr) {
296
338
  // In case of duplicate `sortStr`s, this will return the first match.
@@ -315,12 +357,14 @@ class OnEachScope extends Scope {
315
357
  let pos = this.findPosition(child.sortStr);
316
358
  this.byPosition.splice(pos, 0, child);
317
359
  // 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;
360
+ // and for the next sibling.
361
+ let nextSibling = this.byPosition[pos + 1];
362
+ if (nextSibling) {
363
+ child.precedingSibling = nextSibling.precedingSibling;
364
+ nextSibling.precedingSibling = child;
322
365
  }
323
366
  else {
367
+ child.precedingSibling = this.lastChild || this.precedingSibling;
324
368
  this.lastChild = child;
325
369
  }
326
370
  }
@@ -333,10 +377,20 @@ class OnEachScope extends Scope {
333
377
  // Yep, this is the right scope
334
378
  this.byPosition.splice(pos, 1);
335
379
  if (pos < this.byPosition.length) {
336
- this.byPosition[pos].precedingSibling = pos > 0 ? this.byPosition[pos - 1] : this.precedingSibling;
380
+ let nextSibling = this.byPosition[pos];
381
+ /* istanbul ignore next */
382
+ if (!nextSibling)
383
+ return internalError(8);
384
+ /* istanbul ignore next */
385
+ if (nextSibling.precedingSibling !== child)
386
+ return internalError(13);
387
+ nextSibling.precedingSibling = child.precedingSibling;
337
388
  }
338
389
  else {
339
- this.lastChild = this.byPosition.length ? this.byPosition[this.byPosition.length - 1] : undefined;
390
+ /* istanbul ignore next */
391
+ if (child !== this.lastChild)
392
+ return internalError(12);
393
+ this.lastChild = child.precedingSibling === this.precedingSibling ? undefined : child.precedingSibling;
340
394
  }
341
395
  return;
342
396
  }
@@ -355,6 +409,9 @@ class OnEachItemScope extends Scope {
355
409
  this.parent = parent;
356
410
  this.itemIndex = itemIndex;
357
411
  }
412
+ // toString(): string {
413
+ // return `OnEachItemScope(itemIndex=${this.itemIndex} parentElement=${this.parentElement} parent=${this.parent} precedingSibling=${this.precedingSibling} lastChild=${this.lastChild})`
414
+ // }
358
415
  queueRun() {
359
416
  /* istanbul ignore next */
360
417
  if (currentScope) {
@@ -412,6 +469,9 @@ class ObsCollection {
412
469
  constructor() {
413
470
  this.observers = new Map();
414
471
  }
472
+ // toString(): string {
473
+ // return JSON.stringify(peek(() => this.getRecursive(3)))
474
+ // }
415
475
  addObserver(index, observer) {
416
476
  observer = observer;
417
477
  let obsSet = this.observers.get(index);
@@ -639,7 +699,7 @@ class ObsObject extends ObsMap {
639
699
  *
640
700
  * Supported data types are: `string`, `number`, `boolean`, `undefined`, `null`,
641
701
  * `Array`, `object` and `Map`. The latter three will always have `Store` objects as
642
- * values, creating a tree of `Store`s.
702
+ * values, creating a tree of `Store`-objects.
643
703
  */
644
704
  export class Store {
645
705
  constructor(value = undefined, index = undefined) {
@@ -694,48 +754,48 @@ export class Store {
694
754
  return this.query({ path });
695
755
  }
696
756
  /**
697
- * Like [[`get`]], but doesn't subscribe to changes.
757
+ * Like {@link Store.get}, but doesn't subscribe to changes.
698
758
  */
699
759
  peek(...path) {
700
760
  return this.query({ path, peek: true });
701
761
  }
702
762
  /**
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.
763
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `number`.
764
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
705
765
  */
706
766
  getNumber(...path) { return this.query({ path, type: 'number' }); }
707
767
  /**
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.
768
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `string`.
769
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
710
770
  */
711
771
  getString(...path) { return this.query({ path, type: 'string' }); }
712
772
  /**
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.
773
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `boolean`.
774
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
715
775
  */
716
776
  getBoolean(...path) { return this.query({ path, type: 'boolean' }); }
717
777
  /**
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.
778
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `function`.
779
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
720
780
  */
721
781
  getFunction(...path) { return this.query({ path, type: 'function' }); }
722
782
  /**
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.
783
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `array`.
784
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
725
785
  */
726
786
  getArray(...path) { return this.query({ path, type: 'array' }); }
727
787
  /**
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.
788
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `object`.
789
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
730
790
  */
731
791
  getObject(...path) { return this.query({ path, type: 'object' }); }
732
792
  /**
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.
793
+ * @returns Like {@link Store.get}, but throws a `TypeError` if the resulting value is not of type `map`.
794
+ * Using this instead of just {@link Store.get} is especially useful from within TypeScript.
735
795
  */
736
796
  getMap(...path) { return this.query({ path, type: 'map' }); }
737
797
  /**
738
- * Like [[`get`]], but the first parameter is the default value (returned when the Store
798
+ * Like {@link Store.get}, but the first parameter is the default value (returned when the Store
739
799
  * contains `undefined`). This default value is also used to determine the expected type,
740
800
  * and to throw otherwise.
741
801
  *
@@ -758,7 +818,7 @@ export class Store {
758
818
  return this.query({ type, defaultValue, path });
759
819
  }
760
820
  /** Retrieve a value, subscribing to all read `Store` values. This is a more flexible
761
- * form of the [[`get`]] and [[`peek`]] methods.
821
+ * form of the {@link Store.get} and {@link Store.peek} methods.
762
822
  *
763
823
  * @returns The resulting value, or `undefined` if the `path` does not exist.
764
824
  */
@@ -850,15 +910,15 @@ export class Store {
850
910
  }
851
911
  /**
852
912
  * Sets the value to the last given argument. Any earlier argument are a Store-path that is first
853
- * resolved/created using [[`makeRef`]].
913
+ * resolved/created using {@link Store.makeRef}.
854
914
  *
855
915
  * When a `Store` is passed in as the value, its value will be copied (subscribing to changes). In
856
916
  * case the value is an object, an `Array` or a `Map`, a *reference* to that data structure will
857
917
  * be created, so that changes made through one `Store` will be reflected through the other. Be
858
918
  * 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.)
919
+ * call to {@link Store.get} to throw a `RangeError` (Maximum call stack size exceeded.)
860
920
  *
861
- * If you intent to make a copy instead of a reference, call [[`get`]] on the origin `Store`.
921
+ * If you intent to make a copy instead of a reference, call {@link Store.get} on the origin `Store`.
862
922
  *
863
923
  *
864
924
  * @example
@@ -954,7 +1014,7 @@ export class Store {
954
1014
  return pos;
955
1015
  }
956
1016
  /**
957
- * [[`peek`]] the current value, pass it through `func`, and [[`set`]] the resulting
1017
+ * {@link Store.peek} the current value, pass it through `func`, and {@link Store.set} the resulting
958
1018
  * value.
959
1019
  * @param func The function transforming the value.
960
1020
  */
@@ -966,7 +1026,7 @@ export class Store {
966
1026
  * subscribing to every level.
967
1027
  * In case `undefined` is encountered while resolving the path, a newly
968
1028
  * created `Store` containing `undefined` is returned. In that case, the
969
- * `Store`'s [[`isDetached`]] method will return `true`.
1029
+ * `Store`'s {@link Store.isDetached} method will return `true`.
970
1030
  * In case something other than a collection is encountered, an error is thrown.
971
1031
  */
972
1032
  ref(...path) {
@@ -1019,7 +1079,7 @@ export class Store {
1019
1079
  }
1020
1080
  return store;
1021
1081
  }
1022
- /** @Internal */
1082
+ /** @internal */
1023
1083
  _observe() {
1024
1084
  if (currentScope) {
1025
1085
  if (this.collection.addObserver(this.idx, currentScope)) {
@@ -1031,7 +1091,7 @@ export class Store {
1031
1091
  /**
1032
1092
  * Iterate the specified collection (Array, Map or object), running the given code block for each item.
1033
1093
  * 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.
1094
+ * When an item is removed, the {@link Store.clean} handlers left by its code block are executed.
1035
1095
  *
1036
1096
  *
1037
1097
  *
@@ -1138,7 +1198,7 @@ export class Store {
1138
1198
  return out;
1139
1199
  }
1140
1200
  /**
1141
- * @returns Returns `true` when the `Store` was created by [[`ref`]]ing a path that
1201
+ * @returns Returns `true` when the `Store` was created by {@link Store.ref}ing a path that
1142
1202
  * does not exist.
1143
1203
  */
1144
1204
  isDetached() { return false; }
@@ -1167,12 +1227,18 @@ export class Store {
1167
1227
  class DetachedStore extends Store {
1168
1228
  isDetached() { return true; }
1169
1229
  }
1230
+ let onCreateEnabled = false;
1231
+ let onDestroyMap = new WeakMap();
1232
+ function destroyWithClass(element, cls) {
1233
+ element.classList.add(cls);
1234
+ setTimeout(() => element.remove(), 2000);
1235
+ }
1170
1236
  /**
1171
1237
  * Create a new DOM element, and insert it into the DOM at the position held by the current scope.
1172
1238
  * @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
1239
  * @param rest - The other arguments are flexible and interpreted based on their types:
1174
1240
  * - `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.
1241
+ * - `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
1242
  * - `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
1243
  * - `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
1244
  * @example
@@ -1207,7 +1273,14 @@ export function node(tag = "", ...rest) {
1207
1273
  let type = typeof item;
1208
1274
  if (type === 'function') {
1209
1275
  let scope = new SimpleScope(el, undefined, currentScope.queueOrder + 1, item);
1210
- scope.update();
1276
+ if (onCreateEnabled) {
1277
+ onCreateEnabled = false;
1278
+ scope.update();
1279
+ onCreateEnabled = true;
1280
+ }
1281
+ else {
1282
+ scope.update();
1283
+ }
1211
1284
  // Add it to our list of cleaners. Even if `scope` currently has
1212
1285
  // no cleaners, it may get them in a future refresh.
1213
1286
  currentScope.cleaners.push(scope);
@@ -1262,13 +1335,13 @@ function bindInput(el, store) {
1262
1335
  };
1263
1336
  }
1264
1337
  else {
1338
+ onInputChange = () => store.set(type === 'number' || type === 'range' ? (el.value === '' ? null : +el.value) : el.value);
1265
1339
  if (value === undefined)
1266
- store.set(el.value);
1340
+ onInputChange();
1267
1341
  onStoreChange = value => {
1268
1342
  if (el.value !== value)
1269
1343
  el.value = value;
1270
1344
  };
1271
- onInputChange = () => store.set(el.value);
1272
1345
  }
1273
1346
  observe(() => {
1274
1347
  onStoreChange(store.get());
@@ -1288,16 +1361,16 @@ export function text(text) {
1288
1361
  return;
1289
1362
  currentScope.addNode(document.createTextNode(text));
1290
1363
  }
1291
- export function prop(prop, value = undefined) {
1364
+ export function prop(name, value = undefined) {
1292
1365
  if (!currentScope || !currentScope.parentElement)
1293
1366
  throw new ScopeError(true);
1294
- if (typeof prop === 'object') {
1295
- for (let k in prop) {
1296
- applyProp(currentScope.parentElement, k, prop[k]);
1367
+ if (typeof name === 'object') {
1368
+ for (let k in name) {
1369
+ applyProp(currentScope.parentElement, k, name[k]);
1297
1370
  }
1298
1371
  }
1299
1372
  else {
1300
- applyProp(currentScope.parentElement, prop, value);
1373
+ applyProp(currentScope.parentElement, name, value);
1301
1374
  }
1302
1375
  }
1303
1376
  /**
@@ -1347,7 +1420,7 @@ export function observe(func) {
1347
1420
  mount(undefined, func);
1348
1421
  }
1349
1422
  /**
1350
- * Like [[`observe`]], but allow the function to create DOM elements using [[`node`]].
1423
+ * Like {@link Store.observe}, but allow the function to create DOM elements using {@link Store.node}.
1351
1424
 
1352
1425
  * @param func - The function to be (repeatedly) executed, possibly adding DOM elements to `parentElement`.
1353
1426
  * @param parentElement - A DOM element that will be used as the parent element for calls to `node`.
@@ -1362,7 +1435,7 @@ export function observe(func) {
1362
1435
  * })
1363
1436
  * ```
1364
1437
  *
1365
- * An example nesting [[`observe`]] within `mount`:
1438
+ * An example nesting {@link Store.observe} within `mount`:
1366
1439
  * ```
1367
1440
  * let selected = new Store(0)
1368
1441
  * let colors = new Store(new Map())
@@ -1403,7 +1476,7 @@ export function mount(parentElement, func) {
1403
1476
  currentScope.cleaners.push(scope);
1404
1477
  }
1405
1478
  }
1406
- /** Runs the given function, while not subscribing the current scope when reading [[`Store`]] values.
1479
+ /** Runs the given function, while not subscribing the current scope when reading {@link Store.Store} values.
1407
1480
  *
1408
1481
  * @param func Function to be executed immediately.
1409
1482
  * @returns Whatever `func()` returns.
@@ -1438,33 +1511,47 @@ export function peek(func) {
1438
1511
  * Helper functions
1439
1512
  */
1440
1513
  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);
1514
+ if (prop === 'create') {
1515
+ if (onCreateEnabled) {
1516
+ if (typeof value === 'function') {
1517
+ value(el);
1518
+ }
1519
+ else {
1520
+ el.classList.add(value);
1521
+ setTimeout(function () { el.classList.remove(value); }, 0);
1522
+ }
1449
1523
  }
1450
1524
  }
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;
1525
+ else if (prop === 'destroy') {
1526
+ onDestroyMap.set(el, value);
1454
1527
  }
1455
1528
  else if (typeof value === 'function') {
1456
1529
  // Set an event listener; remove it again on clean.
1457
1530
  el.addEventListener(prop, value);
1458
1531
  clean(() => el.removeEventListener(prop, value));
1459
1532
  }
1460
- else if (prop === 'style' && typeof value === 'object') {
1461
- // `style` can receive an object
1462
- Object.assign(el.style, value);
1533
+ else if (prop === 'value' || prop === 'className' || prop === 'selectedIndex' || value === true || value === false) {
1534
+ // All boolean values and a few specific keys should be set as a property
1535
+ el[prop] = value;
1463
1536
  }
1464
1537
  else if (prop === 'text') {
1465
1538
  // `text` is set as textContent
1466
1539
  el.textContent = value;
1467
1540
  }
1541
+ else if ((prop === 'class' || prop === 'className') && typeof value === 'object') {
1542
+ // Allow setting classes using an object where the keys are the names and
1543
+ // the values are booleans stating whether to set or remove.
1544
+ for (let name in value) {
1545
+ if (value[name])
1546
+ el.classList.add(name);
1547
+ else
1548
+ el.classList.remove(name);
1549
+ }
1550
+ }
1551
+ else if (prop === 'style' && typeof value === 'object') {
1552
+ // `style` can receive an object
1553
+ Object.assign(el.style, value);
1554
+ }
1468
1555
  else {
1469
1556
  // Everything else is an HTML attribute
1470
1557
  el.setAttribute(prop, value);
@@ -1530,6 +1617,67 @@ class ScopeError extends Error {
1530
1617
  super(`Operation not permitted outside of ${mount ? "a mount" : "an observe"}() scope`);
1531
1618
  }
1532
1619
  }
1620
+ const FADE_TIME = 400;
1621
+ const GROW_SHRINK_TRANSITION = `margin ${FADE_TIME}ms ease-out, transform ${FADE_TIME}ms ease-out`;
1622
+ function getGrowShrinkProps(el) {
1623
+ const parentStyle = el.parentElement ? getComputedStyle(el.parentElement) : {};
1624
+ const isHorizontal = parentStyle.display === 'flex' && (parentStyle.flexDirection || '').startsWith('row');
1625
+ return isHorizontal ?
1626
+ { marginLeft: `-${el.offsetWidth / 2}px`, marginRight: `-${el.offsetWidth / 2}px`, transform: "scaleX(0)" } :
1627
+ { marginBottom: `-${el.offsetHeight / 2}px`, marginTop: `-${el.offsetHeight / 2}px`, transform: "scaleY(0)" };
1628
+ }
1629
+ /** Do a grow transition for the given element. This is meant to be used as a
1630
+ * handler for the `create` property.
1631
+ *
1632
+ * @param el The element to transition.
1633
+ *
1634
+ * The transition doesn't look great for table elements, and may have problems
1635
+ * for other specific cases as well.
1636
+ */
1637
+ export function grow(el) {
1638
+ // This timeout is to await all other elements having been added to the Dom
1639
+ scheduleTask(() => {
1640
+ // Make the element size 0 using transforms and negative margins.
1641
+ // This causes a browser layout, as we're querying el.offset<>.
1642
+ let props = getGrowShrinkProps(el);
1643
+ // The timeout is in order to batch all reads and then all writes when there
1644
+ // are multiple simultaneous grow transitions.
1645
+ scheduleTask(() => {
1646
+ Object.assign(el.style, props);
1647
+ // This timeout is to combine multiple transitions into a single browser layout
1648
+ scheduleTask(() => {
1649
+ // Make sure the layouting has been performed, to cause transitions to trigger
1650
+ el.offsetHeight;
1651
+ // Do the transitions
1652
+ el.style.transition = GROW_SHRINK_TRANSITION;
1653
+ for (let prop in props)
1654
+ el.style[prop] = "";
1655
+ setTimeout(() => {
1656
+ // Reset the element to a clean state
1657
+ el.style.transition = "";
1658
+ }, FADE_TIME);
1659
+ }, 2);
1660
+ }, 1);
1661
+ });
1662
+ }
1663
+ /** Do a shrink transition for the given element, and remove it from the DOM
1664
+ * afterwards. This is meant to be used as a handler for the `destroy` property.
1665
+ *
1666
+ * @param el The element to transition and remove.
1667
+ *
1668
+ * The transition doesn't look great for table elements, and may have problems
1669
+ * for other specific cases as well.
1670
+ */
1671
+ export function shrink(el) {
1672
+ const props = getGrowShrinkProps(el);
1673
+ // The timeout is in order to batch all reads and then all writes when there
1674
+ // are multiple simultaneous shrink transitions.
1675
+ scheduleTask(() => {
1676
+ el.style.transition = GROW_SHRINK_TRANSITION;
1677
+ Object.assign(el.style, props);
1678
+ setTimeout(() => el.remove(), FADE_TIME);
1679
+ }, 1);
1680
+ }
1533
1681
  // @ts-ignore
1534
1682
  // istanbul ignore next
1535
1683
  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=[],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(){w=!0;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,w=!1}export function scheduleTask(e,t=0){s({queueOrder:1e3+t,queueRun:e})}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(e){let t,i=this;for(;(t=i.precedingSibling)&&t!==e;){if(t instanceof Node)return t;let e=t.findLastNode();if(e)return e;i=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 I(1);const i=t;t=i.nextSibling||void 0;let n=b.get(i);if(n&&i instanceof Element?!0!==n&&("function"==typeof n?n(i):x(i,n),b.set(i,!0)):this.parentElement.removeChild(i),i===e)break}}}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&&I(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;try{this.renderer()}catch(e){N(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 I(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 I(6);t.remove(),this.byIndex.delete(e),this.removeFromPosition(t)}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);let i=this.byPosition[t+1];i?(e.precedingSibling=i.precedingSibling,i.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 i=this.byPosition[t];if(!i)return I(8);if(i.precedingSibling!==e)return I(13);i.precedingSibling=e.precedingSibling}else{if(e!==this.lastChild)return I(12);this.lastChild=e.precedingSibling===this.precedingSibling?void 0:e.precedingSibling}return}if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return I(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&&I(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){N(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){N(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=C(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 m 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 v extends m{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,C(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=C(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 y}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 v,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=O,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 k(!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 y extends Store{isDetached(){return!0}}let w=!1,b=new WeakMap;function x(e,t){e.classList.add(t),setTimeout(()=>e.remove(),2e3)}export function node(t="",...i){if(!e)throw new k(!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);w?(w=!1,i.update(),w=!0):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)E(n,e,t[e]);else if(t instanceof Store)S(n,t);else if(null!=t)throw new Error("Unexpected argument "+JSON.stringify(t))}}export function html(t){if(!e||!e.parentElement)throw new k(!0);let i=document.createElement(e.parentElement.tagName);for(i.innerHTML=""+t;i.firstChild;)e.addNode(i.firstChild)}function S(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)}):(n=()=>t.set("number"===r||"range"===r?""===e.value?null:+e.value:e.value),void 0===s&&n(),i=t=>{e.value!==t&&(e.value=t)}),observe(()=>{i(t.get())}),e.addEventListener("input",n),clean(()=>{e.removeEventListener("input",n)})}export function text(t){if(!e)throw new k(!0);null!=t&&e.addNode(document.createTextNode(t))}export function prop(t,i){if(!e||!e.parentElement)throw new k(!0);if("object"==typeof t)for(let i in t)E(e.parentElement,i,t[i]);else E(e.parentElement,t,i)}export function getParentElement(){if(!e||!e.parentElement)throw new k(!0);return e.parentElement}export function clean(t){if(!e)throw new k(!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 E(e,t,i){if("create"===t)w&&("function"==typeof i?i(e):(e.classList.add(i),setTimeout((function(){e.classList.remove(i)}),0)));else if("destroy"===t)b.set(e,i);else if("function"==typeof i)e.addEventListener(t,i),clean(()=>e.removeEventListener(t,i));else if("value"===t||"className"===t||"selectedIndex"===t||!0===i||!1===i)e[t]=i;else if("text"===t)e.textContent=i;else if("class"!==t&&"className"!==t||"object"!=typeof i)"style"===t&&"object"==typeof i?Object.assign(e.style,i):e.setAttribute(t,i);else for(let t in i)i[t]?e.classList.add(t):e.classList.remove(t)}function C(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new m;return e.forEach((e,i)=>{let n=C(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=C(e[i]);void 0!==n&&t.rawSet(i,n)}return t}if(e.constructor===Object){let t=new v;for(let i in e){let n=C(e[i]);void 0!==n&&t.rawSet(i,n)}return t}return e}return e}function O(e){return e.index()}function I(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function N(e){setTimeout(()=>{throw e},0)}class k extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}function q(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){scheduleTask(()=>{let t=q(e);scheduleTask(()=>{Object.assign(e.style,t),scheduleTask(()=>{e.offsetHeight,e.style.transition="margin 400ms ease-out, transform 400ms ease-out";for(let i in t)e.style[i]="";setTimeout(()=>{e.style.transition=""},400)},2)},1)})}export function shrink(e){const t=q(e);scheduleTask(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out",Object.assign(e.style,t),setTimeout(()=>e.remove(),400)},1)}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.0.17",
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"