aberdeen 1.0.10 → 1.0.12

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
@@ -19,15 +19,8 @@ let runQueueDepth = 0; // Incremented when a queue event causes another queue ev
19
19
  let topRedrawScope: Scope | undefined; // The scope that triggered the current redraw. Elements drawn at this scope level may trigger 'create' animations.
20
20
 
21
21
  /** @internal */
22
- export type TargetType = any[] | { [key: string]: any };
23
- /** @internal */
24
- export type DatumType =
25
- | TargetType
26
- | boolean
27
- | number
28
- | string
29
- | null
30
- | undefined;
22
+ export type TargetType = any[] | { [key: string | symbol]: any } | Map<any, any>;
23
+
31
24
 
32
25
  function queue(runner: QueueRunner) {
33
26
  if (!sortedQueue) {
@@ -92,9 +85,9 @@ export function runQueue(): void {
92
85
  * be a number, string, or an array of numbers/strings. The sort key is used to sort items
93
86
  * based on their values. The sort key can also be `undefined`, which indicates that the item
94
87
  * should be ignored.
95
- * @private
88
+ * @internal
96
89
  */
97
- export type SortKeyType = number | string | Array<number | string> | undefined;
90
+ export type SortKeyType = number | string | Array<number | string> | undefined | void;
98
91
 
99
92
  /**
100
93
  * Given an integer number or a string, this function returns a string that can be concatenated
@@ -173,7 +166,7 @@ abstract class Scope implements QueueRunner {
173
166
 
174
167
  [ptr: ReverseSortedSetPointer]: this;
175
168
 
176
- abstract onChange(index: any, newData: DatumType, oldData: DatumType): void;
169
+ abstract onChange(index: any, newData: any, oldData: any): void;
177
170
  abstract queueRun(): void;
178
171
 
179
172
  abstract getLastNode(): Node | undefined;
@@ -252,7 +245,7 @@ abstract class ContentScope extends Scope {
252
245
  return this.getLastNode() || this.getPrecedingNode();
253
246
  }
254
247
 
255
- onChange(index: any, newData: DatumType, oldData: DatumType) {
248
+ onChange(index: any, newData: any, oldData: any) {
256
249
  queue(this);
257
250
  }
258
251
 
@@ -445,7 +438,7 @@ class SetArgScope extends ChainedScope {
445
438
  constructor(
446
439
  parentElement: Element,
447
440
  public key: string,
448
- public target: { value: DatumType },
441
+ public target: { value: any },
449
442
  ) {
450
443
  super(parentElement);
451
444
  this.redraw();
@@ -463,7 +456,7 @@ let immediateQueue: ReverseSortedSet<Scope, "prio"> = new ReverseSortedSet(
463
456
  );
464
457
 
465
458
  class ImmediateScope extends RegularScope {
466
- onChange(index: any, newData: DatumType, oldData: DatumType) {
459
+ onChange(index: any, newData: any, oldData: any) {
467
460
  immediateQueue.add(this);
468
461
  }
469
462
  }
@@ -518,9 +511,9 @@ class OnEachScope extends Scope {
518
511
  constructor(
519
512
  proxy: TargetType,
520
513
  /** A function that renders an item */
521
- public renderer: (value: DatumType, key: any) => void,
514
+ public renderer: (value: any, key: any) => void,
522
515
  /** A function returning a number/string/array that defines the position of an item */
523
- public makeSortKey?: (value: DatumType, key: any) => SortKeyType,
516
+ public makeSortKey?: (value: any, key: any) => SortKeyType,
524
517
  ) {
525
518
  super();
526
519
  const target: TargetType = (this.target =
@@ -540,8 +533,8 @@ class OnEachScope extends Scope {
540
533
  }
541
534
  }
542
535
  } else {
543
- for (const key in target) {
544
- if (target[key] !== undefined) {
536
+ for (const [key, value] of getEntries(target)) {
537
+ if (value !== undefined) {
545
538
  new OnEachItemScope(this, key, false);
546
539
  }
547
540
  }
@@ -552,7 +545,7 @@ class OnEachScope extends Scope {
552
545
  return findLastNodeInPrevSiblings(this.prevSibling);
553
546
  }
554
547
 
555
- onChange(index: any, newData: DatumType, oldData: DatumType) {
548
+ onChange(index: any, newData: any, oldData: any) {
556
549
  if (!(this.target instanceof Array) || typeof index === "number")
557
550
  this.changedIndexes.add(index);
558
551
  queue(this);
@@ -565,7 +558,14 @@ class OnEachScope extends Scope {
565
558
  const oldScope = this.byIndex.get(index);
566
559
  if (oldScope) oldScope.remove();
567
560
 
568
- if ((this.target as any)[index] === undefined) {
561
+ let hasValue;
562
+ if (this.target instanceof Map) {
563
+ hasValue = this.target.has(index);
564
+ } else {
565
+ hasValue = (this.target as any)[index] !== undefined;
566
+ }
567
+
568
+ if (!hasValue) {
569
569
  this.byIndex.delete(index);
570
570
  } else {
571
571
  new OnEachItemScope(this, index, true);
@@ -688,9 +688,16 @@ class OnEachItemScope extends ContentScope {
688
688
  // a wildcard subscription to delete/recreate any scopes when that changes.
689
689
  // We ARE creating a proxy around the value though (in case its an object/array),
690
690
  // so we'll have our own scope subscribe to changes on that.
691
- const value: DatumType = optProxy(
692
- (this.parent.target as any)[this.itemIndex],
693
- );
691
+ let value: any;
692
+ const target = this.parent.target;
693
+ let itemIndex = this.itemIndex;
694
+ if (target instanceof Map) {
695
+ value = optProxy(target.get(itemIndex));
696
+ // For Maps, the key may be an object. If so, we'll proxy it as well.
697
+ itemIndex = optProxy(itemIndex);
698
+ } else {
699
+ value = optProxy((target as any)[itemIndex]);
700
+ }
694
701
 
695
702
  // Since makeSortKey may get() the Store, we'll need to set currentScope first.
696
703
  const savedScope = currentScope;
@@ -699,14 +706,14 @@ class OnEachItemScope extends ContentScope {
699
706
  let sortKey: undefined | string | number;
700
707
  try {
701
708
  if (this.parent.makeSortKey) {
702
- const rawSortKey = this.parent.makeSortKey(value, this.itemIndex);
709
+ const rawSortKey = this.parent.makeSortKey(value, itemIndex);
703
710
  if (rawSortKey != null)
704
711
  sortKey =
705
712
  rawSortKey instanceof Array
706
713
  ? rawSortKey.map(partToStr).join("")
707
714
  : rawSortKey;
708
715
  } else {
709
- sortKey = this.itemIndex;
716
+ sortKey = itemIndex;
710
717
  }
711
718
  if (typeof sortKey === "number") sortKey = partToStr(sortKey);
712
719
 
@@ -720,7 +727,7 @@ class OnEachItemScope extends ContentScope {
720
727
  // We're not adding `this` to the `sortedSet` (yet), as that may not be needed,
721
728
  // in case no nodes are created. We'll do it just-in-time in `getPrecedingNode`.
722
729
 
723
- if (sortKey != null) this.parent.renderer(value, this.itemIndex);
730
+ if (sortKey != null) this.parent.renderer(value, itemIndex);
724
731
  } catch (e) {
725
732
  handleError(e, sortKey != null);
726
733
  }
@@ -778,11 +785,16 @@ const ANY_SYMBOL = Symbol("any");
778
785
  */
779
786
  const TARGET_SYMBOL = Symbol("target");
780
787
 
788
+ /**
789
+ * Symbol used internally to track Map size without clashing with actual Map keys named "size".
790
+ */
791
+ const MAP_SIZE_SYMBOL = Symbol("mapSize");
792
+
781
793
  const subscribers = new WeakMap<
782
794
  TargetType,
783
795
  Map<
784
796
  any,
785
- Set<Scope | ((index: any, newData: DatumType, oldData: DatumType) => void)>
797
+ Set<Scope | ((index: any, newData: any, oldData: any) => void)>
786
798
  >
787
799
  >();
788
800
  let peeking = 0; // When > 0, we're not subscribing to any changes
@@ -794,8 +806,8 @@ function subscribe(
794
806
  | Scope
795
807
  | ((
796
808
  index: any,
797
- newData: DatumType,
798
- oldData: DatumType,
809
+ newData: any,
810
+ oldData: any,
799
811
  ) => void) = currentScope,
800
812
  ) {
801
813
  if (observer === ROOT_SCOPE || peeking) return;
@@ -822,15 +834,27 @@ function subscribe(
822
834
  }
823
835
  }
824
836
 
837
+ /**
838
+ * Records in TypeScript pretend that they can have number keys, but in reality they are converted to string.
839
+ * This type changes (number | something) types to (string | something) types, maintaining typing precision as much as possible.
840
+ * @internal
841
+ */
842
+ type KeyToString<K> = K extends number ? string : K extends string | symbol ? K : K extends number | infer U ? string | U : K;
843
+
844
+ export function onEach<K, T>(
845
+ target: Map<K, undefined | T>,
846
+ render: (value: T, key: K) => void,
847
+ makeKey?: (value: T, key: K) => SortKeyType,
848
+ ): void;
825
849
  export function onEach<T>(
826
850
  target: ReadonlyArray<undefined | T>,
827
851
  render: (value: T, index: number) => void,
828
- makeKey?: (value: T, key: any) => SortKeyType,
852
+ makeKey?: (value: T, index: number) => SortKeyType,
829
853
  ): void;
830
854
  export function onEach<K extends string | number | symbol, T>(
831
855
  target: Record<K, undefined | T>,
832
- render: (value: T, index: K) => void,
833
- makeKey?: (value: T, key: K) => SortKeyType,
856
+ render: (value: T, index: KeyToString<K>) => void,
857
+ makeKey?: (value: T, index: KeyToString<K>) => SortKeyType,
834
858
  ): void;
835
859
 
836
860
  /**
@@ -890,8 +914,8 @@ export function onEach<K extends string | number | symbol, T>(
890
914
  */
891
915
  export function onEach(
892
916
  target: TargetType,
893
- render: (value: DatumType, index: any) => void,
894
- makeKey?: (value: DatumType, key: any) => SortKeyType,
917
+ render: (value: any, index: any) => void,
918
+ makeKey?: (value: any, key: any) => SortKeyType,
895
919
  ): void {
896
920
  if (!target || typeof target !== "object")
897
921
  throw new Error("onEach requires an object");
@@ -942,23 +966,23 @@ export function isEmpty(proxied: TargetType): boolean {
942
966
  const scope = currentScope;
943
967
 
944
968
  if (target instanceof Array) {
945
- subscribe(
946
- target,
947
- "length",
948
- (index: any, newData: DatumType, oldData: DatumType) => {
949
- if (!newData !== !oldData) queue(scope);
950
- },
951
- );
969
+ subscribe(target, "length", (index: any, newData: any, oldData: any) => {
970
+ if (!newData !== !oldData) queue(scope);
971
+ });
952
972
  return !target.length;
953
973
  }
974
+
975
+ if (target instanceof Map) {
976
+ subscribe(target, MAP_SIZE_SYMBOL, (index: any, newData: any, oldData: any) => {
977
+ if (!newData !== !oldData) queue(scope);
978
+ });
979
+ return !target.size;
980
+ }
981
+
954
982
  const result = isObjEmpty(target);
955
- subscribe(
956
- target,
957
- ANY_SYMBOL,
958
- (index: any, newData: DatumType, oldData: DatumType) => {
959
- if (result ? oldData === undefined : newData === undefined) queue(scope);
960
- },
961
- );
983
+ subscribe(target, ANY_SYMBOL, (index: any, newData: any, oldData: any) => {
984
+ if (result ? oldData === undefined : newData === undefined) queue(scope);
985
+ });
962
986
  return result;
963
987
  }
964
988
 
@@ -995,6 +1019,7 @@ export interface ValueRef<T> {
995
1019
  */
996
1020
  export function count(proxied: TargetType): ValueRef<number> {
997
1021
  if (proxied instanceof Array) return ref(proxied, "length");
1022
+ if (proxied instanceof Map) return ref(proxied, "size");
998
1023
 
999
1024
  const target = (proxied as any)[TARGET_SYMBOL] || proxied;
1000
1025
  let cnt = 0;
@@ -1004,7 +1029,7 @@ export function count(proxied: TargetType): ValueRef<number> {
1004
1029
  subscribe(
1005
1030
  target,
1006
1031
  ANY_SYMBOL,
1007
- (index: any, newData: DatumType, oldData: DatumType) => {
1032
+ (index: any, newData: any, oldData: any) => {
1008
1033
  if (oldData === newData) {
1009
1034
  } else if (oldData === undefined) result.value = ++cnt;
1010
1035
  else if (newData === undefined) result.value = --cnt;
@@ -1018,8 +1043,8 @@ export function count(proxied: TargetType): ValueRef<number> {
1018
1043
  export function defaultEmitHandler(
1019
1044
  target: TargetType,
1020
1045
  index: string | symbol | number,
1021
- newData: DatumType,
1022
- oldData: DatumType,
1046
+ newData: any,
1047
+ oldData: any,
1023
1048
  ) {
1024
1049
  // We're triggering for values changing from undefined to undefined, as this *may*
1025
1050
  // indicate a change from or to `[empty]` (such as `[,1][0]`).
@@ -1123,6 +1148,96 @@ const arrayHandler: ProxyHandler<any[]> = {
1123
1148
  },
1124
1149
  };
1125
1150
 
1151
+ const mapMethodHandlers = {
1152
+ get(this: any, key: any): any {
1153
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1154
+ subscribe(target, key);
1155
+ return optProxy(target.get(key));
1156
+ },
1157
+ set(this: any, key: any, newData: any): any {
1158
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1159
+ // Make sure newData is unproxied
1160
+ if (typeof newData === "object" && newData)
1161
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
1162
+ const oldData = target.get(key);
1163
+ if (newData !== oldData) {
1164
+ target.set(key, newData);
1165
+ emit(target, key, newData, oldData);
1166
+ emit(target, MAP_SIZE_SYMBOL, target.size, target.size - (oldData === undefined ? 1 : 0));
1167
+ runImmediateQueue();
1168
+ }
1169
+ return this;
1170
+ },
1171
+ delete(this: any, key: any): boolean {
1172
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1173
+ const oldData = target.get(key);
1174
+ const result: boolean = target.delete(key);
1175
+ if (result) {
1176
+ emit(target, key, undefined, oldData);
1177
+ emit(target, MAP_SIZE_SYMBOL, target.size, target.size + 1);
1178
+ runImmediateQueue();
1179
+ }
1180
+ return result;
1181
+ },
1182
+ clear(this: any): void {
1183
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1184
+ const oldSize = target.size;
1185
+ for (const key of target.keys()) {
1186
+ const oldData = target.get(key);
1187
+ emit(target, key, undefined, oldData);
1188
+ }
1189
+ target.clear();
1190
+ emit(target, MAP_SIZE_SYMBOL, 0, oldSize);
1191
+ runImmediateQueue();
1192
+ },
1193
+ has(this: any, key: any): boolean {
1194
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1195
+ subscribe(target, key);
1196
+ return target.has(key);
1197
+ },
1198
+ keys(this: any): IterableIterator<any> {
1199
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1200
+ subscribe(target, ANY_SYMBOL);
1201
+ return target.keys();
1202
+ },
1203
+ values(this: any): IterableIterator<any> {
1204
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1205
+ subscribe(target, ANY_SYMBOL);
1206
+ return target.values();
1207
+ },
1208
+ entries(this: any): IterableIterator<[any, any]> {
1209
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1210
+ subscribe(target, ANY_SYMBOL);
1211
+ return target.entries();
1212
+ },
1213
+ [Symbol.iterator](this: any): IterableIterator<[any, any]> {
1214
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1215
+ subscribe(target, ANY_SYMBOL);
1216
+ return target[Symbol.iterator]();
1217
+ }
1218
+ };
1219
+
1220
+ const mapHandler: ProxyHandler<Map<any, any>> = {
1221
+ get(target: Map<any, any>, prop: any) {
1222
+ if (prop === TARGET_SYMBOL) return target;
1223
+
1224
+ // Handle Map methods using lookup object
1225
+ if (prop in mapMethodHandlers) {
1226
+ return (mapMethodHandlers as any)[prop];
1227
+ }
1228
+
1229
+ // Handle size property
1230
+ if (prop === "size") {
1231
+ subscribe(target, MAP_SIZE_SYMBOL);
1232
+ return target.size;
1233
+ }
1234
+
1235
+ // Handle other properties normally
1236
+ subscribe(target, prop);
1237
+ return optProxy((target as any)[prop]);
1238
+ },
1239
+ };
1240
+
1126
1241
  const proxyMap = new WeakMap<TargetType, /*Proxy*/ TargetType>();
1127
1242
 
1128
1243
  function optProxy(value: any): any {
@@ -1137,15 +1252,21 @@ function optProxy(value: any): any {
1137
1252
  let proxied = proxyMap.get(value);
1138
1253
  if (proxied) return proxied; // Only one proxy per target!
1139
1254
 
1140
- proxied = new Proxy(
1141
- value,
1142
- value instanceof Array ? arrayHandler : objectHandler,
1143
- );
1255
+ let handler;
1256
+ if (value instanceof Array) {
1257
+ handler = arrayHandler;
1258
+ } else if (value instanceof Map) {
1259
+ handler = mapHandler;
1260
+ } else {
1261
+ handler = objectHandler;
1262
+ }
1263
+
1264
+ proxied = new Proxy(value, handler);
1144
1265
  proxyMap.set(value, proxied as TargetType);
1145
1266
  return proxied;
1146
1267
  }
1147
1268
 
1148
- export function proxy<T extends DatumType>(
1269
+ export function proxy<T extends any>(
1149
1270
  target: Array<T>,
1150
1271
  ): Array<
1151
1272
  T extends number
@@ -1157,7 +1278,7 @@ export function proxy<T extends DatumType>(
1157
1278
  : T
1158
1279
  >;
1159
1280
  export function proxy<T extends object>(target: T): T;
1160
- export function proxy<T extends DatumType>(
1281
+ export function proxy<T extends any>(
1161
1282
  target: T,
1162
1283
  ): ValueRef<
1163
1284
  T extends number
@@ -1284,14 +1405,17 @@ function destroyWithClass(element: Element, cls: string) {
1284
1405
  * will recursively copy properties into the existing `dst` object instead of replacing it.
1285
1406
  * This minimizes change notifications for reactive updates.
1286
1407
  * - **Handles Proxies:** Can accept proxied or unproxied objects/arrays for both `dst` and `src`.
1408
+ * - **Cross-Type Copying:** Supports copying between Maps and objects. When copying from an object
1409
+ * to a Map, object properties become Map entries. When copying from a Map to an object, Map entries
1410
+ * become object properties (only for Maps with string/number/symbol keys).
1287
1411
  *
1288
- * @param dst - The destination object/array (proxied or unproxied).
1289
- * @param src - The source object/array (proxied or unproxied). It won't be modified.
1412
+ * @param dst - The destination object/array/Map (proxied or unproxied).
1413
+ * @param src - The source object/array/Map (proxied or unproxied). It won't be modified.
1290
1414
  * @param flags - Bitmask controlling copy behavior:
1291
1415
  * - {@link MERGE}: Performs a partial update. Properties in `dst` not present in `src` are kept.
1292
1416
  * `null`/`undefined` in `src` delete properties in `dst`. Handles partial array updates via object keys.
1293
1417
  * - {@link SHALLOW}: Performs a shallow copy; when an array/object of the right type doesn't exist in `dst` yet, a reference to the array/object in `src` will be made, instead of creating a copy. If the array/object already exists, it won't be replaced (by a reference), but all items will be individually checked and copied like normal, keeping changes (and therefore UI updates) to a minimum.
1294
- * @template T - The type of the objects being copied.
1418
+ * @template T - The type of the destination object.
1295
1419
  * @throws Error if attempting to copy an array into a non-array or vice versa (unless {@link MERGE} is set, allowing for sparse array updates).
1296
1420
  *
1297
1421
  * @example Basic Copy
@@ -1302,6 +1426,22 @@ function destroyWithClass(element: Element, cls: string) {
1302
1426
  * console.log(dest); // proxy({ a: 1, b: { c: 2 } })
1303
1427
  * ```
1304
1428
  *
1429
+ * @example Map to Object
1430
+ * ```typescript
1431
+ * const source = new Map([['x', 3], ['y', 4]]);
1432
+ * const dest = proxy({});
1433
+ * copy(dest, source);
1434
+ * console.log(dest); // proxy({ x: 3, y: 4 })
1435
+ * ```
1436
+ *
1437
+ * @example Object to Map
1438
+ * ```typescript
1439
+ * const source = { x: 3, y: 4 };
1440
+ * const dest = proxy(new Map());
1441
+ * copy(dest, source);
1442
+ * console.log(dest); // proxy(Map([['x', 3], ['y', 4]]))
1443
+ * ```
1444
+ *
1305
1445
  * @example MERGE
1306
1446
  * ```typescript
1307
1447
  * const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
@@ -1327,7 +1467,17 @@ function destroyWithClass(element: Element, cls: string) {
1327
1467
  * console.log(source.nested); // [1, 2, 3] (source was modified)
1328
1468
  * ```
1329
1469
  */
1330
- export function copy<T extends object>(dst: T, src: Partial<T>, flags = 0) {
1470
+
1471
+ // Overload for Map destination with object source
1472
+ export function copy<K, V>(dst: Map<K, V>, src: Record<K extends string | number | symbol ? K : never, V> | Partial<Record<K extends string | number | symbol ? K : never, V>>, flags?: number): void;
1473
+ // Overload for Map destination with Map source
1474
+ export function copy<K, V>(dst: Map<K, V>, src: Map<K, V> | Partial<Map<K, V>>, flags?: number): void;
1475
+ // Overload for object destination with Map source
1476
+ export function copy<T extends Record<string | number | symbol, any>>(dst: T, src: Map<keyof T, T[keyof T]>, flags?: number): void;
1477
+ // Overload for same-type copying
1478
+ export function copy<T extends object>(dst: T, src: Partial<T>, flags?: number): void;
1479
+ // Implementation
1480
+ export function copy(dst: any, src: any, flags = 0) {
1331
1481
  copyRecurse(dst, src, flags);
1332
1482
  runImmediateQueue();
1333
1483
  }
@@ -1348,11 +1498,24 @@ const COPY_EMIT = 64;
1348
1498
  * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
1349
1499
  */
1350
1500
  export function clone<T extends object>(src: T, flags = 0): T {
1351
- const dst = Object.create(Object.getPrototypeOf(src)) as T;
1501
+ let dst: T;
1502
+ if (src instanceof Map) {
1503
+ dst = new Map() as T;
1504
+ } else {
1505
+ dst = Object.create(Object.getPrototypeOf(src)) as T;
1506
+ }
1352
1507
  copyRecurse(dst, src, flags);
1353
1508
  return dst;
1354
1509
  }
1355
1510
 
1511
+ function getEntries(subject: any) {
1512
+ return (subject instanceof Map) ? subject.entries() : Object.entries(subject);
1513
+ }
1514
+
1515
+ function getKeys(subject: any) {
1516
+ return (subject instanceof Map) ? subject.keys() : Object.keys(subject);
1517
+ }
1518
+
1356
1519
  function copyRecurse(dst: any, src: any, flags: number) {
1357
1520
  // We never want to subscribe to reads we do to the target (to find changes). So we'll
1358
1521
  // take the unproxied version and `emit` updates ourselve.
@@ -1376,9 +1539,9 @@ function copyRecurse(dst: any, src: any, flags: number) {
1376
1539
  const dstLen = dst.length;
1377
1540
  const srcLen = src.length;
1378
1541
  for (let i = 0; i < srcLen; i++) {
1379
- copyValue(dst, src, i, flags);
1542
+ copyValue(dst, i, src[i], flags);
1380
1543
  }
1381
- // Leaving additional values in the old array doesn't make sense
1544
+ // Leaving additional values in the old array doesn't make sense, so we'll do this even when MERGE is set:
1382
1545
  if (srcLen !== dstLen) {
1383
1546
  if (flags & COPY_EMIT) {
1384
1547
  for (let i = srcLen; i < dstLen; i++) {
@@ -1393,16 +1556,23 @@ function copyRecurse(dst: any, src: any, flags: number) {
1393
1556
  }
1394
1557
  }
1395
1558
  } else {
1396
- for (const k in src) {
1397
- copyValue(dst, src, k, flags);
1559
+ // Copy all entries from src to dst (both of which can be Map or object)
1560
+ for (const [key, value] of getEntries(src)) {
1561
+ copyValue(dst, key, value, flags);
1398
1562
  }
1563
+
1564
+ // Remove entries from dst that don't exist in src (unless MERGE flag is set)
1399
1565
  if (!(flags & MERGE)) {
1400
- for (const k in dst) {
1401
- if (!(k in src)) {
1402
- const old = dst[k];
1403
- delete dst[k];
1404
- if (flags & COPY_EMIT && old !== undefined) {
1405
- emit(dst, k, undefined, old);
1566
+ if (src instanceof Map) {
1567
+ for (const key of getKeys(dst)) {
1568
+ if (!src.has(key)) {
1569
+ deleteKey(dst, key, flags);
1570
+ }
1571
+ }
1572
+ } else {
1573
+ for (const key of getKeys(dst)) {
1574
+ if (!(key in src)) {
1575
+ deleteKey(dst, key, flags);
1406
1576
  }
1407
1577
  }
1408
1578
  }
@@ -1410,17 +1580,26 @@ function copyRecurse(dst: any, src: any, flags: number) {
1410
1580
  }
1411
1581
  }
1412
1582
 
1413
- function copyValue(dst: any, src: any, index: any, flags: number) {
1414
- const dstValue = dst[index];
1415
- let srcValue = src[index];
1583
+ function deleteKey(dst: any, key: any, flags: number) {
1584
+ let old;
1585
+ if (dst instanceof Map) {
1586
+ old = dst.get(key);
1587
+ dst.delete(key);
1588
+ } else {
1589
+ old = dst[key];
1590
+ delete dst[key];
1591
+ }
1592
+ if (flags & COPY_EMIT && old !== undefined) {
1593
+ emit(dst, key, undefined, old);
1594
+ }
1595
+ }
1596
+
1597
+ function copyValue(dst: any, index: any, srcValue: any, flags: number) {
1598
+ const dstValue = dst instanceof Map ? dst.get(index) : dst[index];
1416
1599
  if (srcValue !== dstValue) {
1417
1600
  if (
1418
- srcValue &&
1419
- dstValue &&
1420
- typeof srcValue === "object" &&
1421
- typeof dstValue === "object" &&
1422
- (srcValue.constructor === dstValue.constructor ||
1423
- (flags & MERGE && dstValue instanceof Array))
1601
+ srcValue && dstValue && typeof srcValue === "object" && typeof dstValue === "object" &&
1602
+ (srcValue.constructor === dstValue.constructor || (flags & MERGE && dstValue instanceof Array))
1424
1603
  ) {
1425
1604
  copyRecurse(dstValue, srcValue, flags);
1426
1605
  return;
@@ -1434,10 +1613,14 @@ function copyValue(dst: any, src: any, index: any, flags: number) {
1434
1613
  copyRecurse(copy, srcValue, 0);
1435
1614
  srcValue = copy;
1436
1615
  }
1437
- const old = dst[index];
1438
- if (flags & MERGE && srcValue == null) delete dst[index];
1439
- else dst[index] = srcValue;
1440
- if (flags & COPY_EMIT) emit(dst, index, srcValue, old);
1616
+ if (dst instanceof Map) {
1617
+ if (flags & MERGE && srcValue == null) dst.delete(index);
1618
+ else dst.set(index, srcValue);
1619
+ } else {
1620
+ if (flags & MERGE && srcValue == null) delete dst[index];
1621
+ else dst[index] = srcValue;
1622
+ }
1623
+ if (flags & COPY_EMIT) emit(dst, index, srcValue, dstValue);
1441
1624
  }
1442
1625
  }
1443
1626
 
@@ -2220,10 +2403,15 @@ export function peek<T>(func: () => T): T {
2220
2403
  }
2221
2404
  }
2222
2405
 
2406
+ /** When using a Map as `source`. */
2407
+ export function map<K, IN, OUT>(
2408
+ source: Map<K, IN>,
2409
+ func: (value: IN, key: K) => undefined | OUT,
2410
+ ): Map<K, OUT>;
2223
2411
  /** When using an object as `source`. */
2224
- export function map<IN, OUT>(
2225
- source: Record<string | symbol, IN>,
2226
- func: (value: IN, index: string | symbol) => undefined | OUT,
2412
+ export function map<IN, const IN_KEY extends string | number | symbol, OUT>(
2413
+ source: Record<IN_KEY, IN>,
2414
+ func: (value: IN, index: KeyToString<IN_KEY>) => undefined | OUT,
2227
2415
  ): Record<string | symbol, OUT>;
2228
2416
  /** When using an array as `source`. */
2229
2417
  export function map<IN, OUT>(
@@ -2275,23 +2463,38 @@ export function map<IN, OUT>(
2275
2463
  */
2276
2464
  export function map(
2277
2465
  source: any,
2278
- func: (value: DatumType, key: any) => any,
2466
+ func: (value: any, key: any) => any,
2279
2467
  ): any {
2280
- const out = optProxy(source instanceof Array ? [] : {});
2281
- onEach(source, (item: DatumType, key: symbol | string | number) => {
2468
+ let out;
2469
+ if (source instanceof Array) {
2470
+ out = optProxy([]);
2471
+ } else if (source instanceof Map) {
2472
+ out = optProxy(new Map());
2473
+ } else {
2474
+ out = optProxy({});
2475
+ }
2476
+
2477
+ onEach(source, (item: any, key: symbol | string | number) => {
2282
2478
  const value = func(item, key);
2283
2479
  if (value !== undefined) {
2284
- out[key] = value;
2285
- clean(() => {
2286
- delete out[key];
2287
- });
2480
+ if (out instanceof Map) {
2481
+ out.set(key, value);
2482
+ clean(() => {
2483
+ out.delete(key);
2484
+ });
2485
+ } else {
2486
+ out[key] = value;
2487
+ clean(() => {
2488
+ delete out[key];
2489
+ });
2490
+ }
2288
2491
  }
2289
2492
  });
2290
2493
  return out;
2291
2494
  }
2292
2495
 
2293
2496
  /** When using an array as `source`. */
2294
- export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2497
+ export function multiMap<IN, OUT extends { [key: string | symbol]: any }>(
2295
2498
  source: Array<IN>,
2296
2499
  func: (value: IN, index: number) => OUT | undefined,
2297
2500
  ): OUT;
@@ -2299,8 +2502,14 @@ export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2299
2502
  export function multiMap<
2300
2503
  K extends string | number | symbol,
2301
2504
  IN,
2302
- OUT extends { [key: string | symbol]: DatumType },
2303
- >(source: Record<K, IN>, func: (value: IN, index: K) => OUT | undefined): OUT;
2505
+ OUT extends { [key: string | symbol]: any },
2506
+ >(source: Record<K, IN>, func: (value: IN, index: KeyToString<K>) => OUT | undefined): OUT;
2507
+ /** When using a Map as `source`. */
2508
+ export function multiMap<
2509
+ K,
2510
+ IN,
2511
+ OUT extends { [key: string | symbol]: any },
2512
+ >(source: Map<K, IN>, func: (value: IN, key: K) => OUT | undefined): OUT;
2304
2513
  /**
2305
2514
  * Reactively maps items from a source proxy (array or object) to a target proxied object,
2306
2515
  * where each source item can contribute multiple key-value pairs to the target.
@@ -2344,10 +2553,10 @@ export function multiMap<
2344
2553
  */
2345
2554
  export function multiMap(
2346
2555
  source: any,
2347
- func: (value: DatumType, key: any) => Record<string | symbol, DatumType>,
2556
+ func: (value: any, key: any) => Record<string | symbol, any>,
2348
2557
  ): any {
2349
2558
  const out = optProxy({});
2350
- onEach(source, (item: DatumType, key: symbol | string | number) => {
2559
+ onEach(source, (item: any, key: symbol | string | number) => {
2351
2560
  const pairs = func(item, key);
2352
2561
  if (pairs) {
2353
2562
  for (const key in pairs) out[key] = pairs[key];
@@ -2373,6 +2582,15 @@ export function partition<
2373
2582
  source: Record<IN_K, IN_V>,
2374
2583
  func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2375
2584
  ): Record<OUT_K, Record<IN_K, IN_V>>;
2585
+ /** When using a Map as `source`. */
2586
+ export function partition<
2587
+ IN_K extends string | number | symbol,
2588
+ OUT_K extends string | number | symbol,
2589
+ IN_V,
2590
+ >(
2591
+ source: Map<IN_K, IN_V>,
2592
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2593
+ ): Record<OUT_K, Record<IN_K, IN_V>>;
2376
2594
 
2377
2595
  /**
2378
2596
  * @overload
@@ -2447,18 +2665,18 @@ export function partition<
2447
2665
  IN_V,
2448
2666
  >(
2449
2667
  source: Record<IN_K, IN_V>,
2450
- func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2451
- ): Record<OUT_K, Record<IN_K, IN_V>> {
2452
- const unproxiedOut = {} as Record<OUT_K, Record<IN_K, IN_V>>;
2668
+ func: (value: IN_V, key: KeyToString<IN_K>) => undefined | OUT_K | OUT_K[],
2669
+ ): Record<OUT_K, Record<KeyToString<IN_K>, IN_V>> {
2670
+ const unproxiedOut = {} as Record<OUT_K, Record<KeyToString<IN_K>, IN_V>>;
2453
2671
  const out = proxy(unproxiedOut);
2454
- onEach(source, (item: IN_V, key: IN_K) => {
2672
+ onEach(source, (item: IN_V, key: KeyToString<IN_K>) => {
2455
2673
  const rsp = func(item, key);
2456
2674
  if (rsp != null) {
2457
2675
  const buckets = rsp instanceof Array ? rsp : [rsp];
2458
2676
  if (buckets.length) {
2459
2677
  for (const bucket of buckets) {
2460
2678
  if (unproxiedOut[bucket]) out[bucket][key] = item;
2461
- else out[bucket] = { [key]: item } as Record<IN_K, IN_V>;
2679
+ else out[bucket] = { [key]: item } as Record<KeyToString<IN_K>, IN_V>;
2462
2680
  }
2463
2681
  clean(() => {
2464
2682
  for (const bucket of buckets) {
@@ -2501,7 +2719,15 @@ export function partition<
2501
2719
  */
2502
2720
  export function dump<T>(data: T): T {
2503
2721
  if (data && typeof data === "object") {
2504
- $({ text: data instanceof Array ? "<array>" : "<object>" });
2722
+ let label;
2723
+ if (data instanceof Array) {
2724
+ label = "<array>";
2725
+ } else if (data instanceof Map) {
2726
+ label = "<Map>";
2727
+ } else {
2728
+ label = "<object>";
2729
+ }
2730
+ $({ text: label });
2505
2731
  $("ul", () => {
2506
2732
  onEach(data as any, (value, key) => {
2507
2733
  $(`li:${JSON.stringify(key)}: `, () => {
@@ -2545,8 +2771,8 @@ export function withEmitHandler(
2545
2771
  handler: (
2546
2772
  target: TargetType,
2547
2773
  index: any,
2548
- newData: DatumType,
2549
- oldData: DatumType,
2774
+ newData: any,
2775
+ oldData: any,
2550
2776
  ) => void,
2551
2777
  func: () => void,
2552
2778
  ) {