@symbiote-native/solid 1.0.0 → 3.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 (107) hide show
  1. package/README.md +159 -86
  2. package/babel-preset.cjs +0 -5
  3. package/build/components/activity-indicator-props.d.ts +5 -0
  4. package/build/components/activity-indicator-props.js +1 -0
  5. package/build/components/button-props.d.ts +5 -0
  6. package/build/components/button-props.js +1 -0
  7. package/build/components/flat-list/index.jsx +4 -4
  8. package/build/components/{image-background.d.ts → image-background-props.d.ts} +2 -3
  9. package/build/components/image-background-props.js +1 -0
  10. package/build/components/image-props.d.ts +5 -0
  11. package/build/components/image-props.js +6 -0
  12. package/build/components/{input-accessory-view.d.ts → input-accessory-view-props.d.ts} +1 -2
  13. package/build/components/input-accessory-view-props.js +5 -0
  14. package/build/components/keyboard-avoiding-view.jsx +15 -6
  15. package/build/components/modal.jsx +6 -6
  16. package/build/components/{pressable.d.ts → pressable-props.d.ts} +10 -7
  17. package/build/components/pressable-props.js +13 -0
  18. package/build/components/{refresh-control.d.ts → refresh-control-props.d.ts} +3 -3
  19. package/build/components/refresh-control-props.js +7 -0
  20. package/build/components/safe-area-view-props.d.ts +10 -0
  21. package/build/components/safe-area-view-props.js +5 -0
  22. package/build/components/{scroll-view/shared.d.ts → scroll-view-props.d.ts} +7 -13
  23. package/build/components/scroll-view-props.js +29 -0
  24. package/build/components/switch-props.d.ts +6 -0
  25. package/build/components/switch-props.js +5 -0
  26. package/build/components/text-input-props.d.ts +8 -0
  27. package/build/components/text-input-props.js +1 -0
  28. package/build/components/{text.d.ts → text-props.d.ts} +3 -3
  29. package/build/components/text-props.js +11 -0
  30. package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +9 -0
  31. package/build/components/touchable-native-feedback/touchable-native-feedback-props.js +1 -0
  32. package/build/components/touchable-props.d.ts +20 -0
  33. package/build/components/touchable-props.js +1 -0
  34. package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +7 -0
  35. package/build/components/touchable-without-feedback/touchable-without-feedback-props.js +1 -0
  36. package/build/components/{view.d.ts → view-props.d.ts} +2 -3
  37. package/build/components/view-props.js +11 -0
  38. package/build/components/virtualized-list/index.android.js +5 -4
  39. package/build/components/virtualized-list/index.ios.js +6 -6
  40. package/build/components/virtualized-list/shared.d.ts +1 -3
  41. package/build/components/virtualized-list/shared.jsx +95 -102
  42. package/build/components/virtualized-list/sticky-header.d.ts +6 -0
  43. package/build/components/{scroll-view → virtualized-list}/sticky-header.jsx +28 -36
  44. package/build/components.d.ts +20 -30
  45. package/build/components.js +6 -33
  46. package/build/create-portal/index.jsx +3 -3
  47. package/build/descriptor-to-solid.js +1 -1
  48. package/build/host-instance.js +2 -2
  49. package/build/index.d.ts +1 -0
  50. package/build/index.js +11 -3
  51. package/build/jsx-runtime.d.ts +39 -2
  52. package/build/modules/animated/index.d.ts +26 -23
  53. package/build/modules/animated/index.js +16 -22
  54. package/build/modules/image.d.ts +3 -0
  55. package/build/modules/image.js +12 -0
  56. package/build/register.d.ts +1 -1
  57. package/build/register.js +12 -28
  58. package/build/renderer.js +74 -61
  59. package/build/utils/stable-keys.d.ts +1 -1
  60. package/build/utils/stable-keys.js +6 -5
  61. package/metro-css-parser.cjs +2 -1
  62. package/package.json +6 -11
  63. package/babel-lower-host-primitives.cjs +0 -323
  64. package/build/components/activity-indicator/index.android.d.ts +0 -2
  65. package/build/components/activity-indicator/index.android.js +0 -11
  66. package/build/components/activity-indicator/index.d.ts +0 -1
  67. package/build/components/activity-indicator/index.ios.d.ts +0 -2
  68. package/build/components/activity-indicator/index.ios.js +0 -10
  69. package/build/components/activity-indicator/index.js +0 -4
  70. package/build/components/activity-indicator/shared.d.ts +0 -8
  71. package/build/components/activity-indicator/shared.js +0 -51
  72. package/build/components/button.d.ts +0 -7
  73. package/build/components/button.jsx +0 -45
  74. package/build/components/image-background.jsx +0 -74
  75. package/build/components/image.d.ts +0 -9
  76. package/build/components/image.js +0 -75
  77. package/build/components/input-accessory-view.jsx +0 -39
  78. package/build/components/pressable.jsx +0 -211
  79. package/build/components/refresh-control.jsx +0 -36
  80. package/build/components/safe-area-view.d.ts +0 -11
  81. package/build/components/safe-area-view.jsx +0 -24
  82. package/build/components/scroll-view/index.android.d.ts +0 -2
  83. package/build/components/scroll-view/index.android.js +0 -11
  84. package/build/components/scroll-view/index.d.ts +0 -2
  85. package/build/components/scroll-view/index.ios.d.ts +0 -2
  86. package/build/components/scroll-view/index.ios.js +0 -8
  87. package/build/components/scroll-view/index.js +0 -9
  88. package/build/components/scroll-view/shared.jsx +0 -334
  89. package/build/components/scroll-view/sticky-header.d.ts +0 -18
  90. package/build/components/switch/index.android.d.ts +0 -2
  91. package/build/components/switch/index.android.js +0 -12
  92. package/build/components/switch/index.d.ts +0 -1
  93. package/build/components/switch/index.ios.d.ts +0 -2
  94. package/build/components/switch/index.ios.js +0 -11
  95. package/build/components/switch/index.js +0 -4
  96. package/build/components/switch/shared.d.ts +0 -11
  97. package/build/components/switch/shared.js +0 -87
  98. package/build/components/text-input.d.ts +0 -10
  99. package/build/components/text-input.js +0 -290
  100. package/build/components/text.jsx +0 -27
  101. package/build/components/touchable/index.d.ts +0 -22
  102. package/build/components/touchable/index.jsx +0 -247
  103. package/build/components/touchable-native-feedback/index.d.ts +0 -16
  104. package/build/components/touchable-native-feedback/index.jsx +0 -68
  105. package/build/components/view.jsx +0 -49
  106. package/build/state-style.d.ts +0 -1
  107. package/build/state-style.js +0 -13
