@symbiote-native/engine 1.2.0 → 1.3.1

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 (110) hide show
  1. package/android/CMakeLists.txt +31 -1
  2. package/android/build.gradle +4 -1
  3. package/build/accessibility-info/index.android.js +21 -17
  4. package/build/accessibility-info/index.ios.js +2 -0
  5. package/build/accessibility-info/shared.d.ts +1 -1
  6. package/build/accessibility-props.d.ts +0 -11
  7. package/build/accessibility-props.js +30 -68
  8. package/build/alert/shared.d.ts +1 -1
  9. package/build/alert/shared.js +3 -7
  10. package/build/animated/graph.js +1 -1
  11. package/build/animated/leaf-lifecycle.js +2 -2
  12. package/build/app-state/index.d.ts +1 -1
  13. package/build/app-state/index.js +35 -27
  14. package/build/asset-source-resolver.d.ts +2 -0
  15. package/build/asset-source-resolver.js +13 -0
  16. package/build/back-handler/index.d.ts +7 -6
  17. package/build/back-handler/index.js +19 -16
  18. package/build/debug.js +8 -22
  19. package/build/dispatch.js +3 -9
  20. package/build/events/index.js +12 -3
  21. package/build/fabric-props.js +75 -180
  22. package/build/fabric.d.ts +2 -8
  23. package/build/fabric.js +27 -33
  24. package/build/host-access.d.ts +1 -128
  25. package/build/host-access.js +96 -205
  26. package/build/host-behavior.d.ts +1 -100
  27. package/build/host-behavior.js +125 -311
  28. package/build/image-loader.d.ts +4 -2
  29. package/build/image-loader.js +24 -48
  30. package/build/image-source-resolver.js +3 -7
  31. package/build/image-source-write.d.ts +1 -12
  32. package/build/image-source-write.js +28 -36
  33. package/build/imperative.d.ts +0 -28
  34. package/build/imperative.js +42 -92
  35. package/build/index.d.ts +3 -2
  36. package/build/index.js +30 -43
  37. package/build/invariant.d.ts +1 -0
  38. package/build/invariant.js +10 -0
  39. package/build/keyboard/index.js +11 -32
  40. package/build/linking/index.android.js +5 -3
  41. package/build/linking/shared.d.ts +1 -1
  42. package/build/linking/shared.js +16 -19
  43. package/build/mutation-buffer.d.ts +0 -177
  44. package/build/mutation-buffer.js +137 -308
  45. package/build/native-engine.d.ts +0 -102
  46. package/build/native-engine.js +38 -98
  47. package/build/native-events.d.ts +5 -0
  48. package/build/native-events.js +30 -18
  49. package/build/native-tree-host.d.ts +0 -21
  50. package/build/native-tree-host.js +14 -31
  51. package/build/node.d.ts +0 -212
  52. package/build/node.js +354 -774
  53. package/build/permissions-android/index.android.d.ts +59 -0
  54. package/build/permissions-android/index.android.js +47 -0
  55. package/build/permissions-android/index.d.ts +1 -115
  56. package/build/permissions-android/index.ios.d.ts +59 -0
  57. package/build/permissions-android/index.ios.js +31 -0
  58. package/build/permissions-android/index.js +3 -184
  59. package/build/permissions-android/shared.d.ts +63 -0
  60. package/build/permissions-android/shared.js +66 -0
  61. package/build/platform/index.android.js +3 -5
  62. package/build/platform/index.ios.js +3 -3
  63. package/build/platform/shared.d.ts +4 -0
  64. package/build/platform/shared.js +9 -0
  65. package/build/platform-color/index.android.d.ts +4 -0
  66. package/build/platform-color/index.android.js +7 -0
  67. package/build/platform-color/index.d.ts +2 -20
  68. package/build/platform-color/index.js +1 -45
  69. package/build/platform-color/shared.d.ts +21 -0
  70. package/build/platform-color/shared.js +41 -0
  71. package/build/post-commit.js +3 -8
  72. package/build/process-aspect-ratio.js +3 -7
  73. package/build/process-background-longhands.js +10 -19
  74. package/build/process-filter.js +11 -19
  75. package/build/process-font-variant.js +3 -7
  76. package/build/registry.d.ts +0 -33
  77. package/build/registry.js +22 -57
  78. package/build/report-error.js +4 -18
  79. package/build/settings/index.android.d.ts +6 -0
  80. package/build/settings/index.android.js +21 -0
  81. package/build/settings/index.d.ts +1 -8
  82. package/build/settings/index.ios.d.ts +8 -0
  83. package/build/settings/index.ios.js +122 -0
  84. package/build/settings/index.js +3 -122
  85. package/build/share/index.android.js +9 -32
  86. package/build/share/index.ios.js +14 -15
  87. package/build/share/shared.d.ts +4 -2
  88. package/build/share/shared.js +7 -10
  89. package/build/status-bar/index.android.js +1 -1
  90. package/build/status-bar/index.ios.js +4 -3
  91. package/build/structured-style.d.ts +0 -9
  92. package/build/structured-style.js +16 -31
  93. package/build/styles.js +3 -6
  94. package/build/surface.d.ts +0 -26
  95. package/build/surface.js +29 -76
  96. package/build/text-input-state.js +4 -8
  97. package/build/toast-android/index.android.d.ts +10 -0
  98. package/build/toast-android/index.android.js +108 -0
  99. package/build/toast-android/index.d.ts +1 -10
  100. package/build/toast-android/index.ios.d.ts +10 -0
  101. package/build/toast-android/index.ios.js +19 -0
  102. package/build/toast-android/index.js +3 -108
  103. package/build/touch-history.js +5 -11
  104. package/build/tree-host.d.ts +0 -270
  105. package/build/tree-host.js +63 -153
  106. package/build/view-config.js +17 -37
  107. package/cpp/SymbioteFabricProps.cpp +204 -120
  108. package/cpp/SymbioteFabricProps.h +5 -0
  109. package/cpp/SymbioteTree.cpp +20 -6
  110. package/package.json +2 -2
