aberdeen 1.0.11 → 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,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,96 @@ const arrayHandler: ProxyHandler<any[]> = {
1127
1148
  },
1128
1149
  };
1129
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
+
1130
1241
  const proxyMap = new WeakMap<TargetType, /*Proxy*/ TargetType>();
1131
1242
 
1132
1243
  function optProxy(value: any): any {
@@ -1141,15 +1252,21 @@ function optProxy(value: any): any {
1141
1252
  let proxied = proxyMap.get(value);
1142
1253
  if (proxied) return proxied; // Only one proxy per target!
1143
1254
 
1144
- proxied = new Proxy(
1145
- value,
1146
- value instanceof Array ? arrayHandler : objectHandler,
1147
- );
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);
1148
1265
  proxyMap.set(value, proxied as TargetType);
1149
1266
  return proxied;
1150
1267
  }
1151
1268
 
1152
- export function proxy<T extends DatumType>(
1269
+ export function proxy<T extends any>(
1153
1270
  target: Array<T>,
1154
1271
  ): Array<
1155
1272
  T extends number
@@ -1161,7 +1278,7 @@ export function proxy<T extends DatumType>(
1161
1278
  : T
1162
1279
  >;
1163
1280
  export function proxy<T extends object>(target: T): T;
1164
- export function proxy<T extends DatumType>(
1281
+ export function proxy<T extends any>(
1165
1282
  target: T,
1166
1283
  ): ValueRef<
1167
1284
  T extends number
@@ -1288,14 +1405,17 @@ function destroyWithClass(element: Element, cls: string) {
1288
1405
  * will recursively copy properties into the existing `dst` object instead of replacing it.
1289
1406
  * This minimizes change notifications for reactive updates.
1290
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).
1291
1411
  *
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.
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.
1294
1414
  * @param flags - Bitmask controlling copy behavior:
1295
1415
  * - {@link MERGE}: Performs a partial update. Properties in `dst` not present in `src` are kept.
1296
1416
  * `null`/`undefined` in `src` delete properties in `dst`. Handles partial array updates via object keys.
1297
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.
1298
- * @template T - The type of the objects being copied.
1418
+ * @template T - The type of the destination object.
1299
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).
1300
1420
  *
1301
1421
  * @example Basic Copy
@@ -1306,6 +1426,22 @@ function destroyWithClass(element: Element, cls: string) {
1306
1426
  * console.log(dest); // proxy({ a: 1, b: { c: 2 } })
1307
1427
  * ```
1308
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
+ *
1309
1445
  * @example MERGE
1310
1446
  * ```typescript
1311
1447
  * const source = { b: { c: 99 }, d: undefined }; // d: undefined will delete
@@ -1331,7 +1467,17 @@ function destroyWithClass(element: Element, cls: string) {
1331
1467
  * console.log(source.nested); // [1, 2, 3] (source was modified)
1332
1468
  * ```
1333
1469
  */
1334
- 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) {
1335
1481
  copyRecurse(dst, src, flags);
1336
1482
  runImmediateQueue();
1337
1483
  }
@@ -1352,11 +1498,24 @@ const COPY_EMIT = 64;
1352
1498
  * @returns A new unproxied array or object (of the same type as `src`), containing a deep (by default) copy of `src`.
1353
1499
  */
1354
1500
  export function clone<T extends object>(src: T, flags = 0): T {
1355
- 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
+ }
1356
1507
  copyRecurse(dst, src, flags);
1357
1508
  return dst;
1358
1509
  }
1359
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
+
1360
1519
  function copyRecurse(dst: any, src: any, flags: number) {
1361
1520
  // We never want to subscribe to reads we do to the target (to find changes). So we'll
1362
1521
  // take the unproxied version and `emit` updates ourselve.
@@ -1380,9 +1539,9 @@ function copyRecurse(dst: any, src: any, flags: number) {
1380
1539
  const dstLen = dst.length;
1381
1540
  const srcLen = src.length;
1382
1541
  for (let i = 0; i < srcLen; i++) {
1383
- copyValue(dst, src, i, flags);
1542
+ copyValue(dst, i, src[i], flags);
1384
1543
  }
1385
- // 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:
1386
1545
  if (srcLen !== dstLen) {
1387
1546
  if (flags & COPY_EMIT) {
1388
1547
  for (let i = srcLen; i < dstLen; i++) {
@@ -1397,16 +1556,23 @@ function copyRecurse(dst: any, src: any, flags: number) {
1397
1556
  }
1398
1557
  }
1399
1558
  } else {
1400
- for (const k in src) {
1401
- 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);
1402
1562
  }
1563
+
1564
+ // Remove entries from dst that don't exist in src (unless MERGE flag is set)
1403
1565
  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);
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);
1410
1576
  }
1411
1577
  }
1412
1578
  }
@@ -1414,17 +1580,26 @@ function copyRecurse(dst: any, src: any, flags: number) {
1414
1580
  }
1415
1581
  }
1416
1582
 
1417
- function copyValue(dst: any, src: any, index: any, flags: number) {
1418
- const dstValue = dst[index];
1419
- 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];
1420
1599
  if (srcValue !== dstValue) {
1421
1600
  if (
1422
- srcValue &&
1423
- dstValue &&
1424
- typeof srcValue === "object" &&
1425
- typeof dstValue === "object" &&
1426
- (srcValue.constructor === dstValue.constructor ||
1427
- (flags & MERGE && dstValue instanceof Array))
1601
+ srcValue && dstValue && typeof srcValue === "object" && typeof dstValue === "object" &&
1602
+ (srcValue.constructor === dstValue.constructor || (flags & MERGE && dstValue instanceof Array))
1428
1603
  ) {
1429
1604
  copyRecurse(dstValue, srcValue, flags);
1430
1605
  return;
@@ -1438,10 +1613,14 @@ function copyValue(dst: any, src: any, index: any, flags: number) {
1438
1613
  copyRecurse(copy, srcValue, 0);
1439
1614
  srcValue = copy;
1440
1615
  }
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);
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);
1445
1624
  }
1446
1625
  }
1447
1626
 
@@ -2224,6 +2403,11 @@ export function peek<T>(func: () => T): T {
2224
2403
  }
2225
2404
  }
2226
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>;
2227
2411
  /** When using an object as `source`. */
2228
2412
  export function map<IN, const IN_KEY extends string | number | symbol, OUT>(
2229
2413
  source: Record<IN_KEY, IN>,
@@ -2279,23 +2463,38 @@ export function map<IN, OUT>(
2279
2463
  */
2280
2464
  export function map(
2281
2465
  source: any,
2282
- func: (value: DatumType, key: any) => any,
2466
+ func: (value: any, key: any) => any,
2283
2467
  ): any {
2284
- const out = optProxy(source instanceof Array ? [] : {});
2285
- 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) => {
2286
2478
  const value = func(item, key);
2287
2479
  if (value !== undefined) {
2288
- out[key] = value;
2289
- clean(() => {
2290
- delete out[key];
2291
- });
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
+ }
2292
2491
  }
2293
2492
  });
2294
2493
  return out;
2295
2494
  }
2296
2495
 
2297
2496
  /** When using an array as `source`. */
2298
- export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2497
+ export function multiMap<IN, OUT extends { [key: string | symbol]: any }>(
2299
2498
  source: Array<IN>,
2300
2499
  func: (value: IN, index: number) => OUT | undefined,
2301
2500
  ): OUT;
@@ -2303,8 +2502,14 @@ export function multiMap<IN, OUT extends { [key: string | symbol]: DatumType }>(
2303
2502
  export function multiMap<
2304
2503
  K extends string | number | symbol,
2305
2504
  IN,
2306
- OUT extends { [key: string | symbol]: DatumType },
2505
+ OUT extends { [key: string | symbol]: any },
2307
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;
2308
2513
  /**
2309
2514
  * Reactively maps items from a source proxy (array or object) to a target proxied object,
2310
2515
  * where each source item can contribute multiple key-value pairs to the target.
@@ -2348,10 +2553,10 @@ export function multiMap<
2348
2553
  */
2349
2554
  export function multiMap(
2350
2555
  source: any,
2351
- func: (value: DatumType, key: any) => Record<string | symbol, DatumType>,
2556
+ func: (value: any, key: any) => Record<string | symbol, any>,
2352
2557
  ): any {
2353
2558
  const out = optProxy({});
2354
- onEach(source, (item: DatumType, key: symbol | string | number) => {
2559
+ onEach(source, (item: any, key: symbol | string | number) => {
2355
2560
  const pairs = func(item, key);
2356
2561
  if (pairs) {
2357
2562
  for (const key in pairs) out[key] = pairs[key];
@@ -2377,6 +2582,15 @@ export function partition<
2377
2582
  source: Record<IN_K, IN_V>,
2378
2583
  func: (value: IN_V, key: IN_K) => undefined | OUT_K | OUT_K[],
2379
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>>;
2380
2594
 
2381
2595
  /**
2382
2596
  * @overload
@@ -2505,7 +2719,15 @@ export function partition<
2505
2719
  */
2506
2720
  export function dump<T>(data: T): T {
2507
2721
  if (data && typeof data === "object") {
2508
- $({ 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 });
2509
2731
  $("ul", () => {
2510
2732
  onEach(data as any, (value, key) => {
2511
2733
  $(`li:${JSON.stringify(key)}: `, () => {
@@ -2549,8 +2771,8 @@ export function withEmitHandler(
2549
2771
  handler: (
2550
2772
  target: TargetType,
2551
2773
  index: any,
2552
- newData: DatumType,
2553
- oldData: DatumType,
2774
+ newData: any,
2775
+ oldData: any,
2554
2776
  ) => void,
2555
2777
  func: () => void,
2556
2778
  ) {