package/README.md CHANGED
@@ -1,76 +1,101 @@
1
1
  # @symbiote-native/solid
2
2
 
3
- SolidJS adapter for SymbioteNative. Compiled Solid JSX drives real native iOS/Android views through
4
- the same `@symbiote-native/engine` every other adapter uses — React's renderer is never in the path.
5
-
6
- **Status: L4 done (parity phase complete).** The renderer seam, `mount`/`unmount`, the
7
- primitives, the stateful touch components, the Touchable family, Button / ImageBackground /
8
- InputAccessoryView, StatusBar and the list family all ship at full parity with the React
9
- reference — off `@symbiote-native/components`, via `descriptorToSolid` where the component has a
10
- shared render fn. The engine-owned runtime modules are re-exported from the barrel. `Portal`
11
- (same-surface content relocation, this adapter's twin of React's `createPortal`) and
12
- `createTunnel` (cross-surface) both ship, built over the universal renderer rather than
13
- `solid-js/web`'s DOM-bound `Portal`/`Dynamic`. `Dynamic` itself stays absent by design — nothing
14
- here needs it, and no other adapter ships a twin for parity to require one. See
15
- `symbiote-new-adapter` §7 for what each layer means.
3
+ The **SolidJS adapter** for [SymbioteNative](../../README.md) — compiled Solid JSX drives real
4
+ native iOS/Android views through the same `@symbiote-native/engine` every other adapter uses,
5
+ with React's renderer never in the path. It targets `createRenderer` from `solid-js/universal`,
6
+ Solid's own custom-renderer API — no shimming of private internals, no compiler running beside
7
+ Metro, no custom Babel transformer path.
8
+
9
+ <div align="center">
10
+
11
+ ![Solid driving real native iOS views through SymbioteNative](../../assets/solid-demo.gif)
12
+
13
+ </div>
14
+
15
+ > New to SymbioteNative? The [root README](../../README.md) has the architecture.
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install @symbiote-native/solid react-native solid-js
23
+ ```
24
+
25
+ `react-native` and `solid-js` stay your app's own top-level dependencies — this package only
26
+ replaces the JS renderer that drives them. `npx @symbiote-native/cli new --framework solid` wires
27
+ the Babel preset, Metro config, and `jsxImportSource` below for a new app; wiring them into an
28
+ existing one still follows [`examples/solid`](../../examples/solid) rather than a generator.
16
29
 
17
- Solid's own control-flow `Switch`/`Match` are NOT re-exported from this package: `Switch` collides
18
- with RN's Switch component, which every adapter must export under that name. Import the control-flow
19
- pair from `solid-js` directly.
30
+ Targets **Solid 1.9**, deliberately not 2.0 (RC as of 2026-08) — 2.0 moves the package to
31
+ `@solidjs/universal`, changes `RendererOptions`, and changes the compiled-output shape. Moving to
32
+ it is a rewrite of `src/renderer.ts`, not a range bump.
20
33
 
21
- The adapter is a regular publishable package (`@symbiote-native/solid` on npm, alongside React,
22
- Vue, Svelte and Angular) — it carries no `"private"` flag, so `changeset publish` and the
23
- pkg.pr.new canary cover it the same as every other adapter.
34
+ ---
24
35
 
25
- ## Why Solid needs no DOM shim and no Metro transformer
36
+ ## Use it
26
37
 
27
- Solid ships an official custom-renderer API, `createRenderer` from `solid-js/universal`. That is
28
- exactly the framework-agnostic seam this project looks for in every framework, so the adapter is
29
- `RendererOptions`' ten methods mapped onto the engine's mutation API — no shimming of private
30
- internals (the Svelte adapter's approach, forced by an unmerged upstream PR), no compiler running
31
- beside Metro (Angular's AOT), and no custom `babelTransformerPath` at all.
38
+ The app is ordinary Solid — the native primitives are plain lowercase intrinsic tags, no import
39
+ needed:
32
40
 
33
- Targets **Solid 1.9**, deliberately not 2.0 (RC as of 2026-08): 2.0 moved the package to
34
- `@solidjs/universal`, changed `RendererOptions` (added `createSentinel`/`cleanupNodes`, gave
35
- `createElement` a staticProps argument) and changed the compiled-output shape (`use()` →
36
- `applyRef`/`ref`, `effect(fn, init)` → `effect(fn, effectFn)`). Moving to it is a rewrite of
37
- `src/renderer.ts`, not a range bump.
41
+ ```jsx
42
+ import { createSignal } from 'solid-js';
38
43
 
