aberdeen 0.0.17 → 0.1.1
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 +12 -0
- package/dist/aberdeen.d.ts +73 -25
- package/dist/aberdeen.js +213 -60
- package/dist/aberdeen.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,3 +116,15 @@ mount(document.body, () => {
|
|
|
116
116
|
## Reference documentation
|
|
117
117
|
|
|
118
118
|
https://vanviegen.github.io/aberdeen/modules.html
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Roadmap
|
|
122
|
+
|
|
123
|
+
- [x] Support for (dis)appear transitions.
|
|
124
|
+
- [x] A better alternative for scheduleTask.
|
|
125
|
+
- [ ] A simple router.
|
|
126
|
+
- [ ] Architecture document.
|
|
127
|
+
- [x] Optimistic client-side predictions.
|
|
128
|
+
- [ ] SVG support.
|
|
129
|
+
- [ ] More user friendly documentation generator.
|
|
130
|
+
- [ ] Performance profiling and tuning regarding lists.
|
package/dist/aberdeen.d.ts
CHANGED
|
@@ -2,21 +2,43 @@ interface QueueRunner {
|
|
|
2
2
|
queueOrder: number;
|
|
3
3
|
queueRun(): void;
|
|
4
4
|
}
|
|
5
|
+
type Patch = Map<ObsCollection, Map<any, [any, any]>>;
|
|
5
6
|
/**
|
|
6
|
-
* Schedule a
|
|
7
|
-
* can be useful to batch together DOM layout read operations and DOM write
|
|
8
|
-
* operations, so that we're not forcing the browser to do more layout calculations
|
|
9
|
-
* than needed. Also, unlike setTimeout or requestAnimationFrame, this doesn't
|
|
10
|
-
* give the browser the chance to render partial DOM states to the screen (which
|
|
11
|
-
* would be seen as glitches/flashes).
|
|
12
|
-
* @param func The function to be called soon.
|
|
13
|
-
* @param order Higher mean later. Defaults to 0, which would still be *after* all
|
|
14
|
-
* node/observe redraws have been handled.
|
|
7
|
+
* Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
|
|
15
8
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
9
|
+
* This function is used to batch DOM read operations together, avoiding unnecessary
|
|
10
|
+
* layout recalculations and improving browser performance. A DOM read operation should
|
|
11
|
+
* only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
|
|
12
|
+
*
|
|
13
|
+
* By batching DOM reads separately from DOM writes, this prevents the browser from
|
|
14
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
15
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
16
|
+
* intermediate DOM states during updates.
|
|
17
|
+
*
|
|
18
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
|
|
19
|
+
* operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
|
|
20
|
+
*
|
|
21
|
+
* @param func The function to be executed as a DOM read operation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function scheduleDomReader(func: () => void): void;
|
|
24
|
+
/**
|
|
25
|
+
* Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
|
|
26
|
+
*
|
|
27
|
+
* This function is used to batch DOM write operations together, avoiding unnecessary
|
|
28
|
+
* layout recalculations and improving browser performance. A DOM write operation should
|
|
29
|
+
* only *write* to the DOM, such as modifying element properties or applying styles.
|
|
30
|
+
*
|
|
31
|
+
* By batching DOM writes separately from DOM reads, this prevents the browser from
|
|
32
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
33
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
34
|
+
* intermediate DOM states during updates.
|
|
35
|
+
*
|
|
36
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
|
|
37
|
+
* operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
|
|
38
|
+
*
|
|
39
|
+
* @param func The function to be executed as a DOM write operation.
|
|
18
40
|
*/
|
|
19
|
-
export declare function
|
|
41
|
+
export declare function scheduleDomWriter(func: () => void): void;
|
|
20
42
|
type SortKeyType = number | string | Array<number | string>;
|
|
21
43
|
interface Observer {
|
|
22
44
|
onChange(index: any, newData: DatumType, oldData: DatumType): void;
|
|
@@ -630,21 +652,47 @@ export declare function mount(parentElement: Element | undefined, func: () => vo
|
|
|
630
652
|
*/
|
|
631
653
|
export declare function peek<T>(func: () => T): T;
|
|
632
654
|
/** Do a grow transition for the given element. This is meant to be used as a
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
655
|
+
* handler for the `create` property.
|
|
656
|
+
*
|
|
657
|
+
* @param el The element to transition.
|
|
658
|
+
*
|
|
659
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
660
|
+
* for other specific cases as well.
|
|
661
|
+
*/
|
|
640
662
|
export declare function grow(el: HTMLElement): void;
|
|
641
663
|
/** Do a shrink transition for the given element, and remove it from the DOM
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
664
|
+
* afterwards. This is meant to be used as a handler for the `destroy` property.
|
|
665
|
+
*
|
|
666
|
+
* @param el The element to transition and remove.
|
|
667
|
+
*
|
|
668
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
669
|
+
* for other specific cases as well.
|
|
670
|
+
*/
|
|
671
|
+
export declare function shrink(el: HTMLElement): void;
|
|
672
|
+
/**
|
|
673
|
+
* Run the provided function, while treating all changes to Observables as predictions,
|
|
674
|
+
* meaning they will be reverted when changes come back from the server (or some other
|
|
675
|
+
* async source).
|
|
676
|
+
* @param predictFunc The function to run. It will generally modify some Observables
|
|
677
|
+
* to immediately reflect state (as closely as possible) that we expect the server
|
|
678
|
+
* to communicate back to us later on.
|
|
679
|
+
* @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
|
|
680
|
+
*/
|
|
681
|
+
export declare function applyPrediction(predictFunc: () => void): Patch;
|
|
682
|
+
/**
|
|
683
|
+
* Temporarily revert all outstanding predictions, optionally run the provided function
|
|
684
|
+
* (which will generally make authoritative changes to the data based on a server response),
|
|
685
|
+
* and then attempt to reapply the predictions on top of the new canonical state, dropping
|
|
686
|
+
* any predictions that can no longer be applied cleanly (the data has been modified) or
|
|
687
|
+
* that were specified in `dropPredictions`.
|
|
645
688
|
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
689
|
+
* All of this is done such that redraws are only triggered if the overall effect is an
|
|
690
|
+
* actual change to an `Observable`.
|
|
691
|
+
* @param canonFunc The function to run without any predictions applied. This will typically
|
|
692
|
+
* make authoritative changes to the data, based on a server response.
|
|
693
|
+
* @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
|
|
694
|
+
* to undo. Typically, when a server response for a certain request is being handled,
|
|
695
|
+
* you'd want to drop the prediction that was done for that request.
|
|
648
696
|
*/
|
|
649
|
-
export declare function
|
|
697
|
+
export declare function applyCanon(canonFunc?: (() => void), dropPredictions?: Array<Patch>): void;
|
|
650
698
|
export {};
|
package/dist/aberdeen.js
CHANGED
|
@@ -2,6 +2,8 @@ let queueArray = [];
|
|
|
2
2
|
let queueSet = new Set();
|
|
3
3
|
let queueOrdered = true;
|
|
4
4
|
let runQueueDepth = 0;
|
|
5
|
+
let queueIndex;
|
|
6
|
+
let recordingPatch;
|
|
5
7
|
function queue(runner) {
|
|
6
8
|
if (queueSet.has(runner))
|
|
7
9
|
return;
|
|
@@ -19,42 +21,73 @@ function queue(runner) {
|
|
|
19
21
|
}
|
|
20
22
|
function runQueue() {
|
|
21
23
|
onCreateEnabled = true;
|
|
22
|
-
for (
|
|
24
|
+
for (queueIndex = 0; queueIndex < queueArray.length;) {
|
|
25
|
+
// Sort queue if new unordered items have been added since last time.
|
|
23
26
|
if (!queueOrdered) {
|
|
24
|
-
queueArray.splice(0,
|
|
25
|
-
|
|
26
|
-
// Order queued observers by depth, lowest first
|
|
27
|
+
queueArray.splice(0, queueIndex);
|
|
28
|
+
queueIndex = 0;
|
|
29
|
+
// Order queued observers by depth, lowest first.
|
|
27
30
|
queueArray.sort((a, b) => a.queueOrder - b.queueOrder);
|
|
28
31
|
queueOrdered = true;
|
|
29
32
|
}
|
|
33
|
+
// Process the rest of what's currently in the queue.
|
|
30
34
|
let batchEndIndex = queueArray.length;
|
|
31
|
-
for (;
|
|
32
|
-
let runner = queueArray[
|
|
35
|
+
for (; queueIndex < batchEndIndex && queueOrdered; queueIndex++) {
|
|
36
|
+
let runner = queueArray[queueIndex];
|
|
33
37
|
queueSet.delete(runner);
|
|
34
38
|
runner.queueRun();
|
|
35
39
|
}
|
|
40
|
+
// If new items have been added to the queue while processing the previous
|
|
41
|
+
// batch, we'll need to run this loop again.
|
|
36
42
|
runQueueDepth++;
|
|
37
43
|
}
|
|
38
44
|
queueArray.length = 0;
|
|
45
|
+
queueIndex = undefined;
|
|
39
46
|
runQueueDepth = 0;
|
|
40
47
|
onCreateEnabled = false;
|
|
41
48
|
}
|
|
49
|
+
let scheduleOrder = 1000;
|
|
42
50
|
/**
|
|
43
|
-
* Schedule a
|
|
44
|
-
* can be useful to batch together DOM layout read operations and DOM write
|
|
45
|
-
* operations, so that we're not forcing the browser to do more layout calculations
|
|
46
|
-
* than needed. Also, unlike setTimeout or requestAnimationFrame, this doesn't
|
|
47
|
-
* give the browser the chance to render partial DOM states to the screen (which
|
|
48
|
-
* would be seen as glitches/flashes).
|
|
49
|
-
* @param func The function to be called soon.
|
|
50
|
-
* @param order Higher mean later. Defaults to 0, which would still be *after* all
|
|
51
|
-
* node/observe redraws have been handled.
|
|
51
|
+
* Schedule a DOM read operation to be executed in Aberdeen's internal task queue.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* This function is used to batch DOM read operations together, avoiding unnecessary
|
|
54
|
+
* layout recalculations and improving browser performance. A DOM read operation should
|
|
55
|
+
* only *read* from the DOM, such as measuring element dimensions or retrieving computed styles.
|
|
56
|
+
*
|
|
57
|
+
* By batching DOM reads separately from DOM writes, this prevents the browser from
|
|
58
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
59
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
60
|
+
* intermediate DOM states during updates.
|
|
61
|
+
*
|
|
62
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM read
|
|
63
|
+
* operations happen before any DOM writes in the same queue cycle, minimizing layout thrashing.
|
|
64
|
+
*
|
|
65
|
+
* @param func The function to be executed as a DOM read operation.
|
|
66
|
+
*/
|
|
67
|
+
export function scheduleDomReader(func) {
|
|
68
|
+
let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? ((queueArray[queueIndex].queueOrder + 1) & (~1)) : 1000;
|
|
69
|
+
queue({ queueOrder: order, queueRun: func });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Schedule a DOM write operation to be executed in Aberdeen's internal task queue.
|
|
73
|
+
*
|
|
74
|
+
* This function is used to batch DOM write operations together, avoiding unnecessary
|
|
75
|
+
* layout recalculations and improving browser performance. A DOM write operation should
|
|
76
|
+
* only *write* to the DOM, such as modifying element properties or applying styles.
|
|
77
|
+
*
|
|
78
|
+
* By batching DOM writes separately from DOM reads, this prevents the browser from
|
|
79
|
+
* interleaving layout reads and writes, which can force additional layout recalculations.
|
|
80
|
+
* This helps reduce visual glitches and flashes by ensuring the browser doesn't render
|
|
81
|
+
* intermediate DOM states during updates.
|
|
82
|
+
*
|
|
83
|
+
* Unlike `setTimeout` or `requestAnimationFrame`, this mechanism ensures that DOM write
|
|
84
|
+
* operations happen after all DOM reads in the same queue cycle, minimizing layout thrashing.
|
|
85
|
+
*
|
|
86
|
+
* @param func The function to be executed as a DOM write operation.
|
|
55
87
|
*/
|
|
56
|
-
export function
|
|
57
|
-
|
|
88
|
+
export function scheduleDomWriter(func) {
|
|
89
|
+
let order = (queueIndex != null && queueIndex < queueArray.length && queueArray[queueIndex].queueOrder >= 1000) ? (queueArray[queueIndex].queueOrder | 1) : 1001;
|
|
90
|
+
queue({ queueOrder: order, queueRun: func });
|
|
58
91
|
}
|
|
59
92
|
/**
|
|
60
93
|
* Given an integer number, a string or an array of these, this function returns a string that can be used
|
|
@@ -490,12 +523,17 @@ class ObsCollection {
|
|
|
490
523
|
obsSet.delete(observer);
|
|
491
524
|
}
|
|
492
525
|
emitChange(index, newData, oldData) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
526
|
+
if (recordingPatch) {
|
|
527
|
+
addToPatch(recordingPatch, this, index, newData, oldData);
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
let obsSet = this.observers.get(index);
|
|
531
|
+
if (obsSet)
|
|
532
|
+
obsSet.forEach(observer => observer.onChange(index, newData, oldData));
|
|
533
|
+
obsSet = this.observers.get(ANY_INDEX);
|
|
534
|
+
if (obsSet)
|
|
535
|
+
obsSet.forEach(observer => observer.onChange(index, newData, oldData));
|
|
536
|
+
}
|
|
499
537
|
}
|
|
500
538
|
_clean(observer) {
|
|
501
539
|
this.removeObserver(ANY_INDEX, observer);
|
|
@@ -1182,7 +1220,7 @@ export class Store {
|
|
|
1182
1220
|
result.forEach((value, key) => {
|
|
1183
1221
|
out.set(key, value);
|
|
1184
1222
|
});
|
|
1185
|
-
keys =
|
|
1223
|
+
keys = [...result.keys()];
|
|
1186
1224
|
}
|
|
1187
1225
|
else {
|
|
1188
1226
|
return;
|
|
@@ -1627,56 +1665,171 @@ function getGrowShrinkProps(el) {
|
|
|
1627
1665
|
{ marginBottom: `-${el.offsetHeight / 2}px`, marginTop: `-${el.offsetHeight / 2}px`, transform: "scaleY(0)" };
|
|
1628
1666
|
}
|
|
1629
1667
|
/** Do a grow transition for the given element. This is meant to be used as a
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1668
|
+
* handler for the `create` property.
|
|
1669
|
+
*
|
|
1670
|
+
* @param el The element to transition.
|
|
1671
|
+
*
|
|
1672
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
1673
|
+
* for other specific cases as well.
|
|
1674
|
+
*/
|
|
1637
1675
|
export function grow(el) {
|
|
1638
1676
|
// This timeout is to await all other elements having been added to the Dom
|
|
1639
|
-
|
|
1677
|
+
scheduleDomReader(() => {
|
|
1640
1678
|
// Make the element size 0 using transforms and negative margins.
|
|
1641
1679
|
// This causes a browser layout, as we're querying el.offset<>.
|
|
1642
1680
|
let props = getGrowShrinkProps(el);
|
|
1643
1681
|
// The timeout is in order to batch all reads and then all writes when there
|
|
1644
1682
|
// are multiple simultaneous grow transitions.
|
|
1645
|
-
|
|
1683
|
+
scheduleDomWriter(() => {
|
|
1646
1684
|
Object.assign(el.style, props);
|
|
1647
1685
|
// This timeout is to combine multiple transitions into a single browser layout
|
|
1648
|
-
|
|
1686
|
+
scheduleDomReader(() => {
|
|
1649
1687
|
// Make sure the layouting has been performed, to cause transitions to trigger
|
|
1650
1688
|
el.offsetHeight;
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1689
|
+
scheduleDomWriter(() => {
|
|
1690
|
+
// Do the transitions
|
|
1691
|
+
el.style.transition = GROW_SHRINK_TRANSITION;
|
|
1692
|
+
for (let prop in props)
|
|
1693
|
+
el.style[prop] = "";
|
|
1694
|
+
setTimeout(() => {
|
|
1695
|
+
// Reset the element to a clean state
|
|
1696
|
+
el.style.transition = "";
|
|
1697
|
+
}, FADE_TIME);
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1661
1701
|
});
|
|
1662
1702
|
}
|
|
1663
1703
|
/** Do a shrink transition for the given element, and remove it from the DOM
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1704
|
+
* afterwards. This is meant to be used as a handler for the `destroy` property.
|
|
1705
|
+
*
|
|
1706
|
+
* @param el The element to transition and remove.
|
|
1707
|
+
*
|
|
1708
|
+
* The transition doesn't look great for table elements, and may have problems
|
|
1709
|
+
* for other specific cases as well.
|
|
1710
|
+
*/
|
|
1711
|
+
export function shrink(el) {
|
|
1712
|
+
scheduleDomReader(() => {
|
|
1713
|
+
const props = getGrowShrinkProps(el);
|
|
1714
|
+
// The timeout is in order to batch all reads and then all writes when there
|
|
1715
|
+
// are multiple simultaneous shrink transitions.
|
|
1716
|
+
scheduleDomWriter(() => {
|
|
1717
|
+
el.style.transition = GROW_SHRINK_TRANSITION;
|
|
1718
|
+
Object.assign(el.style, props);
|
|
1719
|
+
setTimeout(() => el.remove(), FADE_TIME);
|
|
1720
|
+
});
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
function recordPatch(func) {
|
|
1724
|
+
if (recordingPatch)
|
|
1725
|
+
throw new Error(`already recording a patch`);
|
|
1726
|
+
recordingPatch = new Map();
|
|
1727
|
+
try {
|
|
1728
|
+
func();
|
|
1729
|
+
}
|
|
1730
|
+
catch (e) {
|
|
1731
|
+
recordingPatch = undefined;
|
|
1732
|
+
throw e;
|
|
1733
|
+
}
|
|
1734
|
+
const result = recordingPatch;
|
|
1735
|
+
recordingPatch = undefined;
|
|
1736
|
+
return result;
|
|
1737
|
+
}
|
|
1738
|
+
function addToPatch(patch, collection, index, newData, oldData) {
|
|
1739
|
+
let collectionMap = patch.get(collection);
|
|
1740
|
+
if (collectionMap == null) {
|
|
1741
|
+
collectionMap = new Map();
|
|
1742
|
+
patch.set(collection, collectionMap);
|
|
1743
|
+
}
|
|
1744
|
+
let prev = collectionMap.get(index);
|
|
1745
|
+
collectionMap.set(index, [newData, prev == null ? oldData : prev[1]]);
|
|
1746
|
+
}
|
|
1747
|
+
function emitPatch(patch) {
|
|
1748
|
+
for (let [collection, collectionMap] of patch) {
|
|
1749
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1750
|
+
collection.emitChange(index, newData, oldData);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function mergePatch(target, source, reverse = false) {
|
|
1755
|
+
for (let [collection, collectionMap] of source) {
|
|
1756
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1757
|
+
addToPatch(target, collection, index, reverse ? oldData : newData, reverse ? newData : oldData);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
function silentlyApplyPatch(patch, force = false) {
|
|
1762
|
+
for (let [collection, collectionMap] of patch) {
|
|
1763
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1764
|
+
let actualData = collection.rawGet(index);
|
|
1765
|
+
if (actualData !== oldData) {
|
|
1766
|
+
if (force)
|
|
1767
|
+
handleError(new Error(`Applying invalid patch: data ${actualData} is unequal to expected old data ${oldData} for index ${index}`));
|
|
1768
|
+
else
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
for (let [collection, collectionMap] of patch) {
|
|
1774
|
+
for (let [index, [newData, oldData]] of collectionMap) {
|
|
1775
|
+
collection.rawSet(index, newData);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return true;
|
|
1779
|
+
}
|
|
1780
|
+
const appliedPredictions = [];
|
|
1781
|
+
/**
|
|
1782
|
+
* Run the provided function, while treating all changes to Observables as predictions,
|
|
1783
|
+
* meaning they will be reverted when changes come back from the server (or some other
|
|
1784
|
+
* async source).
|
|
1785
|
+
* @param predictFunc The function to run. It will generally modify some Observables
|
|
1786
|
+
* to immediately reflect state (as closely as possible) that we expect the server
|
|
1787
|
+
* to communicate back to us later on.
|
|
1788
|
+
* @returns A `Patch` object. Don't modify it. This is only meant to be passed to `applyCanon`.
|
|
1789
|
+
*/
|
|
1790
|
+
export function applyPrediction(predictFunc) {
|
|
1791
|
+
let patch = recordPatch(predictFunc);
|
|
1792
|
+
appliedPredictions.push(patch);
|
|
1793
|
+
emitPatch(patch);
|
|
1794
|
+
return patch;
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Temporarily revert all outstanding predictions, optionally run the provided function
|
|
1798
|
+
* (which will generally make authoritative changes to the data based on a server response),
|
|
1799
|
+
* and then attempt to reapply the predictions on top of the new canonical state, dropping
|
|
1800
|
+
* any predictions that can no longer be applied cleanly (the data has been modified) or
|
|
1801
|
+
* that were specified in `dropPredictions`.
|
|
1667
1802
|
*
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1803
|
+
* All of this is done such that redraws are only triggered if the overall effect is an
|
|
1804
|
+
* actual change to an `Observable`.
|
|
1805
|
+
* @param canonFunc The function to run without any predictions applied. This will typically
|
|
1806
|
+
* make authoritative changes to the data, based on a server response.
|
|
1807
|
+
* @param dropPredictions An optional list of predictions (as returned by `applyPrediction`)
|
|
1808
|
+
* to undo. Typically, when a server response for a certain request is being handled,
|
|
1809
|
+
* you'd want to drop the prediction that was done for that request.
|
|
1670
1810
|
*/
|
|
1671
|
-
export function
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1811
|
+
export function applyCanon(canonFunc, dropPredictions = []) {
|
|
1812
|
+
let resultPatch = new Map();
|
|
1813
|
+
for (let prediction of appliedPredictions)
|
|
1814
|
+
mergePatch(resultPatch, prediction, true);
|
|
1815
|
+
silentlyApplyPatch(resultPatch, true);
|
|
1816
|
+
for (let prediction of dropPredictions) {
|
|
1817
|
+
let pos = appliedPredictions.indexOf(prediction);
|
|
1818
|
+
if (pos >= 0)
|
|
1819
|
+
appliedPredictions.splice(pos, 1);
|
|
1820
|
+
}
|
|
1821
|
+
if (canonFunc)
|
|
1822
|
+
mergePatch(resultPatch, recordPatch(canonFunc));
|
|
1823
|
+
for (let idx = 0; idx < appliedPredictions.length; idx++) {
|
|
1824
|
+
if (silentlyApplyPatch(appliedPredictions[idx])) {
|
|
1825
|
+
mergePatch(resultPatch, appliedPredictions[idx]);
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
appliedPredictions.splice(idx, 1);
|
|
1829
|
+
idx--;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
emitPatch(resultPatch);
|
|
1680
1833
|
}
|
|
1681
1834
|
// @ts-ignore
|
|
1682
1835
|
// istanbul ignore next
|
package/dist/aberdeen.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,t=[],i=new Set,n=!0,r=0;function s(e){if(!i.has(e)){if(r>42)throw new Error("Too many recursive updates from observes");t.length?e.queueOrder<t[t.length-1].queueOrder&&(n=!1):setTimeout(o,0),t.push(e),i.add(e)}}function o(){w=!0;for(let e=0;e<t.length;){n||(t.splice(0,e),e=0,t.sort((e,t)=>e.queueOrder-t.queueOrder),n=!0);let s=t.length;for(;e<s&&n;e++){let n=t[e];i.delete(n),n.queueRun()}r++}t.length=0,r=0,w=!1}export function scheduleTask(e,t=0){s({queueOrder:1e3+t,queueRun:e})}function a(e){if("string"==typeof e)return e+"";{let t=function(e,t){let i="";for(;e>0;)i+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return i}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class l{constructor(e,t,i){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=i}findPrecedingNode(e){let t,i=this;for(;(t=i.precedingSibling)&&t!==e;){if(t instanceof Node)return t;let e=t.findLastNode();if(e)return e;i=t}}findLastNode(){if(this.lastChild)return this.lastChild instanceof Node?this.lastChild:this.lastChild.findLastNode()||this.lastChild.findPrecedingNode(this.precedingSibling)}addNode(e){if(!this.parentElement)throw new k(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(t=t?t.nextSibling:this.parentElement.firstChild,this.lastChild=void 0;;){if(!t)return I(1);const i=t;t=i.nextSibling||void 0;let n=b.get(i);if(n&&i instanceof Element?!0!==n&&("function"==typeof n?n(i):x(i,n),b.set(i,!0)):this.parentElement.removeChild(i),i===e)break}}}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,i){s(this)}}class h extends l{constructor(e,t,i,n){super(e,t,i),this.renderer=n}queueRun(){e&&I(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;try{this.renderer()}catch(e){N(e)}e=t}}class d{constructor(e,t,i){this.scope=e,this.collection=t,this.triggerCount=i,this.count=t.getCount(),t.addObserver(f,this),e.cleaners.push(this)}onChange(e,t,i){void 0===t?!this.triggerCount&&--this.count||s(this.scope):void 0===i&&(!this.triggerCount&&this.count++||s(this.scope))}_clean(){this.collection.removeObserver(f,this)}}class c extends l{constructor(e,t,i,n,r,s){super(e,t,i),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=n,this.renderer=r,this.makeSortKey=s}onChange(e,t,i){void 0===i?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),s(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),s(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!e)return I(3);let t=e;this.collection.iterateIndexes(this),e=t}addChild(e){let t=new u(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return I(6);t.remove(),this.byIndex.delete(e),this.removeFromPosition(t)}findPosition(e){let t=this.byPosition,i=0,n=t.length;if(!n||e>t[n-1].sortStr)return n;for(;i<n;){let r=i+n>>1;t[r].sortStr<e?i=r+1:n=r}return i}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e);let i=this.byPosition[t+1];i?(e.precedingSibling=i.precedingSibling,i.precedingSibling=e):(e.precedingSibling=this.lastChild||this.precedingSibling,this.lastChild=e)}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e){if(this.byPosition.splice(t,1),t<this.byPosition.length){let i=this.byPosition[t];if(!i)return I(8);if(i.precedingSibling!==e)return I(13);i.precedingSibling=e.precedingSibling}else{if(e!==this.lastChild)return I(12);this.lastChild=e.precedingSibling===this.precedingSibling?void 0:e.precedingSibling}return}if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return I(5)}}}class u extends l{constructor(e,t,i,n,r){super(e,t,i),this.sortStr="",this.parent=n,this.itemIndex=r}queueRun(){e&&I(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let t=e;e=this;let i,n=new Store(this.parent.collection,this.itemIndex);try{i=this.parent.makeSortKey(n)}catch(e){N(e)}let r=this.sortStr,s=null==i?"":(o=i)instanceof Array?o.map(a).join(""):a(o);var o;if(""!==r&&r!==s&&this.parent.removeFromPosition(this),this.sortStr=s,""!==s){s!==r&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){N(e)}}e=t}}const f={};class p{constructor(){this.observers=new Map}addObserver(e,t){t=t;let i=this.observers.get(e);if(i){if(i.has(t))return!1;i.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,t,i){let n=this.observers.get(e);n&&n.forEach(n=>n.onChange(e,t,i)),n=this.observers.get(f),n&&n.forEach(n=>n.onChange(e,t,i))}_clean(e){this.removeObserver(f,e)}setIndex(e,t,i){const n=this.rawGet(e);if(!(n instanceof p)||t instanceof Store||!n.merge(t,i)){let i=C(t);i!==n&&(this.rawSet(e,i),this.emitChange(e,i,n))}}}class g extends p{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=[];for(let e=0;e<this.data.length;e++){let n=this.data[e];i.push(n instanceof p?t?n.getRecursive(t-1):new Store(this,e):n)}return i}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let i=0;i<e.length;i++)this.setIndex(i,e[i],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class m extends p{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i=new Map;return this.data.forEach((e,n)=>{i.set(n,e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e)}),i}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,i)=>{this.setIndex(i,e,t)}),t&&this.data.forEach((t,i)=>{e.has(i)||this.setIndex(i,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,i)=>{e.addChild(i)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class v extends m{getType(){return"object"}getRecursive(t){e&&this.addObserver(f,e)&&e.cleaners.push(this);let i={};return this.data.forEach((e,n)=>{i[n]=e instanceof p?t?e.getRecursive(t-1):new Store(this,n):e}),i}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let i in e)this.setIndex(i,e[i],t);return t&&this.data.forEach((t,i)=>{e.hasOwnProperty(i)||this.setIndex(i,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new g,this.idx=0,void 0!==e&&this.collection.rawSet(0,C(e));else{if(!(e instanceof p))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let i=typeof e;return"object"===i&&(e instanceof Map?i="map":e instanceof Array&&(i="array")),this.query({type:i,defaultValue:e,path:t})}query(t){if(t.peek&&e){let i=e;e=void 0;let n=this.query(t);return e=i,n}let i=(t.path&&t.path.length?this.ref(...t.path):this)._observe();if(t.type&&(void 0!==i||void 0===t.defaultValue)){let e=i instanceof p?i.getType():null===i?"null":typeof i;if(e!==t.type)throw new TypeError(`Expecting ${t.type} but got ${e}`)}return i instanceof p?i.getRecursive(null==t.depth?-1:t.depth-1):void 0===i?t.defaultValue:i}isEmpty(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return!new d(e,i,!1).count}return!i.getCount()}if(void 0===i)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(i))}count(...t){let i=this.ref(...t)._observe();if(i instanceof p){if(e){return new d(e,i,!0).count}return i.getCount()}if(void 0===i)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(i))}getType(...e){let t=this.ref(...e)._observe();return t instanceof p?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!0)}merge(...e){let t=e.pop(),i=this.makeRef(...e);i.collection.setIndex(i.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),i=this.makeRef(...e),n=i.collection.rawGet(i.idx);if(void 0===n)n=new g,i.collection.setIndex(i.idx,n,!0);else if(!(n instanceof g))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=C(t),s=n.data.length;return n.data.push(r),n.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let i=0;i<e.length;i++){let n=t._observe();if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);return new y}t=new Store(n,n.normalizeIndex(e[i]))}return t}makeRef(...e){let t=this;for(let i=0;i<e.length;i++){let n=t.collection.rawGet(t.idx);if(!(n instanceof p)){if(void 0!==n)throw new Error(`Value ${JSON.stringify(n)} is not a collection (nor undefined) in step ${i} of $(${JSON.stringify(e)})`);n=new v,t.collection.rawSet(t.idx,n),t.collection.emitChange(t.idx,n,void 0)}t=new Store(n,n.normalizeIndex(e[i]))}return t}_observe(){return e&&this.collection.addObserver(this.idx,e)&&e.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...t){let i=O,n=t.pop();if("function"!=typeof t[t.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(i=n),n=t.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!e)throw new k(!1);let r=this.ref(...t)._observe();if(r instanceof p){let t=new c(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,r,n,i);r.addObserver(f,t),e.cleaners.push(t),e.lastChild=t,t.renderInitial()}else if(void 0!==r)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(i=>{let n=e(i);if(void 0!==n){let e=i.index();t.set(e,n),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(i=>{let n,r=e(i);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);n=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,i)=>{t.set(i,e)}),n=Array.from(r.keys())}n.length&&clean(()=>{for(let e of n)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class y extends Store{isDetached(){return!0}}let w=!1,b=new WeakMap;function x(e,t){e.classList.add(t),setTimeout(()=>e.remove(),2e3)}export function node(t="",...i){if(!e)throw new k(!0);let n;if(t instanceof Element)n=t;else{let e,i=t.indexOf(".");i>=0&&(e=t.substr(i+1),t=t.substr(0,i)),n=document.createElement(t||"div"),e&&(n.className=e.replaceAll("."," "))}e.addNode(n);for(let t of i){let i=typeof t;if("function"===i){let i=new h(n,void 0,e.queueOrder+1,t);w?(w=!1,i.update(),w=!0):i.update(),e.cleaners.push(i)}else if("string"===i||"number"===i)n.textContent=t;else if("object"===i&&t&&t.constructor===Object)for(let e in t)E(n,e,t[e]);else if(t instanceof Store)S(n,t);else if(null!=t)throw new Error("Unexpected argument "+JSON.stringify(t))}}export function html(t){if(!e||!e.parentElement)throw new k(!0);let i=document.createElement(e.parentElement.tagName);for(i.innerHTML=""+t;i.firstChild;)e.addNode(i.firstChild)}function S(e,t){let i,n,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),i=t=>e.checked=t,n=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),i=t=>e.checked=t===e.value,n=()=>{e.checked&&t.set(e.value)}):(n=()=>t.set("number"===r||"range"===r?""===e.value?null:+e.value:e.value),void 0===s&&n(),i=t=>{e.value!==t&&(e.value=t)}),observe(()=>{i(t.get())}),e.addEventListener("input",n),clean(()=>{e.removeEventListener("input",n)})}export function text(t){if(!e)throw new k(!0);null!=t&&e.addNode(document.createTextNode(t))}export function prop(t,i){if(!e||!e.parentElement)throw new k(!0);if("object"==typeof t)for(let i in t)E(e.parentElement,i,t[i]);else E(e.parentElement,t,i)}export function getParentElement(){if(!e||!e.parentElement)throw new k(!0);return e.parentElement}export function clean(t){if(!e)throw new k(!1);e.cleaners.push({_clean:t})}export function observe(e){mount(void 0,e)}export function mount(t,i){let n;t||!e?n=new h(t,void 0,0,i):(n=new h(e.parentElement,e.lastChild||e.precedingSibling,e.queueOrder+1,i),e.lastChild=n),n.update(),e&&e.cleaners.push(n)}export function peek(t){let i=e;e=void 0;try{return t()}finally{e=i}}function E(e,t,i){if("create"===t)w&&("function"==typeof i?i(e):(e.classList.add(i),setTimeout((function(){e.classList.remove(i)}),0)));else if("destroy"===t)b.set(e,i);else if("function"==typeof i)e.addEventListener(t,i),clean(()=>e.removeEventListener(t,i));else if("value"===t||"className"===t||"selectedIndex"===t||!0===i||!1===i)e[t]=i;else if("text"===t)e.textContent=i;else if("class"!==t&&"className"!==t||"object"!=typeof i)"style"===t&&"object"==typeof i?Object.assign(e.style,i):e.setAttribute(t,i);else for(let t in i)i[t]?e.classList.add(t):e.classList.remove(t)}function C(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new m;return e.forEach((e,i)=>{let n=C(e);void 0!==n&&t.rawSet(i,n)}),t}if(e instanceof Array){let t=new g;for(let i=0;i<e.length;i++){let n=C(e[i]);void 0!==n&&t.rawSet(i,n)}return t}if(e.constructor===Object){let t=new v;for(let i in e){let n=C(e[i]);void 0!==n&&t.rawSet(i,n)}return t}return e}return e}function O(e){return e.index()}function I(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function N(e){setTimeout(()=>{throw e},0)}class k extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}function q(e){const t=e.parentElement?getComputedStyle(e.parentElement):{};return"flex"===t.display&&(t.flexDirection||"").startsWith("row")?{marginLeft:`-${e.offsetWidth/2}px`,marginRight:`-${e.offsetWidth/2}px`,transform:"scaleX(0)"}:{marginBottom:`-${e.offsetHeight/2}px`,marginTop:`-${e.offsetHeight/2}px`,transform:"scaleY(0)"}}export function grow(e){scheduleTask(()=>{let t=q(e);scheduleTask(()=>{Object.assign(e.style,t),scheduleTask(()=>{e.offsetHeight,e.style.transition="margin 400ms ease-out, transform 400ms ease-out";for(let i in t)e.style[i]="";setTimeout(()=>{e.style.transition=""},400)},2)},1)})}export function shrink(e){const t=q(e);scheduleTask(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out",Object.assign(e.style,t),setTimeout(()=>e.remove(),400)},1)}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
|
|
1
|
+
let e,t,n=[],i=new Set,r=!0,s=0;function o(e){if(!i.has(e)){if(s>42)throw new Error("Too many recursive updates from observes");n.length?e.queueOrder<n[n.length-1].queueOrder&&(r=!1):setTimeout(l,0),n.push(e),i.add(e)}}function l(){for(b=!0,e=0;e<n.length;){r||(n.splice(0,e),e=0,n.sort((e,t)=>e.queueOrder-t.queueOrder),r=!0);let t=n.length;for(;e<t&&r;e++){let t=n[e];i.delete(t),t.queueRun()}s++}n.length=0,e=void 0,s=0,b=!1}let a;export function scheduleDomReader(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?n[e].queueOrder+1&-2:1e3,queueRun:t})}export function scheduleDomWriter(t){o({queueOrder:null!=e&&e<n.length&&n[e].queueOrder>=1e3?1|n[e].queueOrder:1001,queueRun:t})}function h(e){if("string"==typeof e)return e+"";{let t=function(e,t){let n="";for(;e>0;)n+=String.fromCharCode(t?65535-e%65533:2+e%65533),e=Math.floor(e/65533);return n}(Math.abs(Math.round(e)),e<0);return String.fromCharCode(128+(e>0?t.length:-t.length))+t}}class d{constructor(e,t,n){this.cleaners=[],this.isDead=!1,this.parentElement=e,this.precedingSibling=t,this.queueOrder=n}findPrecedingNode(e){let t,n=this;for(;(t=n.precedingSibling)&&t!==e;){if(t instanceof Node)return t;let e=t.findLastNode();if(e)return e;n=t}}findLastNode(){if(this.lastChild)return this.lastChild instanceof Node?this.lastChild:this.lastChild.findLastNode()||this.lastChild.findPrecedingNode(this.precedingSibling)}addNode(e){if(!this.parentElement)throw new k(!0);let t=this.findLastNode()||this.findPrecedingNode();this.parentElement.insertBefore(e,t?t.nextSibling:this.parentElement.firstChild),this.lastChild=e}remove(){if(this.parentElement){let e=this.findLastNode();if(e){let t=this.findPrecedingNode();for(t=t?t.nextSibling:this.parentElement.firstChild,this.lastChild=void 0;;){if(!t)return N(1);const n=t;t=n.nextSibling||void 0;let i=S.get(n);if(i&&n instanceof Element?!0!==i&&("function"==typeof i?i(n):E(n,i),S.set(n,!0)):this.parentElement.removeChild(n),n===e)break}}}this._clean()}_clean(){this.isDead=!0;for(let e of this.cleaners)e._clean(this);this.cleaners.length=0}onChange(e,t,n){o(this)}}class u extends d{constructor(e,t,n,i){super(e,t,n),this.renderer=i}queueRun(){a&&N(2),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;try{this.renderer()}catch(e){P(e)}a=e}}class c{constructor(e,t,n){this.scope=e,this.collection=t,this.triggerCount=n,this.count=t.getCount(),t.addObserver(g,this),e.cleaners.push(this)}onChange(e,t,n){void 0===t?!this.triggerCount&&--this.count||o(this.scope):void 0===n&&(!this.triggerCount&&this.count++||o(this.scope))}_clean(){this.collection.removeObserver(g,this)}}class f extends d{constructor(e,t,n,i,r,s){super(e,t,n),this.byPosition=[],this.byIndex=new Map,this.newIndexes=new Set,this.removedIndexes=new Set,this.collection=i,this.renderer=r,this.makeSortKey=s}onChange(e,t,n){void 0===n?this.removedIndexes.has(e)?this.removedIndexes.delete(e):(this.newIndexes.add(e),o(this)):void 0===t&&(this.newIndexes.has(e)?this.newIndexes.delete(e):(this.removedIndexes.add(e),o(this)))}queueRun(){if(this.isDead)return;let e=this.removedIndexes;this.removedIndexes=new Set,e.forEach(e=>{this.removeChild(e)}),e=this.newIndexes,this.newIndexes=new Set,e.forEach(e=>{this.addChild(e)})}_clean(){super._clean(),this.collection.observers.delete(this);for(const[e,t]of this.byIndex)t._clean();this.byPosition.length=0,this.byIndex.clear()}renderInitial(){if(!a)return N(3);let e=a;this.collection.iterateIndexes(this),a=e}addChild(e){let t=new p(this.parentElement,void 0,this.queueOrder+1,this,e);this.byIndex.set(e,t),t.update()}removeChild(e){let t=this.byIndex.get(e);if(!t)return N(6);t.remove(),this.byIndex.delete(e),this.removeFromPosition(t)}findPosition(e){let t=this.byPosition,n=0,i=t.length;if(!i||e>t[i-1].sortStr)return i;for(;n<i;){let r=n+i>>1;t[r].sortStr<e?n=r+1:i=r}return n}insertAtPosition(e){let t=this.findPosition(e.sortStr);this.byPosition.splice(t,0,e);let n=this.byPosition[t+1];n?(e.precedingSibling=n.precedingSibling,n.precedingSibling=e):(e.precedingSibling=this.lastChild||this.precedingSibling,this.lastChild=e)}removeFromPosition(e){if(""===e.sortStr)return;let t=this.findPosition(e.sortStr);for(;;){if(this.byPosition[t]===e){if(this.byPosition.splice(t,1),t<this.byPosition.length){let n=this.byPosition[t];if(!n)return N(8);if(n.precedingSibling!==e)return N(13);n.precedingSibling=e.precedingSibling}else{if(e!==this.lastChild)return N(12);this.lastChild=e.precedingSibling===this.precedingSibling?void 0:e.precedingSibling}return}if(++t>=this.byPosition.length||this.byPosition[t].sortStr!==e.sortStr)return N(5)}}}class p extends d{constructor(e,t,n,i,r){super(e,t,n),this.sortStr="",this.parent=i,this.itemIndex=r}queueRun(){a&&N(4),this.isDead||(this.remove(),this.isDead=!1,this.update())}update(){let e=a;a=this;let t,n=new Store(this.parent.collection,this.itemIndex);try{t=this.parent.makeSortKey(n)}catch(e){P(e)}let i=this.sortStr,r=null==t?"":(s=t)instanceof Array?s.map(h).join(""):h(s);var s;if(""!==i&&i!==r&&this.parent.removeFromPosition(this),this.sortStr=r,""!==r){r!==i&&this.parent.insertAtPosition(this);try{this.parent.renderer(n)}catch(e){P(e)}}a=e}}const g={};class m{constructor(){this.observers=new Map}addObserver(e,t){t=t;let n=this.observers.get(e);if(n){if(n.has(t))return!1;n.add(t)}else this.observers.set(e,new Set([t]));return!0}removeObserver(e,t){this.observers.get(e).delete(t)}emitChange(e,n,i){if(t)M(t,this,e,n,i);else{let t=this.observers.get(e);t&&t.forEach(t=>t.onChange(e,n,i)),t=this.observers.get(g),t&&t.forEach(t=>t.onChange(e,n,i))}}_clean(e){this.removeObserver(g,e)}setIndex(e,t,n){const i=this.rawGet(e);if(!(i instanceof m)||t instanceof Store||!i.merge(t,n)){let n=I(t);n!==i&&(this.rawSet(e,n),this.emitChange(e,n,i))}}}class v extends m{constructor(){super(...arguments),this.data=[]}getType(){return"array"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=[];for(let n=0;n<this.data.length;n++){let i=this.data[n];t.push(i instanceof m?e?i.getRecursive(e-1):new Store(this,n):i)}return t}rawGet(e){return this.data[e]}rawSet(e,t){if(e!==(0|e)||e<0||e>999999)throw new Error("Invalid array index "+JSON.stringify(e));for(this.data[e]=t;this.data.length>0&&void 0===this.data[this.data.length-1];)this.data.pop()}merge(e,t){if(!(e instanceof Array))return!1;for(let n=0;n<e.length;n++)this.setIndex(n,e[n],t);if(t&&this.data.length>e.length){for(let t=e.length;t<this.data.length;t++){let e=this.data[t];void 0!==e&&this.emitChange(t,void 0,e)}this.data.length=e.length}return!0}iterateIndexes(e){for(let t=0;t<this.data.length;t++)void 0!==this.data[t]&&e.addChild(t)}normalizeIndex(e){if("number"==typeof e)return e;if("string"==typeof e){let t=0|e;if(e.length&&t==e)return e}throw new Error("Invalid array index "+JSON.stringify(e))}getCount(){return this.data.length}}class y extends m{constructor(){super(...arguments),this.data=new Map}getType(){return"map"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t=new Map;return this.data.forEach((n,i)=>{t.set(i,n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n)}),t}rawGet(e){return this.data.get(e)}rawSet(e,t){void 0===t?this.data.delete(e):this.data.set(e,t)}merge(e,t){return e instanceof Map&&(e.forEach((e,n)=>{this.setIndex(n,e,t)}),t&&this.data.forEach((t,n)=>{e.has(n)||this.setIndex(n,void 0,!1)}),!0)}iterateIndexes(e){this.data.forEach((t,n)=>{e.addChild(n)})}normalizeIndex(e){return e}getCount(){return this.data.size}}class w extends y{getType(){return"object"}getRecursive(e){a&&this.addObserver(g,a)&&a.cleaners.push(this);let t={};return this.data.forEach((n,i)=>{t[i]=n instanceof m?e?n.getRecursive(e-1):new Store(this,i):n}),t}merge(e,t){if(!e||e.constructor!==Object)return!1;for(let n in e)this.setIndex(n,e[n],t);return t&&this.data.forEach((t,n)=>{e.hasOwnProperty(n)||this.setIndex(n,void 0,!1)}),!0}normalizeIndex(e){let t=typeof e;if("string"===t)return e;if("number"===t)return""+e;throw new Error("Invalid object index "+JSON.stringify(e))}getCount(){let e=0;for(let t of this.data)e++;return e}}export class Store{constructor(e,t){if(void 0===t)this.collection=new v,this.idx=0,void 0!==e&&this.collection.rawSet(0,I(e));else{if(!(e instanceof m))throw new Error("1st parameter should be an ObsCollection if the 2nd is also given");this.collection=e,this.idx=t}}index(){return this.idx}_clean(e){this.collection.removeObserver(this.idx,e)}get(...e){return this.query({path:e})}peek(...e){return this.query({path:e,peek:!0})}getNumber(...e){return this.query({path:e,type:"number"})}getString(...e){return this.query({path:e,type:"string"})}getBoolean(...e){return this.query({path:e,type:"boolean"})}getFunction(...e){return this.query({path:e,type:"function"})}getArray(...e){return this.query({path:e,type:"array"})}getObject(...e){return this.query({path:e,type:"object"})}getMap(...e){return this.query({path:e,type:"map"})}getOr(e,...t){let n=typeof e;return"object"===n&&(e instanceof Map?n="map":e instanceof Array&&(n="array")),this.query({type:n,defaultValue:e,path:t})}query(e){if(e.peek&&a){let t=a;a=void 0;let n=this.query(e);return a=t,n}let t=(e.path&&e.path.length?this.ref(...e.path):this)._observe();if(e.type&&(void 0!==t||void 0===e.defaultValue)){let n=t instanceof m?t.getType():null===t?"null":typeof t;if(n!==e.type)throw new TypeError(`Expecting ${e.type} but got ${n}`)}return t instanceof m?t.getRecursive(null==e.depth?-1:e.depth-1):void 0===t?e.defaultValue:t}isEmpty(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return!new c(a,t,!1).count}return!t.getCount()}if(void 0===t)return!0;throw new Error("isEmpty() expects a collection or undefined, but got "+JSON.stringify(t))}count(...e){let t=this.ref(...e)._observe();if(t instanceof m){if(a){return new c(a,t,!0).count}return t.getCount()}if(void 0===t)return 0;throw new Error("count() expects a collection or undefined, but got "+JSON.stringify(t))}getType(...e){let t=this.ref(...e)._observe();return t instanceof m?t.getType():null===t?"null":typeof t}set(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!0)}merge(...e){let t=e.pop(),n=this.makeRef(...e);n.collection.setIndex(n.idx,t,!1)}delete(...e){let t=this.makeRef(...e);t.collection.setIndex(t.idx,void 0,!0)}push(...e){let t=e.pop(),n=this.makeRef(...e),i=n.collection.rawGet(n.idx);if(void 0===i)i=new v,n.collection.setIndex(n.idx,i,!0);else if(!(i instanceof v))throw new Error("push() is only allowed for an array or undefined (which would become an array)");let r=I(t),s=i.data.length;return i.data.push(r),i.emitChange(s,r,void 0),s}modify(e){this.set(e(this.query({peek:!0})))}ref(...e){let t=this;for(let n=0;n<e.length;n++){let i=t._observe();if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);return new x}t=new Store(i,i.normalizeIndex(e[n]))}return t}makeRef(...e){let t=this;for(let n=0;n<e.length;n++){let i=t.collection.rawGet(t.idx);if(!(i instanceof m)){if(void 0!==i)throw new Error(`Value ${JSON.stringify(i)} is not a collection (nor undefined) in step ${n} of $(${JSON.stringify(e)})`);i=new w,t.collection.rawSet(t.idx,i),t.collection.emitChange(t.idx,i,void 0)}t=new Store(i,i.normalizeIndex(e[n]))}return t}_observe(){return a&&this.collection.addObserver(this.idx,a)&&a.cleaners.push(this),this.collection.rawGet(this.idx)}onEach(...e){let t=q,n=e.pop();if("function"!=typeof e[e.length-1]||"function"!=typeof n&&null!=n||(null!=n&&(t=n),n=e.pop()),"function"!=typeof n)throw new Error("onEach() expects a render function as its last argument but got "+JSON.stringify(n));if(!a)throw new k(!1);let i=this.ref(...e)._observe();if(i instanceof m){let e=new f(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,i,n,t);i.addObserver(g,e),a.cleaners.push(e),a.lastChild=e,e.renderInitial()}else if(void 0!==i)throw new Error("onEach() attempted on a value that is neither a collection nor undefined")}map(e){let t=new Store(new Map);return this.onEach(n=>{let i=e(n);if(void 0!==i){let e=n.index();t.set(e,i),clean(()=>{t.delete(e)})}}),t}multiMap(e){let t=new Store(new Map);return this.onEach(n=>{let i,r=e(n);if(r.constructor===Object){for(let e in r)t.set(e,r[e]);i=Object.keys(r)}else{if(!(r instanceof Map))return;r.forEach((e,n)=>{t.set(n,e)}),i=[...r.keys()]}i.length&&clean(()=>{for(let e of i)t.delete(e)})}),t}isDetached(){return!1}dump(){let e=this.getType();"array"===e||"object"===e||"map"===e?(text("<"+e+">"),node("ul",()=>{this.onEach(e=>{node("li",()=>{text(JSON.stringify(e.index())+": "),e.dump()})})})):text(JSON.stringify(this.get()))}}class x extends Store{isDetached(){return!0}}let b=!1,S=new WeakMap;function E(e,t){e.classList.add(t),setTimeout(()=>e.remove(),2e3)}export function node(e="",...t){if(!a)throw new k(!0);let n;if(e instanceof Element)n=e;else{let t,i=e.indexOf(".");i>=0&&(t=e.substr(i+1),e=e.substr(0,i)),n=document.createElement(e||"div"),t&&(n.className=t.replaceAll("."," "))}a.addNode(n);for(let e of t){let t=typeof e;if("function"===t){let t=new u(n,void 0,a.queueOrder+1,e);b?(b=!1,t.update(),b=!0):t.update(),a.cleaners.push(t)}else if("string"===t||"number"===t)n.textContent=e;else if("object"===t&&e&&e.constructor===Object)for(let t in e)O(n,t,e[t]);else if(e instanceof Store)C(n,e);else if(null!=e)throw new Error("Unexpected argument "+JSON.stringify(e))}}export function html(e){if(!a||!a.parentElement)throw new k(!0);let t=document.createElement(a.parentElement.tagName);for(t.innerHTML=""+e;t.firstChild;)a.addNode(t.firstChild)}function C(e,t){let n,i,r=e.getAttribute("type"),s=t.query({peek:!0});"checkbox"===r?(void 0===s&&t.set(e.checked),n=t=>e.checked=t,i=()=>t.set(e.checked)):"radio"===r?(void 0===s&&e.checked&&t.set(e.value),n=t=>e.checked=t===e.value,i=()=>{e.checked&&t.set(e.value)}):(i=()=>t.set("number"===r||"range"===r?""===e.value?null:+e.value:e.value),void 0===s&&i(),n=t=>{e.value!==t&&(e.value=t)}),observe(()=>{n(t.get())}),e.addEventListener("input",i),clean(()=>{e.removeEventListener("input",i)})}export function text(e){if(!a)throw new k(!0);null!=e&&a.addNode(document.createTextNode(e))}export function prop(e,t){if(!a||!a.parentElement)throw new k(!0);if("object"==typeof e)for(let t in e)O(a.parentElement,t,e[t]);else O(a.parentElement,e,t)}export function getParentElement(){if(!a||!a.parentElement)throw new k(!0);return a.parentElement}export function clean(e){if(!a)throw new k(!1);a.cleaners.push({_clean:e})}export function observe(e){mount(void 0,e)}export function mount(e,t){let n;e||!a?n=new u(e,void 0,0,t):(n=new u(a.parentElement,a.lastChild||a.precedingSibling,a.queueOrder+1,t),a.lastChild=n),n.update(),a&&a.cleaners.push(n)}export function peek(e){let t=a;a=void 0;try{return e()}finally{a=t}}function O(e,t,n){if("create"===t)b&&("function"==typeof n?n(e):(e.classList.add(n),setTimeout((function(){e.classList.remove(n)}),0)));else if("destroy"===t)S.set(e,n);else if("function"==typeof n)e.addEventListener(t,n),clean(()=>e.removeEventListener(t,n));else if("value"===t||"className"===t||"selectedIndex"===t||!0===n||!1===n)e[t]=n;else if("text"===t)e.textContent=n;else if("class"!==t&&"className"!==t||"object"!=typeof n)"style"===t&&"object"==typeof n?Object.assign(e.style,n):e.setAttribute(t,n);else for(let t in n)n[t]?e.classList.add(t):e.classList.remove(t)}function I(e){if("object"==typeof e&&e){if(e instanceof Store)return e._observe();if(e instanceof Map){let t=new y;return e.forEach((e,n)=>{let i=I(e);void 0!==i&&t.rawSet(n,i)}),t}if(e instanceof Array){let t=new v;for(let n=0;n<e.length;n++){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}if(e.constructor===Object){let t=new w;for(let n in e){let i=I(e[n]);void 0!==i&&t.rawSet(n,i)}return t}return e}return e}function q(e){return e.index()}function N(e){let t=new Error("Aberdeen internal error "+e);setTimeout(()=>{throw t},0)}function P(e){setTimeout(()=>{throw e},0)}class k extends Error{constructor(e){super(`Operation not permitted outside of ${e?"a mount":"an observe"}() scope`)}}function R(e){const t=e.parentElement?getComputedStyle(e.parentElement):{};return"flex"===t.display&&(t.flexDirection||"").startsWith("row")?{marginLeft:`-${e.offsetWidth/2}px`,marginRight:`-${e.offsetWidth/2}px`,transform:"scaleX(0)"}:{marginBottom:`-${e.offsetHeight/2}px`,marginTop:`-${e.offsetHeight/2}px`,transform:"scaleY(0)"}}export function grow(e){scheduleDomReader(()=>{let t=R(e);scheduleDomWriter(()=>{Object.assign(e.style,t),scheduleDomReader(()=>{e.offsetHeight,scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out";for(let n in t)e.style[n]="";setTimeout(()=>{e.style.transition=""},400)})})})})}export function shrink(e){scheduleDomReader(()=>{const t=R(e);scheduleDomWriter(()=>{e.style.transition="margin 400ms ease-out, transform 400ms ease-out",Object.assign(e.style,t),setTimeout(()=>e.remove(),400)})})}function j(e){if(t)throw new Error("already recording a patch");t=new Map;try{e()}catch(e){throw t=void 0,e}const n=t;return t=void 0,n}function M(e,t,n,i,r){let s=e.get(t);null==s&&(s=new Map,e.set(t,s));let o=s.get(n);s.set(n,[i,null==o?r:o[1]])}function T(e){for(let[t,n]of e)for(let[e,[i,r]]of n)t.emitChange(e,i,r)}function D(e,t,n=!1){for(let[i,r]of t)for(let[t,[s,o]]of r)M(e,i,t,n?o:s,n?s:o)}function $(e,t=!1){for(let[n,i]of e)for(let[e,[r,s]]of i){let i=n.rawGet(e);if(i!==s){if(!t)return!1;P(new Error(`Applying invalid patch: data ${i} is unequal to expected old data ${s} for index ${e}`))}}for(let[t,n]of e)for(let[e,[i,r]]of n)t.rawSet(e,i);return!0}const _=[];export function applyPrediction(e){let t=j(e);return _.push(t),T(t),t}export function applyCanon(e,t=[]){let n=new Map;for(let e of _)D(n,e,!0);$(n,!0);for(let e of t){let t=_.indexOf(e);t>=0&&_.splice(t,1)}e&&D(n,j(e));for(let e=0;e<_.length;e++)$(_[e])?D(n,_[e]):(_.splice(e,1),e--);T(n)}String.prototype.replaceAll||(String.prototype.replaceAll=function(e,t){return this.split(e).join(t)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aberdeen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A TypeScript/JavaScript library for quickly building performant declarative user interfaces without the use of a virtual DOM.",
|
|
5
5
|
"main": "dist/aberdeen.js",
|
|
6
6
|
"types": "dist/aberdeen.d.js",
|