@ng-native/fabric 0.14.0 → 0.15.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 +47 -5
- package/dist/css.js +219 -51
- package/dist/css.js.map +1 -1
- package/dist/defer-triggers.js +2 -8
- package/dist/defer-triggers.js.map +1 -1
- package/dist/engine.d.ts +62 -17
- package/dist/engine.js +222 -51
- package/dist/engine.js.map +1 -1
- package/dist/fabric.d.ts +1 -1
- package/dist/fabric.js +25 -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/inline-derived-token.js +56 -7
- package/dist/inline-derived-token.js.map +1 -1
- package/dist/node-global.d.ts +9 -0
- package/dist/node-global.js +41 -0
- package/dist/node-global.js.map +1 -0
- package/dist/relative-colour.d.ts +2 -2
- package/dist/relative-colour.js +4 -0
- package/dist/relative-colour.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* because a persistent parent holds references to specific child handles. That bubble is
|
|
8
8
|
* inherent and is what React's own Fabric renderer does.
|
|
9
9
|
*/
|
|
10
|
-
import { boundDeclaration, boundRelativeLength, CSS_SPACE, isNegativeLineHeight, faded, fills, inlineInherited, setsInherited, StyleResolver, usesActive, elementsAtEnd, hasAbove, placeReadElsewhere, siblingReach, } from "./css.js";
|
|
10
|
+
import { boundDeclaration, boundRelativeLength, CSS_SPACE, isNegativeLineHeight, faded, fills, inlineInherited, setsInherited, StyleResolver, usesActive, elementsAtEnd, hasAbove, joinSubjects, placeReadElsewhere, siblingReach, } from "./css.js";
|
|
11
11
|
import { applyAria } from "./aria-props.js";
|
|
12
12
|
import { STYLED_ELEMENTS } from "./element-styles.js";
|
|
13
13
|
import { aimsElsewhere, animationEvent, bezier, interpolate, readsInheritedColour, sample, step, steppedKeys, tracksOf, } from "./transition.js";
|
|
@@ -189,6 +189,13 @@ const VIEW_EVENT_OPT_IN_PROPS = {
|
|
|
189
189
|
topError: 'shouldNotifyLoadEvents',
|
|
190
190
|
topProgress: 'shouldNotifyLoadEvents',
|
|
191
191
|
},
|
|
192
|
+
// `ReactTextInputManager` watches each of these only behind a prop of the handler's name,
|
|
193
|
+
// which `TextInput.js` sets by passing the handler through. iOS's field sends them unasked.
|
|
194
|
+
AndroidTextInput: {
|
|
195
|
+
topContentSizeChange: 'onContentSizeChange',
|
|
196
|
+
topScroll: 'onScroll',
|
|
197
|
+
topKeyPress: 'onKeyPress',
|
|
198
|
+
},
|
|
192
199
|
};
|
|
193
200
|
/**
|
|
194
201
|
* Props the engine itself writes or reads off a node, so never a misspelling: the style, the
|
|
@@ -461,10 +468,14 @@ const blockEdge = (props, side, logical) => [
|
|
|
461
468
|
/**
|
|
462
469
|
* A multiline field's `rows` as its height, where it was given none: that many lines of its
|
|
463
470
|
* line height at the system text size, inside its padding and border, as a browser sizes a `<textarea>`. A native field
|
|
464
|
-
* is as tall as its text, which is one line before anything is typed.
|
|
471
|
+
* is as tall as its text, which is one line before anything is typed. Taken off the props once
|
|
472
|
+
* read: neither platform's native field has a prop of that name.
|
|
465
473
|
*/
|
|
466
|
-
function rowsTall(props, fontScale) {
|
|
474
|
+
function rowsTall(viewName, props, fontScale) {
|
|
475
|
+
if (!TEXT_INPUTS.has(viewName))
|
|
476
|
+
return;
|
|
467
477
|
const rows = Number(props['rows']);
|
|
478
|
+
delete props['rows'];
|
|
468
479
|
const line = props['lineHeight'];
|
|
469
480
|
if (props['multiline'] !== true || !(rows > 0) || typeof line !== 'number')
|
|
470
481
|
return;
|
|
@@ -837,6 +848,12 @@ function placeholderFaded(props) {
|
|
|
837
848
|
else if (opacity <= 0)
|
|
838
849
|
props['placeholderTextColor'] = 'rgba(0, 0, 0, 0)';
|
|
839
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* What a removed prop travels as: null, which native resets the prop on, except `pointerEvents`,
|
|
853
|
+
* which react-native-svg's Android root reads as a string and stops the app on a null. `auto` is
|
|
854
|
+
* what no value means.
|
|
855
|
+
*/
|
|
856
|
+
const cleared = (key) => (key === 'pointerEvents' ? 'auto' : null);
|
|
840
857
|
/** The least alpha iOS sends a touch to a view at: under a hundredth it is passed over. */
|
|
841
858
|
const TOUCHED_ALPHA = 0.011;
|
|
842
859
|
/**
|
|
@@ -1779,6 +1796,8 @@ const EASED_COLOURS = new Set([
|
|
|
1779
1796
|
'borderLeftColor',
|
|
1780
1797
|
]);
|
|
1781
1798
|
const NATIVELY_EASED = new Set(['opacity', 'transform', ...EASED_COLOURS]);
|
|
1799
|
+
/** What a node that hands its background colour over has handed so far: nothing. */
|
|
1800
|
+
const UNREPORTED = Symbol('unreported');
|
|
1782
1801
|
/** The style keys that are a view's background. */
|
|
1783
1802
|
const PAINT_KEYS = [
|
|
1784
1803
|
'backgroundColor',
|
|
@@ -1787,6 +1806,15 @@ const PAINT_KEYS = [
|
|
|
1787
1806
|
'experimental_backgroundPosition',
|
|
1788
1807
|
'experimental_backgroundRepeat',
|
|
1789
1808
|
];
|
|
1809
|
+
/** Note a prop written on a node that keeps `writes`: see there. */
|
|
1810
|
+
function noteWrite(node, key, value) {
|
|
1811
|
+
if (!node.writes)
|
|
1812
|
+
return;
|
|
1813
|
+
if (value === undefined || value === null)
|
|
1814
|
+
node.writes.delete(key);
|
|
1815
|
+
else
|
|
1816
|
+
node.writes.add(key);
|
|
1817
|
+
}
|
|
1790
1818
|
/**
|
|
1791
1819
|
* A node's own props onto what it commits with. No native prop has a hyphen: `data-*` and `aria-*`
|
|
1792
1820
|
* attributes stay on the node for selectors to match, and an `aria-*` one is mapped to the prop
|
|
@@ -1953,9 +1981,14 @@ function reportHover(host) {
|
|
|
1953
1981
|
hoverReported = true;
|
|
1954
1982
|
console.warn('[angular-native] @defer (on hover) fires when a pointer enters the trigger, which takes a trackpad or a mouse (an iPad, or an Android device with one), or when the trigger takes focus. On a phone it waits for focus alone; add another trigger, such as on interaction or on viewport.');
|
|
1955
1983
|
}
|
|
1956
|
-
/** A bound value
|
|
1984
|
+
/** A bound value the cascade works out from tokens: a `var()`, a `color-mix()`, a relative colour. */
|
|
1985
|
+
const BOUND_TOKEN = /^[ \t\n\r\f]*(?:var|color-mix)\(|^[ \t\n\r\f]*[a-z]+\([ \t\n\r\f]*from[ \t\n\r\f]/i;
|
|
1986
|
+
/**
|
|
1987
|
+
* A bound value kept as a declaration the cascade settles: a `var()`, a colour made of tokens, or
|
|
1988
|
+
* a relative length.
|
|
1989
|
+
*/
|
|
1957
1990
|
function boundFrom(prop, text, on) {
|
|
1958
|
-
return
|
|
1991
|
+
return BOUND_TOKEN.test(text)
|
|
1959
1992
|
? boundDeclaration(prop, tokenFromValue(text), on)
|
|
1960
1993
|
: boundRelativeLength(prop, text, on);
|
|
1961
1994
|
}
|
|
@@ -2344,6 +2377,26 @@ function applyIntrinsicSize(props, size) {
|
|
|
2344
2377
|
}
|
|
2345
2378
|
}
|
|
2346
2379
|
const now = () => globalThis.performance?.now?.() ?? Date.now();
|
|
2380
|
+
/** How many boxes `node` is under. */
|
|
2381
|
+
/**
|
|
2382
|
+
* How deep a node is, with the depth of every box on its way up kept in `known`: the boxes that
|
|
2383
|
+
* changed beneath are a node and its ancestors, so each is counted once, not once a box.
|
|
2384
|
+
*/
|
|
2385
|
+
function depthOf(node, known) {
|
|
2386
|
+
const path = [];
|
|
2387
|
+
let depth = -1;
|
|
2388
|
+
for (let up = node; up; up = up.parent) {
|
|
2389
|
+
const found = known.get(up);
|
|
2390
|
+
if (found !== undefined) {
|
|
2391
|
+
depth = found;
|
|
2392
|
+
break;
|
|
2393
|
+
}
|
|
2394
|
+
path.push(up);
|
|
2395
|
+
}
|
|
2396
|
+
for (let i = path.length - 1; i >= 0; i--)
|
|
2397
|
+
known.set(path[i], ++depth);
|
|
2398
|
+
return depth;
|
|
2399
|
+
}
|
|
2347
2400
|
function sameHandles(a, b) {
|
|
2348
2401
|
return a.length === b.length && a.every((handle, i) => handle === b[i]);
|
|
2349
2402
|
}
|
|
@@ -2366,6 +2419,11 @@ function couldBe(compound, node) {
|
|
|
2366
2419
|
return (compound.is ?? []).every((alternatives) => alternatives.some((option) => couldBe(option, node)));
|
|
2367
2420
|
}
|
|
2368
2421
|
const FRAME_BUDGET_MS = 8;
|
|
2422
|
+
/** A solid brush with no colour in it, which stops react-native-svg. */
|
|
2423
|
+
const emptyBrush = (value) => typeof value === 'object' &&
|
|
2424
|
+
value !== null &&
|
|
2425
|
+
value.type === 0 &&
|
|
2426
|
+
value.payload == null;
|
|
2369
2427
|
export class Engine {
|
|
2370
2428
|
/** Scopes the CSS resolver's memo. Bumped once per commit. */
|
|
2371
2429
|
styleEpoch = 0;
|
|
@@ -2819,7 +2877,7 @@ export class Engine {
|
|
|
2819
2877
|
if (sheet === undefined || node.sheet === sheet)
|
|
2820
2878
|
return;
|
|
2821
2879
|
node.sheet = sheet;
|
|
2822
|
-
this.styles.noteSheet(sheet);
|
|
2880
|
+
this.styles.noteSheet(sheet, true);
|
|
2823
2881
|
this.watchStructure(sheet);
|
|
2824
2882
|
this.watchHas(sheet);
|
|
2825
2883
|
this.watchActive(sheet);
|
|
@@ -2855,7 +2913,7 @@ export class Engine {
|
|
|
2855
2913
|
createElement(name, sheet = null) {
|
|
2856
2914
|
const node = new RetainedNode('element', name, this);
|
|
2857
2915
|
node.sheet = sheet;
|
|
2858
|
-
this.styles.noteSheet(sheet);
|
|
2916
|
+
this.styles.noteSheet(sheet, true);
|
|
2859
2917
|
const html = HTML_ELEMENTS.get(name);
|
|
2860
2918
|
if (html !== undefined) {
|
|
2861
2919
|
if (html.role)
|
|
@@ -2885,8 +2943,10 @@ export class Engine {
|
|
|
2885
2943
|
* element matched again, and one named as what a rule's element is inside, `.animating .close`
|
|
2886
2944
|
* or `.close:is(.animating *)`, the elements under the node that such rules are for. What is
|
|
2887
2945
|
* under any of those is styled again from the rules it matched, for what it inherits: a `dark`
|
|
2888
|
-
* class on the root finds no rule again for an element no `dark` rule is for.
|
|
2889
|
-
* has
|
|
2946
|
+
* class on the root finds no rule again for an element no `dark` rule is for. One named in
|
|
2947
|
+
* `:has()`, or answering a test of the class attribute there, has the boxes above the node
|
|
2948
|
+
* asked again, and one named only in `:has(> )` the box right above it. Any other class has the node and everything under it matched again, as any
|
|
2949
|
+
* other change to it does.
|
|
2890
2950
|
*
|
|
2891
2951
|
* A sheet that names a class later restyles what it is for as it arrives, and a node not yet
|
|
2892
2952
|
* committed is styled when it is, with the class as it is then.
|
|
@@ -2905,6 +2965,11 @@ export class Engine {
|
|
|
2905
2965
|
return;
|
|
2906
2966
|
if (reach.own)
|
|
2907
2967
|
node.ownDirty = true;
|
|
2968
|
+
if (this.hasSheets && reach.beneath)
|
|
2969
|
+
this.markBeneath(node.parent);
|
|
2970
|
+
// `:has(> .on)`: only the box right above the node can answer it.
|
|
2971
|
+
else if (this.hasSheets && reach.parentHas)
|
|
2972
|
+
this.markBeneath(node.parent, node.parent?.parent);
|
|
2908
2973
|
if (this.styles.reachesUnder(reach)) {
|
|
2909
2974
|
this.classesAbove += 1;
|
|
2910
2975
|
this.markUnder(node, reach);
|
|
@@ -2962,8 +3027,9 @@ export class Engine {
|
|
|
2962
3027
|
if (node.hostSheet === sheet)
|
|
2963
3028
|
return;
|
|
2964
3029
|
node.hostSheet = sheet;
|
|
2965
|
-
this.styles.noteSheet(sheet);
|
|
3030
|
+
this.styles.noteSheet(sheet, true);
|
|
2966
3031
|
this.watchStructure(sheet);
|
|
3032
|
+
this.watchHas(sheet);
|
|
2967
3033
|
this.watchActive(sheet);
|
|
2968
3034
|
this.markProps(node);
|
|
2969
3035
|
}
|
|
@@ -3253,6 +3319,7 @@ export class Engine {
|
|
|
3253
3319
|
setProp(node, key, value) {
|
|
3254
3320
|
if (!this.eases)
|
|
3255
3321
|
this.noteEasingProp(key, value);
|
|
3322
|
+
noteWrite(node, key, value);
|
|
3256
3323
|
// A prop written again is a binding's, and is committed as any other is.
|
|
3257
3324
|
if (node.attributeOnly?.delete(key))
|
|
3258
3325
|
this.markProps(node, false);
|
|
@@ -3278,7 +3345,9 @@ export class Engine {
|
|
|
3278
3345
|
// And only a prop some selector names: `aria-valuenow` on every tick of a progress bar, a
|
|
3279
3346
|
// field's text on every key, match nothing they did not.
|
|
3280
3347
|
const read = key === 'style' ? this.inlineReachesStyle(node) : this.styles.reads(key);
|
|
3281
|
-
|
|
3348
|
+
// And restyles the elements after it only where a rule asks the prop of an element before
|
|
3349
|
+
// another, `[data-on] ~ .x`: a point of a chart set as a finger moves leaves those after it.
|
|
3350
|
+
this.markProps(node, read || heirs, this.styles.readsBeside(key));
|
|
3282
3351
|
if (SIZES_A_RUN.has(key))
|
|
3283
3352
|
this.markParagraph(node);
|
|
3284
3353
|
}
|
|
@@ -3304,6 +3373,40 @@ export class Engine {
|
|
|
3304
3373
|
this.markProps(node, false);
|
|
3305
3374
|
this.markProps(child, false);
|
|
3306
3375
|
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Hand a node's background colour to `listener` instead of painting it: what its stylesheets,
|
|
3378
|
+
* tokens and inline style resolve `background-color` to, or undefined for none. Called after
|
|
3379
|
+
* each commit that changes it, the first one included. What a component needs whose colour is
|
|
3380
|
+
* drawn by a native view the node is not, from a prop of that view's, as a bottom sheet's is.
|
|
3381
|
+
* The function returned stops it, and the node paints its own background again.
|
|
3382
|
+
*/
|
|
3383
|
+
takePaint(node, listener) {
|
|
3384
|
+
node.paintTaker = listener;
|
|
3385
|
+
node.paintTaken = UNREPORTED;
|
|
3386
|
+
this.markProps(node, false);
|
|
3387
|
+
return () => {
|
|
3388
|
+
if (node.paintTaker !== listener)
|
|
3389
|
+
return;
|
|
3390
|
+
node.paintTaker = undefined;
|
|
3391
|
+
this.paintReports.delete(node);
|
|
3392
|
+
this.markProps(node, false);
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
/** Nodes whose background colour is to be handed over once the commit running ends. */
|
|
3396
|
+
paintReports = new Set();
|
|
3397
|
+
reportPaint() {
|
|
3398
|
+
const nodes = [...this.paintReports];
|
|
3399
|
+
this.paintReports.clear();
|
|
3400
|
+
// One listener that throws stops neither the rest nor what the commit does after them.
|
|
3401
|
+
for (const node of nodes) {
|
|
3402
|
+
try {
|
|
3403
|
+
node.paintTaker?.(node.paintTaken);
|
|
3404
|
+
}
|
|
3405
|
+
catch (error) {
|
|
3406
|
+
this.reportEventError(error, 'paint');
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3307
3410
|
/**
|
|
3308
3411
|
* Keep a prop the node already has as an attribute: on the node for a selector such as
|
|
3309
3412
|
* `:host([variant='primary'])` to match, and left out of what is committed to the native view.
|
|
@@ -3372,7 +3475,9 @@ export class Engine {
|
|
|
3372
3475
|
else
|
|
3373
3476
|
byProp.delete(prop);
|
|
3374
3477
|
node.boundStyle = byProp.size ? [...byProp.values()] : null;
|
|
3375
|
-
|
|
3478
|
+
// Settled by the cascade as the node is styled, and asked about by no selector: nothing after
|
|
3479
|
+
// the node matches otherwise for it.
|
|
3480
|
+
this.markProps(node, true, false);
|
|
3376
3481
|
return bound !== null || refused;
|
|
3377
3482
|
}
|
|
3378
3483
|
/** Whether a bound value is one CSS has none of, which is dropped with a warning. */
|
|
@@ -3436,7 +3541,7 @@ export class Engine {
|
|
|
3436
3541
|
return () => set.delete(fn);
|
|
3437
3542
|
}
|
|
3438
3543
|
// --- dirty marking ----------------------------------------------------
|
|
3439
|
-
markProps(node, affectsStyle = true) {
|
|
3544
|
+
markProps(node, affectsStyle = true, beside = true) {
|
|
3440
3545
|
node.propsDirty = true;
|
|
3441
3546
|
// Classes, `nativeID` and anything else a selector can read live in props, so any of them
|
|
3442
3547
|
// changing can change what this node matches, and through it what its whole subtree does.
|
|
@@ -3446,7 +3551,8 @@ export class Engine {
|
|
|
3446
3551
|
node.styleDirty = true;
|
|
3447
3552
|
// `.peer:focus ~ .label`: what a later sibling matches can hang on this node's classes and
|
|
3448
3553
|
// state, though nothing about the sibling moved. Only sheets that ask about siblings pay.
|
|
3449
|
-
|
|
3554
|
+
// Not for a change `beside` says no rule asks of an element before another.
|
|
3555
|
+
if (this.structuralSheets && beside)
|
|
3450
3556
|
this.markLaterSiblings(node);
|
|
3451
3557
|
if (this.hasSheets)
|
|
3452
3558
|
this.markBeneath(node.parent);
|
|
@@ -3474,11 +3580,12 @@ export class Engine {
|
|
|
3474
3580
|
}
|
|
3475
3581
|
/**
|
|
3476
3582
|
* Something at or beneath `from` changed: every node from there up may match a `:has()` rule
|
|
3477
|
-
* it did not, or stop matching one. They are matched again rather than
|
|
3478
|
-
* which would restyle everything under them. Only sheets that use
|
|
3583
|
+
* it did not, or stop matching one, up to `upTo` and not it. They are matched again rather than
|
|
3584
|
+
* marked for restyling, which would restyle everything under them. Only sheets that use
|
|
3585
|
+
* `:has()` pay.
|
|
3479
3586
|
*/
|
|
3480
|
-
markBeneath(from) {
|
|
3481
|
-
for (let node = from; node; node = node.parent) {
|
|
3587
|
+
markBeneath(from, upTo = null) {
|
|
3588
|
+
for (let node = from; node && node !== upTo; node = node.parent) {
|
|
3482
3589
|
node.hasDirty = true;
|
|
3483
3590
|
if (this.hasReaders.size)
|
|
3484
3591
|
this.changedBeneath.add(node);
|
|
@@ -3495,20 +3602,49 @@ export class Engine {
|
|
|
3495
3602
|
* Have matched again what is under each box that changed beneath and that such a rule could
|
|
3496
3603
|
* be asking: the nodes the rule is for, and no other. Once for a commit, however many changes
|
|
3497
3604
|
* there were beneath the box: a table gains its rows one at a time.
|
|
3605
|
+
*
|
|
3606
|
+
* A rule asked of a box above is walked under the highest box that asks it, which reaches what
|
|
3607
|
+
* is under the boxes beneath that one as well: a rule `:has()` asks of any box is asked by every
|
|
3608
|
+
* box from the change up to the root, and walked once, from the root. The rules a box asks are
|
|
3609
|
+
* walked together, the tree beneath it once.
|
|
3498
3610
|
*/
|
|
3499
3611
|
markUnderHas() {
|
|
3500
3612
|
if (!this.changedBeneath.size)
|
|
3501
3613
|
return;
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3614
|
+
const readers = [...this.hasReaders.values()].flat();
|
|
3615
|
+
const depths = new Map();
|
|
3616
|
+
for (const node of this.changedBeneath)
|
|
3617
|
+
depthOf(node, depths);
|
|
3618
|
+
const boxes = [...this.changedBeneath].sort((a, b) => depths.get(a) - depths.get(b));
|
|
3619
|
+
// By box walked, the rules walked under it and under every box above it.
|
|
3620
|
+
const walked = new Map();
|
|
3621
|
+
for (const node of boxes) {
|
|
3622
|
+
let above;
|
|
3623
|
+
for (let up = node.parent; up && !above; up = up.parent)
|
|
3624
|
+
above = walked.get(up);
|
|
3625
|
+
const all = this.markUnderBox(node, readers, above);
|
|
3626
|
+
if (all)
|
|
3627
|
+
walked.set(node, all);
|
|
3509
3628
|
}
|
|
3510
3629
|
this.changedBeneath.clear();
|
|
3511
3630
|
}
|
|
3631
|
+
/**
|
|
3632
|
+
* `markUnderHas` for one box: the rules it asks that no box above it was walked for, walked
|
|
3633
|
+
* together. What was walked under it and above it, which is `above` where it asks no more.
|
|
3634
|
+
*/
|
|
3635
|
+
markUnderBox(node, readers, above) {
|
|
3636
|
+
const asked = [];
|
|
3637
|
+
for (const reader of readers) {
|
|
3638
|
+
if (above?.has(reader) || !reader.classes.every((name) => node.classes?.has(name)))
|
|
3639
|
+
continue;
|
|
3640
|
+
asked.push(reader);
|
|
3641
|
+
}
|
|
3642
|
+
if (!asked.length)
|
|
3643
|
+
return above;
|
|
3644
|
+
const subjects = asked.map((reader) => reader.subjects);
|
|
3645
|
+
this.markUnder(node, subjects.length === 1 ? subjects[0] : joinSubjects(subjects));
|
|
3646
|
+
return new Set([...(above ?? []), ...asked]);
|
|
3647
|
+
}
|
|
3512
3648
|
/**
|
|
3513
3649
|
* What `node` matches changed, which the elements after it can read: as many of them as a rule
|
|
3514
3650
|
* steps along from one, every one for a `~`, and none where no sheet steps at all.
|
|
@@ -3694,6 +3830,7 @@ export class Engine {
|
|
|
3694
3830
|
this.stats.slowCommits++;
|
|
3695
3831
|
}
|
|
3696
3832
|
this.flushTransitionEvents();
|
|
3833
|
+
this.reportPaint();
|
|
3697
3834
|
this.playSized();
|
|
3698
3835
|
this.scrollDriver?.afterCommit();
|
|
3699
3836
|
// Read first: the commit that settles keyframes starts by clearing it.
|
|
@@ -3735,14 +3872,14 @@ export class Engine {
|
|
|
3735
3872
|
/** The first commit whose new views have not been drawn yet, while `commitUnseen` runs. */
|
|
3736
3873
|
unseen = Infinity;
|
|
3737
3874
|
/**
|
|
3738
|
-
* A commit for what changed
|
|
3739
|
-
*
|
|
3875
|
+
* A commit for what changed after earlier commits, before a frame has drawn them. `since` is
|
|
3876
|
+
* how many commits there had been before the first of them (`stats.commits`).
|
|
3740
3877
|
*
|
|
3741
3878
|
* A view those commits created has not been seen in the style they gave it, so what this one
|
|
3742
|
-
* gives it is where it starts, and no transition runs toward it. That is what a browser
|
|
3743
|
-
*
|
|
3744
|
-
* is built on: Angular adds the enter class after the render pass that created
|
|
3745
|
-
* takes it off a frame later, which is the one change the transition is for.
|
|
3879
|
+
* gives it is where it starts, and no transition runs toward it. That is what a browser does
|
|
3880
|
+
* with a class added to an element before the frame after it was inserted, and what
|
|
3881
|
+
* `animate.enter` is built on: Angular adds the enter class after the render pass that created
|
|
3882
|
+
* the element, and takes it off a frame later, which is the one change the transition is for.
|
|
3746
3883
|
*/
|
|
3747
3884
|
commitUnseen(since) {
|
|
3748
3885
|
this.unseen = since;
|
|
@@ -3902,7 +4039,6 @@ export class Engine {
|
|
|
3902
4039
|
const resolved = props['pointerEvents'];
|
|
3903
4040
|
writeOwnProps(node.props, props, node.attributeOnly);
|
|
3904
4041
|
withTextContent(node, viewName, props);
|
|
3905
|
-
renameFonts(viewName, props, this.postScriptName);
|
|
3906
4042
|
truncatedAs(node, props);
|
|
3907
4043
|
const cascaded = props['transform'];
|
|
3908
4044
|
const inline = inlineOf(node);
|
|
@@ -3920,6 +4056,8 @@ export class Engine {
|
|
|
3920
4056
|
flattenStyle(node.props[STYLE_OVERRIDE], style);
|
|
3921
4057
|
this.fontFaces.apply(style);
|
|
3922
4058
|
holdLoadingFamily(node, style);
|
|
4059
|
+
// Once the face is matched: a family a view's own font names is the face its weight chose.
|
|
4060
|
+
renameFonts(viewName, style, this.postScriptName);
|
|
3923
4061
|
if (viewName === PARAGRAPH) {
|
|
3924
4062
|
alignText(style, this.directionOf(node, style));
|
|
3925
4063
|
this.wholeWords(node, style);
|
|
@@ -3928,7 +4066,7 @@ export class Engine {
|
|
|
3928
4066
|
if (this.fontsRefreshed)
|
|
3929
4067
|
this.capForFonts(node, style);
|
|
3930
4068
|
alignMultiline(viewName, style);
|
|
3931
|
-
rowsTall(style, this.fontScale);
|
|
4069
|
+
rowsTall(viewName, style, this.fontScale);
|
|
3932
4070
|
const merged = composeTransform(node, this.eased(node, style));
|
|
3933
4071
|
// On iOS: Android's field says the baseline of the line it is sized and centred by.
|
|
3934
4072
|
// Still under the row that marked it: a row taken away is not committed again to unmark it.
|
|
@@ -4055,6 +4193,11 @@ export class Engine {
|
|
|
4055
4193
|
* its parent's: the cascade's, and over it the parent's inline style.
|
|
4056
4194
|
*/
|
|
4057
4195
|
movePaint(node, merged) {
|
|
4196
|
+
if (node.paintTaker) {
|
|
4197
|
+
// Only the colour is handed over: a background image stays painted on the node.
|
|
4198
|
+
this.takenPaint(node, merged['backgroundColor']);
|
|
4199
|
+
delete merged['backgroundColor'];
|
|
4200
|
+
}
|
|
4058
4201
|
if (node.paintsOn)
|
|
4059
4202
|
for (const key of PAINT_KEYS)
|
|
4060
4203
|
delete merged[key];
|
|
@@ -4066,6 +4209,13 @@ export class Engine {
|
|
|
4066
4209
|
if (paint[key] !== undefined)
|
|
4067
4210
|
merged[key] = paint[key];
|
|
4068
4211
|
}
|
|
4212
|
+
/** Note the background colour a node that hands it over resolves to, where it changed. */
|
|
4213
|
+
takenPaint(node, color) {
|
|
4214
|
+
if (color === node.paintTaken)
|
|
4215
|
+
return;
|
|
4216
|
+
node.paintTaken = color;
|
|
4217
|
+
this.paintReports.add(node);
|
|
4218
|
+
}
|
|
4069
4219
|
/**
|
|
4070
4220
|
* A container whose direction or alignment changed since a child with a `fit-content` size
|
|
4071
4221
|
* read it, or whose height stopped or started being one a percentage is taken of since a child
|
|
@@ -4890,13 +5040,13 @@ export class Engine {
|
|
|
4890
5040
|
/**
|
|
4891
5041
|
* Commit where it ends each transition of a size or a place that just started, for native to
|
|
4892
5042
|
* move the view there: a layout animation, which the commit asks for once it is known that
|
|
4893
|
-
* nothing else in it moves. One still on its way is committed where it ends too.
|
|
5043
|
+
* nothing else in it moves. One still on its way is committed where it ends too. On a host
|
|
5044
|
+
* with no layout animation they are eased from JavaScript, but for one sent on again and again.
|
|
4894
5045
|
*
|
|
4895
5046
|
* A width eased from JavaScript is a commit a frame, and a commit lays the whole screen out.
|
|
4896
5047
|
*/
|
|
4897
5048
|
layEased(node, state, started, props, now) {
|
|
4898
|
-
|
|
4899
|
-
return;
|
|
5049
|
+
const native = this.fabric.configureNextLayoutAnimation !== undefined;
|
|
4900
5050
|
for (const key of started) {
|
|
4901
5051
|
const eased = state.get(key);
|
|
4902
5052
|
if (!LAID_OUT.has(key) || eased.start > now || eased.duration <= 0)
|
|
@@ -4908,6 +5058,8 @@ export class Engine {
|
|
|
4908
5058
|
props[key] = eased.to;
|
|
4909
5059
|
continue;
|
|
4910
5060
|
}
|
|
5061
|
+
if (!native)
|
|
5062
|
+
continue;
|
|
4911
5063
|
eased.laid = true;
|
|
4912
5064
|
this.laying.push({ node, key, eased });
|
|
4913
5065
|
}
|
|
@@ -4920,10 +5072,11 @@ export class Engine {
|
|
|
4920
5072
|
/**
|
|
4921
5073
|
* Whether a transition of a size is one sent on again and again, each before the last had
|
|
4922
5074
|
* ended: a progress bar that steps every 50ms and eases each step over 150. Native moves the
|
|
4923
|
-
* view, and lays the screen out, every frame while a layout animation runs,
|
|
4924
|
-
* every
|
|
4925
|
-
*
|
|
4926
|
-
*
|
|
5075
|
+
* view, and lays the screen out, every frame while a layout animation runs, and JavaScript
|
|
5076
|
+
* commits every frame while it eases one, so one started at every step is one that never
|
|
5077
|
+
* stops. The first, and one sent back before it ends, are played; from the second sent on in
|
|
5078
|
+
* a row, each is committed where it ends, and the steps are the motion. Played again once the
|
|
5079
|
+
* size has been left alone for as long as it lasts.
|
|
4927
5080
|
*/
|
|
4928
5081
|
sentOnAgain(node, key, eased, now) {
|
|
4929
5082
|
const starts = this.laidStarts.get(node) ?? this.laidStarts.set(node, new Map()).get(node);
|
|
@@ -5323,15 +5476,21 @@ export class Engine {
|
|
|
5323
5476
|
}
|
|
5324
5477
|
return this.animating;
|
|
5325
5478
|
}
|
|
5326
|
-
/**
|
|
5327
|
-
|
|
5479
|
+
/**
|
|
5480
|
+
* The host's conversions, applied to a copy. A removed prop travels as `cleared` says, null for
|
|
5481
|
+
* nearly every one. On an update a colour dropped as unreadable is null too: JSI leaves out an
|
|
5482
|
+
* undefined key, and Fabric keeps what a clone leaves out.
|
|
5483
|
+
*/
|
|
5484
|
+
processed(props, update) {
|
|
5328
5485
|
const out = {};
|
|
5329
5486
|
for (const key of Object.keys(props)) {
|
|
5330
5487
|
const value = props[key];
|
|
5331
|
-
if (value === null
|
|
5488
|
+
if (value === null)
|
|
5489
|
+
out[key] = cleared(key);
|
|
5490
|
+
else if (value === undefined)
|
|
5332
5491
|
out[key] = value;
|
|
5333
5492
|
else if (isColorProp(key))
|
|
5334
|
-
out[key] = this.
|
|
5493
|
+
out[key] = this.colourProp(key, value, update);
|
|
5335
5494
|
// A list of sources has already been resolved by whoever built it, as RN's resolver
|
|
5336
5495
|
// also assumes: it passes any object through and only turns a number into one.
|
|
5337
5496
|
else if (ASSET_PROPS.has(key) && !Array.isArray(value))
|
|
@@ -5493,7 +5652,7 @@ export class Engine {
|
|
|
5493
5652
|
this.noteMoved(node, propsPayload, childHandles.length !== previous.childHandles.length, previous.props['text']);
|
|
5494
5653
|
let handle = previous.handle;
|
|
5495
5654
|
if (childrenChanged || propsPayload) {
|
|
5496
|
-
handle = this.clone(previous.handle, propsPayload && this.processed(propsPayload), childrenChanged ? childHandles : null);
|
|
5655
|
+
handle = this.clone(previous.handle, propsPayload && this.processed(propsPayload, true), childrenChanged ? childHandles : null);
|
|
5497
5656
|
// A clone is the same view, so it keeps the tag it was created with, not the newest tag
|
|
5498
5657
|
// handed out, which is some other node's.
|
|
5499
5658
|
node.committed = { handle, tag: previous.tag, props, childHandles, viewName };
|
|
@@ -5838,7 +5997,7 @@ export class Engine {
|
|
|
5838
5997
|
...STAND_INS[name],
|
|
5839
5998
|
};
|
|
5840
5999
|
const payload = diffProps(kept.committed.props, props);
|
|
5841
|
-
const handle = this.clone(kept.committed.handle, payload && this.processed(payload), []);
|
|
6000
|
+
const handle = this.clone(kept.committed.handle, payload && this.processed(payload, true), []);
|
|
5842
6001
|
kept.committed = { ...kept.committed, handle, props, childHandles: [] };
|
|
5843
6002
|
kept.node = null;
|
|
5844
6003
|
}
|
|
@@ -5936,7 +6095,7 @@ export class Engine {
|
|
|
5936
6095
|
const tag = nextTag();
|
|
5937
6096
|
this.stats.createdNodes++;
|
|
5938
6097
|
// instanceHandle is the retained node itself: it is what Fabric hands back on events.
|
|
5939
|
-
const handle = this.fabric.createNode(tag, viewName, this.rootTag, this.processed(props), node);
|
|
6098
|
+
const handle = this.fabric.createNode(tag, viewName, this.rootTag, this.processed(props, false), node);
|
|
5940
6099
|
for (const child of childHandles)
|
|
5941
6100
|
this.fabric.appendChild(handle, child);
|
|
5942
6101
|
node.committed = { handle, tag, props, childHandles, viewName };
|
|
@@ -6228,17 +6387,29 @@ export class Engine {
|
|
|
6228
6387
|
resolveAsset(value) {
|
|
6229
6388
|
return this.resolveAssetSource(value);
|
|
6230
6389
|
}
|
|
6390
|
+
/** A colour prop as `processed` sends it: one dropped as unreadable is null on an update. */
|
|
6391
|
+
colourProp(key, value, update) {
|
|
6392
|
+
const painted = this.painted(key, value);
|
|
6393
|
+
return painted === undefined && update ? null : painted;
|
|
6394
|
+
}
|
|
6231
6395
|
/**
|
|
6232
6396
|
* A colour prop as native takes it. A shape's `fill` or `stroke` the cascade settled from a
|
|
6233
|
-
* token is the solid brush react-native-svg takes,
|
|
6397
|
+
* token is the solid brush react-native-svg takes, `none` no paint at all, and one the host
|
|
6398
|
+
* reads no colour in no paint of the shape's own.
|
|
6234
6399
|
*/
|
|
6235
6400
|
painted(key, value) {
|
|
6236
6401
|
if (!BRUSH_PROPS.has(key))
|
|
6237
6402
|
return this.color(value);
|
|
6238
|
-
// Already a brush, as the icon wrote it: only one the cascade settled is still text.
|
|
6403
|
+
// Already a brush, as the icon wrote it: only one the cascade settled is still text. An
|
|
6404
|
+
// icon's solid brush holds whatever the host read, which is nothing for an unreadable colour.
|
|
6239
6405
|
if (typeof value !== 'string')
|
|
6240
|
-
return value;
|
|
6241
|
-
|
|
6406
|
+
return emptyBrush(value) ? undefined : value;
|
|
6407
|
+
if (value === 'none')
|
|
6408
|
+
return null;
|
|
6409
|
+
// A colour the host cannot read is dropped, as a browser drops it: a solid brush with no
|
|
6410
|
+
// colour in it stops the app.
|
|
6411
|
+
const payload = this.color(value);
|
|
6412
|
+
return payload == null ? undefined : { type: 0, payload };
|
|
6242
6413
|
}
|
|
6243
6414
|
/**
|
|
6244
6415
|
* The host's `processColor`, and the one place a platform colour becomes one.
|