39
- ## Wiring an app
44
+ export default function App() {
45
+ const [count, setCount] = createSignal(0);
46
+ return (
47
+ <view style={{ padding: 24 }}>
48
+ <text>Taps: {count()}</text>
49
+ <pressable onPress={() => setCount(c => c + 1)}>
50
+ <text>Tap me</text>
51
+ </pressable>
52
+ </view>
53
+ );
54
+ }
55
+ ```
40
56
 
41
- `babel.config.js` — the preset ships preconfigured, so the app can't get `moduleName` or `generate`
42
- wrong:
57
+ The zero-config entry wires the RN-backed host seams and registers the app in one call — this is
58
+ what [`examples/solid`](../../examples/solid) actually uses:
43
59
 
44
60
  ```js
45
- module.exports = {
46
- presets: [
47
- 'module:@react-native/babel-preset',
48
- // LAST on purpose: Babel applies presets in reverse order, so this runs FIRST and claims the
49
- // JSX before the React Native preset's own React-JSX transform can.
50
- '@symbiote-native/solid/babel-preset',
51
- ],
52
- };
61
+ // index.js
62
+ import { createApp } from '@symbiote-native/solid/bootstrap';
63
+ import App from './App';
64
+ import { name as appName } from './app.json';
65
+
66
+ createApp(App).mount(appName);
53
67
  ```
54
68
 
55
- `metro.config.js` needs `unstable_conditionNames: ['browser']` — `solid-js`'s export map has a `node`
56
- branch pointing at its SSR build, and resolving that into a native bundle is the same failure mode
57
- the Svelte example hit (`lifecycle_function_unavailable` on the first mount).
69
+ For anything the defaults don't cover, drive the lower-level seam directly — the same
70
+ `registerRunnable` seam every other adapter uses:
58
71
 
59
- Solid compiles a component to `function App(props)` — a function with an uppercase name, which is
60
- exactly what react-refresh's `isLikelyComponentType` heuristic looks for. It then tries to patch a
61
- React Fiber tree that does not exist in this path, and the update is swallowed with no error. Force a
62
- full reload instead: `unstable_forceFullRefreshPatterns: [/\.tsx$/]`.
72
+ ```js
73
+ // index.js
74
+ import { AppRegistry } from 'react-native';
75
+ import { mount } from '@symbiote-native/solid';
76
+ import App from './App';
77
+ import { name as appName } from './app.json';
78
+
79
+ AppRegistry.registerRunnable(appName, ({ rootTag }) => {
80
+ mount(rootTag, App);
81
+ });
82
+ ```
63
83
 
64
- ## Host-primitive lowering — `<View>`/`<Text>`/`<Pressable>` compile to intrinsics, not components
84
+ `babel.config.js` needs the adapter's preset LAST — Babel applies presets in reverse order, so
85
+ listing it last runs it first, claiming the JSX before RN's own React-JSX transform can:
65
86
 
66
- `babel-preset-solid` (wired above) runs `babel-lower-host-primitives.cjs` before compiling: a
67
- `<View>`/`<Text>` with no reactive prop, and a `<Pressable>` whose `style`/children don't need a
68
- component instance, rewrite to the engine's intrinsic tags (`symbiote-view`, `symbiote-text`,
69
- `symbiote-pressable`) instead of going through `createComponent` + a props Proxy. Nothing in app
70
- code has to opt in — the preset does it transparently, and a call site that can't be lowered
71
- (a spread, a render-prop child, an unreadable attribute) falls back to the component path
72
- unchanged. See root `CLAUDE.md`, "Where we stand against stock React Native", for the measured
73
- device numbers this bought — do not hand-copy them here, they move with every engine/adapter cut.
87
+ ```js
88
+ module.exports = {
89
+ presets: ['module:@react-native/babel-preset', '@symbiote-native/solid/babel-preset'],
90
+ };
91
+ ```
92
+
93
+ ## Primitives are plain intrinsic tags — `<view>`/`<text>`/`<pressable>`, no component involved
94
+
95
+ An app writes the lowercase tags directly (`view`, `text`, `pressable`, …) — `babel-preset-solid`'s
96
+ own `isComponent` check already treats a lowercase name as an element, so JSX compiles straight to
97
+ `createElement`/`setProp` calls against `symbiote-view` etc., no import and nothing to opt into.
98
+ Nothing rewrites the source on the way there, so this adapter carries no Babel plugin of its own.
74
99
 
75
100
  ## `./renderer` is a compiler target, not a convenience export
76
101
 
@@ -96,46 +121,94 @@ cite the runtime line that forces them.
96
121
  through those lookups, so hiding a node it inserted itself desyncs its bookkeeping from the tree.
97
122
  Anchors are invisible to Fabric, not to traversal.
98
123
 
99
- ## Typing — the app points `jsxImportSource` here, and that is the whole setup
124
+ `tsconfig.json` points `jsxImportSource` at this package — the whole typing setup, no
125
+ `solid-env.d.ts`, no per-file pragma:
100
126
 
101
127
  ```jsonc
