@symbiote-native/components 3.1.1 → 3.1.2
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/build/behaviors/activity-indicator/shared.js +29 -71
- package/build/behaviors/button.d.ts +0 -9
- package/build/behaviors/button.js +47 -233
- package/build/behaviors/image-background.js +21 -81
- package/build/behaviors/image.js +3 -10
- package/build/behaviors/input-accessory-view.js +10 -51
- package/build/behaviors/pressable.d.ts +0 -48
- package/build/behaviors/pressable.js +59 -149
- package/build/behaviors/scroll-view/index.android.js +10 -30
- package/build/behaviors/scroll-view/shared.js +54 -181
- package/build/behaviors/scroll-view/sticky.d.ts +0 -8
- package/build/behaviors/scroll-view/sticky.js +54 -142
- package/build/behaviors/text-input.d.ts +0 -8
- package/build/behaviors/text-input.js +57 -156
- package/build/behaviors/touchable-highlight.js +14 -54
- package/build/behaviors/touchable-native-feedback.js +9 -32
- package/build/behaviors/touchable-opacity.js +3 -18
- package/build/behaviors/touchable-without-feedback.js +6 -24
- package/build/component-names/index.android.js +6 -8
- package/build/component-names/shared.js +12 -42
- package/build/index.js +13 -19
- package/build/scroll-view-commands.js +3 -11
- package/build/state/pressable.js +18 -43
- package/build/state/sticky-header-reducer.js +103 -149
- package/build/state/touchable.js +3 -5
- package/build/state/virtualized-list-reducer.js +21 -48
- package/build/state/virtualized-list.js +63 -148
- package/build/text-props.js +3 -13
- package/build/view/render-button.js +13 -44
- package/build/view/render-input-accessory-view.js +8 -24
- package/build/view/render-pressable/index.js +3 -4
- package/build/view/render-scroll-view.js +13 -23
- package/build/view/render-touchable-native-feedback.js +5 -14
- package/host-primitives.cjs +33 -207
- package/package.json +3 -3
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
// ScrollView's behavior on Android, where a RefreshControl is not a child at all.
|
|
2
|
-
//
|
|
3
2
|
// An Android ScrollView holds exactly ONE child, so a sibling refresh control is an `addViewAt`
|
|
4
|
-
// crash
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// the child
|
|
14
|
-
//
|
|
15
|
-
// WHY THEY RESISTED THREE ITERATIONS, and what changed. Every seam the engine had read UP —
|
|
16
|
-
// `ownerProps`, `IOwner.tagName`, `IAncestorLookup` — and the wrapper is the scroll view's PARENT
|
|
17
|
-
// asking for the scroll view's style, the one direction none of them go. `IFirstChild` is that
|
|
18
|
-
// direction, and it is the same argument `ownerProps` already made rather than a new one: the tree
|
|
19
|
-
// lives in C++, so reading another node is a pointer hop, and RN itself builds this parent FROM its
|
|
20
|
-
// child (`cloneElement(refreshControl, {style: outer}, scrollView)`).
|
|
21
|
-
//
|
|
22
|
-
// `slotDerived: ['style']` STAYS AND IS NOW LOAD-BEARING FOR THE ENGINE'S RULE. A rule re-runs when
|
|
23
|
-
// ITS node is dirty; the wrapper derives from a node that is not itself, so an owner style write
|
|
24
|
-
// must mark it (`routeProp`'s `node.wrapper` branch). Without this entry the wrapper freezes at its
|
|
25
|
-
// mount frame while the scroller visibly restyles inside it —
|
|
26
|
-
// `core/engine/cpp/tests/js/scroll-view-wrap-payload.itest.ts` is the case that says so.
|
|
27
|
-
//
|
|
28
|
-
// AND NOTHING HERE IS `#ifdef ANDROID` OR `Platform.OS`, on either side: the engine's rules are
|
|
29
|
-
// gated on TOPOLOGY. iOS claims the refresh control BESIDE the content, so a scroll view is never
|
|
30
|
-
// one's child there and neither branch can fire. That is strictly better than a compile-time split
|
|
31
|
-
// for the reason `Switch`/`AndroidSwitch` already showed — and it is why the wrap's payload fixture
|
|
32
|
-
// runs on the ORDINARY test host rather than needing the Android arm.
|
|
3
|
+
// crash. RN inverts the tree instead: `AndroidSwipeRefreshLayout` WRAPS the scroll view
|
|
4
|
+
// (`ScrollView.js:1856`) — layout style on the wrapper, visual style on the scroller.
|
|
5
|
+
// This file is a CLAIM MODE and nothing else: both prop folds are `SymbioteFabricProps.cpp` now —
|
|
6
|
+
// `foldScrollViewProps` takes the visual half when its parent is a refresh control,
|
|
7
|
+
// `foldRefreshWrapperProps` the layout half of the child it wraps.
|
|
8
|
+
// `slotDerived: ['style']` is load-bearing: the wrapper derives from a node that is not itself, so
|
|
9
|
+
// an owner style write must mark IT dirty (`routeProp`'s `node.wrapper` branch) or it freezes at
|
|
10
|
+
// its mount frame while the scroller visibly restyles (`scroll-view-wrap-payload.itest.ts`).
|
|
11
|
+
// Gated on TOPOLOGY, not `#ifdef ANDROID`/`Platform.OS`: iOS claims the refresh control BESIDE
|
|
12
|
+
// the content, so a scroll view is never one's child there and neither branch can fire.
|
|
33
13
|
import { registerScrollViewBehaviors } from './shared.js';
|
|
34
14
|
const android = {
|
|
35
15
|
claimMode: 'wrap',
|
|
@@ -1,86 +1,25 @@
|
|
|
1
|
-
// ScrollView's host behavior,
|
|
2
|
-
//
|
|
3
|
-
// `
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// `
|
|
22
|
-
// `
|
|
23
|
-
// machine. Full scope and the reusable infra already in place: audit skill, "Found, NOT fixed:
|
|
24
|
-
// ScrollView's keyboardShouldPersistTaps".
|
|
25
|
-
//
|
|
26
|
-
// TODO(rn-parity): `stickyHeaderHiddenOnScroll` is completely absent — no prop surface, no state,
|
|
27
|
-
// no adapter wiring (`grep -rn "stickyHeaderHiddenOnScroll\|hiddenOnScroll"` returns zero hits).
|
|
28
|
-
// Vendor composes an `Animated.diffClamp` over the scroll delta and adds it to the ordinary sticky
|
|
29
|
-
// translateY (`ScrollViewStickyHeader.js:39,84-103`). Needs a new running clamped-offset state
|
|
30
|
-
// ADDED to `STICKY_TRANSLATE_PROP` (our sticky pin is a discrete debounced number, not a live
|
|
31
|
-
// `Animated` composition), and the fix touches `reduceSticky`
|
|
32
|
-
// (`state/sticky-header-reducer.ts`) — the shared decision function every adapter's own sticky
|
|
33
|
-
// component plus Angular's projection controller also run — not just this file. Audit skill,
|
|
34
|
-
// "Found, NOT fixed: ScrollView's stickyHeaderHiddenOnScroll".
|
|
35
|
-
//
|
|
36
|
-
// WHAT A COMPOSED PRIMITIVE COSTS TODAY. Every adapter's ScrollView wrapper builds the same two
|
|
37
|
-
// nodes: `selectScrollIntrinsics` picks a scroll intrinsic and a content intrinsic, and the
|
|
38
|
-
// wrapper's body nests `<content>{children}</content>` inside `<scroll>`. That body is a framework
|
|
39
|
-
// component instance per ScrollView — a Vue instance, a Solid props Proxy, Svelte anchors, an
|
|
40
|
-
// Angular LView — which is precisely the currency a tag exists to delete. `foldPayload` gives a tag
|
|
41
|
-
// its wrapper's PROP MAPPING; nothing gave it the wrapper's COMPOSITION, so a composed primitive
|
|
42
|
-
// could not become a tag at all no matter what its props did. This is that half.
|
|
43
|
-
//
|
|
44
|
-
// WHY THE TAG CARRIES THE AXIS. `buildStructure` runs at `createElement`, before a single prop is
|
|
45
|
-
// routed, so it cannot read `horizontal`. It does not need to: horizontal scroll is already a
|
|
46
|
-
// SEPARATE intrinsic (`horizontal-scroll-view` — a different native ViewManager on
|
|
47
|
-
// Android, not RCTScrollView with a flag), so the decision the behavior needs is in the tag it was
|
|
48
|
-
// looked up by. One behavior per tag, each knowing its own content intrinsic. That is the same
|
|
49
|
-
// shape `intrinsicWhen` gives TextInput's `multiline`, arrived at from the other side.
|
|
50
|
-
//
|
|
51
|
-
// REGISTERED BY SVELTE SINCE 2026-09-07 (`adapters/svelte/src/register.ts`), and by no other
|
|
52
|
-
// adapter — this paragraph read "NOT REGISTERED BY ANY ADAPTER" for three days after that stopped
|
|
53
|
-
// being true. The hazard still holds for the four that have not registered: `scroll-view` is the
|
|
54
|
-
// tag their WRAPPERS emit, and a wrapper builds its own content node from `selectScrollIntrinsics`,
|
|
55
|
-
// as does `VirtualizedList`. Registering while either stands silently gives those trees a SECOND
|
|
56
|
-
// content node: `RCTScrollView > RCTScrollContentView > RCTScrollContentView`. So the precondition
|
|
57
|
-
// per adapter is that nothing else builds the content node.
|
|
58
|
-
//
|
|
59
|
-
// A SECOND TAG WAS NOT THE ANSWER, and this reverses what this header said until 2026-09-07: a
|
|
60
|
-
// second spelling only keeps two owners apart while two paths exist, and it buys a rename across
|
|
61
|
-
// every call site now plus another when one path dies. The decision was that the ENGINE becomes the
|
|
62
|
-
// single owner of the content node. That cut LANDED 2026-09-11: nothing else builds a content node
|
|
63
|
-
// any more, and all five adapters register this behavior through
|
|
64
|
-
// `@symbiote-native/components/register`.
|
|
65
|
-
//
|
|
66
|
-
// STYLE, on both nodes, and the precedence is the part that is easy to get silently wrong. The
|
|
67
|
-
// wrapper composes exactly two arrays, and this reproduces both:
|
|
68
|
-
//
|
|
69
|
-
// owner [scrollViewBaseStyle, style] base UNDER the app's, so an explicit
|
|
70
|
-
// flexDirection still wins
|
|
71
|
-
// slot [contentContainerStyle, {flexDirection:'row'}] row OVER the app's, on horizontal only
|
|
72
|
-
//
|
|
73
|
-
// Which is why the two halves use different seams rather than one. `contentContainerStyle` is
|
|
74
|
-
// written by the app on the OWNER and belongs to the slot, so it travels through `slotProps` — a
|
|
75
|
-
// pure RENAME (`contentContainerStyle` -> the slot's `style`) that goes through the slot's own
|
|
76
|
-
// `routeProp` and inherits style merging, class merging and the already-published guard. The
|
|
77
|
-
// CONSTANT half is a `payloadFold`, because a fold is where precedence can be expressed: the
|
|
78
|
-
// owner's puts the base first, the slot's puts the row direction last. A redirect that also tried
|
|
79
|
-
// to compose would have to pick one order for both.
|
|
80
|
-
//
|
|
81
|
-
// The slot's fold is assigned to the node inside `buildStructure`, not declared on the behavior:
|
|
82
|
-
// `IHostBehavior.foldPayload` is the OWNER's, wired by `attachHostBehavior`, and a behavior that
|
|
83
|
-
// builds a node owns what that node carries.
|
|
1
|
+
// ScrollView's host behavior, platform-invariant half — pilot for `buildStructure` + `childHost`,
|
|
2
|
+
// `slotProps`, `slotDerived`, `claimedChildren`. Only the RefreshControl claim differs by platform
|
|
3
|
+
// (`index.ios` beside the content view, `index.android` wraps it); everything else is shared.
|
|
4
|
+
// TODO(rn-parity): `keyboardShouldPersistTaps` is type-only — no capture-phase responder
|
|
5
|
+
// negotiation eats a tap-elsewhere to dismiss the keyboard (`ScrollView.js:1360-1590`). Needs
|
|
6
|
+
// `TextInputState.isTextInput`-equivalent wiring on this tag; see audit skill.
|
|
7
|
+
// TODO(rn-parity): `stickyHeaderHiddenOnScroll` is entirely absent (no prop, no state, no
|
|
8
|
+
// wiring). Vendor composes `Animated.diffClamp` over the scroll delta onto the sticky translateY
|
|
9
|
+
// (`ScrollViewStickyHeader.js:39,84-103`); our sticky pin is a discrete number, not live Animated.
|
|
10
|
+
// A composed primitive could not become a tag from props alone: `foldPayload` gives a tag its
|
|
11
|
+
// wrapper's PROP MAPPING, but every adapter's ScrollView wrapper also built the same two nodes as
|
|
12
|
+
// a framework component instance — this file gives the tag the wrapper's COMPOSITION too.
|
|
13
|
+
// `buildStructure` runs at `createElement`, before any prop routes, so it can't read `horizontal`
|
|
14
|
+
// — it doesn't need to: the axis is a SEPARATE intrinsic (`horizontal-scroll-view`, a different
|
|
15
|
+
// native ViewManager on Android), so one behavior per tag already knows its own content intrinsic.
|
|
16
|
+
// The ENGINE is the single owner of the content node: an adapter wrapper building one for the
|
|
17
|
+
// same tag would double-commit it if registered while the wrapper still stands.
|
|
18
|
+
// STYLE precedence differs by node: owner is [base, style] (base UNDER the app's), slot is
|
|
19
|
+
// [contentContainerStyle, rowStyle] (row OVER the app's, horizontal only).
|
|
20
|
+
// `contentContainerStyle` travels through `slotProps` as a pure RENAME onto the slot's `style`;
|
|
21
|
+
// the row-direction CONSTANT is a `payloadFold` instead, assigned to the slot node inside
|
|
22
|
+
// `buildStructure` since `IHostBehavior.foldPayload` is only ever the OWNER's.
|
|
84
23
|
import { appendChild, appListenerFor, createElement, dlog, registerHostBehavior, setBehaviorListener, setEventListener, } from '@symbiote-native/engine';
|
|
85
24
|
import { descriptorFor } from '../../component-names';
|
|
86
25
|
import { didContentSizeChange, readLayoutDimension, } from '../../view/render-scroll-view.js';
|
|
@@ -100,71 +39,25 @@ const SLOT_DERIVED = [
|
|
|
100
39
|
'snapToAlignment',
|
|
101
40
|
'removeClippedSubviews',
|
|
102
41
|
];
|
|
103
|
-
// A `<RefreshControl>` written among the app's children is claimed
|
|
104
|
-
//
|
|
105
|
-
// through `descriptorFor`, so this is `PullToRefreshView` on iOS and `AndroidSwipeRefreshLayout`
|
|
106
|
-
// on Android without either name appearing here.
|
|
42
|
+
// A `<RefreshControl>` written among the app's children is claimed; WHAT the owner does with it
|
|
43
|
+
// is the one thing that genuinely differs per platform — see the platform files.
|
|
107
44
|
export const REFRESH_CONTROL = descriptorFor('refresh-control').component;
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
// rather than read off props, which is the same source `buildStructure` picked the content
|
|
117
|
-
// intrinsic from; an app that also writes `horizontal` on the vertical tag is contradicting the
|
|
118
|
-
// element it chose, and the tag wins.
|
|
119
|
-
//
|
|
120
|
-
// `nestedScrollEnabled` defaults ON only on the Android RefreshControl WRAP path, as RN does
|
|
121
|
-
// (`ScrollView.js:1862`); elsewhere only the authored value is sent.
|
|
122
|
-
// THE OWNER'S FOLD IS GONE (2026-09-18) — `foldScrollViewProps` in `SymbioteFabricProps.cpp`. Every
|
|
123
|
-
// input it had was the node's own bag plus the AXIS, and the axis is the tag (`scroll-view` vs
|
|
124
|
-
// `horizontal-scroll-view`), so it was a tag rule by every criterion: the base style composition,
|
|
125
|
-
// `nestedScrollEnabled`, the `horizontal` strip, the asymmetric bounce pair, the two ViewConfig-less
|
|
126
|
-
// strips, and `decelerationRate`. Contract:
|
|
127
|
-
// `core/engine/cpp/tests/js/scroll-view-payload.itest.ts`.
|
|
128
|
-
//
|
|
129
|
-
// IT WAS BLOCKED ON A MISSING LOG, not on anything about the rule. An app writing `horizontal` on
|
|
130
|
-
// the vertical tag has it IGNORED, and this fold `dlog`'d where to write it instead — while
|
|
131
|
-
// `core/engine/cpp` had no logging facility at all, only `throw jsi::JSError`. Moving the rule as
|
|
132
|
-
// written would have deleted a diagnostic, which `<keep_logs_gate_behind_DEBUG>` forbids. So
|
|
133
|
-
// `SymbioteDebug.h` was built first and this is its first caller
|
|
134
|
-
// (`core/engine/cpp/tests/js/native-debug-log.itest.ts`).
|
|
135
|
-
//
|
|
136
|
-
// `decelerationRate`'s two constants went with it and are `#ifdef ANDROID` there: on iOS BOTH tags
|
|
137
|
-
// resolve to `RCTScrollView`, so a component name cannot tell iOS-vertical from Android-vertical the
|
|
138
|
-
// way `foldSwitchProps` can. That leaves the Android half outside headless reach — the same gap
|
|
139
|
-
// already recorded for `android_ripple`, and the only part of this rule a test here cannot see.
|
|
140
|
-
// THE SLOT'S FOLD IS GONE (2026-09-18) — `foldScrollContentProps` in `SymbioteFabricProps.cpp`.
|
|
141
|
-
// Its two halves came from different places and the second is why it took until now:
|
|
142
|
-
//
|
|
143
|
-
// rowStyle a CONSTANT, horizontal only, composed OVER the app's contentContainerStyle
|
|
144
|
-
// — a function of the content node's OWN tag, portable from the start
|
|
145
|
-
// collapsableChildren DERIVED from `maintainVisibleContentPosition` / `snapToAlignment`, which
|
|
146
|
-
// stay on the OWNER
|
|
147
|
-
//
|
|
148
|
-
// "A per-node rule cannot reach another node" is what this file used to say, and it was a fact about
|
|
149
|
-
// the JS FOLD rather than about the engine: the tree lives in C++, so a node knows its parent and
|
|
150
|
-
// `fabricProps` now takes `ownerProps` from it. Contract:
|
|
151
|
-
// `core/engine/cpp/tests/js/scroll-content-payload.itest.ts`.
|
|
152
|
-
// `rowStyle` USED TO BE A PARAMETER HERE and is not one any more: the content node's row direction
|
|
153
|
-
// is decided in the engine from that node's OWN tag (`horizontal-scroll-content`), so this builder
|
|
154
|
-
// no longer needs to know the axis to build it.
|
|
45
|
+
// foldScrollViewProps in C++ owns the OWNER's fold: the per-axis base style UNDER the app's,
|
|
46
|
+
// decelerationRate resolved from RN's two words to a platform friction constant, and the
|
|
47
|
+
// horizontal strip and bounce pair — all tag rules, since strings reach Fabric unreadable.
|
|
48
|
+
// `horizontal` is a real C++ prop and the separate ViewManager is ANDROID's — on iOS both tags
|
|
49
|
+
// resolve to RCTScrollView, so the prop is what turns the axis there. Written from the TAG, not
|
|
50
|
+
// read off props; an app also writing `horizontal` on the vertical tag loses to the tag.
|
|
51
|
+
// foldScrollContentProps in C++ owns the slot's fold: rowStyle (horizontal-only constant) and
|
|
52
|
+
// collapsableChildren, derived from the OWNER's maintainVisibleContentPosition/snapToAlignment.
|
|
155
53
|
function buildContent(contentIntrinsic) {
|
|
156
54
|
return (node) => {
|
|
157
55
|
const descriptor = descriptorFor(contentIntrinsic);
|
|
158
56
|
const content = createElement(descriptor.component, descriptor.isText, contentIntrinsic);
|
|
159
|
-
// `collapsable: false`
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// prop is ABSENT because that is the only thing distinguishing the two routes).
|
|
164
|
-
// Lands directly on the owner, because `node.childHost` is still undefined here: the engine
|
|
165
|
-
// assigns it from what this returns. That ordering is why `buildStructure` RETURNS the slot
|
|
166
|
-
// instead of setting the field itself — a behavior that set it first would redirect its own
|
|
167
|
-
// structure into the slot it was building.
|
|
57
|
+
// `collapsable: false` is a TAG constant now (`foldScrollContentProps`, `ScrollView.js:1747`),
|
|
58
|
+
// not seeded here — `scroll-content-payload.itest.ts` asserts the authored prop stays ABSENT.
|
|
59
|
+
// Lands on the owner: `node.childHost` is undefined here, so `buildStructure` RETURNS the
|
|
60
|
+
// slot instead of setting the field itself.
|
|
168
61
|
appendChild(node, content);
|
|
169
62
|
return content;
|
|
170
63
|
};
|
|
@@ -173,14 +66,10 @@ function buildContent(contentIntrinsic) {
|
|
|
173
66
|
// fire the app's handler — RN dedupes the same way (`_handleContentOnLayout`). Off the node: this
|
|
174
67
|
// exists only for the ScrollViews an app wired a handler to.
|
|
175
68
|
const lastContentSize = new WeakMap();
|
|
176
|
-
// RN synthesizes onContentSizeChange from the CONTENT view's own onLayout — there
|
|
177
|
-
// content-size event
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
// The app's callback takes `(width, height)`, not an event, which is why `contentSizeChange` is an
|
|
182
|
-
// OWNED listener: `setEventListener` wraps an ordinary listener as `(event) => handler(event)` and
|
|
183
|
-
// would call a two-number handler with one event. Owned names are stashed raw instead.
|
|
69
|
+
// RN synthesizes onContentSizeChange from the CONTENT view's own onLayout — there's no native
|
|
70
|
+
// content-size event. A tag has no inner node of its own, so the behavior installs it on the slot.
|
|
71
|
+
// The app's callback takes `(width, height)`, not an event, so `contentSizeChange` is an OWNED
|
|
72
|
+
// listener: setEventListener would wrap it as `(event) => handler(event)` and call it wrong.
|
|
184
73
|
function contentSizeListener(owner) {
|
|
185
74
|
return (event) => {
|
|
186
75
|
const handler = appListenerFor(owner, 'contentSizeChange');
|
|
@@ -200,16 +89,10 @@ function contentSizeListener(owner) {
|
|
|
200
89
|
handler(width, height);
|
|
201
90
|
};
|
|
202
91
|
}
|
|
203
|
-
// RN installs the content `onLayout` only when the app passed `onContentSizeChange
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
// prop.
|
|
208
|
-
//
|
|
209
|
-
// It follows the LISTENER rather than a commit, which is what `onOwnedListenerChange` is for: a
|
|
210
|
-
// listener flip changes no payload by itself, so the commit after it is a no-op and a post-commit
|
|
211
|
-
// hook would never fire. Measured on exactly this — the wire worked (mount commits for other
|
|
212
|
-
// reasons) and the UNWIRE silently did not.
|
|
92
|
+
// RN installs the content `onLayout` only when the app passed `onContentSizeChange`: `onLayout`
|
|
93
|
+
// is gated, so wiring it unconditionally would put `onLayout: true` in every ScrollView's payload.
|
|
94
|
+
// Follows the LISTENER rather than a commit, since a listener flip changes no payload by itself —
|
|
95
|
+
// the commit after it is a no-op and a post-commit hook would never fire.
|
|
213
96
|
function syncContentSizeWiring(owner, wired) {
|
|
214
97
|
const slot = owner.childHost;
|
|
215
98
|
if (slot === undefined)
|
|
@@ -233,11 +116,9 @@ function syncOwnedListener(owner, name, wired) {
|
|
|
233
116
|
}
|
|
234
117
|
function scrollBehavior(contentIntrinsic, rowStyle, platform) {
|
|
235
118
|
return {
|
|
236
|
-
// `scroll` and `layout` are owned for the collision reason
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
// is single-slot — so a behavior that installed either without owning it would silently evict
|
|
240
|
-
// the app's.
|
|
119
|
+
// `scroll` and `layout` are owned for the collision reason, not because the behavior consumes
|
|
120
|
+
// them: node.listeners is single-slot, so installing either without owning it would silently
|
|
121
|
+
// evict the app's own handler.
|
|
241
122
|
ownedListeners: [
|
|
242
123
|
'contentSizeChange',
|
|
243
124
|
'scroll',
|
|
@@ -248,20 +129,16 @@ function scrollBehavior(contentIntrinsic, rowStyle, platform) {
|
|
|
248
129
|
slotDerived: [...SLOT_DERIVED, ...(platform.slotDerived ?? [])],
|
|
249
130
|
claimedChildren: { [REFRESH_CONTROL]: platform.claimMode },
|
|
250
131
|
buildStructure: buildContent(contentIntrinsic),
|
|
251
|
-
// The scroll dispatcher is installed
|
|
252
|
-
//
|
|
253
|
-
// forward per scroll event on a ScrollView with no sticky child, which is what RN pays too.
|
|
254
|
-
// Nothing else is taken — no timer, and the two conditional listeners are wired on a flip.
|
|
132
|
+
// The scroll dispatcher is installed unconditionally: it drives the sticky AnimatedValue, and
|
|
133
|
+
// a header can register long after this node was created.
|
|
255
134
|
attach(node) {
|
|
256
135
|
markScrollOwner(node);
|
|
257
136
|
setBehaviorListener(node, 'scroll', event => handleOwnerScroll(node, event));
|
|
258
137
|
installResponderPredicates(node);
|
|
259
138
|
},
|
|
260
139
|
onOwnedListenerChange: syncOwnedListener,
|
|
261
|
-
// The one beat
|
|
262
|
-
//
|
|
263
|
-
// over the ScrollViews alone, and `reconcileStickyIndices` returns on a WeakSet miss for any
|
|
264
|
-
// that never used the prop.
|
|
140
|
+
// The one beat where the app's children are all present — no hook reports a children CHANGE,
|
|
141
|
+
// and reconcileStickyIndices returns on a WeakSet miss for any that never used the prop.
|
|
265
142
|
afterCommit: reconcileStickyIndices,
|
|
266
143
|
detach(node) {
|
|
267
144
|
lastContentSize.delete(node);
|
|
@@ -274,13 +151,9 @@ function scrollBehavior(contentIntrinsic, rowStyle, platform) {
|
|
|
274
151
|
export function registerScrollViewBehaviors(platform) {
|
|
275
152
|
registerHostBehavior(SCROLL_VIEW_TAG, scrollBehavior('scroll-content', undefined, platform));
|
|
276
153
|
registerHostBehavior(HORIZONTAL_SCROLL_VIEW_TAG, scrollBehavior('horizontal-scroll-content', { flexDirection: 'row' }, platform));
|
|
277
|
-
// REGISTRATIONS WITH NO RUNTIME,
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// built by `buildStructure` and named by no app, which is exactly the shape that trap has.
|
|
281
|
-
//
|
|
282
|
-
// Same reasoning as `activity-indicator-spinner`'s stub: a registration is how this codebase says
|
|
283
|
-
// a tag HAS platform semantics, which is the claim being made.
|
|
154
|
+
// REGISTRATIONS WITH NO RUNTIME, what hand the content tags to the host: a tag with no behavior
|
|
155
|
+
// registered carries an EMPTY tagName in C++ and no rule can fire for it. A registration is how
|
|
156
|
+
// this codebase says a tag HAS platform semantics.
|
|
284
157
|
for (const contentTag of ['scroll-content', 'horizontal-scroll-content'])
|
|
285
158
|
registerHostBehavior(contentTag, { attach() { }, detach() { } });
|
|
286
159
|
// With the scroll views, never on its own: a sticky header is meaningless without an owner to
|
|
@@ -6,13 +6,5 @@ export declare function hasStickyHeaders(owner: ISymbioteNode): boolean;
|
|
|
6
6
|
export declare function syncOwnerLayout(owner: ISymbioteNode): void;
|
|
7
7
|
export declare function handleOwnerScroll(owner: ISymbioteNode, event: ISymbioteEvent): void;
|
|
8
8
|
export declare function releaseStickyOwner(owner: ISymbioteNode): void;
|
|
9
|
-
/**
|
|
10
|
-
* Bring the synthesized wrappers in line with `stickyHeaderIndices`. Called from the ScrollView
|
|
11
|
-
* behavior's `afterCommit`, the one beat at which the app's children are all present.
|
|
12
|
-
*
|
|
13
|
-
* O(slot children) per commit, once — never per mutation. Angular's controller coalesces to one
|
|
14
|
-
* pass per change detection for exactly this reason: its per-mutation walk was O(M²) and died at
|
|
15
|
-
* 801 children.
|
|
16
|
-
*/
|
|
17
9
|
export declare function reconcileStickyIndices(owner: ISymbioteNode): void;
|
|
18
10
|
export declare const stickyHeaderBehavior: IHostBehavior;
|