@ng-native/fabric 0.7.0 → 0.8.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/background-layers.d.ts +18 -0
- package/dist/background-layers.js +70 -0
- package/dist/background-layers.js.map +1 -0
- package/dist/css.d.ts +16 -0
- package/dist/css.js +135 -22
- package/dist/css.js.map +1 -1
- package/dist/engine.d.ts +79 -1
- package/dist/engine.js +425 -20
- package/dist/engine.js.map +1 -1
- package/dist/fabric.d.ts +1 -1
- package/dist/fabric.js +1 -0
- package/dist/fabric.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/native-drive.js +9 -0
- package/dist/native-drive.js.map +1 -1
- package/dist/scroll-animation.d.ts +11 -0
- package/dist/scroll-animation.js +30 -1
- package/dist/scroll-animation.js.map +1 -1
- package/dist/transition.d.ts +23 -0
- package/dist/transition.js +32 -9
- package/dist/transition.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
import { boundDeclaration, faded, fills, inlineInherited, setsInherited, StyleResolver, usesActive, elementsAtEnd, placeReadElsewhere, siblingReach, } from "./css.js";
|
|
11
11
|
import { applyAria } from "./aria-props.js";
|
|
12
12
|
import { STYLED_ELEMENTS } from "./element-styles.js";
|
|
13
|
-
import { animationEvent, bezier, interpolate, readsInheritedColour, sample, step, steppedKeys, tracksOf, } from "./transition.js";
|
|
13
|
+
import { aimsElsewhere, animationEvent, bezier, interpolate, readsInheritedColour, sample, step, steppedKeys, tracksOf, } from "./transition.js";
|
|
14
14
|
import { isFormsInput } from "./forms-inputs.js";
|
|
15
15
|
import { transformList } from "./inline-transform.js";
|
|
16
16
|
import { tokenFromValue } from "./inline-token.js";
|
|
17
17
|
import { NativeScrollDriver, scrollFeed, } from "./native-drive.js";
|
|
18
|
-
import { clockChannels, firstFrame, movesByShare, rangeOf, scrollChannels, sharesAsPoints, } from "./scroll-animation.js";
|
|
18
|
+
import { clockChannels, colourChannels, firstFrame, movesByShare, rangeOf, scrollChannels, sharesAsPoints, } from "./scroll-animation.js";
|
|
19
19
|
import { FontFaces } from "./font-faces.js";
|
|
20
20
|
import { premultipliedStops } from "./premultiplied-stops.js";
|
|
21
21
|
/**
|
|
@@ -394,6 +394,24 @@ const PARAGRAPH = 'Paragraph';
|
|
|
394
394
|
* that is laid out in its flow. Words written straight into the view are the paragraph the
|
|
395
395
|
* engine makes around them, and a child that is not displayed has no view to ask.
|
|
396
396
|
*/
|
|
397
|
+
/**
|
|
398
|
+
* A view with a transition is a native view for as long as it has one, as an `Animated.View` is.
|
|
399
|
+
*
|
|
400
|
+
* React Native gives a view a native view of its own for an opacity under 1, a transform or a
|
|
401
|
+
* background, and takes it away when the last such reason goes. A wrapper that fades in was one
|
|
402
|
+
* for the length of the fade and none after, and Fabric moved everything inside it to the view
|
|
403
|
+
* above as the fade ended. Being moved takes a focused field out of the window for a moment, and
|
|
404
|
+
* it lost its focus: a field focused as its container faded in blurred when the fade finished.
|
|
405
|
+
*
|
|
406
|
+
* A floor, as `keepNative` is: a `collapsable` written on the element is left alone. Not for
|
|
407
|
+
* `transition: none`, which transitions nothing.
|
|
408
|
+
*/
|
|
409
|
+
function keepForTransition(node, props, spec) {
|
|
410
|
+
if (viewNameOf(node) !== DEFAULT_VIEW || props['collapsable'] !== undefined)
|
|
411
|
+
return;
|
|
412
|
+
if (Object.keys(spec).length > 0)
|
|
413
|
+
props['collapsable'] = false;
|
|
414
|
+
}
|
|
397
415
|
function firstInFlow(node) {
|
|
398
416
|
for (const child of node.children ?? []) {
|
|
399
417
|
// Not the paragraph itself, which holds the words it was made around.
|
|
@@ -576,6 +594,44 @@ function inlineOf(node) {
|
|
|
576
594
|
delete inline[key];
|
|
577
595
|
return inline;
|
|
578
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* What a node's `@keyframes` animation sets this frame, less each property a rule it matches
|
|
599
|
+
* declared `!important`: an important author declaration is above the animation in the cascade.
|
|
600
|
+
* A transition is above both, so `transitioned` takes no such care.
|
|
601
|
+
*/
|
|
602
|
+
function playedOver(node) {
|
|
603
|
+
const values = node.playing?.values;
|
|
604
|
+
const important = node.styleCache?.important;
|
|
605
|
+
if (!values || !important)
|
|
606
|
+
return values ?? NO_STYLE;
|
|
607
|
+
const played = { ...values };
|
|
608
|
+
for (const key of important)
|
|
609
|
+
delete played[key];
|
|
610
|
+
return played;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* The properties among an animation's tracks that a rule `node` matches declared `!important`,
|
|
614
|
+
* which native is not handed: what native plays it sets on the view, over what the rule says.
|
|
615
|
+
*/
|
|
616
|
+
function pinnedIn(node, tracks) {
|
|
617
|
+
const important = node.styleCache?.important;
|
|
618
|
+
return important ? [...tracks.keys()].filter((key) => important.has(key)) : [];
|
|
619
|
+
}
|
|
620
|
+
/** Whether native is playing an animation whose important properties are no longer the ones left out. */
|
|
621
|
+
function pinsChanged(node, running) {
|
|
622
|
+
if (!running.native)
|
|
623
|
+
return false;
|
|
624
|
+
return pinnedIn(node, running.tracks).join(' ') !== running.pinned?.join(' ');
|
|
625
|
+
}
|
|
626
|
+
/** `tracks` less the properties named. */
|
|
627
|
+
function unpinned(tracks, pinned) {
|
|
628
|
+
if (!pinned.length)
|
|
629
|
+
return tracks;
|
|
630
|
+
const kept = new Map(tracks);
|
|
631
|
+
for (const key of pinned)
|
|
632
|
+
kept.delete(key);
|
|
633
|
+
return kept;
|
|
634
|
+
}
|
|
579
635
|
/**
|
|
580
636
|
* What a container's own style says for `key`, in the order `mergeProps` applies them: a
|
|
581
637
|
* component's override, inline, a prop, its stylesheet, its default.
|
|
@@ -1319,6 +1375,69 @@ function repaint(node) {
|
|
|
1319
1375
|
}
|
|
1320
1376
|
/** A sixtieth of a second: how far apart the frames native is given for an animation are. */
|
|
1321
1377
|
const NATIVE_FRAME = 1000 / 60;
|
|
1378
|
+
/**
|
|
1379
|
+
* What native eases a set of transitions on one clock by: a colour by its own channels, opacity
|
|
1380
|
+
* and a transform as an animation of them is. Nothing where any of them is no value native can
|
|
1381
|
+
* ease, which leaves them all to JavaScript.
|
|
1382
|
+
*/
|
|
1383
|
+
function easedChannels(tracks, spec, props) {
|
|
1384
|
+
const tinted = new Map([...tracks].filter(([key]) => EASED_COLOURS.has(key)));
|
|
1385
|
+
const moved = new Map([...tracks].filter(([key]) => !EASED_COLOURS.has(key)));
|
|
1386
|
+
const colors = colourChannels(tinted, spec);
|
|
1387
|
+
const { channels, held } = clockChannels(moved, spec, props);
|
|
1388
|
+
if (!colors || held.length)
|
|
1389
|
+
return null;
|
|
1390
|
+
const any = channels.opacity || channels.transform.length || tinted.size;
|
|
1391
|
+
return any ? { ...channels, colors } : null;
|
|
1392
|
+
}
|
|
1393
|
+
/** The sides a length is written for, and the two it is written for at once. */
|
|
1394
|
+
const EDGES = ['', 'Top', 'Right', 'Bottom', 'Left', 'Horizontal', 'Vertical'];
|
|
1395
|
+
/**
|
|
1396
|
+
* The properties of a transition that resize or move a box, which native eases by laying the
|
|
1397
|
+
* view out where it ends: see `layEased`.
|
|
1398
|
+
*/
|
|
1399
|
+
const LAID_OUT = new Set([
|
|
1400
|
+
...['width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight'],
|
|
1401
|
+
...['top', 'right', 'bottom', 'left', 'flexBasis'],
|
|
1402
|
+
...EDGES.map((edge) => `margin${edge}`),
|
|
1403
|
+
]);
|
|
1404
|
+
/** Every prop that can resize or move a box, a view's own or another's. */
|
|
1405
|
+
const MOVES_BOXES = new Set([
|
|
1406
|
+
...LAID_OUT,
|
|
1407
|
+
...EDGES.map((edge) => `padding${edge}`),
|
|
1408
|
+
...['borderWidth', 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth'],
|
|
1409
|
+
...['flex', 'flexGrow', 'flexShrink', 'flexDirection', 'flexWrap', 'alignItems', 'alignSelf'],
|
|
1410
|
+
...['alignContent', 'justifyContent', 'gap', 'rowGap', 'columnGap', 'display', 'position'],
|
|
1411
|
+
...['aspectRatio', 'fontSize', 'lineHeight', 'fontFamily', 'fontWeight', 'letterSpacing'],
|
|
1412
|
+
...['numberOfLines', 'start', 'end'],
|
|
1413
|
+
]);
|
|
1414
|
+
/** The curves a layout animation has, as the timing function each is. */
|
|
1415
|
+
const LAYOUT_CURVES = [
|
|
1416
|
+
['linear', [0, 0, 1, 1]],
|
|
1417
|
+
['easeIn', [0.42, 0, 1, 1]],
|
|
1418
|
+
['easeOut', [0, 0, 0.58, 1]],
|
|
1419
|
+
['easeInEaseOut', [0.42, 0, 0.58, 1]],
|
|
1420
|
+
];
|
|
1421
|
+
/** The curve of a layout animation nearest a timing function, by where each is along the way. */
|
|
1422
|
+
function layoutCurve(easing) {
|
|
1423
|
+
const along = [0.2, 0.4, 0.6, 0.8];
|
|
1424
|
+
const apart = (curve) => along.reduce((sum, at) => sum + (bezier(curve, at) - bezier(easing, at)) ** 2, 0);
|
|
1425
|
+
let nearest = LAYOUT_CURVES[0];
|
|
1426
|
+
for (const curve of LAYOUT_CURVES)
|
|
1427
|
+
if (apart(curve[1]) < apart(nearest[1]))
|
|
1428
|
+
nearest = curve;
|
|
1429
|
+
return nearest[0];
|
|
1430
|
+
}
|
|
1431
|
+
/** The properties of a transition native can play: see `easeNatively`. */
|
|
1432
|
+
const EASED_COLOURS = new Set([
|
|
1433
|
+
'backgroundColor',
|
|
1434
|
+
'borderColor',
|
|
1435
|
+
'borderTopColor',
|
|
1436
|
+
'borderRightColor',
|
|
1437
|
+
'borderBottomColor',
|
|
1438
|
+
'borderLeftColor',
|
|
1439
|
+
]);
|
|
1440
|
+
const NATIVELY_EASED = new Set(['opacity', 'transform', ...EASED_COLOURS]);
|
|
1322
1441
|
/** The style keys that are a view's background. */
|
|
1323
1442
|
const PAINT_KEYS = [
|
|
1324
1443
|
'backgroundColor',
|
|
@@ -2579,6 +2698,7 @@ export class Engine {
|
|
|
2579
2698
|
// as. Native gives a view one parent for its life, and aborts on a second.
|
|
2580
2699
|
if (node.committed)
|
|
2581
2700
|
this.forgetCommitted(node);
|
|
2701
|
+
this.stopEased(node);
|
|
2582
2702
|
node.transitions = undefined;
|
|
2583
2703
|
if (node.playing)
|
|
2584
2704
|
this.stopNative(node, node.playing);
|
|
@@ -2624,7 +2744,10 @@ export class Engine {
|
|
|
2624
2744
|
// inline `direction` is the exception, because the paragraphs below it align by it.
|
|
2625
2745
|
// And a child that takes one of this node's own values, an `inherit`, follows it.
|
|
2626
2746
|
const heirs = node.styleCache?.heirs === true;
|
|
2627
|
-
|
|
2747
|
+
// And only a prop some selector names: `aria-valuenow` on every tick of a progress bar, a
|
|
2748
|
+
// field's text on every key, match nothing they did not.
|
|
2749
|
+
const read = key === 'style' ? this.inlineReachesStyle(node) : this.styles.reads(key);
|
|
2750
|
+
this.markProps(node, read || heirs);
|
|
2628
2751
|
}
|
|
2629
2752
|
/**
|
|
2630
2753
|
* Paint a node's background on one of its children instead of on the node: the node keeps its
|
|
@@ -2934,11 +3057,15 @@ export class Engine {
|
|
|
2934
3057
|
// Faces registered before the walk, the global sheet's among them, reach every node in it.
|
|
2935
3058
|
this.facesAdded = false;
|
|
2936
3059
|
const set = this.fabric.createChildSet(this.rootTag);
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3060
|
+
let handles = this.rootHandles();
|
|
3061
|
+
// Again, where transitions it laid out where they end are to be eased from JavaScript.
|
|
3062
|
+
if (this.layOut()) {
|
|
3063
|
+
handles = this.rootHandles();
|
|
3064
|
+
// What that found resized is the transitions themselves, at the frame they are eased to.
|
|
3065
|
+
this.movesOthers = false;
|
|
2941
3066
|
}
|
|
3067
|
+
for (const handle of handles)
|
|
3068
|
+
this.fabric.appendChildToSet(set, handle);
|
|
2942
3069
|
this.clearFlags(this.root);
|
|
2943
3070
|
this.announceCommit();
|
|
2944
3071
|
this.fabric.completeRoot(this.rootTag, set);
|
|
@@ -3425,7 +3552,7 @@ export class Engine {
|
|
|
3425
3552
|
if (spec.timeline)
|
|
3426
3553
|
return this.scrollAnimated(node, spec, frames, props);
|
|
3427
3554
|
this.startPlaying(node, spec, frames, props);
|
|
3428
|
-
return Object.assign(props, node
|
|
3555
|
+
return Object.assign(props, playedOver(node));
|
|
3429
3556
|
}
|
|
3430
3557
|
/**
|
|
3431
3558
|
* A clock animation the scroll plays instead: the same animation where only the timeline moved,
|
|
@@ -3451,7 +3578,7 @@ export class Engine {
|
|
|
3451
3578
|
if (running && !running.done)
|
|
3452
3579
|
this.emitTransition(node, 'topAnimationcancel', running.spec.name);
|
|
3453
3580
|
}
|
|
3454
|
-
/** Stop on native each animation whose view has left the tree. */
|
|
3581
|
+
/** Stop on native each animation and transition whose view has left the tree. */
|
|
3455
3582
|
releasePlayedNatively() {
|
|
3456
3583
|
for (const node of [...this.playedNatively]) {
|
|
3457
3584
|
if (this.topOf(node) === this.root)
|
|
@@ -3460,6 +3587,10 @@ export class Engine {
|
|
|
3460
3587
|
node.playing = undefined;
|
|
3461
3588
|
this.markProps(node, false);
|
|
3462
3589
|
}
|
|
3590
|
+
for (const node of [...this.easedNatively]) {
|
|
3591
|
+
if (this.topOf(node) !== this.root)
|
|
3592
|
+
this.stopEased(node);
|
|
3593
|
+
}
|
|
3463
3594
|
}
|
|
3464
3595
|
/** Every node whose animation native is playing, with no frame of it in JavaScript. */
|
|
3465
3596
|
playedNatively = new Set();
|
|
@@ -3531,7 +3662,9 @@ export class Engine {
|
|
|
3531
3662
|
const sized = this.scrollDriver ? this.sized(node, running, props) : null;
|
|
3532
3663
|
if (!this.scrollDriver || !sized)
|
|
3533
3664
|
return false;
|
|
3534
|
-
const
|
|
3665
|
+
const pinned = pinnedIn(node, sized.tracks);
|
|
3666
|
+
const tracks = unpinned(sized.tracks, pinned);
|
|
3667
|
+
const { channels, held, span } = clockChannels(tracks, spec, sized.resting);
|
|
3535
3668
|
if (held.length || !(channels.opacity || channels.transform.length))
|
|
3536
3669
|
return false;
|
|
3537
3670
|
// There and back is one native animation: an odd number of ways has no whole number of them.
|
|
@@ -3544,6 +3677,7 @@ export class Engine {
|
|
|
3544
3677
|
if (!native)
|
|
3545
3678
|
return false;
|
|
3546
3679
|
running.native = native;
|
|
3680
|
+
running.pinned = pinned;
|
|
3547
3681
|
// As Animated does: Fabric flattens a view that only lays out, and then there is no native
|
|
3548
3682
|
// view for the animation to move.
|
|
3549
3683
|
running.values = { ...running.values, collapsable: false };
|
|
@@ -3620,8 +3754,9 @@ export class Engine {
|
|
|
3620
3754
|
this.emitTransition(node, 'topAnimationstart', spec.name);
|
|
3621
3755
|
return;
|
|
3622
3756
|
}
|
|
3623
|
-
// A view made again is another view: the one native was moving is gone.
|
|
3624
|
-
|
|
3757
|
+
// A view made again is another view: the one native was moving is gone. And one whose
|
|
3758
|
+
// important declarations changed is moving what it now must not, or not what it now may.
|
|
3759
|
+
if (node.committed === null || pinsChanged(node, current))
|
|
3625
3760
|
this.backToScript(node, current);
|
|
3626
3761
|
if (this.playedFrames.get(current) !== frames || inherited !== current.inherited) {
|
|
3627
3762
|
this.backToScript(node, current);
|
|
@@ -3691,25 +3826,28 @@ export class Engine {
|
|
|
3691
3826
|
scrollAnimated(node, spec, frames, props) {
|
|
3692
3827
|
const current = node.scrolled;
|
|
3693
3828
|
const inherited = this.inheritedColour(node, frames);
|
|
3694
|
-
|
|
3829
|
+
const same = current?.frames === frames && sameAnimation(current.spec, spec);
|
|
3830
|
+
if (same && pinnedIn(node, current.tracks).join(' ') === current.pinned.join(' ')) {
|
|
3695
3831
|
return Object.assign(props, this.rescrolled(node, current, frames, props, inherited));
|
|
3696
3832
|
}
|
|
3697
3833
|
this.stopScrolled(node);
|
|
3698
3834
|
this.handOverToScroll(node, spec);
|
|
3699
3835
|
const tracks = tracksOf(frames, props, inherited);
|
|
3836
|
+
const pinned = pinnedIn(node, tracks);
|
|
3837
|
+
const played = unpinned(tracks, pinned);
|
|
3700
3838
|
const source = this.scrollSourceOf(node);
|
|
3701
3839
|
const extent = source ? (this.scrollExtents.get(source)?.[spec.timeline] ?? null) : null;
|
|
3702
3840
|
const range = rangeOf(spec, extent);
|
|
3703
|
-
const first = firstFrame(
|
|
3841
|
+
const first = firstFrame(played, spec, range);
|
|
3704
3842
|
const resting = { ...props };
|
|
3705
3843
|
if (!source && this.dev)
|
|
3706
3844
|
this.reportUnscrolled(spec.name);
|
|
3707
|
-
const drive = source ? this.driveScrolled(node, source, spec,
|
|
3845
|
+
const drive = source ? this.driveScrolled(node, source, spec, played, resting, range) : null;
|
|
3708
3846
|
// As Animated does: Fabric flattens a view that only lays out, and then there is no native
|
|
3709
3847
|
// view for the animation to move. Committed with the first frame, so it stays put.
|
|
3710
3848
|
if (drive)
|
|
3711
3849
|
first['collapsable'] = false;
|
|
3712
|
-
node.scrolled = { spec, frames, tracks, resting, first, source, drive, inherited };
|
|
3850
|
+
node.scrolled = { spec, frames, tracks, pinned, resting, first, source, drive, inherited };
|
|
3713
3851
|
return Object.assign(props, first);
|
|
3714
3852
|
}
|
|
3715
3853
|
/**
|
|
@@ -3786,7 +3924,7 @@ export class Engine {
|
|
|
3786
3924
|
if (!scrolled?.drive)
|
|
3787
3925
|
continue;
|
|
3788
3926
|
const range = rangeOf(scrolled.spec, extent[scrolled.spec.timeline]);
|
|
3789
|
-
scrolled.drive.update(scrollChannels(scrolled.tracks, scrolled.spec, scrolled.resting, range).channels);
|
|
3927
|
+
scrolled.drive.update(scrollChannels(unpinned(scrolled.tracks, scrolled.pinned), scrolled.spec, scrolled.resting, range).channels);
|
|
3790
3928
|
}
|
|
3791
3929
|
}
|
|
3792
3930
|
reportUnscrolled(name) {
|
|
@@ -3812,24 +3950,290 @@ export class Engine {
|
|
|
3812
3950
|
`has loaded, so the block has to exist somewhere by the time the animation starts.`);
|
|
3813
3951
|
}
|
|
3814
3952
|
transitioned(node, props) {
|
|
3815
|
-
|
|
3816
|
-
if (node.bornIn >= this.unseen)
|
|
3817
|
-
node.transitions = undefined;
|
|
3953
|
+
this.unseenTransitions(node);
|
|
3818
3954
|
const spec = transitionSpec(props);
|
|
3819
3955
|
if (!spec && !node.transitions)
|
|
3820
3956
|
return props;
|
|
3957
|
+
if (spec)
|
|
3958
|
+
keepForTransition(node, props, spec);
|
|
3821
3959
|
const state = (node.transitions ??= new Map());
|
|
3822
3960
|
const now = this.now();
|
|
3961
|
+
this.takeBackEased(node, state, props, spec, now);
|
|
3962
|
+
this.takeBackLaid(state, props, spec, now);
|
|
3963
|
+
const started = [];
|
|
3823
3964
|
for (const key of steppedKeys(props, spec, state)) {
|
|
3824
3965
|
if (step(state, key, props, spec?.[key] ?? spec?.['all'], now)) {
|
|
3825
3966
|
this.running.add(node);
|
|
3967
|
+
started.push(key);
|
|
3826
3968
|
this.emitTransition(node, 'topTransitionstart', key);
|
|
3827
3969
|
}
|
|
3828
3970
|
}
|
|
3971
|
+
this.easeNatively(node, state, started, props, now);
|
|
3829
3972
|
if (state.size === 0)
|
|
3830
3973
|
node.transitions = undefined;
|
|
3831
3974
|
return props;
|
|
3832
3975
|
}
|
|
3976
|
+
/** Nobody saw what the last commit gave a view it created, so this is where the view starts. */
|
|
3977
|
+
unseenTransitions(node) {
|
|
3978
|
+
if (node.bornIn < this.unseen)
|
|
3979
|
+
return;
|
|
3980
|
+
this.stopEased(node);
|
|
3981
|
+
node.transitions = undefined;
|
|
3982
|
+
}
|
|
3983
|
+
/** The elements native is playing a transition of. */
|
|
3984
|
+
easedNatively = new Set();
|
|
3985
|
+
/**
|
|
3986
|
+
* Hand to native each transition that just started and that it can play: opacity and
|
|
3987
|
+
* transforms, with no wait before they start. Those that start together, take as long and ease
|
|
3988
|
+
* alike share a clock. Nothing of them runs in JavaScript until native says they have ended:
|
|
3989
|
+
* a press that fades or slides something is a commit to start it and a commit to end it.
|
|
3990
|
+
*
|
|
3991
|
+
* ponytail: a colour is eased from JavaScript still, a commit a frame, and so is anything
|
|
3992
|
+
* that waits. Native has a colour node for the first; give the driver a channel for it.
|
|
3993
|
+
*/
|
|
3994
|
+
easeNatively(node, state, started, props, now) {
|
|
3995
|
+
if (started.length)
|
|
3996
|
+
this.startEased(node, state, started, props, now);
|
|
3997
|
+
this.layEased(node, state, started, props, now);
|
|
3998
|
+
const inScript = [...state.values()].some((eased) => !eased.done && !eased.native && !eased.laid);
|
|
3999
|
+
if (!inScript)
|
|
4000
|
+
this.running.delete(node);
|
|
4001
|
+
// As Animated does: Fabric flattens a view that only lays out, and then there is no native
|
|
4002
|
+
// view for a transition to move.
|
|
4003
|
+
if (this.easedNatively.has(node))
|
|
4004
|
+
props['collapsable'] = false;
|
|
4005
|
+
}
|
|
4006
|
+
/** The root's children as the views they are committed as, each reconciled. */
|
|
4007
|
+
rootHandles() {
|
|
4008
|
+
const handles = [];
|
|
4009
|
+
for (const child of this.visibleChildren(this.root)) {
|
|
4010
|
+
if (!this.withheld(child))
|
|
4011
|
+
handles.push(this.reconcileUnder(this.root, child));
|
|
4012
|
+
}
|
|
4013
|
+
return handles;
|
|
4014
|
+
}
|
|
4015
|
+
/** The transitions this commit lays out where they end, for native to move the views to. */
|
|
4016
|
+
laying = [];
|
|
4017
|
+
/** Whether this commit resizes or moves a box that is in no such transition. */
|
|
4018
|
+
movesOthers = false;
|
|
4019
|
+
/**
|
|
4020
|
+
* Commit where it ends each transition of a size or a place that just started, for native to
|
|
4021
|
+
* move the view there: a layout animation, which the commit asks for once it is known that
|
|
4022
|
+
* nothing else in it moves. One still on its way is committed where it ends too.
|
|
4023
|
+
*
|
|
4024
|
+
* A width eased from JavaScript is a commit a frame, and a commit lays the whole screen out.
|
|
4025
|
+
*/
|
|
4026
|
+
layEased(node, state, started, props, now) {
|
|
4027
|
+
if (!this.fabric.configureNextLayoutAnimation)
|
|
4028
|
+
return;
|
|
4029
|
+
for (const key of started) {
|
|
4030
|
+
const eased = state.get(key);
|
|
4031
|
+
if (!LAID_OUT.has(key) || eased.start > now || eased.duration <= 0)
|
|
4032
|
+
continue;
|
|
4033
|
+
eased.laid = true;
|
|
4034
|
+
this.laying.push({ node, key, eased });
|
|
4035
|
+
}
|
|
4036
|
+
for (const [key, eased] of state)
|
|
4037
|
+
if (eased.laid && !eased.done)
|
|
4038
|
+
props[key] = eased.to;
|
|
4039
|
+
}
|
|
4040
|
+
/**
|
|
4041
|
+
* Before a commit is handed over: ask native to move what it lays out, or take the
|
|
4042
|
+
* transitions back to ease them from JavaScript where the commit moves anything else, and
|
|
4043
|
+
* where they would not all take as long. Answers whether the tree has to be reconciled again.
|
|
4044
|
+
*/
|
|
4045
|
+
layOut() {
|
|
4046
|
+
const laying = this.laying;
|
|
4047
|
+
const others = this.movesOthers;
|
|
4048
|
+
this.laying = [];
|
|
4049
|
+
this.movesOthers = false;
|
|
4050
|
+
if (!laying.length)
|
|
4051
|
+
return false;
|
|
4052
|
+
const [first] = laying;
|
|
4053
|
+
const alike = laying.every(({ eased }) => eased.duration === first.eased.duration);
|
|
4054
|
+
if (others || !alike) {
|
|
4055
|
+
for (const { node, eased } of laying) {
|
|
4056
|
+
eased.laid = undefined;
|
|
4057
|
+
this.running.add(node);
|
|
4058
|
+
}
|
|
4059
|
+
for (const { node } of laying)
|
|
4060
|
+
this.markProps(node, false);
|
|
4061
|
+
return true;
|
|
4062
|
+
}
|
|
4063
|
+
const { duration, easing } = first.eased;
|
|
4064
|
+
const nothing = () => undefined;
|
|
4065
|
+
this.fabric.configureNextLayoutAnimation({ duration, update: { type: layoutCurve(easing) } }, nothing, nothing);
|
|
4066
|
+
for (const { node, key, eased } of laying) {
|
|
4067
|
+
setTimeout(() => this.endLaid(node, key, eased), duration);
|
|
4068
|
+
}
|
|
4069
|
+
return false;
|
|
4070
|
+
}
|
|
4071
|
+
/**
|
|
4072
|
+
* Note a commit that resizes or moves a box in no transition native lays out: a prop of one
|
|
4073
|
+
* that moves boxes, children that came or went, text longer or shorter than it was.
|
|
4074
|
+
*/
|
|
4075
|
+
noteMoved(node, payload, resized, words) {
|
|
4076
|
+
if (this.movesOthers)
|
|
4077
|
+
return;
|
|
4078
|
+
if (resized)
|
|
4079
|
+
this.movesOthers = true;
|
|
4080
|
+
for (const key in payload) {
|
|
4081
|
+
const eased = node.transitions?.get(key);
|
|
4082
|
+
if (eased?.laid && !eased.done)
|
|
4083
|
+
continue;
|
|
4084
|
+
// Text as long as it was is taken to be as wide, as a grid takes it: see `shapeOf`.
|
|
4085
|
+
const longer = key === 'text' && String(payload[key]).length !== String(words).length;
|
|
4086
|
+
if (MOVES_BOXES.has(key) || longer)
|
|
4087
|
+
this.movesOthers = true;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
/** A transition native laid out has had its time: it is where it ends, and says so. */
|
|
4091
|
+
endLaid(node, key, eased) {
|
|
4092
|
+
if (!eased.laid || eased.done || node.transitions?.get(key) !== eased)
|
|
4093
|
+
return;
|
|
4094
|
+
Object.assign(eased, { current: eased.to, done: true, laid: undefined });
|
|
4095
|
+
this.emitTransition(node, 'topTransitionend', key);
|
|
4096
|
+
// No commit comes of it, which is what tells listeners otherwise: it is where it ends.
|
|
4097
|
+
this.flushTransitionEvents();
|
|
4098
|
+
// And what a listener did, taking away an element that has left, is committed.
|
|
4099
|
+
if (this.pending)
|
|
4100
|
+
this.commit();
|
|
4101
|
+
}
|
|
4102
|
+
/**
|
|
4103
|
+
* Bring a transition native laid out to where its clock has got to, where it is sent another
|
|
4104
|
+
* way or its rule has gone: that is where it goes on from.
|
|
4105
|
+
*/
|
|
4106
|
+
takeBackLaid(state, props, spec, now) {
|
|
4107
|
+
for (const [key, eased] of state) {
|
|
4108
|
+
if (!eased.laid || eased.done)
|
|
4109
|
+
continue;
|
|
4110
|
+
const kept = spec?.[key] ?? spec?.['all'];
|
|
4111
|
+
if (kept && !aimsElsewhere(eased, key, props))
|
|
4112
|
+
continue;
|
|
4113
|
+
const progress = Math.min(1, Math.max(0, (now - eased.start) / eased.duration));
|
|
4114
|
+
eased.current = interpolate(eased.from, eased.to, bezier(eased.easing, progress));
|
|
4115
|
+
eased.laid = undefined;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
startEased(node, state, started, props, now) {
|
|
4119
|
+
if (!this.scrollDriver)
|
|
4120
|
+
return;
|
|
4121
|
+
const clocks = new Map();
|
|
4122
|
+
for (const key of started) {
|
|
4123
|
+
const eased = state.get(key);
|
|
4124
|
+
if (!NATIVELY_EASED.has(key) || eased.start > now || eased.duration <= 0)
|
|
4125
|
+
continue;
|
|
4126
|
+
const clock = `${eased.duration} ${eased.easing.join()}`;
|
|
4127
|
+
clocks.set(clock, [...(clocks.get(clock) ?? []), key]);
|
|
4128
|
+
}
|
|
4129
|
+
for (const keys of clocks.values())
|
|
4130
|
+
this.playEased(node, state, keys, props);
|
|
4131
|
+
}
|
|
4132
|
+
playEased(node, state, keys, props) {
|
|
4133
|
+
const first = state.get(keys[0]);
|
|
4134
|
+
const tracks = new Map(keys.map((key) => {
|
|
4135
|
+
const { current, to, easing } = state.get(key);
|
|
4136
|
+
return [
|
|
4137
|
+
key,
|
|
4138
|
+
[
|
|
4139
|
+
{ offset: 0, value: current, easing },
|
|
4140
|
+
{ offset: 1, value: to },
|
|
4141
|
+
],
|
|
4142
|
+
];
|
|
4143
|
+
}));
|
|
4144
|
+
const spec = {
|
|
4145
|
+
name: '',
|
|
4146
|
+
duration: first.duration,
|
|
4147
|
+
delay: 0,
|
|
4148
|
+
easing: first.easing,
|
|
4149
|
+
iterations: 1,
|
|
4150
|
+
fill: 'both',
|
|
4151
|
+
};
|
|
4152
|
+
const channels = easedChannels(tracks, spec, props);
|
|
4153
|
+
if (!channels)
|
|
4154
|
+
return;
|
|
4155
|
+
const length = Math.max(2, Math.round(first.duration / NATIVE_FRAME) + 1);
|
|
4156
|
+
const frames = Array.from({ length }, (_, i) => i / (length - 1));
|
|
4157
|
+
const clock = { keys, stop: () => undefined };
|
|
4158
|
+
const native = this.scrollDriver.play(node, channels, { frames, toValue: 1, iterations: 1 }, (view) => this.tagOf(view), () => this.endedEased(node, clock));
|
|
4159
|
+
if (!native)
|
|
4160
|
+
return;
|
|
4161
|
+
clock.stop = () => native.stop();
|
|
4162
|
+
for (const key of keys)
|
|
4163
|
+
state.get(key).native = clock;
|
|
4164
|
+
this.easedNatively.add(node);
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Native reached the end of the transitions on one clock. Where they end is committed before
|
|
4168
|
+
* the view is let go, so nothing shows of where they started, which is what it was committed
|
|
4169
|
+
* with.
|
|
4170
|
+
*/
|
|
4171
|
+
endedEased(node, clock) {
|
|
4172
|
+
const state = node.transitions;
|
|
4173
|
+
const keys = clock.keys.filter((key) => state?.get(key)?.native === clock);
|
|
4174
|
+
for (const key of keys) {
|
|
4175
|
+
const eased = state.get(key);
|
|
4176
|
+
Object.assign(eased, { current: eased.to, done: true, native: undefined });
|
|
4177
|
+
this.emitTransition(node, 'topTransitionend', key);
|
|
4178
|
+
this.markProps(node, false);
|
|
4179
|
+
}
|
|
4180
|
+
this.forgetEased(node);
|
|
4181
|
+
if (keys.length)
|
|
4182
|
+
this.commit();
|
|
4183
|
+
clock.stop();
|
|
4184
|
+
}
|
|
4185
|
+
/** No longer one native is easing, once none of its transitions is native's. */
|
|
4186
|
+
forgetEased(node) {
|
|
4187
|
+
for (const eased of node.transitions?.values() ?? [])
|
|
4188
|
+
if (eased.native)
|
|
4189
|
+
return;
|
|
4190
|
+
this.easedNatively.delete(node);
|
|
4191
|
+
}
|
|
4192
|
+
/**
|
|
4193
|
+
* Take back from native each transition it can no longer play as it was started: one sent
|
|
4194
|
+
* another way, one whose rule has gone, any of a view that was made again. Each is brought to
|
|
4195
|
+
* where its clock has got to, which is where it goes on from, here or on native again.
|
|
4196
|
+
*/
|
|
4197
|
+
takeBackEased(node, state, props, spec, now) {
|
|
4198
|
+
if (!this.easedNatively.has(node))
|
|
4199
|
+
return;
|
|
4200
|
+
const remade = node.committed === null;
|
|
4201
|
+
for (const [key, eased] of state) {
|
|
4202
|
+
if (!eased.native)
|
|
4203
|
+
continue;
|
|
4204
|
+
const kept = spec?.[key] ?? spec?.['all'];
|
|
4205
|
+
if (remade || !kept || aimsElsewhere(eased, key, props)) {
|
|
4206
|
+
this.backToScriptEased(node, eased.native, now);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
/** Stop a clock native runs, and go on from JavaScript with each transition it was playing. */
|
|
4211
|
+
backToScriptEased(node, clock, now) {
|
|
4212
|
+
for (const key of clock.keys) {
|
|
4213
|
+
const eased = node.transitions?.get(key);
|
|
4214
|
+
if (eased?.native !== clock)
|
|
4215
|
+
continue;
|
|
4216
|
+
eased.native = undefined;
|
|
4217
|
+
const progress = Math.min(1, Math.max(0, (now - eased.start) / eased.duration));
|
|
4218
|
+
eased.current = interpolate(eased.from, eased.to, bezier(eased.easing, progress));
|
|
4219
|
+
this.running.add(node);
|
|
4220
|
+
}
|
|
4221
|
+
clock.stop();
|
|
4222
|
+
this.forgetEased(node);
|
|
4223
|
+
}
|
|
4224
|
+
/** Let go of what native is easing on an element that is going, or gone. */
|
|
4225
|
+
stopEased(node) {
|
|
4226
|
+
if (!this.easedNatively.delete(node))
|
|
4227
|
+
return;
|
|
4228
|
+
const clocks = new Set();
|
|
4229
|
+
for (const eased of node.transitions?.values() ?? []) {
|
|
4230
|
+
if (eased.native)
|
|
4231
|
+
clocks.add(eased.native);
|
|
4232
|
+
eased.native = undefined;
|
|
4233
|
+
}
|
|
4234
|
+
for (const clock of clocks)
|
|
4235
|
+
clock.stop();
|
|
4236
|
+
}
|
|
3833
4237
|
/**
|
|
3834
4238
|
* Whether an animation that has finished goes on holding its last frame. With a forwards fill
|
|
3835
4239
|
* it does, for good: without one the element falls back to whatever the cascade gives it, and
|
|
@@ -4101,6 +4505,7 @@ export class Engine {
|
|
|
4101
4505
|
const props = propsMoved ? this.mergeProps(node, viewName) : previous.props;
|
|
4102
4506
|
const propsPayload = propsMoved ? this.propsPayload(node, previous.props, props) : null;
|
|
4103
4507
|
const childrenChanged = this.childrenMoved(node, childHandles, previous.childHandles);
|
|
4508
|
+
this.noteMoved(node, propsPayload, childHandles.length !== previous.childHandles.length, previous.props['text']);
|
|
4104
4509
|
let handle = previous.handle;
|
|
4105
4510
|
if (childrenChanged || propsPayload) {
|
|
4106
4511
|
handle = this.clone(previous.handle, propsPayload && this.processed(propsPayload), childrenChanged ? childHandles : null);
|