@@ -1,28 +1,12 @@
1
- // The TREE HOST seam — the one place JS asks about a tree it does not hold.
2
- //
3
- // `@symbiote-native/engine` keeps NO tree. Every adapter mutation appends an opcode to
4
- // `mutation-buffer.ts`, and turning that buffer into a tree is the HOST's job: native on device, and
5
- // headlessly the TypeScript applier in `@symbiote-native/test-utils`, installed by `installFabric()`.
6
- // That applier lives there for the same reason the fake `nativeFabricUIManager` does — it is a JS
7
- // stand-in for a native thing, and `core/test-utils` is a devDependency of the adapters rather than a
8
- // runtime one, so nothing an app loads contains a JS tree.
9
- //
10
- // Shaped after `fabric.ts`'s slot seam on purpose: a resolver plus a test-time installer.
11
- //
12
- // `undefined` / empty is an ORDINARY answer from every read here, not an error — the same contract
13
- // `committedRecordOf` already carried. A runtime with no host, a node the host has not seen, and a
14
- // genuinely absent value are indistinguishable to a caller, and all three degrade.
1
+ // The tree host seam — the one place JS asks about a tree it does not hold. @symbiote-native/engine
2
+ // keeps no tree: every adapter mutation appends an opcode (mutation-buffer.ts), and turning that
3
+ // into a tree is the host's job — native on device, a TypeScript applier headlessly (test-utils).
15
4
  import { hasChangedSinceCommit, hasPendingOps, noteCommitDrained, recordCommit, takeBatch, } from './mutation-buffer.js';
16
5
  import { nativeEngine } from './native-engine.js';
17
6
  import { takePropStats } from './node.js';
