@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.
Files changed (96) hide show
  1. package/README.md +39 -14
  2. package/android/CMakeLists.txt +51 -0
  3. package/android/build.gradle +90 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/cpp/SymbioteEngineJni.cpp +72 -0
  6. package/android/src/main/java/dev/symbiotenative/engine/SymbioteEngineModule.kt +43 -0
  7. package/android/src/main/java/dev/symbiotenative/engine/SymbioteEnginePackage.kt +35 -0
  8. package/build/accessibility-info/shared.js +1 -1
  9. package/build/accessibility-props.d.ts +1 -7
  10. package/build/accessibility-props.js +22 -21
  11. package/build/animated/animations/composition.d.ts +1 -1
  12. package/build/animated/animations/composition.js +18 -4
  13. package/build/animated/easing.d.ts +3 -2
  14. package/build/animated/easing.js +17 -88
  15. package/build/animated/event.js +6 -1
  16. package/build/animated/graph.d.ts +2 -0
  17. package/build/animated/graph.js +14 -0
  18. package/build/animated/host-binding.d.ts +39 -0
  19. package/build/animated/host-binding.js +278 -0
  20. package/build/animated/index.d.ts +1 -1
  21. package/build/animated/leaf-lifecycle.js +10 -22
  22. package/build/animated/mock.d.ts +1 -19
  23. package/build/animated/props.js +1 -1
  24. package/build/animated/rgba.js +16 -50
  25. package/build/events/index.js +123 -33
  26. package/build/fabric-props.d.ts +1 -1
  27. package/build/fabric-props.js +129 -182
  28. package/build/fabric.d.ts +10 -0
  29. package/build/fabric.js +40 -0
  30. package/build/host-access.d.ts +125 -0
  31. package/build/host-access.js +280 -0
  32. package/build/host-behavior.d.ts +107 -7
  33. package/build/host-behavior.js +309 -31
  34. package/build/image-source-write.d.ts +16 -0
  35. package/build/image-source-write.js +65 -0
  36. package/build/imperative.d.ts +49 -0
  37. package/build/imperative.js +258 -0
  38. package/build/index.d.ts +18 -10
  39. package/build/index.js +65 -10
  40. package/build/mutation-buffer.d.ts +222 -0
  41. package/build/mutation-buffer.js +491 -0
  42. package/build/native-engine.d.ts +182 -0
  43. package/build/native-engine.js +178 -0
  44. package/build/native-tree-host.d.ts +25 -0
  45. package/build/native-tree-host.js +66 -0
  46. package/build/node.d.ts +191 -60
  47. package/build/node.js +1034 -327
  48. package/build/pan-responder/index.d.ts +2 -2
  49. package/build/pan-responder/index.js +37 -56
  50. package/build/platform-color/index.d.ts +1 -1
  51. package/build/platform-color/index.js +11 -4
  52. package/build/process-background-image/index.js +30 -566
  53. package/build/process-background-longhands.d.ts +4 -0
  54. package/build/process-background-longhands.js +44 -0
  55. package/build/process-box-shadow/index.js +23 -187
  56. package/build/process-filter.js +27 -300
  57. package/build/process-transform/index.d.ts +1 -1
  58. package/build/process-transform/index.js +25 -107
  59. package/build/process-transform-origin/index.d.ts +1 -1
  60. package/build/process-transform-origin/index.js +29 -102
  61. package/build/registry.d.ts +36 -0
  62. package/build/registry.js +73 -0
  63. package/build/sound-manager/index.d.ts +3 -0
  64. package/build/sound-manager/index.js +36 -0
  65. package/build/structured-style.d.ts +10 -0
  66. package/build/structured-style.js +180 -0
  67. package/build/style-registry/index.d.ts +14 -0
  68. package/build/style-registry/index.js +60 -11
  69. package/build/styles.d.ts +5 -1
  70. package/build/surface.d.ts +31 -2
  71. package/build/surface.js +138 -42
  72. package/build/text-input-state.d.ts +1 -0
  73. package/build/text-input-state.js +17 -3
  74. package/build/tree-host.d.ts +307 -0
  75. package/build/tree-host.js +211 -0
  76. package/build/view-config.js +4 -4
  77. package/codegen-specs/NativeSymbioteEngine.ts +27 -0
  78. package/cpp/SymbioteDebug.cpp +51 -0
  79. package/cpp/SymbioteDebug.h +54 -0
  80. package/cpp/SymbioteEngineBindings.cpp +232 -0
  81. package/cpp/SymbioteEngineBindings.h +59 -0
  82. package/cpp/SymbioteFabricProps.cpp +2619 -0
  83. package/cpp/SymbioteFabricProps.h +223 -0
  84. package/cpp/SymbioteTree.cpp +2478 -0
  85. package/cpp/SymbioteTree.h +257 -0
  86. package/ios/SymbioteEngineModule.h +25 -0
  87. package/ios/SymbioteEngineModule.mm +44 -0
  88. package/package.json +31 -3
  89. package/react-native.config.cjs +23 -0
  90. package/symbiote-engine.podspec +42 -0
  91. package/build/animated/bezier.d.ts +0 -1
  92. package/build/animated/bezier.js +0 -102
  93. package/build/commit.d.ts +0 -49
  94. package/build/commit.js +0 -1030
  95. package/build/tags.d.ts +0 -2
  96. 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