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/README.md +1 -1
- package/dist/aberdeen.d.ts +38 -17
- package/dist/aberdeen.js +244 -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 +363 -99
- 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,10 +834,18 @@ function subscribe(
|
|
|
822
834
|
}
|
|
823
835
|
}
|
|
824
836
|
|
|
825
|
-
|
|
826
|
-
|
|
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:
|
|
898
|
-
makeKey?: (value:
|
|
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
|
-
|
|
951
|
-
|
|
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
|
-
|
|
961
|
-
|
|
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:
|
|
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:
|
|
1026
|
-
oldData:
|
|
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
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
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
|
|
1418
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
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:
|
|
2508
|
+
func: (value: any, key: any) => any,
|
|
2283
2509
|
): any {
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
|
2289
|
-
|
|
2290
|
-
|
|
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]:
|
|
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]:
|
|
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:
|
|
2598
|
+
func: (value: any, key: any) => Record<string | symbol, any>,
|
|
2352
2599
|
): any {
|
|
2353
2600
|
const out = optProxy({});
|
|
2354
|
-
onEach(source, (item:
|
|
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
|
-
|
|
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:
|
|
2553
|
-
oldData:
|
|
2816
|
+
newData: any,
|
|
2817
|
+
oldData: any,
|
|
2554
2818
|
) => void,
|
|
2555
2819
|
func: () => void,
|
|
2556
2820
|
) {
|