aberdeen 1.0.5 → 1.0.7

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/src/aberdeen.ts CHANGED
@@ -1,30 +1,36 @@
1
1
  import { ReverseSortedSet } from "./helpers/reverseSortedSet.js";
2
2
 
3
3
  /*
4
- * QueueRunner
5
- *
6
- * `queue()`d runners are executed on the next timer tick, by order of their
7
- * `prio` values.
8
- */
4
+ * QueueRunner
5
+ *
6
+ * `queue()`d runners are executed on the next timer tick, by order of their
7
+ * `prio` values.
8
+ */
9
9
  interface QueueRunner {
10
10
  prio: number; // Higher values have higher priority
11
11
  queueRun(): void;
12
12
  }
13
13
 
14
14
  let sortedQueue: ReverseSortedSet<QueueRunner> | undefined; // When set, a runQueue is scheduled or currently running.
15
- let runQueueDepth = 0 // Incremented when a queue event causes another queue event to be added. Reset when queue is empty. Throw when >= 42 to break (infinite) recursion.
16
- let topRedrawScope: Scope | undefined // The scope that triggered the current redraw. Elements drawn at this scope level may trigger 'create' animations.
15
+ let runQueueDepth = 0; // Incremented when a queue event causes another queue event to be added. Reset when queue is empty. Throw when >= 42 to break (infinite) recursion.
16
+ let topRedrawScope: Scope | undefined; // The scope that triggered the current redraw. Elements drawn at this scope level may trigger 'create' animations.
17
17
 
18
18
  /** @internal */
19
- export type TargetType = any[] | {[key: string]: any};
19
+ export type TargetType = any[] | { [key: string]: any };
20
20
  /** @internal */
21
- export type DatumType = TargetType | boolean | number | string | null | undefined;
21
+ export type DatumType =
22
+ | TargetType
23
+ | boolean
24
+ | number
25
+ | string
26
+ | null
27
+ | undefined;
22
28
 
