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/README.md +1 -1
- package/dist/aberdeen.d.ts +42 -19
- package/dist/aberdeen.js +203 -34
- package/dist/aberdeen.js.map +3 -3
- package/dist/prediction.d.ts +2 -2
- package/dist/prediction.js.map +1 -1
- package/dist/route.js +2 -2
- package/dist/route.js.map +3 -3
- package/dist-min/aberdeen.js +5 -5
- package/dist-min/aberdeen.js.map +3 -3
- package/dist-min/prediction.js.map +1 -1
- package/dist-min/route.js +2 -2
- package/dist-min/route.js.map +3 -3
- package/package.json +2 -2
- package/src/aberdeen.ts +335 -109
- package/src/prediction.ts +4 -4
- package/src/route.ts +1 -1
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
|
-
|
|
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
|
-
* @
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
544
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
692
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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:
|
|
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:
|
|
798
|
-
oldData:
|
|
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,
|
|
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,
|
|
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:
|
|
894
|
-
makeKey?: (value:
|
|
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
|
-
|
|
947
|
-
|
|
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
|
-
|
|
957
|
-
|
|
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:
|
|
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:
|
|
1022
|
-
oldData:
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
|
1414
|
-
|
|
1415
|
-
|
|
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
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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<
|
|
2226
|
-
func: (value: IN, index:
|
|
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:
|
|
2466
|
+
func: (value: any, key: any) => any,
|
|
2279
2467
|
): any {
|
|
2280
|
-
|
|
2281
|
-
|
|
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
|
|
2285
|
-
|
|
2286
|
-
|
|
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]:
|
|
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]:
|
|
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:
|
|
2556
|
+
func: (value: any, key: any) => Record<string | symbol, any>,
|
|
2348
2557
|
): any {
|
|
2349
2558
|
const out = optProxy({});
|
|
2350
|
-
onEach(source, (item:
|
|
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
|
|
2452
|
-
const unproxiedOut = {} as Record<OUT_K, Record<IN_K
|
|
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
|
|
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
|
-
|
|
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:
|
|
2549
|
-
oldData:
|
|
2774
|
+
newData: any,
|
|
2775
|
+
oldData: any,
|
|
2550
2776
|
) => void,
|
|
2551
2777
|
func: () => void,
|
|
2552
2778
|
) {
|