@ng-native/fabric 0.18.1 → 0.19.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/engine.d.ts +72 -17
- package/dist/engine.js +238 -21
- package/dist/engine.js.map +1 -1
- package/package.json +1 -1
package/dist/engine.d.ts
CHANGED
|
@@ -228,12 +228,8 @@ export interface EngineNode extends HostNode {
|
|
|
228
228
|
nodeText: string;
|
|
229
229
|
childList: EngineNode[];
|
|
230
230
|
parent: EngineNode | null;
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
* node put the child there instead, so a component's template lands inside a view its owner
|
|
234
|
-
* placed in the host element. Only the view itself goes in this node.
|
|
235
|
-
*/
|
|
236
|
-
contentView?: EngineNode;
|
|
231
|
+
/** Set by `Engine.wrapChildren`: the view this node's children are committed in. */
|
|
232
|
+
wrapper?: Wrapper;
|
|
237
233
|
listeners: Map<string, Set<(event: unknown) => void>> | null;
|
|
238
234
|
/** CSS classes on this node, for selector matching. */
|
|
239
235
|
classes: Set<string> | null;
|
|
@@ -311,11 +307,11 @@ export interface EngineNode extends HostNode {
|
|
|
311
307
|
scrollsOverflow?: true;
|
|
312
308
|
/** Set while this element is one: see `Engine.settleScrolling`. */
|
|
313
309
|
scrollView?: true;
|
|
314
|
-
/**
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
310
|
+
/**
|
|
311
|
+
* The view a scrolling element's children are in, or a wrapped one's (`wrapper`), as it was
|
|
312
|
+
* last committed: a wrapper's with the props it was committed with.
|
|
313
|
+
*/
|
|
314
|
+
inside?: Inside;
|
|
319
315
|
/** Set while `settleLine` has given an always-inline element a line for the boxes it holds. */
|
|
320
316
|
lined?: true;
|
|
321
317
|
/** Set while an always-inline element is a flex box or a grid by its own style. */
|
|
@@ -545,6 +541,21 @@ export declare function markPage(node: EngineNode): void;
|
|
|
545
541
|
* and its `overflow` is layout alone.
|
|
546
542
|
*/
|
|
547
543
|
export declare function scrollsOverflow(node: EngineNode): void;
|
|
544
|
+
/**
|
|
545
|
+
* A view the engine commits an element's children in, which is no node of the tree: see
|
|
546
|
+
* `Engine.wrapChildren`.
|
|
547
|
+
*/
|
|
548
|
+
export interface Wrapper {
|
|
549
|
+
readonly viewName: string;
|
|
550
|
+
readonly props: Readonly<Record<string, unknown>>;
|
|
551
|
+
}
|
|
552
|
+
/** The view an element's children are committed in, as `inside` keeps it. */
|
|
553
|
+
interface Inside {
|
|
554
|
+
handle: FabricNode;
|
|
555
|
+
childHandles: FabricNode[];
|
|
556
|
+
/** A wrapper's props, as it was committed with them. */
|
|
557
|
+
props?: Record<string, unknown>;
|
|
558
|
+
}
|
|
548
559
|
/**
|
|
549
560
|
* Mark an element as the host of a component. The platform adapter calls this from
|
|
550
561
|
* `RendererFactory2.createRenderer`, which Angular calls once for every component it mounts,
|
|
@@ -1172,12 +1183,30 @@ export declare class Engine implements HostEngine {
|
|
|
1172
1183
|
* keyboard's top edge, for a field the keyboard would otherwise hide.
|
|
1173
1184
|
*
|
|
1174
1185
|
* Measured in the window, all of it, so the scroll view's offset needs no tracking: it is how
|
|
1175
|
-
* far its content's top edge is above its own.
|
|
1186
|
+
* far its content's top edge is above its own. In a sheet on iOS the window is the sheet's: see
|
|
1187
|
+
* `sheetTop`.
|
|
1188
|
+
*
|
|
1189
|
+
* A scroll view under a navigation bar rests scrolled above zero, by the inset the bar adds, and
|
|
1190
|
+
* is not scrolled up past where it rests, nor past zero from below it: how far above zero it can
|
|
1191
|
+
* go is known only while it is there.
|
|
1176
1192
|
*/
|
|
1177
1193
|
reveal(node: EngineNode, options?: {
|
|
1178
1194
|
visibleBottom?: number;
|
|
1179
1195
|
margin?: number;
|
|
1180
1196
|
}): void;
|
|
1197
|
+
/**
|
|
1198
|
+
* How far down the window the iOS sheet a node is presented in starts, where that is known, so
|
|
1199
|
+
* the keyboard's `visibleBottom` can be brought into the sheet. React Native measures a view in a
|
|
1200
|
+
* presented sheet from the sheet's own top: the sheet's place on the screen is UIKit's, and the
|
|
1201
|
+
* shadow tree has the sheet at the top of the window.
|
|
1202
|
+
*
|
|
1203
|
+
* A sheet as wide as the window is attached to its bottom edge, so it starts the window's height
|
|
1204
|
+
* less its own down. One that is narrower (an iPad's form sheet, or one floating at a partial
|
|
1205
|
+
* detent) could be anywhere, and one no taller than the room above the keyboard may have risen
|
|
1206
|
+
* over it, so for those nothing is known and nothing is taken off. On Android the sheet is laid
|
|
1207
|
+
* out in the shadow tree, so nothing is either.
|
|
1208
|
+
*/
|
|
1209
|
+
private sheetTop;
|
|
1181
1210
|
/**
|
|
1182
1211
|
* The view a scroll view's content is in: an element that scrolls its overflow holds its
|
|
1183
1212
|
* children in one of its own, and any other scroll view's first child is it.
|
|
@@ -1344,8 +1373,8 @@ export declare class Engine implements HostEngine {
|
|
|
1344
1373
|
/** Angular's `@if`/`@for`/`ViewContainerRef` markers. Ordered, never committed. */
|
|
1345
1374
|
createAnchor(): EngineNode;
|
|
1346
1375
|
createText(value: string): EngineNode;
|
|
1347
|
-
appendChild(
|
|
1348
|
-
insertBefore(
|
|
1376
|
+
appendChild(parent: EngineNode, child: EngineNode): void;
|
|
1377
|
+
insertBefore(parent: EngineNode, child: EngineNode, ref: EngineNode | null): void;
|
|
1349
1378
|
/**
|
|
1350
1379
|
* A node coming under a parent other than the one it is under, or under any once it has been
|
|
1351
1380
|
* taken out: what is over it may be other than it was styled under. The resolver sees that for
|
|
@@ -1354,6 +1383,19 @@ export declare class Engine implements HostEngine {
|
|
|
1354
1383
|
*/
|
|
1355
1384
|
private markAdopted;
|
|
1356
1385
|
removeChild(parent: EngineNode | null, child: EngineNode): void;
|
|
1386
|
+
/**
|
|
1387
|
+
* Commit an element's children inside a view of the engine's own, `wrapper.viewName` with
|
|
1388
|
+
* `wrapper.props`, and the element's own view around it; null commits them in the element
|
|
1389
|
+
* again. For a native view that wants its content in a child of a kind it names, where the
|
|
1390
|
+
* element is a component's host: the tree keeps the children in the element, so the
|
|
1391
|
+
* component's `:host` rules, a `:host >` selector and what is inherited reach them as written,
|
|
1392
|
+
* and how the element places its children (`flex-direction`, `justify-content`, `align-items`,
|
|
1393
|
+
* `gap`) is committed on the wrapper, which holds them. What is hoisted into the element and
|
|
1394
|
+
* its fixed boxes stay its own, after the wrapper. Called again with another wrapper's props,
|
|
1395
|
+
* the wrapper is cloned with them; another view name makes the element and what it holds again,
|
|
1396
|
+
* as native gives a view one parent for as long as it lives.
|
|
1397
|
+
*/
|
|
1398
|
+
wrapChildren(node: EngineNode, wrapper: Wrapper | null): void;
|
|
1357
1399
|
/**
|
|
1358
1400
|
* A child came or went under a view that takes its text as a prop: that prop changed. A span
|
|
1359
1401
|
* changes it too, by keeping or dropping the space before it.
|
|
@@ -3096,6 +3138,18 @@ export declare class Engine implements HostEngine {
|
|
|
3096
3138
|
* for a content that was cloned.
|
|
3097
3139
|
*/
|
|
3098
3140
|
private reconcileScroller;
|
|
3141
|
+
/**
|
|
3142
|
+
* `reconcile` for an element whose children are in a wrapper (`wrapChildren`): its own view,
|
|
3143
|
+
* holding the wrapper and then `beside`, and the wrapper holding its children. The wrapper is
|
|
3144
|
+
* created with the element and cloned for what changed of its own, as a scroll view's content
|
|
3145
|
+
* is.
|
|
3146
|
+
*/
|
|
3147
|
+
private reconcileWrapped;
|
|
3148
|
+
/** `noteClone` for a wrapped element, whose children are counted in its wrapper. */
|
|
3149
|
+
private noteWrappedClone;
|
|
3150
|
+
/** A wrapper cloned for what changed of its props, and for children that moved. */
|
|
3151
|
+
private clonedWrapper;
|
|
3152
|
+
private createWrapped;
|
|
3099
3153
|
/** A view cloned for the props and the children that changed, or itself where neither did. */
|
|
3100
3154
|
private clonedFor;
|
|
3101
3155
|
private createScroller;
|
|
@@ -3233,7 +3287,8 @@ export declare class Engine implements HostEngine {
|
|
|
3233
3287
|
/**
|
|
3234
3288
|
* `reconcileChildren` for a node other nodes hoist into: its own children, each followed by
|
|
3235
3289
|
* the hoisted nodes written inside it, then those written in it directly, then the views it
|
|
3236
|
-
* keeps for hoisted nodes that are gone.
|
|
3290
|
+
* keeps for hoisted nodes that are gone. A wrapped node's hoisted nodes and kept views go in
|
|
3291
|
+
* `beside`, beside its wrapper and not in it, in the same order.
|
|
3237
3292
|
*/
|
|
3238
3293
|
private reconcileHoistTarget;
|
|
3239
3294
|
/**
|
|
@@ -3531,8 +3586,8 @@ export declare class Engine implements HostEngine {
|
|
|
3531
3586
|
* `unstable_*EventPriority` constants) is read into the facade but not acted on. Angular's
|
|
3532
3587
|
* zoneless scheduler already flushes on a microtask, which is soon enough for taps. It stops
|
|
3533
3588
|
* being soon enough for a controlled TextInput, where the echo has to beat the next
|
|
3534
|
-
* keystroke.
|
|
3535
|
-
* a synchronous flush for discrete priority is the fix if it ever stops being enough.
|
|
3589
|
+
* keystroke. The text input's event count covers that today by letting native reject a stale
|
|
3590
|
+
* write; a synchronous flush for discrete priority is the fix if it ever stops being enough.
|
|
3536
3591
|
*
|
|
3537
3592
|
* **Nothing thrown here may escape.** Fabric calls this synchronously from C++
|
|
3538
3593
|
* (`UIManagerBinding::dispatchEventToJS`), often from inside a mounting transaction, and a throw
|
package/dist/engine.js
CHANGED
|
@@ -341,6 +341,8 @@ const ENGINE_PROPS = new Set([
|
|
|
341
341
|
'disabled',
|
|
342
342
|
'intrinsicSize',
|
|
343
343
|
'styleOverride',
|
|
344
|
+
// A text field's latest event count, committed as `mostRecentEventCount`: see `echoedEventCount`.
|
|
345
|
+
'latestEventCount',
|
|
344
346
|
...Object.values(EVENT_OPT_IN_PROPS),
|
|
345
347
|
...Object.values(VIEW_EVENT_OPT_IN_PROPS).flatMap((props) => Object.values(props)),
|
|
346
348
|
]);
|
|
@@ -459,15 +461,33 @@ export function markPage(node) {
|
|
|
459
461
|
export function scrollsOverflow(node) {
|
|
460
462
|
node.scrollsOverflow = true;
|
|
461
463
|
}
|
|
464
|
+
/** How a flex container places its children, which the view they are in takes. */
|
|
465
|
+
const CHILD_PLACING_KEYS = [
|
|
466
|
+
...['flexDirection', 'flexWrap', 'alignItems', 'alignContent', 'justifyContent'],
|
|
467
|
+
...['gap', 'rowGap', 'columnGap'],
|
|
468
|
+
];
|
|
462
469
|
/** What a scrolling element lays out what it holds by, which its content view takes. */
|
|
463
470
|
const CONTENT_KEYS = new Set([
|
|
464
471
|
...['padding', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'],
|
|
465
472
|
...['paddingHorizontal', 'paddingVertical', 'paddingStart', 'paddingEnd'],
|
|
466
473
|
...['paddingInline', 'paddingBlock', 'paddingInlineStart', 'paddingInlineEnd'],
|
|
467
474
|
...['paddingBlockStart', 'paddingBlockEnd'],
|
|
468
|
-
...
|
|
469
|
-
...['gap', 'rowGap', 'columnGap'],
|
|
475
|
+
...CHILD_PLACING_KEYS,
|
|
470
476
|
]);
|
|
477
|
+
const CHILD_PLACING = new Set(CHILD_PLACING_KEYS);
|
|
478
|
+
/**
|
|
479
|
+
* A wrapped element's props split between its two views: the box it is, on its own view, and how
|
|
480
|
+
* its children are placed, on the wrapper, after the wrapper's own props. Its padding stays on
|
|
481
|
+
* the element, outside what the wrapper insets by.
|
|
482
|
+
*/
|
|
483
|
+
function splitWrapped(props, own = {}) {
|
|
484
|
+
const box = {};
|
|
485
|
+
const content = { ...own };
|
|
486
|
+
for (const [key, value] of Object.entries(props)) {
|
|
487
|
+
(CHILD_PLACING.has(key) ? content : box)[key] = value;
|
|
488
|
+
}
|
|
489
|
+
return { box, content };
|
|
490
|
+
}
|
|
471
491
|
/** A scrolling element's props split between its two views: see `scrollerProps`. */
|
|
472
492
|
function splitScroller(props) {
|
|
473
493
|
const box = {};
|
|
@@ -3442,6 +3462,65 @@ function alignMultiline(viewName, props) {
|
|
|
3442
3462
|
return;
|
|
3443
3463
|
props['textAlignVertical'] ??= 'top';
|
|
3444
3464
|
}
|
|
3465
|
+
/** The props React Native builds a text field's text attributes from, as `BaseTextProps` reads them. */
|
|
3466
|
+
const TEXT_ATTRIBUTES = [
|
|
3467
|
+
'color',
|
|
3468
|
+
'backgroundColor',
|
|
3469
|
+
'opacity',
|
|
3470
|
+
'fontFamily',
|
|
3471
|
+
'fontSize',
|
|
3472
|
+
'fontStyle',
|
|
3473
|
+
'fontWeight',
|
|
3474
|
+
'fontVariant',
|
|
3475
|
+
'fontVariationSettings',
|
|
3476
|
+
'allowFontScaling',
|
|
3477
|
+
'maxFontSizeMultiplier',
|
|
3478
|
+
'dynamicTypeRamp',
|
|
3479
|
+
'letterSpacing',
|
|
3480
|
+
'lineHeight',
|
|
3481
|
+
'textAlign',
|
|
3482
|
+
'writingDirection',
|
|
3483
|
+
'direction',
|
|
3484
|
+
'lineBreakStrategyIOS',
|
|
3485
|
+
'lineBreakModeIOS',
|
|
3486
|
+
'textDecorationLine',
|
|
3487
|
+
'textDecorationColor',
|
|
3488
|
+
'textDecorationStyle',
|
|
3489
|
+
'textShadowColor',
|
|
3490
|
+
'textShadowOffset',
|
|
3491
|
+
'textShadowRadius',
|
|
3492
|
+
'textTransform',
|
|
3493
|
+
];
|
|
3494
|
+
/**
|
|
3495
|
+
* The prop a text field that echoes its text puts its latest event count in, for
|
|
3496
|
+
* `echoedEventCount` to commit as `mostRecentEventCount`. It never reaches native.
|
|
3497
|
+
*/
|
|
3498
|
+
const LATEST_EVENT_COUNT = 'latestEventCount';
|
|
3499
|
+
/**
|
|
3500
|
+
* The event count a text field that echoes its text is committed with: the latest on Android, and
|
|
3501
|
+
* on iOS 0, the count native starts at, but for a commit that changes a multiline field's text
|
|
3502
|
+
* style, which carries the latest.
|
|
3503
|
+
*
|
|
3504
|
+
* iOS takes the `text` prop only with the count it holds, and then replaces the field's text unless
|
|
3505
|
+
* the two compare equal, attributes and all. UIKit gives an empty field's first typed text a
|
|
3506
|
+
* paragraph style whose line break strategy differs from the one RN compares it with, so an echo
|
|
3507
|
+
* of that very text replaces it, and the keystrokes queued behind the replace are lost. Text from
|
|
3508
|
+
* code goes as `setTextAndSelection` instead, which carries the count. A restyle still needs the
|
|
3509
|
+
* replace on a multiline field: a single-line one restyles its text from its new default
|
|
3510
|
+
* attributes, a multiline one only takes them for what is typed next.
|
|
3511
|
+
*
|
|
3512
|
+
* A field that sets `mostRecentEventCount` itself, and never echoes, is left as it is.
|
|
3513
|
+
*/
|
|
3514
|
+
function echoedEventCount(viewName, props, previous) {
|
|
3515
|
+
const latest = props[LATEST_EVENT_COUNT];
|
|
3516
|
+
if (latest === undefined)
|
|
3517
|
+
return;
|
|
3518
|
+
delete props[LATEST_EVENT_COUNT];
|
|
3519
|
+
const restyled = props['multiline'] === true &&
|
|
3520
|
+
previous !== undefined &&
|
|
3521
|
+
TEXT_ATTRIBUTES.some((key) => !Object.is(previous[key], props[key]));
|
|
3522
|
+
props['mostRecentEventCount'] = viewName !== 'TextInput' || restyled ? latest : 0;
|
|
3523
|
+
}
|
|
3445
3524
|
/** The props of a run of text that change how tall its line is drawn. */
|
|
3446
3525
|
const SIZES_A_RUN = new Set(['style', 'allowFontScaling', 'maxFontSizeMultiplier']);
|
|
3447
3526
|
/**
|
|
@@ -3631,6 +3710,12 @@ function seenByCss(merged) {
|
|
|
3631
3710
|
/** A screen of a native stack, which is what the swipe back is a gesture of. */
|
|
3632
3711
|
const STACK_SCREEN = 'RNSScreen';
|
|
3633
3712
|
const FIXED_BOUNDARIES = new Set([MODAL_HOST, STACK_SCREEN, 'RNSFullWindowOverlay']);
|
|
3713
|
+
/**
|
|
3714
|
+
* The presentations that can leave a sheet short of the top of the screen on iOS: a screen's
|
|
3715
|
+
* `stackPresentation` (`modal` is a page sheet from iOS 13) or a modal host's `presentationStyle`.
|
|
3716
|
+
* The others cover the screen, or what they are presented over.
|
|
3717
|
+
*/
|
|
3718
|
+
const SHEETS = new Set(['modal', 'formSheet', 'pageSheet']);
|
|
3634
3719
|
/** What an element's `touch-action` is: bound on it, or from the rules it matches. */
|
|
3635
3720
|
function touchActionOf(node) {
|
|
3636
3721
|
return inlineOf(node)['touchAction'] ?? node.styleCache?.style['touchAction'];
|
|
@@ -4726,6 +4811,7 @@ class RetainedNode {
|
|
|
4726
4811
|
inlineInherits = undefined;
|
|
4727
4812
|
line = undefined;
|
|
4728
4813
|
inside = undefined;
|
|
4814
|
+
wrapper = undefined;
|
|
4729
4815
|
transitions;
|
|
4730
4816
|
playing;
|
|
4731
4817
|
scrolled;
|
|
@@ -4945,7 +5031,7 @@ const ENGINE_NODE_FIELDS = {
|
|
|
4945
5031
|
structureDirty: true,
|
|
4946
5032
|
subtreeDirty: true,
|
|
4947
5033
|
inside: true,
|
|
4948
|
-
|
|
5034
|
+
wrapper: true,
|
|
4949
5035
|
scrollsOverflow: true,
|
|
4950
5036
|
engine: true,
|
|
4951
5037
|
};
|
|
@@ -6011,7 +6097,12 @@ export class Engine {
|
|
|
6011
6097
|
* keyboard's top edge, for a field the keyboard would otherwise hide.
|
|
6012
6098
|
*
|
|
6013
6099
|
* Measured in the window, all of it, so the scroll view's offset needs no tracking: it is how
|
|
6014
|
-
* far its content's top edge is above its own.
|
|
6100
|
+
* far its content's top edge is above its own. In a sheet on iOS the window is the sheet's: see
|
|
6101
|
+
* `sheetTop`.
|
|
6102
|
+
*
|
|
6103
|
+
* A scroll view under a navigation bar rests scrolled above zero, by the inset the bar adds, and
|
|
6104
|
+
* is not scrolled up past where it rests, nor past zero from below it: how far above zero it can
|
|
6105
|
+
* go is known only while it is there.
|
|
6015
6106
|
*/
|
|
6016
6107
|
reveal(node, options = {}) {
|
|
6017
6108
|
const scroll = scrollingDown(node);
|
|
@@ -6019,23 +6110,68 @@ export class Engine {
|
|
|
6019
6110
|
if (!scroll || !content)
|
|
6020
6111
|
return;
|
|
6021
6112
|
const margin = options.margin ?? 16;
|
|
6113
|
+
const { visibleBottom } = options;
|
|
6114
|
+
const covered = visibleBottom === undefined ? Infinity : visibleBottom - this.sheetTop(node, visibleBottom);
|
|
6022
6115
|
this.measure(scroll, (view) => {
|
|
6023
6116
|
this.measureHandle(content, (inner) => {
|
|
6024
6117
|
this.measure(node, (target) => {
|
|
6025
6118
|
const offset = view.y - inner.y;
|
|
6026
6119
|
const top = view.y + margin;
|
|
6027
|
-
const bottom = Math.min(view.y + view.height,
|
|
6120
|
+
const bottom = Math.min(view.y + view.height, covered) - margin;
|
|
6028
6121
|
let by = 0;
|
|
6029
6122
|
if (target.y + target.height > bottom)
|
|
6030
6123
|
by = target.y + target.height - bottom;
|
|
6031
6124
|
else if (target.y < top)
|
|
6032
6125
|
by = target.y - top;
|
|
6033
6126
|
if (by !== 0)
|
|
6034
|
-
this.dispatchCommand(scroll, 'scrollTo', [
|
|
6127
|
+
this.dispatchCommand(scroll, 'scrollTo', [
|
|
6128
|
+
0,
|
|
6129
|
+
Math.max(Math.min(0, offset), offset + by),
|
|
6130
|
+
true,
|
|
6131
|
+
]);
|
|
6035
6132
|
});
|
|
6036
6133
|
});
|
|
6037
6134
|
});
|
|
6038
6135
|
}
|
|
6136
|
+
/**
|
|
6137
|
+
* How far down the window the iOS sheet a node is presented in starts, where that is known, so
|
|
6138
|
+
* the keyboard's `visibleBottom` can be brought into the sheet. React Native measures a view in a
|
|
6139
|
+
* presented sheet from the sheet's own top: the sheet's place on the screen is UIKit's, and the
|
|
6140
|
+
* shadow tree has the sheet at the top of the window.
|
|
6141
|
+
*
|
|
6142
|
+
* A sheet as wide as the window is attached to its bottom edge, so it starts the window's height
|
|
6143
|
+
* less its own down. One that is narrower (an iPad's form sheet, or one floating at a partial
|
|
6144
|
+
* detent) could be anywhere, and one no taller than the room above the keyboard may have risen
|
|
6145
|
+
* over it, so for those nothing is known and nothing is taken off. On Android the sheet is laid
|
|
6146
|
+
* out in the shadow tree, so nothing is either.
|
|
6147
|
+
*/
|
|
6148
|
+
sheetTop(node, keyboardTop) {
|
|
6149
|
+
if (platformOS !== 'ios')
|
|
6150
|
+
return 0;
|
|
6151
|
+
for (let up = node.parent; up; up = up.parent) {
|
|
6152
|
+
if (up.nodeKind !== 'element')
|
|
6153
|
+
continue;
|
|
6154
|
+
const viewName = viewNameOf(up);
|
|
6155
|
+
// React Native's own modal measures what it holds from its own top, wherever it is written.
|
|
6156
|
+
const modal = viewName === MODAL_HOST;
|
|
6157
|
+
const presented = modal
|
|
6158
|
+
? up.props['presentationStyle']
|
|
6159
|
+
: viewName === STACK_SCREEN && up.props['stackPresentation'];
|
|
6160
|
+
// A screen pushed in a stack is measured in whatever it is pushed in.
|
|
6161
|
+
if (!modal && (!presented || presented === 'push'))
|
|
6162
|
+
continue;
|
|
6163
|
+
if (!SHEETS.has(String(presented)))
|
|
6164
|
+
return 0;
|
|
6165
|
+
let top = 0;
|
|
6166
|
+
this.measure(up, (sheet) => {
|
|
6167
|
+
if (sheet.width < this.viewportSize.width || sheet.height <= keyboardTop)
|
|
6168
|
+
return;
|
|
6169
|
+
top = Math.max(0, this.viewportSize.height - (modal ? 0 : sheet.y) - sheet.height);
|
|
6170
|
+
});
|
|
6171
|
+
return top;
|
|
6172
|
+
}
|
|
6173
|
+
return 0;
|
|
6174
|
+
}
|
|
6039
6175
|
/**
|
|
6040
6176
|
* The view a scroll view's content is in: an element that scrolls its overflow holds its
|
|
6041
6177
|
* children in one of its own, and any other scroll view's first child is it.
|
|
@@ -6496,8 +6632,7 @@ export class Engine {
|
|
|
6496
6632
|
node.nodeText = value;
|
|
6497
6633
|
return node;
|
|
6498
6634
|
}
|
|
6499
|
-
appendChild(
|
|
6500
|
-
const parent = into.contentView && into.contentView !== child ? into.contentView : into;
|
|
6635
|
+
appendChild(parent, child) {
|
|
6501
6636
|
this.markAdopted(child, parent);
|
|
6502
6637
|
this.detach(child);
|
|
6503
6638
|
this.markReturned(child);
|
|
@@ -6512,8 +6647,7 @@ export class Engine {
|
|
|
6512
6647
|
this.markStructure(parent, child, parent.childList.length - 1);
|
|
6513
6648
|
this.markTextContent(parent, child);
|
|
6514
6649
|
}
|
|
6515
|
-
insertBefore(
|
|
6516
|
-
const parent = into.contentView && into.contentView !== child ? into.contentView : into;
|
|
6650
|
+
insertBefore(parent, child, ref) {
|
|
6517
6651
|
this.markAdopted(child, parent);
|
|
6518
6652
|
this.detach(child);
|
|
6519
6653
|
this.markReturned(child);
|
|
@@ -6557,6 +6691,25 @@ export class Engine {
|
|
|
6557
6691
|
this.markStructure(target, child, at);
|
|
6558
6692
|
this.markTextContent(target, child);
|
|
6559
6693
|
}
|
|
6694
|
+
/**
|
|
6695
|
+
* Commit an element's children inside a view of the engine's own, `wrapper.viewName` with
|
|
6696
|
+
* `wrapper.props`, and the element's own view around it; null commits them in the element
|
|
6697
|
+
* again. For a native view that wants its content in a child of a kind it names, where the
|
|
6698
|
+
* element is a component's host: the tree keeps the children in the element, so the
|
|
6699
|
+
* component's `:host` rules, a `:host >` selector and what is inherited reach them as written,
|
|
6700
|
+
* and how the element places its children (`flex-direction`, `justify-content`, `align-items`,
|
|
6701
|
+
* `gap`) is committed on the wrapper, which holds them. What is hoisted into the element and
|
|
6702
|
+
* its fixed boxes stay its own, after the wrapper. Called again with another wrapper's props,
|
|
6703
|
+
* the wrapper is cloned with them; another view name makes the element and what it holds again,
|
|
6704
|
+
* as native gives a view one parent for as long as it lives.
|
|
6705
|
+
*/
|
|
6706
|
+
wrapChildren(node, wrapper) {
|
|
6707
|
+
const was = node.wrapper;
|
|
6708
|
+
node.wrapper = wrapper ?? undefined;
|
|
6709
|
+
if (was?.viewName !== wrapper?.viewName && node.committed)
|
|
6710
|
+
this.forgetCommitted(node);
|
|
6711
|
+
this.markProps(node, false);
|
|
6712
|
+
}
|
|
6560
6713
|
/**
|
|
6561
6714
|
* A child came or went under a view that takes its text as a prop: that prop changed. A span
|
|
6562
6715
|
* changes it too, by keeping or dropping the space before it.
|
|
@@ -10323,6 +10476,8 @@ export class Engine {
|
|
|
10323
10476
|
// On iOS: Android's field says the baseline of the line it is sized and centred by.
|
|
10324
10477
|
if (field)
|
|
10325
10478
|
centreSingleLine(viewName, merged, this.fontScale, isOnBaseline(node, viewName));
|
|
10479
|
+
if (field)
|
|
10480
|
+
echoedEventCount(viewName, merged, node.committed?.props);
|
|
10326
10481
|
// After a transition, which eases the basis as the basis it was written as.
|
|
10327
10482
|
basisAsSize(node, merged);
|
|
10328
10483
|
// Each asked of the key it reads first, here and in `engineLayout`: it is asked of every node
|
|
@@ -12636,15 +12791,19 @@ export class Engine {
|
|
|
12636
12791
|
this.forgetRenamed(node, viewName);
|
|
12637
12792
|
this.noteBaselineFields(node);
|
|
12638
12793
|
this.freshBaselines(node);
|
|
12639
|
-
|
|
12794
|
+
// A wrapped node's own views beside the wrapper: what is hoisted into it, and its fixed boxes.
|
|
12795
|
+
const beside = node.wrapper && [];
|
|
12796
|
+
const childHandles = this.reconcileChildren(node, viewName, style, beside);
|
|
12640
12797
|
if (this.heldMoves.size)
|
|
12641
12798
|
this.heldHandles(node, childHandles);
|
|
12642
12799
|
if (this.fixedIn.size)
|
|
12643
|
-
this.fixedHandles(node, childHandles);
|
|
12800
|
+
this.fixedHandles(node, beside ?? childHandles);
|
|
12644
12801
|
this.raise(node, viewName);
|
|
12645
12802
|
noteTouches(node, style);
|
|
12646
12803
|
if (scroller)
|
|
12647
12804
|
return this.reconcileScroller(node, viewName, childHandles, styleChanged);
|
|
12805
|
+
if (beside)
|
|
12806
|
+
return this.reconcileWrapped(node, viewName, childHandles, beside, styleChanged);
|
|
12648
12807
|
const previous = node.committed;
|
|
12649
12808
|
this.notePresented(node, viewName);
|
|
12650
12809
|
if (!previous)
|
|
@@ -12744,6 +12903,62 @@ export class Engine {
|
|
|
12744
12903
|
this.clearFlags(node);
|
|
12745
12904
|
return handle;
|
|
12746
12905
|
}
|
|
12906
|
+
/**
|
|
12907
|
+
* `reconcile` for an element whose children are in a wrapper (`wrapChildren`): its own view,
|
|
12908
|
+
* holding the wrapper and then `beside`, and the wrapper holding its children. The wrapper is
|
|
12909
|
+
* created with the element and cloned for what changed of its own, as a scroll view's content
|
|
12910
|
+
* is.
|
|
12911
|
+
*/
|
|
12912
|
+
reconcileWrapped(node, viewName, childHandles, beside, styleChanged) {
|
|
12913
|
+
const previous = node.committed;
|
|
12914
|
+
const held = node.inside;
|
|
12915
|
+
if (!previous || !held?.props)
|
|
12916
|
+
return this.createWrapped(node, viewName, childHandles, beside);
|
|
12917
|
+
const propsMoved = node.propsDirty || styleChanged;
|
|
12918
|
+
const props = propsMoved ? this.mergeProps(node, viewName) : previous.props;
|
|
12919
|
+
const payload = propsMoved ? this.propsPayload(node, previous.props, props) : null;
|
|
12920
|
+
if (this.asked & LAYOUTS)
|
|
12921
|
+
this.noteHow(node, payload);
|
|
12922
|
+
const moved = this.childrenMoved(node, childHandles, held.childHandles);
|
|
12923
|
+
this.noteWrappedClone(node, payload, moved, childHandles, previous, held);
|
|
12924
|
+
const content = propsMoved ? splitWrapped(props, node.wrapper.props).content : held.props;
|
|
12925
|
+
const inside = this.clonedWrapper(held, content, moved ? childHandles : null);
|
|
12926
|
+
const around = [inside, ...beside];
|
|
12927
|
+
const aroundMoved = !sameHandles(around, previous.childHandles);
|
|
12928
|
+
const box = payload && someProps(splitWrapped(payload).box);
|
|
12929
|
+
const boxPayload = this.paintNatively(node, viewName, props, box, aroundMoved);
|
|
12930
|
+
const handle = this.clonedFor(previous.handle, boxPayload, aroundMoved ? around : null);
|
|
12931
|
+
node.committed = { handle, tag: previous.tag, props, childHandles: around, viewName };
|
|
12932
|
+
node.inside = { handle: inside, childHandles, props: content };
|
|
12933
|
+
this.clearFlags(node);
|
|
12934
|
+
return handle;
|
|
12935
|
+
}
|
|
12936
|
+
/** `noteClone` for a wrapped element, whose children are counted in its wrapper. */
|
|
12937
|
+
noteWrappedClone(node, payload, moved, childHandles, previous, held) {
|
|
12938
|
+
if (!this.notesMoves)
|
|
12939
|
+
return;
|
|
12940
|
+
const before = { ...previous, childHandles: held.childHandles };
|
|
12941
|
+
this.noteClone(node, payload, moved, childHandles, before);
|
|
12942
|
+
}
|
|
12943
|
+
/** A wrapper cloned for what changed of its props, and for children that moved. */
|
|
12944
|
+
clonedWrapper(held, props, children) {
|
|
12945
|
+
const payload = props === held.props ? null : diffProps(held.props, props);
|
|
12946
|
+
return this.clonedFor(held.handle, payload, children);
|
|
12947
|
+
}
|
|
12948
|
+
createWrapped(node, viewName, childHandles, beside) {
|
|
12949
|
+
const wrapper = node.wrapper;
|
|
12950
|
+
const props = this.mergeProps(node, viewName);
|
|
12951
|
+
const { box, content } = splitWrapped(props, wrapper.props);
|
|
12952
|
+
// A touch on the wrapper lands on the element, as one on a scroll view's content does.
|
|
12953
|
+
const inside = this.fabric.createNode(nextTag(), wrapper.viewName, this.rootTag, this.processed(content, false), node);
|
|
12954
|
+
this.stats.createdNodes++;
|
|
12955
|
+
for (const child of childHandles)
|
|
12956
|
+
this.fabric.appendChild(inside, child);
|
|
12957
|
+
const handle = this.create(node, viewName, box, [inside, ...beside]);
|
|
12958
|
+
node.committed = { ...node.committed, props };
|
|
12959
|
+
node.inside = { handle: inside, childHandles, props: content };
|
|
12960
|
+
return handle;
|
|
12961
|
+
}
|
|
12747
12962
|
/** A view cloned for the props and the children that changed, or itself where neither did. */
|
|
12748
12963
|
clonedFor(handle, props, children) {
|
|
12749
12964
|
if (!props && !children)
|
|
@@ -12803,12 +13018,12 @@ export class Engine {
|
|
|
12803
13018
|
// Exempt from the complexity limit as `mergeProps` is: run for every node with children, where
|
|
12804
13019
|
// the loop in a function of its own was a call more a node in `pnpm bench`.
|
|
12805
13020
|
// eslint-disable-next-line complexity
|
|
12806
|
-
reconcileChildren(node, viewName, style) {
|
|
13021
|
+
reconcileChildren(node, viewName, style, beside) {
|
|
12807
13022
|
const context = style?.context;
|
|
12808
13023
|
if (HOIST_ONLY.has(node.tag))
|
|
12809
13024
|
return this.reconcileOnlyHoisted(node);
|
|
12810
13025
|
if (HOIST_TARGETS.has(node.tag))
|
|
12811
|
-
return this.reconcileHoistTarget(node, context);
|
|
13026
|
+
return this.reconcileHoistTarget(node, context, beside);
|
|
12812
13027
|
if (node.flow !== undefined)
|
|
12813
13028
|
return this.reconcileInFlow(node, node.flow, viewName, context);
|
|
12814
13029
|
// A node with no flow of its own holds nothing in a line: each child is its own view under it.
|
|
@@ -13206,10 +13421,12 @@ export class Engine {
|
|
|
13206
13421
|
/**
|
|
13207
13422
|
* `reconcileChildren` for a node other nodes hoist into: its own children, each followed by
|
|
13208
13423
|
* the hoisted nodes written inside it, then those written in it directly, then the views it
|
|
13209
|
-
* keeps for hoisted nodes that are gone.
|
|
13424
|
+
* keeps for hoisted nodes that are gone. A wrapped node's hoisted nodes and kept views go in
|
|
13425
|
+
* `beside`, beside its wrapper and not in it, in the same order.
|
|
13210
13426
|
*/
|
|
13211
|
-
reconcileHoistTarget(node, context) {
|
|
13427
|
+
reconcileHoistTarget(node, context, beside) {
|
|
13212
13428
|
const handles = [];
|
|
13429
|
+
const landing = beside ?? handles;
|
|
13213
13430
|
const hoisted = this.hoistedInto(node);
|
|
13214
13431
|
const landed = new Set();
|
|
13215
13432
|
// One written directly in the node goes after its siblings: a page writes its header first,
|
|
@@ -13227,13 +13444,13 @@ export class Engine {
|
|
|
13227
13444
|
const moved = hoisted?.get(child);
|
|
13228
13445
|
for (const written of moved ?? []) {
|
|
13229
13446
|
if (!this.undisplayedUnder(node, written))
|
|
13230
|
-
|
|
13447
|
+
landing.push(this.land(node, written, landed));
|
|
13231
13448
|
}
|
|
13232
13449
|
}
|
|
13233
13450
|
for (const child of direct)
|
|
13234
|
-
|
|
13451
|
+
landing.push(this.land(node, child, landed));
|
|
13235
13452
|
if (node.kept)
|
|
13236
|
-
this.standIn(node, landed,
|
|
13453
|
+
this.standIn(node, landed, landing);
|
|
13237
13454
|
return handles;
|
|
13238
13455
|
}
|
|
13239
13456
|
/**
|
|
@@ -14006,8 +14223,8 @@ export class Engine {
|
|
|
14006
14223
|
* `unstable_*EventPriority` constants) is read into the facade but not acted on. Angular's
|
|
14007
14224
|
* zoneless scheduler already flushes on a microtask, which is soon enough for taps. It stops
|
|
14008
14225
|
* being soon enough for a controlled TextInput, where the echo has to beat the next
|
|
14009
|
-
* keystroke.
|
|
14010
|
-
* a synchronous flush for discrete priority is the fix if it ever stops being enough.
|
|
14226
|
+
* keystroke. The text input's event count covers that today by letting native reject a stale
|
|
14227
|
+
* write; a synchronous flush for discrete priority is the fix if it ever stops being enough.
|
|
14011
14228
|
*
|
|
14012
14229
|
* **Nothing thrown here may escape.** Fabric calls this synchronously from C++
|
|
14013
14230
|
* (`UIManagerBinding::dispatchEventToJS`), often from inside a mounting transaction, and a throw
|