23
29
  function queue(runner: QueueRunner) {
24
30
  if (!sortedQueue) {
25
- sortedQueue = new ReverseSortedSet<QueueRunner>('prio');
31
+ sortedQueue = new ReverseSortedSet<QueueRunner>("prio");
26
32
  setTimeout(runQueue, 0);
27
- } else if (!(runQueueDepth&1)) {
33
+ } else if (!(runQueueDepth & 1)) {
28
34
  runQueueDepth++; // Make it uneven
29
35
  if (runQueueDepth > 98) {
30
36
  throw new Error("Too many recursive updates from observes");
@@ -50,7 +56,7 @@ function queue(runner: QueueRunner) {
50
56
  * @example
51
57
  * ```typescript
52
58
  * const data = proxy("before");
53
- *
59
+ *
54
60
  * $({text: data});
55
61
  * console.log(1, document.body.innerHTML); // before
56
62
  *
@@ -60,57 +66,61 @@ function queue(runner: QueueRunner) {
60
66
  * // Normally, the DOM update would happen after a timeout.
61
67
  * // But this causes an immediate update:
62
68
  * runQueue();
63
- *
69
+ *
64
70
  * console.log(2, document.body.innerHTML); // after
65
71
  * ```
66
72
  */
67
73
  export function runQueue(): void {
68
74
  let time = Date.now();
69
- while(sortedQueue) {
75
+ while (sortedQueue) {
70
76
  const runner = sortedQueue.fetchLast();
71
77
  if (!runner) break;
72
- if (runQueueDepth&1) runQueueDepth++; // Make it even
78
+ if (runQueueDepth & 1) runQueueDepth++; // Make it even
73
79
  runner.queueRun();
74
80
  }
75
81
  sortedQueue = undefined;
76
82
  runQueueDepth = 0;
77
83
  time = Date.now() - time;
78
- if (time>1) console.debug(`Aberdeen queue took ${time}ms`);
84
+ if (time > 9) console.debug(`Aberdeen queue took ${time}ms`);
79
85
  }
80
86
 
81
-
82
87
  /**
83
- * A sort key, as used by {@link onEach}, is a value that determines the order of items. It can
88
+ * A sort key, as used by {@link onEach}, is a value that determines the order of items. It can
84
89
  * be a number, string, or an array of numbers/strings. The sort key is used to sort items
85
90
  * based on their values. The sort key can also be `undefined`, which indicates that the item
86
91
  * should be ignored.
87
92
  * @private
88
93
  */
89
- export type SortKeyType = number | string | Array<number|string> | undefined;
94
+ export type SortKeyType = number | string | Array<number | string> | undefined;
90
95
 
91
96
  /**
92
- * Given an integer number or a string, this function returns a string that can be concatenated
93
- * with other strings to create a composed sort key, that follows natural number ordering.
94
- */
95
- function partToStr(part: number|string): string {
96
- if (typeof part === 'string') {
97
- return part + '\x01'; // end-of-string
98
- }
99
- let result = '';
97
+ * Given an integer number or a string, this function returns a string that can be concatenated
98
+ * with other strings to create a composed sort key, that follows natural number ordering.
99
+ */
100
+ function partToStr(part: number | string): string {
101
+ if (typeof part === "string") {
102
+ return `${part}\x01`; // end-of-string
103
+ }
104
+ let result = "";
100
105
  let num = Math.abs(Math.round(part));
101
106
  const negative = part < 0;
102
- while(num > 0) {
107
+ while (num > 0) {
103
108
  /*
104
- * We're reserving a few character codes:
105
- * 0 - for compatibility
106
- * 1 - separator between string array items
107
- * 65535 - for compatibility
108
- */
109
- result += String.fromCharCode(negative ? 65534 - (num % 65533) : 2 + (num % 65533));
109
+ * We're reserving a few character codes:
110
+ * 0 - for compatibility
111
+ * 1 - separator between string array items
112
+ * 65535 - for compatibility
113
+ */
114
+ result += String.fromCharCode(
115
+ negative ? 65534 - (num % 65533) : 2 + (num % 65533),
116
+ );
110
117
  num = Math.floor(num / 65533);
111
118
  }
112
119
  // Prefix the number of digits, counting down from 128 for negative and up for positive
113
- return String.fromCharCode(128 + (negative ? -result.length : result.length)) + result;
120
+ return (
121
+ String.fromCharCode(128 + (negative ? -result.length : result.length)) +
122
+ result
123
+ );
114
124
  }
115
125
 
116
126
  /**
@@ -141,14 +151,13 @@ function partToStr(part: number|string): string {
141
151
  * @see {@link onEach} for usage with sorting.
142
152
  */
143
153
  export function invertString(input: string): string {
144
- let result = '';
154
+ let result = "";
145
155
  for (let i = 0; i < input.length; i++) {
146
156
  result += String.fromCodePoint(65535 - input.charCodeAt(i));
147
157
  }
148
158
  return result;
149
159
  }
150
160
 
151
-
152
161
  // Each new scope gets a lower prio than all scopes before it, by decrementing
153
162
  // this counter.
154
163
  let lastPrio = 0;
@@ -161,7 +170,7 @@ abstract class Scope implements QueueRunner {
161
170
 
162
171
  abstract onChange(index: any, newData: DatumType, oldData: DatumType): void;
163
172
  abstract queueRun(): void;
164
-
173
+
165
174
  abstract getLastNode(): Node | undefined;
166
175
  abstract getPrecedingNode(): Node | undefined;
167
176
  abstract delete(): void;
@@ -187,9 +196,11 @@ abstract class Scope implements QueueRunner {
187
196
  abstract class ContentScope extends Scope {
188
197
  // The list of clean functions to be called when this scope is cleaned. These can
189
198
  // be for child scopes, subscriptions as well as `clean(..)` hooks.
190
- cleaners: Array<{delete: (scope: Scope) => void} | (() => void)>;
199
+ cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)>;
191
200
 
192
- constructor(cleaners: Array<{delete: (scope: Scope) => void} | (() => void)> = []) {
201
+ constructor(
202
+ cleaners: Array<{ delete: (scope: Scope) => void } | (() => void)> = [],
203
+ ) {
193
204
  super();
194
205
  this.cleaners = cleaners;
195
206
  }
@@ -197,7 +208,7 @@ abstract class ContentScope extends Scope {
197
208
  lastChild: Node | Scope | undefined;
198
209
 
199
210
  // Should be subclassed in most cases..
200
- redraw() {};
211
+ redraw() {}
201
212
 
202
213
  abstract parentElement: Element;
203
214
 
@@ -210,8 +221,8 @@ abstract class ContentScope extends Scope {
210
221
  * It is called `delete`, so that the list of cleaners can also contain `Set`s.
211
222
  */
212
223
  delete(/* ignore observer argument */) {
213
- for(let cleaner of this.cleaners) {
214
- if (typeof cleaner === 'function') cleaner();
224
+ for (const cleaner of this.cleaners) {
225
+ if (typeof cleaner === "function") cleaner();
215
226
  else cleaner.delete(this); // pass in observer argument, in case `cleaner` is a `Set`
216
227
  }
217
228
  this.cleaners.length = 0;
@@ -220,13 +231,13 @@ abstract class ContentScope extends Scope {
220
231
  // To prepare for a redraw or to help GC when we're being removed:
221
232
  this.lastChild = undefined;
222
233
  }
223
-
234
+
224
235
  queueRun() {
225
236
  this.remove();
226
-
227
- topRedrawScope = this
237
+
238
+ topRedrawScope = this;
228
239
  this.redraw();
229
- topRedrawScope = undefined
240
+ topRedrawScope = undefined;
230
241
  }
231
242
 
232
243
  getInsertAfterNode() {
@@ -242,7 +253,6 @@ abstract class ContentScope extends Scope {
242
253
  }
243
254
  }
244
255
 
245
-
246
256
  class ChainedScope extends ContentScope {
247
257
  // The node or scope right before this scope that has the same `parentElement`.
248
258
  public prevSibling: Node | Scope | undefined;
@@ -251,7 +261,7 @@ class ChainedScope extends ContentScope {
251
261
  // The parent DOM element we'll add our child nodes to.
252
262
  public parentElement: Element,
253
263
  // When true, we share our 'cleaners' list with the parent scope.
254
- useParentCleaners: boolean = false,
264
+ useParentCleaners = false,
255
265
  ) {
256
266
  super(useParentCleaners ? currentScope.cleaners : []);
257
267
  if (parentElement === currentScope.parentElement) {
@@ -268,25 +278,25 @@ class ChainedScope extends ContentScope {
268
278
  getPrecedingNode(): Node | undefined {
269
279
  return findLastNodeInPrevSiblings(this.prevSibling);
270
280
  }
271
-
281
+
272
282
  getChildPrevSibling() {
273
283
  return this.lastChild || this.prevSibling;
274
284
  }
275
285
  }
276
286
 
277
287
  /**
278
- * @internal
279
- * A `RegularScope` is created with a `render` function that is run initially,
280
- * and again when any of the `Store`s that this function reads are changed. Any
281
- * DOM elements that is given a `render` function for its contents has its own scope.
282
- * The `Scope` manages the position in the DOM tree elements created by `render`
283
- * are inserted at. Before a rerender, all previously created elements are removed
284
- * and the `clean` functions for the scope and all sub-scopes are called.
285
- */
288
+ * @internal
289
+ * A `RegularScope` is created with a `render` function that is run initially,
290
+ * and again when any of the `Store`s that this function reads are changed. Any
291
+ * DOM elements that is given a `render` function for its contents has its own scope.
292
+ * The `Scope` manages the position in the DOM tree elements created by `render`
293
+ * are inserted at. Before a rerender, all previously created elements are removed
294
+ * and the `clean` functions for the scope and all sub-scopes are called.
295
+ */
286
296
  class RegularScope extends ChainedScope {
287
297
  constructor(
288
298
  parentElement: Element,
289
- // The function that will be reactively called. Elements it creates using `$` are
299
+ // The function that will be reactively called. Elements it creates using `$` are
290
300
  // added to the appropriate position within `parentElement`.
291
301
  public renderer: () => any,
292
302
  ) {
@@ -297,11 +307,11 @@ class RegularScope extends ChainedScope {
297
307
  }
298
308
 
299
309
  redraw() {
300
- let savedScope = currentScope;
310
+ const savedScope = currentScope;
301
311
  currentScope = this;
302
312
  try {
303
313
  this.renderer();
304
- } catch(e) {
314
+ } catch (e) {
305
315
  // Throw the error async, so the rest of the rendering can continue
306
316
  handleError(e, true);
307
317
  }
@@ -309,7 +319,6 @@ class RegularScope extends ChainedScope {
309
319
  }
310
320
  }
311
321
 
312
-
313
322
  class RootScope extends ContentScope {
314
323
  parentElement = document.body;
315
324
  getPrecedingNode(): Node | undefined {
@@ -321,12 +330,12 @@ class MountScope extends ContentScope {
321
330
  constructor(
322
331
  // The parent DOM element we'll add our child nodes to
323
332
  public parentElement: Element,
324
- // The function that
333
+ // The function that
325
334
  public renderer: () => any,
326
335
  ) {
327
336
  super();
328
337
  this.redraw();
329
- currentScope.cleaners.push(this)
338
+ currentScope.cleaners.push(this);
330
339
  }
331
340
 
332
341
  redraw() {
@@ -350,16 +359,18 @@ class MountScope extends ContentScope {
350
359
  }
351
360
  }
352
361
 
353
-
354
362
  // Remove node and all its preceding siblings (uptil and excluding preNode)
355
363
  // from the DOM, using onDestroy if applicable.
356
- function removeNodes(node: Node | null | undefined, preNode: Node | null | undefined) {
357
- while(node && node !== preNode) {
364
+ function removeNodes(
365
+ node: Node | null | undefined,
366
+ preNode: Node | null | undefined,
367
+ ) {
368
+ while (node && node !== preNode) {
358
369
  const prevNode: Node | null = node.previousSibling;
359
- let onDestroy = onDestroyMap.get(node);
370
+ const onDestroy = onDestroyMap.get(node);
360
371
  if (onDestroy && node instanceof Element) {
361
372
  if (onDestroy !== true) {
362
- if (typeof onDestroy === 'function') {
373
+ if (typeof onDestroy === "function") {
363
374
  onDestroy(node);
364
375
  } else {
365
376
  destroyWithClass(node, onDestroy);
@@ -369,7 +380,7 @@ function removeNodes(node: Node | null | undefined, preNode: Node | null | undef
369
380
  }
370
381
  // Ignore the deleting element
371
382
  } else {
372
- (node as Element|Text).remove();
383
+ (node as Element | Text).remove();
373
384
  }
374
385
  node = prevNode;
375
386
  }
@@ -377,14 +388,15 @@ function removeNodes(node: Node | null | undefined, preNode: Node | null | undef
377
388
 
378
389
  // Get a reference to the last node within `sibling` or any of its preceding siblings.
379
390
  // If a `Node` is given, that node is returned.
380
- function findLastNodeInPrevSiblings(sibling: Node | Scope | undefined): Node | undefined {
391
+ function findLastNodeInPrevSiblings(
392
+ sibling: Node | Scope | undefined,
393
+ ): Node | undefined {
381
394
  if (!sibling || sibling instanceof Node) return sibling;
382
395
  return sibling.getLastNode() || sibling.getPrecedingNode();
383
396
  }
384
397
 
385
-
386
- class ResultScope<T extends DatumType | void> extends ChainedScope {
387
- public result: ValueRef<T> = optProxy({value: undefined});
398
+ class ResultScope<T> extends ChainedScope {
399
+ public result: ValueRef<T> = optProxy({ value: undefined });
388
400
 
389
401
  constructor(
390
402
  parentElement: Element,
@@ -396,11 +408,11 @@ class ResultScope<T extends DatumType | void> extends ChainedScope {
396
408
  }
397
409
 
398
410
  redraw() {
399
- let savedScope = currentScope;
411
+ const savedScope = currentScope;
400
412
  currentScope = this;
401
413
  try {
402
414
  this.result.value = this.renderer();
403
- } catch(e) {
415
+ } catch (e) {
404
416
  // Throw the error async, so the rest of the rendering can continue
405
417
  handleError(e, true);
406
418
  }
@@ -410,28 +422,27 @@ class ResultScope<T extends DatumType | void> extends ChainedScope {
410
422
 
411
423
  /**
412
424
  * A `Scope` subclass optimized for reactively setting just a single element property
413
- * based on a proxied reference.
425
+ * based on a proxied reference.
414
426
  */
415
427
 
416
428
  class SetArgScope extends ChainedScope {
417
429
  constructor(
418
430
  parentElement: Element,
419
431
  public key: string,
420
- public target: {value: DatumType},
432
+ public target: { value: DatumType },
421
433
  ) {
422
434
  super(parentElement);
423
435
  this.redraw();
424
436
  }
425
437
  redraw() {
426
- let savedScope = currentScope;
438
+ const savedScope = currentScope;
427
439
  currentScope = this;
428
- applyArg(this.key, this.target.value)
440
+ applyArg(this.key, this.target.value);
429
441
  currentScope = savedScope;
430
442
  }
431
443
  }
432
444
 
433
-
434
- let immediateQueue: ReverseSortedSet<Scope> = new ReverseSortedSet('prio');
445
+ let immediateQueue: ReverseSortedSet<Scope> = new ReverseSortedSet("prio");
435
446
 
436
447
  class ImmediateScope extends RegularScope {
437
448
  onChange(index: any, newData: DatumType, oldData: DatumType) {
@@ -441,16 +452,20 @@ class ImmediateScope extends RegularScope {
441
452
 
442
453
  let immediateQueueRunning = false;
443
454
  function runImmediateQueue() {
444
- for(let count=0; !immediateQueue.isEmpty() && !immediateQueueRunning; count++) {
455
+ for (
456
+ let count = 0;
457
+ !immediateQueue.isEmpty() && !immediateQueueRunning;
458
+ count++
459
+ ) {
445
460
  if (count > 42) {
446
461
  immediateQueue.clear();
447
462
  throw new Error("Too many immediate-mode recursive updates");
448
463
  }
449
464
  immediateQueueRunning = true;
450
- let copy = immediateQueue;
451
- immediateQueue = new ReverseSortedSet('prio');
465
+ const copy = immediateQueue;
466
+ immediateQueue = new ReverseSortedSet("prio");
452
467
  try {
453
- for(const scope of copy) {
468
+ for (const scope of copy) {
454
469
  // On exception, the exception will be bubbled up to the call site, discarding any
455
470
  // remaining immediate scopes from the queue. This behavior is perhaps debatable,
456
471
  // but getting a synchronous exception at the call site can be very helpful.
@@ -462,34 +477,37 @@ function runImmediateQueue() {
462
477
  }
463
478
  }
464
479
 
465
-
466
480
  /** @internal */
467
481
  class OnEachScope extends Scope {
482
+ // biome-ignore lint/correctness/noInvalidUseBeforeDeclaration: circular, as currentScope is initialized with a Scope
468
483
  parentElement: Element = currentScope.parentElement;
469
484
  prevSibling: Node | Scope | undefined;
470
485
 
471
486
  /** The data structure we are iterating */
472
487
  target: TargetType;
473
-
488
+
474
489
  /** All item scopes, by array index or object key. This is used for removing an item scope when its value
475
490
  * disappears, and calling all subscope cleaners. */
476
- byIndex: Map<any,OnEachItemScope> = new Map();
491
+ byIndex: Map<any, OnEachItemScope> = new Map();
477
492
 
478
493
  /** The reverse-ordered list of item scopes, not including those for which makeSortKey returned undefined. */
479
- sortedSet: ReverseSortedSet<OnEachItemScope> = new ReverseSortedSet('sortKey');
494
+ sortedSet: ReverseSortedSet<OnEachItemScope> = new ReverseSortedSet(
495
+ "sortKey",
496
+ );
480
497
 
481
498
  /** Indexes that have been created/removed and need to be handled in the next `queueRun`. */
482
499
  changedIndexes: Set<any> = new Set();
483
-
500
+
484
501
  constructor(
485
502
  proxy: TargetType,
486
503
  /** A function that renders an item */
487
- public renderer: (value: DatumType, key: any, ) => void,
504
+ public renderer: (value: DatumType, key: any) => void,
488
505
  /** A function returning a number/string/array that defines the position of an item */
489
506
  public makeSortKey?: (value: DatumType, key: any) => SortKeyType,
490
507
  ) {
491
508
  super();
492
- const target: TargetType = this.target = (proxy as any)[TARGET_SYMBOL] || proxy;
509
+ const target: TargetType = (this.target =
510
+ (proxy as any)[TARGET_SYMBOL] || proxy);
493
511
 
494
512
  subscribe(target, ANY_SYMBOL, this);
495
513
  this.prevSibling = currentScope.getChildPrevSibling();
@@ -499,13 +517,13 @@ class OnEachScope extends Scope {
499
517
 
500
518
  // Do _addChild() calls for initial items
501
519
  if (target instanceof Array) {
502
- for(let i=0; i<target.length; i++) {
503
- if (target[i]!==undefined) {
520
+ for (let i = 0; i < target.length; i++) {
521
+ if (target[i] !== undefined) {
504
522
  new OnEachItemScope(this, i, false);
505
523
  }
506
524
  }
507
525
  } else {
508
- for(const key in target) {
526
+ for (const key in target) {
509
527
  if (target[key] !== undefined) {
510
528
  new OnEachItemScope(this, key, false);
511
529
  }
@@ -516,16 +534,17 @@ class OnEachScope extends Scope {
516
534
  getPrecedingNode(): Node | undefined {
517
535
  return findLastNodeInPrevSiblings(this.prevSibling);
518
536
  }
519
-
537
+
520
538
  onChange(index: any, newData: DatumType, oldData: DatumType) {
521
- if (!(this.target instanceof Array) || typeof index === 'number') this.changedIndexes.add(index);
539
+ if (!(this.target instanceof Array) || typeof index === "number")
540
+ this.changedIndexes.add(index);
522
541
  queue(this);
523
542
  }
524
-
543
+
525
544
  queueRun() {
526
- let indexes = this.changedIndexes;
545
+ const indexes = this.changedIndexes;
527
546
  this.changedIndexes = new Set();
528
- for(let index of indexes) {
547
+ for (const index of indexes) {
529
548
  const oldScope = this.byIndex.get(index);
530
549
  if (oldScope) oldScope.remove();
531
550
 
@@ -537,23 +556,26 @@ class OnEachScope extends Scope {
537
556
  }
538
557
  topRedrawScope = undefined;
539
558
  }
540
-
559
+
541
560
  delete() {
542
561
  // Propagate to all our subscopes
543
562
  for (const scope of this.byIndex.values()) {
544
563
  scope.delete();
545
564
  }
546
-
565
+
566
+ sortedQueue?.remove(this); // This is very fast and O(1) when not queued
567
+
547
568
  // Help garbage collection:
548
569
  this.byIndex.clear();
549
570
  setTimeout(() => {
550
571
  // Unsure if this is a good idea. It takes time, but presumably makes things a lot easier for GC...
551
- this.sortedSet.clear();
572
+ this.sortedSet.clear();
552
573
  }, 1);
553
574
  }
554
-
575
+
555
576
  getLastNode(): Node | undefined {
556
- for(let scope of this.sortedSet) { // Iterates starting at last child scope.
577
+ for (const scope of this.sortedSet) {
578
+ // Iterates starting at last child scope.
557
579
  const node = scope.getActualLastNode();
558
580
  if (node) return node;
559
581
  }
@@ -564,7 +586,7 @@ class OnEachScope extends Scope {
564
586
  class OnEachItemScope extends ContentScope {
565
587
  sortKey: string | number | undefined; // When undefined, this scope is currently not showing in the list
566
588
  public parentElement: Element;
567
-
589
+
568
590
  constructor(
569
591
  public parent: OnEachScope,
570
592
  public itemIndex: any,
@@ -592,7 +614,7 @@ class OnEachItemScope extends ContentScope {
592
614
  // of the sortedSet now (if we weren't already).
593
615
  // This will do nothing and barely take any time of `this` is already part of the set:
594
616
  this.parent.sortedSet.add(this);
595
-
617
+
596
618
  const preScope = this.parent.sortedSet.prev(this);
597
619
  // As preScope should have inserted itself as its first child, this should
598
620
  // recursively call getPrecedingNode() on preScope in case it doesn't
@@ -610,7 +632,7 @@ class OnEachItemScope extends ContentScope {
610
632
  getActualLastNode(): Node | undefined {
611
633
  let child = this.lastChild;
612
634
 
613
- while(child && child !== this) {
635
+ while (child && child !== this) {
614
636
  if (child instanceof Node) return child;
615
637
  const node = child.getLastNode();
616
638
  if (node) return node;
@@ -621,7 +643,7 @@ class OnEachItemScope extends ContentScope {
621
643
  queueRun() {
622
644
  /* c8 ignore next */
623
645
  if (currentScope !== ROOT_SCOPE) internalError(4);
624
-
646
+
625
647
  // We're not calling `remove` here, as we don't want to remove ourselves from
626
648
  // the sorted set. `redraw` will take care of that, if needed.
627
649
  // Also, we can't use `getLastNode` here, as we've hacked it to return the
@@ -633,12 +655,12 @@ class OnEachItemScope extends ContentScope {
633
655
 
634
656
  this.delete();
635
657
  this.lastChild = this; // apply the hack (see constructor) again
636
-
658
+
637
659
  topRedrawScope = this;
638
660
  this.redraw();
639
661
  topRedrawScope = undefined;
640
662
  }
641
-
663
+
642
664
  redraw() {
643
665
  // Have the makeSortKey function return an ordering int/string/array.
644
666
 
@@ -646,22 +668,28 @@ class OnEachItemScope extends ContentScope {
646
668
  // a wildcard subscription to delete/recreate any scopes when that changes.
647
669
  // We ARE creating a proxy around the value though (in case its an object/array),
648
670
  // so we'll have our own scope subscribe to changes on that.
649
- const value: DatumType = optProxy((this.parent.target as any)[this.itemIndex]);
671
+ const value: DatumType = optProxy(
672
+ (this.parent.target as any)[this.itemIndex],
673
+ );
650
674
 
651
675
  // Since makeSortKey may get() the Store, we'll need to set currentScope first.
652
- let savedScope = currentScope;
676
+ const savedScope = currentScope;
653
677
  currentScope = this;
654
-
655
- let sortKey : undefined | string | number;
678
+
679
+ let sortKey: undefined | string | number;
656
680
  try {
657
681
  if (this.parent.makeSortKey) {
658
- let rawSortKey = this.parent.makeSortKey(value, this.itemIndex);
659
- if (rawSortKey != null) sortKey = rawSortKey instanceof Array ? rawSortKey.map(partToStr).join('') : rawSortKey;
682
+ const rawSortKey = this.parent.makeSortKey(value, this.itemIndex);
683
+ if (rawSortKey != null)
684
+ sortKey =
685
+ rawSortKey instanceof Array
686
+ ? rawSortKey.map(partToStr).join("")
687
+ : rawSortKey;
660
688
  } else {
661
689
  sortKey = this.itemIndex;
662
690
  }
663
- if (typeof sortKey === 'number') sortKey = partToStr(sortKey);
664
-
691
+ if (typeof sortKey === "number") sortKey = partToStr(sortKey);
692
+
665
693
  if (this.sortKey !== sortKey) {
666
694
  // If the sortKey is changed, make sure `this` is removed from the
667
695
  // set before setting the new sortKey to it.
@@ -673,8 +701,8 @@ class OnEachItemScope extends ContentScope {
673
701
  // in case no nodes are created. We'll do it just-in-time in `getPrecedingNode`.
674
702
 
675
703
  if (sortKey != null) this.parent.renderer(value, this.itemIndex);
676
- } catch(e) {
677
- handleError(e, sortKey!=null);
704
+ } catch (e) {
705
+ handleError(e, sortKey != null);
678
706
  }
679
707
 
680
708
  currentScope = savedScope;
@@ -705,64 +733,89 @@ class OnEachItemScope extends ContentScope {
705
733
  function addNode(node: Node) {
706
734
  const parentEl = currentScope.parentElement;
707
735
  const prevNode = currentScope.getInsertAfterNode();
708
- parentEl.insertBefore(node, prevNode ? prevNode.nextSibling : parentEl.firstChild);
736
+ parentEl.insertBefore(
737
+ node,
738
+ prevNode ? prevNode.nextSibling : parentEl.firstChild,
739
+ );
709
740
  currentScope.lastChild = node;
710
741
  }
711
742
 
712
-
713
743
  /**
714
- * This global is set during the execution of a `Scope.render`. It is used by
715
- * functions like `$` and `clean`.
716
- */
744
+ * This global is set during the execution of a `Scope.render`. It is used by
745
+ * functions like `$` and `clean`.
746
+ */
717
747
  const ROOT_SCOPE = new RootScope();
718
748
  let currentScope: ContentScope = ROOT_SCOPE;
719
749
 
720
750
  /**
721
- * A special Node observer index to subscribe to any value in the map changing.
722
- */
723
- const ANY_SYMBOL = Symbol('any');
751
+ * A special Node observer index to subscribe to any value in the map changing.
752
+ */
753
+ const ANY_SYMBOL = Symbol("any");
724
754
 
725
755
  /**
726
756
  * When our proxy objects need to lookup `obj[TARGET_SYMBOL]` it returns its
727
757
  * target, to be used in our wrapped methods.
728
758
  */
729
- const TARGET_SYMBOL = Symbol('target');
730
-
731
-
732
- const subscribers = new WeakMap<TargetType, Map<any, Set<Scope | ((index: any, newData: DatumType, oldData: DatumType) => void)>>>;
759
+ const TARGET_SYMBOL = Symbol("target");
760
+
761
+ const subscribers = new WeakMap<
762
+ TargetType,
763
+ Map<
764
+ any,
765
+ Set<Scope | ((index: any, newData: DatumType, oldData: DatumType) => void)>
766
+ >
767
+ >();
733
768
  let peeking = 0; // When > 0, we're not subscribing to any changes
734
769
 
735
- function subscribe(target: any, index: symbol|string|number, observer: Scope | ((index: any, newData: DatumType, oldData: DatumType) => void) = currentScope) {
770
+ function subscribe(
771
+ target: any,
772
+ index: symbol | string | number,
773
+ observer:
774
+ | Scope
775
+ | ((
776
+ index: any,
777
+ newData: DatumType,
778
+ oldData: DatumType,
779
+ ) => void) = currentScope,
780
+ ) {
736
781
  if (observer === ROOT_SCOPE || peeking) return;
737
782
 
738
783
  let byTarget = subscribers.get(target);
739
- if (!byTarget) subscribers.set(target, byTarget = new Map());
784
+ if (!byTarget) subscribers.set(target, (byTarget = new Map()));
740
785
 
741
786
  // No need to subscribe to specific keys if we're already subscribed to ANY
742
787
  if (index !== ANY_SYMBOL && byTarget.get(ANY_SYMBOL)?.has(observer)) return;
743
788
 
744
789
  let byIndex = byTarget.get(index);
745
- if (!byIndex) byTarget.set(index, byIndex = new Set());
790
+ if (!byIndex) byTarget.set(index, (byIndex = new Set()));
746
791
 
747
792
  if (byIndex.has(observer)) return;
748
793
 
749
794
  byIndex.add(observer);
750
-
795
+
751
796
  if (observer === currentScope) {
752
797
  currentScope.cleaners.push(byIndex);
753
798
  } else {
754
- currentScope.cleaners.push(function() {
799
+ currentScope.cleaners.push(() => {
755
800
  byIndex.delete(observer);
756
801
  });
757
802
  }
758
803
  }
759
804
 
760
- export function onEach<T>(target: Array<undefined|T>, render: (value: T, index: number) => void, makeKey?: (value: T, key: any) => SortKeyType): void;
761
- export function onEach<K extends string|number|symbol,T>(target: Record<K,undefined|T>, render: (value: T, index: K) => void, makeKey?: (value: T, key: K) => SortKeyType): void;
805
+ export function onEach<T>(
806
+ target: Array<undefined | T>,
807
+ render: (value: T, index: number) => void,
808
+ makeKey?: (value: T, key: any) => SortKeyType,
809
+ ): void;
810
+ export function onEach<K extends string | number | symbol, T>(
811
+ target: Record<K, undefined | T>,
812
+ render: (value: T, index: K) => void,
813
+ makeKey?: (value: T, key: K) => SortKeyType,
814
+ ): void;
762
815
 
763
816
  /**
764
817
  * Reactively iterates over the items of an observable array or object, optionally rendering content for each item.
765
- *
818
+ *
766
819
  * Automatically updates when items are added, removed, or modified.
767
820
  *
768
821
  * @param target The observable array or object to iterate over. Values that are `undefined` are skipped.
@@ -796,7 +849,7 @@ export function onEach<K extends string|number|symbol,T>(target: Record<K,undefi
796
849
  * $(`p:${user.name} (id=${user.id})`);
797
850
  * }, (user) => [user.group, user.name]); // Sort by group, and within each group sort by name
798
851
  * ```
799
- *
852
+ *
800
853
  * @example Iterating an object
801
854
  * ```javascript
802
855
  * const config = proxy({ theme: 'dark', fontSize: 14, showTips: true });
@@ -815,15 +868,20 @@ export function onEach<K extends string|number|symbol,T>(target: Record<K,undefi
815
868
  * ```
816
869
  * @see {@link invertString} To easily create keys for reverse sorting.
817
870
  */
818
- export function onEach(target: TargetType, render: (value: DatumType, index: any) => void, makeKey?: (value: DatumType, key: any) => SortKeyType): void {
819
- if (!target || typeof target !== 'object') throw new Error('onEach requires an object');
871
+ export function onEach(
872
+ target: TargetType,
873
+ render: (value: DatumType, index: any) => void,
874
+ makeKey?: (value: DatumType, key: any) => SortKeyType,
875
+ ): void {
876
+ if (!target || typeof target !== "object")
877
+ throw new Error("onEach requires an object");
820
878
  target = (target as any)[TARGET_SYMBOL] || target;
821
879
 
822
880
  new OnEachScope(target, render, makeKey);
823
881
  }
824
882
 
825
883
  function isObjEmpty(obj: object): boolean {
826
- for(let k in obj) return false;
884
+ for (const k in obj) return false;
827
885
  return true;
828
886
  }
829
887
 
@@ -864,17 +922,24 @@ export function isEmpty(proxied: TargetType): boolean {
864
922
  const scope = currentScope;
865
923
 
866
924
  if (target instanceof Array) {
867
- subscribe(target, 'length', function(index: any, newData: DatumType, oldData: DatumType) {
868
- if (!newData !== !oldData) queue(scope);
869
- });
925
+ subscribe(
926
+ target,
927
+ "length",
928
+ (index: any, newData: DatumType, oldData: DatumType) => {
929
+ if (!newData !== !oldData) queue(scope);
930
+ },
931
+ );
870
932
  return !target.length;
871
- } else {
872
- const result = isObjEmpty(target);
873
- subscribe(target, ANY_SYMBOL, function(index: any, newData: DatumType, oldData: DatumType) {
874
- if (result ? oldData===undefined : newData===undefined) queue(scope);
875
- });
876
- return result;
877
933
  }
934
+ const result = isObjEmpty(target);
935
+ subscribe(
936
+ target,
937
+ ANY_SYMBOL,
938
+ (index: any, newData: DatumType, oldData: DatumType) => {
939
+ if (result ? oldData === undefined : newData === undefined) queue(scope);
940
+ },
941
+ );
942
+ return result;
878
943
  }
879
944
 
880
945
  /** @private */
@@ -909,53 +974,62 @@ export interface ValueRef<T> {
909
974
  * ```
910
975
  */
911
976
  export function count(proxied: TargetType): ValueRef<number> {
912
- if (proxied instanceof Array) return ref(proxied, 'length');
977
+ if (proxied instanceof Array) return ref(proxied, "length");
913
978
 
914
979
  const target = (proxied as any)[TARGET_SYMBOL] || proxied;
915
980
  let cnt = 0;
916
- for(let k in target) if (target[k] !== undefined) cnt++;
917
-
981
+ for (const k in target) if (target[k] !== undefined) cnt++;
982
+
918
983
  const result = proxy(cnt);
919
- subscribe(target, ANY_SYMBOL, function(index: any, newData: DatumType, oldData: DatumType) {
920
- if (oldData===newData) {}
921
- else if (oldData===undefined) result.value = ++cnt;
922
- else if (newData===undefined) result.value = --cnt;
923
- });
984
+ subscribe(
985
+ target,
986
+ ANY_SYMBOL,
987
+ (index: any, newData: DatumType, oldData: DatumType) => {
988
+ if (oldData === newData) {
989
+ } else if (oldData === undefined) result.value = ++cnt;
990
+ else if (newData === undefined) result.value = --cnt;
991
+ },
992
+ );
924
993
 
925
994
  return result;
926
995
  }
927
996
 
928
997
  /** @internal */
929
- export function defaultEmitHandler(target: TargetType, index: string|symbol|number, newData: DatumType, oldData: DatumType) {
998
+ export function defaultEmitHandler(
999
+ target: TargetType,
1000
+ index: string | symbol | number,
1001
+ newData: DatumType,
1002
+ oldData: DatumType,
1003
+ ) {
930
1004
  // We're triggering for values changing from undefined to undefined, as this *may*
931
1005
  // indicate a change from or to `[empty]` (such as `[,1][0]`).
932
1006
  if (newData === oldData && newData !== undefined) return;
933
-
1007
+
934
1008
  const byTarget = subscribers.get(target);
935
- if (byTarget===undefined) return;
1009
+ if (byTarget === undefined) return;
936
1010
 
937
- for(const what of [index, ANY_SYMBOL]) {
938
- let byIndex = byTarget.get(what);
1011
+ for (const what of [index, ANY_SYMBOL]) {
1012
+ const byIndex = byTarget.get(what);
939
1013
  if (byIndex) {
940
- for(let observer of byIndex) {
941
- if (typeof observer === 'function') observer(index, newData, oldData);
942
- else observer.onChange(index, newData, oldData)
1014
+ for (const observer of byIndex) {
1015
+ if (typeof observer === "function") observer(index, newData, oldData);
1016
+ else observer.onChange(index, newData, oldData);
943
1017
  }
944
1018
  }
945
1019
  }
946
1020
  }
947
1021
  let emit = defaultEmitHandler;
948
1022
 
949
-
950
1023
  const objectHandler: ProxyHandler<any> = {
951
1024
  get(target: any, prop: any) {
952
- if (prop===TARGET_SYMBOL) return target;
1025
+ if (prop === TARGET_SYMBOL) return target;
953
1026
  subscribe(target, prop);
954
1027
  return optProxy(target[prop]);
955
1028
  },
956
1029
  set(target: any, prop: any, newData: any) {
957
1030
  // Make sure newData is unproxied
958
- if (typeof newData === 'object' && newData) newData = (newData as any)[TARGET_SYMBOL] || newData;
1031
+ if (typeof newData === "object" && newData)
1032
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
959
1033
  const oldData = target[prop];
960
1034
  if (newData !== oldData) {
961
1035
  target[prop] = newData;
@@ -979,32 +1053,33 @@ const objectHandler: ProxyHandler<any> = {
979
1053
  ownKeys(target: any) {
980
1054
  subscribe(target, ANY_SYMBOL);
981
1055
  return Reflect.ownKeys(target);
982
- }
1056
+ },
983
1057
  };
984
1058
 
985
1059
  function arraySet(target: any, prop: any, newData: any) {
986
1060
  // Make sure newData is unproxied
987
- if (typeof newData === 'object' && newData) newData = (newData as any)[TARGET_SYMBOL] || newData;
1061
+ if (typeof newData === "object" && newData)
1062
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
988
1063
  const oldData = target[prop];
989
1064
  if (newData !== oldData) {
990
- let oldLength = target.length;
991
-
992
- if (prop === 'length') {
1065
+ const oldLength = target.length;
1066
+
1067
+ if (prop === "length") {
993
1068
  target.length = newData;
994
1069
 
995
1070
  // We only need to emit for shrinking, as growing just adds undefineds
996
- for(let i=newData; i<oldLength; i++) {
1071
+ for (let i = newData; i < oldLength; i++) {
997
1072
  emit(target, i, undefined, target[i]);
998
1073
  }
999
1074
  } else {
1000
- const intProp = parseInt(prop)
1075
+ const intProp = Number.parseInt(prop);
1001
1076
  if (intProp.toString() === prop) prop = intProp;
1002
1077
 
1003
1078
  target[prop] = newData;
1004
1079
  emit(target, prop, newData, oldData);
1005
1080
  }
1006
1081
  if (target.length !== oldLength) {
1007
- emit(target, 'length', target.length, oldLength);
1082
+ emit(target, "length", target.length, oldLength);
1008
1083
  }
1009
1084
  runImmediateQueue();
1010
1085
  }
@@ -1013,40 +1088,66 @@ function arraySet(target: any, prop: any, newData: any) {
1013
1088
 
1014
1089
  const arrayHandler: ProxyHandler<any[]> = {
1015
1090
  get(target: any, prop: any) {
1016
- if (prop===TARGET_SYMBOL) return target;
1091
+ if (prop === TARGET_SYMBOL) return target;
1017
1092
  let subProp = prop;
1018
- if (typeof prop !== 'symbol') {
1019
- const intProp = parseInt(prop);
1093
+ if (typeof prop !== "symbol") {
1094
+ const intProp = Number.parseInt(prop);
1020
1095
  if (intProp.toString() === prop) subProp = intProp;
1021
1096
  }
1022
1097
  subscribe(target, subProp);
1023
1098
  return optProxy(target[prop]);
1024
1099
  },
1025
1100
  set: arraySet,
1026
- deleteProperty(target: any, prop: string|symbol) {
1101
+ deleteProperty(target: any, prop: string | symbol) {
1027
1102
  return arraySet(target, prop, undefined);
1028
1103
  },
1029
1104
  };
1030
1105
 
1031
- const proxyMap = new WeakMap<TargetType, /*Proxy*/TargetType>();
1106
+ const proxyMap = new WeakMap<TargetType, /*Proxy*/ TargetType>();
1032
1107
 
1033
1108
  function optProxy(value: any): any {
1034
1109
  // If value is a primitive type or already proxied, just return it
1035
- if (typeof value !== 'object' || !value || value[TARGET_SYMBOL] !== undefined) {
1110
+ if (
1111
+ typeof value !== "object" ||
1112
+ !value ||
1113
+ value[TARGET_SYMBOL] !== undefined
1114
+ ) {
1036
1115
  return value;
1037
1116
  }
1038
1117
  let proxied = proxyMap.get(value);
1039
- if (proxied) return proxied // Only one proxy per target!
1040
-
1041
- proxied = new Proxy(value, value instanceof Array ? arrayHandler : objectHandler);
1118
+ if (proxied) return proxied; // Only one proxy per target!
1119
+
1120
+ proxied = new Proxy(
1121
+ value,
1122
+ value instanceof Array ? arrayHandler : objectHandler,
1123
+ );
1042
1124
  proxyMap.set(value, proxied as TargetType);
1043
1125
  return proxied;
1044
1126
  }
1045
1127
 
1046
-
1047
- export function proxy<T extends DatumType>(target: Array<T>): Array<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
1128
+ export function proxy<T extends DatumType>(
1129
+ target: Array<T>,
1130
+ ): Array<
1131
+ T extends number
1132
+ ? number
1133
+ : T extends string
1134
+ ? string
1135
+ : T extends boolean
1136
+ ? boolean
1137
+ : T
1138
+ >;
1048
1139
  export function proxy<T extends object>(target: T): T;
1049
- export function proxy<T extends DatumType>(target: T): ValueRef<T extends number ? number : T extends string ? string : T extends boolean ? boolean : T>;
1140
+ export function proxy<T extends DatumType>(
1141
+ target: T,
1142
+ ): ValueRef<
1143
+ T extends number
1144
+ ? number
1145
+ : T extends string
1146
+ ? string
1147
+ : T extends boolean
1148
+ ? boolean
1149
+ : T
1150
+ >;
1050
1151
 
1051
1152
  /**
1052
1153
  * Creates a reactive proxy around the given data.
@@ -1087,7 +1188,7 @@ export function proxy<T extends DatumType>(target: T): ValueRef<T extends number
1087
1188
  * observe(() => console.log(name.value)); // Subscribes to value
1088
1189
  * setTimeout(() => name.value = 'UI', 2000); // Triggers the observe function
1089
1190
  * ```
1090
- *
1191
+ *
1091
1192
  * @example Class instance
1092
1193
  * ```typescript
1093
1194
  * class Widget {
@@ -1102,7 +1203,9 @@ export function proxy<T extends DatumType>(target: T): ValueRef<T extends number
1102
1203
  * ```
1103
1204
  */
1104
1205
  export function proxy(target: TargetType): TargetType {
1105
- return optProxy(typeof target === 'object' && target !== null ? target : {value: target});
1206
+ return optProxy(
1207
+ typeof target === "object" && target !== null ? target : { value: target },
1208
+ );
1106
1209
  }
1107
1210
 
1108
1211
  /**
@@ -1125,13 +1228,13 @@ export function proxy(target: TargetType): TargetType {
1125
1228
  * $(() => console.log('proxied', userProxy.name));
1126
1229
  * // The following will only ever log once, as we're not subscribing to any observable
1127
1230
  * $(() => console.log('unproxied', rawUser.name));
1128
- *
1231
+ *
1129
1232
  * // This cause the first log to run again:
1130
1233
  * setTimeout(() => userProxy.name += '!', 1000);
1131
- *
1234
+ *
1132
1235
  * // This doesn't cause any new logs:
1133
1236
  * setTimeout(() => rawUser.name += '?', 2000);
1134
- *
1237
+ *
1135
1238
  * // Both userProxy and rawUser end up as `{name: 'Frank!?'}`
1136
1239
  * setTimeout(() => {
1137
1240
  * console.log('final proxied', userProxy)
@@ -1143,10 +1246,11 @@ export function unproxy<T>(target: T): T {
1143
1246
  return target ? (target as any)[TARGET_SYMBOL] || target : target;
1144
1247
  }
1145
1248
 
1146
- let onDestroyMap: WeakMap<Node, string | Function | true> = new WeakMap();
1249
+ const onDestroyMap: WeakMap<Node, string | ((...args: any[]) => void) | true> =
1250
+ new WeakMap();
1147
1251
 
1148
1252
  function destroyWithClass(element: Element, cls: string) {
1149
- const classes = cls.split('.').filter(c=>c);
1253
+ const classes = cls.split(".").filter((c) => c);
1150
1254
  element.classList.add(...classes);
1151
1255
  setTimeout(() => element.remove(), 2000);
1152
1256
  }
@@ -1203,7 +1307,7 @@ function destroyWithClass(element: Element, cls: string) {
1203
1307
  * console.log(source.nested); // [1, 2, 3] (source was modified)
1204
1308
  * ```
1205
1309
  */
1206
- export function copy<T extends object>(dst: T, src: T, flags: number = 0) {
1310
+ export function copy<T extends object>(dst: T, src: Partial<T>, flags = 0) {
1207
1311
  copyRecurse(dst, src, flags);
1208
1312
  runImmediateQueue();
1209
1313
  }
@@ -1216,14 +1320,14 @@ const COPY_EMIT = 64;
1216
1320
 
1217
1321
  /**
1218
1322
  * Clone an (optionally proxied) object or array.
1219
- *
1323
+ *
1220
1324
  * @param src The object or array to clone. If it is proxied, `clone` will subscribe to any changes to the (nested) data structure.
1221
- * @param flags
1325
+ * @param flags
1222
1326
  * - {@link SHALLOW}: Performs a shallow clone, meaning that only the top-level array or object will be copied, while object/array values will just be references to the original data in `src`.
1223
1327
  * @template T - The type of the objects being copied.
1224
1328
  * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
1225
1329
  */
1226
- export function clone<T extends object>(src: T, flags: number = 0): T {
1330
+ export function clone<T extends object>(src: T, flags = 0): T {
1227
1331
  const dst = Object.create(Object.getPrototypeOf(src)) as T;
1228
1332
  copyRecurse(dst, src, flags);
1229
1333
  return dst;
@@ -1245,87 +1349,94 @@ function copyRecurse(dst: any, src: any, flags: number) {
1245
1349
  if (currentScope !== ROOT_SCOPE && !peeking) flags |= COPY_SUBSCRIBE;
1246
1350
  }
1247
1351
 
1248
- if (flags&COPY_SUBSCRIBE) subscribe(src, ANY_SYMBOL);
1249
- if (src instanceof Array) {
1250
- if (!(dst instanceof Array)) throw new Error("Cannot copy array into object");
1352
+ if (flags & COPY_SUBSCRIBE) subscribe(src, ANY_SYMBOL);
1353
+ if (src instanceof Array) {
1354
+ if (!(dst instanceof Array))
1355
+ throw new Error("Cannot copy array into object");
1251
1356
  const dstLen = dst.length;
1252
1357
  const srcLen = src.length;
1253
- for(let i=0; i<srcLen; i++) {
1254
- copyValue(dst, src, i, flags)
1255
- }
1358
+ for (let i = 0; i < srcLen; i++) {
1359
+ copyValue(dst, src, i, flags);
1360
+ }
1256
1361
  // Leaving additional values in the old array doesn't make sense
1257
1362
  if (srcLen !== dstLen) {
1258
- if (flags&COPY_EMIT) {
1259
- for(let i=srcLen; i<dstLen; i++) {
1363
+ if (flags & COPY_EMIT) {
1364
+ for (let i = srcLen; i < dstLen; i++) {
1260
1365
  const old = dst[i];
1261
1366
  dst[i] = undefined;
1262
1367
  emit(dst, i, undefined, old);
1263
1368
  }
1264
1369
  dst.length = srcLen;
1265
- emit(dst, 'length', srcLen, dstLen);
1370
+ emit(dst, "length", srcLen, dstLen);
1266
1371
  } else {
1267
1372
  dst.length = srcLen;
1268
1373
  }
1269
1374
  }
1270
- } else {
1271
- for(let k in src) {
1272
- copyValue(dst, src, k, flags);
1273
- }
1375
+ } else {
1376
+ for (const k in src) {
1377
+ copyValue(dst, src, k, flags);
1378
+ }
1274
1379
  if (!(flags & MERGE)) {
1275
- for(let k in dst) {
1380
+ for (const k in dst) {
1276
1381
  if (!(k in src)) {
1277
1382
  const old = dst[k];
1278
1383
  delete dst[k];
1279
- if (flags&COPY_EMIT && old !== undefined) {
1384
+ if (flags & COPY_EMIT && old !== undefined) {
1280
1385
  emit(dst, k, undefined, old);
1281
1386
  }
1282
1387
  }
1283
1388
  }
1284
1389
  }
1285
- }
1390
+ }
1286
1391
  }
1287
1392
 
1288
1393
  function copyValue(dst: any, src: any, index: any, flags: number) {
1289
- let dstValue = dst[index];
1290
- let srcValue = src[index];
1394
+ const dstValue = dst[index];
1395
+ let srcValue = src[index];
1291
1396
  if (srcValue !== dstValue) {
1292
- if (srcValue && dstValue && typeof srcValue === 'object' && typeof dstValue === 'object' && (srcValue.constructor === dstValue.constructor || (flags&MERGE && dstValue instanceof Array))) {
1397
+ if (
1398
+ srcValue &&
1399
+ dstValue &&
1400
+ typeof srcValue === "object" &&
1401
+ typeof dstValue === "object" &&
1402
+ (srcValue.constructor === dstValue.constructor ||
1403
+ (flags & MERGE && dstValue instanceof Array))
1404
+ ) {
1293
1405
  copyRecurse(dstValue, srcValue, flags);
1294
1406
  return;
1295
1407
  }
1296
-
1297
- if (!(flags&SHALLOW) && srcValue && typeof srcValue === 'object') {
1408
+
1409
+ if (!(flags & SHALLOW) && srcValue && typeof srcValue === "object") {
1298
1410
  // Create an empty object of the same type
1299
- let copy = Object.create(Object.getPrototypeOf(srcValue));
1411
+ const copy = Object.create(Object.getPrototypeOf(srcValue));
1300
1412
  // Copy all properties to it. This doesn't need to emit anything
1301
1413
  // and MERGE does not apply as this is a new branch.
1302
1414
  copyRecurse(copy, srcValue, 0);
1303
1415
  srcValue = copy;
1304
1416
  }
1305
1417
  const old = dst[index];
1306
- if (flags&MERGE && srcValue == null) delete dst[index];
1418
+ if (flags & MERGE && srcValue == null) delete dst[index];
1307
1419
  else dst[index] = srcValue;
1308
- if (flags&COPY_EMIT) emit(dst, index, srcValue, old)
1420
+ if (flags & COPY_EMIT) emit(dst, index, srcValue, old);
1309
1421
  }
1310
1422
  }
1311
1423
 
1312
-
1313
1424
  interface RefTarget {
1314
- proxy: TargetType
1315
- index: any
1425
+ proxy: TargetType;
1426
+ index: any;
1316
1427
  }
1317
1428
  const refHandler: ProxyHandler<RefTarget> = {
1318
1429
  get(target: RefTarget, prop: any) {
1319
- if (prop===TARGET_SYMBOL) {
1430
+ if (prop === TARGET_SYMBOL) {
1320
1431
  // Create a ref to the unproxied version of the target
1321
1432
  return ref(unproxy(target.proxy), target.index);
1322
1433
  }
1323
- if (prop==="value") {
1434
+ if (prop === "value") {
1324
1435
  return (target.proxy as any)[target.index];
1325
1436
  }
1326
1437
  },
1327
1438
  set(target: any, prop: any, value: any) {
1328
- if (prop==="value") {
1439
+ if (prop === "value") {
1329
1440
  (target.proxy as any)[target.index] = value;
1330
1441
  return true;
1331
1442
  }
@@ -1333,7 +1444,6 @@ const refHandler: ProxyHandler<RefTarget> = {
1333
1444
  },
1334
1445
  };
1335
1446
 
1336
-
1337
1447
  /**
1338
1448
  * Creates a reactive reference (`{ value: T }`-like object) to a specific value
1339
1449
  * within a proxied object or array.
@@ -1364,80 +1474,101 @@ const refHandler: ProxyHandler<RefTarget> = {
1364
1474
  * text: ref(formData, 'color'),
1365
1475
  * $color: ref(formData, 'color')
1366
1476
  * });
1367
- *
1477
+ *
1368
1478
  * // Changes are actually stored in formData - this causes logs like `{color: "Blue", velocity 42}`
1369
1479
  * $(() => console.log(formData))
1370
1480
  * ```
1371
1481
  */
1372
- export function ref<T extends TargetType, K extends keyof T>(target: T, index: K): ValueRef<T[K]> {
1373
- return new Proxy({proxy: target, index}, refHandler) as any as ValueRef<T[K]>;
1482
+ export function ref<T extends TargetType, K extends keyof T>(
1483
+ target: T,
1484
+ index: K,
1485
+ ): ValueRef<T[K]> {
1486
+ return new Proxy({ proxy: target, index }, refHandler) as any as ValueRef<
1487
+ T[K]
1488
+ >;
1374
1489
  }
1375
1490
 
1376
-
1377
1491
  function applyBind(el: HTMLInputElement, target: any) {
1378
1492
  let onProxyChange: () => void;
1379
1493
  let onInputChange: () => void;
1380
- let type = el.getAttribute('type');
1381
- let value = unproxy(target).value;
1382
- if (type === 'checkbox') {
1494
+ const type = el.getAttribute("type");
1495
+ const value = unproxy(target).value;
1496
+ if (type === "checkbox") {
1383
1497
  if (value === undefined) target.value = el.checked;
1384
- onProxyChange = () => el.checked = target.value;
1385
- onInputChange = () => target.value = el.checked;
1386
- } else if (type === 'radio') {
1498
+ onProxyChange = () => {
1499
+ el.checked = target.value;
1500
+ };
1501
+ onInputChange = () => {
1502
+ target.value = el.checked;
1503
+ };
1504
+ } else if (type === "radio") {
1387
1505
  if (value === undefined && el.checked) target.value = el.value;
1388
- onProxyChange = () => el.checked = (target.value === el.value);
1506
+ onProxyChange = () => {
1507
+ el.checked = target.value === el.value;
1508
+ };
1389
1509
  onInputChange = () => {
1390
1510
  if (el.checked) target.value = el.value;
1391
- }
1511
+ };
1392
1512
  } else {
1393
- onInputChange = () => target.value = type==='number' || type==='range' ? (el.value==='' ? null : +el.value) : el.value;
1513
+ onInputChange = () => {
1514
+ target.value =
1515
+ type === "number" || type === "range"
1516
+ ? el.value === ""
1517
+ ? null
1518
+ : +el.value
1519
+ : el.value;
1520
+ };
1394
1521
  if (value === undefined) onInputChange();
1395
1522
  onProxyChange = () => {
1396
1523
  el.value = target.value;
1397
- if (el.tagName==='SELECT' && el.value != target.value) throw new Error(`SELECT has no '${target.value}' OPTION (yet)`);
1398
- }
1524
+ // biome-ignore lint/suspicious/noDoubleEquals: it's fine for numbers to be casts to strings here
1525
+ if (el.tagName === "SELECT" && el.value != target.value)
1526
+ throw new Error(`SELECT has no '${target.value}' OPTION (yet)`);
1527
+ };
1399
1528
  }
1400
1529
  observe(onProxyChange);
1401
- el.addEventListener('input', onInputChange);
1530
+ el.addEventListener("input", onInputChange);
1402
1531
  clean(() => {
1403
- el.removeEventListener('input', onInputChange);
1532
+ el.removeEventListener("input", onInputChange);
1404
1533
  });
1405
1534
  }
1406
1535
 
1407
- const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1408
- create: function(value: any) {
1536
+ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
1537
+ create: (value: any) => {
1409
1538
  const el = currentScope.parentElement;
1410
1539
  if (currentScope !== topRedrawScope) return;
1411
- if (typeof value === 'function') {
1540
+ if (typeof value === "function") {
1412
1541
  value(el);
1413
1542
  } else {
1414
- const classes = value.split('.').filter((c: any)=>c);
1543
+ const classes = value.split(".").filter((c: any) => c);
1415
1544
  el.classList.add(...classes);
1416
- (async function(){ // attempt to prevent layout trashing
1545
+ (async () => {
1546
+ // attempt to prevent layout trashing
1417
1547
  (el as HTMLElement).offsetHeight; // trigger layout
1418
1548
  el.classList.remove(...classes);
1419
1549
  })();
1420
1550
  }
1421
1551
  },
1422
- destroy: function(value: any) {
1552
+ destroy: (value: any) => {
1423
1553
  const el = currentScope.parentElement;
1424
1554
  onDestroyMap.set(el, value);
1425
1555
  },
1426
- html: function(value: any) {
1427
- let tmpParent = document.createElement(currentScope.parentElement.tagName);
1428
- tmpParent.innerHTML = ''+value;
1429
- while(tmpParent.firstChild) addNode(tmpParent.firstChild);
1556
+ html: (value: any) => {
1557
+ const tmpParent = document.createElement(
1558
+ currentScope.parentElement.tagName,
1559
+ );
1560
+ tmpParent.innerHTML = `${value}`;
1561
+ while (tmpParent.firstChild) addNode(tmpParent.firstChild);
1430
1562
  },
1431
- text: function(value: any) {
1563
+ text: (value: any) => {
1432
1564
  addNode(document.createTextNode(value));
1433
1565
  },
1434
- element: function(value: any) {
1435
- if (!(value instanceof Node)) throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
1566
+ element: (value: any) => {
1567
+ if (!(value instanceof Node))
1568
+ throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
1436
1569
  addNode(value);
1437
1570
  },
1438
- }
1439
-
1440
-
1571
+ };
1441
1572
 
1442
1573
  /**
1443
1574
  * The core function for building reactive user interfaces in Aberdeen. It creates and inserts new DOM elements
@@ -1485,12 +1616,12 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1485
1616
  * $color: 'red'
1486
1617
  * });
1487
1618
  * ```
1488
- *
1619
+ *
1489
1620
  * @example Create Nested Elements
1490
1621
  * ```typescript
1491
1622
  * let inputElement: Element = $('label:Click me', 'input', {type: 'checkbox'});
1492
1623
  * // You should usually not touch raw DOM elements, unless when integrating
1493
- * // with non-Aberdeen code.
1624
+ * // with non-Aberdeen code.
1494
1625
  * console.log('DOM element:', inputElement);
1495
1626
  * ```
1496
1627
  *
@@ -1525,62 +1656,71 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1525
1656
  * ```
1526
1657
  */
1527
1658
 
1528
-
1529
- export function $(...args: (string | null | undefined | false | (() => void) | Record<string,any>)[]): void | Element {
1530
- let savedCurrentScope;
1531
- let err;
1532
- let result;
1533
-
1534
- for(let arg of args) {
1659
+ export function $(
1660
+ ...args: (
1661
+ | string
1662
+ | null
1663
+ | undefined
1664
+ | false
1665
+ | (() => void)
1666
+ | Record<string, any>
1667
+ )[]
1668
+ ): undefined | Element {
1669
+ let savedCurrentScope: undefined | ContentScope;
1670
+ let err: undefined | string;
1671
+ let result: undefined | Element;
1672
+
1673
+ for (let arg of args) {
1535
1674
  if (arg == null || arg === false) continue;
1536
- if (typeof arg === 'string') {
1537
- let text, classes: undefined | string;
1538
- const textPos = arg.indexOf(':');
1675
+ if (typeof arg === "string") {
1676
+ let text: undefined | string;
1677
+ let classes: undefined | string;
1678
+ const textPos = arg.indexOf(":");
1539
1679
  if (textPos >= 0) {
1540
- text = arg.substring(textPos+1);
1541
- arg = arg.substring(0,textPos);
1680
+ text = arg.substring(textPos + 1);
1681
+ arg = arg.substring(0, textPos);
1542
1682
  }
1543
- const classPos = arg.indexOf('.');
1683
+ const classPos = arg.indexOf(".");
1544
1684
  if (classPos >= 0) {
1545
- classes = arg.substring(classPos+1);
1685
+ classes = arg.substring(classPos + 1);
1546
1686
  arg = arg.substring(0, classPos);
1547
1687
  }
1548
- if (arg === '') { // Add text or classes to parent
1688
+ if (arg === "") {
1689
+ // Add text or classes to parent
1549
1690
  if (text) addNode(document.createTextNode(text));
1550
1691
  if (classes) {
1551
1692
  const el = currentScope.parentElement;
1552
- el.classList.add(...classes.split('.'));
1693
+ el.classList.add(...classes.split("."));
1553
1694
  if (!savedCurrentScope) {
1554
- clean(() => el.classList.remove(...classes.split('.')));
1695
+ clean(() => el.classList.remove(...classes.split(".")));
1555
1696
  }
1556
1697
  }
1557
- } else if (arg.indexOf(' ') >= 0) {
1698
+ } else if (arg.indexOf(" ") >= 0) {
1558
1699
  err = `Tag '${arg}' cannot contain space`;
1559
1700
  break;
1560
1701
  } else {
1561
1702
  result = document.createElement(arg);
1562
- if (classes) result.className = classes.replaceAll('.', ' ');
1703
+ if (classes) result.className = classes.replaceAll(".", " ");
1563
1704
  if (text) result.textContent = text;
1564
1705
  addNode(result);
1565
1706
  if (!savedCurrentScope) {
1566
1707
  savedCurrentScope = currentScope;
1567
1708
  }
1568
- let newScope = new ChainedScope(result, true);
1709
+ const newScope = new ChainedScope(result, true);
1569
1710
  newScope.lastChild = result.lastChild || undefined;
1570
1711
  if (topRedrawScope === currentScope) topRedrawScope = newScope;
1571
1712
  currentScope = newScope;
1572
1713
  }
1573
- }
1574
- else if (typeof arg === 'object') {
1714
+ } else if (typeof arg === "object") {
1575
1715
  if (arg.constructor !== Object) {
1576
1716
  err = `Unexpected argument: ${arg}`;
1577
1717
  break;
1578
1718
  }
1579
- for(const key in arg) {
1719
+ for (const key in arg) {
1580
1720
  const val = arg[key];
1581
1721
  applyArg(key, val);
1582
1722
  }
1583
- } else if (typeof arg === 'function') {
1723
+ } else if (typeof arg === "function") {
1584
1724
  new RegularScope(currentScope.parentElement, arg);
1585
1725
  } else {
1586
1726
  err = `Unexpected argument: ${arg}`;
@@ -1649,71 +1789,88 @@ let cssCount = 0;
1649
1789
  * color: "#107ab0",
1650
1790
  * }
1651
1791
  * }, true); // Pass true for global
1652
- *
1792
+ *
1653
1793
  * $('a:Styled link');
1654
1794
  * ```
1655
1795
  */
1656
- export function insertCss(style: object, global: boolean = false): string {
1657
- const prefix = global ? "" : ".AbdStl" + ++cssCount;
1658
- let css = styleToCss(style, prefix);
1659
- if (css) $('style:'+css);
1796
+ export function insertCss(style: object, global = false): string {
1797
+ const prefix = global ? "" : `.AbdStl${++cssCount}`;
1798
+ const css = styleToCss(style, prefix);
1799
+ if (css) $(`style:${css}`);
1660
1800
  return prefix;
1661
1801
  }
1662
1802
 
1663
1803
  function styleToCss(style: object, prefix: string): string {
1664
- let props = '';
1665
- let rules = '';
1666
- for(const kOr in style) {
1804
+ let props = "";
1805
+ let rules = "";
1806
+ for (const kOr in style) {
1667
1807
  const v = (style as any)[kOr];
1668
- for(const k of kOr.split(/, ?/g)) {
1669
- if (v && typeof v === 'object') {
1670
- if (k.startsWith('@')) { // media queries
1671
- rules += k + '{\n' + styleToCss(v, prefix) + '}\n';
1808
+ for (const k of kOr.split(/, ?/g)) {
1809
+ if (v && typeof v === "object") {
1810
+ if (k.startsWith("@")) {
1811
+ // media queries
1812
+ rules += `${k}{\n${styleToCss(v, prefix)}}\n`;
1672
1813
  } else {
1673
- rules += styleToCss(v, k.includes('&') ? k.replace(/&/g, prefix) : prefix+' '+k);
1814
+ rules += styleToCss(
1815
+ v,
1816
+ k.includes("&") ? k.replace(/&/g, prefix) : `${prefix} ${k}`,
1817
+ );
1674
1818
  }
1675
1819
  } else {
1676
- props += k.replace(/[A-Z]/g, letter => '-'+letter.toLowerCase()) +":"+v+";";
1820
+ props += `${k.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}:${v};`;
1677
1821
  }
1678
1822
  }
1679
1823
  }
1680
- if (props) rules = (prefix.trimStart() || '*') + '{'+props+'}\n' + rules;
1824
+ if (props) rules = `${prefix.trimStart() || "*"}{${props}}\n${rules}`;
1681
1825
  return rules;
1682
1826
  }
1683
1827
 
1684
1828
  function applyArg(key: string, value: any) {
1685
1829
  const el = currentScope.parentElement;
1686
- if (typeof value === 'object' && value !== null && value[TARGET_SYMBOL]) { // Value is a proxy
1687
- if (key === 'bind') {
1688
- applyBind(el as HTMLInputElement, value)
1830
+ if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
1831
+ // Value is a proxy
1832
+ if (key === "bind") {
1833
+ applyBind(el as HTMLInputElement, value);
1689
1834
  } else {
1690
- new SetArgScope(el, key, value)
1835
+ new SetArgScope(el, key, value);
1691
1836
  // SetArgScope will (repeatedly) call `applyArg` again with the actual value
1692
1837
  }
1693
- } else if (key[0] === '.') { // CSS class(es)
1694
- const classes = key.substring(1).split('.');
1838
+ } else if (key[0] === ".") {
1839
+ // CSS class(es)
1840
+ const classes = key.substring(1).split(".");
1695
1841
  if (value) el.classList.add(...classes);
1696
1842
  else el.classList.remove(...classes);
1697
- } else if (key[0] === '$') { // Style
1843
+ } else if (key[0] === "$") {
1844
+ // Style
1698
1845
  const name = key.substring(1);
1699
- if (value==null || value===false) (el as any).style[name] = ''
1700
- else (el as any).style[name] = ''+value;
1701
- } else if (value == null) { // Value left empty
1846
+ if (value == null || value === false) (el as any).style[name] = "";
1847
+ else (el as any).style[name] = `${value}`;
1848
+ } else if (value == null) {
1849
+ // Value left empty
1702
1850
  // Do nothing
1703
- } else if (key in SPECIAL_PROPS) { // Special property
1851
+ } else if (key in SPECIAL_PROPS) {
1852
+ // Special property
1704
1853
  SPECIAL_PROPS[key](value);
1705
- } else if (typeof value === 'function') { // Event listener
1854
+ } else if (typeof value === "function") {
1855
+ // Event listener
1706
1856
  el.addEventListener(key, value);
1707
1857
  clean(() => el.removeEventListener(key, value));
1708
- } else if (value===true || value===false || key==='value' || key==='selectedIndex') { // DOM property
1858
+ } else if (
1859
+ value === true ||
1860
+ value === false ||
1861
+ key === "value" ||
1862
+ key === "selectedIndex"
1863
+ ) {
1864
+ // DOM property
1709
1865
  (el as any)[key] = value;
1710
- } else { // HTML attribute
1866
+ } else {
1867
+ // HTML attribute
1711
1868
  el.setAttribute(key, value);
1712
1869
  }
1713
1870
  }
1714
1871
 
1715
1872
  function defaultOnError(error: Error) {
1716
- console.error('Error while in Aberdeen render:', error);
1873
+ console.error("Error while in Aberdeen render:", error);
1717
1874
  return true;
1718
1875
  }
1719
1876
  let onError: (error: Error) => boolean | undefined = defaultOnError;
@@ -1749,7 +1906,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1749
1906
  *
1750
1907
  * return false; // Suppress default console log and DOM error message
1751
1908
  * });
1752
- *
1909
+ *
1753
1910
  * // Styling for our custom error message
1754
1911
  * insertCss({
1755
1912
  * '.error-message': {
@@ -1760,7 +1917,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1760
1917
  * padding: '2px 4px',
1761
1918
  * }
1762
1919
  * }, true); // global style
1763
- *
1920
+ *
1764
1921
  * // Cause an error within a render scope.
1765
1922
  * $('div.box', () => {
1766
1923
  * // Will cause our error handler to insert an error message within the box
@@ -1768,11 +1925,12 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1768
1925
  * })
1769
1926
  * ```
1770
1927
  */
1771
- export function setErrorHandler(handler?: (error: Error) => boolean | undefined) {
1928
+ export function setErrorHandler(
1929
+ handler?: (error: Error) => boolean | undefined,
1930
+ ) {
1772
1931
  onError = handler || defaultOnError;
1773
1932
  }
1774
1933
 
1775
-
1776
1934
  /**
1777
1935
  * Gets the parent DOM `Element` where nodes created by {@link $} would currently be inserted.
1778
1936
  *
@@ -1801,7 +1959,6 @@ export function getParentElement(): Element {
1801
1959
  return currentScope.parentElement;
1802
1960
  }
1803
1961
 
1804
-
1805
1962
  /**
1806
1963
  * Registers a cleanup function to be executed just before the current reactive scope
1807
1964
  * is destroyed or redraws.
@@ -1826,14 +1983,14 @@ export function getParentElement(): Element {
1826
1983
  * // we don't want to subscribe.
1827
1984
  * peek(() => sum.value += item);
1828
1985
  * // Clean gets called before each rerun for a certain item index
1829
- * // No need for peek here, as the clean code doesn't run in an
1986
+ * // No need for peek here, as the clean code doesn't run in an
1830
1987
  * // observe scope.
1831
1988
  * clean(() => sum.value -= item);
1832
1989
  * })
1833
- *
1990
+ *
1834
1991
  * // Show the sum
1835
1992
  * $('h1', {text: sum});
1836
- *
1993
+ *
1837
1994
  * // Make random changes to the array
1838
1995
  * const rnd = () => 0|(Math.random()*20);
1839
1996
  * setInterval(() => myArray[rnd()] = rnd(), 1000);
@@ -1844,7 +2001,6 @@ export function clean(cleaner: () => void) {
1844
2001
  currentScope.cleaners.push(cleaner);
1845
2002
  }
1846
2003
 
1847
-
1848
2004
  /**
1849
2005
  * Creates a reactive scope that automatically re-executes the provided function
1850
2006
  * whenever any proxied data (created by {@link proxy}) read during its last execution changes, storing
@@ -1876,9 +2032,9 @@ export function clean(cleaner: () => void) {
1876
2032
  * });
1877
2033
  * });
1878
2034
  * ```
1879
- *
2035
+ *
1880
2036
  * ***Note*** that the above could just as easily be done using `$(func)` instead of `observe(func)`.
1881
- *
2037
+ *
1882
2038
  * @example Observation with return value
1883
2039
  * ```typescript
1884
2040
  * const counter = proxy(0);
@@ -1893,8 +2049,8 @@ export function clean(cleaner: () => void) {
1893
2049
  * @overload
1894
2050
  * @param func Func without a return value.
1895
2051
  */
1896
- export function observe<T extends (DatumType | void)>(func: () => T): ValueRef<T> {
1897
- return (new ResultScope<T>(currentScope.parentElement, func)).result;
2052
+ export function observe<T>(func: () => T): ValueRef<T> {
2053
+ return new ResultScope<T>(currentScope.parentElement, func).result;
1898
2054
  }
1899
2055
 
1900
2056
  /**
@@ -1940,7 +2096,7 @@ export function immediateObserve(func: () => void) {
1940
2096
  * Calls to {@link $} inside `func` will append nodes to `parentElement`.
1941
2097
  * You can nest {@link observe} or other {@link $} scopes within `func`.
1942
2098
  * Use {@link unmountAll} to clean up all mounted scopes and their DOM nodes.
1943
- *
2099
+ *
1944
2100
  * Mounting scopes happens reactively, meaning that if this function is called from within another
1945
2101
  * ({@link observe} or {@link $} or {@link mount}) scope that gets cleaned up, so will the mount.
1946
2102
  *
@@ -1951,7 +2107,7 @@ export function immediateObserve(func: () => void) {
1951
2107
  * ```javascript
1952
2108
  * // Create a pre-existing DOM structure (without Aberdeen)
1953
2109
  * document.body.innerHTML = `<h3>Static content <span id="title-extra"></span></h3><div class="box" id="app-root"></div>`;
1954
- *
2110
+ *
1955
2111
  * import { mount, $, proxy } from 'aberdeen';
1956
2112
  *
1957
2113
  * const runTime = proxy(0);
@@ -1968,7 +2124,7 @@ export function immediateObserve(func: () => void) {
1968
2124
  * }
1969
2125
  * });
1970
2126
  * ```
1971
- *
2127
+ *
1972
2128
  * Note how the inner mount behaves reactively as well, automatically unmounting when it's parent observer scope re-runs.
1973
2129
  */
1974
2130
 
@@ -2021,9 +2177,15 @@ export function peek<T>(func: () => T): T {
2021
2177
  }
2022
2178
 
2023
2179
  /** When using an object as `source`. */
2024
- export function map<IN,OUT>(source: Record<string|symbol,IN>, func: (value: IN, index: string|symbol) => undefined|OUT): Record<string|symbol,OUT>;
2180
+ export function map<IN, OUT>(
2181
+ source: Record<string | symbol, IN>,
2182
+ func: (value: IN, index: string | symbol) => undefined | OUT,
2183
+ ): Record<string | symbol, OUT>;
2025
2184
  /** When using an array as `source`. */
2026
- export function map<IN,OUT>(source: Array<IN>, func: (value: IN, index: number) => undefined|OUT): Array<OUT>;
2185
+ export function map<IN, OUT>(
2186
+ source: Array<IN>,
2187
+ func: (value: IN, index: number) => undefined | OUT,
2188
+ ): Array<OUT>;
2027
2189
  /**
2028
2190
  * Reactively maps/filters items from a proxied source array or object to a new proxied array or object.
2029
2191
  *
@@ -2067,24 +2229,34 @@ export function map<IN,OUT>(source: Array<IN>, func: (value: IN, index: number)
2067
2229
  * // activeUserNames becomes proxy({ u1: 'Alice', u2: 'Bob', u3: 'Charlie' })
2068
2230
  * ```
2069
2231
  */
2070
- export function map(source: any, func: (value: DatumType, key: any) => any): any {
2071
- let out = optProxy(source instanceof Array ? [] : {});
2072
- onEach(source, (item: DatumType, key: symbol|string|number) => {
2073
- let value = func(item, key);
2232
+ export function map(
2233
+ source: any,
2234
+ func: (value: DatumType, key: any) => any,
2235
+ ): any {
2236
+ const out = optProxy(source instanceof Array ? [] : {});
2237
+ onEach(source, (item: DatumType, key: symbol | string | number) => {
2238
+ const value = func(item, key);
2074
2239
  if (value !== undefined) {
2075
2240
  out[key] = value;
2076
2241
  clean(() => {
2077
2242
  delete out[key];
2078
- })
2243
+ });
2079
2244
  }
2080
- })
2081
- return out
2245
+ });
2246
+ return out;
2082
2247
  }
2083
2248
 
2084
2249
  /** When using an array as `source`. */
2085
- export function multiMap<IN,OUT extends {[key: string|symbol]: DatumType}>(source: Array<IN>, func: (value: IN, index: number) => OUT | undefined): OUT;
2250
+ export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2251
+ source: Array<IN>,
2252
+ func: (value: IN, index: number) => OUT | undefined,
2253
+ ): OUT;
2086
2254
  /** When using an object as `source`. */
2087
- export function multiMap<K extends string|number|symbol,IN,OUT extends {[key: string|symbol]: DatumType}>(source: Record<K,IN>, func: (value: IN, index: K) => OUT | undefined): OUT;
2255
+ export function multiMap<
2256
+ K extends string | number | symbol,
2257
+ IN,
2258
+ OUT extends { [key: string | symbol]: DatumType },
2259
+ >(source: Record<K, IN>, func: (value: IN, index: K) => OUT | undefined): OUT;
2088
2260
  /**
2089
2261
  * Reactively maps items from a source proxy (array or object) to a target proxied object,
2090
2262
  * where each source item can contribute multiple key-value pairs to the target.
@@ -2119,31 +2291,44 @@ export function multiMap<K extends string|number|symbol,IN,OUT extends {[key: st
2119
2291
  * [item.id+item.id]: item.value*10,
2120
2292
  * }));
2121
2293
  * // itemsById is proxy({ a: 10, aa: 100, b: 20, bb: 200 })
2122
- *
2294
+ *
2123
2295
  * $(() => console.log(itemsById));
2124
2296
  *
2125
2297
  * items.push({ id: 'c', value: 30 });
2126
2298
  * // itemsById becomes proxy({ a: 10, aa: 100, b: 20, bb: 200, c: 30, cc: 300 })
2127
2299
  * ```
2128
2300
  */
2129
- export function multiMap(source: any, func: (value: DatumType, key: any) => Record<string|symbol,DatumType>): any {
2130
- let out = optProxy({});
2131
- onEach(source, (item: DatumType, key: symbol|string|number) => {
2132
- let pairs = func(item, key);
2301
+ export function multiMap(
2302
+ source: any,
2303
+ func: (value: DatumType, key: any) => Record<string | symbol, DatumType>,
2304
+ ): any {
2305
+ const out = optProxy({});
2306
+ onEach(source, (item: DatumType, key: symbol | string | number) => {
2307
+ const pairs = func(item, key);
2133
2308
  if (pairs) {
2134
- for(let key in pairs) out[key] = pairs[key];
2309
+ for (const key in pairs) out[key] = pairs[key];
2135
2310
  clean(() => {
2136
- for(let key in pairs) delete out[key];
2137
- })
2311
+ for (const key in pairs) delete out[key];
2312
+ });
2138
2313
  }
2139
- })
2140
- return out
2314
+ });
2315
+ return out;
2141
2316
  }
2142
2317
 
2143
2318
  /** When using an object as `array`. */
2144
- export function partition<OUT_K extends string|number|symbol, IN_V>(source: IN_V[], func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[]): Record<OUT_K,Record<number,IN_V>>;
2319
+ export function partition<OUT_K extends string | number | symbol, IN_V>(
2320
+ source: IN_V[],
2321
+ func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[],
2322
+ ): Record<OUT_K, Record<number, IN_V>>;
2145
2323
  /** When using an object as `source`. */
2146
- export function partition<IN_K extends string|number|symbol, OUT_K extends string|number|symbol, IN_V>(source: Record<IN_K,IN_V>, func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[]): Record<OUT_K,Record<IN_K,IN_V>>;
2324
+ export function partition<
2325
+ IN_K extends string | number | symbol,
2326
+ OUT_K extends string | number | symbol,
2327
+ IN_V,
2328
+ >(
2329
+ source: Record<IN_K, IN_V>,
2330
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2331
+ ): Record<OUT_K, Record<IN_K, IN_V>>;
2147
2332
 
2148
2333
  /**
2149
2334
  * @overload
@@ -2189,10 +2374,10 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2189
2374
  * // Partition products by category. Output keys are categories (string).
2190
2375
  * // Inner keys are original array indices (number).
2191
2376
  * const productsByCategory = partition(products, (product) => product.category);
2192
- *
2377
+ *
2193
2378
  * // Reactively show the data structure
2194
2379
  * dump(productsByCategory);
2195
- *
2380
+ *
2196
2381
  * // Make random changes to the categories, to show reactiveness
2197
2382
  * setInterval(() => products[0|(Math.random()*3)].category = ['Snack','Fruit','Veg'][0|(Math.random()*3)], 2000);
2198
2383
  * ```
@@ -2212,31 +2397,37 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2212
2397
  * console.log(usersByTag);
2213
2398
  * ```
2214
2399
  */
2215
- export function partition<IN_K extends string|number|symbol, OUT_K extends string|number|symbol, IN_V>(source: Record<IN_K,IN_V>, func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[]): Record<OUT_K,Record<IN_K,IN_V>> {
2216
- const unproxiedOut = {} as Record<OUT_K,Record<IN_K,IN_V>>;
2400
+ export function partition<
2401
+ IN_K extends string | number | symbol,
2402
+ OUT_K extends string | number | symbol,
2403
+ IN_V,
2404
+ >(
2405
+ source: Record<IN_K, IN_V>,
2406
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2407
+ ): Record<OUT_K, Record<IN_K, IN_V>> {
2408
+ const unproxiedOut = {} as Record<OUT_K, Record<IN_K, IN_V>>;
2217
2409
  const out = proxy(unproxiedOut);
2218
2410
  onEach(source, (item: IN_V, key: IN_K) => {
2219
- let rsp = func(item, key);
2411
+ const rsp = func(item, key);
2220
2412
  if (rsp != null) {
2221
2413
  const buckets = rsp instanceof Array ? rsp : [rsp];
2222
2414
  if (buckets.length) {
2223
- for(let bucket of buckets) {
2415
+ for (const bucket of buckets) {
2224
2416
  if (unproxiedOut[bucket]) out[bucket][key] = item;
2225
- else out[bucket] = {[key]: item} as Record<IN_K, IN_V>;
2417
+ else out[bucket] = { [key]: item } as Record<IN_K, IN_V>;
2226
2418
  }
2227
2419
  clean(() => {
2228
- for(let bucket of buckets) {
2420
+ for (const bucket of buckets) {
2229
2421
  delete out[bucket][key];
2230
2422
  if (isObjEmpty(unproxiedOut[bucket])) delete out[bucket];
2231
- }
2232
- })
2423
+ }
2424
+ });
2233
2425
  }
2234
2426
  }
2235
- })
2427
+ });
2236
2428
  return out;
2237
2429
  }
2238
2430
 
2239
-
2240
2431
  /**
2241
2432
  * Renders a live, recursive dump of a proxied data structure (or any value)
2242
2433
  * into the DOM at the current {@link $} insertion point.
@@ -2265,28 +2456,28 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2265
2456
  * ```
2266
2457
  */
2267
2458
  export function dump<T>(data: T): T {
2268
- if (data && typeof data === 'object') {
2269
- $({text: data instanceof Array ? "<array>" : "<object>"});
2270
- $('ul', () => {
2459
+ if (data && typeof data === "object") {
2460
+ $({ text: data instanceof Array ? "<array>" : "<object>" });
2461
+ $("ul", () => {
2271
2462
  onEach(data as any, (value, key) => {
2272
- $('li:'+JSON.stringify(key)+": ", () => {
2273
- dump(value)
2274
- })
2275
- })
2276
- })
2463
+ $(`li:${JSON.stringify(key)}: `, () => {
2464
+ dump(value);
2465
+ });
2466
+ });
2467
+ });
2277
2468
  } else {
2278
- $({text: JSON.stringify(data)})
2469
+ $({ text: JSON.stringify(data) });
2279
2470
  }
2280
- return data
2471
+ return data;
2281
2472
  }
2282
2473
 
2283
2474
  /*
2284
- * Helper functions
2285
- */
2475
+ * Helper functions
2476
+ */
2286
2477
 
2287
2478
  /* c8 ignore start */
2288
2479
  function internalError(code: number): never {
2289
- throw new Error("Aberdeen internal error "+code);
2480
+ throw new Error(`Aberdeen internal error ${code}`);
2290
2481
  }
2291
2482
  /* c8 ignore end */
2292
2483
 
@@ -2297,7 +2488,7 @@ function handleError(e: any, showMessage: boolean) {
2297
2488
  console.error(e);
2298
2489
  }
2299
2490
  try {
2300
- if (showMessage) $('div.aberdeen-error:Error');
2491
+ if (showMessage) $("div.aberdeen-error:Error");
2301
2492
  } catch {
2302
2493
  // Error while adding the error marker to the DOM. Apparently, we're in
2303
2494
  // an awkward context. The error should already have been logged by
@@ -2306,7 +2497,15 @@ function handleError(e: any, showMessage: boolean) {
2306
2497
  }
2307
2498
 
2308
2499
  /** @internal */
2309
- export function withEmitHandler(handler: (target: TargetType, index: any, newData: DatumType, oldData: DatumType) => void, func: ()=>void) {
2500
+ export function withEmitHandler(
2501
+ handler: (
2502
+ target: TargetType,
2503
+ index: any,
2504
+ newData: DatumType,
2505
+ oldData: DatumType,
2506
+ ) => void,
2507
+ func: () => void,
2508
+ ) {
2310
2509
  const oldEmitHandler = emit;
2311
2510
  emit = handler;
2312
2511
  try {
@@ -2315,12 +2514,3 @@ export function withEmitHandler(handler: (target: TargetType, index: any, newDat
2315
2514
  emit = oldEmitHandler;
2316
2515
  }
2317
2516
  }
2318
-
2319
- // @ts-ignore
2320
- // c8 ignore next
2321
- if (!String.prototype.replaceAll) String.prototype.replaceAll = function(from, to) { return this.split(from).join(to) }
2322
- declare global {
2323
- interface String {
2324
- replaceAll(from: string, to: string): string;
2325
- }
2326
- }