@symbiote-native/engine 0.4.0 → 1.0.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/README.md +39 -14
- package/android/CMakeLists.txt +51 -0
- package/android/build.gradle +90 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/SymbioteEngineJni.cpp +72 -0
- package/android/src/main/java/dev/symbiotenative/engine/SymbioteEngineModule.kt +43 -0
- package/android/src/main/java/dev/symbiotenative/engine/SymbioteEnginePackage.kt +35 -0
- package/build/accessibility-info/shared.js +1 -1
- package/build/accessibility-props.d.ts +1 -7
- package/build/accessibility-props.js +22 -21
- package/build/animated/animations/composition.d.ts +1 -1
- package/build/animated/animations/composition.js +18 -4
- package/build/animated/easing.d.ts +3 -2
- package/build/animated/easing.js +17 -88
- package/build/animated/event.js +6 -1
- package/build/animated/graph.d.ts +2 -0
- package/build/animated/graph.js +14 -0
- package/build/animated/host-binding.d.ts +39 -0
- package/build/animated/host-binding.js +278 -0
- package/build/animated/index.d.ts +1 -1
- package/build/animated/leaf-lifecycle.js +10 -22
- package/build/animated/mock.d.ts +1 -19
- package/build/animated/props.js +1 -1
- package/build/animated/rgba.js +16 -50
- package/build/events/index.js +123 -33
- package/build/fabric-props.d.ts +1 -1
- package/build/fabric-props.js +129 -182
- package/build/fabric.d.ts +10 -0
- package/build/fabric.js +40 -0
- package/build/host-access.d.ts +125 -0
- package/build/host-access.js +280 -0
- package/build/host-behavior.d.ts +107 -7
- package/build/host-behavior.js +309 -31
- package/build/image-source-write.d.ts +16 -0
- package/build/image-source-write.js +65 -0
- package/build/imperative.d.ts +49 -0
- package/build/imperative.js +258 -0
- package/build/index.d.ts +18 -10
- package/build/index.js +65 -10
- package/build/mutation-buffer.d.ts +222 -0
- package/build/mutation-buffer.js +491 -0
- package/build/native-engine.d.ts +182 -0
- package/build/native-engine.js +178 -0
- package/build/native-tree-host.d.ts +25 -0
- package/build/native-tree-host.js +66 -0
- package/build/node.d.ts +191 -60
- package/build/node.js +1034 -327
- package/build/pan-responder/index.d.ts +2 -2
- package/build/pan-responder/index.js +37 -56
- package/build/platform-color/index.d.ts +1 -1
- package/build/platform-color/index.js +11 -4
- package/build/process-background-image/index.js +30 -566
- package/build/process-background-longhands.d.ts +4 -0
- package/build/process-background-longhands.js +44 -0
- package/build/process-box-shadow/index.js +23 -187
- package/build/process-filter.js +27 -300
- package/build/process-transform/index.d.ts +1 -1
- package/build/process-transform/index.js +25 -107
- package/build/process-transform-origin/index.d.ts +1 -1
- package/build/process-transform-origin/index.js +29 -102
- package/build/registry.d.ts +36 -0
- package/build/registry.js +73 -0
- package/build/sound-manager/index.d.ts +3 -0
- package/build/sound-manager/index.js +36 -0
- package/build/structured-style.d.ts +10 -0
- package/build/structured-style.js +180 -0
- package/build/style-registry/index.d.ts +14 -0
- package/build/style-registry/index.js +60 -11
- package/build/styles.d.ts +5 -1
- package/build/surface.d.ts +31 -2
- package/build/surface.js +138 -42
- package/build/text-input-state.d.ts +1 -0
- package/build/text-input-state.js +17 -3
- package/build/tree-host.d.ts +307 -0
- package/build/tree-host.js +211 -0
- package/build/view-config.js +4 -4
- package/codegen-specs/NativeSymbioteEngine.ts +27 -0
- package/cpp/SymbioteDebug.cpp +51 -0
- package/cpp/SymbioteDebug.h +54 -0
- package/cpp/SymbioteEngineBindings.cpp +232 -0
- package/cpp/SymbioteEngineBindings.h +59 -0
- package/cpp/SymbioteFabricProps.cpp +2619 -0
- package/cpp/SymbioteFabricProps.h +223 -0
- package/cpp/SymbioteTree.cpp +2478 -0
- package/cpp/SymbioteTree.h +257 -0
- package/ios/SymbioteEngineModule.h +25 -0
- package/ios/SymbioteEngineModule.mm +44 -0
- package/package.json +31 -3
- package/react-native.config.cjs +23 -0
- package/symbiote-engine.podspec +42 -0
- package/build/animated/bezier.d.ts +0 -1
- package/build/animated/bezier.js +0 -102
- package/build/commit.d.ts +0 -49
- package/build/commit.js +0 -1030
- package/build/tags.d.ts +0 -2
- package/build/tags.js +0 -40
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <folly/dynamic.h>
|
|
4
|
+
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace symbiote {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A node's authored prop bag as the FLAT payload Fabric's C++ props expect.
|
|
12
|
+
*
|
|
13
|
+
* The reference is `core/engine/src/fabric-props.ts`, called by the TypeScript applier on the line
|
|
14
|
+
* `SymbioteTree.cpp` hands `node.props` to `createNode`. Read the two side by side, same as
|
|
15
|
+
* `SymbioteTree.cpp` and `tree-applier.ts`: what is here mirrors it, and what is deliberately NOT
|
|
16
|
+
* here is enumerated in the implementation's header.
|
|
17
|
+
*
|
|
18
|
+
* `component` is the node's OWN view name, never `materialize`'s resolved one. A `<Text>` inside a
|
|
19
|
+
* `<Text>` commits as `RCTVirtualText`, and the reference keys its processors on the handle's
|
|
20
|
+
* component, which is never rewritten to the virtual name. Passing the resolved name would silently
|
|
21
|
+
* change which processors run on every nested text node.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* A behavior's own payload fold, for the ones that genuinely cannot live on this side.
|
|
25
|
+
*
|
|
26
|
+
* THIS USED TO SAY "IT CANNOT BE PORTED", FULL STOP, AND THAT WAS TOO WIDE. Two have moved —
|
|
27
|
+
* `<text-input>`'s W3C aliases and `<pressable>`'s accessibility/ripple/machine-key rule — because
|
|
28
|
+
* both are a function of the TAG and nothing else, which is the definition of user-agent behavior.
|
|
29
|
+
* What the old wording was actually right about is the rest:
|
|
30
|
+
*
|
|
31
|
+
* a third-party view's `validAttributes[*].process`, since `registerHostBehavior` takes any tag
|
|
32
|
+
* and a C++ table would cover only the primitives that happen to be ours;
|
|
33
|
+
*
|
|
34
|
+
* `stickyFold`, which is built PER NODE and reads `runtime.state.translateY` — live JS state that
|
|
35
|
+
* is not a prop and moves on every scroll frame. Nothing folded ahead of time can serve it.
|
|
36
|
+
*
|
|
37
|
+
* `stickyFold` IS THE LAST ONE, as of 2026-09-18, and the case that nearly joined it is the useful
|
|
38
|
+
* comparison. TouchableHighlight's underlay was listed here too, on the same "live state" reasoning:
|
|
39
|
+
* `shown` flips inside a gesture and no props-only rule can see it. True, and not the question. That
|
|
40
|
+
* fold's RULE was three ordinary inputs and one bit, so the bit crosses (`OP_SET_UNDERLAY_SHOWN`) and
|
|
41
|
+
* the rule is `foldTouchableHighlightUnderlay` below.
|
|
42
|
+
*
|
|
43
|
+
* THAT PARAGRAPH FIRST SAID THE DIFFERENCE WAS RATE — "`translateY` moves every frame while a finger
|
|
44
|
+
* drags" — AND IT IS WRONG, checked against the vendor an hour later. The value this fold commits is
|
|
45
|
+
* the DEBOUNCED one (`ScrollViewStickyHeader.js:144-159`, 15 ms on Android and 64 ms on iOS); the
|
|
46
|
+
* per-frame half rides an Animated graph and never passes through a fold at all. So sticky commits
|
|
47
|
+
* at roughly the rate a bit would cross, and rate does not separate them.
|
|
48
|
+
*
|
|
49
|
+
* WHAT ACTUALLY KEEPS IT HERE is that its fold is the DECLARATIVE HALF OF A PAIR. The smooth pin is
|
|
50
|
+
* an `AnimatedProps` leaf built in JS carrying `{transform, zIndex}`, written imperatively to the
|
|
51
|
+
* same node, and the fold carries the same `zIndex` so an imperative write cannot drop it
|
|
52
|
+
* (`behaviors/scroll-view/sticky.ts`). Move the fold's copy to a rule and the constant exists in C++
|
|
53
|
+
* AND in that props map — a real mirror, with a real reason, which is the shape this migration
|
|
54
|
+
* deletes rather than creates. The leaf is JS because `Animated` is; the fold is JS because the leaf
|
|
55
|
+
* is.
|
|
56
|
+
*
|
|
57
|
+
* So the open question is not "port the fold" but "should the sticky pin be native at all" — which is
|
|
58
|
+
* what a browser does (`position: sticky` is the engine's, with no page-side animated value) and is a
|
|
59
|
+
* project rather than a port. Recorded here so the next reader does not re-derive the rate argument
|
|
60
|
+
* and act on it.
|
|
61
|
+
*
|
|
62
|
+
* So a fold that is a property of the tag moves here; a fold that is a property of the instance
|
|
63
|
+
* stays a JS closure and this side calls it. `SymbioteTree` supplies the wrapper; the cost is one
|
|
64
|
+
* JSI round trip plus a bag marshalled both ways, per folded node per commit.
|
|
65
|
+
*/
|
|
66
|
+
using IPayloadFold = std::function<folly::dynamic(const folly::dynamic &)>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `tagName` is the INTRINSIC TAG (`pressable`), empty for a node that carries no host behavior.
|
|
70
|
+
*
|
|
71
|
+
* It is separate from `component` because it has to be: a `<pressable>` commits as `RCTView`, so
|
|
72
|
+
* the Fabric view name cannot distinguish it from a plain view, and a rule keyed off the view name
|
|
73
|
+
* would either miss every pressable or fire on every view. `<text-input>` is the case that hid
|
|
74
|
+
* this — its view name happens to name it uniquely, so the first port needed no tag at all.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* `ownerProps` is the PARENT node's props, or nullptr at a root.
|
|
78
|
+
*
|
|
79
|
+
* WHY A RULE MAY READ ITS PARENT AT ALL, when the whole point of a tag rule is that it is a function
|
|
80
|
+
* of one node's own bag. Several of RN's component bodies build a node whose props are DERIVED from
|
|
81
|
+
* the node above it — ScrollView's content view takes `collapsableChildren` from the scroller's
|
|
82
|
+
* `maintainVisibleContentPosition`, ImageBackground's image takes its size from the wrapper's style.
|
|
83
|
+
* In the wrapper world that was ordinary: one `render()` saw both. Split into per-node rules it looks
|
|
84
|
+
* impossible, and this argument is why it is not: the TREE LIVES IN C++ NOW, so a node already knows
|
|
85
|
+
* its parent and reading it costs a pointer hop rather than a JS closure and a crossing.
|
|
86
|
+
*
|
|
87
|
+
* It does NOT make everything portable. A rule may read the parent's PROPS, which are declarative
|
|
88
|
+
* and present at commit time. It cannot read live JS state (`stickyFold`'s `translateY`) or anything
|
|
89
|
+
* a framework computes per render; those stay JS folds.
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* `hasPressListener` — whether the APP has a callback wired to `press`, which is a name the behavior
|
|
93
|
+
* owns and which therefore never becomes a prop.
|
|
94
|
+
*
|
|
95
|
+
* THIS PARAGRAPH USED TO SAY THE OPPOSITE, and the correction is the useful part. The boundary above
|
|
96
|
+
* listed "an owned LISTENER (`focusable`'s `onPress !== undefined`, which lives in the stash and not
|
|
97
|
+
* in any bag)" beside live JS state, as a thing a rule could never see. That conflated two different
|
|
98
|
+
* facts about a listener: its FUNCTION, which is the application's and must never cross, and its
|
|
99
|
+
* EXISTENCE, which is one bit the platform is entitled to know.
|
|
100
|
+
*
|
|
101
|
+
* The browser settles which is which rather than taste. A UA computes focusability itself, and it
|
|
102
|
+
* can, because `addEventListener` is the UA's own API — the browser knows which of its elements
|
|
103
|
+
* carry a click handler while the handler's body stays the page's. So the bit crosses, once per
|
|
104
|
+
* flip, as `OP_SET_OWNED_LISTENER`; the closure stays in the JS stash where it always was.
|
|
105
|
+
*
|
|
106
|
+
* What is genuinely unreachable is narrower than the old wording: a value only JS can COMPUTE. A
|
|
107
|
+
* value JS merely happens to HOLD is a wiring question, and wiring is cheap.
|
|
108
|
+
*/
|
|
109
|
+
/**
|
|
110
|
+
* A rule's way of asking for an ANCESTOR further up than its parent.
|
|
111
|
+
*
|
|
112
|
+
* `ownerProps` answers the common case and `Button`'s label is the one that needs more: its style is
|
|
113
|
+
* a function of the BUTTON's `color` and `disabled` while its parent is the wrapping view, so the
|
|
114
|
+
* node it must read is a grandparent on iOS and a parent on Android. "Two up" is the wrong question
|
|
115
|
+
* to build a seam around — what the rule wants is **the nearest ancestor that is a button**, which
|
|
116
|
+
* is a CSS ancestor selector and is the shape a browser would use.
|
|
117
|
+
*
|
|
118
|
+
* A function pointer plus a context rather than a `std::function`, because this is on the per-node
|
|
119
|
+
* commit path: a `std::function` would allocate for every node whether or not any rule asks. This
|
|
120
|
+
* costs one pointer pair to pass and one indirect call only when a rule actually looks.
|
|
121
|
+
*
|
|
122
|
+
* The walk is the TREE's, which is why this is a callback at all — `SymbioteTree` owns `Node` and
|
|
123
|
+
* this translation unit does not. What lives here is which tag to ask for; what lives there is how
|
|
124
|
+
* to find it.
|
|
125
|
+
*/
|
|
126
|
+
struct IAncestorLookup {
|
|
127
|
+
/** The nearest ancestor carrying `tag`, or nullptr. Never the node itself. */
|
|
128
|
+
const folly::dynamic *(*find)(const void *context, const char *tag) = nullptr;
|
|
129
|
+
const void *context = nullptr;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The parent, as the three facts a rule can ask about it.
|
|
134
|
+
*
|
|
135
|
+
* `props` was a bare parameter until the descendant rule below needed the other two, and bundling
|
|
136
|
+
* them is not tidying: a rule keyed on the parent's TAG is a different kind of rule from one keyed
|
|
137
|
+
* on its own, and this is the one place that distinction is expressible.
|
|
138
|
+
*
|
|
139
|
+
* `tagName` is what makes a DESCENDANT rule possible — the shape a user-agent stylesheet has always
|
|
140
|
+
* had (`td > *`), and the only shape that can serve `TouchableNativeFeedback` /
|
|
141
|
+
* `TouchableWithoutFeedback`. Those render no view: RN's bodies end in `cloneElement(child, {…})`,
|
|
142
|
+
* so our tag commits an anchor and the owner's props land on whatever the app wrote underneath. That
|
|
143
|
+
* child's own tag is usually EMPTY — a plain `<view>` registers no behavior — so a self-keyed rule
|
|
144
|
+
* can never reach it, and giving it the owner's tag is not available either, since it may already
|
|
145
|
+
* own one.
|
|
146
|
+
*
|
|
147
|
+
* `hasPressListener` is the parent's bit, not the node's. `focusable` on a cloned child is a
|
|
148
|
+
* function of whether the OWNER has a press callback, which is exactly the fact
|
|
149
|
+
* `OP_SET_OWNED_LISTENER` already carries — read one hop up instead of on self.
|
|
150
|
+
*/
|
|
151
|
+
struct IOwner {
|
|
152
|
+
const folly::dynamic *props = nullptr;
|
|
153
|
+
const char *tagName = nullptr;
|
|
154
|
+
bool hasPressListener = false;
|
|
155
|
+
// TouchableHighlight's two halves land on TWO nodes — background on the container, opacity on the
|
|
156
|
+
// single child (`TouchableHighlight.js:358-361, 379-383`) — so the child's rule needs the owner's
|
|
157
|
+
// feedback state, which is `ISelf`'s and therefore unreachable from down here without these. Same
|
|
158
|
+
// argument `hasPressListener` above already makes: a parent's bit, read one hop up.
|
|
159
|
+
bool underlayShown = false;
|
|
160
|
+
bool hasAnyPressListener = false;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The FIRST CHILD, as the two facts a rule can ask about it — the only seam here that reads DOWN.
|
|
165
|
+
*
|
|
166
|
+
* `IOwner`, `IAncestorLookup` and `ownerProps` all read UP, and three iterations of this migration
|
|
167
|
+
* recorded ScrollView's Android RefreshControl wrap as unportable because it is the one rule that
|
|
168
|
+
* needs the other direction: `AndroidSwipeRefreshLayout` WRAPS the scroll view, and the app's style
|
|
169
|
+
* is split across the two boxes with the wrapper taking the LAYOUT half of a style written on the
|
|
170
|
+
* node BELOW it (`ScrollView.js:1854-1863`).
|
|
171
|
+
*
|
|
172
|
+
* IT IS NOT A NEW KIND OF CLAIM, which is what makes it affordable. `ownerProps`' own argument was
|
|
173
|
+
* that the tree lives in C++, so reading another node costs a pointer hop rather than a closure and
|
|
174
|
+
* a crossing — and that argument never mentioned a direction. Upstream builds the parent FROM the
|
|
175
|
+
* child here (`cloneElement(refreshControl, {style: outer}, scrollView)`), so "derived from what it
|
|
176
|
+
* contains" is RN's shape rather than one invented for this seam; a UA has the same (`:has()`, and
|
|
177
|
+
* a table frame that has always followed its cells).
|
|
178
|
+
*
|
|
179
|
+
* THE DIRTY PATH IS THE HALF THAT IS NOT FREE, and it already existed. A rule runs when ITS node is
|
|
180
|
+
* dirty, so a wrapper reading its child re-derives only if a write to that child marks the wrapper —
|
|
181
|
+
* which `routeProp` does for `node.wrapper` under `slotDerived`. Without it the wrapper freezes at
|
|
182
|
+
* its mount frame while the scroller visibly restyles inside it.
|
|
183
|
+
*
|
|
184
|
+
* FIRST child rather than a list, deliberately: the only shape that needs this is a wrapper, and a
|
|
185
|
+
* wrapper has exactly one. A rule that wanted to survey N children would be reading the tree rather
|
|
186
|
+
* than deriving from it, which is the line this seam should not cross.
|
|
187
|
+
*/
|
|
188
|
+
struct IFirstChild {
|
|
189
|
+
const folly::dynamic *props = nullptr;
|
|
190
|
+
const char *tagName = nullptr;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The node's own facts that are NOT props — the bits a behavior owns and the platform is entitled to.
|
|
195
|
+
*
|
|
196
|
+
* It was a bare `bool hasPressListener` parameter until a second bit needed to cross, and bundling
|
|
197
|
+
* is the same move `IOwner` made when `ownerProps` grew a tag: a lone bool beside three structs is
|
|
198
|
+
* the shape that grows a fourth positional argument nobody can read at the call site.
|
|
199
|
+
*
|
|
200
|
+
* `hasPressListener` is `onPress` ALONE, which is what `focusable` asks (`TouchableOpacity.js:
|
|
201
|
+
* 336-339`). `hasAnyPressListener` is any of RN's four (`TouchableHighlight.js:296-302`), which is
|
|
202
|
+
* what "does this control react to a touch at all" asks. Two questions, deliberately not one.
|
|
203
|
+
*
|
|
204
|
+
* `underlayShown` is feedback STATE rather than wiring: TouchableHighlight's underlay, which lags the
|
|
205
|
+
* press through a hold timer that stays in JS.
|
|
206
|
+
*/
|
|
207
|
+
struct ISelf {
|
|
208
|
+
bool hasPressListener = false;
|
|
209
|
+
bool hasAnyPressListener = false;
|
|
210
|
+
bool underlayShown = false;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
folly::dynamic fabricProps(
|
|
214
|
+
const std::string &component,
|
|
215
|
+
const std::string &tagName,
|
|
216
|
+
const folly::dynamic &props,
|
|
217
|
+
const IPayloadFold &fold = {},
|
|
218
|
+
const IOwner &owner = {},
|
|
219
|
+
const ISelf &self = {},
|
|
220
|
+
const IAncestorLookup &ancestors = {},
|
|
221
|
+
const IFirstChild &firstChild = {});
|
|
222
|
+
|
|
223
|
+
} // namespace symbiote
|