aberdeen 1.0.4 → 1.0.6

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 > 1) 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,24 @@ 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
+
547
566
  // Help garbage collection:
548
567
  this.byIndex.clear();
549
568
  setTimeout(() => {
550
569
  // Unsure if this is a good idea. It takes time, but presumably makes things a lot easier for GC...
551
- this.sortedSet.clear();
570
+ this.sortedSet.clear();
552
571
  }, 1);
553
572
  }
554
-
573
+
555
574
  getLastNode(): Node | undefined {
556
- for(let scope of this.sortedSet) { // Iterates starting at last child scope.
575
+ for (const scope of this.sortedSet) {
576
+ // Iterates starting at last child scope.
557
577
  const node = scope.getActualLastNode();
558
578
  if (node) return node;
559
579
  }
@@ -564,7 +584,7 @@ class OnEachScope extends Scope {
564
584
  class OnEachItemScope extends ContentScope {
565
585
  sortKey: string | number | undefined; // When undefined, this scope is currently not showing in the list
566
586
  public parentElement: Element;
567
-
587
+
568
588
  constructor(
569
589
  public parent: OnEachScope,
570
590
  public itemIndex: any,
@@ -592,7 +612,7 @@ class OnEachItemScope extends ContentScope {
592
612
  // of the sortedSet now (if we weren't already).
593
613
  // This will do nothing and barely take any time of `this` is already part of the set:
594
614
  this.parent.sortedSet.add(this);
595
-
615
+
596
616
  const preScope = this.parent.sortedSet.prev(this);
597
617
  // As preScope should have inserted itself as its first child, this should
598
618
  // recursively call getPrecedingNode() on preScope in case it doesn't
@@ -610,7 +630,7 @@ class OnEachItemScope extends ContentScope {
610
630
  getActualLastNode(): Node | undefined {
611
631
  let child = this.lastChild;
612
632
 
613
- while(child && child !== this) {
633
+ while (child && child !== this) {
614
634
  if (child instanceof Node) return child;
615
635
  const node = child.getLastNode();
616
636
  if (node) return node;
@@ -621,7 +641,7 @@ class OnEachItemScope extends ContentScope {
621
641
  queueRun() {
622
642
  /* c8 ignore next */
623
643
  if (currentScope !== ROOT_SCOPE) internalError(4);
624
-
644
+
625
645
  // We're not calling `remove` here, as we don't want to remove ourselves from
626
646
  // the sorted set. `redraw` will take care of that, if needed.
627
647
  // Also, we can't use `getLastNode` here, as we've hacked it to return the
@@ -633,12 +653,12 @@ class OnEachItemScope extends ContentScope {
633
653
 
634
654
  this.delete();
635
655
  this.lastChild = this; // apply the hack (see constructor) again
636
-
656
+
637
657
  topRedrawScope = this;
638
658
  this.redraw();
639
659
  topRedrawScope = undefined;
640
660
  }
641
-
661
+
642
662
  redraw() {
643
663
  // Have the makeSortKey function return an ordering int/string/array.
644
664
 
@@ -646,22 +666,28 @@ class OnEachItemScope extends ContentScope {
646
666
  // a wildcard subscription to delete/recreate any scopes when that changes.
647
667
  // We ARE creating a proxy around the value though (in case its an object/array),
648
668
  // so we'll have our own scope subscribe to changes on that.
649
- const value: DatumType = optProxy((this.parent.target as any)[this.itemIndex]);
669
+ const value: DatumType = optProxy(
670
+ (this.parent.target as any)[this.itemIndex],
671
+ );
650
672
 
651
673
  // Since makeSortKey may get() the Store, we'll need to set currentScope first.
652
- let savedScope = currentScope;
674
+ const savedScope = currentScope;
653
675
  currentScope = this;
654
-
655
- let sortKey : undefined | string | number;
676
+
677
+ let sortKey: undefined | string | number;
656
678
  try {
657
679
  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;
680
+ const rawSortKey = this.parent.makeSortKey(value, this.itemIndex);
681
+ if (rawSortKey != null)
682
+ sortKey =
683
+ rawSortKey instanceof Array
684
+ ? rawSortKey.map(partToStr).join("")
685
+ : rawSortKey;
660
686
  } else {
661
687
  sortKey = this.itemIndex;
662
688
  }
663
- if (typeof sortKey === 'number') sortKey = partToStr(sortKey);
664
-
689
+ if (typeof sortKey === "number") sortKey = partToStr(sortKey);
690
+
665
691
  if (this.sortKey !== sortKey) {
666
692
  // If the sortKey is changed, make sure `this` is removed from the
667
693
  // set before setting the new sortKey to it.
@@ -673,8 +699,8 @@ class OnEachItemScope extends ContentScope {
673
699
  // in case no nodes are created. We'll do it just-in-time in `getPrecedingNode`.
674
700
 
675
701
  if (sortKey != null) this.parent.renderer(value, this.itemIndex);
676
- } catch(e) {
677
- handleError(e, sortKey!=null);
702
+ } catch (e) {
703
+ handleError(e, sortKey != null);
678
704
  }
679
705
 
680
706
  currentScope = savedScope;
@@ -705,64 +731,89 @@ class OnEachItemScope extends ContentScope {
705
731
  function addNode(node: Node) {
706
732
  const parentEl = currentScope.parentElement;
707
733
  const prevNode = currentScope.getInsertAfterNode();
708
- parentEl.insertBefore(node, prevNode ? prevNode.nextSibling : parentEl.firstChild);
734
+ parentEl.insertBefore(
735
+ node,
736
+ prevNode ? prevNode.nextSibling : parentEl.firstChild,
737
+ );
709
738
  currentScope.lastChild = node;
710
739
  }
711
740
 
712
-
713
741
  /**
714
- * This global is set during the execution of a `Scope.render`. It is used by
715
- * functions like `$` and `clean`.
716
- */
742
+ * This global is set during the execution of a `Scope.render`. It is used by
743
+ * functions like `$` and `clean`.
744
+ */
717
745
  const ROOT_SCOPE = new RootScope();
718
746
  let currentScope: ContentScope = ROOT_SCOPE;
719
747
 
720
748
  /**
721
- * A special Node observer index to subscribe to any value in the map changing.
722
- */
723
- const ANY_SYMBOL = Symbol('any');
749
+ * A special Node observer index to subscribe to any value in the map changing.
750
+ */
751
+ const ANY_SYMBOL = Symbol("any");
724
752
 
725
753
  /**
726
754
  * When our proxy objects need to lookup `obj[TARGET_SYMBOL]` it returns its
727
755
  * target, to be used in our wrapped methods.
728
756
  */
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)>>>;
757
+ const TARGET_SYMBOL = Symbol("target");
758
+
759
+ const subscribers = new WeakMap<
760
+ TargetType,
761
+ Map<
762
+ any,
763
+ Set<Scope | ((index: any, newData: DatumType, oldData: DatumType) => void)>
764
+ >
765
+ >();
733
766
  let peeking = 0; // When > 0, we're not subscribing to any changes
734
767
 
735
- function subscribe(target: any, index: symbol|string|number, observer: Scope | ((index: any, newData: DatumType, oldData: DatumType) => void) = currentScope) {
768
+ function subscribe(
769
+ target: any,
770
+ index: symbol | string | number,
771
+ observer:
772
+ | Scope
773
+ | ((
774
+ index: any,
775
+ newData: DatumType,
776
+ oldData: DatumType,
777
+ ) => void) = currentScope,
778
+ ) {
736
779
  if (observer === ROOT_SCOPE || peeking) return;
737
780
 
738
781
  let byTarget = subscribers.get(target);
739
- if (!byTarget) subscribers.set(target, byTarget = new Map());
782
+ if (!byTarget) subscribers.set(target, (byTarget = new Map()));
740
783
 
741
784
  // No need to subscribe to specific keys if we're already subscribed to ANY
742
785
  if (index !== ANY_SYMBOL && byTarget.get(ANY_SYMBOL)?.has(observer)) return;
743
786
 
744
787
  let byIndex = byTarget.get(index);
745
- if (!byIndex) byTarget.set(index, byIndex = new Set());
788
+ if (!byIndex) byTarget.set(index, (byIndex = new Set()));
746
789
 
747
790
  if (byIndex.has(observer)) return;
748
791
 
749
792
  byIndex.add(observer);
750
-
793
+
751
794
  if (observer === currentScope) {
752
795
  currentScope.cleaners.push(byIndex);
753
796
  } else {
754
- currentScope.cleaners.push(function() {
797
+ currentScope.cleaners.push(() => {
755
798
  byIndex.delete(observer);
756
799
  });
757
800
  }
758
801
  }
759
802
 
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;
803
+ export function onEach<T>(
804
+ target: Array<undefined | T>,
805
+ render: (value: T, index: number) => void,
806
+ makeKey?: (value: T, key: any) => SortKeyType,
807
+ ): void;
808
+ export function onEach<K extends string | number | symbol, T>(
809
+ target: Record<K, undefined | T>,
810
+ render: (value: T, index: K) => void,
811
+ makeKey?: (value: T, key: K) => SortKeyType,
812
+ ): void;
762
813
 
763
814
  /**
764
815
  * Reactively iterates over the items of an observable array or object, optionally rendering content for each item.
765
- *
816
+ *
766
817
  * Automatically updates when items are added, removed, or modified.
767
818
  *
768
819
  * @param target The observable array or object to iterate over. Values that are `undefined` are skipped.
@@ -796,7 +847,7 @@ export function onEach<K extends string|number|symbol,T>(target: Record<K,undefi
796
847
  * $(`p:${user.name} (id=${user.id})`);
797
848
  * }, (user) => [user.group, user.name]); // Sort by group, and within each group sort by name
798
849
  * ```
799
- *
850
+ *
800
851
  * @example Iterating an object
801
852
  * ```javascript
802
853
  * const config = proxy({ theme: 'dark', fontSize: 14, showTips: true });
@@ -815,15 +866,20 @@ export function onEach<K extends string|number|symbol,T>(target: Record<K,undefi
815
866
  * ```
816
867
  * @see {@link invertString} To easily create keys for reverse sorting.
817
868
  */
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');
869
+ export function onEach(
870
+ target: TargetType,
871
+ render: (value: DatumType, index: any) => void,
872
+ makeKey?: (value: DatumType, key: any) => SortKeyType,
873
+ ): void {
874
+ if (!target || typeof target !== "object")
875
+ throw new Error("onEach requires an object");
820
876
  target = (target as any)[TARGET_SYMBOL] || target;
821
877
 
822
878
  new OnEachScope(target, render, makeKey);
823
879
  }
824
880
 
825
881
  function isObjEmpty(obj: object): boolean {
826
- for(let k in obj) return false;
882
+ for (const k in obj) return false;
827
883
  return true;
828
884
  }
829
885
 
@@ -864,17 +920,24 @@ export function isEmpty(proxied: TargetType): boolean {
864
920
  const scope = currentScope;
865
921
 
866
922
  if (target instanceof Array) {
867
- subscribe(target, 'length', function(index: any, newData: DatumType, oldData: DatumType) {
868
- if (!newData !== !oldData) queue(scope);
869
- });
923
+ subscribe(
924
+ target,
925
+ "length",
926
+ (index: any, newData: DatumType, oldData: DatumType) => {
927
+ if (!newData !== !oldData) queue(scope);
928
+ },
929
+ );
870
930
  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
931
  }
932
+ const result = isObjEmpty(target);
933
+ subscribe(
934
+ target,
935
+ ANY_SYMBOL,
936
+ (index: any, newData: DatumType, oldData: DatumType) => {
937
+ if (result ? oldData === undefined : newData === undefined) queue(scope);
938
+ },
939
+ );
940
+ return result;
878
941
  }
879
942
 
880
943
  /** @private */
@@ -909,53 +972,62 @@ export interface ValueRef<T> {
909
972
  * ```
910
973
  */
911
974
  export function count(proxied: TargetType): ValueRef<number> {
912
- if (proxied instanceof Array) return ref(proxied, 'length');
975
+ if (proxied instanceof Array) return ref(proxied, "length");
913
976
 
914
977
  const target = (proxied as any)[TARGET_SYMBOL] || proxied;
915
978
  let cnt = 0;
916
- for(let k in target) if (target[k] !== undefined) cnt++;
917
-
979
+ for (const k in target) if (target[k] !== undefined) cnt++;
980
+
918
981
  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
- });
982
+ subscribe(
983
+ target,
984
+ ANY_SYMBOL,
985
+ (index: any, newData: DatumType, oldData: DatumType) => {
986
+ if (oldData === newData) {
987
+ } else if (oldData === undefined) result.value = ++cnt;
988
+ else if (newData === undefined) result.value = --cnt;
989
+ },
990
+ );
924
991
 
925
992
  return result;
926
993
  }
927
994
 
928
995
  /** @internal */
929
- export function defaultEmitHandler(target: TargetType, index: string|symbol|number, newData: DatumType, oldData: DatumType) {
996
+ export function defaultEmitHandler(
997
+ target: TargetType,
998
+ index: string | symbol | number,
999
+ newData: DatumType,
1000
+ oldData: DatumType,
1001
+ ) {
930
1002
  // We're triggering for values changing from undefined to undefined, as this *may*
931
1003
  // indicate a change from or to `[empty]` (such as `[,1][0]`).
932
1004
  if (newData === oldData && newData !== undefined) return;
933
-
1005
+
934
1006
  const byTarget = subscribers.get(target);
935
- if (byTarget===undefined) return;
1007
+ if (byTarget === undefined) return;
936
1008
 
937
- for(const what of [index, ANY_SYMBOL]) {
938
- let byIndex = byTarget.get(what);
1009
+ for (const what of [index, ANY_SYMBOL]) {
1010
+ const byIndex = byTarget.get(what);
939
1011
  if (byIndex) {
940
- for(let observer of byIndex) {
941
- if (typeof observer === 'function') observer(index, newData, oldData);
942
- else observer.onChange(index, newData, oldData)
1012
+ for (const observer of byIndex) {
1013
+ if (typeof observer === "function") observer(index, newData, oldData);
1014
+ else observer.onChange(index, newData, oldData);
943
1015
  }
944
1016
  }
945
1017
  }
946
1018
  }
947
1019
  let emit = defaultEmitHandler;
948
1020
 
949
-
950
1021
  const objectHandler: ProxyHandler<any> = {
951
1022
  get(target: any, prop: any) {
952
- if (prop===TARGET_SYMBOL) return target;
1023
+ if (prop === TARGET_SYMBOL) return target;
953
1024
  subscribe(target, prop);
954
1025
  return optProxy(target[prop]);
955
1026
  },
956
1027
  set(target: any, prop: any, newData: any) {
957
1028
  // Make sure newData is unproxied
958
- if (typeof newData === 'object' && newData) newData = (newData as any)[TARGET_SYMBOL] || newData;
1029
+ if (typeof newData === "object" && newData)
1030
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
959
1031
  const oldData = target[prop];
960
1032
  if (newData !== oldData) {
961
1033
  target[prop] = newData;
@@ -979,32 +1051,33 @@ const objectHandler: ProxyHandler<any> = {
979
1051
  ownKeys(target: any) {
980
1052
  subscribe(target, ANY_SYMBOL);
981
1053
  return Reflect.ownKeys(target);
982
- }
1054
+ },
983
1055
  };
984
1056
 
985
1057
  function arraySet(target: any, prop: any, newData: any) {
986
1058
  // Make sure newData is unproxied
987
- if (typeof newData === 'object' && newData) newData = (newData as any)[TARGET_SYMBOL] || newData;
1059
+ if (typeof newData === "object" && newData)
1060
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
988
1061
  const oldData = target[prop];
989
1062
  if (newData !== oldData) {
990
- let oldLength = target.length;
991
-
992
- if (prop === 'length') {
1063
+ const oldLength = target.length;
1064
+
1065
+ if (prop === "length") {
993
1066
  target.length = newData;
994
1067
 
995
1068
  // We only need to emit for shrinking, as growing just adds undefineds
996
- for(let i=newData; i<oldLength; i++) {
1069
+ for (let i = newData; i < oldLength; i++) {
997
1070
  emit(target, i, undefined, target[i]);
998
1071
  }
999
1072
  } else {
1000
- const intProp = parseInt(prop)
1073
+ const intProp = Number.parseInt(prop);
1001
1074
  if (intProp.toString() === prop) prop = intProp;
1002
1075
 
1003
1076
  target[prop] = newData;
1004
1077
  emit(target, prop, newData, oldData);
1005
1078
  }
1006
1079
  if (target.length !== oldLength) {
1007
- emit(target, 'length', target.length, oldLength);
1080
+ emit(target, "length", target.length, oldLength);
1008
1081
  }
1009
1082
  runImmediateQueue();
1010
1083
  }
@@ -1013,40 +1086,66 @@ function arraySet(target: any, prop: any, newData: any) {
1013
1086
 
1014
1087
  const arrayHandler: ProxyHandler<any[]> = {
1015
1088
  get(target: any, prop: any) {
1016
- if (prop===TARGET_SYMBOL) return target;
1089
+ if (prop === TARGET_SYMBOL) return target;
1017
1090
  let subProp = prop;
1018
- if (typeof prop !== 'symbol') {
1019
- const intProp = parseInt(prop);
1091
+ if (typeof prop !== "symbol") {
1092
+ const intProp = Number.parseInt(prop);
1020
1093
  if (intProp.toString() === prop) subProp = intProp;
1021
1094
  }
1022
1095
  subscribe(target, subProp);
1023
1096
  return optProxy(target[prop]);
1024
1097
  },
1025
1098
  set: arraySet,
1026
- deleteProperty(target: any, prop: string|symbol) {
1099
+ deleteProperty(target: any, prop: string | symbol) {
1027
1100
  return arraySet(target, prop, undefined);
1028
1101
  },
1029
1102
  };
1030
1103
 
1031
- const proxyMap = new WeakMap<TargetType, /*Proxy*/TargetType>();
1104
+ const proxyMap = new WeakMap<TargetType, /*Proxy*/ TargetType>();
1032
1105
 
1033
1106
  function optProxy(value: any): any {
1034
1107
  // If value is a primitive type or already proxied, just return it
1035
- if (typeof value !== 'object' || !value || value[TARGET_SYMBOL] !== undefined) {
1108
+ if (
1109
+ typeof value !== "object" ||
1110
+ !value ||
1111
+ value[TARGET_SYMBOL] !== undefined
1112
+ ) {
1036
1113
  return value;
1037
1114
  }
1038
1115
  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);
1116
+ if (proxied) return proxied; // Only one proxy per target!
1117
+
1118
+ proxied = new Proxy(
1119
+ value,
1120
+ value instanceof Array ? arrayHandler : objectHandler,
1121
+ );
1042
1122
  proxyMap.set(value, proxied as TargetType);
1043
1123
  return proxied;
1044
1124
  }
1045
1125
 
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>;
1126
+ export function proxy<T extends DatumType>(
1127
+ target: Array<T>,
1128
+ ): Array<
1129
+ T extends number
1130
+ ? number
1131
+ : T extends string
1132
+ ? string
1133
+ : T extends boolean
1134
+ ? boolean
1135
+ : T
1136
+ >;
1048
1137
  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>;
1138
+ export function proxy<T extends DatumType>(
1139
+ target: T,
1140
+ ): ValueRef<
1141
+ T extends number
1142
+ ? number
1143
+ : T extends string
1144
+ ? string
1145
+ : T extends boolean
1146
+ ? boolean
1147
+ : T
1148
+ >;
1050
1149
 
1051
1150
  /**
1052
1151
  * Creates a reactive proxy around the given data.
@@ -1087,7 +1186,7 @@ export function proxy<T extends DatumType>(target: T): ValueRef<T extends number
1087
1186
  * observe(() => console.log(name.value)); // Subscribes to value
1088
1187
  * setTimeout(() => name.value = 'UI', 2000); // Triggers the observe function
1089
1188
  * ```
1090
- *
1189
+ *
1091
1190
  * @example Class instance
1092
1191
  * ```typescript
1093
1192
  * class Widget {
@@ -1102,7 +1201,9 @@ export function proxy<T extends DatumType>(target: T): ValueRef<T extends number
1102
1201
  * ```
1103
1202
  */
1104
1203
  export function proxy(target: TargetType): TargetType {
1105
- return optProxy(typeof target === 'object' && target !== null ? target : {value: target});
1204
+ return optProxy(
1205
+ typeof target === "object" && target !== null ? target : { value: target },
1206
+ );
1106
1207
  }
1107
1208
 
1108
1209
  /**
@@ -1125,13 +1226,13 @@ export function proxy(target: TargetType): TargetType {
1125
1226
  * $(() => console.log('proxied', userProxy.name));
1126
1227
  * // The following will only ever log once, as we're not subscribing to any observable
1127
1228
  * $(() => console.log('unproxied', rawUser.name));
1128
- *
1229
+ *
1129
1230
  * // This cause the first log to run again:
1130
1231
  * setTimeout(() => userProxy.name += '!', 1000);
1131
- *
1232
+ *
1132
1233
  * // This doesn't cause any new logs:
1133
1234
  * setTimeout(() => rawUser.name += '?', 2000);
1134
- *
1235
+ *
1135
1236
  * // Both userProxy and rawUser end up as `{name: 'Frank!?'}`
1136
1237
  * setTimeout(() => {
1137
1238
  * console.log('final proxied', userProxy)
@@ -1143,10 +1244,11 @@ export function unproxy<T>(target: T): T {
1143
1244
  return target ? (target as any)[TARGET_SYMBOL] || target : target;
1144
1245
  }
1145
1246
 
1146
- let onDestroyMap: WeakMap<Node, string | Function | true> = new WeakMap();
1247
+ const onDestroyMap: WeakMap<Node, string | ((...args: any[]) => void) | true> =
1248
+ new WeakMap();
1147
1249
 
1148
1250
  function destroyWithClass(element: Element, cls: string) {
1149
- const classes = cls.split('.').filter(c=>c);
1251
+ const classes = cls.split(".").filter((c) => c);
1150
1252
  element.classList.add(...classes);
1151
1253
  setTimeout(() => element.remove(), 2000);
1152
1254
  }
@@ -1203,7 +1305,7 @@ function destroyWithClass(element: Element, cls: string) {
1203
1305
  * console.log(source.nested); // [1, 2, 3] (source was modified)
1204
1306
  * ```
1205
1307
  */
1206
- export function copy<T extends object>(dst: T, src: T, flags: number = 0) {
1308
+ export function copy<T extends object>(dst: T, src: Partial<T>, flags = 0) {
1207
1309
  copyRecurse(dst, src, flags);
1208
1310
  runImmediateQueue();
1209
1311
  }
@@ -1216,14 +1318,14 @@ const COPY_EMIT = 64;
1216
1318
 
1217
1319
  /**
1218
1320
  * Clone an (optionally proxied) object or array.
1219
- *
1321
+ *
1220
1322
  * @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
1323
+ * @param flags
1222
1324
  * - {@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
1325
  * @template T - The type of the objects being copied.
1224
1326
  * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
1225
1327
  */
1226
- export function clone<T extends object>(src: T, flags: number = 0): T {
1328
+ export function clone<T extends object>(src: T, flags = 0): T {
1227
1329
  const dst = Object.create(Object.getPrototypeOf(src)) as T;
1228
1330
  copyRecurse(dst, src, flags);
1229
1331
  return dst;
@@ -1245,87 +1347,94 @@ function copyRecurse(dst: any, src: any, flags: number) {
1245
1347
  if (currentScope !== ROOT_SCOPE && !peeking) flags |= COPY_SUBSCRIBE;
1246
1348
  }
1247
1349
 
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");
1350
+ if (flags & COPY_SUBSCRIBE) subscribe(src, ANY_SYMBOL);
1351
+ if (src instanceof Array) {
1352
+ if (!(dst instanceof Array))
1353
+ throw new Error("Cannot copy array into object");
1251
1354
  const dstLen = dst.length;
1252
1355
  const srcLen = src.length;
1253
- for(let i=0; i<srcLen; i++) {
1254
- copyValue(dst, src, i, flags)
1255
- }
1356
+ for (let i = 0; i < srcLen; i++) {
1357
+ copyValue(dst, src, i, flags);
1358
+ }
1256
1359
  // Leaving additional values in the old array doesn't make sense
1257
1360
  if (srcLen !== dstLen) {
1258
- if (flags&COPY_EMIT) {
1259
- for(let i=srcLen; i<dstLen; i++) {
1361
+ if (flags & COPY_EMIT) {
1362
+ for (let i = srcLen; i < dstLen; i++) {
1260
1363
  const old = dst[i];
1261
1364
  dst[i] = undefined;
1262
1365
  emit(dst, i, undefined, old);
1263
1366
  }
1264
1367
  dst.length = srcLen;
1265
- emit(dst, 'length', srcLen, dstLen);
1368
+ emit(dst, "length", srcLen, dstLen);
1266
1369
  } else {
1267
1370
  dst.length = srcLen;
1268
1371
  }
1269
1372
  }
1270
- } else {
1271
- for(let k in src) {
1272
- copyValue(dst, src, k, flags);
1273
- }
1373
+ } else {
1374
+ for (const k in src) {
1375
+ copyValue(dst, src, k, flags);
1376
+ }
1274
1377
  if (!(flags & MERGE)) {
1275
- for(let k in dst) {
1378
+ for (const k in dst) {
1276
1379
  if (!(k in src)) {
1277
1380
  const old = dst[k];
1278
1381
  delete dst[k];
1279
- if (flags&COPY_EMIT && old !== undefined) {
1382
+ if (flags & COPY_EMIT && old !== undefined) {
1280
1383
  emit(dst, k, undefined, old);
1281
1384
  }
1282
1385
  }
1283
1386
  }
1284
1387
  }
1285
- }
1388
+ }
1286
1389
  }
1287
1390
 
1288
1391
  function copyValue(dst: any, src: any, index: any, flags: number) {
1289
- let dstValue = dst[index];
1290
- let srcValue = src[index];
1392
+ const dstValue = dst[index];
1393
+ let srcValue = src[index];
1291
1394
  if (srcValue !== dstValue) {
1292
- if (srcValue && dstValue && typeof srcValue === 'object' && typeof dstValue === 'object' && (srcValue.constructor === dstValue.constructor || (flags&MERGE && dstValue instanceof Array))) {
1395
+ if (
1396
+ srcValue &&
1397
+ dstValue &&
1398
+ typeof srcValue === "object" &&
1399
+ typeof dstValue === "object" &&
1400
+ (srcValue.constructor === dstValue.constructor ||
1401
+ (flags & MERGE && dstValue instanceof Array))
1402
+ ) {
1293
1403
  copyRecurse(dstValue, srcValue, flags);
1294
1404
  return;
1295
1405
  }
1296
-
1297
- if (!(flags&SHALLOW) && srcValue && typeof srcValue === 'object') {
1406
+
1407
+ if (!(flags & SHALLOW) && srcValue && typeof srcValue === "object") {
1298
1408
  // Create an empty object of the same type
1299
- let copy = Object.create(Object.getPrototypeOf(srcValue));
1409
+ const copy = Object.create(Object.getPrototypeOf(srcValue));
1300
1410
  // Copy all properties to it. This doesn't need to emit anything
1301
1411
  // and MERGE does not apply as this is a new branch.
1302
1412
  copyRecurse(copy, srcValue, 0);
1303
1413
  srcValue = copy;
1304
1414
  }
1305
1415
  const old = dst[index];
1306
- if (flags&MERGE && srcValue == null) delete dst[index];
1416
+ if (flags & MERGE && srcValue == null) delete dst[index];
1307
1417
  else dst[index] = srcValue;
1308
- if (flags&COPY_EMIT) emit(dst, index, srcValue, old)
1418
+ if (flags & COPY_EMIT) emit(dst, index, srcValue, old);
1309
1419
  }
1310
1420
  }
1311
1421
 
1312
-
1313
1422
  interface RefTarget {
1314
- proxy: TargetType
1315
- index: any
1423
+ proxy: TargetType;
1424
+ index: any;
1316
1425
  }
1317
1426
  const refHandler: ProxyHandler<RefTarget> = {
1318
1427
  get(target: RefTarget, prop: any) {
1319
- if (prop===TARGET_SYMBOL) {
1428
+ if (prop === TARGET_SYMBOL) {
1320
1429
  // Create a ref to the unproxied version of the target
1321
1430
  return ref(unproxy(target.proxy), target.index);
1322
1431
  }
1323
- if (prop==="value") {
1432
+ if (prop === "value") {
1324
1433
  return (target.proxy as any)[target.index];
1325
1434
  }
1326
1435
  },
1327
1436
  set(target: any, prop: any, value: any) {
1328
- if (prop==="value") {
1437
+ if (prop === "value") {
1329
1438
  (target.proxy as any)[target.index] = value;
1330
1439
  return true;
1331
1440
  }
@@ -1333,7 +1442,6 @@ const refHandler: ProxyHandler<RefTarget> = {
1333
1442
  },
1334
1443
  };
1335
1444
 
1336
-
1337
1445
  /**
1338
1446
  * Creates a reactive reference (`{ value: T }`-like object) to a specific value
1339
1447
  * within a proxied object or array.
@@ -1364,78 +1472,101 @@ const refHandler: ProxyHandler<RefTarget> = {
1364
1472
  * text: ref(formData, 'color'),
1365
1473
  * $color: ref(formData, 'color')
1366
1474
  * });
1367
- *
1475
+ *
1368
1476
  * // Changes are actually stored in formData - this causes logs like `{color: "Blue", velocity 42}`
1369
1477
  * $(() => console.log(formData))
1370
1478
  * ```
1371
1479
  */
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]>;
1480
+ export function ref<T extends TargetType, K extends keyof T>(
1481
+ target: T,
1482
+ index: K,
1483
+ ): ValueRef<T[K]> {
1484
+ return new Proxy({ proxy: target, index }, refHandler) as any as ValueRef<
1485
+ T[K]
1486
+ >;
1374
1487
  }
1375
1488
 
1376
-
1377
- function applyBind(_el: Element, target: any) {
1378
- const el = _el as HTMLInputElement;
1489
+ function applyBind(el: HTMLInputElement, target: any) {
1379
1490
  let onProxyChange: () => void;
1380
1491
  let onInputChange: () => void;
1381
- let type = el.getAttribute('type');
1382
- let value = unproxy(target).value;
1383
- if (type === 'checkbox') {
1492
+ const type = el.getAttribute("type");
1493
+ const value = unproxy(target).value;
1494
+ if (type === "checkbox") {
1384
1495
  if (value === undefined) target.value = el.checked;
1385
- onProxyChange = () => el.checked = target.value;
1386
- onInputChange = () => target.value = el.checked;
1387
- } else if (type === 'radio') {
1496
+ onProxyChange = () => {
1497
+ el.checked = target.value;
1498
+ };
1499
+ onInputChange = () => {
1500
+ target.value = el.checked;
1501
+ };
1502
+ } else if (type === "radio") {
1388
1503
  if (value === undefined && el.checked) target.value = el.value;
1389
- onProxyChange = () => el.checked = (target.value === el.value);
1504
+ onProxyChange = () => {
1505
+ el.checked = target.value === el.value;
1506
+ };
1390
1507
  onInputChange = () => {
1391
1508
  if (el.checked) target.value = el.value;
1392
- }
1509
+ };
1393
1510
  } else {
1394
- onInputChange = () => target.value = type==='number' || type==='range' ? (el.value==='' ? null : +el.value) : el.value;
1511
+ onInputChange = () => {
1512
+ target.value =
1513
+ type === "number" || type === "range"
1514
+ ? el.value === ""
1515
+ ? null
1516
+ : +el.value
1517
+ : el.value;
1518
+ };
1395
1519
  if (value === undefined) onInputChange();
1396
- onProxyChange = () => el.value = target.value
1520
+ onProxyChange = () => {
1521
+ el.value = target.value;
1522
+ // biome-ignore lint/suspicious/noDoubleEquals: it's fine for numbers to be casts to strings here
1523
+ if (el.tagName === "SELECT" && el.value != target.value)
1524
+ throw new Error(`SELECT has no '${target.value}' OPTION (yet)`);
1525
+ };
1397
1526
  }
1398
1527
  observe(onProxyChange);
1399
- el.addEventListener('input', onInputChange);
1528
+ el.addEventListener("input", onInputChange);
1400
1529
  clean(() => {
1401
- el.removeEventListener('input', onInputChange);
1530
+ el.removeEventListener("input", onInputChange);
1402
1531
  });
1403
1532
  }
1404
1533
 
1405
- const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1406
- create: function(value: any) {
1534
+ const SPECIAL_PROPS: { [key: string]: (value: any) => void } = {
1535
+ create: (value: any) => {
1407
1536
  const el = currentScope.parentElement;
1408
1537
  if (currentScope !== topRedrawScope) return;
1409
- if (typeof value === 'function') {
1538
+ if (typeof value === "function") {
1410
1539
  value(el);
1411
1540
  } else {
1412
- const classes = value.split('.').filter((c: any)=>c);
1541
+ const classes = value.split(".").filter((c: any) => c);
1413
1542
  el.classList.add(...classes);
1414
- (async function(){ // attempt to prevent layout trashing
1543
+ (async () => {
1544
+ // attempt to prevent layout trashing
1415
1545
  (el as HTMLElement).offsetHeight; // trigger layout
1416
1546
  el.classList.remove(...classes);
1417
1547
  })();
1418
1548
  }
1419
1549
  },
1420
- destroy: function(value: any) {
1550
+ destroy: (value: any) => {
1421
1551
  const el = currentScope.parentElement;
1422
1552
  onDestroyMap.set(el, value);
1423
1553
  },
1424
- html: function(value: any) {
1425
- let tmpParent = document.createElement(currentScope.parentElement.tagName);
1426
- tmpParent.innerHTML = ''+value;
1427
- while(tmpParent.firstChild) addNode(tmpParent.firstChild);
1554
+ html: (value: any) => {
1555
+ const tmpParent = document.createElement(
1556
+ currentScope.parentElement.tagName,
1557
+ );
1558
+ tmpParent.innerHTML = `${value}`;
1559
+ while (tmpParent.firstChild) addNode(tmpParent.firstChild);
1428
1560
  },
1429
- text: function(value: any) {
1561
+ text: (value: any) => {
1430
1562
  addNode(document.createTextNode(value));
1431
1563
  },
1432
- element: function(value: any) {
1433
- if (!(value instanceof Node)) throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
1564
+ element: (value: any) => {
1565
+ if (!(value instanceof Node))
1566
+ throw new Error(`Unexpected element-argument: ${JSON.parse(value)}`);
1434
1567
  addNode(value);
1435
1568
  },
1436
- }
1437
-
1438
-
1569
+ };
1439
1570
 
1440
1571
  /**
1441
1572
  * The core function for building reactive user interfaces in Aberdeen. It creates and inserts new DOM elements
@@ -1483,12 +1614,12 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1483
1614
  * $color: 'red'
1484
1615
  * });
1485
1616
  * ```
1486
- *
1617
+ *
1487
1618
  * @example Create Nested Elements
1488
1619
  * ```typescript
1489
1620
  * let inputElement: Element = $('label:Click me', 'input', {type: 'checkbox'});
1490
1621
  * // You should usually not touch raw DOM elements, unless when integrating
1491
- * // with non-Aberdeen code.
1622
+ * // with non-Aberdeen code.
1492
1623
  * console.log('DOM element:', inputElement);
1493
1624
  * ```
1494
1625
  *
@@ -1523,62 +1654,71 @@ const SPECIAL_PROPS: {[key: string]: (value: any) => void} = {
1523
1654
  * ```
1524
1655
  */
1525
1656
 
1526
-
1527
- export function $(...args: (string | null | undefined | false | (() => void) | Record<string,any>)[]): void | Element {
1528
- let savedCurrentScope;
1529
- let err;
1530
- let result;
1531
-
1532
- for(let arg of args) {
1657
+ export function $(
1658
+ ...args: (
1659
+ | string
1660
+ | null
1661
+ | undefined
1662
+ | false
1663
+ | (() => void)
1664
+ | Record<string, any>
1665
+ )[]
1666
+ ): undefined | Element {
1667
+ let savedCurrentScope: undefined | ContentScope;
1668
+ let err: undefined | string;
1669
+ let result: undefined | Element;
1670
+
1671
+ for (let arg of args) {
1533
1672
  if (arg == null || arg === false) continue;
1534
- if (typeof arg === 'string') {
1535
- let text, classes: undefined | string;
1536
- const textPos = arg.indexOf(':');
1673
+ if (typeof arg === "string") {
1674
+ let text: undefined | string;
1675
+ let classes: undefined | string;
1676
+ const textPos = arg.indexOf(":");
1537
1677
  if (textPos >= 0) {
1538
- text = arg.substring(textPos+1);
1539
- arg = arg.substring(0,textPos);
1678
+ text = arg.substring(textPos + 1);
1679
+ arg = arg.substring(0, textPos);
1540
1680
  }
1541
- const classPos = arg.indexOf('.');
1681
+ const classPos = arg.indexOf(".");
1542
1682
  if (classPos >= 0) {
1543
- classes = arg.substring(classPos+1);
1683
+ classes = arg.substring(classPos + 1);
1544
1684
  arg = arg.substring(0, classPos);
1545
1685
  }
1546
- if (arg === '') { // Add text or classes to parent
1686
+ if (arg === "") {
1687
+ // Add text or classes to parent
1547
1688
  if (text) addNode(document.createTextNode(text));
1548
1689
  if (classes) {
1549
1690
  const el = currentScope.parentElement;
1550
- el.classList.add(...classes.split('.'));
1691
+ el.classList.add(...classes.split("."));
1551
1692
  if (!savedCurrentScope) {
1552
- clean(() => el.classList.remove(...classes.split('.')));
1693
+ clean(() => el.classList.remove(...classes.split(".")));
1553
1694
  }
1554
1695
  }
1555
- } else if (arg.indexOf(' ') >= 0) {
1696
+ } else if (arg.indexOf(" ") >= 0) {
1556
1697
  err = `Tag '${arg}' cannot contain space`;
1557
1698
  break;
1558
1699
  } else {
1559
1700
  result = document.createElement(arg);
1560
- if (classes) result.className = classes.replaceAll('.', ' ');
1701
+ if (classes) result.className = classes.replaceAll(".", " ");
1561
1702
  if (text) result.textContent = text;
1562
1703
  addNode(result);
1563
1704
  if (!savedCurrentScope) {
1564
1705
  savedCurrentScope = currentScope;
1565
1706
  }
1566
- let newScope = new ChainedScope(result, true);
1707
+ const newScope = new ChainedScope(result, true);
1567
1708
  newScope.lastChild = result.lastChild || undefined;
1568
1709
  if (topRedrawScope === currentScope) topRedrawScope = newScope;
1569
1710
  currentScope = newScope;
1570
1711
  }
1571
- }
1572
- else if (typeof arg === 'object') {
1712
+ } else if (typeof arg === "object") {
1573
1713
  if (arg.constructor !== Object) {
1574
1714
  err = `Unexpected argument: ${arg}`;
1575
1715
  break;
1576
1716
  }
1577
- for(const key in arg) {
1717
+ for (const key in arg) {
1578
1718
  const val = arg[key];
1579
1719
  applyArg(key, val);
1580
1720
  }
1581
- } else if (typeof arg === 'function') {
1721
+ } else if (typeof arg === "function") {
1582
1722
  new RegularScope(currentScope.parentElement, arg);
1583
1723
  } else {
1584
1724
  err = `Unexpected argument: ${arg}`;
@@ -1647,71 +1787,88 @@ let cssCount = 0;
1647
1787
  * color: "#107ab0",
1648
1788
  * }
1649
1789
  * }, true); // Pass true for global
1650
- *
1790
+ *
1651
1791
  * $('a:Styled link');
1652
1792
  * ```
1653
1793
  */
1654
- export function insertCss(style: object, global: boolean = false): string {
1655
- const prefix = global ? "" : ".AbdStl" + ++cssCount;
1656
- let css = styleToCss(style, prefix);
1657
- if (css) $('style:'+css);
1794
+ export function insertCss(style: object, global = false): string {
1795
+ const prefix = global ? "" : `.AbdStl${++cssCount}`;
1796
+ const css = styleToCss(style, prefix);
1797
+ if (css) $(`style:${css}`);
1658
1798
  return prefix;
1659
1799
  }
1660
1800
 
1661
1801
  function styleToCss(style: object, prefix: string): string {
1662
- let props = '';
1663
- let rules = '';
1664
- for(const kOr in style) {
1802
+ let props = "";
1803
+ let rules = "";
1804
+ for (const kOr in style) {
1665
1805
  const v = (style as any)[kOr];
1666
- for(const k of kOr.split(/, ?/g)) {
1667
- if (v && typeof v === 'object') {
1668
- if (k.startsWith('@')) { // media queries
1669
- rules += k + '{\n' + styleToCss(v, prefix) + '}\n';
1806
+ for (const k of kOr.split(/, ?/g)) {
1807
+ if (v && typeof v === "object") {
1808
+ if (k.startsWith("@")) {
1809
+ // media queries
1810
+ rules += `${k}{\n${styleToCss(v, prefix)}}\n`;
1670
1811
  } else {
1671
- rules += styleToCss(v, k.includes('&') ? k.replace(/&/g, prefix) : prefix+' '+k);
1812
+ rules += styleToCss(
1813
+ v,
1814
+ k.includes("&") ? k.replace(/&/g, prefix) : `${prefix} ${k}`,
1815
+ );
1672
1816
  }
1673
1817
  } else {
1674
- props += k.replace(/[A-Z]/g, letter => '-'+letter.toLowerCase()) +":"+v+";";
1818
+ props += `${k.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}:${v};`;
1675
1819
  }
1676
1820
  }
1677
1821
  }
1678
- if (props) rules = (prefix.trimStart() || '*') + '{'+props+'}\n' + rules;
1822
+ if (props) rules = `${prefix.trimStart() || "*"}{${props}}\n${rules}`;
1679
1823
  return rules;
1680
1824
  }
1681
1825
 
1682
1826
  function applyArg(key: string, value: any) {
1683
1827
  const el = currentScope.parentElement;
1684
- if (typeof value === 'object' && value !== null && value[TARGET_SYMBOL]) { // Value is a proxy
1685
- if (key === 'bind') {
1686
- applyBind(el, value)
1828
+ if (typeof value === "object" && value !== null && value[TARGET_SYMBOL]) {
1829
+ // Value is a proxy
1830
+ if (key === "bind") {
1831
+ applyBind(el as HTMLInputElement, value);
1687
1832
  } else {
1688
- new SetArgScope(el, key, value)
1833
+ new SetArgScope(el, key, value);
1689
1834
  // SetArgScope will (repeatedly) call `applyArg` again with the actual value
1690
1835
  }
1691
- } else if (key[0] === '.') { // CSS class(es)
1692
- const classes = key.substring(1).split('.');
1836
+ } else if (key[0] === ".") {
1837
+ // CSS class(es)
1838
+ const classes = key.substring(1).split(".");
1693
1839
  if (value) el.classList.add(...classes);
1694
1840
  else el.classList.remove(...classes);
1695
- } else if (key[0] === '$') { // Style
1841
+ } else if (key[0] === "$") {
1842
+ // Style
1696
1843
  const name = key.substring(1);
1697
- if (value==null || value===false) (el as any).style[name] = ''
1698
- else (el as any).style[name] = ''+value;
1699
- } else if (value == null) { // Value left empty
1844
+ if (value == null || value === false) (el as any).style[name] = "";
1845
+ else (el as any).style[name] = `${value}`;
1846
+ } else if (value == null) {
1847
+ // Value left empty
1700
1848
  // Do nothing
1701
- } else if (key in SPECIAL_PROPS) { // Special property
1849
+ } else if (key in SPECIAL_PROPS) {
1850
+ // Special property
1702
1851
  SPECIAL_PROPS[key](value);
1703
- } else if (typeof value === 'function') { // Event listener
1852
+ } else if (typeof value === "function") {
1853
+ // Event listener
1704
1854
  el.addEventListener(key, value);
1705
1855
  clean(() => el.removeEventListener(key, value));
1706
- } else if (value===true || value===false || key==='value' || key==='selectedIndex') { // DOM property
1856
+ } else if (
1857
+ value === true ||
1858
+ value === false ||
1859
+ key === "value" ||
1860
+ key === "selectedIndex"
1861
+ ) {
1862
+ // DOM property
1707
1863
  (el as any)[key] = value;
1708
- } else { // HTML attribute
1864
+ } else {
1865
+ // HTML attribute
1709
1866
  el.setAttribute(key, value);
1710
1867
  }
1711
1868
  }
1712
1869
 
1713
1870
  function defaultOnError(error: Error) {
1714
- console.error('Error while in Aberdeen render:', error);
1871
+ console.error("Error while in Aberdeen render:", error);
1715
1872
  return true;
1716
1873
  }
1717
1874
  let onError: (error: Error) => boolean | undefined = defaultOnError;
@@ -1747,7 +1904,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1747
1904
  *
1748
1905
  * return false; // Suppress default console log and DOM error message
1749
1906
  * });
1750
- *
1907
+ *
1751
1908
  * // Styling for our custom error message
1752
1909
  * insertCss({
1753
1910
  * '.error-message': {
@@ -1758,7 +1915,7 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1758
1915
  * padding: '2px 4px',
1759
1916
  * }
1760
1917
  * }, true); // global style
1761
- *
1918
+ *
1762
1919
  * // Cause an error within a render scope.
1763
1920
  * $('div.box', () => {
1764
1921
  * // Will cause our error handler to insert an error message within the box
@@ -1766,11 +1923,12 @@ let onError: (error: Error) => boolean | undefined = defaultOnError;
1766
1923
  * })
1767
1924
  * ```
1768
1925
  */
1769
- export function setErrorHandler(handler?: (error: Error) => boolean | undefined) {
1926
+ export function setErrorHandler(
1927
+ handler?: (error: Error) => boolean | undefined,
1928
+ ) {
1770
1929
  onError = handler || defaultOnError;
1771
1930
  }
1772
1931
 
1773
-
1774
1932
  /**
1775
1933
  * Gets the parent DOM `Element` where nodes created by {@link $} would currently be inserted.
1776
1934
  *
@@ -1799,7 +1957,6 @@ export function getParentElement(): Element {
1799
1957
  return currentScope.parentElement;
1800
1958
  }
1801
1959
 
1802
-
1803
1960
  /**
1804
1961
  * Registers a cleanup function to be executed just before the current reactive scope
1805
1962
  * is destroyed or redraws.
@@ -1824,14 +1981,14 @@ export function getParentElement(): Element {
1824
1981
  * // we don't want to subscribe.
1825
1982
  * peek(() => sum.value += item);
1826
1983
  * // Clean gets called before each rerun for a certain item index
1827
- * // No need for peek here, as the clean code doesn't run in an
1984
+ * // No need for peek here, as the clean code doesn't run in an
1828
1985
  * // observe scope.
1829
1986
  * clean(() => sum.value -= item);
1830
1987
  * })
1831
- *
1988
+ *
1832
1989
  * // Show the sum
1833
1990
  * $('h1', {text: sum});
1834
- *
1991
+ *
1835
1992
  * // Make random changes to the array
1836
1993
  * const rnd = () => 0|(Math.random()*20);
1837
1994
  * setInterval(() => myArray[rnd()] = rnd(), 1000);
@@ -1842,7 +1999,6 @@ export function clean(cleaner: () => void) {
1842
1999
  currentScope.cleaners.push(cleaner);
1843
2000
  }
1844
2001
 
1845
-
1846
2002
  /**
1847
2003
  * Creates a reactive scope that automatically re-executes the provided function
1848
2004
  * whenever any proxied data (created by {@link proxy}) read during its last execution changes, storing
@@ -1874,9 +2030,9 @@ export function clean(cleaner: () => void) {
1874
2030
  * });
1875
2031
  * });
1876
2032
  * ```
1877
- *
2033
+ *
1878
2034
  * ***Note*** that the above could just as easily be done using `$(func)` instead of `observe(func)`.
1879
- *
2035
+ *
1880
2036
  * @example Observation with return value
1881
2037
  * ```typescript
1882
2038
  * const counter = proxy(0);
@@ -1891,8 +2047,8 @@ export function clean(cleaner: () => void) {
1891
2047
  * @overload
1892
2048
  * @param func Func without a return value.
1893
2049
  */
1894
- export function observe<T extends (DatumType | void)>(func: () => T): ValueRef<T> {
1895
- return (new ResultScope<T>(currentScope.parentElement, func)).result;
2050
+ export function observe<T>(func: () => T): ValueRef<T> {
2051
+ return new ResultScope<T>(currentScope.parentElement, func).result;
1896
2052
  }
1897
2053
 
1898
2054
  /**
@@ -1938,7 +2094,7 @@ export function immediateObserve(func: () => void) {
1938
2094
  * Calls to {@link $} inside `func` will append nodes to `parentElement`.
1939
2095
  * You can nest {@link observe} or other {@link $} scopes within `func`.
1940
2096
  * Use {@link unmountAll} to clean up all mounted scopes and their DOM nodes.
1941
- *
2097
+ *
1942
2098
  * Mounting scopes happens reactively, meaning that if this function is called from within another
1943
2099
  * ({@link observe} or {@link $} or {@link mount}) scope that gets cleaned up, so will the mount.
1944
2100
  *
@@ -1949,7 +2105,7 @@ export function immediateObserve(func: () => void) {
1949
2105
  * ```javascript
1950
2106
  * // Create a pre-existing DOM structure (without Aberdeen)
1951
2107
  * document.body.innerHTML = `<h3>Static content <span id="title-extra"></span></h3><div class="box" id="app-root"></div>`;
1952
- *
2108
+ *
1953
2109
  * import { mount, $, proxy } from 'aberdeen';
1954
2110
  *
1955
2111
  * const runTime = proxy(0);
@@ -1966,7 +2122,7 @@ export function immediateObserve(func: () => void) {
1966
2122
  * }
1967
2123
  * });
1968
2124
  * ```
1969
- *
2125
+ *
1970
2126
  * Note how the inner mount behaves reactively as well, automatically unmounting when it's parent observer scope re-runs.
1971
2127
  */
1972
2128
 
@@ -2019,9 +2175,15 @@ export function peek<T>(func: () => T): T {
2019
2175
  }
2020
2176
 
2021
2177
  /** When using an object as `source`. */
2022
- export function map<IN,OUT>(source: Record<string|symbol,IN>, func: (value: IN, index: string|symbol) => undefined|OUT): Record<string|symbol,OUT>;
2178
+ export function map<IN, OUT>(
2179
+ source: Record<string | symbol, IN>,
2180
+ func: (value: IN, index: string | symbol) => undefined | OUT,
2181
+ ): Record<string | symbol, OUT>;
2023
2182
  /** When using an array as `source`. */
2024
- export function map<IN,OUT>(source: Array<IN>, func: (value: IN, index: number) => undefined|OUT): Array<OUT>;
2183
+ export function map<IN, OUT>(
2184
+ source: Array<IN>,
2185
+ func: (value: IN, index: number) => undefined | OUT,
2186
+ ): Array<OUT>;
2025
2187
  /**
2026
2188
  * Reactively maps/filters items from a proxied source array or object to a new proxied array or object.
2027
2189
  *
@@ -2065,24 +2227,34 @@ export function map<IN,OUT>(source: Array<IN>, func: (value: IN, index: number)
2065
2227
  * // activeUserNames becomes proxy({ u1: 'Alice', u2: 'Bob', u3: 'Charlie' })
2066
2228
  * ```
2067
2229
  */
2068
- export function map(source: any, func: (value: DatumType, key: any) => any): any {
2069
- let out = optProxy(source instanceof Array ? [] : {});
2070
- onEach(source, (item: DatumType, key: symbol|string|number) => {
2071
- let value = func(item, key);
2230
+ export function map(
2231
+ source: any,
2232
+ func: (value: DatumType, key: any) => any,
2233
+ ): any {
2234
+ const out = optProxy(source instanceof Array ? [] : {});
2235
+ onEach(source, (item: DatumType, key: symbol | string | number) => {
2236
+ const value = func(item, key);
2072
2237
  if (value !== undefined) {
2073
2238
  out[key] = value;
2074
2239
  clean(() => {
2075
2240
  delete out[key];
2076
- })
2241
+ });
2077
2242
  }
2078
- })
2079
- return out
2243
+ });
2244
+ return out;
2080
2245
  }
2081
2246
 
2082
2247
  /** When using an array as `source`. */
2083
- export function multiMap<IN,OUT extends {[key: string|symbol]: DatumType}>(source: Array<IN>, func: (value: IN, index: number) => OUT | undefined): OUT;
2248
+ export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2249
+ source: Array<IN>,
2250
+ func: (value: IN, index: number) => OUT | undefined,
2251
+ ): OUT;
2084
2252
  /** When using an object as `source`. */
2085
- 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;
2253
+ export function multiMap<
2254
+ K extends string | number | symbol,
2255
+ IN,
2256
+ OUT extends { [key: string | symbol]: DatumType },
2257
+ >(source: Record<K, IN>, func: (value: IN, index: K) => OUT | undefined): OUT;
2086
2258
  /**
2087
2259
  * Reactively maps items from a source proxy (array or object) to a target proxied object,
2088
2260
  * where each source item can contribute multiple key-value pairs to the target.
@@ -2117,31 +2289,44 @@ export function multiMap<K extends string|number|symbol,IN,OUT extends {[key: st
2117
2289
  * [item.id+item.id]: item.value*10,
2118
2290
  * }));
2119
2291
  * // itemsById is proxy({ a: 10, aa: 100, b: 20, bb: 200 })
2120
- *
2292
+ *
2121
2293
  * $(() => console.log(itemsById));
2122
2294
  *
2123
2295
  * items.push({ id: 'c', value: 30 });
2124
2296
  * // itemsById becomes proxy({ a: 10, aa: 100, b: 20, bb: 200, c: 30, cc: 300 })
2125
2297
  * ```
2126
2298
  */
2127
- export function multiMap(source: any, func: (value: DatumType, key: any) => Record<string|symbol,DatumType>): any {
2128
- let out = optProxy({});
2129
- onEach(source, (item: DatumType, key: symbol|string|number) => {
2130
- let pairs = func(item, key);
2299
+ export function multiMap(
2300
+ source: any,
2301
+ func: (value: DatumType, key: any) => Record<string | symbol, DatumType>,
2302
+ ): any {
2303
+ const out = optProxy({});
2304
+ onEach(source, (item: DatumType, key: symbol | string | number) => {
2305
+ const pairs = func(item, key);
2131
2306
  if (pairs) {
2132
- for(let key in pairs) out[key] = pairs[key];
2307
+ for (const key in pairs) out[key] = pairs[key];
2133
2308
  clean(() => {
2134
- for(let key in pairs) delete out[key];
2135
- })
2309
+ for (const key in pairs) delete out[key];
2310
+ });
2136
2311
  }
2137
- })
2138
- return out
2312
+ });
2313
+ return out;
2139
2314
  }
2140
2315
 
2141
2316
  /** When using an object as `array`. */
2142
- 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>>;
2317
+ export function partition<OUT_K extends string | number | symbol, IN_V>(
2318
+ source: IN_V[],
2319
+ func: (value: IN_V, key: number) => undefined | OUT_K | OUT_K[],
2320
+ ): Record<OUT_K, Record<number, IN_V>>;
2143
2321
  /** When using an object as `source`. */
2144
- 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>>;
2322
+ export function partition<
2323
+ IN_K extends string | number | symbol,
2324
+ OUT_K extends string | number | symbol,
2325
+ IN_V,
2326
+ >(
2327
+ source: Record<IN_K, IN_V>,
2328
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2329
+ ): Record<OUT_K, Record<IN_K, IN_V>>;
2145
2330
 
2146
2331
  /**
2147
2332
  * @overload
@@ -2187,10 +2372,10 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2187
2372
  * // Partition products by category. Output keys are categories (string).
2188
2373
  * // Inner keys are original array indices (number).
2189
2374
  * const productsByCategory = partition(products, (product) => product.category);
2190
- *
2375
+ *
2191
2376
  * // Reactively show the data structure
2192
2377
  * dump(productsByCategory);
2193
- *
2378
+ *
2194
2379
  * // Make random changes to the categories, to show reactiveness
2195
2380
  * setInterval(() => products[0|(Math.random()*3)].category = ['Snack','Fruit','Veg'][0|(Math.random()*3)], 2000);
2196
2381
  * ```
@@ -2210,31 +2395,37 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2210
2395
  * console.log(usersByTag);
2211
2396
  * ```
2212
2397
  */
2213
- 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>> {
2214
- const unproxiedOut = {} as Record<OUT_K,Record<IN_K,IN_V>>;
2398
+ export function partition<
2399
+ IN_K extends string | number | symbol,
2400
+ OUT_K extends string | number | symbol,
2401
+ IN_V,
2402
+ >(
2403
+ source: Record<IN_K, IN_V>,
2404
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2405
+ ): Record<OUT_K, Record<IN_K, IN_V>> {
2406
+ const unproxiedOut = {} as Record<OUT_K, Record<IN_K, IN_V>>;
2215
2407
  const out = proxy(unproxiedOut);
2216
2408
  onEach(source, (item: IN_V, key: IN_K) => {
2217
- let rsp = func(item, key);
2409
+ const rsp = func(item, key);
2218
2410
  if (rsp != null) {
2219
2411
  const buckets = rsp instanceof Array ? rsp : [rsp];
2220
2412
  if (buckets.length) {
2221
- for(let bucket of buckets) {
2413
+ for (const bucket of buckets) {
2222
2414
  if (unproxiedOut[bucket]) out[bucket][key] = item;
2223
- else out[bucket] = {[key]: item} as Record<IN_K, IN_V>;
2415
+ else out[bucket] = { [key]: item } as Record<IN_K, IN_V>;
2224
2416
  }
2225
2417
  clean(() => {
2226
- for(let bucket of buckets) {
2418
+ for (const bucket of buckets) {
2227
2419
  delete out[bucket][key];
2228
2420
  if (isObjEmpty(unproxiedOut[bucket])) delete out[bucket];
2229
- }
2230
- })
2421
+ }
2422
+ });
2231
2423
  }
2232
2424
  }
2233
- })
2425
+ });
2234
2426
  return out;
2235
2427
  }
2236
2428
 
2237
-
2238
2429
  /**
2239
2430
  * Renders a live, recursive dump of a proxied data structure (or any value)
2240
2431
  * into the DOM at the current {@link $} insertion point.
@@ -2263,28 +2454,28 @@ export function partition<IN_K extends string|number|symbol, OUT_K extends strin
2263
2454
  * ```
2264
2455
  */
2265
2456
  export function dump<T>(data: T): T {
2266
- if (data && typeof data === 'object') {
2267
- $({text: data instanceof Array ? "<array>" : "<object>"});
2268
- $('ul', () => {
2457
+ if (data && typeof data === "object") {
2458
+ $({ text: data instanceof Array ? "<array>" : "<object>" });
2459
+ $("ul", () => {
2269
2460
  onEach(data as any, (value, key) => {
2270
- $('li:'+JSON.stringify(key)+": ", () => {
2271
- dump(value)
2272
- })
2273
- })
2274
- })
2461
+ $(`li:${JSON.stringify(key)}: `, () => {
2462
+ dump(value);
2463
+ });
2464
+ });
2465
+ });
2275
2466
  } else {
2276
- $({text: JSON.stringify(data)})
2467
+ $({ text: JSON.stringify(data) });
2277
2468
  }
2278
- return data
2469
+ return data;
2279
2470
  }
2280
2471
 
2281
2472
  /*
2282
- * Helper functions
2283
- */
2473
+ * Helper functions
2474
+ */
2284
2475
 
2285
2476
  /* c8 ignore start */
2286
2477
  function internalError(code: number): never {
2287
- throw new Error("Aberdeen internal error "+code);
2478
+ throw new Error(`Aberdeen internal error ${code}`);
2288
2479
  }
2289
2480
  /* c8 ignore end */
2290
2481
 
@@ -2295,7 +2486,7 @@ function handleError(e: any, showMessage: boolean) {
2295
2486
  console.error(e);
2296
2487
  }
2297
2488
  try {
2298
- if (showMessage) $('div.aberdeen-error:Error');
2489
+ if (showMessage) $("div.aberdeen-error:Error");
2299
2490
  } catch {
2300
2491
  // Error while adding the error marker to the DOM. Apparently, we're in
2301
2492
  // an awkward context. The error should already have been logged by
@@ -2304,7 +2495,15 @@ function handleError(e: any, showMessage: boolean) {
2304
2495
  }
2305
2496
 
2306
2497
  /** @internal */
2307
- export function withEmitHandler(handler: (target: TargetType, index: any, newData: DatumType, oldData: DatumType) => void, func: ()=>void) {
2498
+ export function withEmitHandler(
2499
+ handler: (
2500
+ target: TargetType,
2501
+ index: any,
2502
+ newData: DatumType,
2503
+ oldData: DatumType,
2504
+ ) => void,
2505
+ func: () => void,
2506
+ ) {
2308
2507
  const oldEmitHandler = emit;
2309
2508
  emit = handler;
2310
2509
  try {
@@ -2313,12 +2512,3 @@ export function withEmitHandler(handler: (target: TargetType, index: any, newDat
2313
2512
  emit = oldEmitHandler;
2314
2513
  }
2315
2514
  }
2316
-
2317
- // @ts-ignore
2318
- // c8 ignore next
2319
- if (!String.prototype.replaceAll) String.prototype.replaceAll = function(from, to) { return this.split(from).join(to) }
2320
- declare global {
2321
- interface String {
2322
- replaceAll(from: string, to: string): string;
2323
- }
2324
- }