@symbiote-native/solid 2.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.
- package/README.md +155 -81
- package/build/components/flat-list/index.jsx +1 -1
- package/build/components/keyboard-avoiding-view.jsx +6 -2
- package/build/components/pressable-props.d.ts +2 -0
- package/build/components/touchable-native-feedback/touchable-native-feedback-props.d.ts +1 -0
- package/build/components/touchable-props.d.ts +2 -0
- package/build/components/touchable-without-feedback/touchable-without-feedback-props.d.ts +1 -0
- package/build/components/virtualized-list/shared.jsx +9 -5
- package/build/create-portal/index.jsx +2 -2
- package/build/host-instance.js +2 -2
- package/build/modules/animated/index.d.ts +26 -19
- package/build/renderer.js +30 -80
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,75 +1,101 @@
|
|
|
1
1
|
# @symbiote-native/solid
|
|
2
2
|
|
|
3
|
-
SolidJS adapter for SymbioteNative.
|
|
4
|
-
the same `@symbiote-native/engine` every other adapter uses
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+

|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
36
|
+
## Use it
|
|
26
37
|
|
|
27
|
-
Solid
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
For anything the defaults don't cover, drive the lower-level seam directly — the same
|
|
70
|
+
`registerRunnable` seam every other adapter uses:
|
|
71
|
+
|
|
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
|
+
```
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
module.exports = {
|
|
89
|
+
presets: ['module:@react-native/babel-preset', '@symbiote-native/solid/babel-preset'],
|
|
90
|
+
};
|
|
91
|
+
```
|
|
63
92
|
|
|
64
93
|
## Primitives are plain intrinsic tags — `<view>`/`<text>`/`<pressable>`, no component involved
|
|
65
94
|
|
|
66
95
|
An app writes the lowercase tags directly (`view`, `text`, `pressable`, …) — `babel-preset-solid`'s
|
|
67
96
|
own `isComponent` check already treats a lowercase name as an element, so JSX compiles straight to
|
|
68
97
|
`createElement`/`setProp` calls against `symbiote-view` etc., no import and nothing to opt into.
|
|
69
|
-
|
|
70
|
-
`<Pressable>` COMPONENT call site into the same intrinsics (`babel-lower-host-primitives.cjs`,
|
|
71
|
-
deleted 2026-09-08) — there is no component call site left to rewrite. See root `CLAUDE.md`,
|
|
72
|
-
"Where we stand against stock React Native", for the measured device numbers.
|
|
98
|
+
Nothing rewrites the source on the way there, so this adapter carries no Babel plugin of its own.
|
|
73
99
|
|
|
74
100
|
## `./renderer` is a compiler target, not a convenience export
|
|
75
101
|
|
|
@@ -95,46 +121,94 @@ cite the runtime line that forces them.
|
|
|
95
121
|
through those lookups, so hiding a node it inserted itself desyncs its bookkeeping from the tree.
|
|
96
122
|
Anchors are invisible to Fabric, not to traversal.
|
|
97
123
|
|
|
98
|
-
|
|
124
|
+
`tsconfig.json` points `jsxImportSource` at this package — the whole typing setup, no
|
|
125
|
+
`solid-env.d.ts`, no per-file pragma:
|
|
99
126
|
|
|
100
127
|
```jsonc
|
|
101
|
-
|
|
102
|
-
"jsx": "preserve",
|
|
103
|
-
"jsxImportSource": "@symbiote-native/solid"
|
|
128
|
+
{ "jsx": "preserve", "jsxImportSource": "@symbiote-native/solid" }
|
|
104
129
|
```
|
|
105
130
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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.
|
|
110
165
|
|
|
111
|
-
|
|
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.
|
|
112
172
|
|
|
113
|
-
|
|
114
|
-
'JSX.IntrinsicElements'` instead of building fine and failing on device. Augmenting solid-js's
|
|
115
|
-
namespace merges into its HTML/SVG list, so `<div>` stayed legal there. The Svelte adapter needed
|
|
116
|
-
a whole preprocessor for the same guarantee.
|
|
117
|
-
- **JSX children are type-checked at all.** solid-js's `JSX.Element` names the DOM's `Node`, which
|
|
118
|
-
does not resolve without a DOM lib — and `skipLibCheck` swallows that error rather than reporting
|
|
119
|
-
it, leaving `Element` as `any` and every JSX child position unchecked. The visible symptom was
|
|
120
|
-
small (a render-prop parameter needed an explicit annotation); the cause was not.
|
|
173
|
+
---
|
|
121
174
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
```
|
|
127
201
|
|
|
128
|
-
|
|
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).
|
|
129
207
|
|
|
130
|
-
|
|
131
|
-
per-surface renderer through — the active surface is module state, set by `mount`. The Svelte adapter
|
|
132
|
-
reached the same single-root conclusion for its own reasons.
|
|
208
|
+
---
|
|
133
209
|
|
|
134
210
|
## Reference
|
|
135
211
|
|
|
136
|
-
- The canary app: `examples/solid`
|
|
137
|
-
- The engine API this targets: the `symbiote-engine-core` skill, `core/engine/src/node.ts`.
|
|
138
|
-
- Building/porting an adapter: the `symbiote-new-adapter` skill.
|
|
212
|
+
- The canary app: [`examples/solid`](../../examples/solid).
|
|
139
213
|
- The nodeOps table this mirrors: `adapters/vue/src/renderer/index.ts`.
|
|
140
214
|
- The same seam on an ANSI target: `wolf-tui/packages/solid/src/renderer/node-ops.ts`.
|
|
@@ -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
|
}
|
|
@@ -23,7 +23,7 @@
|
|
|
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
28
|
import { withStableKeys } from '../utils/stable-keys.js';
|
|
29
29
|
// Read by KeyboardAvoidingView itself; everything else (the aria/role aliases, accessibility*,
|
|
@@ -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
|
|
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
|
};
|
|
@@ -15,7 +15,9 @@ export interface IPressableProps extends IAccessibilityProps, IAriaProps {
|
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
focusable?: boolean;
|
|
17
17
|
cancelable?: boolean;
|
|
18
|
+
blockNativeResponder?: boolean;
|
|
18
19
|
hitSlop?: IRectOffset;
|
|
20
|
+
testOnly_pressed?: boolean;
|
|
19
21
|
pressRetentionOffset?: IRectOffset;
|
|
20
22
|
unstable_pressDelay?: number;
|
|
21
23
|
minPressDuration?: number;
|
|
@@ -13,6 +13,8 @@ export interface ITouchableOpacityProps extends ITouchableBaseProps {
|
|
|
13
13
|
export interface ITouchableHighlightProps extends ITouchableBaseProps {
|
|
14
14
|
activeOpacity?: number;
|
|
15
15
|
underlayColor?: string;
|
|
16
|
+
touchSoundDisabled?: boolean;
|
|
17
|
+
testOnly_pressed?: boolean;
|
|
16
18
|
onShowUnderlay?: () => void;
|
|
17
19
|
onHideUnderlay?: () => void;
|
|
18
20
|
}
|
|
@@ -3,4 +3,5 @@ import type { JSX } from '../../jsx-runtime';
|
|
|
3
3
|
import type { IPressableProps } from '../pressable-props';
|
|
4
4
|
export interface ITouchableWithoutFeedbackProps extends Omit<IPressableProps, 'style' | 'children'>, IPressTimingProps {
|
|
5
5
|
children?: JSX.Element;
|
|
6
|
+
touchSoundDisabled?: boolean;
|
|
6
7
|
}
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
// the flip is an explicit rebuild boundary (trap 5), with the build untracked so every other prop
|
|
55
55
|
// re-props the SAME nodes through `spread`.
|
|
56
56
|
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, splitProps, untrack, } from 'solid-js';
|
|
57
|
-
import {
|
|
57
|
+
import { DEFAULT_INITIAL_NUM_TO_RENDER, DEFAULT_MAX_TO_RENDER_PER_BATCH, DEFAULT_UPDATE_CELLS_BATCHING_PERIOD, DEFAULT_WINDOW_SIZE, EMPTY_OFFSET, FIRST_INDEX, NO_INDEX, INVERTED_X_STYLE, INVERTED_Y_STYLE, buildListPlan, buildScrollViewHandle, buildViewabilityPairs, nextStickyHeaderY, isSeparatorGapInRange, listEffectSignature, attachStickyScroll, forwardScrollEvent, resolveAccessibilityProps, resolveScrollForwarding, selectScrollIntrinsics, createInitialListState, readLayoutLength, readLayoutOffset, readLayoutNumber, readScrollOffset, reduceList, resolveItemKey, } from '@symbiote-native/components';
|
|
58
58
|
import { AnimatedValue, dlog, event as animatedEvent, getNativeTag, isNativeAnimatedAvailable, isSymbioteNode, whenCommitted, resolveClassName, } from '@symbiote-native/engine';
|
|
59
59
|
import { createElement, insert, insertNode, spread } from '../../renderer.js';
|
|
60
60
|
import { withStableKeys } from '../../utils/stable-keys.js';
|
|
@@ -190,8 +190,8 @@ export function createVirtualizedList() {
|
|
|
190
190
|
maxToRenderPerBatch: props.maxToRenderPerBatch ?? DEFAULT_MAX_TO_RENDER_PER_BATCH,
|
|
191
191
|
updateCellsBatchingPeriod: props.updateCellsBatchingPeriod ??
|
|
192
192
|
DEFAULT_UPDATE_CELLS_BATCHING_PERIOD,
|
|
193
|
-
onEndReachedThreshold: props.onEndReachedThreshold
|
|
194
|
-
onStartReachedThreshold: props.onStartReachedThreshold
|
|
193
|
+
onEndReachedThreshold: props.onEndReachedThreshold,
|
|
194
|
+
onStartReachedThreshold: props.onStartReachedThreshold,
|
|
195
195
|
maintainVisibleContentPosition: props.maintainVisibleContentPosition,
|
|
196
196
|
initialScrollIndex: props.initialScrollIndex,
|
|
197
197
|
onEndReachedActive: props.onEndReached !== undefined,
|
|
@@ -242,8 +242,12 @@ export function createVirtualizedList() {
|
|
|
242
242
|
// lastViewable is folded back only when the fire actually LANDS, so a debounce superseded
|
|
243
243
|
// mid-flight still diffs against the last committed set.
|
|
244
244
|
const fire = () => {
|
|
245
|
-
for (const pair of pairs)
|
|
246
|
-
pair.onViewableItemsChanged(
|
|
245
|
+
for (const pair of pairs) {
|
|
246
|
+
pair.onViewableItemsChanged({
|
|
247
|
+
...info,
|
|
248
|
+
viewabilityConfig: pair.viewabilityConfig,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
247
251
|
dispatch({ kind: 'viewable-fired', map });
|
|
248
252
|
};
|
|
249
253
|
if (viewableTimer !== null) {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
// structurally the same silent no-paint React documents for its own resetAfterCommit. Cross-
|
|
36
36
|
// surface content sharing is a different mechanism entirely: `createTunnel` (../create-tunnel).
|
|
37
37
|
import { createMemo, createRenderEffect, onCleanup } from 'solid-js';
|
|
38
|
-
import { createAnchor, dlog, isSymbioteNode, SymbioteSurface, } from '@symbiote-native/engine';
|
|
38
|
+
import { componentOf, createAnchor, dlog, isSymbioteNode, SymbioteSurface, } from '@symbiote-native/engine';
|
|
39
39
|
import { insert, insertNode, removeNode } from '../renderer.js';
|
|
40
40
|
// The Solid flavour of React's isSymbioteNode guard, and it needs its own wording: React tells the
|
|
41
41
|
// caller to check for a forgotten `.current`, which does not exist here. A Solid `ref` is a plain
|
|
@@ -68,7 +68,7 @@ export function Portal(props) {
|
|
|
68
68
|
// deferred createEffect because a DOM append IS the paint.
|
|
69
69
|
createRenderEffect(() => {
|
|
70
70
|
const to = target();
|
|
71
|
-
dlog(`solid portal -> ${to instanceof SymbioteSurface ? 'surface' : to
|
|
71
|
+
dlog(`solid portal -> ${to instanceof SymbioteSurface ? 'surface' : componentOf(to)}`);
|
|
72
72
|
insertNode(to, host);
|
|
73
73
|
// Runs both on a `mount` change (before the effect re-attaches elsewhere) and on the Portal's
|
|
74
74
|
// own disposal — the content leaves the target either way.
|
package/build/host-instance.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// The node -> tag resolution itself belongs to the engine (getNativeTag, keyed on the raw node in the
|
|
10
10
|
// commit mirror); this only unwraps the Solid-shaped inputs onto it. An uncommitted or unknown input
|
|
11
11
|
// has no tag yet and surfaces as null.
|
|
12
|
-
import { getNativeTag, isSymbioteNode, dlog } from '@symbiote-native/engine';
|
|
12
|
+
import { componentOf, getNativeTag, isSymbioteNode, dlog, } from '@symbiote-native/engine';
|
|
13
13
|
// Solid's counterpart to Vue's isRef branch. Solid has no ref wrapper — `ref={el}` writes the node
|
|
14
14
|
// straight into a variable — but holding that node in a signal is idiomatic, and a signal reads as a
|
|
15
15
|
// zero-argument accessor. Calling it is the only way to unwrap one (there is no isSignal predicate:
|
|
@@ -28,7 +28,7 @@ export function findNodeHandle(componentOrHandle) {
|
|
|
28
28
|
return candidate;
|
|
29
29
|
if (isSymbioteNode(candidate)) {
|
|
30
30
|
const tag = getNativeTag(candidate) ?? null;
|
|
31
|
-
dlog(`findNodeHandle: component=${candidate
|
|
31
|
+
dlog(`findNodeHandle: component=${componentOf(candidate)} tag=${tag}`);
|
|
32
32
|
return tag;
|
|
33
33
|
}
|
|
34
34
|
dlog('findNodeHandle: input did not resolve to a symbiote host node');
|
|
@@ -8,25 +8,7 @@ export declare const Animated: {
|
|
|
8
8
|
Value: typeof AnimatedValue;
|
|
9
9
|
ValueXY: typeof AnimatedValueXY;
|
|
10
10
|
Color: typeof AnimatedColor;
|
|
11
|
-
Easing:
|
|
12
|
-
step0(n: number): number;
|
|
13
|
-
step1(n: number): number;
|
|
14
|
-
linear(t: number): number;
|
|
15
|
-
ease(t: number): number;
|
|
16
|
-
quad(t: number): number;
|
|
17
|
-
cubic(t: number): number;
|
|
18
|
-
poly(n: number): import("@symbiote-native/engine").IEasingFunction;
|
|
19
|
-
sin(t: number): number;
|
|
20
|
-
circle(t: number): number;
|
|
21
|
-
exp(t: number): number;
|
|
22
|
-
elastic(bounciness?: number): import("@symbiote-native/engine").IEasingFunction;
|
|
23
|
-
back(s?: number): import("@symbiote-native/engine").IEasingFunction;
|
|
24
|
-
bounce(t: number): number;
|
|
25
|
-
bezier(x1: number, y1: number, x2: number, y2: number): import("@symbiote-native/engine").IEasingFunction;
|
|
26
|
-
in(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
27
|
-
out(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
28
|
-
inOut(easing: import("@symbiote-native/engine").IEasingFunction): import("@symbiote-native/engine").IEasingFunction;
|
|
29
|
-
};
|
|
11
|
+
Easing: import("@symbiote-native/engine").IEasing;
|
|
30
12
|
timing: typeof timing;
|
|
31
13
|
spring: typeof spring;
|
|
32
14
|
decay: typeof decay;
|
|
@@ -47,4 +29,29 @@ export declare const Animated: {
|
|
|
47
29
|
FlatList: IAnimatedComponent;
|
|
48
30
|
SectionList: IAnimatedComponent;
|
|
49
31
|
createAnimatedComponent: typeof createAnimatedComponent;
|
|
32
|
+
} | {
|
|
33
|
+
Value: typeof AnimatedValue;
|
|
34
|
+
ValueXY: typeof AnimatedValueXY;
|
|
35
|
+
Color: typeof AnimatedColor;
|
|
36
|
+
Easing: import("@symbiote-native/engine").IEasing;
|
|
37
|
+
timing: (value: AnimatedValue, config: import("@symbiote-native/engine").ITimingConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
38
|
+
spring: (value: AnimatedValue, config: import("@symbiote-native/engine").ISpringConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
39
|
+
decay: (_value: AnimatedValue, _config: import("@symbiote-native/engine").IDecayConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
40
|
+
parallel: (animations: import("@symbiote-native/engine").ICompositeAnimation[], _config?: import("@symbiote-native/engine").IParallelConfig) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
41
|
+
sequence: (animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
42
|
+
stagger: (_time: number, animations: import("@symbiote-native/engine").ICompositeAnimation[]) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
43
|
+
loop: (_animation: import("@symbiote-native/engine").ICompositeAnimation) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
44
|
+
delay: (_time: number) => import("@symbiote-native/engine").ICompositeAnimation;
|
|
45
|
+
add: typeof add;
|
|
46
|
+
subtract: typeof subtract;
|
|
47
|
+
multiply: typeof multiply;
|
|
48
|
+
divide: typeof divide;
|
|
49
|
+
modulo: typeof modulo;
|
|
50
|
+
diffClamp: typeof diffClamp;
|
|
51
|
+
event: typeof event;
|
|
52
|
+
forkEvent: typeof forkEvent;
|
|
53
|
+
unforkEvent: typeof unforkEvent;
|
|
54
|
+
FlatList: IAnimatedComponent;
|
|
55
|
+
SectionList: IAnimatedComponent;
|
|
56
|
+
createAnimatedComponent: typeof createAnimatedComponent;
|
|
50
57
|
};
|
package/build/renderer.js
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
// babel.transformSync over representative JSX (elements, dynamic text, <Show>, <For>, spread,
|
|
18
18
|
// ref) rather than read off the docs.
|
|
19
19
|
import { createRenderer } from 'solid-js/universal';
|
|
20
|
-
import { appendChild, createAnchor, createElement as createEngineElement, createRawText, dlog, insertBefore, removeChild as removeEngineChild, routeProp, setNodePressed, setProp as setEngineProp, setText as setEngineText,
|
|
20
|
+
import { appendChild, createAnchor, createElement as createEngineElement, createRawText, dlog, firstChildOf, insertBefore, isRawTextNode, isTextContainer, nextSiblingOf, parentOf, removeChild as removeEngineChild, routeProp, setNodePressed, setProp as setEngineProp, setText as setEngineText, textOf, toPublicInstance, SymbioteSurface, propOf, } from '@symbiote-native/engine';
|
|
21
21
|
import { descriptorFor, TEXT_INPUT_MULTILINE_TAG, } from '@symbiote-native/components';
|
|
22
22
|
function isSurface(node) {
|
|
23
23
|
return node instanceof SymbioteSurface;
|
|
24
24
|
}
|
|
25
25
|
function isRawText(node) {
|
|
26
|
-
return !isSurface(node) && node
|
|
26
|
+
return !isSurface(node) && isRawTextNode(node);
|
|
27
27
|
}
|
|
28
28
|
// One active surface per process. This is FORCED here, not chosen: the compiled-JSX contract above
|
|
29
29
|
// means every mutation arrives through module-level functions with no surface argument, so there is
|
|
@@ -50,11 +50,6 @@ function requestCommit() {
|
|
|
50
50
|
}
|
|
51
51
|
activeSurface.requestCommit();
|
|
52
52
|
}
|
|
53
|
-
function siblingsOf(node) {
|
|
54
|
-
if (node.parent !== undefined)
|
|
55
|
-
return node.parent.children;
|
|
56
|
-
return activeSurface?.children ?? [];
|
|
57
|
-
}
|
|
58
53
|
// Hoisted out of nodeOps and exported because descriptor-to-solid.ts needs the same text-update
|
|
59
54
|
// path for a Descriptor's string child, and createRenderer() only hands back the 11 names the JSX
|
|
60
55
|
// compiler imports — replaceText is not among them. Going through here rather than calling the
|
|
@@ -84,10 +79,7 @@ function asText(value) {
|
|
|
84
79
|
// has to DETACH its anchor host from the portal target on cleanup. Going through here rather than
|
|
85
80
|
// calling the engine's removeChild directly is what keeps the mutation paired with requestCommit(),
|
|
86
81
|
// and keeps the surface-vs-node branch in one place.
|
|
87
|
-
// The
|
|
88
|
-
// rather than the pressed styling. A `<Pressable>` the lowering left as a component still owns its
|
|
89
|
-
// own press machine, and one call here puts the node into its pressed state so `.btn:active`
|
|
90
|
-
// applies exactly as it would on a lowered tag.
|
|
82
|
+
// The `:active` half: one call here puts the node into its pressed state so `.btn:active` applies.
|
|
91
83
|
//
|
|
92
84
|
// Exported paired with requestCommit() for the same reason removeNode below is: the press arrives
|
|
93
85
|
// from a NATIVE EVENT, outside any renderer mutation, so nothing else schedules a commit. React's
|
|
@@ -110,41 +102,23 @@ export function removeNode(parent, node) {
|
|
|
110
102
|
removeEngineChild(parent, node);
|
|
111
103
|
requestCommit();
|
|
112
104
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const [key, fold] of TEXT_FOLDS)
|
|
119
|
-
setEngineProp(node, key, fold(undefined));
|
|
120
|
-
}
|
|
121
|
-
// Seeding at CREATE is not enough, and the gap is device-only. A framework that clears a prop it
|
|
122
|
-
// set earlier hands us an explicit `undefined` at PATCH time, and the default has to come BACK
|
|
123
|
-
// rather than stay cleared — RN treats a missing prop and an explicit undefined alike, and only a
|
|
124
|
-
// literal `false` opts out of allowFontScaling. Two Map lookups on text nodes only, and none at
|
|
125
|
-
// all on a View, so it stays off the hot path.
|
|
126
|
-
function foldTextValue(node, key, value) {
|
|
127
|
-
if (!node.isText)
|
|
128
|
-
return value;
|
|
129
|
-
const fold = TEXT_FOLDS.get(key);
|
|
130
|
-
return fold === undefined ? value : fold(value);
|
|
131
|
-
}
|
|
132
|
-
// The alias fold, at the RENDERER — the only place it can live now that an app writes the tag
|
|
133
|
-
// itself. A bare `<view id="x">` has no wrapper to inherit the fold from, and `id` is a key Fabric
|
|
134
|
-
// does not know, so it committed nothing while the component committed `nativeID`. Measured by
|
|
135
|
-
// mounting both forms and diffing committed key NAMES; totals were identical and said nothing.
|
|
105
|
+
// RN's two Text defaults left this renderer on 2026-09-18, and the per-key FOLD went with them. It
|
|
106
|
+
// was the last and subtlest of the three shapes this file tried: a create-time SEED (gone a month
|
|
107
|
+
// earlier, 6 000 wasted crossings per 1 000-row create), then a substitute-on-`undefined`, then a
|
|
108
|
+
// fold per key — because `?? 'tail'` has to catch a null too, and substituting only on `undefined`
|
|
109
|
+
// meant `<text ellipsizeMode={null}>` committed null, device-only and silent.
|
|
136
110
|
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
111
|
+
// All three were answering a question the layer below now answers for everyone: the rule reads the
|
|
112
|
+
// AUTHORED bag at payload time (`foldTextDefaults`, `SymbioteFabricProps.cpp`), where a null, an
|
|
113
|
+
// explicit `undefined` and an absent prop are all simply "not a value the author chose". The
|
|
114
|
+
// null case that cost this file two revisions is `ellipsize->isNull()` there, in one place, for
|
|
115
|
+
// every adapter.
|
|
116
|
+
// The `id` -> `nativeID` fold and its `aliasedNodes` WeakSet left this file on 2026-09-18. The
|
|
117
|
+
// memory was the right shape and the wrong LAYER: `routeProp` carries it now, so all five adapters
|
|
118
|
+
// resolve the precedence identically instead of three of them doing it three ways.
|
|
144
119
|
// `multiline` selects between TWO Fabric views, so the TAG decides and no prop write moves a node
|
|
145
|
-
// between them.
|
|
146
|
-
//
|
|
147
|
-
// and an author writing the tag by hand has neither, which leaves two silent divergences.
|
|
120
|
+
// between them. An author writing `<text-input multiline>` instead of `<text-input-multiline>` gets
|
|
121
|
+
// the single-line view with a prop no ViewConfig on it declares — two silent divergences.
|
|
148
122
|
//
|
|
149
123
|
// The shared behavior (`core/components/src/behaviors/text-input.ts`) already makes the PAYLOAD
|
|
150
124
|
// follow the tag. The complaint has to live here instead of there: `foldPayload` runs inside the
|
|
@@ -152,32 +126,12 @@ const ALIAS_TO = 'nativeID';
|
|
|
152
126
|
// call site — measured, a test awaiting the mount sees `nothing committed` rather than the error.
|
|
153
127
|
const MULTILINE_PROP = 'multiline';
|
|
154
128
|
function assertMultilineMatchesTag(node, value) {
|
|
155
|
-
if ((value === true) === (node
|
|
129
|
+
if ((value === true) === (propOf(node, MULTILINE_PROP) === true))
|
|
156
130
|
return;
|
|
157
131
|
throw new Error(`multiline={${String(value)}} contradicts the tag: <text-input> and ` +
|
|
158
132
|
`<text-input-multiline> are different Fabric views and no prop write moves a node ` +
|
|
159
133
|
`between them. Pick the tag (a runtime choice needs a <Show> around both).`);
|
|
160
134
|
}
|
|
161
|
-
// RN gives `id` UNCONDITIONAL priority when both are set (View.js:77-79,
|
|
162
|
-
// `processedProps.nativeID = id`), and `foldHostBag` reproduces that by deleting the source key out
|
|
163
|
-
// of a whole bag. This renderer never sees a bag — it folds one key at a time — so precedence would
|
|
164
|
-
// otherwise come out as source order, and `<view id nativeID>` would keep the stale legacy value
|
|
165
|
-
// while `<view nativeID id>` would not. The wrapper hid that; a bare tag does not.
|
|
166
|
-
//
|
|
167
|
-
// So the node remembers that its nativeID came from an `id`, and a later raw `nativeID` write loses
|
|
168
|
-
// to it. Off the hot path in every ordinary case: nothing is touched unless the prop being written
|
|
169
|
-
// is one of these two names.
|
|
170
|
-
const aliasedNodes = new WeakSet();
|
|
171
|
-
function foldAliasKey(node, name, value) {
|
|
172
|
-
if (name === ALIAS_FROM) {
|
|
173
|
-
if (value === undefined)
|
|
174
|
-
aliasedNodes.delete(node);
|
|
175
|
-
else
|
|
176
|
-
aliasedNodes.add(node);
|
|
177
|
-
return ALIAS_TO;
|
|
178
|
-
}
|
|
179
|
-
return name;
|
|
180
|
-
}
|
|
181
135
|
const nodeOps = {
|
|
182
136
|
createElement(tag) {
|
|
183
137
|
const descriptor = descriptorFor(tag);
|
|
@@ -187,8 +141,6 @@ const nodeOps = {
|
|
|
187
141
|
// nothing, and the press machine silently never attaches. Registering under the Fabric name
|
|
188
142
|
// instead would be worse: every plain `View` would get a press machine.
|
|
189
143
|
const node = createEngineElement(descriptor.component, descriptor.isText, tag);
|
|
190
|
-
if (descriptor.isText)
|
|
191
|
-
seedTextDefaults(node);
|
|
192
144
|
// Only the multiline tag is seeded: writing `multiline: false` on the single-line one would add
|
|
193
145
|
// a key the wrapper's payload does not carry, i.e. a divergence in the other direction.
|
|
194
146
|
if (tag === TEXT_INPUT_MULTILINE_TAG)
|
|
@@ -230,13 +182,11 @@ const nodeOps = {
|
|
|
230
182
|
return;
|
|
231
183
|
if (name === MULTILINE_PROP)
|
|
232
184
|
assertMultilineMatchesTag(node, value);
|
|
233
|
-
if (name === ALIAS_TO && aliasedNodes.has(node))
|
|
234
|
-
return;
|
|
235
185
|
// routeProp makes the prop-vs-event decision from the node's ViewConfig (onPress on a View
|
|
236
186
|
// becomes a listener; onTintColor on a Switch stays a prop), and centralizes the class+style
|
|
237
|
-
// merge. Shared with React and Vue — never re-implement an
|
|
238
|
-
// (symbiote-engine-core §2).
|
|
239
|
-
routeProp(node,
|
|
187
|
+
// merge and the `id` -> `nativeID` rename. Shared with React and Vue — never re-implement an
|
|
188
|
+
// `onX` check here (symbiote-engine-core §2).
|
|
189
|
+
routeProp(node, name, value);
|
|
240
190
|
requestCommit();
|
|
241
191
|
},
|
|
242
192
|
insertNode(parent, node, anchor) {
|
|
@@ -246,12 +196,12 @@ const nodeOps = {
|
|
|
246
196
|
if (isSurface(node))
|
|
247
197
|
return;
|
|
248
198
|
const before = anchor !== undefined && !isSurface(anchor) ? anchor : undefined;
|
|
249
|
-
if (isRawText(node) && (isSurface(parent) || !parent
|
|
199
|
+
if (isRawText(node) && (isSurface(parent) || !isTextContainer(parent))) {
|
|
250
200
|
// Fabric has no bare-text host: RCTRawText is only valid as a <Text> child. Reached by a
|
|
251
201
|
// dynamic expression that resolves to a string outside a <Text>, e.g.
|
|
252
202
|
// `<symbiote-view>{label()}</symbiote-view>`. Failing loudly at mount beats building an
|
|
253
203
|
// invalid tree that crashes deeper in native with a far less legible error.
|
|
254
|
-
throw new Error(`Text string "${
|
|
204
|
+
throw new Error(`Text string "${textOf(node) ?? ''}" must be rendered inside a <Text>`);
|
|
255
205
|
}
|
|
256
206
|
if (isSurface(parent)) {
|
|
257
207
|
if (before !== undefined)
|
|
@@ -281,10 +231,10 @@ const nodeOps = {
|
|
|
281
231
|
getParentNode(node) {
|
|
282
232
|
if (isSurface(node))
|
|
283
233
|
return undefined;
|
|
284
|
-
const parent = node
|
|
234
|
+
const parent = parentOf(node) ?? activeSurface;
|
|
285
235
|
if (parent === undefined || isSurface(parent))
|
|
286
236
|
return parent;
|
|
287
|
-
const grandparent = parent
|
|
237
|
+
const grandparent = parentOf(parent);
|
|
288
238
|
if (grandparent !== undefined && grandparent.childHost === parent) {
|
|
289
239
|
return grandparent;
|
|
290
240
|
}
|
|
@@ -307,14 +257,14 @@ const nodeOps = {
|
|
|
307
257
|
getFirstChild(node) {
|
|
308
258
|
if (isSurface(node))
|
|
309
259
|
return node.children[0];
|
|
310
|
-
return (node.childHost ?? node)
|
|
260
|
+
return firstChildOf(node.childHost ?? node);
|
|
311
261
|
},
|
|
312
262
|
getNextSibling(node) {
|
|
313
263
|
if (isSurface(node))
|
|
314
264
|
return undefined;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
return
|
|
265
|
+
// The surface is handed over because a TOP-LEVEL node has no parent to read a sibling list
|
|
266
|
+
// from — the surface owns that list. The engine returns undefined rather than guessing.
|
|
267
|
+
return nextSiblingOf(node, activeSurface);
|
|
318
268
|
},
|
|
319
269
|
};
|
|
320
270
|
// The compiled-JSX contract. See the module header: these names are imported by generated code,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbiote-native/solid",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "SymbioteNative's Solid adapter — a solid-js/universal createRenderer driving real native iOS/Android views through the same engine as every other adapter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -62,19 +62,19 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"babel-preset-solid": "^1.9.12",
|
|
64
64
|
"typescript": "~6.0.0",
|
|
65
|
-
"@symbiote-native/components": "
|
|
65
|
+
"@symbiote-native/components": "3.0.0",
|
|
66
66
|
"@symbiote-native/css-parser": "0.5.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"solid-js": ">=1.9.0",
|
|
70
70
|
"react-native": ">=0.86",
|
|
71
|
-
"@symbiote-native/engine": "^0.
|
|
71
|
+
"@symbiote-native/engine": "^1.0.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@babel/core": "^7.25.2",
|
|
75
75
|
"solid-js": "^1.9.14",
|
|
76
|
-
"@symbiote-native/engine": "0.
|
|
77
|
-
"@symbiote-native/test-utils": "0.3.
|
|
76
|
+
"@symbiote-native/engine": "1.0.0",
|
|
77
|
+
"@symbiote-native/test-utils": "0.3.1"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"typecheck": "tsc --build",
|