18
- /**
19
- * The census of a tree nobody counted — what a host with no walk of its own answers, and what
20
- * `censusRetainedTree` answers when no host is installed at all.
21
- *
22
- * Not exported from the package barrel: it is a HOST-author constant, and an app reading a census
23
- * asserts against a mounted tree, where a zero from here cannot be mistaken for a zero from a real
24
- * empty tree.
25
- */
7
+ // The census of a tree nobody counted — what a host with no walk answers, and what
8
+ // censusRetainedTree answers with no host installed. Not exported from the package barrel: an app
9
+ // reading a census asserts against a mounted tree, so a zero from here must not read as a real one.
26
10
  export const EMPTY_CENSUS = {
27
11
  nodes: 0,
28
12
  anchors: 0,
@@ -31,45 +15,25 @@ export const EMPTY_CENSUS = {
31
15
  flattenWidths: [],
32
16
  };
33
17
  let host;
34
- /**
35
- * Install the tree host. `installFabric()` (test-utils) calls this with the TypeScript applier; the
36
- * native module installs itself the same way once its bindings carry these reads.
37
- *
38
- * Passing `undefined` uninstalls, so a fixture can prove a path degrades rather than throws.
39
- */
18
+ // Install the tree host. installFabric() (test-utils) calls this with the TypeScript applier; the
19
+ // native module installs itself the same way. Passing undefined uninstalls, so a fixture can prove
20
+ // a path degrades rather than throws.
40
21
  export function setTreeHost(next) {
41
22
  host = next;
42
23
  }
43
- /** The installed host, or `undefined` on a runtime that has none. */
24
+ // The installed host, or undefined on a runtime that has none.
44
25
  export function treeHost() {
45
26
  return host;
46
27
  }
47
- /**
48
- * Push everything recorded since the last apply into the host.
49
- *
50
- * Every READ goes through here first. A reconciler navigates the tree it is mid-way through
51
- * BUILDING — Vue, Solid, Svelte and Angular all ask for a parent or a sibling between mutations and
52
- * long before the commit — so a host that only learned of ops at `completeRoot` would answer about a
53
- * tree several operations stale. The ops are structural, so applying them early costs nothing: only
54
- * `OP_COMMIT` reaches Fabric, and one is recorded solely by `commitSurfaceOps` below, immediately
55
- * before its own drain.
56
- */
57
- /**
58
- * Adapters that COALESCE writes, given the last moment to record what they are holding.
59
- *
60
- * An adapter cannot always publish a write the instant its framework hands it over. Angular's
61
- * styling engine has no whole-value call — `ɵɵstyleMap` delivers one key per `Renderer2.setStyle`
62
- * — so the renderer accumulates the run and writes RN's one `style` prop once. That accumulator has
63
- * to be emptied before anything can observe the tree, and the adapter cannot know when that is: a
64
- * read and a commit both arrive from elsewhere.
65
- *
66
- * Both of them come through `flushOps`, which is what makes this the right seam and a cheap one — it
67
- * is the single door in front of every drain, `commit` included.
68
- *
69
- * NOT A COMMIT HOOK. It fires before every read as well, so a listener must be idempotent and must
70
- * do nothing when it holds nothing. It runs BEFORE the `hasPendingOps` check on purpose: a listener
71
- * holding a write has ops that are not in the buffer yet, so an empty buffer is no reason to skip it.
72
- */
28
+ // Push everything recorded since the last apply into the host. Every read goes through here first
29
+ // — a reconciler navigates the tree mid-build (Vue/Solid/Svelte/Angular all ask for a parent or
30
+ // sibling before commit), so a host only told at completeRoot would answer about a stale tree.
31
+ // Adapters that coalesce writes, given the last moment to record what they're holding. Angular's
32
+ // styling engine has no whole-value call (ɵɵstyleMap delivers one key per setStyle), so the
33
+ // renderer accumulates a run and writes RN's one style prop only once — flushed from here.
34
+ // Not a commit hook: fires before every read too, so a listener must be idempotent and must do
35
+ // nothing when it holds nothing. Runs before the hasPendingOps check, since a listener holding a
36
+ // write has ops not in the buffer yet.
73
37
  const beforeFlush = new Set();
74
38
  export function registerBeforeFlush(listener) {
75
39
  beforeFlush.add(listener);
@@ -79,13 +43,9 @@ export function registerBeforeFlush(listener) {
79
43
  // and ask the same listener for what it has already handed over. One flag rather than per-listener
80
44
  // bookkeeping: the whole set is being drained, and re-entering any of it is the same mistake.
81
45
  let settling = false;
82
- /**
83
- * Collect what the listeners are holding, WITHOUT draining.
84
- *
85
- * Separate from `flushOps` because a read may legitimately decide it needs no drain — `parentOf`
86
- * skips one for a node whose placement is not pending — and skipping the drain must not also skip
87
- * asking. A held write is still a write, and a reader that cannot see it is reading a stale tree.
88
- */
46
+ // Collect what the listeners are holding, without draining. Separate from flushOps because a read
47
+ // may legitimately decide it needs no drain (parentOf skips one for a node whose placement isn't
48
+ // pending), but skipping the drain must not also skip asking a held write.
89
49
  export function settleBeforeFlush() {
90
50
  if (settling || beforeFlush.size === 0)
91
51
  return;
@@ -106,63 +66,38 @@ export function flushOps() {
106
66
  }
107
67
  // Commits this window, for readCommitProfile below.
108
68
  let commits = 0;
109
- // A surface to ask for this window's node counts. They live in C++ and this profile is id-less, so
110
- // the window has to keep a tag to ask WITH, and exactly one: `walkCost_` is file-scope in
111
- // `SymbioteTree.cpp`, shared by every surface and drained on read, so asking a second surface in the
69
+ // A surface to ask for this window's node counts, exactly one: walkCost_ is file-scope in
70
+ // SymbioteTree.cpp, shared by every surface and drained on read, so asking a second surface in the
112
71
  // same window reads zeroes and a sum would be wrong rather than merely redundant.
113
72
  let lastCommittedTag;
114
- /**
115
- * Record a surface's commit and drain the buffer into the host.
116
- *
117
- * A SURFACE IS AN ANCHOR: an anchor is a node whose children belong to its parent's list, and a
118
- * surface is a node whose children belong to the root child set. Same shape, which is why `OP_COMMIT`
119
- * names one node and the host needs no `rootTag -> map`.
120
- *
121
- * `others` is every OTHER live surface, and it exists because the buffer is GLOBAL while a commit
122
- * names ONE root. A framework can mutate a tree that belongs to a different surface than the one
123
- * whose renderer it is holding — a portal, a tunnel, any cross-surface move — and every adapter
124
- * then asks its OWN surface to commit. The ops reach the host either way, so the other surface is
125
- * left correctly updated in the tree and never handed to `completeRoot`: a stale Fabric root that
126
- * nothing will refresh until something unrelated dirties it. Naming every root here is what makes a
127
- * commit mean "flush what changed" rather than "flush the surface I happen to be bound to".
128
- *
129
- * A single-surface app — every example, and the overwhelming case — passes an empty list and the
130
- * behaviour is byte-identical to naming only itself.
131
- */
73
+ // Record a surface's commit and drain the buffer into the host. A surface is an anchor: its
74
+ // children belong to the root child set the same way an anchor's belong to its parent's list,
75
+ // which is why OP_COMMIT names one node and the host needs no rootTag -> map.
76
+ // `others` is every other live surface, needed because the buffer is global while a commit names
77
+ // one root — a portal or cross-surface move can mutate a different surface than the one
78
+ // committing, and naming every root here is what flushes what actually changed.
79
+ // A single-surface app (the overwhelming case) passes an empty list, byte-identical to naming
80
+ // only itself.
132
81
  export function commitSurfaceOps(rootTag, surface, others = []) {
133
82
  commits += 1;
134
- // No host: the ops STAY PENDING. Draining them here would be silent data loss — a surface created
135
- // before a host is installed would have its own `createElement` thrown away, and the next commit
136
- // that DOES reach a host names a node that host never saw. Nothing is red until then, and the
137
- // throw when it comes names the commit rather than the discard.
138
- //
139
- // The commit op is not recorded either, for the same reason: it would sit at the head of the next
140
- // batch, ahead of the creates it depends on.
83
+ // No host: ops stay pending, not drained — a surface created before a host installs would have
84
+ // its own createElement thrown away, and the next commit that does reach a host would name a
85
+ // node that host never saw. The commit op itself is skipped too, for the same reason.
141
86
  if (host === undefined)
142
87
  return;
143
- // NOTHING TO PUBLISH — return before the host is asked to do anything.
144
- //
145
- // The host already declines `completeRoot` when the root child set comes back identical, but it
146
- // decides that AFTER rebuilding the set: every child of the committed surface is visited to
147
- // rediscover that none of them moved. Measured on the reference applier, a commit with nothing
148
- // pending cost 0.0145 ms over 500 rows and 0.0822 over 4 000 — linear in the width of the surface,
149
- // for a commit that publishes nothing.
150
- //
151
- // Who pays it: any frame where a framework re-ran an effect and produced no change, which for a
152
- // reactive adapter is most frames.
153
- //
154
- // The post-commit hooks are NOT affected. `notifyCommitted`, `runPostCommitHooks`,
155
- // `runDeferredAttaches` and the `afterCommit` drain all run in `surface.ts` AFTER this call and
156
- // are deliberately not gated on the commit having made native calls — a fold that strips a prop
157
- // makes its own commit byte-identical, and the hook reacting to that flip must still fire.
88
+ // Nothing to publish — return before the host does anything. The host already declines
89
+ // completeRoot on an identical child set, but only after rebuilding it: every child is
90
+ // revisited to discover none moved, linear cost for a commit that publishes nothing.
91
+ // Post-commit hooks are NOT affected: notifyCommitted/runPostCommitHooks/runDeferredAttaches/
92
+ // afterCommit all run after this call in surface.ts, deliberately ungated on native calls — a
93
+ // fold that strips a prop makes its commit byte-identical, and the hook must still fire.
158
94
  if (!hasChangedSinceCommit())
159
95
  return;
160
96
  for (const [otherTag, otherSurface] of others) {
161
97
  recordCommit(otherTag, otherSurface);
162
98
  }
163
- // `undefined` means this surface no longer OWNS its root — a re-mount on the same rootTag took
164
- // it. Its ops still drain, because a teardown is what carries the removals; completing the root
165
- // would hand Fabric the dead surface's emptied tree over the live one's.
99
+ // undefined means this surface no longer owns its root — a re-mount on the same rootTag took
100
+ // it. Its ops still drain, since a teardown is what carries the removals.
166
101
  if (rootTag !== undefined) {
167
102
  recordCommit(rootTag, surface);
168
103
  lastCommittedTag = rootTag;
@@ -170,20 +105,15 @@ export function commitSurfaceOps(rootTag, surface, others = []) {
170
105
  host.applyOps(takeBatch());
171
106
  noteCommitDrained();
172
107
  }
173
- /**
174
- * NOTE: reading this now DRAINS the surface telemetry too — `nodesCreated` is folded in from
175
- * `readSurfaceTelemetry`, which zeroes on read in C++. A sampler polling this on an interval
176
- * therefore empties what a later `readSurfaceTelemetry` call would have reported.
177
- */
108
+ // Reading this now drains the surface telemetry too — nodesCreated is folded in from
109
+ // readSurfaceTelemetry, which zeroes on read in C++, so a sampler polling this on an interval
110
+ // empties what a later readSurfaceTelemetry call would have reported.
178
111
  export function readCommitProfile() {
179
- // ONE read, not two: `readSurfaceTelemetry` zeroes the C++ accumulator, so asking it twice hands
180
- // the second caller zeroes and the field would read as "the buffer never crossed".
181
- //
182
- // Gated on a commit having LANDED in this window, and not merely on a tag being known. Fabric's
183
- // `TransactionTelemetry::getCommitStartTime` asserts that a commit has started, so asking a
184
- // surface that has not committed since the last read aborts the process in a debug build and
185
- // reads an undefined time point in a release one. The window's own `commits` is the only thing
186
- // that answers "is there anything to ask about" without asking.
112
+ // One read, not two: readSurfaceTelemetry zeroes the C++ accumulator, so asking twice hands the
113
+ // second caller zeroes and the field would read as "the buffer never crossed".
114
+ // Gated on a commit having landed this window, not merely a tag being known: Fabric's telemetry
115
+ // asserts a commit has started, so asking an uncommitted surface aborts in debug and reads
116
+ // garbage in release. `commits` answers "anything to ask about" without asking.
187
117
  const telemetry = commits === 0 || lastCommittedTag === undefined
188
118
  ? undefined
189
119
  : readSurfaceTelemetry(lastCommittedTag);
@@ -198,44 +128,24 @@ export function readCommitProfile() {
198
128
  commits = 0;
199
129
  return snapshot;
200
130
  }
201
- /**
202
- * RN's own commit telemetry for a surface THIS HOST NEED NOT HAVE DRIVEN, or `undefined` when the
203
- * runtime cannot answer.
204
- *
205
- * It exists for one comparison and should not be reached for anything else: `readCommitProfile`
206
- * accumulates inside our own commit, so it can only ever describe a tree we built, and the standing
207
- * open question is whether a tree-wide text re-measure is something we cause or something a Fabric
208
- * commit costs whoever drives it. Point this at a surface React Native's OWN renderer committed and
209
- * the two numbers are directly comparable — same device, same RN, same tree.
210
- *
211
- * `undefined` rather than zeroes on a runtime without the binding, deliberately: zeroes would read
212
- * as "the other renderer measures no text", which is precisely the claim under test.
213
- */
131
+ // RN's own commit telemetry for a surface this host need not have driven, or undefined when the
132
+ // runtime cannot answer. Exists for one comparison: point this at a surface RN's own renderer
133
+ // committed and the two numbers are directly comparable — same device, same RN, same tree.
134
+ // undefined rather than zeroes on a runtime without the binding, deliberately: zeroes would read
135
+ // as "the other renderer measures no text", which is precisely the claim under test.
214
136
  export function readSurfaceTelemetry(surfaceId) {
215
137
  return nativeEngine()?.readSurfaceTelemetry?.(surfaceId);
216
138
  }
217
- /**
218
- * Arm the C++ half's diagnostics (`SymbioteDebug.h`), which `installBindings` already did from
219
- * `DEBUG=1` / `globalThis.__SYMBIOTE_DEBUG__` at install.
220
- *
221
- * This is for the LATER toggle — the runtime escape hatch `debug.ts` documents for hosts where the
222
- * env is not reachable. Without it a `globalThis.__SYMBIOTE_DEBUG__ = true` typed into a running app
223
- * would flip the JS half and silently leave the engine's own half dark, which is the surprise worth
224
- * the six lines.
225
- */
139
+ // Arm the C++ half's diagnostics (SymbioteDebug.h), already done by installBindings from DEBUG=1 /
140
+ // globalThis.__SYMBIOTE_DEBUG__ at install. This is for the LATER toggle: without it, flipping the
141
+ // runtime escape hatch would flip the JS half and silently leave the engine's half dark.
226
142
  export function setNativeDebug(enabled) {
227
143
  nativeEngine()?.setDebugEnabled?.(enabled);
228
144
  }
229
- /**
230
- * Drain what the C++ half has logged since the last call.
231
- *
232
- * The reason those lines are retained at all rather than only written to stderr: a diagnostic nobody
233
- * can assert on is one that rots. This is what lets a test say "the engine warned about that" —
234
- * see `core/engine/cpp/tests/js/native-debug-log.itest.ts`.
235
- *
236
- * An empty array on a runtime without the binding, not `undefined`: the question "what was logged"
237
- * has an honest empty answer, unlike the telemetry read above, where a zero would be a false claim.
238
- */
145
+ // Drain what the C++ half has logged since the last call. Retained rather than only written to
146
+ // stderr so a test can assert "the engine warned about that" (native-debug-log.itest.ts).
147
+ // An empty array on a runtime without the binding, not undefined: "what was logged" has an honest
148
+ // empty answer, unlike the telemetry read above, where a zero would be a false claim.
239
149
  export function takeNativeDebugLog() {
240
150
  return nativeEngine()?.takeDebugLog?.() ?? [];
241
151
  }
@@ -1,16 +1,9 @@
1
- // Per-native-component event declarations: symbiote's slimmed ViewConfigRegistry.
2
- // Mirrors React Native's ViewConfig: each Fabric component declares which event
3
- // names it can emit. This is native-component knowledge (a Switch fires `change`,
4
- // a ScrollView fires `scroll`), shared by every adapter, so it lives here, not in
5
- // any one framework adapter.
6
- //
7
- // Flat-bag adapters (React / Vue / Solid) hand props and handlers mixed together
8
- // and must split them. They consult this registry to tell an event handler
9
- // (`onChange` -> `change`, a declared event) from a native prop that merely looks
10
- // like one (`onTintColor` -> `tintColor`, NOT a declared event -> stays a prop and
11
- // reaches Fabric). Structural adapters (Svelte `addEventListener`, Angular
12
- // `Renderer2.listen`) deliver events pre-separated and call `setEventListener`
13
- // directly, bypassing this entirely.
1
+ // Per-native-component event declarations: symbiote's slimmed ViewConfigRegistry. Mirrors RN's
2
+ // ViewConfig — each Fabric component declares which event names it can emit — shared by every
3
+ // adapter, so it lives here rather than in any one framework adapter.
4
+ // Flat-bag adapters (React/Vue/Solid) hand props and handlers mixed together and must split them:
5
+ // they consult this registry to tell an event handler (onChange -> change) from a native prop that
6
+ // merely looks like one. Structural adapters (Svelte, Angular) deliver events pre-separated.
14
7
  import { isRegisteredEvent } from './registry.js';
15
8
  // Accessibility events from RN's base ViewConfig: every view can emit them.
16
9
  // accessibilityAction fires on iOS + Android; the other three are iOS-only and inert
@@ -21,20 +14,15 @@ const A11Y_EVENTS = [
21
14
  'magicTap',
22
15
  'accessibilityEscape',
23
16
  ];
24
- // Events every view can emit, from RN's base ViewConfig. `press`/`pressIn`/`pressOut`/
25
- // `longPress` are synthesized from the touch stream (see events.ts); `layout` is
26
- // universal; `focus`/`blur` are RN's bubbling focus events declared on the base View
27
- // (ViewPropTypes.js FocusEventProps), so any view can emit them; the accessibility
28
- // events are base too, so they reach every component.
17
+ // Events every view can emit, from RN's base ViewConfig. press/pressIn/pressOut/longPress are
18
+ // synthesized from the touch stream (events.ts); layout is universal; focus/blur are RN's bubbling
19
+ // focus events on the base View, so any view can emit them; the accessibility events are base too.
29
20
  const BASE_EVENTS = [
30
21
  'press',
31
22
  'pressIn',
32
23
  'pressOut',
33
- // Synthesized from the touch stream like its four siblings, and omitted here until 2026-09-02.
34
- // A name the press machine OWNS but the engine does not route is dead: `routeProp` hands an `on*`
35
- // prop to `setEventListener` (and thus to the behavior's stash) only for a registered event, so
36
- // `onPressMove` landed in `node.props` where nothing reads it. The wrapper that used to pass the
37
- // same callback to the machine directly hid this.
24
+ // Must be registered like its four siblings: routeProp hands an on* prop to setEventListener
25
+ // only for a registered event, so an unregistered onPressMove lands in node.props unread.
38
26
  'pressMove',
39
27
  'longPress',
40
28
  'layout',
@@ -42,15 +30,9 @@ const BASE_EVENTS = [
42
30
  'blur',
43
31
  ...A11Y_EVENTS,
44
32
  ];
45
- // An event set is platform-invariant: a text input emits `change` on iOS and Android
46
- // alike; only the native component NAME differs (iOS RCTSinglelineTextInputView vs
47
- // Android AndroidTextInput, iOS Switch vs Android AndroidSwitch). So each primitive's
48
- // events are declared ONCE and keyed under BOTH platform names below. The table is
49
- // consulted by the resolved native name (SymbioteNode.component), and only one
50
- // platform's names ever exist at runtime, so the other platform's keys are inert (no
51
- // Platform.OS branch), the names simply coexist. Missing the Android keys is what made
52
- // onValueChange (and Switch/Modal/RefreshControl events) silently dead on Android: the
53
- // onX prop failed isEventFor, fell to setProp, and no listener was ever registered.
33
+ // An event set is platform-invariant: a text input emits `change` on iOS and Android alike, only
34
+ // the native component name differs. Each primitive's events are declared once and keyed under
35
+ // both platform names — consulted by the resolved name, so only one side is ever live at runtime.
54
36
  const TEXT_INPUT_EVENTS = [
55
37
  'change',
56
38
  'focus',
@@ -67,11 +49,9 @@ const MODAL_EVENTS = [
67
49
  'requestClose',
68
50
  'orientationChange',
69
51
  ];
70
- // A scroll view's events are the same on both axes and both platforms; only the native
71
- // NAME differs (iOS RCTScrollView for both; Android RCTScrollView vertical vs
72
- // AndroidHorizontalScrollView horizontal). Declared once, keyed under each name below. The
73
- // Android horizontal name was missing, so a horizontal FlatList's onScroll never fired and
74
- // its windowing stalled: same failure mode as the text-input keys.
52
+ // A scroll view's events are the same on both axes and both platforms; only the native name
53
+ // differs (Android RCTScrollView vertical vs AndroidHorizontalScrollView horizontal). Declared
54
+ // once, keyed under each name below — same shape as the text-input keys above.
75
55
  const SCROLL_EVENTS = [
76
56
  'scroll',
77
57
  'scrollBeginDrag',