aberdeen 1.0.11 → 1.0.13

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,10 +834,18 @@ function subscribe(
822
834
  }
823
835
  }
824
836
 
825
- // Records in TypeScript pretend that they can have number keys, but in reality they are converted to string.
826
- // This type changes (number | something) types to (string | something) types, maintaining typing precision as much as possible.
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
+ */
827
842
  type KeyToString<K> = K extends number ? string : K extends string | symbol ? K : K extends number | infer U ? string | U : K;
828
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;
829
849
  export function onEach<T>(
830
850
  target: ReadonlyArray<undefined | T>,
831
851
  render: (value: T, index: number) => void,
@@ -894,8 +914,8 @@ export function onEach<K extends string | number | symbol, T>(
894
914
  */
895
915
  export function onEach(
896
916
  target: TargetType,
897
- render: (value: DatumType, index: any) => void,
898
- makeKey?: (value: DatumType, key: any) => SortKeyType,
917
+ render: (value: any, index: any) => void,
918
+ makeKey?: (value: any, key: any) => SortKeyType,
899
919
  ): void {
900
920
  if (!target || typeof target !== "object")
901
921
  throw new Error("onEach requires an object");
@@ -946,23 +966,23 @@ export function isEmpty(proxied: TargetType): boolean {
946
966
  const scope = currentScope;
947
967
 
948
968
  if (target instanceof Array) {
949
- subscribe(
950
- target,
951
- "length",
952
- (index: any, newData: DatumType, oldData: DatumType) => {
953
- if (!newData !== !oldData) queue(scope);
954
- },
955
- );
969
+ subscribe(target, "length", (index: any, newData: any, oldData: any) => {
970
+ if (!newData !== !oldData) queue(scope);
971
+ });
956
972
  return !target.length;
957
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
+
958
982
  const result = isObjEmpty(target);
959
- subscribe(
960
- target,
961
- ANY_SYMBOL,
962
- (index: any, newData: DatumType, oldData: DatumType) => {
963
- if (result ? oldData === undefined : newData === undefined) queue(scope);
964
- },
965
- );
983
+ subscribe(target, ANY_SYMBOL, (index: any, newData: any, oldData: any) => {
984
+ if (result ? oldData === undefined : newData === undefined) queue(scope);
985
+ });
966
986
  return result;
967
987
  }
968
988
 
@@ -999,6 +1019,7 @@ export interface ValueRef<T> {
999
1019
  */
1000
1020
  export function count(proxied: TargetType): ValueRef<number> {
1001
1021
  if (proxied instanceof Array) return ref(proxied, "length");
1022
+ if (proxied instanceof Map) return ref(proxied, "size");
1002
1023
 
1003
1024
  const target = (proxied as any)[TARGET_SYMBOL] || proxied;
1004
1025
  let cnt = 0;
@@ -1008,7 +1029,7 @@ export function count(proxied: TargetType): ValueRef<number> {
1008
1029
  subscribe(
1009
1030
  target,
1010
1031
  ANY_SYMBOL,
1011
- (index: any, newData: DatumType, oldData: DatumType) => {
1032
+ (index: any, newData: any, oldData: any) => {
1012
1033
  if (oldData === newData) {
1013
1034
  } else if (oldData === undefined) result.value = ++cnt;
1014
1035
  else if (newData === undefined) result.value = --cnt;
@@ -1022,8 +1043,8 @@ export function count(proxied: TargetType): ValueRef<number> {
1022
1043
  export function defaultEmitHandler(
1023
1044
  target: TargetType,
1024
1045
  index: string | symbol | number,
1025
- newData: DatumType,
1026
- oldData: DatumType,
1046
+ newData: any,
1047
+ oldData: any,
1027
1048
  ) {
1028
1049
  // We're triggering for values changing from undefined to undefined, as this *may*
1029
1050
  // indicate a change from or to `[empty]` (such as `[,1][0]`).
@@ -1127,6 +1148,138 @@ const arrayHandler: ProxyHandler<any[]> = {
1127
1148
  },
1128
1149
  };
1129
1150
 
1151
+ /**
1152
+ * Helper functions that wrap iterators to proxy values
1153
+ */
1154
+ function wrapIteratorSingle(iterator: IterableIterator<any>): IterableIterator<any> {
1155
+ return {
1156
+ [Symbol.iterator]() { return this; },
1157
+ next() {
1158
+ const result = iterator.next();
1159
+ if (result.done) return result;
1160
+ return {
1161
+ done: false,
1162
+ value: optProxy(result.value)
1163
+ };
1164
+ }
1165
+ };
1166
+ }
1167
+ function wrapIteratorPair(iterator: IterableIterator<[any, any]>): IterableIterator<[any, any]> {
1168
+ return {
1169
+ [Symbol.iterator]() { return this; },
1170
+ next() {
1171
+ const result = iterator.next();
1172
+ if (result.done) return result;
1173
+ return {
1174
+ done: false,
1175
+ value: [optProxy(result.value[0]), optProxy(result.value[1])]
1176
+ };
1177
+ }
1178
+ };
1179
+ }
1180
+
1181
+ const mapMethodHandlers = {
1182
+ get(this: any, key: any): any {
1183
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1184
+ // Make sure key is unproxied
1185
+ if (typeof key === "object" && key)
1186
+ key = (key as any)[TARGET_SYMBOL] || key;
1187
+ subscribe(target, key);
1188
+ return optProxy(target.get(key));
1189
+ },
1190
+ set(this: any, key: any, newData: any): any {
1191
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1192
+ // Make sure key and newData are unproxied
1193
+ if (typeof key === "object" && key)
1194
+ key = (key as any)[TARGET_SYMBOL] || key;
1195
+ if (typeof newData === "object" && newData)
1196
+ newData = (newData as any)[TARGET_SYMBOL] || newData;
1197
+ const oldData = target.get(key);
1198
+ if (newData !== oldData) {
1199
+ const oldSize = target.size;
1200
+ target.set(key, newData);
1201
+ emit(target, key, newData, oldData);
1202
+ emit(target, MAP_SIZE_SYMBOL, target.size, oldSize);
1203
+ runImmediateQueue();
1204
+ }
1205
+ return this;
1206
+ },
1207
+ delete(this: any, key: any): boolean {
1208
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1209
+ // Make sure key is unproxied
1210
+ if (typeof key === "object" && key)
1211
+ key = (key as any)[TARGET_SYMBOL] || key;
1212
+ const oldData = target.get(key);
1213
+ const result: boolean = target.delete(key);
1214
+ if (result) {
1215
+ emit(target, key, undefined, oldData);
1216
+ emit(target, MAP_SIZE_SYMBOL, target.size, target.size + 1);
1217
+ runImmediateQueue();
1218
+ }
1219
+ return result;
1220
+ },
1221
+ clear(this: any): void {
1222
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1223
+ const oldSize = target.size;
1224
+ for (const key of target.keys()) {
1225
+ const oldData = target.get(key);
1226
+ emit(target, key, undefined, oldData);
1227
+ }
1228
+ target.clear();
1229
+ emit(target, MAP_SIZE_SYMBOL, 0, oldSize);
1230
+ runImmediateQueue();
1231
+ },
1232
+ has(this: any, key: any): boolean {
1233
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1234
+ // Make sure key is unproxied
1235
+ if (typeof key === "object" && key)
1236
+ key = (key as any)[TARGET_SYMBOL] || key;
1237
+ subscribe(target, key);
1238
+ return target.has(key);
1239
+ },
1240
+ keys(this: any): IterableIterator<any> {
1241
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1242
+ subscribe(target, ANY_SYMBOL);
1243
+ return wrapIteratorSingle(target.keys());
1244
+ },
1245
+ values(this: any): IterableIterator<any> {
1246
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1247
+ subscribe(target, ANY_SYMBOL);
1248
+ return wrapIteratorSingle(target.values());
1249
+ },
1250
+ entries(this: any): IterableIterator<[any, any]> {
1251
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1252
+ subscribe(target, ANY_SYMBOL);
1253
+ return wrapIteratorPair(target.entries());
1254
+ },
1255
+ [Symbol.iterator](this: any): IterableIterator<[any, any]> {
1256
+ const target: Map<any, any> = this[TARGET_SYMBOL];
1257
+ subscribe(target, ANY_SYMBOL);
1258
+ return wrapIteratorPair(target[Symbol.iterator]());
1259
+ }
1260
+ };
1261
+
1262
+ const mapHandler: ProxyHandler<Map<any, any>> = {
1263
+ get(target: Map<any, any>, prop: any) {
1264
+ if (prop === TARGET_SYMBOL) return target;
1265
+
1266
+ // Handle Map methods using lookup object
1267
+ if (prop in mapMethodHandlers) {
1268
+ return (mapMethodHandlers as any)[prop];
1269
+ }
1270
+
1271
+ // Handle size property
1272
+ if (prop === "size") {
1273
+ subscribe(target, MAP_SIZE_SYMBOL);
1274
+ return target.size;
1275
+ }
1276
+
1277
+ // Handle other properties normally
1278
+ subscribe(target, prop);
1279
+ return optProxy((target as any)[prop]);
1280
+ },
1281
+ };
1282
+
1130
1283
  const proxyMap = new WeakMap<TargetType, /*Proxy*/ TargetType>();
1131
1284
 
1132
1285
  function optProxy(value: any): any {
@@ -1141,15 +1294,21 @@ function optProxy(value: any): any {
1141
1294
  let proxied = proxyMap.get(value);
1142
1295
  if (proxied) return proxied; // Only one proxy per target!
1143
1296
 
1144
- proxied = new Proxy(
1145
- value,
1146
- value instanceof Array ? arrayHandler : objectHandler,
1147
- );
1297
+ let handler;
1298
+ if (value instanceof Array) {
1299
+ handler = arrayHandler;
1300
+ } else if (value instanceof Map) {
1301
+ handler = mapHandler;
1302
+ } else {
1303
+ handler = objectHandler;
1304
+ }
1305
+
1306
+ proxied = new Proxy(value, handler);
1148
1307
  proxyMap.set(value, proxied as TargetType);
1149
1308
  return proxied;
1150
1309
  }
1151
1310
 
1152
- export function proxy<T extends DatumType>(
1311
+ export function proxy<T extends any>(
1153
1312
  target: Array<T>,
1154
1313
  ): Array<
1155
1314
  T extends number
@@ -1161,7 +1320,7 @@ export function proxy<T extends DatumType>(
1161
1320
  : T
1162
1321
  >;
1163
1322
  export function proxy<T extends object>(target: T): T;
1164
- export function proxy<T extends DatumType>(
1323
+ export function proxy<T extends any>(
1165
1324
  target: T,
1166
1325
  ): ValueRef<
1167
1326
  T extends number
@@ -1288,14 +1447,17 @@ function destroyWithClass(element: Element, cls: string) {
1288
1447
  * will recursively copy properties into the existing `dst` object instead of replacing it.
1289
1448
  * This minimizes change notifications for reactive updates.
1290
1449
  * - **Handles Proxies:** Can accept proxied or unproxied objects/arrays for both `dst` and `src`.
1450
+ * - **Cross-Type Copying:** Supports copying between Maps and objects. When copying from an object
1451
+ * to a Map, object properties become Map entries. When copying from a Map to an object, Map entries
1452
+ * become object properties (only for Maps with string/number/symbol keys).
1291
1453
  *
1292
- * @param dst - The destination object/array (proxied or unproxied).
1293
- * @param src - The source object/array (proxied or unproxied). It won't be modified.
1454
+ * @param dst - The destination object/array/Map (proxied or unproxied).
1455
+ * @param src - The source object/array/Map (proxied or unproxied). It won't be modified.
1294
1456
  * @param flags - Bitmask controlling copy behavior:
1295
1457
  * - {@link MERGE}: Performs a partial update. Properties in `dst` not present in `src` are kept.
1296
1458
  * `null`/`undefined` in `src` delete properties in `dst`. Handles partial array updates via object keys.
1297
1459
  * - {@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.
1298
- * @template T - The type of the objects being copied.
1460
+ * @template T - The type of the destination object.
1299
1461
  * @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).
1300
1462
  *
1301
1463
  * @example Basic Copy
@@ -1306,6 +1468,22 @@ function destroyWithClass(element: Element, cls: string) {
1306
1468
  * console.log(dest); // proxy({ a: 1, b: { c: 2 } })
1307
1469
  * ```
1308
1470
  *
1471
+ * @example Map to Object
1472
+ * ```typescript
1473
+ * const source = new Map([['x', 3], ['y', 4]]);
1474
+ * const dest = proxy({});
1475
+ * copy(dest, source);
1476
+ * console.log(dest); // proxy({ x: 3, y: 4 })
1477
+ * ```
1478
+ *
1479
+ * @example Object to Map
1480
+ * ```typescript
1481
+ * const source = { x: 3, y: 4 };
1482
+ * const dest = proxy(new Map());
1483
+ * copy(dest, source);
1484
+ * console.log(dest); // proxy(Map([['x', 3], ['y', 4]]))
1485
+ * ```
1486
+ *
1309
1487
  * @example MERGE
1310
1488
  * ```typescript
1311
1489
  * const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
@@ -1331,7 +1509,17 @@ function destroyWithClass(element: Element, cls: string) {
1331
1509
  * console.log(source.nested); // [1, 2, 3] (source was modified)
1332
1510
  * ```
1333
1511
  */
1334
- export function copy<T extends object>(dst: T, src: Partial<T>, flags = 0) {
1512
+
1513
+ // Overload for Map destination with object source
1514
+ 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;
1515
+ // Overload for Map destination with Map source
1516
+ export function copy<K, V>(dst: Map<K, V>, src: Map<K, V> | Partial<Map<K, V>>, flags?: number): void;
1517
+ // Overload for object destination with Map source
1518
+ export function copy<T extends Record<string | number | symbol, any>>(dst: T, src: Map<keyof T, T[keyof T]>, flags?: number): void;
1519
+ // Overload for same-type copying
1520
+ export function copy<T extends object>(dst: T, src: Partial<T>, flags?: number): void;
1521
+ // Implementation
1522
+ export function copy(dst: any, src: any, flags = 0) {
1335
1523
  copyRecurse(dst, src, flags);
1336
1524
  runImmediateQueue();
1337
1525
  }
@@ -1352,11 +1540,24 @@ const COPY_EMIT = 64;
1352
1540
  * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
1353
1541
  */
1354
1542
  export function clone<T extends object>(src: T, flags = 0): T {
1355
- const dst = Object.create(Object.getPrototypeOf(src)) as T;
1543
+ let dst: T;
1544
+ if (src instanceof Map) {
1545
+ dst = new Map() as T;
1546
+ } else {
1547
+ dst = Object.create(Object.getPrototypeOf(src)) as T;
1548
+ }
1356
1549
  copyRecurse(dst, src, flags);
1357
1550
  return dst;
1358
1551
  }
1359
1552
 
1553
+ function getEntries(subject: any) {
1554
+ return (subject instanceof Map) ? subject.entries() : Object.entries(subject);
1555
+ }
1556
+
1557
+ function getKeys(subject: any) {
1558
+ return (subject instanceof Map) ? subject.keys() : Object.keys(subject);
1559
+ }
1560
+
1360
1561
  function copyRecurse(dst: any, src: any, flags: number) {
1361
1562
  // We never want to subscribe to reads we do to the target (to find changes). So we'll
1362
1563
  // take the unproxied version and `emit` updates ourselve.
@@ -1380,9 +1581,9 @@ function copyRecurse(dst: any, src: any, flags: number) {
1380
1581
  const dstLen = dst.length;
1381
1582
  const srcLen = src.length;
1382
1583
  for (let i = 0; i < srcLen; i++) {
1383
- copyValue(dst, src, i, flags);
1584
+ copyValue(dst, i, src[i], flags);
1384
1585
  }
1385
- // Leaving additional values in the old array doesn't make sense
1586
+ // Leaving additional values in the old array doesn't make sense, so we'll do this even when MERGE is set:
1386
1587
  if (srcLen !== dstLen) {
1387
1588
  if (flags & COPY_EMIT) {
1388
1589
  for (let i = srcLen; i < dstLen; i++) {
@@ -1397,16 +1598,23 @@ function copyRecurse(dst: any, src: any, flags: number) {
1397
1598
  }
1398
1599
  }
1399
1600
  } else {
1400
- for (const k in src) {
1401
- copyValue(dst, src, k, flags);
1601
+ // Copy all entries from src to dst (both of which can be Map or object)
1602
+ for (const [key, value] of getEntries(src)) {
1603
+ copyValue(dst, key, value, flags);
1402
1604
  }
1605
+
1606
+ // Remove entries from dst that don't exist in src (unless MERGE flag is set)
1403
1607
  if (!(flags & MERGE)) {
1404
- for (const k in dst) {
1405
- if (!(k in src)) {
1406
- const old = dst[k];
1407
- delete dst[k];
1408
- if (flags & COPY_EMIT && old !== undefined) {
1409
- emit(dst, k, undefined, old);
1608
+ if (src instanceof Map) {
1609
+ for (const key of getKeys(dst)) {
1610
+ if (!src.has(key)) {
1611
+ deleteKey(dst, key, flags);
1612
+ }
1613
+ }
1614
+ } else {
1615
+ for (const key of getKeys(dst)) {
1616
+ if (!(key in src)) {
1617
+ deleteKey(dst, key, flags);
1410
1618
  }
1411
1619
  }
1412
1620
  }
@@ -1414,17 +1622,26 @@ function copyRecurse(dst: any, src: any, flags: number) {
1414
1622
  }
1415
1623
  }
1416
1624
 
1417
- function copyValue(dst: any, src: any, index: any, flags: number) {
1418
- const dstValue = dst[index];
1419
- let srcValue = src[index];
1625
+ function deleteKey(dst: any, key: any, flags: number) {
1626
+ let old;
1627
+ if (dst instanceof Map) {
1628
+ old = dst.get(key);
1629
+ dst.delete(key);
1630
+ } else {
1631
+ old = dst[key];
1632
+ delete dst[key];
1633
+ }
1634
+ if (flags & COPY_EMIT && old !== undefined) {
1635
+ emit(dst, key, undefined, old);
1636
+ }
1637
+ }
1638
+
1639
+ function copyValue(dst: any, index: any, srcValue: any, flags: number) {
1640
+ const dstValue = dst instanceof Map ? dst.get(index) : dst[index];
1420
1641
  if (srcValue !== dstValue) {
1421
1642
  if (
1422
- srcValue &&
1423
- dstValue &&
1424
- typeof srcValue === "object" &&
1425
- typeof dstValue === "object" &&
1426
- (srcValue.constructor === dstValue.constructor ||
1427
- (flags & MERGE && dstValue instanceof Array))
1643
+ srcValue && dstValue && typeof srcValue === "object" && typeof dstValue === "object" &&
1644
+ (srcValue.constructor === dstValue.constructor || (flags & MERGE && dstValue instanceof Array))
1428
1645
  ) {
1429
1646
  copyRecurse(dstValue, srcValue, flags);
1430
1647
  return;
@@ -1438,10 +1655,14 @@ function copyValue(dst: any, src: any, index: any, flags: number) {
1438
1655
  copyRecurse(copy, srcValue, 0);
1439
1656
  srcValue = copy;
1440
1657
  }
1441
- const old = dst[index];
1442
- if (flags & MERGE && srcValue == null) delete dst[index];
1443
- else dst[index] = srcValue;
1444
- if (flags & COPY_EMIT) emit(dst, index, srcValue, old);
1658
+ if (dst instanceof Map) {
1659
+ if (flags & MERGE && srcValue == null) dst.delete(index);
1660
+ else dst.set(index, srcValue);
1661
+ } else {
1662
+ if (flags & MERGE && srcValue == null) delete dst[index];
1663
+ else dst[index] = srcValue;
1664
+ }
1665
+ if (flags & COPY_EMIT) emit(dst, index, srcValue, dstValue);
1445
1666
  }
1446
1667
  }
1447
1668
 
@@ -2224,6 +2445,11 @@ export function peek<T>(func: () => T): T {
2224
2445
  }
2225
2446
  }
2226
2447
 
2448
+ /** When using a Map as `source`. */
2449
+ export function map<K, IN, OUT>(
2450
+ source: Map<K, IN>,
2451
+ func: (value: IN, key: K) => undefined | OUT,
2452
+ ): Map<K, OUT>;
2227
2453
  /** When using an object as `source`. */
2228
2454
  export function map<IN, const IN_KEY extends string | number | symbol, OUT>(
2229
2455
  source: Record<IN_KEY, IN>,
@@ -2279,23 +2505,38 @@ export function map<IN, OUT>(
2279
2505
  */
2280
2506
  export function map(
2281
2507
  source: any,
2282
- func: (value: DatumType, key: any) => any,
2508
+ func: (value: any, key: any) => any,
2283
2509
  ): any {
2284
- const out = optProxy(source instanceof Array ? [] : {});
2285
- onEach(source, (item: DatumType, key: symbol | string | number) => {
2510
+ let out;
2511
+ if (source instanceof Array) {
2512
+ out = optProxy([]);
2513
+ } else if (source instanceof Map) {
2514
+ out = optProxy(new Map());
2515
+ } else {
2516
+ out = optProxy({});
2517
+ }
2518
+
2519
+ onEach(source, (item: any, key: symbol | string | number) => {
2286
2520
  const value = func(item, key);
2287
2521
  if (value !== undefined) {
2288
- out[key] = value;
2289
- clean(() => {
2290
- delete out[key];
2291
- });
2522
+ if (out instanceof Map) {
2523
+ out.set(key, value);
2524
+ clean(() => {
2525
+ out.delete(key);
2526
+ });
2527
+ } else {
2528
+ out[key] = value;
2529
+ clean(() => {
2530
+ delete out[key];
2531
+ });
2532
+ }
2292
2533
  }
2293
2534
  });
2294
2535
  return out;
2295
2536
  }
2296
2537
 
2297
2538
  /** When using an array as `source`. */
2298
- export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2539
+ export function multiMap<IN, OUT extends { [key: string | symbol]: any }>(
2299
2540
  source: Array<IN>,
2300
2541
  func: (value: IN, index: number) => OUT | undefined,
2301
2542
  ): OUT;
@@ -2303,8 +2544,14 @@ export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2303
2544
  export function multiMap<
2304
2545
  K extends string | number | symbol,
2305
2546
  IN,
2306
- OUT extends { [key: string | symbol]: DatumType },
2547
+ OUT extends { [key: string | symbol]: any },
2307
2548
  >(source: Record<K, IN>, func: (value: IN, index: KeyToString<K>) => OUT | undefined): OUT;
2549
+ /** When using a Map as `source`. */
2550
+ export function multiMap<
2551
+ K,
2552
+ IN,
2553
+ OUT extends { [key: string | symbol]: any },
2554
+ >(source: Map<K, IN>, func: (value: IN, key: K) => OUT | undefined): OUT;
2308
2555
  /**
2309
2556
  * Reactively maps items from a source proxy (array or object) to a target proxied object,
2310
2557
  * where each source item can contribute multiple key-value pairs to the target.
@@ -2348,10 +2595,10 @@ export function multiMap<
2348
2595
  */
2349
2596
  export function multiMap(
2350
2597
  source: any,
2351
- func: (value: DatumType, key: any) => Record<string | symbol, DatumType>,
2598
+ func: (value: any, key: any) => Record<string | symbol, any>,
2352
2599
  ): any {
2353
2600
  const out = optProxy({});
2354
- onEach(source, (item: DatumType, key: symbol | string | number) => {
2601
+ onEach(source, (item: any, key: symbol | string | number) => {
2355
2602
  const pairs = func(item, key);
2356
2603
  if (pairs) {
2357
2604
  for (const key in pairs) out[key] = pairs[key];
@@ -2377,6 +2624,15 @@ export function partition<
2377
2624
  source: Record<IN_K, IN_V>,
2378
2625
  func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2379
2626
  ): Record<OUT_K, Record<IN_K, IN_V>>;
2627
+ /** When using a Map as `source`. */
2628
+ export function partition<
2629
+ IN_K extends string | number | symbol,
2630
+ OUT_K extends string | number | symbol,
2631
+ IN_V,
2632
+ >(
2633
+ source: Map<IN_K, IN_V>,
2634
+ func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2635
+ ): Record<OUT_K, Record<IN_K, IN_V>>;
2380
2636
 
2381
2637
  /**
2382
2638
  * @overload
@@ -2505,7 +2761,15 @@ export function partition<
2505
2761
  */
2506
2762
  export function dump<T>(data: T): T {
2507
2763
  if (data && typeof data === "object") {
2508
- $({ text: data instanceof Array ? "<array>" : "<object>" });
2764
+ let label;
2765
+ if (data instanceof Array) {
2766
+ label = "<array>";
2767
+ } else if (data instanceof Map) {
2768
+ label = "<Map>";
2769
+ } else {
2770
+ label = "<object>";
2771
+ }
2772
+ $({ text: label });
2509
2773
  $("ul", () => {
2510
2774
  onEach(data as any, (value, key) => {
2511
2775
  $(`li:${JSON.stringify(key)}: `, () => {
@@ -2549,8 +2813,8 @@ export function withEmitHandler(
2549
2813
  handler: (
2550
2814
  target: TargetType,
2551
2815
  index: any,
2552
- newData: DatumType,
2553
- oldData: DatumType,
2816
+ newData: any,
2817
+ oldData: any,
2554
2818
  ) => void,
2555
2819
  func: () => void,
2556
2820
  ) {