102
- // tsconfig.json
103
- "jsx": "preserve",
104
- "jsxImportSource": "@symbiote-native/solid"
128
+ { "jsx": "preserve", "jsxImportSource": "@symbiote-native/solid" }
105
129
  ```
106
130
 
107
- That is all an app needs. No `/// <reference types>` line, no `solid-env.d.ts`, no per-file pragma:
108
- TypeScript resolves the entire JSX namespace from this package's `./jsx-runtime` entry, which is
109
- type-only (the emitted JS is `export {};` and nothing ever imports it — JSX becomes calls on
110
- `./renderer` through babel-preset-solid's `moduleName`, a separate mechanism).
131
+ Solid's own control-flow `Switch`/`Match` are **not** re-exported from this package — `Switch`
132
+ collides with RN's `Switch` component, which every adapter must export under that name. Import
133
+ the control-flow pair from `solid-js` directly.
134
+
135
+ ---
136
+
137
+ ## Parity — and the one gap
138
+
139
+ Solid reaches the same primitives, runtime modules, `Animated` on both drivers, gestures,
140
+ accessibility, and the `VirtualizedList` family as React, Vue, Svelte, and Angular, verified
141
+ on-device on iOS and Android — the root README's ["How Fast, Against Stock React
142
+ Native"](../../README.md#how-fast-against-stock-react-native) section has the measured numbers.
143
+ That parity is **structural, not hand-copied**: the component logic (state machines +
144
+ render functions) is written **once** in `@symbiote-native/components`, and Solid supplies only
145
+ its lifecycle via `descriptorToSolid`. `Portal` (same-surface content relocation) and
146
+ `createTunnel` (cross-surface) both ship, built over the universal renderer rather than
147
+ `solid-js/web`'s DOM-bound equivalents; `Dynamic` stays absent by design — nothing here needs it.
148
+
149
+ The one deliberate gap, same as every other non-React adapter — **third-party React component
150
+ packages** (`@react-native-community/slider` used directly) run only under the React adapter:
151
+ their body calls React hooks off the React dispatcher, which is null under Solid.
152
+ `@symbiote-native/slider` (this repo's own wrapper) _does_ ship a real Solid build through the
153
+ same `createNode`-by-ViewConfig path Solid uses for its own primitives.
154
+
155
+ ---
156
+
157
+ ## A Solid-specific gotcha — Fast Refresh, not Metro's export conditions
158
+
159
+ A compiled Solid component is a function with an uppercase name (`function App(props)`) —
160
+ exactly what react-refresh's `isLikelyComponentType` heuristic accepts as a React component.
161
+ Metro's HMR then tries to patch a React Fiber tree that doesn't exist here (no `react-reconciler`
162
+ in this path), and the update is silently swallowed: no error, no visible change.
163
+ `metro.config.js` needs `unstable_forceFullRefreshPatterns: [/\.tsx$/]` to force every `.tsx`
164
+ edit to a full reload instead.
111
165
 
112
- Two things follow that the earlier `declare module 'solid-js'` shim could not give:
166
+ `solid-js`'s export map does have a `node` branch pointing at its SSR build — the same shape that
167
+ broke the Svelte canary — but Metro's own default condition set (`['react-native']`, contributed
168
+ by `@react-native/metro-config`) never includes `node`, so the SSR branch is unreachable and the
169
+ client build resolves correctly with no extra `unstable_conditionNames` config. Adding one would
170
+ not fix anything that's broken and would actively drop `'react-native'` from the condition set
171
+ (`mergeConfig` replaces the array rather than extending it) — leave it alone.
113
172
 
114
- - **A web tag is a compile error.** `<div>` reports `TS2339: Property 'div' does not exist on type
115
- 'JSX.IntrinsicElements'` instead of building fine and failing on device. Augmenting solid-js's
116
- namespace merges into its HTML/SVG list, so `<div>` stayed legal there. The Svelte adapter needed
117
- a whole preprocessor for the same guarantee.
118
- - **JSX children are type-checked at all.** solid-js's `JSX.Element` names the DOM's `Node`, which
119
- does not resolve without a DOM lib — and `skipLibCheck` swallows that error rather than reporting
120
- it, leaving `Element` as `any` and every JSX child position unchecked. The visible symptom was
121
- small (a render-prop parameter needed an explicit annotation); the cause was not.
173
+ ---
122
174
 
123
- The risk recorded when this was deferred — that our `Element` must line up with what solid-js's own
124
- `<Show>`/`<For>` return — resolves for the same reason: solid's is `any` in the DOM-less programs we
125
- target, so it is assignable to anything of ours. `src/jsx-runtime.test.tsx` pins the runtime half;
126
- the type half rides on `examples/solid`'s typecheck. Full rationale, the measurements behind each
127
- claim, and the one config that would break it: `.claude/rules/solid-jsx-namespace.md`.
175
+ ## Run it
176
+
177
+ [`examples/solid`](../../examples/solid) is a stock React Native 0.86 app — the steps are
178
+ identical to the [React adapter](../react/README.md#run-it), just swap the directory:
179
+
180
+ ```bash
181
+ cd examples/solid
182
+ npm install
183
+ bundle install # first time only — installs CocoaPods itself
184
+ bundle exec pod install # fetch native pods
185
+
186
+ # terminal 1 — Metro (DEBUG=1 turns on diagnostic logs)
187
+ DEBUG=1 npm start --reset-cache
188
+
189
+ # terminal 2 — build + launch
190
+ npm run ios # iOS simulator
191
+ npm run android # Android emulator
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Test it
197
+
198
+ ```bash
199
+ pnpm test # vitest, from the workspace root — headless, fake Fabric slot
200
+ ```
128
201
 
129
- ## Single root per process
202
+ The canary's own real-device verification is the perf benchmark run (see [Run
203
+ it](#run-it) plus the root README's device numbers), not an automated on-device suite —
204
+ `examples/solid` is the one canary without a Detox `e2e/` setup yet, unlike React/Vue/Svelte/
205
+ Angular. Why headless testing comes for free otherwise: see
206
+ [Testing](../../README.md#testing).
130
207
 
131
- Compiled JSX calls module-level functions with no surface argument, so there is nowhere to thread a
132
- per-surface renderer through — the active surface is module state, set by `mount`. The Svelte adapter
133
- reached the same single-root conclusion for its own reasons.
208
+ ---
134
209
 
135
210
  ## Reference
136
211
 
137
- - The canary app: `examples/solid` (its README covers running it and the checks it carries).
138
- - The engine API this targets: the `symbiote-engine-core` skill, `core/engine/src/node.ts`.
139
- - Building/porting an adapter: the `symbiote-new-adapter` skill.
212
+ - The canary app: [`examples/solid`](../../examples/solid).
140
213
  - The nodeOps table this mirrors: `adapters/vue/src/renderer/index.ts`.
141
214
  - The same seam on an ANSI target: `wolf-tui/packages/solid/src/renderer/node-ops.ts`.
package/babel-preset.cjs CHANGED
@@ -24,11 +24,6 @@
24
24
  // React-JSX transform gets a chance to claim the same JSX nodes.
25
25
  module.exports = function symbioteSolidBabelPreset(_api, options = {}) {
26
26
  return {
27
- // Babel runs PLUGINS before PRESETS, which is exactly the order this needs: the lowering
28
- // rewrites <View>/<Text> to their intrinsic tags while they are still JSX, so babel-preset-solid
29
- // then compiles them as elements (createElement + setProp) instead of as components
30
- // (createComponent + a props Proxy). Reversed, there would be nothing left to rewrite.
31
- plugins: [require('./babel-lower-host-primitives.cjs')],
32
27
  presets: [
33
28
  [
34
29
  require('babel-preset-solid'),
@@ -0,0 +1,5 @@
1
+ import type { IActivityIndicatorProps as IActivityIndicatorBaseProps } from '@symbiote-native/components';
2
+ import type { IClassNameValue } from '@symbiote-native/engine';
3
+ export type IActivityIndicatorProps = IActivityIndicatorBaseProps & {
4
+ class?: IClassNameValue;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { IClassNameValue } from '@symbiote-native/engine';
2
+ import type { IButtonProps as IButtonBaseProps } from '@symbiote-native/components';
3
+ export type IButtonProps = IButtonBaseProps & {
4
+ class?: IClassNameValue;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -66,7 +66,7 @@ export function FlatList(props) {
66
66
  : props.columnWrapperStyle,
67
67
  ]);
68
68
  function renderRow(info) {
69
- return (<symbiote-view style={rowStyle()}>
69
+ return (<view style={rowStyle()}>
70
70
  <Index each={info().item.items}>
71
71
  {(item, column) => {
72
72
  // Called ONCE and untracked, the shape VirtualizedList's own cell build uses: a
@@ -79,10 +79,10 @@ export function FlatList(props) {
79
79
  // separators handle — the divider sits between rows, not between columns.
80
80
  separators: info().separators,
81
81
  })));
82
- return (<symbiote-view style={COLUMN_STYLE}>{content}</symbiote-view>);
82
+ return <view style={COLUMN_STYLE}>{content}</view>;
83
83
  }}
84
84
  </Index>
85
- </symbiote-view>);
85
+ </view>);
86
86
  }
87
87
  // The divider between rows shows real items (last of the row above, first of the row below), so
88
88
  // the caller's separator, typed on ItemT, never sees the IRow wrapper.
@@ -113,6 +113,6 @@ export function FlatList(props) {
113
113
  // rebuilds (RN documents changing it live as unsupported — "change the key prop" — which is the
114
114
  // same fresh start) while every other prop change re-props the live list.
115
115
  return (<Show when={isMultiColumn()} fallback={<VirtualizedList {...passthrough} data={props.data} getItem={(_source, index) => props.data[index]} getItemCount={() => props.data.length} renderItem={props.renderItem} keyExtractor={props.keyExtractor} ItemSeparatorComponent={props.ItemSeparatorComponent} onViewableItemsChanged={props.onViewableItemsChanged} viewabilityConfigCallbackPairs={props.viewabilityConfigCallbackPairs}/>}>
116
- <VirtualizedList {...passthrough} data={rows()} getItem={(_source, index) => rows()[index]} getItemCount={() => rows().length} renderItem={renderRow} keyExtractor={rowKeyExtractor} ItemSeparatorComponent={rowSeparatorComponent()} onViewableItemsChanged={rowViewability()} viewabilityConfigCallbackPairs={rowViewabilityPairs()}/>
116
+ <VirtualizedList {...passthrough} data={rows()} getItem={(_source, index) => rows()[index]} getItemCount={() => rows().length} renderItem={renderRow} keyExtractor={(row) => rowKeyExtractor(row, props.keyExtractor)} ItemSeparatorComponent={rowSeparatorComponent()} onViewableItemsChanged={rowViewability()} viewabilityConfigCallbackPairs={rowViewabilityPairs()}/>
117
117
  </Show>);
118
118
  }
@@ -1,10 +1,9 @@
1
+ import type { IImageProps } from '@symbiote-native/components';
2
+ import type { IClassNameValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
1
3
  import type { JSX } from '../jsx-runtime';
2
- import { type IImageProps } from '@symbiote-native/components';
3
- import { type IClassNameValue, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
4
4
  export interface IImageBackgroundProps extends Omit<IImageProps, 'style'> {
5
5
  style?: IStyleProp<IViewStyle>;
6
6
  imageStyle?: IStyleProp<IViewStyle> | string;
7
7
  class?: IClassNameValue;
8
8
  children?: JSX.Element;
9
9
  }
10
- export declare function ImageBackground(props: IImageBackgroundProps): JSX.Element;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { IImageProps as IImageBaseProps } from '@symbiote-native/components';
2
+ import type { IClassNameValue } from '@symbiote-native/engine';
3
+ export type IImageProps = IImageBaseProps & {
4
+ class?: IClassNameValue;
5
+ };
@@ -0,0 +1,6 @@
1
+ // The `<image>` tag's prop surface. `renderImage`'s whole fold — the source / src / srcSet
2
+ // resolution, the width+height fold into style, resizeMode and tintColor read back off the style,
3
+ // alt -> accessibilityLabel, the array shape native expects — runs in the tag's own behavior
4
+ // (`registerImageBehavior`, wired by `../register`), so the wrapper that used to call it is gone.
5
+ // The Image STATICS moved to `../modules/image`: an imperative API with no view.
6
+ export {};
@@ -1,5 +1,5 @@
1
1
  import type { JSX } from '../jsx-runtime';
2
- import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
2
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
3
  import type { IClassNameValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
4
4
  export interface IInputAccessoryViewProps extends IAccessibilityProps, IAriaProps {
5
5
  nativeID?: string;
@@ -8,4 +8,3 @@ export interface IInputAccessoryViewProps extends IAccessibilityProps, IAriaProp
8
8
  class?: IClassNameValue;
9
9
  children?: JSX.Element;
10
10
  }
11
- export declare function InputAccessoryView(props: IInputAccessoryViewProps): JSX.Element;
@@ -0,0 +1,5 @@
1
+ // The `<input-accessory-view>` tag's prop surface. The wrapper that used to carry it is gone —
2
+ // `renderInputAccessoryView`'s nativeID / backgroundColor / style mapping runs in the tag's own
3
+ // behavior (`registerInputAccessoryViewBehavior`, wired by `../register`), and the aria/role fold
4
+ // runs in the engine's `fabricProps`.
5
+ export {};
@@ -23,9 +23,9 @@
23
23
  // keyboard event without re-subscribing (React re-runs its useEffect for that; here the closure
24
24
  // already sees the live value).
25
25
  import { createMemo, createSignal, onCleanup, splitProps } from 'solid-js';
26
- import { computeInset, keyboardAvoidingEventNamesFor, readKeyboardFrame, readLayoutFrame, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
26
+ import { computeInset, configureKeyboardAvoidingAnimation, keyboardAvoidingEventNamesFor, readKeyboardAnimationTiming, readKeyboardFrame, readLayoutFrame, readPrefersCrossFadeTransitions, resolveKeyboardAvoidingLayout, DEFAULT_VERTICAL_OFFSET, } from '@symbiote-native/components';
27
27
  import { Keyboard, Platform, dlog, } from '@symbiote-native/engine';
28
- import { View } from './view.jsx';
28
+ import { withStableKeys } from '../utils/stable-keys.js';
29
29
  // Read by KeyboardAvoidingView itself; everything else (the aria/role aliases, accessibility*,
30
30
  // testID, class) forwards onto the wrapper View, which folds them once through
31
31
  // resolveAccessibilityProps and widens the vanished-key case with withStableKeys.
@@ -62,6 +62,7 @@ export function KeyboardAvoidingView(props) {
62
62
  const onShow = (payload) => {
63
63
  const keyboard = readKeyboardFrame(payload);
64
64
  const offset = local.keyboardVerticalOffset ?? DEFAULT_VERTICAL_OFFSET;
65
+ const previousInset = inset();
65
66
  const next = computeInset(frame, keyboard, offset, {
66
67
  behavior: local.behavior,
67
68
  // The inset CURRENTLY applied (RN's this.state.bottom). Read from the signal at event time so
@@ -69,9 +70,12 @@ export function KeyboardAvoidingView(props) {
69
70
  // frame shorter by exactly that much and core adds it back to cancel the shrink. Without it
70
71
  // each further keyboard event computes a smaller overlap and the view walks back down under
71
72
  // the keyboard. Core gates the correction on 'height'; the other modes ignore the value.
72
- previousInset: inset(),
73
+ previousInset,
73
74
  prefersCrossFadeTransitions,
74
75
  });
76
+ // RN's `_updateBottomIfNecessary` skips the animation when the inset did not change.
77
+ if (next !== previousInset)
78
+ configureKeyboardAvoidingAnimation(readKeyboardAnimationTiming(payload), local.enabled !== false);
75
79
  dlog(`KeyboardAvoidingView show -> inset ${next}`);
76
80
  setInset(next);
77
81
  };
@@ -144,9 +148,14 @@ export function KeyboardAvoidingView(props) {
144
148
  // Last, so KAV's own measure always runs; the caller's handler is invoked from inside it.
145
149
  onLayout: handleLayout,
146
150
  }));
151
+ // The half `View` used to supply, now that the wrapper is a bare tag: `spread` has no removal
152
+ // pass, so a key that stops being emitted (`rest` is a splitProps proxy, and `wrapperStyle` is
153
+ // absent in some behaviors) would keep its last value on the native view forever
154
+ // (.claude/rules/solid-descriptor-bridge.md §1).
155
+ const stableWrapperProps = withStableKeys(wrapperProps);
147
156
  // 'nested' ('position') pushes the children in an inner View by `bottom: inset`; the wrapper modes
148
157
  // ('padding' / 'height' / no behavior) adjust the single wrapper directly.
149
- return (<View {...wrapperProps()}>
150
- {isNested() ? (<View style={innerStyle()}>{local.children}</View>) : (local.children)}
151
- </View>);
158
+ return (<view {...stableWrapperProps()}>
159
+ {isNested() ? (<view style={innerStyle()}>{local.children}</view>) : (local.children)}
160
+ </view>);
152
161
  }
@@ -18,8 +18,8 @@
18
18
  // user's components, which is the case the render-fn boundary rules OUT of core
19
19
  // (.claude/rules/component-render-fn-boundary.md), so the bridge would have nothing to do with the
20
20
  // half that matters and would hand back a root whose container child we would then have to reach
21
- // into to `insert` into. renderModal always paints the SAME two-tag shape (one symbiote-modal host
22
- // wrapping one collapsable symbiote-view container; only prop VALUES vary), so the tags are written
21
+ // into to `insert` into. renderModal always paints the SAME two-tag shape (one modal host
22
+ // wrapping one collapsable view container; only prop VALUES vary), so the tags are written
23
23
  // out and the computed props read off the fixed positions — the same call React's createElement
24
24
  // chain, Vue's h() chain and Svelte's markup all make. `children` then reaches the container
25
25
  // through the compiler's own `insert`, which is the point of view.tsx's header.
@@ -100,7 +100,7 @@ export function Modal(props) {
100
100
  style: local.style,
101
101
  passthrough: resolveAccessibilityProps(rest),
102
102
  });
103
- // root = symbiote-modal > [container]; the user children nest UNDER the container View, never as
103
+ // root = modal > [container]; the user children nest UNDER the container View, never as
104
104
  // a direct sibling of the host (RN's modal content layout, render-modal.ts).
105
105
  const container = () => {
106
106
  const [first] = descriptor().children;
@@ -118,8 +118,8 @@ export function Modal(props) {
118
118
  class: local.class,
119
119
  }));
120
120
  return (<Show when={shouldRender()}>
121
- <symbiote-modal {...hostBag()}>
122
- <symbiote-view {...containerBag()}>{local.children}</symbiote-view>
123
- </symbiote-modal>
121
+ <modal {...hostBag()}>
122
+ <view {...containerBag()}>{local.children}</view>
123
+ </modal>
124
124
  </Show>);
125
125
  }
@@ -1,10 +1,10 @@
1
- import { type Accessor } from 'solid-js';
1
+ import type { Ref } from 'solid-js';
2
2
  import type { JSX } from '../jsx-runtime';
3
- import { type IAccessibilityProps, type IAriaProps, type IPressHandler, type IPressState, type IPressableAndroidRippleConfig, type IRectOffset } from '@symbiote-native/components';
4
- import { type IClassNameValue, type IStyleProp, type IViewStyle } from '@symbiote-native/engine';
3
+ import type { IAccessibilityProps, IAriaProps, IPressHandler, IPressState, IPressableAndroidRippleConfig, IRectOffset } from '@symbiote-native/components';
4
+ import type { IClassNameValue, IStyleProp, IViewStyle } from '@symbiote-native/engine';
5
+ import type { IHostInstance } from '../host-instance';
5
6
  export type { IPressState, IPressableAndroidRippleConfig, } from '@symbiote-native/components';
6
7
  type IPressableStyle = IStyleProp<IViewStyle> | ((state: IPressState) => IStyleProp<IViewStyle>);
7
- type IPressableChildren = JSX.Element | ((state: Accessor<IPressState>) => JSX.Element);
8
8
  export interface IPressableProps extends IAccessibilityProps, IAriaProps {
9
9
  onPress?: IPressHandler;
10
10
  onPressIn?: IPressHandler;
@@ -13,10 +13,14 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
13
13
  onLongPress?: IPressHandler;
14
14
  delayLongPress?: number;
15
15
  disabled?: boolean;
16
+ focusable?: boolean;
16
17
  cancelable?: boolean;
18
+ blockNativeResponder?: boolean;
17
19
  hitSlop?: IRectOffset;
20
+ testOnly_pressed?: boolean;
18
21
  pressRetentionOffset?: IRectOffset;
19
22
  unstable_pressDelay?: number;
23
+ minPressDuration?: number;
20
24
  android_ripple?: IPressableAndroidRippleConfig;
21
25
  android_disableSound?: boolean;
22
26
  onHoverIn?: IPressHandler;
@@ -25,7 +29,6 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
25
29
  delayHoverOut?: number;
26
30
  style?: IPressableStyle;
27
31
  class?: IClassNameValue;
28
- children?: IPressableChildren;
32
+ ref?: Ref<IHostInstance>;
33
+ children?: JSX.Element;
29
34
  }
30
- export declare function Pressable(props: IPressableProps): JSX.Element;
31
- export declare function TouchablePressable(props: IPressableProps): JSX.Element;
@@ -0,0 +1,13 @@
1
+ // The `<pressable>` tag's prop surface. The press machine — timers, the retention region, the
2
+ // long-press and hover legs, the `disabled`/`focusable`/`accessible` folds and the Android ripple —
3
+ // lives on the engine node as `core/components/src/behaviors/pressable.ts`, wired by
4
+ // `../register`.
5
+ //
6
+ // THERE IS NO RENDER-PROP CHILD, and that is the one surface a tag cannot carry — same fact as
7
+ // Vue's scoped slot (`adapters/vue/src/components/pressable-props.ts`), Solid's own version of it.
8
+ // Press state lives on the engine node and never crosses back into Solid's reactivity as an
9
+ // accessor, so `{state => …}` has no channel any more. A `style` CALLBACK still works — the engine
10
+ // resolves it at both values of `pressed` (`isStyleCallback`, `core/engine/src/node.ts`) — and a
11
+ // `:active` CSS rule is cheaper still. A CHILD that needs the state has to be given it: mirror
12
+ // `onPressIn`/`onPressOut` into a local signal, which is what any app styling a descendant does.
13
+ export {};
@@ -1,8 +1,9 @@
1
1
  import type { JSX } from '../jsx-runtime';
2
- import { type IAccessibilityProps, type IAriaProps } from '@symbiote-native/components';
3
- import { type IClassNameValue } from '@symbiote-native/engine';
2
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
+ import type { IClassNameValue } from '@symbiote-native/engine';
4
4
  export interface IRefreshControlProps extends IAccessibilityProps, IAriaProps {
5
5
  refreshing: boolean;
6
+ id?: string;
6
7
  onRefresh?: () => void | Promise<void>;
7
8
  tintColor?: string;
8
9
  title?: string;
@@ -15,4 +16,3 @@ export interface IRefreshControlProps extends IAccessibilityProps, IAriaProps {
15
16
  class?: IClassNameValue;
16
17
  children?: JSX.Element;
17
18
  }
18
- export declare function RefreshControl(props: IRefreshControlProps): JSX.Element;
@@ -0,0 +1,7 @@
1
+ // The `<refresh-control>` tag's prop surface. On iOS the tag resolves to Fabric's PullToRefreshView,
2
+ // which lives INSIDE a scroll view as a childless sibling before the content container; on Android it
3
+ // is AndroidSwipeRefreshLayout and WRAPS the scroll view instead. Which shape is built is the scroll
4
+ // view's job, and the controlled-spinner handshake — mirror what native reported, command the value
5
+ // back down when the app's `refreshing` disagrees — is the tag's own behavior
6
+ // (`registerRefreshControlBehavior`, wired by `../register`).
7
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { JSX } from '../jsx-runtime';
2
+ import type { IAccessibilityProps, IAriaProps } from '@symbiote-native/components';
3
+ import type { IClassNameValue, IStyleProp, ISymbioteEvent, IViewStyle } from '@symbiote-native/engine';
4
+ export interface ISafeAreaViewProps extends IAccessibilityProps, IAriaProps {
5
+ style?: IStyleProp<IViewStyle>;
6
+ id?: string;
7
+ class?: IClassNameValue;
8
+ onLayout?: (event: ISymbioteEvent) => void;
9
+ children?: JSX.Element;
10
+ }
@@ -0,0 +1,5 @@
1
+ // The `<safe-area-view>` tag's prop surface. The wrapper forwarded a bag and nothing else — the
2
+ // host does all the inset math, there is no JS-side layout anywhere in this project, and the
3
+ // `id -> nativeID` alias plus the aria/role fold both run below the adapter (`foldHostBag` and the
4
+ // engine's `fabricProps`). So there was nothing left for a component to hold.
5
+ export {};