@ng-native/fabric 0.8.0 → 0.9.0
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/dist/css.d.ts +51 -3
- package/dist/css.js +194 -40
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +61 -9
- package/dist/engine.js +238 -50
- package/dist/engine.js.map +1 -1
- package/dist/transition.d.ts +11 -0
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -206,6 +206,8 @@ export interface EngineNode extends HostNode {
|
|
|
206
206
|
styleCache: StyleCache | null;
|
|
207
207
|
/** Set when something that could change what this node matches has changed. */
|
|
208
208
|
styleDirty: boolean;
|
|
209
|
+
/** See `StyleTarget.ownDirty`. */
|
|
210
|
+
ownDirty: boolean;
|
|
209
211
|
/** See `StyleTarget.hasDirty`. */
|
|
210
212
|
hasDirty: boolean;
|
|
211
213
|
/** See `StyleTarget.stateDirty`. */
|
|
@@ -777,16 +779,21 @@ export declare class Engine implements HostEngine {
|
|
|
777
779
|
* Mark a node whose classes changed, by what the rules that name those classes can restyle.
|
|
778
780
|
*
|
|
779
781
|
* A class no selector names changes no style: a library's own marker, of an overlay that is
|
|
780
|
-
* animating or a control that was touched. One
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
782
|
+
* animating or a control that was touched. One a rule is for the element of, `.on`, has that
|
|
783
|
+
* element matched again, and one named as what a rule's element is inside, `.animating .close`
|
|
784
|
+
* or `.close:is(.animating *)`, the elements under the node that such rules are for. What is
|
|
785
|
+
* under any of those is styled again from the rules it matched, for what it inherits: a `dark`
|
|
786
|
+
* class on the root finds no rule again for an element no `dark` rule is for. Any other class
|
|
787
|
+
* has the node and everything under it matched again, as any other change to it does.
|
|
784
788
|
*
|
|
785
789
|
* A sheet that names a class later restyles what it is for as it arrives, and a node not yet
|
|
786
790
|
* committed is styled when it is, with the class as it is then.
|
|
787
791
|
*/
|
|
788
792
|
private markClasses;
|
|
789
|
-
/**
|
|
793
|
+
/**
|
|
794
|
+
* Have matched again each element under a node that one of `subjects`' rules could be for. One
|
|
795
|
+
* under another is marked too: what is under an element marked this way keeps its rules.
|
|
796
|
+
*/
|
|
790
797
|
private markUnder;
|
|
791
798
|
/**
|
|
792
799
|
* The sheet of the component `node` hosts, whose `:host` rules it matches. Set again when a hot
|
|
@@ -800,13 +807,23 @@ export declare class Engine implements HostEngine {
|
|
|
800
807
|
* the web's mount point has, unless the app's `:host` sizes it otherwise.
|
|
801
808
|
*/
|
|
802
809
|
setDefaultStyle(node: EngineNode, style: Readonly<Record<string, unknown>> | undefined): void;
|
|
803
|
-
/**
|
|
810
|
+
/**
|
|
811
|
+
* `class="a b"` from a template. Replaces the set rather than adding to it. The same classes in
|
|
812
|
+
* another order are a change too, to a selector that reads the class attribute, `[class^="a"]`.
|
|
813
|
+
*/
|
|
804
814
|
setClasses(node: EngineNode, value: string): void;
|
|
805
815
|
/** Angular's `@if`/`@for`/`ViewContainerRef` markers. Ordered, never committed. */
|
|
806
816
|
createAnchor(): EngineNode;
|
|
807
817
|
createText(value: string): EngineNode;
|
|
808
818
|
appendChild(parent: EngineNode, child: EngineNode): void;
|
|
809
819
|
insertBefore(parent: EngineNode, child: EngineNode, ref: EngineNode | null): void;
|
|
820
|
+
/**
|
|
821
|
+
* A node coming under a parent other than the one it is under, or under any once it has been
|
|
822
|
+
* taken out: what is over it may be other than it was styled under. The resolver sees that for
|
|
823
|
+
* itself by the parent's cache, except between two boxes that share one, where it is told here.
|
|
824
|
+
* Moved within its own parent, a row of a list reordered, it is styled as it was.
|
|
825
|
+
*/
|
|
826
|
+
private markAdopted;
|
|
810
827
|
removeChild(parent: EngineNode | null, child: EngineNode): void;
|
|
811
828
|
/**
|
|
812
829
|
* A child came or went under a view that takes its text as a prop: that prop changed. A span
|
|
@@ -1122,6 +1139,24 @@ export declare class Engine implements HostEngine {
|
|
|
1122
1139
|
* own frames, which is what lets an element animate in without differing from anything.
|
|
1123
1140
|
*/
|
|
1124
1141
|
private animated;
|
|
1142
|
+
/**
|
|
1143
|
+
* The keyframes a node's animation names: those of its own component's sheet where it has
|
|
1144
|
+
* some by that name, as Angular scopes a component's keyframes in a browser, and otherwise
|
|
1145
|
+
* whichever sheet has them, since any other is global.
|
|
1146
|
+
*
|
|
1147
|
+
* ponytail: a host is asked of the sheet it is written in before its own component's, so
|
|
1148
|
+
* a `:host` animation is played by its parent's frames where both have the name.
|
|
1149
|
+
*/
|
|
1150
|
+
private keyframesOf;
|
|
1151
|
+
/** Each node's frames as they settle on it, kept for as long as they settle the same. */
|
|
1152
|
+
private readonly settledFrames;
|
|
1153
|
+
/**
|
|
1154
|
+
* An animation's frames for the node playing it: those with a value that waits for the node,
|
|
1155
|
+
* a `var()` it sets, settled against it. The same frames are answered while they settle the
|
|
1156
|
+
* same, and others once a variable changes, which is what an animation under way is played
|
|
1157
|
+
* on from: see `reframe`.
|
|
1158
|
+
*/
|
|
1159
|
+
private framesFor;
|
|
1125
1160
|
/**
|
|
1126
1161
|
* A clock animation the scroll plays instead: the same animation where only the timeline moved,
|
|
1127
1162
|
* and one cancelled where the name changed.
|
|
@@ -1179,6 +1214,8 @@ export declare class Engine implements HostEngine {
|
|
|
1179
1214
|
private backToScript;
|
|
1180
1215
|
/** Start the clock on an animation, unless the node is already playing this one. */
|
|
1181
1216
|
private startPlaying;
|
|
1217
|
+
/** Play an animation that has just started: by native's layout, by native's driver, or here. */
|
|
1218
|
+
private playSomewhere;
|
|
1182
1219
|
/**
|
|
1183
1220
|
* The colour `node` inherits, for frames that set `color: currentColor`, which is that colour
|
|
1184
1221
|
* wherever the animation has got to, as in CSS. Undefined for frames that do not.
|
|
@@ -1258,12 +1295,27 @@ export declare class Engine implements HostEngine {
|
|
|
1258
1295
|
* A width eased from JavaScript is a commit a frame, and a commit lays the whole screen out.
|
|
1259
1296
|
*/
|
|
1260
1297
|
private layEased;
|
|
1298
|
+
/** The animations this commit lays out at their last frame, for native to move the views to. */
|
|
1299
|
+
private laidPlayed;
|
|
1300
|
+
/**
|
|
1301
|
+
* Whether an animation that has just started is one native can play by laying the view out
|
|
1302
|
+
* at its last frame: two frames of sizes or places, played once from where the view is drawn
|
|
1303
|
+
* now, which is where native moves it from. It is then committed at that frame; see `layOut`.
|
|
1304
|
+
*/
|
|
1305
|
+
private layPlayed;
|
|
1306
|
+
/** Go on from JavaScript with an animation native was to lay out, by the clock it started on. */
|
|
1307
|
+
private playLaidHere;
|
|
1308
|
+
/** An animation native laid out has had its time: it is at its last frame, and says so. */
|
|
1309
|
+
private endLaidPlayed;
|
|
1261
1310
|
/**
|
|
1262
|
-
* Before a commit is handed over: ask native to move what it lays out,
|
|
1263
|
-
*
|
|
1264
|
-
*
|
|
1311
|
+
* Before a commit is handed over: ask native to move what it lays out, and take back to
|
|
1312
|
+
* ease from JavaScript whatever it cannot: all of it where the commit moves anything else,
|
|
1313
|
+
* and otherwise what would not take as long as the longest. Answers whether the tree has to
|
|
1314
|
+
* be reconciled again.
|
|
1265
1315
|
*/
|
|
1266
1316
|
private layOut;
|
|
1317
|
+
/** Ask native to move what the commit lays out over `duration`, and end each when it is up. */
|
|
1318
|
+
private askLayout;
|
|
1267
1319
|
/**
|
|
1268
1320
|
* Note a commit that resizes or moves a box in no transition native lays out: a prop of one
|
|
1269
1321
|
* that moves boxes, children that came or went, text longer or shorter than it was.
|
package/dist/engine.js
CHANGED
|
@@ -28,7 +28,7 @@ function animationOf(props) {
|
|
|
28
28
|
let spec = props['$animation'];
|
|
29
29
|
if (spec !== undefined)
|
|
30
30
|
delete props['$animation'];
|
|
31
|
-
for (const [key, field] of
|
|
31
|
+
for (const [key, field] of ANIMATION_ENTRIES) {
|
|
32
32
|
if (!(key in props))
|
|
33
33
|
continue;
|
|
34
34
|
const value = props[key];
|
|
@@ -47,6 +47,8 @@ const ANIMATION_PARTS = {
|
|
|
47
47
|
$animationFill: 'fill',
|
|
48
48
|
$playState: 'paused',
|
|
49
49
|
};
|
|
50
|
+
/** Listed once: `animationOf` reads them for every node that commits. */
|
|
51
|
+
const ANIMATION_ENTRIES = Object.entries(ANIMATION_PARTS);
|
|
50
52
|
function animationPart(field, value) {
|
|
51
53
|
switch (field) {
|
|
52
54
|
case 'iterations':
|
|
@@ -537,8 +539,8 @@ function lineBaseline(props, line, [, top, , bottom], fontScale) {
|
|
|
537
539
|
* out on both platforms: on Android, `EditText` centres a line box taller than the font's own
|
|
538
540
|
* about 1.7pt high of centre in a 44pt field, and centres the font's own exactly. A percentage
|
|
539
541
|
* height sizes the field only where its parent's height is definite, which is known at layout
|
|
540
|
-
* alone, so such a field is left as it was. Elsewhere Android sizes the field by the
|
|
541
|
-
* and centres the text in it itself, and keeps it.
|
|
542
|
+
* alone, so on Android such a field is left as it was. Elsewhere Android sizes the field by the
|
|
543
|
+
* line height and centres the text in it itself, and keeps it.
|
|
542
544
|
*
|
|
543
545
|
* On iOS, React Native's field sets `lineHeight` as the paragraph's minimum and maximum line
|
|
544
546
|
* height and, unlike a paragraph (`RCTApplyBaselineOffset`), never offsets the baseline, so the
|
|
@@ -561,10 +563,9 @@ function centreSingleLine(viewName, props, fontScale, onBaseline = false) {
|
|
|
561
563
|
heightBaseline(props, height, onBaseline, fontScale);
|
|
562
564
|
return;
|
|
563
565
|
}
|
|
564
|
-
//
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
// Android sizes the field by its line height, and centres the text in it, itself.
|
|
566
|
+
// Android sizes the field by its line height, and centres the text in it, itself. A
|
|
567
|
+
// percentage is a fixed height only where the parent's is definite, known at layout alone:
|
|
568
|
+
// there too the line height is left out on iOS, and kept as the least the field is tall.
|
|
568
569
|
if (viewName !== 'TextInput')
|
|
569
570
|
return;
|
|
570
571
|
const edge = (side, logical) => blockEdge(props, side, logical);
|
|
@@ -847,10 +848,11 @@ const TOUCHED_ALPHA = 0.011;
|
|
|
847
848
|
function stillTouched(props) {
|
|
848
849
|
// One told to take no touch is not to be sent one, seen or not.
|
|
849
850
|
if (props['pointerEvents'] === 'none')
|
|
850
|
-
return;
|
|
851
|
+
return props;
|
|
851
852
|
const opacity = props['opacity'];
|
|
852
853
|
if (typeof opacity === 'number' && opacity < TOUCHED_ALPHA)
|
|
853
854
|
props['opacity'] = TOUCHED_ALPHA;
|
|
855
|
+
return props;
|
|
854
856
|
}
|
|
855
857
|
/**
|
|
856
858
|
* Leave `aspect-ratio` out for a box with both a width and a height. CSS takes the ratio only
|
|
@@ -1411,6 +1413,23 @@ const MOVES_BOXES = new Set([
|
|
|
1411
1413
|
...['aspectRatio', 'fontSize', 'lineHeight', 'fontFamily', 'fontWeight', 'letterSpacing'],
|
|
1412
1414
|
...['numberOfLines', 'start', 'end'],
|
|
1413
1415
|
]);
|
|
1416
|
+
/** Whether native is moving a view to a prop's value, by a transition or an animation it laid out. */
|
|
1417
|
+
function laidByNative(node, key) {
|
|
1418
|
+
const eased = node.transitions?.get(key);
|
|
1419
|
+
if (eased?.laid && !eased.done)
|
|
1420
|
+
return true;
|
|
1421
|
+
return node.playing?.laid === true && node.playing.tracks.has(key);
|
|
1422
|
+
}
|
|
1423
|
+
/** Whether an animation plays once through, forwards, with no wait before it. */
|
|
1424
|
+
const playedOnce = (spec) => spec.delay === 0 && spec.duration > 0 && spec.iterations === 1 && !spec.direction;
|
|
1425
|
+
/** Whether a track is two frames on one curve, the first of them where the view is drawn. */
|
|
1426
|
+
function fromWhereDrawn(track, drawn) {
|
|
1427
|
+
const [first, last] = track;
|
|
1428
|
+
if (track.length !== 2 || first.easing || last.value == null)
|
|
1429
|
+
return false;
|
|
1430
|
+
// One that ends where it starts moves nothing: there is nothing for native to lay out.
|
|
1431
|
+
return Object.is(first.value, drawn) && !Object.is(first.value, last.value);
|
|
1432
|
+
}
|
|
1414
1433
|
/** The curves a layout animation has, as the timing function each is. */
|
|
1415
1434
|
const LAYOUT_CURVES = [
|
|
1416
1435
|
['linear', [0, 0, 1, 1]],
|
|
@@ -1645,6 +1664,7 @@ class RetainedNode {
|
|
|
1645
1664
|
customProperties = null;
|
|
1646
1665
|
styleCache = null;
|
|
1647
1666
|
styleDirty = true;
|
|
1667
|
+
ownDirty = false;
|
|
1648
1668
|
styleCommitted = null;
|
|
1649
1669
|
fitContainer = undefined;
|
|
1650
1670
|
heightBasis = undefined;
|
|
@@ -1861,6 +1881,8 @@ const TRANSITION_TIMING = {
|
|
|
1861
1881
|
$transitionDelay: 'delay',
|
|
1862
1882
|
$transitionEasing: 'easing',
|
|
1863
1883
|
};
|
|
1884
|
+
/** Listed once, as `ANIMATION_ENTRIES` is. */
|
|
1885
|
+
const TRANSITION_ENTRIES = Object.entries(TRANSITION_TIMING);
|
|
1864
1886
|
/**
|
|
1865
1887
|
* The transition spec, with any timing set by a rule of its own laid over it: `.duration-700`
|
|
1866
1888
|
* beside `.transition`. A rule that writes the timing itself compiles it to `null`, which is how
|
|
@@ -1874,7 +1896,7 @@ function transitionSpec(props) {
|
|
|
1874
1896
|
let spec = props['$transition'];
|
|
1875
1897
|
if (spec !== undefined)
|
|
1876
1898
|
delete props['$transition'];
|
|
1877
|
-
for (const [key, part] of
|
|
1899
|
+
for (const [key, part] of TRANSITION_ENTRIES) {
|
|
1878
1900
|
if (!(key in props))
|
|
1879
1901
|
continue;
|
|
1880
1902
|
const value = props[key];
|
|
@@ -1993,6 +2015,14 @@ const now = () => globalThis.performance?.now?.() ?? Date.now();
|
|
|
1993
2015
|
function sameHandles(a, b) {
|
|
1994
2016
|
return a.length === b.length && a.every((handle, i) => handle === b[i]);
|
|
1995
2017
|
}
|
|
2018
|
+
/** Whether two sets of the same classes hold them in the same order. */
|
|
2019
|
+
function inOrder(a, b) {
|
|
2020
|
+
const other = b.values();
|
|
2021
|
+
for (const name of a)
|
|
2022
|
+
if (other.next().value !== name)
|
|
2023
|
+
return false;
|
|
2024
|
+
return true;
|
|
2025
|
+
}
|
|
1996
2026
|
/**
|
|
1997
2027
|
* Whether a node could be the element a compound is written for, by its classes alone: its own,
|
|
1998
2028
|
* and those of one alternative of each `:is()` on it, which is where Tailwind names a group or
|
|
@@ -2495,34 +2525,46 @@ export class Engine {
|
|
|
2495
2525
|
* Mark a node whose classes changed, by what the rules that name those classes can restyle.
|
|
2496
2526
|
*
|
|
2497
2527
|
* A class no selector names changes no style: a library's own marker, of an overlay that is
|
|
2498
|
-
* animating or a control that was touched. One
|
|
2499
|
-
*
|
|
2500
|
-
*
|
|
2501
|
-
*
|
|
2528
|
+
* animating or a control that was touched. One a rule is for the element of, `.on`, has that
|
|
2529
|
+
* element matched again, and one named as what a rule's element is inside, `.animating .close`
|
|
2530
|
+
* or `.close:is(.animating *)`, the elements under the node that such rules are for. What is
|
|
2531
|
+
* under any of those is styled again from the rules it matched, for what it inherits: a `dark`
|
|
2532
|
+
* class on the root finds no rule again for an element no `dark` rule is for. Any other class
|
|
2533
|
+
* has the node and everything under it matched again, as any other change to it does.
|
|
2502
2534
|
*
|
|
2503
2535
|
* A sheet that names a class later restyles what it is for as it arrives, and a node not yet
|
|
2504
2536
|
* committed is styled when it is, with the class as it is then.
|
|
2505
2537
|
*/
|
|
2506
2538
|
markClasses(node, changed) {
|
|
2539
|
+
// An element still being built, in no tree and never styled: nothing is styled by it yet,
|
|
2540
|
+
// and it is styled when it is committed, with the classes it has then. Every element of a
|
|
2541
|
+
// screen is given its classes this way, so what they reach is not asked for each.
|
|
2542
|
+
if (node.parent === null && node.styleCache === null)
|
|
2543
|
+
return this.markProps(node, false);
|
|
2507
2544
|
const reach = this.styles.reachOf(changed);
|
|
2508
2545
|
if (reach === true)
|
|
2509
2546
|
return this.markProps(node);
|
|
2510
2547
|
this.markProps(node, false);
|
|
2511
|
-
if (reach)
|
|
2548
|
+
if (!reach)
|
|
2549
|
+
return;
|
|
2550
|
+
if (reach.own)
|
|
2551
|
+
node.ownDirty = true;
|
|
2552
|
+
if (this.styles.reachesUnder(reach))
|
|
2512
2553
|
this.markUnder(node, reach);
|
|
2513
2554
|
}
|
|
2514
|
-
/**
|
|
2555
|
+
/**
|
|
2556
|
+
* Have matched again each element under a node that one of `subjects`' rules could be for. One
|
|
2557
|
+
* under another is marked too: what is under an element marked this way keeps its rules.
|
|
2558
|
+
*/
|
|
2515
2559
|
markUnder(node, subjects) {
|
|
2516
2560
|
for (const child of node.children) {
|
|
2517
2561
|
if (child.kind !== 'element')
|
|
2518
2562
|
continue;
|
|
2519
2563
|
if (this.styles.isFor(subjects, child)) {
|
|
2520
|
-
|
|
2521
|
-
child.styleDirty = true;
|
|
2564
|
+
child.ownDirty = true;
|
|
2522
2565
|
this.markPath(child);
|
|
2523
2566
|
}
|
|
2524
|
-
|
|
2525
|
-
this.markUnder(child, subjects);
|
|
2567
|
+
this.markUnder(child, subjects);
|
|
2526
2568
|
}
|
|
2527
2569
|
}
|
|
2528
2570
|
/**
|
|
@@ -2548,7 +2590,10 @@ export class Engine {
|
|
|
2548
2590
|
node.defaultStyle = style;
|
|
2549
2591
|
this.markProps(node);
|
|
2550
2592
|
}
|
|
2551
|
-
/**
|
|
2593
|
+
/**
|
|
2594
|
+
* `class="a b"` from a template. Replaces the set rather than adding to it. The same classes in
|
|
2595
|
+
* another order are a change too, to a selector that reads the class attribute, `[class^="a"]`.
|
|
2596
|
+
*/
|
|
2552
2597
|
setClasses(node, value) {
|
|
2553
2598
|
const names = value.split(/\s+/).filter(Boolean);
|
|
2554
2599
|
const before = node.classes;
|
|
@@ -2558,7 +2603,8 @@ export class Engine {
|
|
|
2558
2603
|
for (const name of before ?? [])
|
|
2559
2604
|
if (!node.classes?.has(name))
|
|
2560
2605
|
changed.push(name);
|
|
2561
|
-
|
|
2606
|
+
const reordered = !changed.length && !!before && !!node.classes && !inOrder(before, node.classes);
|
|
2607
|
+
this.markClasses(node, reordered ? node.classes : changed);
|
|
2562
2608
|
}
|
|
2563
2609
|
/** Angular's `@if`/`@for`/`ViewContainerRef` markers. Ordered, never committed. */
|
|
2564
2610
|
createAnchor() {
|
|
@@ -2570,6 +2616,7 @@ export class Engine {
|
|
|
2570
2616
|
return node;
|
|
2571
2617
|
}
|
|
2572
2618
|
appendChild(parent, child) {
|
|
2619
|
+
this.markAdopted(child, parent);
|
|
2573
2620
|
this.detach(child);
|
|
2574
2621
|
child.parent = parent;
|
|
2575
2622
|
parent.children.push(child);
|
|
@@ -2579,6 +2626,7 @@ export class Engine {
|
|
|
2579
2626
|
this.markTextContent(parent, child);
|
|
2580
2627
|
}
|
|
2581
2628
|
insertBefore(parent, child, ref) {
|
|
2629
|
+
this.markAdopted(child, parent);
|
|
2582
2630
|
this.detach(child);
|
|
2583
2631
|
child.parent = parent;
|
|
2584
2632
|
const at = ref ? parent.children.indexOf(ref) : -1;
|
|
@@ -2591,6 +2639,16 @@ export class Engine {
|
|
|
2591
2639
|
this.markStructure(parent, child, at < 0 ? parent.children.length - 1 : at);
|
|
2592
2640
|
this.markTextContent(parent, child);
|
|
2593
2641
|
}
|
|
2642
|
+
/**
|
|
2643
|
+
* A node coming under a parent other than the one it is under, or under any once it has been
|
|
2644
|
+
* taken out: what is over it may be other than it was styled under. The resolver sees that for
|
|
2645
|
+
* itself by the parent's cache, except between two boxes that share one, where it is told here.
|
|
2646
|
+
* Moved within its own parent, a row of a list reordered, it is styled as it was.
|
|
2647
|
+
*/
|
|
2648
|
+
markAdopted(child, parent) {
|
|
2649
|
+
if (child.parent !== parent && this.styles.styledUnderShared(child))
|
|
2650
|
+
child.styleDirty = true;
|
|
2651
|
+
}
|
|
2594
2652
|
removeChild(parent, child) {
|
|
2595
2653
|
const target = parent ?? child.parent;
|
|
2596
2654
|
if (!target)
|
|
@@ -2842,7 +2900,10 @@ export class Engine {
|
|
|
2842
2900
|
else {
|
|
2843
2901
|
node.customProperties = { ...current, [name]: token };
|
|
2844
2902
|
}
|
|
2845
|
-
|
|
2903
|
+
// In scope for the node's own rules and for what is under it, and read by no selector: the
|
|
2904
|
+
// node is styled again, and those under it from the rules they matched.
|
|
2905
|
+
this.markProps(node, false);
|
|
2906
|
+
node.ownDirty = true;
|
|
2846
2907
|
}
|
|
2847
2908
|
setText(node, value) {
|
|
2848
2909
|
const filled = (node.text === '') !== (value === '');
|
|
@@ -3538,7 +3599,8 @@ export class Engine {
|
|
|
3538
3599
|
}
|
|
3539
3600
|
return props;
|
|
3540
3601
|
}
|
|
3541
|
-
const
|
|
3602
|
+
const written = this.keyframesOf(node, spec.name);
|
|
3603
|
+
const frames = written && this.framesFor(node, written);
|
|
3542
3604
|
if (!frames) {
|
|
3543
3605
|
// Keyframes a hot swap deleted, from under an animation that was playing them, or ones a
|
|
3544
3606
|
// sheet later in this commit has: see `settleKeyframes`.
|
|
@@ -3554,6 +3616,49 @@ export class Engine {
|
|
|
3554
3616
|
this.startPlaying(node, spec, frames, props);
|
|
3555
3617
|
return Object.assign(props, playedOver(node));
|
|
3556
3618
|
}
|
|
3619
|
+
/**
|
|
3620
|
+
* The keyframes a node's animation names: those of its own component's sheet where it has
|
|
3621
|
+
* some by that name, as Angular scopes a component's keyframes in a browser, and otherwise
|
|
3622
|
+
* whichever sheet has them, since any other is global.
|
|
3623
|
+
*
|
|
3624
|
+
* ponytail: a host is asked of the sheet it is written in before its own component's, so
|
|
3625
|
+
* a `:host` animation is played by its parent's frames where both have the name.
|
|
3626
|
+
*/
|
|
3627
|
+
keyframesOf(node, name) {
|
|
3628
|
+
const own = node.sheet?.keyframes?.[name] ?? node.hostSheet?.keyframes?.[name];
|
|
3629
|
+
return own ?? this.keyframes.get(name);
|
|
3630
|
+
}
|
|
3631
|
+
/** Each node's frames as they settle on it, kept for as long as they settle the same. */
|
|
3632
|
+
settledFrames = new WeakMap();
|
|
3633
|
+
/**
|
|
3634
|
+
* An animation's frames for the node playing it: those with a value that waits for the node,
|
|
3635
|
+
* a `var()` it sets, settled against it. The same frames are answered while they settle the
|
|
3636
|
+
* same, and others once a variable changes, which is what an animation under way is played
|
|
3637
|
+
* on from: see `reframe`.
|
|
3638
|
+
*/
|
|
3639
|
+
framesFor(node, frames) {
|
|
3640
|
+
if (!frames.some((frame) => frame.deferred))
|
|
3641
|
+
return frames;
|
|
3642
|
+
const kept = this.settledFrames.get(node);
|
|
3643
|
+
// Styled as it was, it settles as it did: a commit a frame asks, and most change nothing.
|
|
3644
|
+
const styled = node.styleCache;
|
|
3645
|
+
if (kept?.of === frames && kept.styled === styled)
|
|
3646
|
+
return kept.frames;
|
|
3647
|
+
const settled = frames.map((frame) => frame.deferred && this.styles.settleFor(node, frame.deferred, this.styleEpoch));
|
|
3648
|
+
const key = JSON.stringify(settled);
|
|
3649
|
+
if (kept?.of === frames && kept.key === key) {
|
|
3650
|
+
kept.styled = styled;
|
|
3651
|
+
return kept.frames;
|
|
3652
|
+
}
|
|
3653
|
+
const made = frames.map((frame, at) => {
|
|
3654
|
+
const values = Object.entries(settled[at] ?? {}).filter(([, value]) => value !== undefined);
|
|
3655
|
+
return values.length
|
|
3656
|
+
? { ...frame, declarations: { ...frame.declarations, ...Object.fromEntries(values) } }
|
|
3657
|
+
: frame;
|
|
3658
|
+
});
|
|
3659
|
+
this.settledFrames.set(node, { of: frames, styled, key, frames: made });
|
|
3660
|
+
return made;
|
|
3661
|
+
}
|
|
3557
3662
|
/**
|
|
3558
3663
|
* A clock animation the scroll plays instead: the same animation where only the timeline moved,
|
|
3559
3664
|
* and one cancelled where the name changed.
|
|
@@ -3749,8 +3854,8 @@ export class Engine {
|
|
|
3749
3854
|
node.playing = started;
|
|
3750
3855
|
if (spec.paused)
|
|
3751
3856
|
started.pausedAt = this.now();
|
|
3752
|
-
else
|
|
3753
|
-
this.
|
|
3857
|
+
else
|
|
3858
|
+
this.playSomewhere(node, started, props);
|
|
3754
3859
|
this.emitTransition(node, 'topAnimationstart', spec.name);
|
|
3755
3860
|
return;
|
|
3756
3861
|
}
|
|
@@ -3764,6 +3869,13 @@ export class Engine {
|
|
|
3764
3869
|
}
|
|
3765
3870
|
this.playOrPause(node, node.playing, spec);
|
|
3766
3871
|
}
|
|
3872
|
+
/** Play an animation that has just started: by native's layout, by native's driver, or here. */
|
|
3873
|
+
playSomewhere(node, started, props) {
|
|
3874
|
+
if (this.layPlayed(node, started))
|
|
3875
|
+
this.laidPlayed.push({ node, running: started });
|
|
3876
|
+
else if (!this.playNatively(node, started, props))
|
|
3877
|
+
this.playing.add(node);
|
|
3878
|
+
}
|
|
3767
3879
|
/**
|
|
3768
3880
|
* The colour `node` inherits, for frames that set `color: currentColor`, which is that colour
|
|
3769
3881
|
* wherever the animation has got to, as in CSS. Undefined for frames that do not.
|
|
@@ -3781,7 +3893,8 @@ export class Engine {
|
|
|
3781
3893
|
*/
|
|
3782
3894
|
reframe(node, current, frames, props, inherited) {
|
|
3783
3895
|
const tracks = tracksOf(frames, props, inherited);
|
|
3784
|
-
|
|
3896
|
+
// One native was laying out goes on from JavaScript, by the clock it started on.
|
|
3897
|
+
const reframed = { ...current, tracks, inherited, laid: undefined };
|
|
3785
3898
|
const { values, finished } = sample(reframed, current.pausedAt ?? this.now());
|
|
3786
3899
|
const holds = current.spec.fill === 'forwards' || current.spec.fill === 'both';
|
|
3787
3900
|
reframed.values = finished && !holds ? {} : values;
|
|
@@ -3975,7 +4088,8 @@ export class Engine {
|
|
|
3975
4088
|
}
|
|
3976
4089
|
/** Nobody saw what the last commit gave a view it created, so this is where the view starts. */
|
|
3977
4090
|
unseenTransitions(node) {
|
|
3978
|
-
|
|
4091
|
+
// One with no commit it was born in has been seen, whatever was committed since.
|
|
4092
|
+
if (node.bornIn === undefined || node.bornIn < this.unseen)
|
|
3979
4093
|
return;
|
|
3980
4094
|
this.stopEased(node);
|
|
3981
4095
|
node.transitions = undefined;
|
|
@@ -4037,36 +4151,101 @@ export class Engine {
|
|
|
4037
4151
|
if (eased.laid && !eased.done)
|
|
4038
4152
|
props[key] = eased.to;
|
|
4039
4153
|
}
|
|
4154
|
+
/** The animations this commit lays out at their last frame, for native to move the views to. */
|
|
4155
|
+
laidPlayed = [];
|
|
4040
4156
|
/**
|
|
4041
|
-
*
|
|
4042
|
-
*
|
|
4043
|
-
*
|
|
4157
|
+
* Whether an animation that has just started is one native can play by laying the view out
|
|
4158
|
+
* at its last frame: two frames of sizes or places, played once from where the view is drawn
|
|
4159
|
+
* now, which is where native moves it from. It is then committed at that frame; see `layOut`.
|
|
4160
|
+
*/
|
|
4161
|
+
layPlayed(node, running) {
|
|
4162
|
+
const drawn = node.committed?.props;
|
|
4163
|
+
const { spec, tracks } = running;
|
|
4164
|
+
if (!this.fabric.configureNextLayoutAnimation || !drawn || !tracks.size)
|
|
4165
|
+
return false;
|
|
4166
|
+
if (!playedOnce(spec))
|
|
4167
|
+
return false;
|
|
4168
|
+
for (const [key, track] of tracks) {
|
|
4169
|
+
if (!LAID_OUT.has(key) || !fromWhereDrawn(track, drawn[key]))
|
|
4170
|
+
return false;
|
|
4171
|
+
}
|
|
4172
|
+
running.laid = true;
|
|
4173
|
+
running.values = Object.fromEntries([...tracks].map(([key, track]) => [key, track[1].value]));
|
|
4174
|
+
return true;
|
|
4175
|
+
}
|
|
4176
|
+
/** Go on from JavaScript with an animation native was to lay out, by the clock it started on. */
|
|
4177
|
+
playLaidHere(node, running) {
|
|
4178
|
+
running.laid = undefined;
|
|
4179
|
+
running.values = sample(running, this.now()).values;
|
|
4180
|
+
this.playing.add(node);
|
|
4181
|
+
}
|
|
4182
|
+
/** An animation native laid out has had its time: it is at its last frame, and says so. */
|
|
4183
|
+
endLaidPlayed(node, running) {
|
|
4184
|
+
if (!running.laid || running.done || node.playing !== running)
|
|
4185
|
+
return;
|
|
4186
|
+
Object.assign(running, { done: true, laid: undefined });
|
|
4187
|
+
if (!this.keeps(node, running)) {
|
|
4188
|
+
running.values = {};
|
|
4189
|
+
this.markProps(node, false);
|
|
4190
|
+
}
|
|
4191
|
+
this.emitTransition(node, 'topAnimationend', running.spec.name);
|
|
4192
|
+
this.flushTransitionEvents();
|
|
4193
|
+
if (this.pending)
|
|
4194
|
+
this.commit();
|
|
4195
|
+
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Before a commit is handed over: ask native to move what it lays out, and take back to
|
|
4198
|
+
* ease from JavaScript whatever it cannot: all of it where the commit moves anything else,
|
|
4199
|
+
* and otherwise what would not take as long as the longest. Answers whether the tree has to
|
|
4200
|
+
* be reconciled again.
|
|
4044
4201
|
*/
|
|
4045
4202
|
layOut() {
|
|
4046
4203
|
const laying = this.laying;
|
|
4204
|
+
const played = this.laidPlayed.filter(({ node, running }) => node.playing === running);
|
|
4047
4205
|
const others = this.movesOthers;
|
|
4048
4206
|
this.laying = [];
|
|
4207
|
+
this.laidPlayed = [];
|
|
4049
4208
|
this.movesOthers = false;
|
|
4050
|
-
if (!laying.length)
|
|
4209
|
+
if (!laying.length && !played.length)
|
|
4051
4210
|
return false;
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4211
|
+
// Native takes one length of time for a commit: the longest asked of it, and whatever
|
|
4212
|
+
// would take another is eased from JavaScript. None of it, where anything else moves.
|
|
4213
|
+
const longest = Math.max(...laying.map(({ eased }) => eased.duration), ...played.map(({ running }) => running.spec.duration));
|
|
4214
|
+
const kept = (duration) => !others && duration === longest;
|
|
4215
|
+
const back = [];
|
|
4216
|
+
for (const { node, eased } of laying) {
|
|
4217
|
+
if (kept(eased.duration))
|
|
4218
|
+
continue;
|
|
4219
|
+
eased.laid = undefined;
|
|
4220
|
+
this.running.add(node);
|
|
4221
|
+
back.push(node);
|
|
4222
|
+
}
|
|
4223
|
+
for (const { node, running } of played) {
|
|
4224
|
+
if (kept(running.spec.duration))
|
|
4225
|
+
continue;
|
|
4226
|
+
this.playLaidHere(node, running);
|
|
4227
|
+
back.push(node);
|
|
4062
4228
|
}
|
|
4063
|
-
const
|
|
4229
|
+
for (const node of back)
|
|
4230
|
+
this.markProps(node, false);
|
|
4231
|
+
if (!others)
|
|
4232
|
+
this.askLayout(laying, played, longest);
|
|
4233
|
+
return back.length > 0;
|
|
4234
|
+
}
|
|
4235
|
+
/** Ask native to move what the commit lays out over `duration`, and end each when it is up. */
|
|
4236
|
+
askLayout(laying, played, duration) {
|
|
4237
|
+
const easing = laying.find(({ eased }) => eased.laid)?.eased.easing ??
|
|
4238
|
+
played.find(({ running }) => running.laid).running.spec.easing;
|
|
4064
4239
|
const nothing = () => undefined;
|
|
4065
4240
|
this.fabric.configureNextLayoutAnimation({ duration, update: { type: layoutCurve(easing) } }, nothing, nothing);
|
|
4066
4241
|
for (const { node, key, eased } of laying) {
|
|
4067
|
-
|
|
4242
|
+
if (eased.laid)
|
|
4243
|
+
setTimeout(() => this.endLaid(node, key, eased), duration);
|
|
4244
|
+
}
|
|
4245
|
+
for (const { node, running } of played) {
|
|
4246
|
+
if (running.laid)
|
|
4247
|
+
setTimeout(() => this.endLaidPlayed(node, running), duration);
|
|
4068
4248
|
}
|
|
4069
|
-
return false;
|
|
4070
4249
|
}
|
|
4071
4250
|
/**
|
|
4072
4251
|
* Note a commit that resizes or moves a box in no transition native lays out: a prop of one
|
|
@@ -4078,8 +4257,7 @@ export class Engine {
|
|
|
4078
4257
|
if (resized)
|
|
4079
4258
|
this.movesOthers = true;
|
|
4080
4259
|
for (const key in payload) {
|
|
4081
|
-
|
|
4082
|
-
if (eased?.laid && !eased.done)
|
|
4260
|
+
if (laidByNative(node, key))
|
|
4083
4261
|
continue;
|
|
4084
4262
|
// Text as long as it was is taken to be as wide, as a grid takes it: see `shapeOf`.
|
|
4085
4263
|
const longer = key === 'text' && String(payload[key]).length !== String(words).length;
|
|
@@ -4131,13 +4309,16 @@ export class Engine {
|
|
|
4131
4309
|
}
|
|
4132
4310
|
playEased(node, state, keys, props) {
|
|
4133
4311
|
const first = state.get(keys[0]);
|
|
4312
|
+
// Where the view is committed: one that takes a touch stops short of no opacity.
|
|
4313
|
+
const touched = this.responders.has(node);
|
|
4314
|
+
const seen = (key, value) => touched && key === 'opacity' ? stillTouched({ ...props, opacity: value })['opacity'] : value;
|
|
4134
4315
|
const tracks = new Map(keys.map((key) => {
|
|
4135
4316
|
const { current, to, easing } = state.get(key);
|
|
4136
4317
|
return [
|
|
4137
4318
|
key,
|
|
4138
4319
|
[
|
|
4139
|
-
{ offset: 0, value: current, easing },
|
|
4140
|
-
{ offset: 1, value: to },
|
|
4320
|
+
{ offset: 0, value: seen(key, current), easing },
|
|
4321
|
+
{ offset: 1, value: seen(key, to) },
|
|
4141
4322
|
],
|
|
4142
4323
|
];
|
|
4143
4324
|
}));
|
|
@@ -4986,6 +5167,11 @@ export class Engine {
|
|
|
4986
5167
|
setResponder(node, handlers) {
|
|
4987
5168
|
this.registerEventHandler();
|
|
4988
5169
|
this.responders.set(node, handlers);
|
|
5170
|
+
// A fade native is playing was given to it as a view that takes no touch fades: to
|
|
5171
|
+
// nothing. From here it is eased to where the view is committed.
|
|
5172
|
+
const fade = node.transitions?.get('opacity')?.native;
|
|
5173
|
+
if (fade)
|
|
5174
|
+
this.backToScriptEased(node, fade, this.now());
|
|
4989
5175
|
this.keepNative(node, true);
|
|
4990
5176
|
// What it is committed with can hang on whether it takes a touch: see `stillTouched`.
|
|
4991
5177
|
this.markProps(node);
|
|
@@ -5213,12 +5399,14 @@ export class Engine {
|
|
|
5213
5399
|
this.currentResponder = null;
|
|
5214
5400
|
this.tellNative(node, false);
|
|
5215
5401
|
const handlers = this.responders.get(node);
|
|
5402
|
+
// Before the handler is told: what it does is for the render pass after it to commit
|
|
5403
|
+
// whole, and not to be drawn part made with the view no longer pressed.
|
|
5404
|
+
if (changed)
|
|
5405
|
+
this.commit();
|
|
5216
5406
|
if (terminated)
|
|
5217
5407
|
handlers?.onResponderTerminate?.(event);
|
|
5218
5408
|
else
|
|
5219
5409
|
handlers?.onResponderRelease?.(event);
|
|
5220
|
-
if (changed)
|
|
5221
|
-
this.commit();
|
|
5222
5410
|
}
|
|
5223
5411
|
tellNative(node, isResponder) {
|
|
5224
5412
|
const handle = node.committed?.handle;
|